/* $Id: pmksetup.c 1912 2006-10-14 22:31:11Z coudercd $ */

/*
 * Copyright (c) 2003-2006 Damien Couderc
 * Copyright (c) 2003-2004 Xavier Santolaria
 * 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 it first as if it was <sys/types.h> - this will avoid errors */
#include "compat/pmk_sys_types.h"
#include <sys/utsname.h>
#include <sys/wait.h>

#include <dirent.h>
#include <errno.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdlib.h>

#include "compat/pmk_stdio.h"
#include "compat/pmk_string.h"
#include "compat/pmk_unistd.h"
#include "common.h"
#include "detect_cpu.h"
#include "dynarray.h"
#include "parse.h"
#include "pmksetup.h"


/*#define PMKSETUP_DEBUG 1*/
/*#define WITHOUT_FORK 1*/

/********************
 * global variables *
 ***********************************************************************/

extern int	 optind;

FILE		*sfp;			/* scratch file pointer */
char		 sfn[MAXPATHLEN];	/* scratch file name */

htable		*ht;
int			 verbose_flag = 0;	/* -V option at the cmd-line */

hpair		 predef[] = {
				{PMKCONF_MISC_SYSCONFDIR,	SYSCONFDIR},
				{PMKCONF_MISC_PREFIX,		"/usr/local"},
				{PMKCONF_PATH_INC,		"/usr/include"},
				{PMKCONF_PATH_LIB,		"/usr/lib"}
};
int			 nbpredef = sizeof(predef) / sizeof(hpair);


/*************
 * functions *
 ***********************************************************************/

/*****************
 * record_data() *
 ***********************************************************************
 DESCR
	record data

 IN
	pht: hash table
	key: record name
	opchar: operation char
		PMKSTP_REC_REMV => remove
		PMKSTP_REC_UPDT => add/update
	value: record data

 OUT
	boolean
 ***********************************************************************/

bool record_data(htable *pht, char *key, char opchar, char *value) {
	prsopt	*ppo;

	switch (opchar) {
		case PMKSTP_REC_REMV :
			/* delete record */
			ppo = prsopt_init_adv(key, opchar, NULL);
			if (ppo == NULL) {
				return(false);
			}
			break;

		case PMKSTP_REC_UPDT :
			/* update record */
			ppo = prsopt_init_adv(key, opchar, value);
			if (ppo == NULL) {
				return(false);
			}
			break;

		default :
			/* unknown operation */
#ifdef PMKSETUP_DEBUG
			debugf("unknown '%c' operator in record_data.",
								opchar);
#endif
			return(false);
	}

	if (hash_update(pht, ppo->key, ppo) == HASH_ADD_FAIL) {
		prsopt_destroy(ppo);
		return(false);
	}

	return(true);
}


/*****************
 * gather_data() *
 ***********************************************************************
 DESCR
	Gathering of system data, predefinied data, ...

 IN
	pht: storage hash table

 OUT
	boolean
 ***********************************************************************/

bool gather_data(htable *pht) {
	printf("==> Looking for default parameters...\n");

	/* gather env variables */
	if (get_env_vars(pht) == false) {
		errorf("failed to gather env variables.");
		return(false);
	}

 	/* gather env variables and look for specific binaries */
	if (get_binaries(pht) == false) {
		errorf("failed to locate binaries.");
		return(false);
	}

	/* set predifined variables */
	if (predef_vars(pht) == false) {
		errorf("predefined variables.");
		return(false);
	}

	check_libpath(pht);

	/* check byte order */
	if (byte_order_check(pht) == false) {
		errorf("failure in byte order check.");
		return(false);
	}

	/* try to detect cpu family and specific data */
	if (get_cpu_data(pht) == false) {
		errorf("failure in cpu detection.");
		return(false);
	}

	if (check_echo(pht) == false) {
		errorf("failure in echo check.");
		return(false);
	}

	return(true);
}


/********************
 * write_new_data() *
 ***********************************************************************
 DESCR
	Write remaining data

 IN
	pht: hash table

 OUT
 	boolean
 ***********************************************************************/

bool write_new_data(htable *pht) {
	char			*val;
	hkeys			*phk;
	prsopt			*ppo;
	unsigned int	 i;

	phk = hash_keys_sorted(pht);
	if (phk == NULL) {
		verbosef("Nothing to merge.");
		return(true);
	}

	/* processing remaining keys */
	for(i = 0 ; i < phk->nkey ; i++) {
#ifdef PMKSETUP_DEBUG
		debugf("write_new_data: processing '%s'", phk->keys[i]);
#endif
		ppo = hash_get(pht, phk->keys[i]);
		if (ppo == NULL) {
			errorf("unable to get '%s' record.", phk->keys[i]);
			return(false);
		}

		/* check if this key is marked for being removed */
		if (ppo->opchar == PMKSTP_REC_REMV) {
#ifdef PMKSETUP_DEBUG
			debugf("write_new_data: skipping '%s', remove record.",
								phk->keys[i]);
#endif
			continue;
		}

		/* add value */
		val = po_get_str(ppo->value);
		if (val == NULL) {
			errorf("'%s' key has an empty value.", phk->keys[i]);
			return(false);
		}

		fprintf(sfp, PMKSTP_WRITE_FORMAT, phk->keys[i],
						CHAR_ASSIGN_UPDATE, val);
	}

	hash_free_hkeys(phk);

	return(true);
}


/***************
 * check_opt() *
 ***********************************************************************
 DESCR
	Check and process option

 IN
	pht: htable for comparison
	popt: option to process

 OUT
	boolean
 ***********************************************************************/

bool check_opt(htable *cht, prsopt *popt) {
	char	*recval,
			*optval;
	prsopt	*ppo;

	optval = po_get_str(popt->value);

	if ((popt->opchar == CHAR_COMMENT) || (popt->opchar == CHAR_EOS)) {
		/* found comment or empty line */
		fprintf(sfp, "%s\n", optval);
#ifdef PMKSETUP_DEBUG
		debugf("check_opt() appended '%s\n'", optval);
#endif
		return(true);
	}

#ifdef PMKSETUP_DEBUG
	debugf("check_opt: checking key '%s'", popt->key);
#endif

	ppo = hash_get(ht, popt->key);
	if (ppo == NULL) {
#ifdef PMKSETUP_DEBUG
		debugf("check_opt: key '%s' does not exist.", popt->key);
#endif
		fprintf(sfp, PMKSTP_WRITE_FORMAT, popt->key, popt->opchar, optval);
		return(true);
	}

	/* XXX check value ? */
	recval = po_get_str(ppo->value);

#ifdef PMKSETUP_DEBUG
	debugf("check_opt: original opchar '%c'", popt->opchar);
#endif

	/* checking the VAR<->VALUE separator */
	switch (popt->opchar) {
		case CHAR_ASSIGN_UPDATE :
#ifdef PMKSETUP_DEBUG
			debugf("check_opt: update opchar '%c'", ppo->opchar);
#endif
			switch (ppo->opchar) {
				case PMKSTP_REC_REMV :
					/* do nothing, value will not be refreshed */
					verbosef("Removing '%s'", popt->key);
					break;

				case PMKSTP_REC_UPDT :
					/* update value */
					fprintf(sfp, PMKSTP_WRITE_FORMAT, popt->key, CHAR_ASSIGN_UPDATE, recval);
#ifdef PMKSETUP_DEBUG
					debugf("check_opt() appended '" PMKSTP_WRITE_FORMAT "'", popt->key, CHAR_ASSIGN_UPDATE, recval);
#endif
					break;

			}

			hash_delete(cht, popt->key);
			break;

		case CHAR_ASSIGN_STATIC :
			/* static definition, stay unchanged */
			fprintf(sfp, PMKSTP_WRITE_FORMAT, popt->key, CHAR_ASSIGN_STATIC, optval);
#ifdef PMKSETUP_DEBUG
			debugf("check_opt() appended '" PMKSTP_WRITE_FORMAT "'", popt->key, CHAR_ASSIGN_STATIC, optval);
#endif
			hash_delete(cht, popt->key);
			break;

		default :
			/* should not happen now */
			errorf("unknown operator '%c'.", popt->opchar);
			return(false);
			break;
	}

	return(true);
}


/******************
 * get_env_vars() *
 ***********************************************************************
 DESCR
	Get the environment variables needed for the configuration file

 IN
	ht: hash table where we have to store the values

 OUT
	boolean
 ***********************************************************************/

bool get_env_vars(htable *pht) {
	struct utsname	 utsname;
	char			*bin_path;

	if (uname(&utsname) == -1) {
		errorf("uname : %s.", strerror(errno));
		return(false);
	}

	if (record_data(pht, PMKCONF_OS_NAME, 'u', utsname.sysname) == false)
		return(false);
	verbosef("Setting '%s' => '%s'", PMKCONF_OS_NAME, utsname.sysname);

	if (record_data(pht, PMKCONF_OS_VERS, 'u', utsname.release) == false)
		return(false);
	verbosef("Setting '%s' => '%s'", PMKCONF_OS_VERS, utsname.release);

	if (record_data(pht, PMKCONF_OS_ARCH, 'u', utsname.machine) == false)
		return(false);
	verbosef("Setting '%s' => '%s'", PMKCONF_OS_ARCH, utsname.machine);

	/* getting the environment variable PATH */
	if ((bin_path = getenv("PATH")) == NULL) {
		errorf("could not get the PATH environment variable.");
		return(false);
	}

	if (record_data(pht, PMKCONF_PATH_BIN, 'u', bin_path) == false)
		return(false);
	verbosef("Setting '%s' => '%s'", PMKCONF_PATH_BIN, bin_path);

	return(true);
}


/******************
 * get_binaries() *
 ***********************************************************************
 DESCR
	Look for location of some predefined binaries

 IN
	ht: hash table where we have to store the values

 OUT
	boolean
 ***********************************************************************/

bool get_binaries(htable *pht) {
	char			 fbin[MAXPATHLEN],	/* full binary path */
					*pstr,
					*pcc;
	dynary			*stpath;
	prsopt			*ppo;
	unsigned int	 i;

		/*
		 * splitting the PATH variable and storing in a
		 * dynamic array for later use by find_file
		 */

	ppo = hash_get(pht, PMKCONF_PATH_BIN);
	if (ppo == NULL) {
		errorf("unable to get binary path record.");
		return(false);
	}

	pstr = po_get_str(ppo->value);
	if (pstr == NULL) {
		errorf("unable to get binary path value.");
		return(false);
	}

	stpath = str_to_dynary(pstr , PATH_STR_DELIMITER);
	if (stpath == NULL) {
		errorf("could not split the PATH environment variable correctly.");
		return(false);
	}

	/* check standard compilers */
	pcc = NULL;
	for (i = 0 ; i < nb_c_compilers ; i++) {
		if (find_file(stpath, c_compilers[i][0], fbin, sizeof(fbin)) == true) {
			if (record_data(pht, c_compilers[i][1], 'u', fbin) == false) {
				da_destroy(stpath);
				return(false);
			}
			verbosef("Setting '%s' => '%s'", c_compilers[i][1], fbin);
			if (pcc == NULL) {
				pcc = c_compilers[i][1];
			}
		} else {
			verbosef("Info : '%s' (%s) not found", c_compilers[i][0], c_compilers[i][1]);
		}
	}

	/* setting PMKCONF_BIN_CC */
	if (pcc != NULL) {
		/* standard compiler found */
		snprintf(fbin, sizeof(fbin), "$%s", pcc); /* XXX check ? */
		if (record_data(pht, PMKCONF_BIN_CC, 'u', fbin) == false) {
			da_destroy(stpath);
			return(false);
		}
		verbosef("Setting '%s' => '%s'", PMKCONF_BIN_CC, fbin);
	} else {
		/* try to fallback on cc which is an "historical standard" */
		if (find_file(stpath, "cc", fbin, sizeof(fbin)) == true) {
			if (record_data(pht, PMKCONF_BIN_CC, 'u', fbin) == false) {
				da_destroy(stpath);
				return(false);
			}
			verbosef("Setting '%s' => '%s'", PMKCONF_BIN_CC, fbin);
		} else {
			/* okay then maybe a fucking non standard "gcc" name ? */
			if (find_file(stpath, "gcc", fbin, sizeof(fbin)) == true) {
				if (record_data(pht, PMKCONF_BIN_CC, 'u', fbin) == false) {
					da_destroy(stpath);
					return(false);
				}
				verbosef("Setting '%s' => '%s'", PMKCONF_BIN_CC, fbin);
			} else {
				/* no luck, unable to find the c compiler */
				errorf("cannot find CC compiler.");
				da_destroy(stpath);
				return(false);
			}
		}
	}


	/* searching some specific binaries */
	for (i = 0; i < MAXBINS; i++) {
		if (find_file(stpath, binaries[i][0], fbin, sizeof(fbin)) == true) {
			if (record_data(pht, binaries[i][1], 'u', fbin) == false) {
				da_destroy(stpath);
				return(false);
			}
			verbosef("Setting '%s' => '%s'", binaries[i][1], fbin);
		} else {
			verbosef("**warning: '%s' Not Found", binaries[i][0]);
		}
	}
	da_destroy(stpath);
	return(true);
}


/*****************
 * predef_vars() *
 ***********************************************************************
 DESCR
	Add to the hash table the predefined variables we cannot get
	automagically.

 IN
	pht: hash table where we have to store the values

 OUT
	boolean
 ***********************************************************************/

bool predef_vars(htable *pht) {
	int	 i;

	for (i = 0 ; i < nbpredef ; i++) {
		if (record_data(pht, predef[i].key, 'u', predef[i].value) == false) {
			errorf("failed to add '%s'.", predef[i].key);
			return(false);
		}
		verbosef("Setting '%s' => '%s'", predef[i].key, predef[i].value);
	}

	return(true);
}


/****************
 * check_echo() *
 ***********************************************************************
 DESCR
	Check echo output

 IN
	pht: hash table where we have to store the values

 OUT
	boolean
 ***********************************************************************/

bool check_echo(htable *pht) {
	FILE	*echo_pipe = NULL;
	char	 buf[TMP_BUF_LEN],
			 echocmd[TMP_BUF_LEN];
	char	*echo_n, *echo_c, *echo_t;
	size_t	 s;

	snprintf(echocmd, sizeof(echocmd), ECHO_CMD); /* should not fail */

	if ((echo_pipe = popen(echocmd, "r")) == NULL) {
		errorf("unable to execute '%s'.", echocmd);
		return(false);
	}

	s = fread(buf, sizeof(char), sizeof(buf), echo_pipe);
	buf[s] = CHAR_EOS;

	if (feof(echo_pipe) == 0) {
		errorf("pipe not empty.");
		pclose(echo_pipe);
		return(false);
	}

	pclose(echo_pipe);

	if (strncmp(buf, "one\\c\n-n two\nthree\n", sizeof(buf)) == 0) {
		/* ECHO_N= ECHO_C='\n' ECHO_T='\t' */
		echo_n = ECHO_EMPTY;
		echo_c = ECHO_NL;
		echo_t = ECHO_HT;
	} else {
		if (strncmp(buf, "one\\c\ntwothree\n", sizeof(buf)) == 0) {
			/* ECHO_N='-n' ECHO_C= ECHO_T= */
			echo_n = ECHO_N;
			echo_c = ECHO_EMPTY;
			echo_t = ECHO_EMPTY;
		} else {
			if (strncmp(buf, "one-n two\nthree\n", sizeof(buf)) == 0) {
				/* ECHO_N= ECHO_C='\c' ECHO_T=  */
				echo_n = ECHO_EMPTY;
				echo_c = ECHO_C;
				echo_t = ECHO_EMPTY;
			} else {
				if (strncmp(buf, "onetwothree\n", sizeof(buf)) == 0) {
					/* ECHO_N= ECHO_C='\\c' ECHO_T= */
					echo_n = ECHO_EMPTY;
					echo_c = ECHO_C;
					echo_t = ECHO_EMPTY;
				} else {
					errorf("unable to set ECHO_* variables.");
					return(false);
				}
			}
		}
	}

	if (record_data(pht, PMKCONF_AC_ECHO_N, 'u', echo_n) == false)
		return(false);
	verbosef("Setting '%s' => '%s'", PMKCONF_AC_ECHO_N, echo_n);

	if (record_data(pht, PMKCONF_AC_ECHO_C, 'u', echo_c) == false)
		return(false);
	verbosef("Setting '%s' => '%s'", PMKCONF_AC_ECHO_C, echo_c);

	if (record_data(pht, PMKCONF_AC_ECHO_T, 'u', echo_t) == false)
		return(false);
	verbosef("Setting '%s' => '%s'", PMKCONF_AC_ECHO_T, echo_t);

	return(true);
}


/*******************
 * check_libpath() *
 ***********************************************************************
 DESCR
	Check pkgconfig libpath

 IN
	pht: hash table where we have to store the values

 OUT
	boolean

 NOTE
	this path must be relative to the real prefix. This means that we
	can't rely on predefined prefix.
 ***********************************************************************/

bool check_libpath(htable *pht) {
	char	 libpath[MAXPATHLEN];

	/* build the path dynamically */
	/* variable prefix */
	strlcpy(libpath, "$", sizeof(libpath)); /* should not fail */

	/* prefix variable name */
	strlcat(libpath, PMKCONF_MISC_PREFIX,
				sizeof(libpath)); /* no check yet */

	/* pkgconfig path suffix */
	if (strlcat_b(libpath, PMKVAL_LIB_PKGCONFIG,
				sizeof(libpath)) == false)
		return(false);

	if (hash_get(pht, PMKCONF_BIN_PKGCONFIG) != NULL) {
		if (dir_exists(libpath) == 0) {
			if (record_data(pht, PMKCONF_PC_PATH_LIB,
						'u', libpath) == false)
				return(false);

			verbosef("Setting '%s' => '%s'",
					PMKCONF_PC_PATH_LIB, libpath);
		} else {
			verbosef("**warning: %s does not exist.", libpath);
		}
	}
	return(true);
}


/******************
 * get_cpu_data() *
 ***********************************************************************
 DESCR
	Check the cpu family

 IN
	uname_m: uname machine string

 OUT
	boolean
 ***********************************************************************/

bool get_cpu_data(htable *pht) {
	char			*uname_m;
	char			*pstr;
	hkeys			*phk;
	htable			*spht;
	pmkobj			*po;
	prsdata			*pdata;
	prsopt			*ppo;
	unsigned int	 i;

	ppo = hash_get(pht, PMKCONF_OS_ARCH);
	if (ppo == NULL) {
		errorf("failed to get value for %s", PMKCONF_OS_ARCH);
		return(false);
	}

	po = ppo->value;
	if (po == NULL) {
		errorf("unexpected data for %s", PMKCONF_OS_ARCH);
		return(false);
	}

	uname_m = po_get_str(po);
	if (uname_m == NULL) {
		errorf("unexpected value for %s", PMKCONF_OS_ARCH);
		return(false);
	}

	pdata = parse_cpu_data(PMKCPU_DATA);
	if (pdata == NULL) {
		/* error message already done */
		return(false);
	}

	pstr = check_cpu_arch(uname_m, pdata); /* no check, never NULL */
	if (record_data(pht, PMKCONF_HW_CPU_ARCH, 'u', pstr) == false) {
		errorf("failed to record value for %s", PMKCONF_HW_CPU_ARCH);
		return(false);
	}
	verbosef("Setting '%s' => '%s'", PMKCONF_HW_CPU_ARCH, pstr);

	spht = arch_wrapper(pdata, pstr);
	if (spht != NULL) {
		phk = hash_keys(spht);
		if (phk != NULL) {
			for(i = 0 ; i < phk->nkey ; i++) {
				pstr = hash_get(spht, phk->keys[i]); /* should not be NULL */

				if (record_data(pht, phk->keys[i], 'u', pstr) == false)
					return(false);
				verbosef("Setting '%s' => '%s'", phk->keys[i], pstr);
			}

			hash_free_hkeys(phk);
		}
		hash_destroy(spht);
	}

	prsdata_destroy(pdata);

	return(true);
}


/****************
 * dir_exists() *
 ***********************************************************************
 DESCR
	Check if a directory does exist

 IN
	fdir: directory to search

 OUT
	boolean
 ***********************************************************************/

bool dir_exists(const char *fdir) {
		DIR		*dirp;
		size_t	 len;

	len = strlen(fdir);

	if (len < MAXPATHLEN) {
		dirp = opendir(fdir);
		if (dirp != NULL) {
			closedir(dirp);
			return(true);
		}
	}
	return(false);
}


/**********************
 * byte_order_check() *
 ***********************************************************************
 DESCR
	Byte order check

 IN
	pht: hash table to store value

 OUT
	boolean
 ***********************************************************************/

bool byte_order_check(htable *pht) {
	char	bo_type[16];
	int	num = 0x41424344;

	if (((((char *)&num)[0]) == 0x41) && ((((char *)&num)[1]) == 0x42) &&
		((((char *)&num)[2]) == 0x43) && ((((char *)&num)[3]) == 0x44)) {
		strlcpy(bo_type, HW_ENDIAN_BIG,
				sizeof(bo_type)); /* should not fail */
	} else {
		if (((((char *)&num)[3]) == 0x41) &&
				((((char *)&num)[2]) == 0x42) &&
				((((char *)&num)[1]) == 0x43) &&
				((((char *)&num)[0]) == 0x44)) {
			strlcpy(bo_type, HW_ENDIAN_LITTLE,
					sizeof(bo_type)); /* should not fail */
		} else {
			strlcpy(bo_type, HW_ENDIAN_UNKNOWN,
					sizeof(bo_type)); /* should not fail */
		}
	}

	if (record_data(pht, PMKCONF_HW_BYTEORDER, 'u', bo_type) == false)
		return(false);

	verbosef("Setting '%s' => '%s'", PMKCONF_HW_BYTEORDER, bo_type);

	return(true);
}


/**************
 * verbosef() *
 ***********************************************************************
 DESCR
	Simple formated verbose function

 IN
	fmt: format string

 OUT
	NONE
 ***********************************************************************/

void verbosef(const char *fmt, ...) {
	char	 buf[MAX_ERR_MSG_LEN];
	va_list	 plst;

	if (verbose_flag == 1) {
		va_start(plst, fmt);
		vsnprintf(buf, sizeof(buf), fmt, plst);
		va_end(plst);

		printf("%s\n", buf);
	}
}


/********************
 * detection_loop() *
 ***********************************************************************
 DESCR
	Detection loop

 IN
	argc:	argument number
	argv:	argument array

 OUT
	boolean
 ***********************************************************************/

bool detection_loop(int argc, char *argv[]) {
	FILE		*config;
	bool		 process_clopts = false,
				 cfg_backup = false;
	char		*pstr;
	int			 ch;
	prsopt		*ppo;
#ifndef errno
	extern int	 errno;
#endif /* errno */

	optind = 1;
	while ((ch = getopt(argc, argv, PMKSTP_OPT_STR)) != -1) {
		switch(ch) {
			case 'r' :
				/* mark to be deleted in hash */
				if (record_data(ht, optarg, PMKSTP_REC_REMV, NULL) == false) {
					errorf("failed to record '%s'.", optarg);
					return(false);
				}

				process_clopts = true;
				break;

			case 'u' :
				/* add or update value */
				ppo = prsopt_init();
				if (ppo == NULL) {
					errorf("prsopt init failed.");
					return(false);
				}

				if (parse_clopt(optarg, ppo, PRS_PMKCONF_SEP) == false) {
					errorf("bad argument for -%c option: %s.", ch, optarg);
					return(false);
				}

				/* add in hash */
				pstr = po_get_str(ppo->value);
				if (pstr == NULL) {
					errorf("failed to get argument for -%c option", ch);
					return(false);
				}

				/* record prsopt structure directly, fast and simple */
				ppo->opchar = PMKSTP_REC_UPDT;
				if (hash_update(ht, ppo->key, ppo) == HASH_ADD_FAIL) {
					prsopt_destroy(ppo);
					errorf("hash update failed.");
					return(false);
				}

				/* XXX use record_data => cost much memory and cpu XXX			*/
				/*if (record_data(ht, ppo->key, PMKSTP_REC_UPDT, pstr) == false) {*/
				/*		errorf("failed to record '%s'.", ppo->key);			 */
				/*		prsopt_destroy(ppo);									*/
				/*		return(false);										  */
				/*}															   */
				/*prsopt_destroy(ppo);						  */

				process_clopts = true;
				break;

			case 'v' :
			case 'V' :
				/*
				 * already processed in main()
				 */
				break;

			case '?' :
			default :
				usage();
				/* NOTREACHED */
		}
	}

	argc -= optind;
	argv += optind;

	printf("PMKSETUP version %s", PREMAKE_VERSION);
#ifdef DEBUG
	printf(" [SUB #%s] [SNAP #%s]", PREMAKE_SUBVER_PMKSETUP, PREMAKE_SNAP);
#endif /* DEBUG */
	printf("\n\n");

	if (process_clopts == false) {
		/* standard behavior, gathering data */
		if (gather_data(ht) == false)
			return(false);
	}

	/* parse configuration file */
	config = fopen(PREMAKE_CONFIG_PATH, "r");
	if (config != NULL) {
		/* switch backup flag */
		cfg_backup = true;

		printf("==> Configuration file found: %s\n",
						PREMAKE_CONFIG_PATH);
		if (parse_pmkconf(config, ht, PRS_PMKCONF_SEP,
						check_opt) == false) {
			fclose(config);
			errorf("parsing failed.");
			return(false);
		} else {
			fclose(config);
		}
	} else {
		printf("==> Configuration file not found.\n");
	}

	printf("==> Merging remaining data...\n");
	/* writing the remaining data stored in the hash */
	if (write_new_data(ht) == false)
		return(false);

	return(true);
}


/****************
 * child_loop() *
 ***********************************************************************
 DESCR
	Child main loop

 IN
	uid:	privsep user id
	gid:	privsep user group
	argc:	argument number
	argv:	argument array

 OUT
	NONE
 ***********************************************************************/

void child_loop(uid_t uid, gid_t gid, int argc, char *argv[]) {
#ifndef WITHOUT_FORK
	if (getuid() == 0) {
		/* user has root privs, DROP THEM ! */
		if (setgid(gid) != 0) {
			/* failed to change gid */
			errorf(EMSG_PRIV_FMT, "gid");
			_exit(EXIT_FAILURE);
		}

		if (setegid(gid) != 0) {
			/* failed to change egid */
			errorf(EMSG_PRIV_FMT, "egid");
			_exit(EXIT_FAILURE);
		}

		if (setuid(uid) != 0) {
			/* failed to change uid */
			errorf(EMSG_PRIV_FMT, "uid");
			_exit(EXIT_FAILURE);
		}

		if (seteuid(uid) != 0) {
			/* failed to change euid */
			errorf(EMSG_PRIV_FMT, "euid");
			_exit(EXIT_FAILURE);
		}
	}
#endif /* WITHOUT_FORK */

#ifdef PMKSETUP_DEBUG
	debugf("gid = %d", getgid());
	debugf("uid = %d", getuid());
#endif /* PMKSETUP_DEBUG */

	ht = hash_init_adv(MAX_CONF_OPT, NULL, (void (*)(void *)) prsopt_destroy, NULL);
	if (ht == NULL) {
		errorf("cannot create hash table.");
		exit(EXIT_FAILURE);
	}

	if (detection_loop(argc, argv) == false) {
		hash_destroy(ht);
		exit(EXIT_FAILURE);
	}

	/* destroying the hash once we're done with it */
	hash_destroy(ht);

}


/*****************
 * parent_loop() *
 ***********************************************************************
 DESCR
	Parent main loop

 IN
	pid: pid of the child

 OUT
	NONE
 ***********************************************************************/

void parent_loop(pid_t pid) {
	bool	 error = false;
#ifndef WITHOUT_FORK
	int		 status;

	waitpid(pid, &status, WUNTRACED);

#ifdef PMKSETUP_DEBUG
	debugf("waitpid() status = %d", WEXITSTATUS(status));
#endif /* PMKSETUP_DEBUG */
#endif /* WITHOUT_FORK */

	if (fclose(sfp) != 0) {
		/* hazardous result => exit */
		errorf("cannot close temporary file '%s' : %s.", sfp,
							strerror(errno));
		exit(EXIT_FAILURE);
	}

#ifdef WITHOUT_FORK
	/*
	 * without fork, the child part will exit() if a problem occur
	 * so we shouldn't need to care about what happens
	 */
#else
	/* if child status is ok, writing changes */
	if (status == 0) {
#endif
		/*
		 * check if pmk.conf already exists
		 * NOTE: no race condition here for access(), BUT
		 * BE CAREFUL if changes are to be made.
		 */
		if (access(PREMAKE_CONFIG_PATH, F_OK) == 0) { /* see above */
			/* backup configuration file */
			printf("==> Backing up configuration file: %s\n",
						PREMAKE_CONFIG_PATH_BAK);

			if (rename(PREMAKE_CONFIG_PATH,
					PREMAKE_CONFIG_PATH_BAK) != 0) {
				errorf("configuration file backup failed: %s.",
							strerror(errno));
				error = true;
			}
		}

		/* copying the temporary config to the system one */
		printf("==> Saving configuration file: %s\n",
						PREMAKE_CONFIG_PATH);
		if (fcopy(sfn, PREMAKE_CONFIG_PATH,
					PREMAKE_CONFIG_MODE) == false) {
			errorf("failed to copy temporary file '%s'.", sfn);
			exit(EXIT_FAILURE);
		}
#ifndef WITHOUT_FORK
	}
#endif

#ifdef PMKSETUP_DEBUG
	debugf("%s has not been deleted!", sfn);
#else
	if (unlink(sfn) == -1) {
		errorf("cannot remove temporary file: '%s' : %s.",
					sfn, strerror(errno));
		error = true;
	}
#endif	/* PMKSETUP_DEBUG */

#ifndef WITHOUT_FORK
	if (status != 0) {
		errorf("child failed (status %d)", status);
		exit(EXIT_FAILURE);
	}
#endif /* WITHOUT_FORK */

	if (error == true)
		exit(EXIT_FAILURE);
}


/***********
 * usage() *
 ***********************************************************************
 DESCR
	pmksetup(8) usage

 IN
	NONE

 OUT
	NONE
 ***********************************************************************/

void usage(void) {
	fprintf(stderr, "usage: pmksetup [-hVv] "
		"[-r variable] [-u variable=value]\n");
	exit(EXIT_FAILURE);
}


/**********
 * main() *
 ***********************************************************************
 DESCR
	Main loop
 ***********************************************************************/

int main(int argc, char *argv[]) {
	struct passwd	*pw;
	gid_t			 gid = 0;
	pid_t			 pid;
	uid_t			 uid = 0;
	int				 ch;

	optind = 1;
	while ((ch = getopt(argc, argv, PMKSTP_OPT_STR)) != -1) {
		switch(ch) {
			case 'r' :
			case 'u' :
				/*
				 * ignore this options at this level, it
				 * will be processed later
				 */
				break;

			case 'v' :
				fprintf(stderr, "%s\n", PREMAKE_VERSION);
				return(true);
				break;

			case 'V' :
				if (verbose_flag == 0)
					verbose_flag = 1;
				break;

			case '?' :
			default :
				usage();
				/* NOTREACHED */
		}
	}


	if (getuid() == 0) {
#ifdef PMKSETUP_DEBUG
		debugf("PRIVSEP_USER = '%s'", PRIVSEP_USER);
#endif /* PMKSETUP_DEBUG */
		pw = getpwnam(PRIVSEP_USER);
		if (pw == NULL) {
			errorf("cannot get user data for '%s'.", PRIVSEP_USER);
			exit(EXIT_FAILURE);
		}
		uid = pw->pw_uid;
		gid = pw->pw_gid;
	}

	/* check if syconfdir exists */
	if (access(CONFDIR, F_OK) != 0) { /* no race condition, just mkdir() */
		verbosef("==> Creating '%s' directory.", CONFDIR);
		if (mkdir(CONFDIR, S_IRWXU | S_IRGRP | S_IXGRP |
						S_IROTH | S_IXOTH) != 0) {
			errorf("cannot create '%s' directory : %s.",
						CONFDIR, strerror(errno));
			exit(EXIT_FAILURE);
		}
	}

	sfp = tmp_open(PREMAKE_CONFIG_TMP, "w", sfn, sizeof(sfn));
	if (sfp == NULL) {
		errorf("cannot open temporary file '%s' : %s.",
				sfn, strerror(errno));
		exit(EXIT_FAILURE);
	}

#ifndef WITHOUT_FORK
	/* forking detection code */
	pid = fork();
	switch (pid) {
		case -1:
			/* fork failed */
			errorf("fork failed.");
			break;

		case 0:
			/* child start here after forking */
#endif /* WITHOUT_FORK */
			child_loop(uid, gid, argc, argv);
#ifndef WITHOUT_FORK
			break;

		default:
			/* parent continue here after forking */
#else
			pid = getpid();
#endif /* WITHOUT_FORK */
			parent_loop(pid);
#ifndef WITHOUT_FORK
			break;
	}
#endif /* WITHOUT_FORK */

	return(EXIT_SUCCESS);
}

/* vim: set noexpandtab tabstop=4 softtabstop=4 shiftwidth=4: */


syntax highlighted by Code2HTML, v. 0.9.1