/****************************************************************************\
* HWSETUP-MOL - an Automatic Hardware Configuration Tool for Make One Linux  *
* Modifier: Keicho Kondo <dgel@users.sourceforge.jp>                         *
* this program is based on HWSETUP                                           *
* and original notification is followed ...                                  *
\****************************************************************************/

/****************************************************************************\
* HWSETUP - non-interactive hardware detection and configuration             *
* loads modules, generates /dev links, no isapnp autoconfiguration (yet)     *
* needs kudzu-devel (ver. 0.23 and up)                                       *
* Author: Klaus Knopper <knopper@knopper.net>                                *
\****************************************************************************/

/* Needed for strcasestr */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <pci/pci.h>
#undef _i_wanna_build_this_crap_
/* #define _i_wanna_build_this_crap_ 1 */ /* Necessary for ISAPNP */
#include "kudzu.h"
#if defined(_i_wanna_build_this_crap_)
#include "isapnp.h"
#endif

#define VERSION "HWSETUP-MOL 1.1, an automatic hardware configuration tool for Make One Linux\n" \
                "(C) 2005-2006 Keicho Kondo <dgel@users.sourceforge.jp>\n\n" \
                "original version is...\n" \
                "HWSETUP 1.2, an automatic hardware configuration tool\n" \
                "(C) 2006 Klaus Knopper <knoppix@knopper.net>\n\n"

#define CARDSDB "/usr/share/hwdata/Cards"
#define XPATH "/usr/X11R6/bin/"
#define XMODPATH "/usr/X11R6/lib/modules/drivers/"

#define VERBOSE_PRINT 1
#define VERBOSE_PROMPT 2

#define SKIP_AUDIO 1
#define SKIP_SCSI 2
#define SKIP_VIDEO 4
#define SKIP_NETWORK 8
#define SKIP_RAID 16

#define MAX_TIME 120   /* Maximum of seconds to run, total */
#define MAX_TIME_MODULE 4 /* Maximum time in seconds to wait until a module */
                          /* is successfully loaded before continuing       */

#ifdef BLACKLIST
/* Do not, under any circumstances, load these modules automatically, */
/* even if in pcitable. (libkudzu may ignore this, and the KNOPPIX    */
/* autoconfig scripts may probe them, too)  */
char *blacklist[] =
{ "apm","agpgart","yenta_socket","i82092","i82365","tcic",
  "pcmcia_core","ds","ohci1394","hisax",
  /* Winmodems, unusable, can block sound slot */
  "snd-atiixp-modem", "snd-intel8x0m","snd-via82xx-modem"
};
#endif

/* If a conflicting module in a row is already loaded, the new module will not be probed anymore */
#define CONFLICT_SET 2
struct conflict { char *name; int loaded; } conflicts [][CONFLICT_SET] =
{
 {{ "ad1848",0}, {"snd-nm256",0}},
 {{ "ali5455",0}, {"intel8x0",0}},
 {{ "cmpci",0}, {"snd-cmipci",0}},
 {{ "cs46xx",0}, {"snd-cs46xx",0}},
 {{ "cs4281",0}, {"snd-cs4281",0}},
 {{ "emu10k1",0}, {"snd-emu10k1",0}},
 {{ "es1370",0}, {"snd-ens1370",0}},
 {{ "es1371",0}, {"snd-ens1371",0}},
 {{ "esssolo1",0}, {"snd-es1938",0}},
 {{ "forte",0}, {"snd-fm801",0}},
 {{ "i810_audio",0}, {"snd-intel8x0",0}},
 {{ "maestro",0}, {"snd-es1960",0}},
 {{ "maestro3",0}, {"snd-maestro3",0}},
 {{ "nm256_audio",0}, {"snd-nm256",0}},
 {{ "rme96xx",0}, {"snd-rme9652",0}},
 {{ "sonicvibes",0}, {"snd-sonicvibes",0}},
 {{ "trident",0}, {"snd-trident",0}},
 {{ "via82cxxx_audio",0}, {"snd-via82xx",0}},
 {{ "ymfpci",0}, {"snd-ymfpci",0}},
 {{ "sk98lin",0}, {"skge",0}}
};

struct loaded_module { char *name; struct loaded_module *next; } *loaded_modules = NULL;

/* compare module names, case insensitive and with -/_ */
int modcmp(char *m1, char *m2)
{
 if(!m1 || !m2) return 1;
 for(;;)
  {
   int c1 = tolower(*m1);
   int c2 = tolower(*m2);
   if(c1 == '_') c1 = '-';
   if(c2 == '_') c2 = '-';
   if(c1 != c2) return 1;
   if(!c1 || !c2) break;
   m1++; m2++;
  }
 return 0;
}

/* returns true if module already loaded */
int check_loaded(char *name)
{
 struct loaded_module *curr = loaded_modules;
 while(curr)
  {
   if(name && curr->name && !modcmp(name, curr->name)) return 1;
   curr = curr->next;
  }
 return 0;
}

/* Returns name of conflicting module, or NULL if no conflict */
char *check_conflict(char *name)
{
 int i;
 if(!name) return 0;
 for(i=0; i<(sizeof(conflicts)/sizeof(struct conflict)/CONFLICT_SET); i++)
  {
   int j;
   for(j=0; j<CONFLICT_SET; j++)
    {
     if(!modcmp(name,conflicts[i][j].name) &&
        conflicts[i][!j].loaded) return conflicts[i][!j].name;
    }
  }
 return NULL;
}

void set_conflict(char *name)
{
 int i;
 if(!name) return;
 for(i=0; i<(sizeof(conflicts)/sizeof(struct conflict)/CONFLICT_SET); i++)
  {
   int j;
   for(j=0; j<CONFLICT_SET; j++)
    {
     if(!modcmp(name,conflicts[i][j].name)) conflicts[i][j].loaded=1;
    }
  }
}

void check_proc_modules(void)
{
 struct loaded_module *curr = NULL, *new = NULL;
 FILE *f = fopen("/proc/modules", "r");
 if(!f) return;
 for(;;)
  {
   char buffer[1024], *name;
   memset(buffer,0,sizeof(buffer));
   if(!fgets(buffer,1024, f) || ferror(f)) break;
   new = (struct loaded_module *) malloc(sizeof(struct loaded_module));
   if(!new) { fclose(f); return; }
   memset(new,0,sizeof(struct loaded_module));
   if(!loaded_modules) { loaded_modules = curr = new; }
   else
    {
     curr->next = new;
     curr = curr->next;
    }
   name = strtok(buffer," ");
   if(!name) continue;
   curr->name = strdup(name);
   set_conflict(name);
  }
 fclose(f);
}

char *get_description(struct device *d)
{
 /* pci.ids lookup using the pci library, i.e. most likely /usr/share/misc/pci.ids */
 static char devbuf[128];

 memset(devbuf,0,sizeof(devbuf));
 if(d)
  {
   static struct pci_access *pacc = NULL;
   int vendorid, deviceid;
   switch(d->bus)
    {
     case BUS_PCI: vendorid=((struct pciDevice *)d)->vendorId;
                   deviceid=((struct pciDevice *)d)->deviceId;
                   break;
     case BUS_USB: vendorid=((struct usbDevice *)d)->vendorId;
                   deviceid=((struct usbDevice *)d)->deviceId;
                   break;
     default:      return d->desc;
    }
   if(!pacc)
    {
     if(!(pacc=pci_alloc())) return d->desc;
     pci_init(pacc);
    }
   if(vendorid>0 && deviceid>0 &&
       pci_lookup_name(pacc, devbuf, sizeof(devbuf),
                       PCI_LOOKUP_VENDOR | PCI_LOOKUP_DEVICE,
                       vendorid, deviceid, 0, 0))
    {
     devbuf[127]=0;
     return devbuf;
    }
  }
 return d->desc;
}

/* These need to be global, so we can kill them in case of problems */
pid_t wpid=0;

int syntax(char *option)
{
 printf(VERSION);
 if(option) fprintf(stderr,"hwsetup-mol: Unknown option '%s'\n\n",option);
 printf("Usage: hwsetup-mol\n"
         "               -v       be verbose\n"
         "               -p       print rotating prompt\n"
         "               -a       ignore audio devices\n"
         "               -s       ignore scsi controllers\n"
		 "               -video   ignore video devices\n"
		 "               -net     ignore network devices\n"
		 "               -raid    ignore raid devices\n"
         "               -n       probe only, don't configure anything.\n");
 return option?1:0;
}

pid_t startwheel(void) /* Feedback while detecting hardware */
{
 char v[]="Autoconfiguring devices... ";
 char r[]="/-\\|";
 char *b="\b";
 pid_t pid;
 if((pid=fork())>0) return pid; /* return Child PID to Master process */
 else if(pid==0)
  {
   int i,j;
   /* Allow killing of process */
   signal(SIGHUP,SIG_DFL);
   signal(SIGINT,SIG_DFL);
   signal(SIGTERM,SIG_DFL);
   write(2,v,sizeof(v)-1);
   for(i=j=0;;i++,j++)
    {
     if(j%8==7) write(2,"\033[42;32m \033[0m",13); /* a green space */
     write(2,&r[i%4],1); write(2,b,1);
     usleep(40000);
    }
   exit(0); /* Exit child process */
  }
 return 0;
}

int exists(char *filename)
{
 struct stat s;
 return !stat(filename,&s);
}

struct xinfo {
char xserver[16];
char xmodule[16];
char xdesc[128];
char xopts[128];
};

struct xinfo *getxinfo(struct device *d)
{
 const char *xfree4="XFree86", *xorg="Xorg", *xvesa4="vesa";
 const char *xpath=XPATH;
 static struct xinfo xi;
 int rescanned=0;
 memset(&xi,0,sizeof(struct xinfo));
 if(d->desc) strcpy(xi.xdesc, get_description(d));
 if(d->driver)
  {
   const char *server[]={"XF86_3DLabs", "XF86_Mach64", "XF86_Mach32",
	   "XF86_Mach8", "XF86_AGX", "XF86_P9000", "XF86_S3V", "XF86_S3V",
	   "XF86_S3", "XF86_W32", "XF86_8514", "XF86_I128", "XF86_SVGA",
	    xfree4, xorg, NULL};
   char *xpos;
   if(!strncasecmp(d->driver,"Card:",5)) /* RedHat Cards-DB */
    { /* Kudzu "Cards" format */
     FILE *cardsdb;
     char xfree3server[128];
     memset(xfree3server,0,sizeof(xfree3server));
     if((cardsdb=fopen(CARDSDB,"r"))!=NULL)
      { /* Try to find Server and Module in /usr/share/kudzu/Cards */
       char buffer[1024];
       char searchfor[128];
       int found=0;
       memset(searchfor,0,sizeof(searchfor));
       sscanf(&d->driver[5],"%127[^\r\n]",searchfor);
       while(!found && !feof(cardsdb) && fgets(buffer,1024,cardsdb))
        {
         char sfound[128];
         memset(sfound,0,sizeof(sfound));
         if(strncasecmp(buffer,"NAME ",5) || 
           (sscanf(&buffer[5],"%127[^\r\n]",sfound)!=1) ||
           strcasecmp(sfound,searchfor)) continue;
         while(!feof(cardsdb) && fgets(buffer,1024,cardsdb))
          {
           if(buffer[0]<32) break; /* End-of-line */
           if(!strncasecmp(buffer,"SERVER ",7))
            {
             char x[20]="";
             if(sscanf(&buffer[7],"%19s",x)==1)
              {
               char xserver[32];
               char fullpath[128];
               char *xf[2]={"","XF86_"};
               int i;
               for(i=0;i<2;i++)
                {
                 sprintf(xserver,"%s%.24s",xf[i],x);
                 sprintf(fullpath,"%.90s%.32s",xpath,xserver);
                 if(exists(fullpath))
                  {
                   strncpy(xfree3server,xserver,sizeof(xfree3server));
                   break; /* for */
                  }
                }
              }
            }
           else if(!strncasecmp(buffer,"DRIVER ",7))
            {
             char xmodule[32];
             char fullpath[128];
             sscanf(&buffer[7],"%31s",xmodule);
             sprintf(fullpath,XMODPATH"%.31s_drv.o",xmodule);
             if(exists(fullpath))
              {
               strncpy(xi.xmodule,xmodule,sizeof(xi.xmodule));
              }
            }
           else if(!strncasecmp(buffer,"SEE ",4)&&rescanned<10)
            { /* rescan Cards-DB for other server */
             fseek(cardsdb,0L,SEEK_SET); ++rescanned;
             memset(searchfor,0,sizeof(searchfor));
             sscanf(&buffer[4],"%127[^\r\n]",searchfor);
             break; /* Continue with outer while() */
            }
          }
        }
       fclose(cardsdb);
      }
    }
   /* Card not found in Cards database -> Try to read "Xorg(module)" from driver */
   if(!*xi.xserver)
    {
     int i;
     for(i=0;server[i]!=NULL;i++)
      {
       if(strstr(d->driver,server[i]))
        {
         char fullpath[128];
         sprintf(fullpath,"%.90s%.32s",xpath,server[i]);
         if(!exists(fullpath)) continue;
         strncpy(xi.xserver,server[i],sizeof(xi.xserver));
	}
      }
    }
   if((xpos=strstr(d->driver,xorg))!=NULL) /* Check for Xorg */
    {
     char xm[32]="";
     if(sscanf(xpos,"Xorg(%30[^)])",xm)==1) strcpy(xi.xmodule,xm);
    }
   else if((xpos=strstr(d->driver,xfree4))!=NULL) /* Check for XFree 4 */
    {
     char xm[32]="";
     if(sscanf(xpos,"XFree86(%30[^)])",xm)==1) strcpy(xi.xmodule,xm);
    }
  } /* if -> driver */
 /* Special options required? */
 if(d->desc)
  {
   strncpy(xi.xdesc,get_description(d),128);
   xi.xdesc[127] = 0; /* 0-terminate (strncpy is not safe) */
  }
  /* Fallback values: Guess xorg driver from pci.ids description (or vendor-string) */
 if(!*xi.xserver) strcpy(xi.xserver,xorg);
 if(!*xi.xmodule)
  {
   if(*xi.xdesc)
    { /* first, the most common or "easy to guess" ones */
     if(strcasestr(xi.xdesc,"Riva") && strcasestr(xi.xdesc,"128")) strcpy(xi.xmodule,"riva128");
     else if(strcasestr(xi.xdesc,"NVidia")) strcpy(xi.xmodule,"nv");
     else if(strcasestr(xi.xdesc,"ATI "))
      {
       if(strcasestr(xi.xdesc,"R128")) strcpy(xi.xmodule,"r128");
       else strcpy(xi.xmodule,"ati"); /* Will autoload "radeon" */
      }
     else if(strcasestr(xi.xdesc,"Trident")) strcpy(xi.xmodule,"trident");
     else if(strcasestr(xi.xdesc,"Rendition")) strcpy(xi.xmodule,"rendition");
     else if(strcasestr(xi.xdesc,"Tseng")) strcpy(xi.xmodule,"tseng");
     else if(strcasestr(xi.xdesc,"Intel"))
      {
       if(strcasestr(xi.xdesc,"128")) strcpy(xi.xmodule,"i128");
       else if(strcasestr(xi.xdesc,"740")) strcpy(xi.xmodule,"i740");
       else if(strcasestr(xi.xdesc,"810")||strcasestr(xi.xdesc,"82")||strcasestr(xi.xdesc,"830")) strcpy(xi.xmodule,"i810");
       else strcpy(xi.xmodule,xvesa4);
      }
     else if(strcasestr(xi.xdesc,"Matrox")) strcpy(xi.xmodule,"mga");
     else if(strcasestr(xi.xdesc,"Neomagic")) strcpy(xi.xmodule,"neomagic");
     else if(strcasestr(xi.xdesc,"VMWare")) strcpy(xi.xmodule,"vmware");
     else if(strcasestr(xi.xdesc,"Savage"))
      {
       if(strcasestr(xi.xdesc,"S3Virge")) strcpy(xi.xmodule,"s3virge");
       else if(strcasestr(xi.xdesc,"S3")) strcpy(xi.xmodule,"s3");
       else strcpy(xi.xmodule,"savage");
      }
     else if(strcasestr(xi.xdesc,"Cyrix")) strcpy(xi.xmodule,"cyrix");
     else if(strcasestr(xi.xdesc,"NSC ")) strcpy(xi.xmodule,"nsc");
     else if(strcasestr(xi.xdesc,"SIS "))
      {
       if(strcasestr(xi.xdesc,"USB")) strcpy(xi.xmodule,"sisusb");
       else strcpy(xi.xmodule,"sis");
      }
     else if(strcasestr(xi.xdesc,"Cirrus"))
      {
       if(strcasestr(xi.xdesc,"Alpine")) strcpy(xi.xmodule,"cirrus_alpine");
       if(strcasestr(xi.xdesc,"Laguna")) strcpy(xi.xmodule,"cirrus_laguna");
       else strcpy(xi.xmodule,"cirrus");
      }
     else if(strcasestr(xi.xdesc,"Newport")) strcpy(xi.xmodule,"newport");
     else if(strcasestr(xi.xdesc,"Siliconmotion")||strcasestr(xi.xdesc,"Silicon Motion")) strcpy(xi.xmodule,"siliconmotion");
     else if(strcasestr(xi.xdesc,"Chips")) strcpy(xi.xmodule,"chips");
     else if(strcasestr(xi.xdesc,"VIA ")) strcpy(xi.xmodule,"via");
    }
   else strcpy(xi.xmodule,xvesa4);
  }
 if(*xi.xdesc)
  {
   /* Handle special cards that require special options */
   if(strstr(xi.xdesc,"Trident")||strstr(xi.xdesc,"TGUI")
          ||strstr(xi.xdesc,"Cirrus")||strstr(xi.xdesc,"clgd"))
    {
     if(!strcmp(xi.xserver,xfree4)||!strcmp(xi.xserver,xorg))
      strncpy(xi.xopts,"-depth 16",sizeof(xi.xopts)-1);
     else
      strncpy(xi.xopts,"-bpp 16",sizeof(xi.xopts)-1);
    }
   else if(strstr(xi.xdesc,"Savage 4")) /* S3 Inc.|Savage 4 */
    {
     if(!strcmp(xi.xserver,xfree4)||!strcmp(xi.xserver,xorg))
      strncpy(xi.xopts,"-depth 32",sizeof(xi.xopts)-1);
     else
      strncpy(xi.xopts,"-bpp 32",sizeof(xi.xopts)-1);
    }
  }
 return &xi;
}

void hw_info(struct device *d)
{
 enum deviceClass class=d->type;
 enum deviceBus     bus=d->bus;
 char *unknown="UNKNOWN";
 char *desc;
 /* These used to be much easier when they were still arrays... */
 char *classname=class==CLASS_UNSPEC?"UNSPEC":  class==CLASS_OTHER?"OTHER":
                 class==CLASS_NETWORK?"NETWORK":class==CLASS_SCSI?"SCSI":
                 class==CLASS_VIDEO?"VIDEO":    class==CLASS_AUDIO?"AUDIO":
                 class==CLASS_MOUSE?"MOUSE":    class==CLASS_MODEM?"MODEM":
                 class==CLASS_CDROM?"CDROM":    class==CLASS_TAPE?"TAPE":
                 class==CLASS_FLOPPY?"FLOPPY":  class==CLASS_SCANNER?"SCANNER":
                 class==CLASS_HD?"HD":          class==CLASS_RAID?"RAID":
                 class==CLASS_PRINTER?"PRINTER":class==CLASS_CAPTURE?"CAPTURE":
                 class==CLASS_USB?"USB":        class==CLASS_MONITOR?"MONITOR":
                 class==CLASS_KEYBOARD?"KEYBOARD":unknown;
 char *busname=  bus==BUS_OTHER?"OTHER":  bus==BUS_PCI? "PCI":
                 bus==BUS_SBUS?"SBUS":    bus==BUS_PSAUX?"PSAUX":
                 bus==BUS_SERIAL?"SERIAL":bus==BUS_PARALLEL?"PARALLEL":
                 bus==BUS_SCSI?"SCSI":    bus==BUS_IDE?"IDE":
                 bus==BUS_DDC?"DDC":      bus==BUS_USB?"USB":
                 bus==BUS_KEYBOARD?"KEYBOARD":
#if defined(_i_wanna_build_this_crap_)
                 bus==BUS_ISAPNP?"ISAPNP":
#endif
                 unknown;
 desc = get_description(d);
 printf(
 "---\n"
 "class:  %s\n"
 "bus:    %s\n"
 "device: %s\n"
 "driver: %s\n"
 "desc:   %s\n",classname, busname, d->device?d->device:"(null)",d->driver,
                desc?desc:"(empty)");
}

/* rename /dev/mouse -> /dev/mouse1, /dev/mouse1 -> /dev/mouse2 recursive */
int checkmoveup(char *filename, int oldnum)
{
 int newnum=oldnum+1;
 char srcname[64], dstname[64];
 struct stat buf;
 sprintf(srcname,(oldnum>0)?"%.32s%d":"%.32s",filename,oldnum);
 if(stat(srcname,&buf)) return 0; /* File does not exist, OK. */
 sprintf(dstname,"%.32s%d",filename,newnum);
 /* recursive if file exists, otherwise just rename it */
 return (!stat(dstname,&buf) && checkmoveup(filename,newnum))?errno:
        rename(srcname,dstname);
}

int link_dev(struct device *d, char *target, int tnum, int verbose)
{
 const char devdir[]="/dev/";
 if(d&&d->device)
  {
   char devname[64], dstname[64];
   sprintf(devname,"%s%.32s",devdir,d->device);
   sprintf(dstname,"%s%.32s",devdir,target);
   if(checkmoveup(dstname, tnum)) return -1; /* Read-only FS?! */
   if(tnum>0) sprintf(dstname,"%s%.32s%1d",devdir,target,tnum);
   if(verbose&VERBOSE_PRINT) printf("symlink(%.32s,%.32s)\n",devname,dstname);
   return symlink(devname,dstname);
  }
 return -1;
}

void segfault_handler(int dummy)
{
 signal(SIGSEGV,SIG_IGN);
 fprintf(stderr,"\nWARNING: Caught signal SEGV while executing modprobe.\n");
 fflush(stderr);
}

void alarm_handler(int dummy)
{
 signal(SIGALRM,SIG_IGN);
 fprintf(stderr,"\nWARNING: Autodetection seems to hang,\n"
                "please check your computers BIOS settings.\n");
 fflush(stderr);
 if(wpid) { kill(wpid,SIGTERM); usleep(2500000); kill(wpid,SIGKILL); wpid=0; }
 exit(1); /* exit program */
}

int load_mod(char *m,int verbose)
{
 int pstatus,i;
 time_t now;
 pid_t mpid;
 char *cc;
 if((m==NULL)||(!strcmp("unknown",m))||(!strcmp("ignore",m))|| check_loaded(m)) return 0;
#ifdef BLACKLIST
 for(i=0;i<(sizeof(blacklist)/sizeof(char*));i++)
  {
   if(!modcmp(blacklist[i],m))
    {
     if(verbose&VERBOSE_PRINT) printf("not loading module %.32s (is in blacklist)\n",m);
     return 0;
    }
  }
#endif
 if((cc=check_conflict(m))!=NULL)
  {
   if(verbose&VERBOSE_PRINT) printf("not loading module %.32s (conflicts with loaded module '%.32s' for same device)\n", m, cc);
   return 0;
  }
 if((mpid=fork())==0)
  { /* child process */
   if(verbose&VERBOSE_PRINT) printf("modprobe(%.32s)\n",m);
   signal(SIGSEGV,segfault_handler);
   /* Send modprobe errors to /dev/null */
   if(!(verbose&VERBOSE_PRINT)) freopen("/dev/null","w",stderr);
   execl("/sbin/modprobe","modprobe",m,NULL);
   exit(1);
  }
 now=time(0);
 do
  {
   usleep(125000); /* Delay 1/8s */
   /* We SHOULD wait for modprobe to finish! */
   if(waitpid(mpid,&pstatus,WNOHANG)) break;
  }
 while((time(0) - now) < MAX_TIME_MODULE);
 set_conflict(m);
 return pstatus;
}

#if defined(_i_wanna_build_this_crap_)
int configure_isapnp(struct device *dev,int verbose)
{
 int io[64],io_max;
 int irq[16],irq_max;
 int dma[8],dma_max;
 struct isapnpDevice *d=(struct isapnpDevice *)dev;
 if(d->io)
  {
   if(verbose&VERBOSE_PRINT) printf("io:     ");
   for(io_max=0;io_max<64&&(io[io_max]=d->io[io_max])!=-1;io_max++)
     if(verbose&VERBOSE_PRINT) printf("0x%x, ",(int)io[io_max]);
  if(verbose&VERBOSE_PRINT) printf("\n");
  }
 if(d->irq)
  {
   if(verbose&VERBOSE_PRINT) printf("irq:    ");
   for(irq_max=0;irq_max<16&&(irq[irq_max]=d->irq[irq_max])!=-1;irq_max++)
     if(verbose&VERBOSE_PRINT) printf("0x%d, ",(int)irq[irq_max]);
  if(verbose&VERBOSE_PRINT) printf("\n");
  }
 if(d->dma)
  {
   if(verbose&VERBOSE_PRINT) printf("dma:    ");
   for(dma_max=0;dma_max<8&&(dma[dma_max]=d->dma[dma_max])!=-1;dma_max++)
     if(verbose&VERBOSE_PRINT) printf("%d, ",(int)dma[dma_max]);
  if(verbose&VERBOSE_PRINT) printf("\n");
  }
 /* no configuration possible (yet) */
#if 0
 /* Hier müssten die freien Interrupts/IOs/DMAs untersucht werden, aber erst
    NACHDEM alle anderen Treiber geladen wurden, anschließend eine
    /etc/isapnp.conf mit den vermutlich richtigen Werten geschrieben und
    mit isapnp geladen werden. Abschließend: */
 return modprobe(d->driver,free_io,free_irq,free_dma,verbose);
#endif
 return(0);
}
#endif

int writeconfig(char *name,struct device *d,int verbose)
{
 FILE *f,*k;
 const char *kconfig="/etc/sysconfig/mol/mol";
 char *desc;
 unlink(name);
 if((f=fopen(name,"w"))==NULL)
  { /* Read-only filesystem on /etc ?! */
   fprintf(stderr,"Can't write to '%s': %s",name,strerror(errno));
   return 1;
  }
 if((k=fopen(kconfig,"a"))==NULL) { fclose(f); return 1; }
 if(verbose&VERBOSE_PRINT)
  {
   printf("write  config(%s)\n",name);
   printf("update config(%s)\n",kconfig);
  }
 desc = get_description(d);
 switch(d->type)
  {
   case CLASS_AUDIO:
    {
     if(desc)      fprintf(f,"FULLNAME=\"%s\"\n",desc),
                   fprintf(k,"SOUND_FULLNAME=\"%s\"\n",desc);
     if(d->driver)
      {
       char *cc=check_conflict(d->driver);
       fprintf(f,"DRIVER=\"%s\"\n",cc?cc:d->driver),
       fprintf(k,"SOUND_DRIVER=\"%s\"\n",cc?cc:d->driver);
      }
    }; break;
   case CLASS_MOUSE:
    {
     char *t1,*t2;
     if(d->bus==BUS_PSAUX)    { t1="ps2"; t2="PS/2"; }
     else if(d->bus==BUS_USB) { t1="imps2"; t2="IMPS/2"; }
     else                     { t1="ms"; t2="Microsoft"; }
     fprintf(f,"MOUSETYPE=\"%s\"\nXMOUSETYPE=\"%s\"\n",t1,t2);
     if(desc)      fprintf(f,"FULLNAME=\"%s\"\n",desc),
                   fprintf(k,"MOUSE_FULLNAME=\"%s\"\n",desc);
     if(d->device) fprintf(f,"DEVICE=\"/dev/%s\"\n",d->device),
                   fprintf(k,"MOUSE_DEVICE=\"/dev/%s\"\n",d->device);
    }; break;
   case CLASS_NETWORK:
    {
     if(desc)      fprintf(f,"FULLNAME=\"%s\"\n",desc),
                   fprintf(k,"NETCARD_FULLNAME=\"%s\"\n",desc);
     if(d->driver) fprintf(f,"DRIVER=\"%s\"\n",d->driver),
                   fprintf(k,"NETCARD_DRIVER=\"%s\"\n",d->driver);
    }; break;
   case CLASS_VIDEO:
    {
     const char *xserver="XSERVER=\"%s\"\n";
     const char *xmodule="XMODULE=\"%s\"\n";
     const char *xopts="XOPTIONS=\"%s\"\n";
     const char *xdesc="XDESC=\"%s\"\n";
     struct xinfo *xi=getxinfo(d);
     if(xi)
      {
       if(*xi->xserver)
        { fprintf(f,xserver,xi->xserver); fprintf(k,xserver,xi->xserver); }
       if(*xi->xmodule)
        { fprintf(f,xmodule,xi->xmodule); fprintf(k,xmodule,xi->xmodule); }
       if(*xi->xopts)
        { fprintf(f,xopts,xi->xopts); fprintf(k,xopts,xi->xopts); }
       if(*xi->xdesc)
        { fprintf(f,xdesc,xi->xdesc); fprintf(k,xdesc,xi->xdesc); }
      }
    }; break;
   case CLASS_FLOPPY:
    {
     if(desc)      fprintf(f,"FULLNAME='%s'\n",desc),
                   fprintf(k,"FLOPPY_FULLNAME='%s'\n",desc);
     if(d->device) fprintf(f,"DEVICE=\"/dev/%s\"\n",d->device),
                   fprintf(k,"FLOPPY_DEVICE=\"/dev/%s\"\n",d->device);
     if(d->driver) fprintf(f,"DRIVER=\"%s\"\n",d->driver),
                   fprintf(k,"FLOPPY_DRIVER=\"%s\"\n",d->driver);
    }; break;
   default: break;
  }
 fclose(f); fclose(k); 
 return 0;
}

int hw_setup(enum deviceClass dc, int verbose, int probeonly, int skip)
{
 int i,mouse=0,cdrom=0,modem=0,scanner=0;
 struct device **currentDevs, *d, *serialmouse=NULL, *usbmouse=NULL;
 if(verbose&VERBOSE_PROMPT) wpid=startwheel();
 currentDevs=probeDevices(dc,BUS_UNSPEC,PROBE_ALL);
 if(verbose&VERBOSE_PROMPT&&wpid>0) { kill(wpid,SIGTERM); wpid=0; usleep(160000); write(2,"\033[0m Done.\n",11); }
 if(currentDevs==NULL) return -1;
 check_proc_modules(); /* Get currently loaded module list */
 for(i=0;(d=currentDevs[i]);i++)
  {
   if(verbose&VERBOSE_PRINT) hw_info(d); 
   if(!probeonly)
    {
#if defined(_i_wanna_build_this_crap_)
     if(d->bus==BUS_ISAPNP&&configure_isapnp(d,verbose)) continue;
#endif
     switch(d->type)
      {
       case CLASS_MOUSE:   /* Choose serial over PS2/USB mouse IF present  */
                           /* For some reason, PS/2 ALWAYS detects a mouse */
                           if(d->bus==BUS_SERIAL)
                            { mouse=0; serialmouse=d; }
                           else if(d->bus==BUS_USB) /* Need usbdevfs for */
                            { mouse=0; usbmouse=d;  /* this to work      */
                              load_mod(d->driver,verbose); }
                           if(!mouse)
                             writeconfig("/etc/sysconfig/mol/mouse",d,verbose);
                           link_dev(d,"mouse",mouse++,verbose);
                           break;
       case CLASS_CDROM:   if(d->bus==BUS_USB) load_mod(d->driver,verbose);
                           link_dev(d,"cdrom",    cdrom++,verbose); break;
       case CLASS_MODEM:   if(d->bus==BUS_USB) load_mod(d->driver,verbose);
                           link_dev(d,"modem",    modem++,verbose); break;
       case CLASS_SCANNER: if(d->bus==BUS_USB) load_mod(d->driver,verbose);
                           link_dev(d,"scanner",scanner++,verbose); break;
       case CLASS_VIDEO:   if(skip&SKIP_VIDEO) break;
                           writeconfig("/etc/sysconfig/mol/xserver",d,verbose);
                           break;
       case CLASS_AUDIO:   if(skip&SKIP_AUDIO) break;
                           writeconfig("/etc/sysconfig/mol/sound",d,verbose);
                           load_mod(d->driver,verbose); break;
       case CLASS_NETWORK: if(skip&SKIP_NETWORK) break;
                           writeconfig("/etc/sysconfig/mol/netcard",d,verbose);
                           load_mod(d->driver,verbose); break;
       case CLASS_FLOPPY:  writeconfig("/etc/sysconfig/mol/floppy",d,verbose);
                           load_mod(d->driver,verbose); break;
       case CLASS_KEYBOARD:if(d->bus==BUS_USB) load_mod(d->driver,verbose);
                           break;
       case CLASS_CAPTURE: /* Just load the module for these */
       case CLASS_SCSI:    if(skip&SKIP_SCSI) break;
       case CLASS_OTHER:   /* Yet unsupported or "guessed" devices in kudzu. Module probe may hang here. */
       case CLASS_RAID:    if(skip&SKIP_RAID) break;
                           load_mod(d->driver,verbose); break;
       case CLASS_SOCKET:  /* yenta_socket or similar is handled by knoppix-autoconfig */
       default:            /* do nothing */ break;
      }
    }
  }
 return 0;
}

int main(int argc, char **argv)
{
 int i, verbose=0, probeonly=0, skip=0;
 enum deviceClass dc=CLASS_UNSPEC;
 for(i=1;i<argc;i++)
  {
   if(!strcasecmp(argv[i],"-v"))      verbose|=VERBOSE_PRINT;
   else if(!strcasecmp(argv[i],"-p")) verbose|=VERBOSE_PROMPT;
   else if(!strcasecmp(argv[i],"-a")) skip|=SKIP_AUDIO;
   else if(!strcasecmp(argv[i],"-s")) skip|=SKIP_SCSI;
   else if(!strcasecmp(argv[i],"-video")) skip|=SKIP_VIDEO;
   else if(!strcasecmp(argv[i],"-net")) skip|=SKIP_NETWORK;
   else if(!strcasecmp(argv[i],"-raid")) skip|=SKIP_RAID;
   else if(!strcasecmp(argv[i],"-n")) probeonly=1;
   else return syntax(argv[i]);
  }
 /* Allow SIGTERM, SIGINT: rmmod depends on this. */
 signal(SIGTERM,SIG_DFL); signal(SIGINT,SIG_DFL);
 signal(SIGALRM,alarm_handler); alarm(MAX_TIME);
 return hw_setup(dc,verbose,probeonly,skip);
}
