/* $Id: common.c 1555 2006-08-17 20:10:28Z mipsator $ */

/*
 * Copyright (c) 2003-2006 Damien Couderc
 * Copyright (c) 2003-2004 Xavier Santolaria <xavier@santolaria.net>
 * 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.
 *
 */


/*
 * Credits for patches :
 *	- Ted Unangst
 */

#include <sys/param.h>
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdlib.h>

#include "compat/pmk_string.h"
#include "compat/pmk_stdio.h"
#include "compat/pmk_unistd.h"
#include "common.h"

/*#define	MKVAR_DEBUG	1*/

FILE	*pmk_log_fp;


/**************
 * get_line() *
 ***********************************************************************
 DESCR
	get a line from a file and remove newline character

 IN
	fd : file descriptor
	line : buffer that will contain the line
	lsize : size of the buffer

 OUT
	returns a boolean :
		- true when a line is available
		- false when error or eof occured
 ***********************************************************************/

bool get_line(FILE *fd, char *line, size_t lsize) {
	char	*p;

	if (fgets(line, lsize, fd) != NULL) {
		p = line;
		while (*p != CHAR_EOS) {
			if (*p == '\n') {
				/* remove trailing newline */
				*p= CHAR_EOS;
			}
			p++;
		}
		return(true);
	} else {
		/* reached eof or error */
		return(false);
	}
}


/****************
 * env_to_opt() *
 ***********************************************************************
 DESCR
	put env variable in an option

 IN
	env_name :	variable name
	opt :		option struct

 OUT
	returns true if env_name exists
 ***********************************************************************/

bool env_to_opt(char *env_name, pmkcmdopt *opt) {
	bool	 rval;
	char	*env_val;

	env_val = getenv(env_name);
	if (env_val == NULL) {
		/* env variable name not found */
		rval = false;
	} else {
		/* okay get it */
		if (strlcpy_b(opt->value, env_val,
					sizeof(opt->value)) == false) {
			rval = true;
		} else {
			rval = false;
		}

	}
	return(rval);
}


/******************
 * get_make_var() *
 ***********************************************************************
 DESCR
	get variable content from make

 IN
	varname:	name of the variable like for example CC
	result:		storage of result
	rsize:		result size

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

bool get_make_var(char *varname, char *result, size_t rsize) {
	FILE	*mfp,
		*tfp;
	bool	 rval;
	char	 mfn[MAXPATHLEN],
		 varstr[TMP_BUF_LEN];

	if (strlcpy_b(mfn, TMP_MK_FILE, sizeof(mfn)) == false) {
		errorf("failed to create temporary file");
		return(false);
	}

	mfp = tmps_open(TMP_MK_FILE, "w", mfn, sizeof(mfn), strlen(MK_FILE_EXT));
	if (mfp != NULL) {
		/* create a tmp makefile with the following format :
			all:
				@printf ${VARNAME}

		   /!\ should check content of VARNAME, could result
		   	in a security breach.
		*/
		fprintf(mfp, MKVAR_FMT_MK, varname, MKVAR_FILE);
		fclose(mfp);
#ifdef MKVAR_DEBUG
debugf(MKVAR_FMT_MK, varname, MKVAR_FILE);
#endif
	} else {
#ifdef MKVAR_DEBUG
		errorf("failed to open '%s' : %s.", mfn, strerror(errno));
#endif
		return(false);
	}

	if (snprintf_b(varstr, sizeof(varstr), MKVAR_FMT_CMD, mfn) == false)
		return(false);

	if (system(varstr) == 0) {
		tfp = fopen(MKVAR_FILE, "r");
		if (tfp != NULL) {
			/* catch output of make */
			if (get_line(tfp, result, rsize) == false) {
#ifdef MKVAR_DEBUG
				errorf("failed to get result from '%s' : %s.",
						varstr, strerror(errno));
#endif
				rval = false;
			}
			fclose(tfp);

			rval = true;
		} else {
#ifdef MKVAR_DEBUG
			errorf("failed to open '%s' : %s.", varstr,
						strerror(errno));
#endif
			rval = false;
		}
	} else {
#ifdef MKVAR_DEBUG
		errorf("failed to execute '%s' : %s.", varstr,
						strerror(errno));
#endif
		rval = false;
	}


	if (unlink(mfn) == -1) {
		/* cannot remove temporary file */
		errorf("cannot remove temporary file: '%s' : %s.",
			mfn, strerror(errno));
	}

	if (unlink(MKVAR_FILE) == -1) {
		/* cannot remove temporary file */
#ifdef MKVAR_DEBUG
		errorf("cannot remove temporary file: '%s' : %s.",
			mfn, strerror(errno));
#endif
	}

	return(rval);
}


/******************
 * str_to_ulong() *
 ***********************************************************************
 DESCR
	convert a string into an unsigned long

 IN
	XXX

 OUT
	XXX
 ***********************************************************************/

bool str_to_ulong(char *str, int base, unsigned long *value) {
	char	*ep;

	*value = strtoul(str, &ep, base);
	if (*str == '\0' || *ep != '\0') {
		return(false); /* not a number */
	}

	if ((errno == ERANGE) && (*value == ULONG_MAX)) {
		return(false); /* out of range */
	}

	return(true);
}


/*******************
 * str_to_dynary() *
 ***********************************************************************
 DESCR
	split a string into a dynamic array (one separator)

 IN
	str :	string to split
	sep :	separator

 OUT
	dynary or NULL

 NOTE
	move to dynary.c ?
 ***********************************************************************/

dynary *str_to_dynary(char *str, char sep) {
	static char	buf[2];

	snprintf(buf, sizeof(buf), "%c", sep); /* should not overflow */
	return(str_to_dynary_adv(str, buf));
}


/***********************
 * str_to_dynary_adv() *
 ***********************************************************************
 DESCR
	split a string into a dynamic array (list of separators)

 IN
	str :	string to split
	sep :	separators list

 OUT
	dynary or NULL

 NOTE
	move to dynary.c ?
 ***********************************************************************/

dynary *str_to_dynary_adv(char *str, char *seplst) {
	char	 buf[MAXPATHLEN],
		*pbuf;
	dynary	*da;
	int	 s;

	if (str == NULL) {
		/* protect against NULL */
		return(NULL);
	}

	/* init dynary */
	da = da_init();
	if (da == NULL) {
		return(NULL);
	}

	s = sizeof(buf);
	pbuf = buf;

	while (*str != CHAR_EOS) {
		/* check if character is in separator list */
		if (strchr(seplst, *str) != NULL) {
			*pbuf = CHAR_EOS;
			if (da_push(da, strdup(buf)) == false) {
				da_destroy(da);
				return(NULL);
			}
			pbuf = buf;
			s = sizeof(buf);
		} else {
			*pbuf = *str;
			pbuf++;
			s--;
			if (s == 0) {
				/* not enough space in buffer */
				da_destroy(da);
				return(NULL);
			}
		}
		str++;
	}

	*pbuf = CHAR_EOS;
	if (da_push(da, strdup(buf)) == false) {
		da_destroy(da);
		return(NULL);
	}

	return(da);
}


/*******************
 * find_file_dir() *
 ***********************************************************************
 DESCR
	find if one dir contain a given file in the specified list of path

 IN
	da :	path list
	fname :	file to search
	fpath :	storage of the directory
	fplen :	size of the storage

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

bool find_file_dir(dynary *da, char *fname, char *fpath, size_t fplen) {
	char		 tstr[MAXPATHLEN],
			*path;
	unsigned int	 i;

	for (i = 0 ; i < da_usize(da) ; i++) {
		path = da_idx(da, i);

		strlcpy(tstr, path, sizeof(tstr)); /* no check */
		strlcat(tstr, "/", sizeof(tstr)); /* no check */
		if (strlcat_b(tstr, fname, sizeof(tstr)) == true) {
			if (access(tstr, F_OK) == 0) { /* no race condition here */
				if (strlcpy_b(fpath, path, fplen) == true) {
					/* fpath correctly set */
					return(true);
				} else {
					errorf("strlcpy failed in find_file_dir().");
					return(false);
				}
			}
		}

	}

	/* not found */
	return(false);
}


/***************
 * find_file() *
 ***********************************************************************
 DESCR
	find a file in the specified list of path

 IN
	da :	path list
	fname :	file to search
	fpath :	storage of the full path
	fplen :	size of the storage

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

bool find_file(dynary *da, char *fname, char *fpath, size_t fplen) {
	bool	rval = false;

	if (find_file_dir(da, fname, fpath, fplen) == true) {
		strlcat(fpath, "/", fplen); /* no check */
		if (strlcat_b(fpath, fname, fplen) == true) {
			/* fpath set correctly */
			rval = true;
		}
	}
	return(rval);
}


/*******************
 * get_file_path() *
 ***********************************************************************
 DESCR
	get path of given filename in given path list

 IN
	filename :	name of the file to look for
	path :		string of the list of path
	storage :	storage for resulting path
	size :		size of storage

 OUT
	returns true if filename is found in one of the list's path
 ***********************************************************************/

bool get_file_path(char *filename, char *path, char *storage, size_t size) {
	bool	 rval = false;
	dynary	*bplst;

	/* fill dynary with path */
	bplst= str_to_dynary(path, PATH_STR_DELIMITER);
	if (bplst == NULL) {
		errorf("failed to put a path into a dynamic array.");
		return(false);
	}

	/* try to locate binary */
	if (find_file(bplst, filename, storage, size) == true) {
		rval = true;
	} else {
		rval = false;
	}

	da_destroy(bplst);
	return(rval);
}


/************
 * errorf() *
 ***********************************************************************
 DESCR
	formated simple error

 IN
	fmt :	format string followed by arguments

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

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

	va_start(plst, fmt);
	vsnprintf(buf, sizeof(buf), fmt, plst);
	va_end(plst);

	fprintf(stderr, "Error : %s\n", buf);
}


/*****************
 * errorf_line() *
 ***********************************************************************
 DESCR
	formated parse error

 IN
	filename :	parsed file name
	line :		line of error
	fmt :		format string followed by arguments

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

void errorf_line(char *filename, int line, const char *fmt, ...) {
	char	buf[TMP_BUF_LEN];
	va_list	plst;

	va_start(plst, fmt);
	vsnprintf(buf, sizeof(buf), fmt, plst);
	va_end(plst);

	fprintf(stderr, "Error in '%s' line %d : %s\n", filename, line, buf);
}


/************
 * debugf() *
 ***********************************************************************
 DESCR
	formated debug message

 IN
	fmt :	format string followed by arguments

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

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

	va_start(plst, fmt);
	vsnprintf(buf, sizeof(buf), fmt, plst);
	va_end(plst);

	fprintf(stdout, "!DEBUG! %s\n", buf);
	fflush(stdout);
}


/******************
 * pmk_log_open() *
 ***********************************************************************
 DESCR
	open log file

 IN
	logname :	log file name

 OUT
	returns true if opened
 ***********************************************************************/

bool pmk_log_open(char *logname) {
	if (pmk_log_fp != NULL) {
		errorf("'%s' is already open.", logname);
		return(false);
	}
	pmk_log_fp = fopen(logname, "w");
	if (pmk_log_fp == NULL) {
		errorf("while opening '%s'.", logname);
		return(false);
	} else {
		return(true);
	}
}


/*******************
 * pmk_log_close() *
 ***********************************************************************
 DESCR
	close log file

 IN
	NONE

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

void pmk_log_close(void) {
	fflush(pmk_log_fp);
	fclose(pmk_log_fp);
}


/*************
 * pmk_log() *
 ***********************************************************************
 DESCR
	log formatted line

 IN
	fmt :	format string

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

bool pmk_log(const char *fmt, ...) {
	char	buf[TMP_BUF_LEN];
	va_list	plst;

	va_start(plst, fmt);
	vsnprintf(buf, sizeof(buf), fmt, plst);
	va_end(plst);

	if (pmk_log_fp != NULL) {
		fprintf(pmk_log_fp, buf);
		fflush(pmk_log_fp);
		fprintf(stdout, buf);
		fflush(stdout);
		return(true);
	} else {
		errorf("unable to log.");
		return(false);
	}
}


/***********
 * fcopy() *
 ***********************************************************************
 DESCR
	copy file

 IN
	src :	file to copy
	dst :	destination file
	mode :	destination perms

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

bool fcopy(char *src, char *dst, mode_t mode) {
	char		cbuf[S_BLKSIZE];
	bool		do_loop = true,
			rval = true;
	int		src_fd,
			dst_fd;
	ssize_t		rsz;

	/* try to open both source and destination files */
	src_fd = open(src, O_RDONLY, 0);
	if (src_fd == -1) {
		errorf("cannot open '%s' : %s.", src, strerror(errno));
		return(false);
	}
/*debugf("mode = %o", mode);*/
	dst_fd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, mode);
	if (dst_fd == -1) {
		close(src_fd);
		errorf("cannot open '%s' : %s.", dst, strerror(errno));
		return(false);
	}

	while (do_loop == true) {
		/* reading data */
		rsz = read(src_fd, cbuf, sizeof(cbuf));
		switch(rsz) {
			case -1:
				/* read error */
				errorf("failed to read '%s' : %s.", src, strerror(errno));
				do_loop = false;
				rval = false;
				break;

			case 0:
				/* no more data to copy */
				do_loop = false;
				break;

			default:
				/* data read, trying to write */
				if (write(dst_fd, cbuf, rsz) != rsz) {
					/* write failed */
					errorf("failed to write '%s' : %s.", dst, strerror(errno));
					do_loop = false;
					rval = false;
				}
				break;
		}
	}

	close(src_fd);
	close(dst_fd);

	return(rval);
}


/**************
 * tmp_open() *
 ***********************************************************************
 DESCR
	open temporary file

 IN
	tfile :	template file name
	mode :	file mode
	buf :	buffer for the randomized file name
	bsize :	buffer size

 OUT
	file structure or NULL
 ***********************************************************************/

FILE *tmp_open(char *tfile, char *mode, char *buf, size_t bsize) {
	int	fd;

	/* copy file name in buf */
	if (strlcpy_b(buf, tfile, bsize) == false)
		return(NULL);

	/* randomize file name */
	fd = mkstemp(buf);
	if (fd == -1) {
		return(NULL);
	}

	return(fdopen(fd, mode));
}


/**************
 * tmps_open() *
 ***********************************************************************
 DESCR
	open temporary file with suffix

 IN
	tfile :	template file name
	mode :	file mode
	buf :	buffer for the randomized file name
	bsize :	buffer size
	slen :	suffix length

 OUT
	file structure or NULL
 ***********************************************************************/

FILE *tmps_open(char *tfile, char *mode, char *buf, size_t bsize, size_t slen) {
	int	fd;

	/* copy file name in buf */
	if (strlcpy_b(buf, tfile, bsize) == false)
		return(NULL);

	/* randomize file name */
	fd = mkstemps(buf, slen);
	if (fd == -1) {
		return(NULL);
	}

	return(fdopen(fd, mode));
}



syntax highlighted by Code2HTML, v. 0.9.1