/*
 * Configurable ps-like program.
 * Initialisation file parsing routines.
 *
 * Copyright (c) 2010 David I. Bell
 * Permission is granted to use, distribute, or modify this source,
 * provided that this copyright notice remains intact.
 */

#include <errno.h>

#include "ips.h"


#define	ALLOC_SIZE	100		/* allocation size for lines */


/*
 * Local procedures.
 */
static char *	ReadLine(FILE * fp);


/*
 * Try to access the initial file in the user's home directory.
 * The file may or may not exist.
 */
BOOL
ParseUserInitFile(void)
{
	const char *	home;
	char *		fullName;
	BOOL		result;

	/*
	 * Get the user's home directory and then build the full
	 * path from it and the user init file name.
	 */
	home = getenv("HOME");

	if (home == NULL)
		return TRUE;

	fullName = AllocMemory(strlen(home) + sizeof(USER_INIT_FILE) + 2);

	strcpy(fullName, home);
	strcat(fullName, "/");
	strcat(fullName, USER_INIT_FILE);

	/*
	 * Try to parse the file.
	 */
	result = ParseFile(fullName, TRUE);

	free(fullName);

	return result;
}


/*
 * Try to parse the system initialisation file.
 * The file may or may not exist.
 */
BOOL
ParseSystemInitFile(void)
{
	/*
	 * Try to parse the system initialisation file.
	 */
	return ParseFile(SYSTEM_INIT_FILE, TRUE);
}


/*
 * Try to parse the definitions within a specified file.
 * If the isOptional flag is set, then the file does not have to exist.
 * Returns TRUE if the file was successfully parsed.
 */
BOOL
ParseFile(const char * name, BOOL isOptional)
{
	FILE *		fp;
	char *		cp;
	char *		cmd;
	BOOL		status;
	MACRO_TYPE	macroType;

	status = TRUE;

	/*
	 * If the file is optional then check whether it exists.
	 * In not, then return success without doing anything.
	 */
	if (isOptional && (access(name, F_OK) != 0) && (errno == ENOENT))
		return TRUE;

	/*
	 * Check our permissions for reading the file before using it,
	 * in case we are running suid as root.
	 */
	if (access(name, R_OK) != 0)
	{
		fprintf(stderr, "Cannot access \"%s\" for reading: %s\n",
			name, strerror(errno));

		return FALSE;
	}

	fp = fopen(name, "r");

	if (fp == NULL)
	{
		fprintf(stderr, "Cannot open \"%s\" for reading: %s\n",
			name, strerror(errno));

		return FALSE;
	}

	/*
	 * For further security, make sure that the first line contains
	 * the magic string identifying an ips configuration file.
	 */
	cmd = ReadLine(fp);

	if ((cmd == NULL) || (strcmp(cmd, FIRST_LINE) != 0))
	{
		fclose(fp);

		fprintf(stderr,
			"The file \"%s\" does not begin with \"%s\".\n",
			name, FIRST_LINE);

		return FALSE;
	}

	/*
	 * OK, read each line of the file and handle it.
	 */
	while (TRUE)
	{
		cmd = ReadLine(fp);

		if (cmd == NULL)
			break;

		while (isBlank(*cmd))
			cmd++;

		/*
		 * Skip blank lines or lines starting with hash marks.
		 */
		if ((*cmd == '#') || (*cmd == '\0'))
			continue;

		cp = cmd;

		while ((*cp != '\0') && !isBlank(*cp))
			cp++;

		if (*cp)
			*cp++ = '\0';

		while (isBlank(*cp))
			cp++;

		/*
		 * If a macro is being defined, then do that.
		 */
		macroType = MACRO_TYPE_NONE;

		if (strcmp(cmd, "option") == 0)
			macroType = MACRO_TYPE_OPTION;
		else if (strcmp(cmd, "column") == 0)
			macroType = MACRO_TYPE_COLUMN;
		else if (strcmp(cmd, "expr") == 0)
			macroType = MACRO_TYPE_EXPRESSION;

		if (macroType != MACRO_TYPE_NONE)
		{
			cmd = cp;

			while ((*cp != '\0') && !isBlank(*cp))
				cp++;

			if (*cp)
				*cp++ = '\0';

			while (isBlank(*cp))
				cp++;

			if (!DefineMacro(macroType, cmd, cp))
				status = FALSE;

			continue;
		}

		/*
		 * The command is unknown.
		 */
		fprintf(stderr, "%s: Unknown command \"%s\"\n", name, cmd);

		status = FALSE;

		break;
	}

	if (ferror(fp))
	{
		fprintf(stderr, "Error reading \"%s\": %s\n", name,
			strerror(errno));

		fclose(fp);

		return FALSE;
	}

	(void) fclose(fp);

	return status;
}


/*
 * Read the next line from the opened file.
 * Returns a pointer to a static buffer which contains the null
 * terminated line with the newline removed, or a NULL pointer on
 * end of file or error.  This handles backslashes at the end of lines
 * to indicate that the line is continued on the next line.
 */
static char *
ReadLine(FILE * fp)
{
	char *		tmpBuf;
	static	char *	buf;
	static	int	avail;
	static	int	used;

	used = 0;

	while (TRUE)
	{
		/*
		 * Grow the buffer if we need more room.
		 */
		if (used + ALLOC_SIZE > avail)
		{
			if (avail == 0)
				tmpBuf = malloc(ALLOC_SIZE);
			else
				tmpBuf = realloc(buf, avail + ALLOC_SIZE);

			if (tmpBuf == NULL)
			{
				fprintf(stderr, "Cannot allocate memory\n");

				return NULL;
			}

			buf = tmpBuf;
			avail += ALLOC_SIZE;
		}

		/*
		 * Read some more of the file into the buffer after any
		 * part of the line which may have already been read.
		 */
		if (fgets(&buf[used], avail - used, fp) == NULL)
		{
			if (used)
				return buf;

			return NULL;
		}

		/*
		 * Add to the size of the line read so far.
		 */
		used += strlen(&buf[used]);

		/*
		 * If the buffer doesn't end in a newline, then we didn't
		 * read the whole line, so go back and read some more.
		 */
		if ((used > 0) && (buf[used - 1] != '\n'))
			continue;

		/*
		 * Remove the newline from the end of the data.
		 */
		buf[--used] = '\0';

		/*
		 * See if the last character of the line is now a backslash.
		 * If so, then replace it with a blank and continue reading.
		 */
		if ((used > 0) && (buf[used - 1] == '\\'))
		{
			buf[used - 1] = ' ';

			continue;
		}

		/*
		 * The line is completely read in, so return it.
		 */
		return buf;
	}
}

/* END CODE */
