/*
 * 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).
 */

#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>
#include <limits.h>

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

/* For PATH_MAX */
#include "misc.h"

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

#define CONFIG_BUF_LEN     1024
#define CONFIG_MAX_FILES   64

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 *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 xf86_lex_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;
}

static int
StringToToken(const char *str, const 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;
}

/*
 * xf86getToken --
 *      Read next Token from the config file. Handle the global variable
 *      pushToken.
 */
int
xf86getToken(const 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 {
                    strlcpy(configBuf,
                            builtinConfig[builtinIndex], CONFIG_BUF_LEN);
                    ret = configBuf;
                    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 private copy.
             * Use xf86addComment when setting a comment.
             */
            xf86_lex_val.str = strdup(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;
                    xf86_lex_val.numType = PARSE_HEX;
                }
                else {
                    base = 8;
                    xf86_lex_val.numType = PARSE_OCTAL;
                }
            else {
                base = 10;
                xf86_lex_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';
            xf86_lex_val.num = strtoul(configRBuf, NULL, 0);
            xf86_lex_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';
            xf86_lex_val.str = malloc(strlen(configRBuf) + 1);
            strcpy(xf86_lex_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)
        return StringToToken(configRBuf, tab);

    return ERROR_TOKEN;         /* Error catcher */
}

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

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

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

    for (;;) {
        token = xf86getToken(tab);
        if (token == COMMENT) {
            if (comment) {
                *comment = xf86addComment(*comment, xf86_lex_val.str);
                free(xf86_lex_val.str);
                xf86_lex_val.str = NULL;
            }
        }
        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
 *    %%    %
 */

#define XCONFIGSUFFIX	".conf"
#define XCONFENV	"XORGCONFIG"

#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;
            }
            continue;
        }

        path = malloc(PATH_MAX + 1);
        snprintf(path, PATH_MAX + 1, "%s/%s", dirpath, list[i]->d_name);
        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 = NULL, *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);
        if (num < 0) {
            list = NULL;
            num = 0;
        }
        found = AddConfigDirFiles(dirpath, list, num);
        if (!found) {
            free(dirpath);
            dirpath = NULL;
        }
        while (num--)
            free(list[num]);
        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 caller should
 * free() the returned value.
 *
 * 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

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 */
    return OpenConfigFile(path, cmdline, projroot, XCONFIGFILE);
}

/*
 * 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 directory that was
 * opened.  When no directory is found, the return value is NULL. The caller
 * should free() the returned value.
 *
 * The escape sequences allowed in the search path are defined above.
 *
 */
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 */
    return OpenConfigDir(path, cmdline, projroot, XCONFIGDIR);
}

void
xf86closeConfigFile(void)
{
    int i;

    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(const char *format, ...)
{
    va_list ap;
    const 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(const char *format, ...)
{
    va_list ap;
    const 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(const char *section)
{
    free(configSection);
    configSection = strdup(section);
}

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

/*
 * 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;
    } else if (!s2 || *s2 == 0) {
        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, const char *add)
{
    char *str;
    const char *cstr;
    int len, curlen, iscomment, hasnewline = 0, insnewline, 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;

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

    len = strlen(add);
    endnewline = add[len - 1] == '\n';

    insnewline = eol_seen || (curlen && !hasnewline);
    if (insnewline)
        len++;
    if (!iscomment)
        len++;
    if (!endnewline)
        len++;

    /* Allocate + 1 char for '\0' terminator. */
    str = realloc(cur, curlen + len + 1);
    if (!str)
        return cur;

    cur = str;

    if (insnewline)
        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;
}
