/* 
 * Copyright (c) 1997  Metro Link Incorporated
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * 
 * Except as contained in this notice, the name of the Metro Link shall not be
 * used in advertising or otherwise to promote the sale, use or other dealings
 * in this Software without prior written authorization from Metro Link.
 * 
 */
/*
 * Copyright (c) 1997-2003 by The XFree86 Project, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name of the copyright holder(s)
 * and author(s) shall not be used in advertising or otherwise to promote
 * the sale, use or other dealings in this Software without prior written
 * authorization from the copyright holder(s) and author(s).
 */


/* View/edit this file with tab stops set to 4 */

#ifdef HAVE_XORG_CONFIG_H
#include <xorg-config.h>
#endif

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <stdarg.h>
#include <X11/Xdefs.h>
#include <X11/Xfuncproto.h>

#if defined(_POSIX_SOURCE)
#include <limits.h>
#else
#define _POSIX_SOURCE
#include <limits.h>
#undef _POSIX_SOURCE
#endif /* _POSIX_SOURCE */

#if !defined(PATH_MAX)
#if defined(MAXPATHLEN)
#define PATH_MAX MAXPATHLEN
#else
#define PATH_MAX 1024
#endif /* MAXPATHLEN */
#endif /* !PATH_MAX */

#if !defined(MAXHOSTNAMELEN)
#define MAXHOSTNAMELEN 32
#endif /* !MAXHOSTNAMELEN */

#include "Configint.h"
#include "xf86tokens.h"

#define CONFIG_BUF_LEN     1024
#define CONFIG_MAX_FILES   64

static int StringToToken (char *, xf86ConfigSymTabRec *);

static struct {
	FILE *file;
	char *path;
} configFiles[CONFIG_MAX_FILES];
static const char **builtinConfig = NULL;
static int builtinIndex = 0;
static int configPos = 0;		/* current readers position */
static int configLineNo = 0;	/* linenumber */
static char *configBuf, *configRBuf;	/* buffer for lines */
static char *configPath;		/* path to config file */
static char *configDirPath;		/* path to config dir */
static char *configSection = NULL;	/* name of current section being parsed */
static int numFiles = 0;		/* number of config files */
static int curFileIndex = 0;		/* index of current config file */
static int pushToken = LOCK_TOKEN;
static int eol_seen = 0;		/* private state to handle comments */
LexRec val;

/*
 * xf86getNextLine --
 *
 *  read from the configFiles FILE stream until we encounter a new
 *  line; this is effectively just a big wrapper for fgets(3).
 *
 *  xf86getToken() assumes that we will read up to the next
 *  newline; we need to grow configBuf and configRBuf as needed to
 *  support that.
 */

static char*
xf86getNextLine(void)
{
	static int configBufLen = CONFIG_BUF_LEN;
	char *tmpConfigBuf, *tmpConfigRBuf;
	int c, i, pos = 0, eolFound = 0;
	char *ret = NULL;

	/*
	 * reallocate the string if it was grown last time (i.e., is no
	 * longer CONFIG_BUF_LEN); we malloc the new strings first, so
	 * that if either of the mallocs fail, we can fall back on the
	 * existing buffer allocations
	 */

	if (configBufLen != CONFIG_BUF_LEN) {

		tmpConfigBuf = malloc(CONFIG_BUF_LEN);
		tmpConfigRBuf = malloc(CONFIG_BUF_LEN);

		if (!tmpConfigBuf || !tmpConfigRBuf) {

			/*
			 * at least one of the mallocs failed; keep the old buffers
			 * and free any partial allocations
			 */

			free(tmpConfigBuf);
			free(tmpConfigRBuf);

		} else {

			/*
			 * malloc succeeded; free the old buffers and use the new
			 * buffers
			 */

			configBufLen = CONFIG_BUF_LEN;

			free(configBuf);
			free(configRBuf);

			configBuf = tmpConfigBuf;
			configRBuf = tmpConfigRBuf;
		}
	}

	/* read in another block of chars */

	do {
		ret = fgets(configBuf + pos, configBufLen - pos - 1,
			    configFiles[curFileIndex].file);

		if (!ret) {
			/*
			 * if the file doesn't end in a newline, add one
			 * and trigger another read
			 */
			if (pos != 0) {
				strcpy(&configBuf[pos], "\n");
				ret = configBuf;
			} else
				break;
		}

		/* search for EOL in the new block of chars */

		for (i = pos; i < (configBufLen - 1); i++) {
			c = configBuf[i];

			if (c == '\0') break;

			if ((c == '\n') || (c == '\r')) {
				eolFound = 1;
				break;
			}
		}

		/*
		 * if we didn't find EOL, then grow the string and
		 * read in more
		 */

		if (!eolFound) {

			tmpConfigBuf = realloc(configBuf, configBufLen + CONFIG_BUF_LEN);
			tmpConfigRBuf = realloc(configRBuf, configBufLen + CONFIG_BUF_LEN);

			if (!tmpConfigBuf || !tmpConfigRBuf) {

				/*
				 * at least one of the reallocations failed; use the
				 * new allocation that succeeded, but we have to
				 * fallback to the previous configBufLen size and use
				 * the string we have, even though we don't have an
				 * EOL
				 */

				if (tmpConfigBuf) configBuf = tmpConfigBuf;
				if (tmpConfigRBuf) configRBuf = tmpConfigRBuf;

				break;

			} else {

				/* reallocation succeeded */

				configBuf = tmpConfigBuf;
				configRBuf = tmpConfigRBuf;
				pos = i;
				configBufLen += CONFIG_BUF_LEN;
			}
		}

	} while (!eolFound);

	return ret;
}

/* 
 * xf86getToken --
 *      Read next Token from the config file. Handle the global variable
 *      pushToken.
 */
int
xf86getToken (xf86ConfigSymTabRec * tab)
{
	int c, i;

	/* 
	 * First check whether pushToken has a different value than LOCK_TOKEN.
	 * In this case rBuf[] contains a valid STRING/TOKEN/NUMBER. But in the
	 * oth * case the next token must be read from the input.
	 */
	if (pushToken == EOF_TOKEN)
		return EOF_TOKEN;
	else if (pushToken == LOCK_TOKEN)
	{
		/*
		 * eol_seen is only set for the first token after a newline.
		 */
		eol_seen = 0;

		c = configBuf[configPos];

		/* 
		 * Get start of next Token. EOF is handled,
		 * whitespaces are skipped. 
		 */

again:
		if (!c)
		{
			char *ret;
			if (numFiles > 0)
				ret = xf86getNextLine();
			else {
				if (builtinConfig[builtinIndex] == NULL)
					ret = NULL;
				else {
					ret = strncpy(configBuf, builtinConfig[builtinIndex],
							CONFIG_BUF_LEN);
					builtinIndex++;
				}
			}
			if (ret == NULL)
			{
				/*
				 * if necessary, move to the next file and
				 * read the first line
				 */
				if (curFileIndex + 1 < numFiles) {
					curFileIndex++;
					configLineNo = 0;
					goto again;
				}
				else
					return pushToken = EOF_TOKEN;
			}
			configLineNo++;
			configPos = 0;
			eol_seen = 1;
		}

		i = 0;
		for (;;) {
			c = configBuf[configPos++];
			configRBuf[i++] = c;
			switch (c) {
				case ' ':
				case '\t':
				case '\r':
					continue;
				case '\n':
					i = 0;
					continue;
			}
			break;
		}
		if (c == '\0')
			goto again;

		if (c == '#')
		{
			do
			{
				configRBuf[i++] = (c = configBuf[configPos++]);
			}
			while ((c != '\n') && (c != '\r') && (c != '\0'));
			configRBuf[i] = '\0';
			/* XXX no private copy.
			 * Use xf86addComment when setting a comment.
			 */
			val.str = configRBuf;
			return COMMENT;
		}

		/* GJA -- handle '-' and ','  * Be careful: "-hsync" is a keyword. */
		else if ((c == ',') && !isalpha (configBuf[configPos]))
		{
			return COMMA;
		}
		else if ((c == '-') && !isalpha (configBuf[configPos]))
		{
			return DASH;
		}

		/* 
		 * Numbers are returned immediately ...
		 */
		if (isdigit (c))
		{
			int base;

			if (c == '0')
				if ((configBuf[configPos] == 'x') ||
					(configBuf[configPos] == 'X'))
                                {
					base = 16;
                                        val.numType = PARSE_HEX;
                                }
				else
                                {
					base = 8;
                                        val.numType = PARSE_OCTAL;
                                }
			else
                        {
				base = 10;
                                val.numType = PARSE_DECIMAL;
                        }

			configRBuf[0] = c;
			i = 1;
			while (isdigit (c = configBuf[configPos++]) ||
				   (c == '.') || (c == 'x') || (c == 'X') ||
				   ((base == 16) && (((c >= 'a') && (c <= 'f')) ||
									 ((c >= 'A') && (c <= 'F')))))
				configRBuf[i++] = c;
			configPos--;		/* GJA -- one too far */
			configRBuf[i] = '\0';
			val.num = strtoul (configRBuf, NULL, 0);
			val.realnum = atof (configRBuf);
			return NUMBER;
		}

		/* 
		 * All Strings START with a \" ...
		 */
		else if (c == '\"')
		{
			i = -1;
			do
			{
				configRBuf[++i] = (c = configBuf[configPos++]);
			}
			while ((c != '\"') && (c != '\n') && (c != '\r') && (c != '\0'));
			configRBuf[i] = '\0';
			val.str = malloc (strlen (configRBuf) + 1);
			strcpy (val.str, configRBuf);	/* private copy ! */
			return STRING;
		}

		/* 
		 * ... and now we MUST have a valid token.  The search is
		 * handled later along with the pushed tokens.
		 */
		else
		{
			configRBuf[0] = c;
			i = 0;
			do
			{
				configRBuf[++i] = (c = configBuf[configPos++]);
			}
			while ((c != ' ') && (c != '\t') && (c != '\n') && (c != '\r') && (c != '\0') && (c != '#'));
			--configPos;
			configRBuf[i] = '\0';
			i = 0;
		}

	}
	else
	{

		/* 
		 * Here we deal with pushed tokens. Reinitialize pushToken again. If
		 * the pushed token was NUMBER || STRING return them again ...
		 */
		int temp = pushToken;
		pushToken = LOCK_TOKEN;

		if (temp == COMMA || temp == DASH)
			return temp;
		if (temp == NUMBER || temp == STRING)
			return temp;
	}

	/* 
	 * Joop, at last we have to lookup the token ...
	 */
	if (tab)
	{
		i = 0;
		while (tab[i].token != -1)
			if (xf86nameCompare (configRBuf, tab[i].name) == 0)
				return tab[i].token;
			else
				i++;
	}

	return ERROR_TOKEN;		/* Error catcher */
}

int
xf86getSubToken (char **comment)
{
	int token;

	for (;;) {
		token = xf86getToken(NULL);
		if (token == COMMENT) {
			if (comment)
				*comment = xf86addComment(*comment, val.str);
		}
		else
			return token;
	}
	/*NOTREACHED*/
}

int
xf86getSubTokenWithTab (char **comment, xf86ConfigSymTabRec *tab)
{
	int token;

	for (;;) {
		token = xf86getToken(tab);
		if (token == COMMENT) {
			if (comment)
				*comment = xf86addComment(*comment, val.str);
		}
		else
			return token;
	}
	/*NOTREACHED*/
}

void
xf86unGetToken (int token)
{
	pushToken = token;
}

char *
xf86tokenString (void)
{
	return configRBuf;
}

int
xf86pathIsAbsolute(const char *path)
{
	if (path && path[0] == '/')
		return 1;
	return 0;
}

/* A path is "safe" if it is relative and if it contains no ".." elements. */
int
xf86pathIsSafe(const char *path)
{
	if (xf86pathIsAbsolute(path))
		return 0;

	/* Compare with ".." */
	if (!strcmp(path, ".."))
		return 0;

	/* Look for leading "../" */
	if (!strncmp(path, "../", 3))
		return 0;

	/* Look for trailing "/.." */
	if ((strlen(path) > 3) && !strcmp(path + strlen(path) - 3, "/.."))
		return 0;

	/* Look for "/../" */
	if (strstr(path, "/../"))
		return 0;

	return 1;
}

/*
 * This function substitutes the following escape sequences:
 *
 *    %A    cmdline argument as an absolute path (must be absolute to match)
 *    %R    cmdline argument as a relative path
 *    %S    cmdline argument as a "safe" path (relative, and no ".." elements)
 *    %X    default config file name ("xorg.conf")
 *    %H    hostname
 *    %E    config file environment ($XORGCONFIG) as an absolute path
 *    %F    config file environment ($XORGCONFIG) as a relative path
 *    %G    config file environment ($XORGCONFIG) as a safe path
 *    %P    projroot
 *    %C    sysconfdir
 *    %D    datadir
 *    %%    %
 */

#ifndef XCONFIGFILE
#define XCONFIGFILE	"xorg.conf"
#endif
#ifndef XCONFIGDIR
#define XCONFIGDIR	"xorg.conf.d"
#endif
#ifndef XCONFIGSUFFIX
#define XCONFIGSUFFIX	".conf"
#endif
#ifndef PROJECTROOT
#define PROJECTROOT	"/usr/X11R6"
#endif
#ifndef SYSCONFDIR
#define SYSCONFDIR	PROJECTROOT "/etc"
#endif
#ifndef DATADIR
#define DATADIR		PROJECTROOT "/share"
#endif
#ifndef XCONFENV
#define XCONFENV	"XORGCONFIG"
#endif

#define BAIL_OUT		do {									\
							free(result);				\
							return NULL;						\
						} while (0)

#define CHECK_LENGTH	do {									\
							if (l > PATH_MAX) {					\
								BAIL_OUT;						\
							}									\
						} while (0)

#define APPEND_STR(s)	do {									\
							if (strlen(s) + l > PATH_MAX) {		\
								BAIL_OUT;						\
							} else {							\
								strcpy(result + l, s);			\
								l += strlen(s);					\
							}									\
						} while (0)

static char *
DoSubstitution(const char *template, const char *cmdline, const char *projroot,
				int *cmdlineUsed, int *envUsed,
				const char *XConfigFile)
{
	char *result;
	int i, l;
	static const char *env = NULL;
	static char *hostname = NULL;

	if (!template)
		return NULL;

	if (cmdlineUsed)
		*cmdlineUsed = 0;
	if (envUsed)
		*envUsed = 0;

	result = malloc(PATH_MAX + 1);
	l = 0;
	for (i = 0; template[i]; i++) {
		if (template[i] != '%') {
			result[l++] = template[i];
			CHECK_LENGTH;
		} else {
			switch (template[++i]) {
			case 'A':
				if (cmdline && xf86pathIsAbsolute(cmdline)) {
					APPEND_STR(cmdline);
					if (cmdlineUsed)
						*cmdlineUsed = 1;
				} else
					BAIL_OUT;
				break;
			case 'R':
				if (cmdline && !xf86pathIsAbsolute(cmdline)) {
					APPEND_STR(cmdline);
					if (cmdlineUsed)
						*cmdlineUsed = 1;
				} else 
					BAIL_OUT;
				break;
			case 'S':
				if (cmdline && xf86pathIsSafe(cmdline)) {
					APPEND_STR(cmdline);
					if (cmdlineUsed)
						*cmdlineUsed = 1;
				} else 
					BAIL_OUT;
				break;
			case 'X':
				APPEND_STR(XConfigFile);
				break;
			case 'H':
				if (!hostname) {
					if ((hostname = malloc(MAXHOSTNAMELEN + 1))) {
						if (gethostname(hostname, MAXHOSTNAMELEN) == 0) {
							hostname[MAXHOSTNAMELEN] = '\0';
						} else {
							free(hostname);
							hostname = NULL;
						}
					}
				}
				if (hostname)
					APPEND_STR(hostname);
				break;
			case 'E':
				if (!env)
					env = getenv(XCONFENV);
				if (env && xf86pathIsAbsolute(env)) {
					APPEND_STR(env);
					if (envUsed)
						*envUsed = 1;
				} else
					BAIL_OUT;
				break;
			case 'F':
				if (!env)
					env = getenv(XCONFENV);
				if (env && !xf86pathIsAbsolute(env)) {
					APPEND_STR(env);
					if (envUsed)
						*envUsed = 1;
				} else
					BAIL_OUT;
				break;
			case 'G':
				if (!env)
					env = getenv(XCONFENV);
				if (env && xf86pathIsSafe(env)) {
					APPEND_STR(env);
					if (envUsed)
						*envUsed = 1;
				} else
					BAIL_OUT;
				break;
			case 'P':
				if (projroot && xf86pathIsAbsolute(projroot))
					APPEND_STR(projroot);
				else
					BAIL_OUT;
				break;
			case 'C':
				APPEND_STR(SYSCONFDIR);
				break;
			case 'D':
				APPEND_STR(DATADIR);
				break;
			case '%':
				result[l++] = '%';
				CHECK_LENGTH;
				break;
			default:
				fprintf(stderr, "invalid escape %%%c found in path template\n",
						template[i]);
				BAIL_OUT;
				break;
			}
		}
	}
#ifdef DEBUG
	fprintf(stderr, "Converted `%s' to `%s'\n", template, result);
#endif
	return result;
}

/*
 * Given some searching parameters, locate and open the xorg config file.
 */
static char *
OpenConfigFile(const char *path, const char *cmdline, const char *projroot,
	       const char *confname)
{
	char *filepath = NULL;
	char *pathcopy;
	const char *template;
	int cmdlineUsed = 0;
	FILE *file = NULL;

	pathcopy = strdup(path);
	for (template = strtok(pathcopy, ","); template && !file;
	     template = strtok(NULL, ",")) {
		filepath = DoSubstitution(template, cmdline, projroot,
					  &cmdlineUsed, NULL, confname);
		if (!filepath)
			continue;
		if (cmdline && !cmdlineUsed) {
			free(filepath);
			filepath = NULL;
			continue;
		}
		file = fopen(filepath, "r");
		if (!file) {
			free(filepath);
			filepath = NULL;
		}
	}

	free(pathcopy);
	if (file) {
		configFiles[numFiles].file = file;
		configFiles[numFiles].path = strdup(filepath);
		numFiles++;
	}
	return filepath;
}

/*
 * Match non-hidden files in the xorg config directory with a .conf
 * suffix. This filter is passed to scandir(3).
 */
static int
ConfigFilter(const struct dirent *de)
{
	const char *name = de->d_name;
	size_t len;
	size_t suflen = strlen(XCONFIGSUFFIX);

	if (!name || name[0] == '.')
		return 0;
	len = strlen(name);
	if(len <= suflen)
		return 0;
	if (strcmp(&name[len-suflen], XCONFIGSUFFIX) != 0)
		return 0;
	return 1;
}

static Bool
AddConfigDirFiles(const char *dirpath, struct dirent **list, int num)
{
	int i;
	Bool openedFile = FALSE;
	Bool warnOnce = FALSE;

	for (i = 0; i < num; i++) {
		char *path;
		FILE *file;

		if (numFiles >= CONFIG_MAX_FILES) {
			if (!warnOnce) {
				ErrorF("Maximum number of configuration "
				       "files opened\n");
				warnOnce = TRUE;
			}
			free(list[i]);
			continue;
		}

		path = malloc(PATH_MAX + 1);
		snprintf(path, PATH_MAX + 1, "%s/%s", dirpath,
			 list[i]->d_name);
		free(list[i]);
		file = fopen(path, "r");
		if (!file) {
			free(path);
			continue;
		}
		openedFile = TRUE;

		configFiles[numFiles].file = file;
		configFiles[numFiles].path = path;
		numFiles++;
	}

	return openedFile;
}

/*
 * Given some searching parameters, locate and open the xorg config
 * directory. The directory does not need to contain config files.
 */
static char *
OpenConfigDir(const char *path, const char *cmdline, const char *projroot,
	      const char *confname)
{
	char *dirpath, *pathcopy;
	const char *template;
	Bool found = FALSE;
	int cmdlineUsed = 0;

	pathcopy = strdup(path);
	for (template = strtok(pathcopy, ","); template && !found;
	     template = strtok(NULL, ",")) {
		struct dirent **list = NULL;
		int num;

		dirpath = DoSubstitution(template, cmdline, projroot,
					 &cmdlineUsed, NULL, confname);
		if (!dirpath)
			continue;
		if (cmdline && !cmdlineUsed) {
			free(dirpath);
			dirpath = NULL;
			continue;
		}

		/* match files named *.conf */
		num = scandir(dirpath, &list, ConfigFilter, alphasort);
		found = AddConfigDirFiles(dirpath, list, num);
		if (!found) {
			free(dirpath);
			dirpath = NULL;
			free(list);
		}
	}

	free(pathcopy);
	return dirpath;
}

/*
 * xf86initConfigFiles -- Setup global variables and buffers.
 */
void
xf86initConfigFiles(void)
{
	curFileIndex = 0;
	configPos = 0;
	configLineNo = 0;
	pushToken = LOCK_TOKEN;

	configBuf = malloc(CONFIG_BUF_LEN);
	configRBuf = malloc(CONFIG_BUF_LEN);
	configBuf[0] = '\0';	/* sanity ... */
}

/*
 * xf86openConfigFile --
 *
 * This function take a config file search path (optional), a command-line
 * specified file name (optional) and the ProjectRoot path (optional) and
 * locates and opens a config file based on that information.  If a
 * command-line file name is specified, then this function fails if none
 * of the located files.
 *
 * The return value is a pointer to the actual name of the file that was
 * opened.  When no file is found, the return value is NULL.
 *
 * The escape sequences allowed in the search path are defined above.
 *
 */

#ifndef DEFAULT_CONF_PATH
#define DEFAULT_CONF_PATH	"/etc/X11/%S," \
							"%P/etc/X11/%S," \
							"/etc/X11/%G," \
							"%P/etc/X11/%G," \
							"/etc/X11/%X-%M," \
							"/etc/X11/%X," \
							"/etc/%X," \
							"%P/etc/X11/%X.%H," \
							"%P/etc/X11/%X-%M," \
							"%P/etc/X11/%X," \
							"%P/lib/X11/%X.%H," \
							"%P/lib/X11/%X-%M," \
							"%P/lib/X11/%X"
#endif

const char *
xf86openConfigFile(const char *path, const char *cmdline, const char *projroot)
{
	if (!path || !path[0])
		path = DEFAULT_CONF_PATH;
	if (!projroot || !projroot[0])
		projroot = PROJECTROOT;

	/* Search for a config file */
	configPath = OpenConfigFile(path, cmdline, projroot, XCONFIGFILE);
	return configPath;
}

/*
 * xf86openConfigDirFiles --
 *
 * This function take a config directory search path (optional), a
 * command-line specified directory name (optional) and the ProjectRoot path
 * (optional) and locates and opens a config directory based on that
 * information.  If a command-line name is specified, then this function
 * fails if it is not found.
 *
 * The return value is a pointer to the actual name of the direcoty that was
 * opened.  When no directory is found, the return value is NULL.
 *
 * The escape sequences allowed in the search path are defined above.
 *
 */
const char *
xf86openConfigDirFiles(const char *path, const char *cmdline,
		       const char *projroot)
{
	if (!path || !path[0])
		path = DEFAULT_CONF_PATH;
	if (!projroot || !projroot[0])
		projroot = PROJECTROOT;

	/* Search for the multiconf directory */
	configDirPath = OpenConfigDir(path, cmdline, projroot, XCONFIGDIR);
	return configDirPath;
}

void
xf86closeConfigFile (void)
{
	int i;

	free (configPath);
	configPath = NULL;
	free (configDirPath);
	configDirPath = NULL;
	free (configRBuf);
	configRBuf = NULL;
	free (configBuf);
	configBuf = NULL;

	if (numFiles == 0) {
		builtinConfig = NULL;
		builtinIndex = 0;
	}
	for (i = 0; i < numFiles; i++) {
		fclose(configFiles[i].file);
		configFiles[i].file = NULL;
		free(configFiles[i].path);
		configFiles[i].path = NULL;
	}
	numFiles = 0;
}

void
xf86setBuiltinConfig(const char *config[])
{
	builtinConfig = config;
}

void
xf86parseError (char *format,...)
{
	va_list ap;
	char *filename = numFiles ? configFiles[curFileIndex].path :
			 "<builtin configuration>";

	ErrorF ("Parse error on line %d of section %s in file %s\n\t",
		 configLineNo, configSection, filename);
	va_start (ap, format);
	VErrorF (format, ap);
	va_end (ap);

	ErrorF ("\n");
}

void
xf86validationError (char *format,...)
{
	va_list ap;
	char *filename = numFiles ? configFiles[curFileIndex].path :
			 "<builtin configuration>";

	ErrorF ("Data incomplete in file %s\n\t", filename);
	va_start (ap, format);
	VErrorF (format, ap);
	va_end (ap);

	ErrorF ("\n");
}

void
xf86setSection (char *section)
{
	free(configSection);
	configSection = strdup(section);
}

/* 
 * xf86getToken --
 *  Lookup a string if it is actually a token in disguise.
 */
int
xf86getStringToken (xf86ConfigSymTabRec * tab)
{
	return StringToToken (val.str, tab);
}

static int
StringToToken (char *str, xf86ConfigSymTabRec * tab)
{
	int i;

	for (i = 0; tab[i].token != -1; i++)
	{
		if (!xf86nameCompare (tab[i].name, str))
			return tab[i].token;
	}
	return ERROR_TOKEN;
}


/* 
 * Compare two names.  The characters '_', ' ', and '\t' are ignored
 * in the comparison.
 */
int
xf86nameCompare (const char *s1, const char *s2)
{
	char c1, c2;

	if (!s1 || *s1 == 0) {
		if (!s2 || *s2 == 0)
			return 0;
		else
			return 1;
		}

	while (*s1 == '_' || *s1 == ' ' || *s1 == '\t')
		s1++;
	while (*s2 == '_' || *s2 == ' ' || *s2 == '\t')
		s2++;
	c1 = (isupper (*s1) ? tolower (*s1) : *s1);
	c2 = (isupper (*s2) ? tolower (*s2) : *s2);
	while (c1 == c2)
	{
		if (c1 == '\0')
			return 0;
		s1++;
		s2++;
		while (*s1 == '_' || *s1 == ' ' || *s1 == '\t')
			s1++;
		while (*s2 == '_' || *s2 == ' ' || *s2 == '\t')
			s2++;
		c1 = (isupper (*s1) ? tolower (*s1) : *s1);
		c2 = (isupper (*s2) ? tolower (*s2) : *s2);
	}
	return c1 - c2;
}

char *
xf86addComment(char *cur, char *add)
{
	char *str;
	int len, curlen, iscomment, hasnewline = 0, endnewline;

	if (add == NULL || add[0] == '\0')
		return cur;

	if (cur) {
		curlen = strlen(cur);
		if (curlen)
		    hasnewline = cur[curlen - 1] == '\n';
		eol_seen = 0;
	}
	else
		curlen = 0;

	str = add;
	iscomment = 0;
	while (*str) {
	    if (*str != ' ' && *str != '\t')
		break;
	    ++str;
	}
	iscomment = (*str == '#');

	len = strlen(add);
	endnewline = add[len - 1] == '\n';
	len +=  1 + iscomment + (!hasnewline) + (!endnewline) + eol_seen;

	if ((str = realloc(cur, len + curlen)) == NULL)
		return cur;

	cur = str;

	if (eol_seen || (curlen && !hasnewline))
		cur[curlen++] = '\n';
	if (!iscomment)
		cur[curlen++] = '#';
	strcpy(cur + curlen, add);
	if (!endnewline)
		strcat(cur, "\n");

	return cur;
}

Bool
xf86getBoolValue(Bool *val, const char *str)
{
	if (!val || !str)
		return FALSE;
	if (*str == '\0') {
		*val = TRUE;
	} else {
		if (xf86nameCompare(str, "1") == 0)
			*val = TRUE;
		else if (xf86nameCompare(str, "on") == 0)
			*val = TRUE;
		else if (xf86nameCompare(str, "true") == 0)
			*val = TRUE;
		else if (xf86nameCompare(str, "yes") == 0)
			*val = TRUE;
		else if (xf86nameCompare(str, "0") == 0)
			*val = FALSE;
		else if (xf86nameCompare(str, "off") == 0)
			*val = FALSE;
		else if (xf86nameCompare(str, "false") == 0)
			*val = FALSE;
		else if (xf86nameCompare(str, "no") == 0)
			*val = FALSE;
		else
			return FALSE;
	}
	return TRUE;
}
