#include <alloca.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <newt.h>
#include <popt.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/vt.h>

#include "cpio.h"
#include "devices.h"
#include "install.h"
#include "intl.h"
#include "kickstart.h"
#include "log.h"
#include "net.h"
#include "run.h"
#include "scsi.h"
#include "windows.h"
#include "pci-probing/pciprobe.h"
#include "install.h"

#define MODULES_PATH "/modules/"


static char * plipDevice = NULL;		/* hack */

struct devnum {
    char * name;
    short major, minor;
    int isChar;
};

static struct devnum devices[] = {
    { "aztcd",		29,	0,	0 },
    { "bpcd",		41,	0,	0 },
    { "cdu31a",		15,	0,	0 },
    { "cdu535",		24,	0,	0 },
    { "cm206cd",	32,	0,	0 },
    { "fd0",     	2,	0,	0 },
    { "fd1",		2,	1,	0 },
    { "gscd",		16,	0,	0 },
    { "lp0",		6,	0,	1 },
    { "lp1",		6,	1,	1 },
    { "lp2",		6,	2,	1 },
    { "mcd",		23,	0,	0 },
    { "mcdx",		20,	0,	0 },
    { "nst0",		9,	128,	1 },
    { "optcd",		17,	0,	0 },
    { "sbpcd",		25,	0,	0 },
    { "scd0",		11,	0,	0 },
    { "scd1",		11,	1,	0 },
    { "sjcd",		18,	0,	0 },
};

int numDevices = sizeof(devices) / sizeof(struct devnum);

struct moduleOptions {
    char * arg;
    char * desc;
    char * defaults;
} ;

struct moduleOptions neOptions[] = {
   { "io=", N_("Base IO port:"), "0x300:0x280:0x320:0x340:0x360" },
   { "irq=", N_("IRQ level:"), NULL },
   { NULL, NULL, NULL }
} ;

struct moduleOptions de4x5Options[] = {
   { "io=", N_("Base IO port:"), "0x0b" },
   { NULL, NULL, NULL }
} ;

struct moduleOptions cdu31aOptions[] = {
   { "cdu31a_port=", N_("Base IO port:"), "" },
   { "cdu31a_irq=", N_("IRQ level:"), "" },
   { NULL, NULL, NULL }
} ;

struct moduleOptions cm206Options[] = {
   { "cm206=", N_("IO base, IRQ:"), "" },
   { NULL, NULL, NULL }
} ;

struct moduleOptions mcdOptions[] = {
   { "mcd=", N_("Base IO port:"), "" },
   { NULL, NULL, NULL }
} ;

struct moduleOptions optcdOptions[] = {
   { "optcd=", N_("Base IO port:"), "" },
   { NULL, NULL, NULL }
} ;

struct moduleOptions fdomainOptions[] = {
   { "setup_called=", N_("Use other options"), "1" },
   { "port_base=", N_("Base IO port:"), "0xd800" },
   { "interrupt_level=", N_("Interrupt level (IRQ):"), "10" },
   { NULL, NULL, NULL }
} ;

struct moduleOptions sbpcdOptions[] = {
   { "sbpcd=", N_("IO base, IRQ, label:"), "" },
   { NULL, NULL, NULL }
} ;

#define MODULE_AUTOPROBE	(1 << 0)
#define MODULE_FAKEAUTOPROBE	(1 << 1)

struct moduleInfo {
    char * name;
    int shouldAutoprobe;
    struct moduleOptions * options;
    int flags;
    char * defaultOptions;
} ;

/* keep this alphabetical! */
struct moduleInfo modules[] = { 
    { "8390", 1, NULL, 0, NULL },
    { "cdu31a", 0, cdu31aOptions, 0, NULL },
    { "cm206", 0, cm206Options, 0, NULL },
    { "de4x5", 1, de4x5Options, MODULE_AUTOPROBE, "io=0" },
    { "ds", 1, NULL, 0, NULL },
    { "fdomain", 1, fdomainOptions, 0, NULL },
    { "i82365", 1, NULL, 0, NULL },
    { "isofs", 1, NULL, 0, NULL },
    { "loop", 1, NULL, 0, NULL },
    { "lp", 1, NULL, 0, NULL },
    { "mcd", 0, mcdOptions, 0, NULL },
    { "ne", 0, neOptions, MODULE_FAKEAUTOPROBE, "io=0x300" },
    { "nfs", 1, NULL, 0, NULL },
    { "optcd", 0, optcdOptions, 0, NULL },
    { "pcmcia_core", 1, NULL, 0, NULL },
    { "sbpcd", 1, sbpcdOptions, 0, NULL },
    { "smbfs", 1, NULL, 0, NULL },
    { "tcic", 1, NULL, 0, NULL },
    { NULL, 0, NULL, 0, NULL }				/* sentinel */
} ;

struct driver {
    char * name;
    char * modules;
    int isLoaded;
    driverOkayFn okay;
    enum driverTypes type;
    enum driverMinor minor;
};

static int checkEthernetDev(struct driver * dev);
static int checkSCSIDev(struct driver * dev);
static int checkPlipDev(struct driver * dev);
static int checkTokenRingDev(struct driver * dev);

static struct driver drivers[] = {
    { "3com 3c509", "3c509", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "3com 3c515", "3c515", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "3com 3c59x (Vortex)", "3c59x", 0, checkEthernetDev,
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "3com 3c90x (Boomerang)", "3c59x", 0, checkEthernetDev,
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "3com 3c501", "3c501", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "3com 3c503", "8390:3c503", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "Allied Telesis AT1700", "at1700", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "Apricot 82596", "apricot", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "ATP", "atp", 0, checkEthernetDev,
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "Cabletron E2100", "8390:e2100", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "Digital 425,434,435,450,500", "de4x5", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "Digital DEPCA and EtherWORKS", "depca", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "Digital EtherWORKS 3", "ewrk3", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "Digital 21040 (Tulip)", "tulip", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "D-Link DE-600 pocket adapter", "de600", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "D-Link DE-620 pocket adapter", "de620", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "EPIC 100", "epic100", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "Eth 16i", "eth16i", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "HP10/100VG any LAN ", "hp100", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "HP LAN/AnyLan", "hp", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "HP PCLAN/plus", "8390:hp-plus", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "Intel EtherExpress", "eexpress", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "Intel EtherExpress Pro", "eepro", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "Intel EtherExpress Pro 100", "eepro100", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "Lance", "lance", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "NE2000 and compatible", "8390:ne", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "NE2000 PCI", "8390:ne2k-pci", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "NI 5210", "ni52", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "NI 6510", "ni65", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "PLIP (parallel port)", "plip", 0, checkPlipDev, 
		DRIVER_NET, DRIVER_MINOR_PLIP },
    { "SMC 9000 series", "smc9194", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "SMC Ultra", "8390:smc-ultra", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "SMC Ultra 32", "8390:smc-ultra32", 0, checkEthernetDev, 
		DRIVER_NET, DRIVER_MINOR_ETHERNET },
    { "Token Ring", "ibmtr", 0, checkTokenRingDev, 
		DRIVER_NET, DRIVER_MINOR_TR },
    { "WD8003, WD8013 and compatible", "8390:wd", 0, checkEthernetDev,
		DRIVER_NET, DRIVER_MINOR_ETHERNET },

    { "Adaptec 152x", "aha152x", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "Adaptec 1542", "aha1542", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "Adaptec 1740", "aha1740", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "Adaptec 2740, 2840, 2940", "aic7xxx", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "AdvanSys Adapters", "advansys", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "Always IN2000", "in2000", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "BusLogic Adapters", "BusLogic", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "DTC 3180/3280", "dtc", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "EATA DMA Adapters", "eata_dma", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "EATA PIO Adapters", "eata_pio", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "Future Domain TMC-885, TMC-950", "seagate", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "Future Domain TMC-16x0", "fdomain", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "ICP GDT RP Series", "gdth", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "Iomega PPA3 (parallel port Zip)", "ppa", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "NCR 5380", "g_NCR5380", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "NCR 53c406a", "NCR53c406a", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "NCR 53c7xx", "53c7,8xx", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "NCR 53C8xx PCI", "ncr53c8xx", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "Pro Audio Spectrum/Studio 16", "pas16", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "Qlogic FAS", "qlogicfas", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "Qlogic ISP", "qlogicisp", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "Seagate ST01/02", "seagate", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "Trantor T128/T128F/T228", "t128", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "UltraStor 14F/34F", "u14-34f", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "UltraStor 14F/24F/34F", "ultrastor", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },
    { "Western Digital wd7000", "wd7000", 0, checkSCSIDev,
		DRIVER_SCSI, DRIVER_MINOR_NONE },

    { "PCMCIA core support", "pcmcia_core", 0, NULL,
		DRIVER_PCMCIA, DRIVER_MINOR_NONE },
    { "PCMCIA card support", "ds", 0, NULL,
		DRIVER_PCMCIA, DRIVER_MINOR_NONE },
    { "PCMCIA i82365 controller", "i82365", 0, NULL,
		DRIVER_PCMCIA, DRIVER_MINOR_NONE },
    { "PCMCIA tcic controller", "tcic", 0, NULL,
		DRIVER_PCMCIA, DRIVER_MINOR_NONE },

    { "iso9660", "isofs", 0, NULL,
		DRIVER_FS, DRIVER_MINOR_NONE },
    { "Network File System (nfs)", "nfs", 0, NULL,
		DRIVER_FS, DRIVER_MINOR_NONE },
    { "Windows SMB", "smbfs", 0, NULL,
		DRIVER_FS, DRIVER_MINOR_NONE },

    { "Aztech CD", "aztcd", 0, NULL,
		DRIVER_CDROM, DRIVER_MINOR_NONE },
    { "Backpack CDROM", "bpcd", 0, NULL,
		DRIVER_CDROM, DRIVER_MINOR_NONE },
    { "Goldstar R420", "gscd", 0, NULL,
		DRIVER_CDROM, DRIVER_MINOR_NONE },
    { "Mitsumi", "mcd", 0, NULL,
		DRIVER_CDROM, DRIVER_MINOR_NONE },
    { "Mitsumi (alternate)", "mcdx", 0, NULL,
		DRIVER_CDROM, DRIVER_MINOR_NONE },
    { "Optics Storage 8000", "optcd", 0, NULL,
		DRIVER_CDROM, DRIVER_MINOR_NONE },
    { "Phillips CM206/CM260", "cm206:cdrom", 0, NULL,
		DRIVER_CDROM, DRIVER_MINOR_NONE },
    { "Sanyo", "sjcd", 0, NULL, 
		DRIVER_CDROM, DRIVER_MINOR_NONE },
    { "Sony CDU-31A", "cdu31a", 0, NULL, 
		DRIVER_CDROM, DRIVER_MINOR_NONE },
    { "Sony CDU-5xx", "sonycd535", 0, NULL, 
		DRIVER_CDROM, DRIVER_MINOR_NONE },
    { "SoundBlaster/Panasonic", "sbpcd", 0, NULL, 
		DRIVER_CDROM, DRIVER_MINOR_NONE },

    { "Loopback device", "loop", 0, NULL, DRIVER_OTHER, DRIVER_MINOR_NONE },
    { "Parallel Printer", "lp", 0, NULL, DRIVER_OTHER, DRIVER_MINOR_NONE },

    { NULL, NULL, 0, NULL, DRIVER_OTHER }, /* sentinel */
};

#define OPTIONS_SPECIFY (1)
#define OPTIONS_DEFAULT (0)
static char * driverOptions[] = { N_("Autoprobe"), N_("Specify options"),
				  NULL };

static const int numDrivers = (sizeof(drivers) / sizeof(struct driver)) - 1;

static int loadDeviceModule(struct driver * driver, 
			    struct driversLoaded ** drlist, int skipPrompts,
			    char * args);
static int getOptions(const char * name, int * argcp, char *** argvp,
			int skipPrompts);
static struct driversLoaded * allocDL(struct driversLoaded ** drlist);
static int intLoadModule(char * modName, enum driverTypes type, 
			 enum driverMinor minor, 
			 struct driversLoaded ** drlist, int skipPrompts,
			 char * args);
static int modulesPanel(enum driverTypes type, struct driver ** drvptr,
			struct driver * defaultDriver);

int devMakeInode(char * name, char * path) {
    int i;
    int major, minor;
    int type;

    if (name[0] == 's' && name[1] == 'd') {
	type = S_IFBLK;
	major = 8;
	minor = (name[2] - 'a') << 4;
	if (name[3] && name[4]) 
	   minor += 10 + (name[4] - '0');
	else if (name[3])
	   minor += (name[3] - '0');
    } else if (name[0] == 'h' && name[1] == 'd') {
	type = S_IFBLK;
	if (name[2] == 'a') 
	    major = 3, minor = 0;
	else if (name[2] == 'b')
	    major = 3, minor = 64;
	else if (name[2] == 'c')
	    major = 22, minor = 0;
	else if (name[2] == 'd')
	    major = 22, minor = 64;
	else if (name[2] == 'e')
	    major = 33, minor = 0;
	else if (name[2] == 'f')
	    major = 33, minor = 64;
	else if (name[2] == 'g')
	    major = 34, minor = 0;
	else if (name[2] == 'h')
	    major = 34, minor = 64;
	else
	    return INST_ERROR;

	if (name[3] && name[4]) 
	   minor += 10 + (name[4] - '0');
	else if (name[3])
	   minor += (name[3] - '0');
    } else if (!strncmp(name, "ram", 3)) {
	type = S_IFBLK;
	major = 1;
	minor = 1;
	if (name[3])
	    minor += name[3] - '1';
    } else {
	for (i = 0; i < numDevices; i++) {
	    if (!strcmp(devices[i].name, name)) break;
	}
	if (i == numDevices) return INST_ERROR;
	major = devices[i].major;
	minor = devices[i].minor;

	if (devices[i].isChar)
	    type = S_IFCHR;
	else
	    type = S_IFBLK;
    }

    unlink(path);
    if (mknod(path, type | 0600, makedev(major, minor))) {
	newtWinMessage(_("Error"), _("Ok"), 
			_("mknod() failed: %s"), strerror(errno));
	return INST_ERROR;
    }

    return 0;
}

void devRemoveInode(char * path) {
    logMessage("removing device file %s", path);
    unlink(path);
}

static int modulesPanel(enum driverTypes type, struct driver ** drvptr,
			struct driver * defaultDriver) {
    int drCount = 0;
    int i, rc;
    char ** driverNames;
    int * indices;
    int option = 0;

    for (i = 0; i < numDrivers; i++) {
	if (drivers[i].type == type) drCount++;
    }

    driverNames = alloca((drCount + 1) * sizeof(*drivers));
    indices = alloca(drCount * sizeof(*indices));
    drCount = 0;
    for (i = 0; i < numDrivers; i++) {
	if (drivers[i].type == type) {
	    if (defaultDriver == (drivers + i)) option = drCount;
	    driverNames[drCount] = drivers[i].name;
	    indices[drCount++] = i;
	}
    }
    driverNames[drCount] = NULL;

    rc = newtWinMenu(_("Load module"), _("Which driver should I try?"),
		     30, 0, 20, 6, driverNames, &option, _("Ok"),
		     _("Back"), NULL);
    if (rc == 2) {
	return INST_CANCEL;
    }

    *drvptr = drivers + indices[option];
    logMessage("picked driver %s", (*drvptr)->name);

    return 0;
}

int loadDeviceDriver(enum driverTypes type, struct driversLoaded ** drlist,
		     int justProbe) {
    struct driver * driver, * defDriver = NULL;
    int rc, i, j;
    int numAvail = -1;
    enum pciClass pciType = PCI_UNSET;
    struct pciDevice **devs, ** probedDev, ** lastDriver;
    int foundOne = 0;
    int ksArgc;
    char * start;
    char ** ksArgv = NULL;
    poptContext optCon;
    char * ksType;
    char * ksDevice;
    char * ksOpts;
    int ksFailOkay;
    char * typeName = "";
    struct poptOption ksOptions[] = {
	    { "missingok", '\0', POPT_ARG_NONE, &ksFailOkay, 0 },
	    { "opts", '\0', POPT_ARG_STRING, &ksOpts, 0 },
	    { 0, 0, 0, 0, 0 }
	};

    switch (type) {
      case DRIVER_SCSI: pciType = PCI_SCSI; typeName = "SCSI"; break;
      case DRIVER_NET: pciType = PCI_ETHERNET; typeName = "ethernet"; break;
      case DRIVER_CDROM: typeName = "cdrom"; break;
      default: pciType = PCI_UNSET; break;
    }

    logMessage("in loadDeviceDriver, ks = %d, typName = %s",
		kickstart, typeName);
    
    #ifdef __i386__
	if (pciType != PCI_UNSET) {
	    logMessage("pci probing for %s devices", typeName);
	    numAvail = pciProbeDevice(pciType, &devs);
	    logMessage("pci probe found %d %s devices", numAvail, typeName);
	}
    #else
	numAvail = 0;
    #endif

    #ifdef __i386__
    if (numAvail > 0) {
	lastDriver = devs + numAvail;
	probedDev = devs;
	for (j = 0; j < numAvail; j++) {
	    probedDev = devs + j;

	    /* if this is the same as the module suggested for another
	       device, just skip this incarnation */
	    for (i = 0; i < j; i++) {
		if (!strcmp((*probedDev)->module[0], devs[i]->module[0]))
		    break;
	    }

	    if (i < j) {
		logMessage("multiple %s devices found", devs[i]->module[0]);
		continue;
	    }

	    for (i = 0; i < numDrivers; i++) {
		if (!strcmp(drivers[i].modules,
			    (*probedDev)->module[0])) break;
	    }

	    if (numDrivers == i) {
		logMessage("module not in install table");
	    } else {
		logMessage("found driver for %s", drivers[i].name);
		if (expert || (*probedDev)->nhits > 1) {
		    if (!defDriver) defDriver = drivers + i;
		} else {
		    rc = loadDeviceModule(drivers + i, drlist, 1, NULL);
		    if (!rc) {
			if (!kickstart)
			    newtWinMessage(_("Probe"), _("Ok"), 
				_("A %s card has been found on your system."), 
				drivers[i].name);
			foundOne = 1;
		    }
		}
	    }
	}

	probedDev = devs;
	for (j = 0; j < numAvail; j++) {
	    pciFreeDevice(devs[j]);
	}

	free(devs);
    }
    #endif

    if (foundOne) return 0;

    /* If we're kickstarting, walk through the list of suggested devices. We
       stop once one is found. If --missingok is not used, give an error. */

    if (kickstart) {
        while (!ksGetCommand(KS_CMD_DEVICE, ksArgv, &ksArgc, &ksArgv) &&
	       !foundOne) {
	    ksFailOkay = 0;
	    ksOpts = NULL;

	    optCon = poptGetContext(NULL, ksArgc, ksArgv, ksOptions, 0);

	    if ((rc = poptGetNextOpt(optCon)) < -1) {
		newtWinMessage(_("device command"),  _("Ok"),
			 _("bad argument to kickstart device command %s: %s"),
			 poptBadOption(optCon, POPT_BADOPTION_NOALIAS), 
			 poptStrerror(rc));
		poptFreeContext(optCon);
		continue;
	    }

	    ksType = poptGetArg(optCon);
	    ksDevice = poptGetArg(optCon);

	    if (!ksType || !ksType || poptGetArg(optCon)) {
		newtWinMessage(_("Error"), _("Ok"), 
			       _("bad arguments to kickstart device command"));
		poptFreeContext(optCon);
		continue;
	    }

	    if (strcasecmp(ksType, typeName)) break;

	    for (i = 0; i < numDrivers; i++) {
		start = strstr(drivers[i].modules, ksDevice);
		if (start && !strcmp(ksDevice, start)) break;
	    }

	    if (numDrivers == i) {
		newtWinMessage(_("Error"), _("Ok"), 
			       _("No module exists for %s"), ksDevice);
		poptFreeContext(optCon);
		continue;
	    } else {
		logMessage("found driver for %s", drivers[i].name);
		rc = loadDeviceModule(drivers + i, drlist, 1, ksOpts);
		if (!rc) {
		    foundOne = 1;
		} else {
		    if (!ksFailOkay) break; /* out of while ksGetCmd() loop */
		}
	    }
	
	    poptFreeContext(optCon);
	}
    }

    if (foundOne) return 0;

    if (justProbe) return INST_ERROR;

    do {
	rc = modulesPanel(type, &driver, defDriver);
	if (rc) return rc;

	rc = loadDeviceModule(driver, drlist, 0, NULL);
	if (rc == INST_ERROR) {
	    errorWindow(_("I can't find the device anywhere on your system!"));
	}
    } while (rc);

    return 0;
}

static int loadDeviceModule(struct driver * driver, 
			    struct driversLoaded ** drlist, int skipPrompts,
			    char * args) {
    char * start, * chptr, ** modStack;
    char moduleName[100];
    int rc;
    int nummods = 1;
    enum driverTypes type;
   
    chptr = start = driver->modules;
    while (*chptr) {
	if (*chptr == ':') nummods++;
	chptr++;
    }

    modStack = alloca(sizeof(char *) * (nummods + 1));

    nummods = 0;
    while (start && *start) {
	chptr = strchr(start, ':');
	if (chptr) {
	    strncpy(moduleName, start, chptr - start);
	    moduleName[chptr - start] = '\0';
	    start = chptr + 1;
	    type = DRIVER_PREREQ;
	} else {
	    strcpy(moduleName, start);
	    start = NULL;
	    type = driver->type;
	}

	if ((rc = intLoadModule(moduleName, type, driver->minor, drlist, 
				skipPrompts, NULL))) {
	    while (nummods) {
		removeModule(modStack[--nummods]);
	    }
	    return rc;
	}

	
	modStack[nummods] = alloca(strlen(moduleName) + 1);
	strcpy(modStack[nummods++], moduleName);
    }

    /* don't do this check for autoprobed devices */
    if (!skipPrompts && driver->okay && !driver->okay(driver)) {
	while (nummods) {
	    removeModule(modStack[--nummods]);
	}
	logMessage("device check function failed to find device");
	return INST_ERROR;
    }

    return 0;
}

int removeModule(char * module) {
    char * argv[] = { "/bin/rmmod", NULL, NULL };

    argv[1] = module;

    return runProgram(RUN_LOG, "/bin/rmmod", argv);
}

char * getPlipDeviceName(void) {
     return plipDevice;
}

int loadModule(char * modName, enum driverTypes type, enum driverMinor minor,
	       struct driversLoaded ** drlist) {
    return intLoadModule(modName, type, minor, drlist, 0, NULL);
}

static int intLoadModule(char * modName, enum driverTypes type, 
			 enum driverMinor minor, 
			 struct driversLoaded ** drlist, int skipPrompts,
			 char * args) {
    struct driversLoaded * dl;
    char * objName;
    char * chptr, * start;
    char ** argv;
    int argc;
    int rc;
    int rmObj = 0;
    int clearWindow = 0;

    if (testing) return 0;

    objName = alloca(strlen(modName) + 15 + strlen(MODULES_PATH));

    strcpy(objName, MODULES_PATH);
    strcat(objName, modName);
    strcat(objName, ".o");

    argc = 2;
    argv = malloc((argc + 1) * sizeof(char *));
    argv[0] = "/bin/insmod";
    argv[1] = objName;
    argv[2] = NULL;

    if (args) {
	chptr = strcpy(alloca(strlen(args) + 1), args);

	while (*chptr) {
	    while (isspace(*chptr) && *chptr) chptr++;
	    if (!*chptr) break;
	    
	    start = chptr;
	    argc++;

	    argv = realloc(argv, (argc + 1) * sizeof(char *));

	    while (!isspace(*chptr) && *chptr) chptr++;

	    argv[argc - 1] = start;

	    if (*chptr) {
		*chptr = '\0';
		chptr++;
	    }
	}

	argv[argc] = NULL;
    } else {
	if ((rc = getOptions(modName, &argc, &argv, skipPrompts))) {
	    free(argv);
	    return rc;
	} 
    }

    if (type == DRIVER_SCSI) {
	winStatus(35, 3, "SCSI", "Scanning SCSI bus...");
	clearWindow = 1;
	if (expert) ioctl(0, VT_ACTIVATE, 4);
    }

    if (runProgram(RUN_LOG, "/bin/insmod", argv)) {
	free(argv);
	logMessage("insmod failed!");
	if (clearWindow) newtPopWindow();
	if (expert) ioctl(0, VT_ACTIVATE, 1);
	return INST_ERROR;
    }

    if (drlist) { 
	dl = allocDL(drlist);

	dl->type = type;
	dl->minor = minor;
	dl->argv = argv;
	dl->argc = argc;
	dl->module = strdup(modName);
	dl->persistFlags = 0;
    }

    if (clearWindow) newtPopWindow();
    if (expert) ioctl(0, VT_ACTIVATE, 1);
    if (rmObj) unlink(objName);

    return 0;
}

static struct driversLoaded * allocDL(struct driversLoaded ** drlist) {
    struct driversLoaded * new;

    if (*drlist == NULL) {
	*drlist = malloc(sizeof(**drlist));
	new = *drlist;
    } else {
	new = *drlist;
	while (new->next) new = new->next;
	new->next = malloc(sizeof(**drlist));
	new = new->next;
    }

    new->next = NULL;

    return new;
}

static int getOptions(const char * name, int * argcp, char *** argvp, 
		      int skipPrompts) {
    int miscParameters;
    char buf[2000];
    struct moduleInfo * mod;
    int numOptions, col, i, rc;
    struct moduleOptions * option;
    char * chptr, * start;
    int choice = OPTIONS_DEFAULT;	/* if (skipPrompts), use defaults */
    struct newtWinEntry * entries;
    char ** values;

    mod = modules;
    while (mod->name) {
	if (!strcmp(mod->name, name)) break;
	mod++;
    }
    if (!mod->name) mod = NULL;
    
    if (mod && !mod->options) {
	(*argcp)++;
	(*argvp) = realloc(*argvp, (*argcp + 1) * sizeof(char *));
	(*argvp)[(*argcp) - 1] = mod->defaultOptions;
	(*argvp)[(*argcp)] = NULL;
	if (!mod->defaultOptions)
	    (*argcp)--;
	
	return 0;
    }

    if (!skipPrompts) {
	if (!mod || mod->shouldAutoprobe) {
	    sprintf(buf, _("In some cases, the %s driver needs to have extra "
		    "information to work properly, although it normally works "
		    "fine without. Would you like to specify extra options "
		    "for it or allow the driver to probe your machine for the "
		    "information it needs? Occasionally, probing will hang a "
		    "computer, but it should not cause any damage."), name);
	    choice = OPTIONS_DEFAULT;
	} else {
	    sprintf(buf, 
		    _("In many cases, the %s driver needs to be provided with "
		      "extra information on your hardware. If you prefer, "
		      "some common values for those parameters will be tried. "
		      "This process can hang a machine, although it should "
		      "not cause any damage."), name);
	    choice = OPTIONS_SPECIFY;
	}

	rc = newtWinMenu(_("Module Options"), buf, 55, 5, 15, 6, 
			 driverOptions, &choice, _("Ok"), _("Back"), NULL);
	if (rc == 2) return INST_CANCEL;
    }

    if (choice == OPTIONS_DEFAULT) {
	(*argcp)++;
	(*argvp) = realloc(*argvp, (*argcp + 1) * sizeof(char *));
	if (mod)
	    (*argvp)[(*argcp) - 1] = mod->defaultOptions;
	(*argvp)[(*argcp)] = NULL;
	if (!mod || !mod->defaultOptions)
	    (*argcp)--;
	
	return 0;
    }

    numOptions = 0;
    col = 0;
    if (mod) {
	option = mod->options;
	while (option->arg) 
	    numOptions++, option++;
    }

    entries = alloca(sizeof(*entries) * (numOptions + 2));
    values = alloca(sizeof(*values) * (numOptions + 2));
    if (mod) {
	option = mod->options;
	numOptions = 0;
	while (option->arg) {
	    entries[numOptions].text = _(option->desc);
	    entries[numOptions].value = values + numOptions;
	    values[numOptions] = option->arg;
	    entries[numOptions].flags = 0;
	    numOptions++, option++;
	}
    }

    if (mod) {
	entries[numOptions].text = _("Miscellaneous options:");
    } else {
	entries[numOptions].text = _("Module options:");
    }
    miscParameters = numOptions;
    entries[numOptions].value = values + numOptions;
    values[numOptions] = NULL;
    entries[numOptions].flags = 0;
    numOptions++;
    entries[numOptions].text = (void *) entries[numOptions].value = NULL;

    rc = newtWinEntries(_("Module Parameters"), _("Module options:"),
		        40, 0, 10, 20, entries, _("Ok"), _("Back"), NULL);

    if (mod) {
	i = *argcp;
	(*argcp) += numOptions;
	(*argvp) = realloc(*argvp, (*argcp + 1) * sizeof(char *));
	numOptions = 0;
     
	option = mod->options;
	while (option->arg) {
	    if (strcmp(*entries[numOptions].value, option->arg) &&
		strlen(*entries[numOptions].value))
		(*argvp)[i++] = *entries[numOptions].value;
	    else
		free(*entries[numOptions].value);
	    numOptions++, option++;
	}
	(*argcp) = i;
    }

    chptr = *entries[miscParameters].value;
    numOptions = 0;
    while (*chptr) {
	while (isspace(*chptr) && *chptr) chptr++;
	if (!*chptr) continue;
	
	numOptions++;
	while (!isspace(*chptr) && *chptr) chptr++;
    }

    i = *argcp;
    (*argcp) += numOptions;
    (*argvp) = realloc(*argvp, (*argcp + 1) * sizeof(char *));
    numOptions = 0;

    chptr = *entries[miscParameters].value;
    numOptions = 0;
    while (*chptr) {
	while (isspace(*chptr) && *chptr) chptr++;
	if (!*chptr) continue;
	
	start = chptr;
	numOptions++;
	while (!isspace(*chptr) && *chptr) chptr++;

	if (*chptr) {
	    *chptr = '\0';
	    (*argvp)[i++] = strdup(start);
	    *chptr = ' ';
	} else
	    (*argvp)[i++] = strdup(start);
    }
    (*argcp) = i;
    (*argvp)[*argcp] = NULL;

    return 0;
}

int readModuleConfPersist(char * prefix, struct driversLoaded * drlist) {
    char buf[255];
    FILE * f;
    char * start, * end, * chptr;
    enum driverTypes type;
    enum driverMinor minor;
    struct driversLoaded * dl;

    strcpy(buf, prefix);
    strcat(buf, "/conf.modules");

    f = fopen(buf, "r");
    if (!f) {
	logMessage("failed to open %s for module information", prefix);
	return INST_ERROR;
    }

    while (fgets(buf, sizeof(buf) - 1, f)) {
 	start = buf;
	end = start + strlen(start) - 1;
	*end = '\0';

	if (!strncmp(start, "alias ", 6)) {
	    start += 6;

	    type = DRIVER_OTHER;
	    minor = DRIVER_MINOR_NONE;

	    while (isspace(*start) && *start) start++;

	    if (!strncmp(start, "eth", 3)) {
		type = DRIVER_NET;
		minor = DRIVER_MINOR_ETHERNET;
		start += 5;
	    } else if (!strncmp(start, "scsi_hostadapter", 16)) {
		type = DRIVER_SCSI;
		start += 17;
	    }

	    if (type != DRIVER_OTHER) {
		dl = drlist;
		while (dl) {
		    if (dl->type == type && dl->minor == minor) break;
		    dl = dl->next;
		}

		while (isspace(*start) && *start) start++;

		if (dl && *start && !strcmp(start, dl->module)) {
		    dl->persistFlags |= PERSIST_ALIAS;
		}
	    }
	} else if (!strncmp(start, "options ", 8)) {
	    start += 8;

	    chptr = start;
	    while (!isspace(*chptr) && *chptr) chptr++;
	    if (!*chptr) continue;
	    
	    *chptr = '\0';

	    dl = drlist;
	    while (dl) {
		if (!strcmp(dl->module, start)) break;
		dl = dl->next;
	    }

	    if (dl) {
		/* we really should check that these options match the
		   ones we used, but that's nontrivial FIXME */
		dl->persistFlags |= PERSIST_OPTIONS;
	    }
	}
    }

    fclose(f);

    return 0;
}

int readModuleConf(char * prefix, struct driversLoaded ** drlist) {
    char buf[255];
    FILE * f;
    char * start, * end, * chptr;
    struct driversLoaded * item = NULL;

    if (testing) return 0;

    strcpy(buf, prefix);
    strcat(buf, "/conf.modules");

    f = fopen(buf, "r");
    if (!f) {
	return INST_ERROR;
    }

    while (fgets(buf, sizeof(buf) - 1, f)) {
 	start = buf;
	end = start + strlen(start) - 1;
	*end = '\0';

	if (!strncmp(start, "alias ", 6)) {
	    start += 6;

	    if (!strncmp(start, "eth", 3)) {
		start += 5;

		item = allocDL(drlist);

		item->module = strdup(start);
		item->argv = NULL;
		item->argc = 0;
		item->persistFlags = 0;
		item->type = DRIVER_NET;
		item->minor = DRIVER_MINOR_ETHERNET;
	    } else if (!strncmp(start, "scsi_hostadapter", 16)) {
		start += 17;
		while (isspace(*start) && *start) start++;
		if (!*start) continue;

		item = allocDL(drlist);

		item->module = strdup(start);
		item->argv = NULL;
		item->argc = 0;
		item->persistFlags = 0;
		item->type = DRIVER_SCSI;
		item->minor = DRIVER_MINOR_NONE;
	    }
	} else if (!strncmp(start, "options ", 8)) {
	    start += 8;

	    chptr = start;
	    while (!isspace(*chptr) && *chptr) chptr++;
	    if (!*chptr) continue;
	    
	    *chptr = '\0';
	    
	    item = *drlist;
	    while (item && strcmp(item->module, start)) item = item->next; 

	    if (!item) {
		item = allocDL(drlist);
		item->module = strdup(start);
		item->argv = NULL;
		item->argc = 0;
		item->persistFlags = 0;
		item->type = DRIVER_OTHER;
		item->minor = DRIVER_MINOR_NONE;
	    }

	    item->argv = malloc(sizeof(char *) * 5);
	    item->argc = 3;
	    item->argv[2] = strdup(chptr + 1);
	}
    }

    fclose(f);

    return 0;
}

int writeModuleConf(char * prefix, struct driversLoaded * dl, int append) {
    char buf[255];
    char buf2[255];
    FILE * f;
    int i;
    int numEth = 0, numTr = 0, numScsi = 0;
    char * mode = append ? "a" : "w";

    if (testing) return 0;

    strcpy(buf, prefix);
    strcat(buf, "/conf.modules");

    if (!append && !access(buf, F_OK)) {
	logMessage("backing up old conf.modules");
	strcpy(buf2, buf);
	strcat(buf2, ".orig");
	rename(buf, buf2);
    }

    f = fopen(buf, mode);
    if (!f) {
	errorWindow("cannot open module config file: %s");
	return INST_ERROR;
    }

    while (dl) {
	if (dl->type == DRIVER_NET) {
	    if (!append || !(dl->persistFlags & PERSIST_ALIAS)) {
		if (dl->minor == DRIVER_MINOR_TR)
		    fprintf(f, "alias tr%d %s\n", numTr++, dl->module);
		else if (dl->minor == DRIVER_MINOR_ETHERNET)
		    fprintf(f, "alias eth%d %s\n", numEth++, dl->module);
	    }
	}
	else if (dl->type == DRIVER_SCSI) {
	    if (!append || !(dl->persistFlags & PERSIST_ALIAS)) {
		if (!numScsi)
		    fprintf(f, "alias scsi_hostadapter %s\n", dl->module);
		else
		    fprintf(f, "alias scsi_hostadapter%d %s\n", numScsi,
			    dl->module);
		numScsi++;
	    }
	}

	if (!append || !(dl->persistFlags & PERSIST_OPTIONS)) {
	    if (dl->argc > 2) {
		fprintf(f, "options %s", dl->module);
		for (i = 2; i < dl->argc; i++) {
		    fprintf(f, " %s", dl->argv[i]);
		}
	    
		fprintf(f, "\n");
	    }
	}

	dl = dl->next;
    }

    fclose(f);

    return 0;
}

static int checkSCSIDev(struct driver * dev) {
    return scsiDeviceAvailable();
}

static int checkEthernetDev(struct driver * dev) {
    return netDeviceAvailable("eth0");
}

static int checkTokenRingDev(struct driver * dev) {
    return netDeviceAvailable("tr0");
}

static int checkPlipDev(struct driver * dev) {
    plipDevice = NULL;

    if (netDeviceAvailable("plip0")) {
	logMessage("plip0 will be used for PLIP");
	plipDevice = "plip0";
    } else if (netDeviceAvailable("plip1")) {
	logMessage("plip1 will be used for PLIP");
	plipDevice = "plip1";
    } else if (netDeviceAvailable("plip2")) {
	logMessage("plip2 will be used for PLIP");
	plipDevice = "plip2";
    }
    
    return (plipDevice != NULL);
}

/* This assumes only one of each driver type is loaded */
int removeDeviceDriver(enum driverTypes type, struct driversLoaded ** drlist) {
    char * buf, * chptr;
    struct driversLoaded * dl, * head;
    struct driver * dri;

    dl = *drlist;
    while (dl && dl->type != type) {
	dl = dl->next;
    }
    if (!dl) return 0;

    dri = drivers;
    while (dri->name) {
	if (!strcmp(dri->modules, dl->module)) break;
	dri++;
    }

    if (!dri->name) return 0;

    buf = alloca(strlen(dri->modules) + 1);
    strcpy(buf, dri->modules);

    chptr = buf + strlen(buf) - 1;
    while (chptr > buf) {
	while (chptr > buf && *chptr != ':') chptr--;
	if (*chptr == ':') {
	    chptr = '\0';
	    removeModule(chptr + 1);
	    chptr--;
	}
    }

    removeModule(buf);

    if (dl == *drlist) {
	*drlist = dl->next;
	free(dl);
    } else if (dl) {
	head = *drlist;
	while (head->next != dl) head = head->next;
	head->next = dl->next;
	free(dl);
    }

    return 0;
}

int loadFilesystem(char * modname, char * fsname, 
		   struct driversLoaded ** drlist) {
    char buf[2048];
    int rc;
    int fd;
    char target[50];

    if ((fd = open("/proc/filesystems", O_RDONLY)) < 0) {
	newtWinMessage(_("Error"), _("Ok"), 
		       _("Cannot open /proc/filesystems: %d"),
		       strerror(errno));
	return INST_ERROR;
    }

    rc = read(fd, buf, sizeof(buf) - 1);
    buf[rc] = '\0';

    sprintf(target, "\t%s\n", fsname);
    if (strstr(buf, target)) return 0;

    rc = loadModule(modname, DRIVER_FS, DRIVER_MINOR_NONE, drlist);
    return rc;
}
