#include <alloca.h>
#include <ctype.h>
#include <fcntl.h>
#include <newt.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <unistd.h>
#include <zlib.h>

#include "devices.h"
#include "install.h"
#include "log.h"
#include "net.h"
#include "perror.h"
#include "run.h"
#include "scsi.h"
#include "windows.h"

#define MODULES_PATH "/modules/"

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

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

const 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 },
};

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

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

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

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

const struct moduleOptions cdu31aOptions[] = {
  { "cdu31a", "IO base, IRQ, PAS?:", "" },
  { NULL, NULL, NULL }
} ;

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

const struct moduleOptions mcdOptions[] = {
  { "mcd", "IO base address:", "" },
  { NULL, NULL, NULL }
} ;

const struct moduleOptions optcdOptions[] = {
  { "optcd", "IO base address:", "" },
  { NULL, NULL, NULL }
} ;

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

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

struct moduleInfo {
   char * name;
   int shouldAutoprobe;
   const 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 },
   { "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 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 },
   { "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 },
   { "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 },
   { "NE2000 and compatible", "8390:ne", 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 ethernet", "8390:smc-ultra", 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 },
   { "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 53C810/53C820 PCI", "53c7,8xx", 0, checkSCSIDev,
               DRIVER_SCSI, DRIVER_MINOR_NONE },
   { "NCR 53C810/53C820 (alternate)", "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 */
};

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

static int loadDeviceModule(struct driver * driver,
                           struct driversLoaded ** drlist);
static int getOptions(const char * name, int * argcp, char *** argvp);
static struct driversLoaded * allocDL(struct driversLoaded ** drlist);

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

   logMessage("making device %s (%d, %d) as %s", name, major, minor, path);

   if (testing)
       messageWindow("mknod", "making device %s (%d, %d) as %s",
                     name, major, minor, path);

   unlink(path);
   if (mknod(path, type | 0600, makedev(major, minor))) {
       messageWindow("Error", perrorstr("mknod() failed"));
       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) {
   int drCount = 0;
   int i;
   newtComponent label, f, listbox;
   newtComponent okay, answer, cancel;

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

   newtOpenWindow(17, 4, 45, 15, "Load module");

   f = newtForm(NULL, NULL, 0);
   label = newtLabel(1, 1, "Which driver should I try?");
   newtFormAddComponent(f, label);

   listbox = newtListbox(5, 3, 6, NEWT_LISTBOX_RETURNEXIT);

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

   newtFormAddComponent(f, listbox);
   okay = newtButton(8, 10, "Ok");
   cancel = newtButton(28, 10, "Cancel");

   newtFormAddComponents(f, okay, cancel, NULL);

   answer = newtRunForm(f);

   if (answer == cancel) {
       newtFormDestroy(f);
       newtPopWindow();
       return INST_CANCEL;
   }

   *drvptr = newtListboxGetCurrent(listbox);

   newtFormDestroy(f);
   newtPopWindow();

   return 0;
}

int loadDeviceDriver(enum driverTypes type, struct driversLoaded ** drlist) {
   struct driver * driver;
   int rc;

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

       rc = loadDeviceModule(driver, drlist);
       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) {
   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 = loadModule(moduleName, type, driver->minor, drlist))) {
           while (nummods) {
               removeModule(modStack[--nummods]);
           }
           return rc;
       }


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

   if (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) {
   struct driversLoaded * dl;
   char * objName;
   char ** argv;
   int argc;
   int rc;
   int fd;
   int rmObj = 0;
   int clearWindow = 0;
   gzFile stream;
   char buf[4096];
   int i = 0;

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

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

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

#ifdef __i386__
   if (access(objName, R_OK)) {
       /* it might be gzipped */
       strcat(objName, ".gz");
       if (access(objName, R_OK)) {
           logMessage("can't find module %s", modName);
           return INST_ERROR;
       }

       stream = gzopen(objName, "r");
       if (!stream) {
           logMessage("gzopen failed to read %s: %s", objName,
                       strerror(errno));
           return INST_ERROR;
       }

       strcpy(objName, "/tmp/");
       strcat(objName, modName);
       strcat(objName, ".o");
       if ((fd = open(objName, O_WRONLY | O_CREAT | O_TRUNC)) < 0) {
           logMessage("failed to create %s: %s", objName, strerror(errno));
           gzclose(stream);
           return INST_ERROR;
       }

       logMessage("uncompressing module for installation");

       while ((i = gzread(stream, buf, sizeof(buf))) > 0) {
           if (write(fd, buf, i) != i) {
               logMessage("write() failed during module decompression: %s\n",
                          strerror(errno));
               close(fd);
               gzclose(stream);
               return INST_ERROR;
           }
       }

       if (i < 0)
           logMessage("write() failed during module decompression: %s\n",
                      strerror(errno));

       close(fd);
       gzclose(stream);
       if (i < 0) return INST_ERROR;

       rmObj = 1;
   }
#endif

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

   if ((rc = getOptions(modName, &argc, &argv))) {
       free(argv);
       if (clearWindow) newtPopWindow();
       return rc;
   }

   if (runProgram(RUN_LOG, "/bin/insmod", argv)) {
       free(argv);
       logMessage("insmod failed!");
       if (clearWindow) newtPopWindow();
       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 (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;
}

#define OPTIONS_SPECIFY ((void *) 1)
#define OPTIONS_DEFAULT ((void *) 2)

static int getOptions(const char * name, int * argcp, char *** argvp) {
   newtComponent form, listbox, text, okay, answer, cancel;
   char ** parameters = NULL;
   char * miscParameters;
   char buf[2000];
   struct moduleInfo * mod;
   void * choice;
   int numOptions, col, miscRow, buttonRow, i;
   const struct moduleOptions * option;
   char * miscText;
   char * chptr, * start;

   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 (!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);

       newtOpenWindow(10, 4, 60, 16, "Module Options");
       listbox = newtListbox(20, 9, 0, NEWT_LISTBOX_RETURNEXIT);
       newtListboxAddEntry(listbox, "Autoprobe", OPTIONS_DEFAULT);
       newtListboxAddEntry(listbox, "Specify options", OPTIONS_SPECIFY);
       buttonRow = 12;
   } 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);
       newtOpenWindow(10, 5, 60, 14, "Module Options");
       listbox = newtListbox(20, 7, 0, NEWT_LISTBOX_RETURNEXIT);
       newtListboxAddEntry(listbox, "Specify options", OPTIONS_SPECIFY);
       newtListboxAddEntry(listbox, "Autoprobe", OPTIONS_DEFAULT);
       buttonRow = 10;
   }

   text = newtTextbox(1, 1, 55, 7, NEWT_TEXTBOX_WRAP);
   newtTextboxSetText(text, buf);

   okay = newtButton(13, buttonRow, "Ok");
   cancel = newtButton(39, buttonRow, "Cancel");

   form = newtForm(NULL, NULL, 0);
   newtFormAddComponents(form, text, listbox, okay, cancel, NULL);

   answer = newtRunForm(form);
   newtPopWindow();

   choice = newtListboxGetCurrent(listbox);
   newtFormDestroy(form);

   if (answer == cancel) {
       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;
   }

   form = newtForm(NULL, NULL, 0);
   newtFormAddComponent(form, newtLabel(1, 1, "Module options:"));

   numOptions = 0;
   col = 0;
   if (mod) {
       option = mod->options;
       while (option->arg) {
           newtFormAddComponent(form, newtLabel(3, 3 + numOptions,
                                option->desc));
           if (strlen(option->desc) > col) col = strlen(option->desc);
           numOptions++;
           option++;
       }
       miscText = "Miscellaneous options:";
   } else {
       miscText = "Module options:";
   }

   if (numOptions)
       miscRow = 4 + numOptions;
   else
       miscRow = 3;

   newtFormAddComponent(form, newtLabel(3, miscRow, "Miscellaneous options:"));

   if (22 > col) col = 22;

   if (numOptions) {
       parameters = alloca(sizeof(*parameters) * numOptions);
       numOptions = 0;
       option = mod->options;
       while (option->arg) {
           sprintf(buf, "%s=", option->arg);
           newtFormAddComponent(form, newtEntry(col + 5, 3 + numOptions,
                                buf, 20, parameters + numOptions,
                                NEWT_ENTRY_SCROLL));
           numOptions++;
           option++;
       }
   }

   newtFormAddComponent(form, newtEntry(col + 5, miscRow, "", 20,
                        &miscParameters, NEWT_ENTRY_SCROLL));

   newtOpenWindow((80 - (col + 30)) / 2, (25 - (miscRow + 6)) / 2, col + 30,
                       miscRow + 6, "Module Parameters");

   okay = newtButton((col + 10) / 3, miscRow + 2, "Ok");
   cancel = newtButton(10 + 2 * ((col + 10) / 3), miscRow + 2, "Cancel");
   newtFormAddComponents(form, okay, cancel, NULL);

   answer = newtRunForm(form);

   newtPopWindow();

   if (answer == cancel) {
       newtFormDestroy(form);
       return INST_CANCEL;
   }

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

       option = mod->options;
       while (option->arg) {
           sprintf(buf, "%s=", option->arg);
           if (strcmp(parameters[numOptions], buf))
               (*argvp)[i++] = strdup(parameters[numOptions]);
           numOptions++, option++;
       }
       (*argcp) = i;
   }

   chptr = miscParameters;
   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 = miscParameters;
   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;

   newtFormDestroy(form);

   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, "eth0", 4)) {
               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_NET;
               item->minor = DRIVER_MINOR_ETHERNET;
           }

           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;
   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))
               fprintf(f, "alias eth0 %s\n", dl->module);
       }
       else if (dl->type == DRIVER_SCSI) {
           if (!append || !(dl->persistFlags & PERSIST_ALIAS))
               fprintf(f, "alias scsi_hostadapter %s\n", dl->module);
       }

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