/* $Id: pmkinstall.c 1462 2005-08-21 08:52:06Z mipsator $ */

/*
 * Copyright (c) 2003-2005 Damien Couderc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *    - Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    - Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *      disclaimer in the documentation and/or other materials provided
 *      with the distribution.
 *    - Neither the name of the copyright holder(s) nor the names of its
 *      contributors may be used to endorse or promote products derived
 *      from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */


#include <sys/stat.h>
/* include it first as if it was <sys/types.h> - this will avoid errors */
#include "compat/pmk_sys_types.h"

#include <errno.h>
#include <grp.h>
#include <limits.h>
#include <pwd.h>
#include <stdlib.h>

#include "compat/pmk_ctype.h"
#include "compat/pmk_libgen.h"
#include "compat/pmk_stdbool.h"
#include "compat/pmk_stdio.h"
#include "compat/pmk_string.h"
#include "compat/pmk_unistd.h"
#include "common.h"
#include "pathtools.h"
#include "pmkinstall.h"
#include "premake.h"

/*#define INST_DEBUG	1*/


extern char	*optarg;
extern int	 optind;
#ifndef errno
extern int	 errno;
#endif

char		*bsfx = DEFAULT_BACKUP_SFX; /* backup extension */


/*
	strip

	file : file to strip

	return : nothing at the moment :)

	NOTE: could use BIN_STRIP from pmk.conf
*/

void strip(char *file) {
	char	*s_path,
		 cmd[MAXPATHLEN];

	s_path = getenv(STRIP_ENV_NAME);
	if (s_path == NULL) {
		errorf("%s env variable not set, skipping.", STRIP_ENV_NAME);
	} else {
		/* build the command */
		if (snprintf_b(cmd, sizeof(cmd), "%s %s", s_path, file) == false) {
			errorf("failed to build strip command, skipping.");
		} else {
			/* stripping */
			if (system(cmd) != EXIT_SUCCESS) {
				errorf("strip failed.");
			}
		}
	}
}

/*
	convert symbolic value to octal

	mstr : symbolic value string
	pmode : resulting mode

	return : boolean

*/

bool symbolic_to_octal_mode(char *mstr, mode_t *pmode) {
	bool	 do_loop = true;
	char	*pstr,
		 op = CHAR_EOS;
	mode_t	 mask = 0,
		 perm = 0;

	pstr = mstr;

	/* who symbols */
	while (do_loop == true) {
#ifdef INST_DEBUG
		debugf("char = '%c'", *pstr);
#endif
		switch (*pstr) {
			case 'a':
				mask = mask | FULL_MASK;
				break;

			case 'g':
				mask = mask | GRP_MASK;
				break;

			case 'o':
				mask = mask | OTH_MASK;
				break;

			case 'u':
				mask = mask | USR_MASK;
				break;

			default:
				/* separator found */
				op = *pstr;
				do_loop = false;
				break;
		}
		pstr++;
#ifdef INST_DEBUG
		debugf("mask = %o", mask);
#endif
	}

	/* check if operator is valid */
	if ((op != '+') && (op != '-') && (op != '=')) {
		errorf("syntax error in symbolic mode '%s'", mstr);
		return(false);
	}

	/* perm symbols */
	do_loop = true;
	while (do_loop == true) {
		switch (*pstr) {
			case 'r':
				perm = perm | R_PERM;
				break;

			case 'w':
				perm = perm | W_PERM;
				break;

			case 'x':
				perm = perm | X_PERM;
				break;

			case 's':
				perm = perm | S_PERM;
				break;

			case CHAR_EOS:
				/* reached end of string */
				do_loop = false;
				break;

			default:
				/* unknown perm */
				errorf("unknown permission '%c'.", *pstr);
				return(false);
				break;
		}
		pstr++;
	}
#ifdef INST_DEBUG
	debugf("perm = %o", perm);
#endif

	/* apply operator */
	switch (op) {
		case '+':
			if (perm == 0) {
				/* no perms given */
				*pmode = 0;
			} else {
				if (mask == 0) {
					/* mask not set, give perms to all */
					mask = FULL_MASK;
				}
				*pmode = mask & perm;
			}
			break;

		case '-':
			/* no perms */
			*pmode = 0;
			break;

		case '=':
			if (mask == 0) {
				*pmode = perm;
			} else {
				*pmode = 0;
			}
			break;
	}

	return(true);
}

/*
	check_mode

	mstr : mode string
	pmode : resulting mode

	return : boolean

*/

bool check_mode(char *mstr, mode_t *pmode) {
	unsigned long	 mode = 0;

	if (mstr == NULL)
		return(false);

	if (isdigit(*mstr) != 0) {
		/* octal value */
		if (str_to_ulong(mstr, 8, &mode) == false) {
			return(false); /* unable to get numerical value */
		}
	} else {
		/* symbolic value */
		if (symbolic_to_octal_mode(mstr, (mode_t *)&mode) == false) {
			return(false);
		};
	}

	/* set mode */
	*pmode = (mode_t) mode;

	return(true);
}

/*
	process owner string

	pstr: owner string
	puid: storage of uid

	returns: true on success else false
*/

bool process_owner(char *pstr, uid_t *puid) {
	struct passwd	*pp = NULL;
	unsigned long	 ul;

	if (isdigit(*pstr) == 0) {
		/* user name */
		pp = getpwnam(pstr);
		if (pp == NULL) {
			errorf("invalid user name.");
			return(false);
		} else {
			*puid = pp->pw_uid;
		}
	} else {
		/* uid */
		if (str_to_ulong(pstr, 10, &ul) == false) {
			errorf("cannot get numerical value of '%s'.", pstr);
			return(false);
		}
		*puid = (uid_t) ul;
	}

	return(true);
}

/*
	process owner string

	pstr: owner string
	puid: storage of uid

	returns: true on success else false
*/

bool process_group(char *pstr, gid_t *pgid) {
	struct group	*pg = NULL;
	unsigned long	 ul;

	if (isdigit(*pstr) == 0) {
		/* group name */
		pg = getgrnam(pstr);
		if (pg == NULL) {
			errorf("invalid group name.");
			return(false);
		}
		*pgid = pg->gr_gid;
	} else {
		/* gid */
		if (str_to_ulong(pstr, 10, &ul) == false) {
			errorf("cannot get numerical value of '%s'.", pstr);
			return(false);
		}
		*pgid = (gid_t) ul;
	}

	return(true);
}

/*
	create directory

	psrc: source directory
	buffer: destination buffer
	bsize: buffer size

	returns: true on success else false
*/

bool create_directory(char *psrc, char *buffer, size_t bsize) {

#ifdef INST_DEBUG
	debugf("create dir '%s'", psrc);
#endif

	/* create path */
	if (*psrc == CHAR_SEP) {
	/* absolute path, copy */
		if (strlcpy_b(buffer, psrc, bsize) == false) {
			errorf("overflow in directory creation (abs. path).");
			return(false);
		}
	} else {
		/* relative, getting current directory */
		if (getcwd(buffer, bsize) == NULL) {
			errorf("unable to get current directory");
			return(false);
		}
		/* appending path */
		strlcat(buffer, STR_SEP, bsize); /* no check */
		if (strlcat_b(buffer, psrc, bsize) == false) {
			errorf("overflow in directory creation (rel. path).");
			return(false);
		}
	}
#ifdef INST_DEBUG
	debugf("dir = '%s'", buffer);
#endif

	if (makepath(buffer, S_IRWXU | S_IRWXG | S_IRWXO) == false) {
		errorf("cannot create directory.");
		return(false);
	}

	return(true);
}

/*
	build destination target

	psrc: source file
	pdst: destination directory
	buffer: destination buffer
	bsize: buffer size

	returns: true on success else false
*/

bool build_destination(char *psrc, char *pdst, char *buffer, size_t bsize) {
	char	*pstr;

	strlcpy(buffer, pdst, bsize); /* no check */
	if (strlcat_b(buffer, STR_SEP, bsize) == false) {
		errorf("overflow detected in destination.");
		return(false);
	}

	pstr = basename(psrc);
	if (pstr == NULL) {
		errorf("unable to get basename of source.");
		return(false);
	}

	if (strlcat_b(buffer, pstr, bsize) == false) {
		errorf("overflow detected in destination.");
		return(false);
	}

	return(true);
}

/*
	usage
*/

void usage(void) {
	fprintf(stderr, "usage: pmkinstall [-cs] [-g group] [-m mode] "
		"[-o owner] file1 file2\n");
	fprintf(stderr, "       pmkinstall [-cs] [-g group] [-m mode] "
		"[-o owner] file1 ... fileN directory\n");
	fprintf(stderr, "       pmkinstall -d [-g group] [-m mode] "
		"[-o owner] directory ...\n");
	fprintf(stderr, "       pmkinstall -v\n");
	fprintf(stderr, "       pmkinstall -h\n");

	exit(EXIT_FAILURE);
}

/*
	main
*/

int main(int argc, char *argv[]) {
	struct stat	 sb;
	bool		 create_dir = false,
			 do_backup = false,
			 do_chown = false,
			 do_strip = false,
			 go_exit = false;
	char		*gstr = NULL,
			*ostr = NULL,
			*src,
			*dst,
			 dir[MAXPATHLEN],
			 backup[MAXPATHLEN];
	gid_t		 gid = (gid_t) -1;
	int		 chr;
	mode_t		 mode = DEFAULT_MODE,
			 tmode;
	uid_t		 uid = (uid_t) -1;
	unsigned int	 src_idx,
			 last_idx;

	while (go_exit == false) {
		chr = getopt(argc, argv, "B:bcdg:hm:o:stv");
		if (chr == -1) {
			go_exit = true;
		} else {
			switch (chr) {
				case 'B' :
					bsfx = optarg;
#ifdef INST_DEBUG
					debugf("backup suffix = %s", bsfx);
#endif
					break;

				case 'b' :
					/* backup */
					do_backup = true;
					break;

				case 'c' :
					/* default behavior, do nothing (backwards compat.) */
					break;

				case 'd' :
					/* create directories */
					create_dir = true;
					break;

				case 'g' :
					/* specify group */
					gstr = optarg;
#ifdef INST_DEBUG
					debugf("gstr = %s", gstr);
#endif
					break;

				case 'm' :
					/* specify mode */
					if (check_mode(optarg, &mode) == false)
						exit(EXIT_FAILURE);
#ifdef INST_DEBUG
					debugf("mode = %o", mode);
#endif
					break;

				case 'o' :
					/* specifiy owner */
					ostr = optarg;
					break;

				case 's' :
					/* strip */
					do_strip = true;
					break;

				case 't' :
					/* autconf shit, only for compat */
					break;

				case 'v' :
					/* display version */
					fprintf(stdout, "%s\n", PREMAKE_VERSION);
					exit(EXIT_SUCCESS);
					break;

				case 'h' :
				case '?' :
				default :
					usage();
					break;
			}
		}
	}

	argc = argc - optind;
	argv = argv + optind;

	/* only directory creation allow 1 parameter */
	if ((argc < 2) && (create_dir == false)) {
		/* not enough parameters */
		usage();
	}

	/* check if an owner has been provided */
	if (ostr != NULL) {
		if (process_owner(ostr, &uid) == false) {
			/* error message already done */
			exit(EXIT_FAILURE);
		}
#ifdef INST_DEBUG
		debugf("uid = %d", uid);
#endif
		do_chown = true;
	}

	/* if a group has been provided */
	if (gstr != NULL) {
		if (process_group(gstr, &gid) == false) {
			/* error message already done */
			exit(EXIT_FAILURE);
		}
#ifdef INST_DEBUG
		debugf("gid = %d", gid);
#endif
		do_chown = true;
	}

	/* set last_idx respectively */
	if (create_dir == true) {
		last_idx = argc;
	} else {
		last_idx = argc - 1;
	}

	/* process each source */
	for (src_idx = 0 ; src_idx < last_idx ; src_idx++) {
		src = argv[src_idx];

		if (create_dir == true) {
			/* create directory */
			if (create_directory(src, dir, sizeof(dir)) == false) {
				/* error message already done */
				exit(EXIT_FAILURE);
			}

			/* set dst for further operations */
			dst = dir;
		} else {
			/* install file	*/

#ifdef INST_DEBUG
			debugf("process install of '%s'", src);
#endif

			/*  set dst */
			dst = argv[last_idx];

#ifdef INST_DEBUG
			debugf("initial dst = '%s'", dst);
#endif

			/* check if destination is a directory */
			if (stat(dst, &sb) == 0) {
				/* many checks to do (is a directory, etc ...) */
				tmode = sb.st_mode & S_IFDIR;
				if (tmode == 0) {
					/* not a directory */
					unlink(dst);
				} else {
					/* build destination */
					if (build_destination(src, dst, dir,
							sizeof(dir)) == false) {
						/* error message already done */
						exit(EXIT_FAILURE);
					}

					/* set dst for further operations */
					dst = dir;
				}
			}


			/* backup, check dst again as it could have been modified */
			if ((do_backup == true) && (stat(dst, &sb) == 0)) {
#ifdef INST_DEBUG
				debugf("backup of '%s'", dst);
#endif
				if (snprintf_b(backup, sizeof(backup),
						"%s%s", dst, bsfx) == false) {
					errorf("failed to generate backup name for '%s': %s.",
							dst, strerror(errno));
					/* XXX exit ? */
				}

				if (rename(dst, backup) != 0) {
					errorf("failed to backup %s: %s.",
							dst, strerror(errno));
				}
			}

#ifdef INST_DEBUG
			debugf("copy to '%s'", dst);
#endif
			/* copy file */
			if (fcopy(src, dst, mode) == false) {
				/* copy failed, error message already displayed */
				exit(EXIT_FAILURE);
			}

			/* strip binary if asked */
			if (do_strip == true) {
				strip(dst);
			}
		}

		/* change owner and group */
		if (do_chown == true) {
#ifdef INST_DEBUG
			debugf("doing chown('%s', %d, %d)", dst, uid, gid);
#endif
			if (chown(dst, uid, gid) != 0) {
				errorf("chown failed : %s.", strerror(errno));
				exit(EXIT_FAILURE);
			}
		}

		/* change perms (must follow chown that can change perms) */
		if (chmod(dst, mode) == -1) {
#ifdef INST_DEBUG
			debugf("chmod('%s', %o)", dst, mode);
#endif
			errorf("chmod failed : %s.", strerror(errno));
			exit(EXIT_FAILURE);
		}
	}

	return(0);
}


syntax highlighted by Code2HTML, v. 0.9.1