/*
 * Copyright © 2003, 2004, 2005 David H. Dawes.
 * Copyright © 2003, 2004, 2005 X-Oz Technologies.
 * All rights reserved.
 *
 * 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:
 *
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions, and the following disclaimer.
 *
 *  2. 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.
 *
 *  3. The end-user documentation included with the redistribution,
 *     if any, must include the following acknowledgment: "This product
 *     includes software developed by X-Oz Technologies
 *     (http://www.x-oz.com/)."  Alternately, this acknowledgment may
 *     appear in the software itself, if and wherever such third-party
 *     acknowledgments normally appear.
 *
 *  4. Except as contained in this notice, the name of X-Oz
 *     Technologies shall not be used in advertising or otherwise to
 *     promote the sale, use or other dealings in this Software without
 *     prior written authorization from X-Oz Technologies.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 X-OZ TECHNOLOGIES OR ITS 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.
 *
 * Author: David Dawes <dawes@x-oz.com>.
 */

/* $XFree86: xc/programs/Xserver/hw/xfree86/common/xf86AutoConfig.c,v 1.11 2006/03/16 16:49:55 dawes Exp $ */

#include "xf86.h"
#include "xf86Parser.h"
#include "xf86tokens.h"
#include "xf86Config.h"
#include "xf86Priv.h"
#include "xf86_OSlib.h"
#include "xf86Bus.h"

#if defined(__sparc__) && !defined(__OpenBSD__)
#define SBUS_SUPPORT
#endif

/*
 * Sections for the default built-in configuration.
 */

#define BUILTIN_MODULE_SECTION \
	"Section \"Module\"\n" \
	"\tIdentifier\t\"Builtin Default Modules\"\n" \
	"\tLoad\t\"extmod\"\n" \
	"\tLoad\t\"dbe\"\n" \
	"\tLoad\t\"glx\"\n" \
	"\tLoad\t\"freetype\"\n" \
	"EndSection\n\n"

#define BUILTIN_DEVICE_NAME \
	"\"Builtin Default %s Device %d\""

#define BUILTIN_DEVICE_SECTION_PRE \
	"Section \"Device\"\n" \
	"\tIdentifier\t" BUILTIN_DEVICE_NAME "\n" \
	"\tDriver\t\"%s\"\n%s"

#define BUILTIN_DEVICE_SECTION_BUSID \
	"\tBusID\t\"%s\"\n"

#define BUILTIN_DEVICE_SECTION_POST \
	"EndSection\n\n"

#define BUILTIN_DEVICE_SECTION \
	BUILTIN_DEVICE_SECTION_PRE \
	BUILTIN_DEVICE_SECTION_POST

#define BUILTIN_MONITOR_NAME \
	"\"Builtin Default Monitor\""

#define BUILTIN_MONITOR_SECTION \
	"Section \"Monitor\"\n" \
	"\tIdentifier\t" BUILTIN_MONITOR_NAME "\n" \
	"\tOption\t\"TargetRefresh\"\t\"75.0\"\n" \
	"EndSection\n\n"

#define BUILTIN_SCREEN_NAME \
	"\"Builtin Default %s Screen %d\""

#define BUILTIN_SCREEN_SECTION \
	"Section \"Screen\"\n" \
	"\tIdentifier\t" BUILTIN_SCREEN_NAME "\n" \
	"\tDevice\t" BUILTIN_DEVICE_NAME "\n" \
	"\tMonitor\t" BUILTIN_MONITOR_NAME "\n" \
	"EndSection\n\n"

#define BUILTIN_LAYOUT_SECTION_PRE \
	"Section \"ServerLayout\"\n" \
	"\tIdentifier\t\"Builtin Default Layout\"\n"

#define BUILTIN_LAYOUT_SCREEN_LINE \
	"\tScreen\t" BUILTIN_SCREEN_NAME "\n"

#define BUILTIN_LAYOUT_SECTION_POST \
	"EndSection\n\n"


#ifndef GET_CONFIG_CMD
#define GET_CONFIG_CMD			"getconfig"
#endif

#ifndef GETCONFIG_DIR
#define GETCONFIG_DIR			PROJECTROOT "/lib/X11/getconfig"
#endif

#define GETCONFIG_WHITESPACE		" \t\n"

static const char **builtinConfig = NULL;
static int builtinLines = 0;
static const char *deviceList[] = {
	"fbdev",
	"vesa",
	"vga",
	NULL
};

/*
 * A built-in config file is stored as an array of strings, with each string
 * representing a single line.  AppendToConfig() breaks up the string "s"
 * into lines, and appends those lines it to builtinConfig.
 */

static void
AppendToList(const char *s, const char ***list, int *lines)
{
    char *str, *newstr, *p;

    str = xnfstrdup(s);
    for (p = strtok(str, "\n"); p; p = strtok(NULL, "\n")) {
	(*lines)++;
	*list = xnfrealloc(*list, (*lines + 1) * sizeof(**list));
	newstr = xnfalloc(strlen(p) + 2);
	strcpy(newstr, p);
	strcat(newstr, "\n");
	(*list)[*lines - 1] = newstr;
	(*list)[*lines] = NULL;
    }
    xfree(str);
}

static void
FreeList(const char ***list, int *lines)
{
    int i;

    for (i = 0; i < *lines; i++) {
	if ((*list)[i])
	    xfree((*list)[i]);
    }
    xfree(*list);
    *list = NULL;
    *lines = 0;
}

static void
FreeConfig(void)
{
    FreeList(&builtinConfig, &builtinLines);
}

static void
AppendToConfig(const char *s)
{
    AppendToList(s, &builtinConfig, &builtinLines);
}

Bool
xf86AutoConfig(void)
{
    const char **p;
    char buf[1024];
    pciVideoPtr *pciptr, info = NULL;
    char *driver = NULL;
    FILE *gp = NULL;
    XF86ConfigPtr pConfig;
    char *busId = NULL, *busIdSpec;
#ifdef SBUS_SUPPORT
    char *promPath = NULL;
#endif

    /* Find the primary device, and get some information about it. */
    if (xf86PciVideoInfo) {
	for (pciptr = xf86PciVideoInfo; (info = *pciptr); pciptr++) {
	    if (xf86IsPrimaryPci(info)) {
		char busnum[8];

		xf86FormatPciBusNumber(info->bus, busnum);
		xasprintf(&busId, "PCI:%s:%d:%d",
			  busnum, info->device, info->func);
		break;
	    }
	}
	if (!info) {
	    xf86MsgVerb(X_INFO, 3, "AutoConfig: Primary device is not PCI.\n");
	}
    } else {
	xf86MsgVerb(X_INFO, 3, "AutoConfig: xf86PciVideoInfo is not set.\n");
    }
#ifdef SBUS_SUPPORT
    if (!busId) {
	sbusDevicePtr psdp, *psdpp;
	Bool useProm = FALSE;

	if (xf86SbusInfo) {
	    if (sparcPromInit() >= 0)
		useProm = TRUE;
	    for (psdpp = xf86SbusInfo; (psdp = *psdpp); psdpp++) {
		if (psdp->fd == -2)
		    continue;
		if (useProm && psdp->node.node)
		    promPath = sparcPromNode2Pathname(&psdp->node);
		else
		    xasprintf(&promPath, "fb%d", psdp->fbNum);

		xasprintf(&busId, "SBUS:%s", promPath);
		break;
	    }
	    if (useProm)
		sparcPromClose();
	} else {
	    xf86MsgVerb(X_INFO, 3, "AutoConfig: xf86SbusInfo is not set.\n");
	}
    }
#endif

    if (!busId) {
	xf86Msg(X_WARNING,
		"AutoConfig: Cannot detect the primary video device.\n");
	busIdSpec = "";
    } else {
	char *tmp;
	char *path = NULL, *a, *b;
	char *searchPath = NULL;

	/*
	 * Look for the getconfig program first in the
	 * xf86FilePaths->modulePath directories, then in BINDIR.
	 * If it isn't found in any of those locations, just use the normal
	 * search path.
	 */

	a = xstrdup(xf86FilePaths->modulePath);
	if (a) {
	    b = strtok(a, ",");
	    while (b) {
		if (path) {
		    xfree(path);
		    path = NULL;
		}
		xasprintf(&path, "%s/%s", b, GET_CONFIG_CMD);
		if (path && access(path, X_OK) == 0)
		    break;
		b = strtok(NULL, ",");
	    }
	    if (!b && path) {
		xfree(path);
		path = NULL;
	    }
	    xfree(a);
	}

#ifdef BINDIR
	if (!path) {
	    path = xnfstrdup(BINDIR "/" GET_CONFIG_CMD);
	    if (access(path, X_OK) != 0) {
		xfree(path);
		path = NULL;
	    }
	}
#endif

	if (!path)
	    path = xnfstrdup(GET_CONFIG_CMD);

	/*
	 * Build up the config file directory search path:
	 *
	 * /etc/X11
	 * PROJECTROOT/etc/X11
	 * xf86FilePaths->modulePath
	 * PROJECTROOT/lib/X11/getconfig  (GETCONFIG_DIR)
	 */

	searchPath = xnfalloc(strlen("/etc/X11") + 1 +
			      strlen(PROJECTROOT "/etc/X11") + 1 +
			      (xf86FilePaths->modulePath ?
				    strlen(xf86FilePaths->modulePath) : 0)
				+ 1 +
			      strlen(GETCONFIG_DIR) + 1);
	strcpy(searchPath, "/etc/X11," PROJECTROOT "/etc/X11,");
	if (xf86FilePaths->modulePath && *xf86FilePaths->modulePath) {
	    strcat(searchPath, xf86FilePaths->modulePath);
	    strcat(searchPath, ",");
	}
	strcat(searchPath, GETCONFIG_DIR);

	xf86MsgVerb(X_INFO, 3, "AutoConfig: Primary is %s\n", busId);

	if (info) {
	    snprintf(buf, sizeof(buf), "%s"
#ifdef DEBUG
		 " -D"
#endif
		 " -X %d"
		 " -I %s"
		 " -v 0x%04x -d 0x%04x -r 0x%02x -s 0x%04x"
		 " -b 0x%04x -c 0x%04x",
		 path,
		 (unsigned int)xf86GetVersion(),
		 searchPath,
		 info->vendor, info->chipType, info->chipRev,
		 info->subsysVendor, info->subsysCard,
		 info->class << 8 | info->subclass);
	}
#ifdef SBUS_SUPPORT
	else if (promPath) {
	    snprintf(buf, sizeof(buf), "%s"
#ifdef DEBUG
		 " -D"
#endif
		 " -X %d"
		 " -I %s"
		 " -S %s",
		 path,
		 (unsigned int)xf86GetVersion(),
		 searchPath,
		 promPath);
	    xfree(promPath);
	}
#endif
	xf86MsgVerb(X_INFO, 3, "AutoConfig: Running \"%s\".\n", buf);
	gp = Popen(buf, "r");
	if (gp) {
	    if (fgets(buf, sizeof(buf) - 1, gp)) {
		buf[strlen(buf) - 1] = '\0';
		tmp = strtok(buf, GETCONFIG_WHITESPACE);
		if (tmp)
		    driver = xnfstrdup(tmp);
	    }
	}
	xfree(path);
	xfree(searchPath);

	xasprintf(&busIdSpec, BUILTIN_DEVICE_SECTION_BUSID, busId);
    }

    AppendToConfig(BUILTIN_MODULE_SECTION);
    AppendToConfig(BUILTIN_MONITOR_SECTION);

    if (driver) {
	snprintf(buf, sizeof(buf), BUILTIN_DEVICE_SECTION_PRE,
		 driver, 0, driver, busIdSpec);
	AppendToConfig(buf);
	xf86MsgVerb(X_INFO, 3, "AutoConfig: New driver is \"%s\".\n", driver);
	buf[0] = '\t';
	while (fgets(buf + 1, sizeof(buf) - 2, gp)) {
	    AppendToConfig(buf);
	    xf86MsgVerb(X_INFO, 3, "AutoConfig: Extra line: '%.*s'.\n",
			(int)strlen(buf) - 2, buf + 1);
	}
	AppendToConfig(BUILTIN_DEVICE_SECTION_POST);
	snprintf(buf, sizeof(buf), BUILTIN_SCREEN_SECTION,
		 driver, 0, driver, 0);
	AppendToConfig(buf);
    }

    if (gp)
	Pclose(gp);

    for (p = deviceList; *p; p++) {
	snprintf(buf, sizeof(buf), BUILTIN_DEVICE_SECTION,
		 *p, 0, *p, busIdSpec);
	AppendToConfig(buf);
	snprintf(buf, sizeof(buf), BUILTIN_SCREEN_SECTION, *p, 0, *p, 0);
	AppendToConfig(buf);
    }

    if (busId) {
	xfree(busId);
	xfree(busIdSpec);
    }

    AppendToConfig(BUILTIN_LAYOUT_SECTION_PRE);
    if (driver) {
	snprintf(buf, sizeof(buf), BUILTIN_LAYOUT_SCREEN_LINE, driver, 0);
	AppendToConfig(buf);
    }
    for (p = deviceList; *p; p++) {
	snprintf(buf, sizeof(buf), BUILTIN_LAYOUT_SCREEN_LINE, *p, 0);
	AppendToConfig(buf);
    }
    AppendToConfig(BUILTIN_LAYOUT_SECTION_POST);

#ifdef BUILTIN_EXTRA
    AppendToConfig(BUILTIN_EXTRA);
#endif

    if (driver)
	xfree(driver);

    xf86MsgVerb(X_DEFAULT, 0,
		"Using default built-in configuration (%d lines)\n",
		builtinLines);

    xf86MsgVerb(X_DEFAULT, 3, "--- Start of built-in configuration ---\n");
    for (p = builtinConfig; *p; p++)
	xf86ErrorFVerb(3, "\t%s", *p);
    xf86MsgVerb(X_DEFAULT, 3, "--- End of built-in configuration ---\n");

    xf86setBuiltinConfig(builtinConfig);
    pConfig = (XF86ConfigPtr)(xf86Info.config);
    xf86Info.config = xf86parseConfigFile(pConfig);
    FreeConfig();
    if (!xf86Info.config) {
	xf86Msg(X_ERROR, "Error parsing the built-in default configuration.\n");
	return FALSE;
    }
    return TRUE;
}

