/*
 * loader.c
 * 
 * This is the installer loader.  Its job is to somehow load the rest
 * of the installer into memory and run it.  This may require setting
 * up some devices and networking, etc. The main point of this code is
 * to stay SMALL! Remember that, live by that, and learn to like it.
 *
 * Erik Troan <ewt@redhat.com>
 * Matt Wilson <msw@redhat.com>
 *
 * Copyright 1999 Red Hat, Inc.
 *
 * This software may be freely redistributed under the terms of the GNU
 * public license.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <arpa/inet.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <net/if.h>
#include <newt.h>
#include <popt.h>
#include <rpmio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/sysmacros.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <zlib.h>

#include "balkan/balkan.h"
#include "isys/imount.h"
#include "isys/isys.h"
#include "isys/probe.h"
#include "kudzu/kudzu.h"

#include "cdrom.h"
#include "devices.h"
#include "kickstart.h"
#include "lang.h"
#include "loader.h"
#include "log.h"
#include "misc.h"
#include "modules.h"
#include "net.h"
#include "pcmcia.h"
#include "urls.h"
#include "windows.h"

int probe_main(int argc, char ** argv);
int rmmod_main(int argc, char ** argv);
int cardmgr_main(int argc, char ** argv);
int ourInsmodCommand(int argc, char ** argv);
int kon_main(int argc, char ** argv);

struct knownDevices devices;

struct installMethod {
    char * name;
    int network;
    enum deviceClass deviceType;			/* for pcmcia */
    char * (*mountImage)(struct installMethod * method,
		      char * location, struct knownDevices * kd,
    		      moduleInfoSet modInfo, moduleList modLoaded,
		      moduleDeps modDeps, int flags);
};

#ifdef INCLUDE_LOCAL
static char * mountCdromImage(struct installMethod * method,
		      char * location, struct knownDevices * kd,
    		      moduleInfoSet modInfo, moduleList modLoaded,
		      moduleDeps modDeps, int flags);
static char * mountHardDrive(struct installMethod * method,
		      char * location, struct knownDevices * kd,
    		      moduleInfoSet modInfo, moduleList modLoaded,
		      moduleDeps modDeps, int flags);
#endif
#ifdef INCLUDE_NETWORK
static char * mountNfsImage(struct installMethod * method,
		      char * location, struct knownDevices * kd,
    		      moduleInfoSet modInfo, moduleList modLoaded,
		      moduleDeps modDeps, int flags);
static char * mountUrlImage(struct installMethod * method,
		      char * location, struct knownDevices * kd,
    		      moduleInfoSet modInfo, moduleList modLoaded,
		      moduleDeps modDeps, int flags);
#endif

static struct installMethod installMethods[] = {
#if defined(INCLUDE_LOCAL)
    { N_("Local CDROM"), 0, CLASS_CDROM, mountCdromImage },
#endif
#if defined(INCLUDE_NETWORK)
    { N_("NFS image"), 1, CLASS_NETWORK, mountNfsImage },
    { "FTP", 1, CLASS_NETWORK, mountUrlImage },
    { "HTTP", 1, CLASS_NETWORK, mountUrlImage },
#endif
#if defined(INCLUDE_LOCAL)
    { N_("Hard drive"), 0, CLASS_HD, mountHardDrive },
#endif
};
static int numMethods = sizeof(installMethods) / sizeof(struct installMethod);

static int newtRunning = 0;
#ifdef INCLUDE_KON
static int startKon = 1;
#endif

void doSuspend(void) {
    newtFinished();
    exit(1);
}

static void startNewt(int flags) {
    if (!newtRunning) {
	newtInit();
	newtCls();
	newtDrawRootText(0, 0, _("Welcome to Red Hat Linux"));

	newtPushHelpLine(_("  <Tab>/<Alt-Tab> between elements  | <Space> selects | <F12> next screen "));

	newtRunning = 1;
        if (FL_TESTING(flags)) 
	    newtSetSuspendCallback((void *) doSuspend, NULL);
    }
}

static void stopNewt(void) {
    if (newtRunning) newtFinished();
}

static void spawnShell(int flags) {
    pid_t pid;
    int fd;

    if (FL_SERIAL(flags)) {
	logMessage("not spawning a shell over a serial connection");
	return;
    }
    if (!FL_TESTING(flags)) {
	fd = open("/dev/tty2", O_RDWR);
	if (fd < 0) {
	    logMessage("cannot open /dev/tty2 -- no shell will be provided");
	    return;
	} else if (access("/bin/sh",  X_OK))  {
	    logMessage("cannot open shell - /bin/sh doesn't exist");
	    return;
	}

	if (!(pid = fork())) {
	    dup2(fd, 0);
	    dup2(fd, 1);
	    dup2(fd, 2);

	    close(fd);
	    setsid();
	    if (ioctl(0, TIOCSCTTY, NULL)) {
		logMessage("could not set new controlling tty");
	    }

	    execl("/bin/sh", "-/bin/sh", NULL);
	    logMessage("exec of /bin/sh failed: %s", strerror(errno));
	}

	close(fd);
    } else {
	logMessage("not spawning a shell as we're in test mode");
    }

    return;
}

static int detectHardware(moduleInfoSet modInfo, 
			  struct moduleInfo *** modules, int flags) {
    struct device ** devices, ** device;
    struct moduleInfo * mod, ** modList;
    int numMods, i;
    char *driver;

    initializeDeviceList();

    logMessage("probing buses");

    devices = probeDevices(CLASS_UNSPEC,BUS_PCI|BUS_SBUS,PROBE_ALL);

    logMessage("finished bus probing");

    if (devices == NULL) {
        *modules = NULL;
	return LOADER_OK;
    }

    modList = malloc(sizeof(*modList) * 50);	/* should be enough */
    numMods = 0;

    for (device = devices; *device; device++) {
	driver = (*device)->driver;
	if (strcmp (driver, "ignore") && strcmp (driver, "unknown")) {
	    logMessage("found suggestion of %s", driver);
	    if ((mod = isysFindModuleInfo(modInfo, driver))) {
		logMessage("found %s device", driver);
		for (i = 0; i < numMods; i++) 
		    if (modList[i] == mod) break;
		if (i == numMods) 
		    modList[numMods++] = mod;
	    }
	}
	freeDevice (*device);
    }

    if (numMods) {
        *modules = modList;
	modList[numMods] = NULL;
    } else {
        free(modList);
	*modules = NULL;
    }

    free(devices);

    return LOADER_OK;
}

int addDeviceManually(moduleInfoSet modInfo, moduleList modLoaded, 
		      moduleDeps modDeps, struct knownDevices * kd, int flags) {
    char * pristineItems[] = { N_("SCSI"), N_("Network") };
    char * items[3];
    int i, rc;
    int choice = 0;
    enum deviceClass type;

    for (i = 0; i < sizeof(pristineItems) / sizeof(*pristineItems); i++) {
	items[i] = _(pristineItems[i]);
    }

    items[i] = NULL;

    do {
	rc = newtWinMenu(_("Devices"), 
		       _("What kind of device would you like to add"), 40,
		       0, 20, 2, items, &choice, _("OK"), _("Back"), NULL);
	if (rc == 2) return LOADER_BACK;

	if (choice == 1)
	    type = DRIVER_NET;
	else
	    type = DRIVER_SCSI;

	rc = devDeviceMenu(type, modInfo, modLoaded, modDeps, flags, NULL);
    } while (rc);

    return 0;
}

int manualDeviceCheck(moduleInfoSet modInfo, moduleList modLoaded, 
		      moduleDeps modDeps, struct knownDevices * kd, int flags) {
    int i, rc;
    char buf[2000];
    struct moduleInfo * mi;
    newtComponent done, add, text, items, form, answer;
    newtGrid grid, buttons;
    int numItems;
    int maxWidth;

    while (1) {
	numItems = 0;
        maxWidth = 0;
	for (i = 0, *buf = '\0'; i < modLoaded->numModules; i++) {
	    if (!modLoaded->mods[i].weLoaded) continue;

	    if (!(mi = isysFindModuleInfo(modInfo, modLoaded->mods[i].name))) {
		continue;
	    }

	    strcat(buf, "    ");
	    strcat(buf, mi->description);

	    if (maxWidth < strlen(mi->description)) 
		maxWidth = strlen(mi->description);

	    strcat(buf, "\n");
	    numItems++;
	}

        if (numItems > 0) {
	    text = newtTextboxReflowed(-1, -1, 
		_("I have found the following devices in your system:"), 
		40, 5, 20, 0);
	    buttons = newtButtonBar(_("Done"), &done, _("Add Device"), &add, 
				    NULL);
	    items = newtTextbox(-1, -1, maxWidth + 8, 
				numItems < 10 ? numItems : 10, 
				(numItems < 10 ? 0 : NEWT_FLAG_SCROLL));
				    
	    newtTextboxSetText(items, buf);

	    grid = newtGridSimpleWindow(text, items, buttons);
	    newtGridWrappedWindow(grid, _("Devices"));

	    form = newtForm(NULL, NULL, 0);
	    newtGridAddComponentsToForm(grid, form, 1);

	    answer = newtRunForm(form);
	    newtPopWindow();

	    newtGridFree(grid, 1);
	    newtFormDestroy(form);

	    if (answer != add)
		break;

	    addDeviceManually(modInfo, modLoaded, modDeps, kd, flags);
	} else {
	    rc = newtWinChoice(_("Devices"), _("Done"), _("Add Device"), 
		    _("I don't have any special device drivers loaded for "
		      "your system. Would you like to load some now?"));
	    if (rc != 2)
		break;

	    addDeviceManually(modInfo, modLoaded, modDeps, kd, flags);
	}
    } 


    return 0;
}

int busProbe(moduleInfoSet modInfo, moduleList modLoaded, moduleDeps modDeps,
	     int justProbe, struct knownDevices * kd, int flags) {
    int i;
    struct moduleInfo ** modList;

    if (FL_NOPROBE(flags)) return 0;

    if (!access("/proc/bus/pci/devices", R_OK) ||
        !access("/proc/openprom", R_OK)) {
        /* autodetect whatever we can */
        if (detectHardware(modInfo, &modList, flags)) {
	    logMessage("failed to scan pci bus!");
	    return 0;
	} else if (modList) {
	    logMessage("found devices justProbe is %d", justProbe);

	    for (i = 0; modList[i]; i++) {
		if (justProbe) {
		    printf("%s\n", modList[i]->moduleName);
		} else {
		    if (modList[i]->major == DRIVER_NET) {
			mlLoadModule(modList[i]->moduleName, modList[i]->path, 
				     modLoaded, modDeps, NULL, flags);
		    }
		}
	    }

	    for (i = 0; !justProbe && modList[i]; i++) {
	    	if (modList[i]->major == DRIVER_SCSI) {
		    startNewt(flags);

		    scsiWindow(modList[i]->moduleName);
		    mlLoadModule(modList[i]->moduleName, modList[i]->path, 
				 modLoaded, modDeps, NULL, flags);
		    sleep(1);
		    newtPopWindow();
		}
	    }

	    kdFindScsiList(kd);
	    kdFindNetList(kd);
	} else 
	    logMessage("found nothing");
    }

    return 0;
}

static int loadCompressedRamdisk(int fd, off_t size, char *title,
				 char *ramdisk, int flags) {
    int rc = 0, ram, i;
    gzFile stream;
    char buf[1024];
    newtComponent form = NULL, scale = NULL;
    int total;

    if (FL_TESTING(flags)) return 0;

    stream = gzdopen(dup(fd), "r");

    strcpy(buf, "/tmp/");
    strcat(buf, ramdisk);
    
    if (devMakeInode(ramdisk, buf)) return 1;
    ram = open(buf, O_WRONLY);
    unlink(buf);

    logMessage("created inode");

    if (title != NULL) {
	if (size > 0)
	    newtCenteredWindow(70, 5, _("Loading"));
	else
	    newtCenteredWindow(70, 3, _("Loading"));
	
	form = newtForm(NULL, NULL, 0);
	
	newtFormAddComponent(form, newtLabel(1, 1, title));
	if (size > 0) {
	    scale = newtScale(1, 3, 68, size);
	    newtFormAddComponent(form, scale);
	}
	newtDrawForm(form);
	newtRefresh();
    }

    total = 0;
    while (!gzeof(stream) && !rc) {
	if ((i = gzread(stream, buf, sizeof(buf))) != sizeof(buf)) {
	    if (!gzeof(stream)) {
		logMessage("error reading from device: %s", strerror(errno));
		rc = 1;
		break;
	    }
	}

	if (write(ram, buf, i) != i) {
	    logMessage("error writing to device: %s", strerror(errno));
	    rc = 1;
	}

	total += i;

	if (title != NULL && size > 0) {
	    newtScaleSet(scale, lseek(fd, 0L, SEEK_CUR));
	    newtRefresh();
	}
    }

    logMessage("done loading %d bytes", total);

    if (title != NULL) {
	newtPopWindow();
	newtFormDestroy(form);
    }
    
    close(ram);
    gzclose(stream);

    return rc;
}

static int loadStage2Ramdisk(int fd, off_t size, int flags) {
    int rc;
    
    rc = loadCompressedRamdisk(fd, size, _("Loading second stage ramdisk..."),
			       "ram3", flags);
    
    if (rc) {
	newtWinMessage(_("Error"), _("OK"), _("Error loading ramdisk."));
	return rc;
    }

    if (devMakeInode("ram3", "/tmp/ram3")) {
	logMessage("failed to make device ram3");
	return 1;
    }
    
    if (doPwMount("/tmp/ram3", "/mnt/runtime", "ext2", 0, 0, NULL, NULL)) {
	newtWinMessage(_("Error"), _("OK"),
		"Error mounting ramdisk. This shouldn't "
		    "happen, and I'm rebooting your system now.");
	exit(1);
    }

    unlink("/tmp/ram3");

    return 0;
}

#ifdef INCLUDE_LOCAL
static char * setupHardDrive(char * device, char * type, char * dir, 
			     int flags) {
    int fd;
    char * path;
    int rc;
    char * url;

    logMessage("mounting device %s as %s", device, type);

    if (!FL_TESTING(flags)) {
	/* +5 skips over /dev/ */
	if (devMakeInode(device, "/tmp/hddev"))
	    logMessage("devMakeInode failed!");

	if (doPwMount("/tmp/hddev", "/tmp/hdimage", type, 1, 0, NULL, NULL))
	    return NULL;

	path = alloca(50 + (dir ? strlen(dir) : 2));
	sprintf(path, "/tmp/hdimage/%s/RedHat/base/stage2.img", 
		    dir ? dir : "");
	if ((fd = open(path, O_RDONLY)) < 0) {
	    logMessage("cannot open %s", path);
	    umount("/tmp/hdimage");
	    return NULL;
	} 

	rc = loadStage2Ramdisk(fd, 0, flags);
	close(fd);
	umount("/tmp/hdimage");
	if (rc) return NULL;
    }

    url = malloc(50 + strlen(dir ? dir : ""));
    sprintf(url, "hd://%s:%s/%s", device, type, dir ? dir : ".");

    return url;
}

#endif

#ifdef INCLUDE_LOCAL

static char * mountHardDrive(struct installMethod * method,
		      char * location, struct knownDevices * kd,
    		      moduleInfoSet modInfo, moduleList modLoaded,
		      moduleDeps modDeps, int flags) {
    int rc;
    int fd;
    int i, j;
    struct {
	char name[20];
	int type;
    } partitions[1024], * part;
    struct partitionTable table;
    newtComponent listbox, label, dirEntry, form, okay, back, text;
    struct newtExitStruct es;
    newtGrid entryGrid, grid, buttons;
    int done = 0;
    char * dir = NULL;
    char * tmpDir;
    char * type;
    char * url = NULL;
    int numPartitions;
    #ifdef __sparc__
    static int ufsloaded;
    #endif

    mlLoadModule("vfat", NULL, modLoaded, modDeps, NULL, flags);

    while (!done) {
	numPartitions = 0;
	for (i = 0; i < kd->numKnown; i++) {
	    if (kd->known[i].class == CLASS_HD) {
		devMakeInode(kd->known[i].name, "/tmp/hddevice");
		if ((fd = open("/tmp/hddevice", O_RDONLY)) >= 0) {
		    if ((rc = balkanReadTable(fd, &table))) {
			logMessage("failed to read partition table for "
				   "device %s: %d", kd->known[i].name, rc);
		    } else {
			for (j = 0; j < table.maxNumPartitions; j++) {
			    switch (table.parts[j].type) {
			    #ifdef __sparc__
			      case BALKAN_PART_UFS:
				if (!ufsloaded) {
				    ufsloaded = 1;
				    mlLoadModule("ufs", NULL, modLoaded, modDeps, NULL, flags);
				}
				/* FALLTHROUGH */
			    #endif
			      case BALKAN_PART_DOS:
			      case BALKAN_PART_EXT2:
				sprintf(partitions[numPartitions].name, 
					"/dev/%s%d", kd->known[i].name, j + 1);
				partitions[numPartitions].type = 
					table.parts[j].type;
				numPartitions++;
			    }
			}
		    }

		    close(fd);
		} else {
		    /* XXX ignore errors on removable drives? */
		}

		unlink("/tmp/hddevice");
	    }
	}
	
	if (!numPartitions) {
	    rc = newtWinChoice(_("Hard Drives"), _("Yes"), _("Back"),
			    _("You don't seem to have any hard drives on "
			      "your system! Would you like to configure "
			      "additional devices?"));
	    if (rc == 2) return NULL;

	    devDeviceMenu(DRIVER_SCSI, modInfo, modLoaded, modDeps, flags, 
			  NULL);
	    kdFindScsiList(kd);

	    continue;
	}

	text = newtTextboxReflowed(-1, -1,
		_("What partition and directory on that partition hold the "
		  "RedHat/RPMS and RedHat/base directories? If you don't "
		  "see the disk drive you're using listed here, press F2 "
		  "to configure additional devices."), 62, 5, 5, 0);

	listbox = newtListbox(-1, -1, numPartitions > 5 ? 5 : numPartitions,
			      NEWT_FLAG_RETURNEXIT | 
				(numPartitions > 5 ? NEWT_FLAG_SCROLL : 0)
			    );
	
	for (i = 0; i < numPartitions; i++) 
	    newtListboxAppendEntry(listbox, partitions[i].name, 
				   partitions + i);
	
	label = newtLabel(-1, -1, _("Directory holding Red Hat:"));

	dirEntry = newtEntry(28, 11, dir, 28, &tmpDir, NEWT_ENTRY_SCROLL);
	
	entryGrid = newtGridHStacked(NEWT_GRID_COMPONENT, label,
				     NEWT_GRID_COMPONENT, dirEntry,
				     NEWT_GRID_EMPTY);

	buttons = newtButtonBar(_("OK"), &okay, _("Back"), &back, NULL);
	
	grid = newtCreateGrid(1, 4);
	newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, text,
			 0, 0, 0, 1, 0, 0);
	newtGridSetField(grid, 0, 1, NEWT_GRID_COMPONENT, listbox,
			 0, 0, 0, 1, 0, 0);
	newtGridSetField(grid, 0, 2, NEWT_GRID_SUBGRID, entryGrid,
			 0, 0, 0, 1, 0, 0);
	newtGridSetField(grid, 0, 3, NEWT_GRID_SUBGRID, buttons,
			 0, 0, 0, 0, 0, NEWT_GRID_FLAG_GROWX);
	
	newtGridWrappedWindow(grid, _("Select Partition"));
	
	form = newtForm(NULL, NULL, NEWT_FLAG_NOF12);
	newtFormAddHotKey(form, NEWT_KEY_F2);

	newtGridAddComponentsToForm(grid, form, 1);
	newtGridFree(grid, 1);

	newtFormRun(form, &es);

	part = newtListboxGetCurrent(listbox);
	
	if (dir) free(dir);
	if (tmpDir && *tmpDir) {
	    /* Protect from form free. */
	    dir = strdup(tmpDir);
	} else  {
	    dir = NULL;
	}
	
	newtFormDestroy(form);
	newtPopWindow();

	if (es.reason == NEWT_EXIT_COMPONENT && es.u.co == back) {
	    return NULL;
	} else if (es.reason == NEWT_EXIT_HOTKEY && es.u.key == NEWT_KEY_F2) {
	    devDeviceMenu(DRIVER_SCSI, modInfo, modLoaded, modDeps, flags, 
			  NULL);
	    kdFindScsiList(kd);
	    continue;
	}

	logMessage("partition %s selected", part->name);
	
	switch (part->type) {
	#ifdef __sparc__
	  case BALKAN_PART_UFS:     type = "ufs"; 		break;
	#endif
	  case BALKAN_PART_EXT2:    type = "ext2"; 		break;
	  case BALKAN_PART_DOS:	    type = "vfat"; 		break;
	  default:	continue;
	}

	url = setupHardDrive(part->name + 5, type, dir, flags);
	if (dir) free(dir);
	if (!url) {
	    newtWinMessage(_("Error"), _("OK"), 
			_("Device %s does not appear to contain "
			  "a Red Hat installation tree."), part->name);
	    continue;
	}

	done = 1; 

	umount("/tmp/hdimage");
	rmdir("/tmp/hdimage");
    }

    return url;
}

static char * setupCdrom(struct installMethod * method,
		      char * location, struct knownDevices * kd,
    		      moduleInfoSet modInfo, moduleList modLoaded,
		      moduleDeps modDeps, int flags, int probeQuickly) {
    int i;
    int rc;
    int hasCdrom = 0;

    do {
	for (i = 0; i < kd->numKnown; i++) {
	    if (kd->known[i].class != CLASS_CDROM) continue;

	    hasCdrom = 1;

	    logMessage("trying to mount device %s", kd->known[i].name);
	    devMakeInode(kd->known[i].name, "/tmp/cdrom");
	    if (!doPwMount("/tmp/cdrom", "/mnt/source", "iso9660", 1, 0, NULL, 
			  NULL)) {
		if (!access("/mnt/source/RedHat/instimage/usr/bin/anaconda", 
			    X_OK)) {
		    symlink("/mnt/source/RedHat/instimage", "/mnt/runtime");
		    return "dir://mnt/source/.";
		}
		umount("/mnt/source");
	    }
	}

	if (probeQuickly) return NULL;

	if (hasCdrom) {
	    rc = newtWinChoice(_("Error"), _("OK"), _("Back"), 
			_("I could not find a Red Hat Linux "
			  "CDROM in any of your CDROM drives. Please insert "
			  "the Red Hat CD and press \"OK\" to retry."));
	    if (rc == 2) return NULL;
	} else {
	    rc = setupCDdevice(kd, modInfo, modLoaded, modDeps, flags);
	    if (rc == LOADER_BACK) return NULL;
	}
    } while (1);

    /* FIXME: For GUI installs to other host (with display=)
       we need to set up networking.  */
    if (getenv("DISPLAY"))
	flags |= LOADER_FLAGS_TEXT;
    
    return "dir://mnt/source/.";
}

static char * mountCdromImage(struct installMethod * method,
		      char * location, struct knownDevices * kd,
    		      moduleInfoSet modInfo, moduleList modLoaded,
		      moduleDeps modDeps, int flags) {
    return setupCdrom(method, location, kd, modInfo, modLoaded, modDeps,
		      flags, 0);
}

#endif

#ifdef INCLUDE_NETWORK

static int ensureNetDevice(struct knownDevices * kd,
    		         moduleInfoSet modInfo, moduleList modLoaded,
		         moduleDeps modDeps, int flags, char ** devNamePtr) {
    int i, rc;
    char * devName = NULL;

    /* Once we find an ethernet card, we're done. Perhaps we should
       let them specify multiple ones here?? */

    for (i = 0; i < kd->numKnown; i++) {
	if (kd->known[i].class == CLASS_NETWORK) {
	    devName = kd->known[i].name;
	    break;
	}
    }

    /* It seems like expert mode should do something here? */
    if (!devName) {
	rc = devDeviceMenu(DRIVER_NET, modInfo, modLoaded, modDeps, flags,
			   NULL);
	if (rc) return rc;
	kdFindNetList(kd);
    }

    if (!devName) {
	for (i = 0; i < kd->numKnown; i++) {
	    if (kd->known[i].class == CLASS_NETWORK) {
		devName = kd->known[i].name;
		break;
	    }
	}
    }

    if (!devName) return LOADER_ERROR;

    *devNamePtr = devName;

    return 0;
}

#endif

#ifdef INCLUDE_NETWORK

#define NFS_STAGE_IP	1
#define NFS_STAGE_NFS	2
#define NFS_STAGE_MOUNT	3
#define NFS_STAGE_DONE	4

static char * mountNfsImage(struct installMethod * method,
		      char * location, struct knownDevices * kd,
    		         moduleInfoSet modInfo, moduleList modLoaded,
		         moduleDeps modDeps, int flags) {
    static struct networkDeviceConfig netDev;
    char * devName;
    int i, rc;
    char * host = NULL;
    char * dir = NULL;
    char * fullPath;
    int stage = NFS_STAGE_IP;

    initLoopback();

    memset(&netDev, 0, sizeof(netDev));

    i = ensureNetDevice(kd, modInfo, modLoaded, modDeps, flags, &devName);
    if (i) return NULL;

    while (stage != NFS_STAGE_DONE) {
        switch (stage) {
	  case NFS_STAGE_IP:
	    rc = readNetConfig(devName, &netDev, flags);
	    if (rc) {
		if (!FL_TESTING(flags)) pumpDisableInterface(devName);
		return NULL;
	    }
	    stage = NFS_STAGE_NFS;
	    break;

	  case NFS_STAGE_NFS:
	    if (nfsGetSetup(&host, &dir) == LOADER_BACK)
		stage = NFS_STAGE_IP;
	    else
		stage = NFS_STAGE_MOUNT;
	    break;

	  case NFS_STAGE_MOUNT:
	    if (FL_TESTING(flags)) {
		stage = NFS_STAGE_DONE;
		break;
	    }

	    mlLoadModule("nfs", NULL, modLoaded, modDeps, NULL, flags);
	    fullPath = alloca(strlen(host) + strlen(dir) + 2);
	    sprintf(fullPath, "%s:%s", host, dir);

	    logMessage("mounting nfs path %s", fullPath);

	    stage = NFS_STAGE_NFS;

	    if (!doPwMount(fullPath, "/mnt/source", "nfs", 1, 0, NULL, NULL)) {
		if (!access("/mnt/source/RedHat/instimage/usr/bin/anaconda", 
			    X_OK)) {
		    symlink("/mnt/source/RedHat/instimage", "/mnt/runtime");
		    stage = NFS_STAGE_DONE;
		} else {
		    umount("/mnt/source");
		    newtWinMessage(_("Error"), _("OK"), 
				   _("That directory does not seem to contain "
				     "a Red Hat installation tree."));
		}
	    } else {
		newtWinMessage(_("Error"), _("OK"), 
		        _("I could not mount that directory from the server"));
	    }

	    break;
        }
    }

    writeNetInfo("/tmp/netinfo", &netDev);

    free(host);
    free(dir);

    return "dir://mnt/source/.";
}

#endif

#ifdef INCLUDE_NETWORK

#define URL_STAGE_IP			1
#define URL_STAGE_MAIN			2
#define URL_STAGE_SECOND		3
#define URL_STAGE_FETCH			4
#define URL_STAGE_DONE			20

static char * mountUrlImage(struct installMethod * method,
		      char * location, struct knownDevices * kd,
    		      moduleInfoSet modInfo, moduleList modLoaded,
		      moduleDeps modDeps, int flags) {
    int i, rc;
    int stage = URL_STAGE_IP;
    char * devName;
    struct iurlinfo ui;
    char needsSecondary = ' ';
    static struct networkDeviceConfig netDev;
    FD_t fd;
    char * url;
    char buf[1024];
    enum urlprotocol_t proto = 
	!strcmp(method->name, "FTP") ? URL_METHOD_FTP : URL_METHOD_HTTP;

    initLoopback();

    i = ensureNetDevice(kd, modInfo, modLoaded, modDeps, flags, &devName);
    if (i) return NULL;

    memset(&ui, 0, sizeof(ui));
    memset(&netDev, 0, sizeof(netDev));

    while (stage != URL_STAGE_DONE) {
        switch (stage) {
	  case URL_STAGE_IP:
	    rc = readNetConfig(devName, &netDev, flags);
	    if (rc) {
		if (!FL_TESTING(flags)) pumpDisableInterface(devName);
		return NULL;
	    }
	    stage = NFS_STAGE_NFS;

	  case URL_STAGE_MAIN:
	    rc = urlMainSetupPanel(&ui, proto, &needsSecondary);
	    if (rc) 
		stage = URL_STAGE_IP;
	    else
		stage = needsSecondary != ' ' ? 
			URL_STAGE_SECOND : URL_STAGE_FETCH;
	    break;

	  case URL_STAGE_SECOND:
	    rc = urlSecondarySetupPanel(&ui, proto);
	    stage = rc ? URL_STAGE_MAIN : URL_STAGE_FETCH;
	    break;

	  case URL_STAGE_FETCH:
	    if (FL_TESTING(flags)) {
		stage = URL_STAGE_DONE;
		break;
	    }

	    fd = urlinstStartTransfer(&ui, "base/stage2.img");
	    
	    if (fd == NULL || fdFileno(fd) < 0) {
		newtPopWindow();
		snprintf(buf, sizeof(buf), "%s/RedHat/base/stage2.img",
			 ui.urlprefix);
		newtWinMessage(_("FTP"), _("OK"), 
		       _("Unable to retrieve the second stage ramdisk"));
		/*XXX ufdClose(fd);*/
		stage = URL_STAGE_MAIN;
		break;
	    }
	    
	    rc = loadStage2Ramdisk(fdFileno(fd), 0, flags);
	    urlinstFinishTransfer(fd);
	    if (!rc)
		stage = URL_STAGE_DONE;

	    break;
        }
    }

    url = malloc(strlen(ui.urlprefix) + 2);
    strcpy(url, ui.urlprefix);

    writeNetInfo("/tmp/netinfo", &netDev);

    return url;
}

#endif
    
static char * doMountImage(char * location,
			   struct knownDevices * kd,
			   moduleInfoSet modInfo,
			   moduleList modLoaded,
			   moduleDeps modDeps,
			   char ** lang,
			   char ** keymap,
			   char ** kbdtype,
			   int flags) {
    static int defaultMethod = 0;
    int i, rc;
    int validMethods[10];
    int numValidMethods = 0;
    char * installNames[10];
    int methodNum = 0;
    int networkAvailable = 0;
    int localAvailable = 0;
    void * class;
    char * url = NULL;
    enum { STEP_LANG, STEP_KBD, STEP_METHOD, STEP_URL, STEP_DONE } step;

    if ((class = isysGetModuleList(modInfo, DRIVER_NET))) {
	networkAvailable = 1;
	free(class);
    }

    if ((class = isysGetModuleList(modInfo, DRIVER_SCSI))) {
	localAvailable = 1;
	free(class);
    }

#if defined(__alpha__)
    for (i = 0; i < numMethods; i++) {
	installNames[numValidMethods] = installMethods[i].name;
	validMethods[numValidMethods++] = i;
    }
#elif defined(INCLUDE_PCMCIA)
    for (i = 0; i < numMethods; i++) {
	int j;

	for (j = 0; j < kd->numKnown; j++)
	    if (installMethods[i].deviceType == kd->known[j].class) break;

	if (j < kd->numKnown) {
	    if (i == defaultMethod) methodNum = numValidMethods;

	    installNames[numValidMethods] = installMethods[i].name;
	    validMethods[numValidMethods++] = i;
	}
    }
#else
    for (i = 0; i < numMethods; i++) {
	if ((networkAvailable && installMethods[i].network) ||
		(localAvailable && !installMethods[i].network)) {
	    if (i == defaultMethod) methodNum = numValidMethods;

	    installNames[numValidMethods] = installMethods[i].name;
	    validMethods[numValidMethods++] = i;
	}
    }
#endif

    installNames[numValidMethods] = NULL;

    if (!numValidMethods) {
	logMessage("no install methods have the required devices!\n");
	exit(1);
    }

#if defined (INCLUDE_LOCAL) || defined (__sparc__)
# ifdef __sparc__
    /* Check any attached CDROM device for a
       Red Hat CD. If there is one there, just die happy */
    if (!FL_EXPERT(flags)) {
# else
    /* If no network is available, check any attached CDROM device for a
       Red Hat CD. If there is one there, just die happy */
    if (!networkAvailable && !FL_EXPERT(flags)) {
# endif
	url = setupCdrom(NULL, location, kd, modInfo, modLoaded, modDeps,
			 flags, 1);
	if (url) return url;
    }
#endif /* defined (INCLUDE_LOCAL) || defined (__sparc__) */

    startNewt(flags);

    step = STEP_LANG;
    while (step != STEP_DONE) {
	switch (step) {
	case STEP_LANG:
	    chooseLanguage(lang, flags);
	    step = STEP_KBD;
	    break;
	    
	case STEP_KBD:
	    rc = chooseKeyboard (keymap, kbdtype, flags);

	    if (rc == LOADER_BACK)
		step = STEP_LANG;
	    else
		step = STEP_METHOD;
	    break;
	    
	case STEP_METHOD:
	    rc = newtWinMenu(FL_RESCUE(flags) ? _("Rescue Method") :
			     _("Installation Method"), 
			     FL_RESCUE(flags) ?
			     _("What type of media contains the rescue image?")
			     :
			     _("What type of media contains the packages to be "
			       "installed?"), 
			     30, 10, 20, 6, installNames, 
			     &methodNum, _("OK"), _("Back"), NULL);
	    if (rc && rc != 1)
		step = STEP_KBD;
	    else
		step = STEP_URL;
	    break;
	case STEP_URL:
	    url = installMethods[validMethods[methodNum]].mountImage(
		   installMethods + validMethods[methodNum], location,
    		   kd, modInfo, modLoaded, modDeps, flags);
	    if (!url)
		step = STEP_METHOD;
	    else
		step = STEP_DONE;
	    break;
	default:
	    break;
	}
	
    }

    return url;
}

static int kickstartDevices(struct knownDevices * kd, moduleInfoSet modInfo, 
			    moduleList modLoaded, moduleDeps modDeps, 
			    int flags) {
    char ** ksArgv = NULL;
    int ksArgc, rc;
    char * opts, * device, * type;
    char ** optv;
    poptContext optCon;
    int doContinue, missingOkay;	/* obsolete */
    char * fsType = "ext2";
    char * fs;
    struct poptOption diskTable[] = {
	    { "type", 't', POPT_ARG_STRING, &fsType, 0 },
	    { 0, 0, 0, 0, 0 }
	};
    struct poptOption table[] = {
	    { "continue", '\0', POPT_ARG_STRING, &doContinue, 0 },
	    { "missingok", '\0', POPT_ARG_STRING, &missingOkay, 0 },
	    { "opts", '\0', POPT_ARG_STRING, &opts, 0 },
	    { 0, 0, 0, 0, 0 }
	};

    if (!ksGetCommand(KS_CMD_DRIVERDISK, NULL, &ksArgc, &ksArgv)) {
	optCon = poptGetContext(NULL, ksArgc, ksArgv, diskTable, 0);

	do {
	    if ((rc = poptGetNextOpt(optCon)) < -1) {
		logMessage("bad argument to kickstart driverdisk command "
			"%s: %s",
		       poptBadOption(optCon, POPT_BADOPTION_NOALIAS), 
		       poptStrerror(rc));
		break;
	    }

	    fs = poptGetArg(optCon);

	    if (!fs || poptGetArg(optCon)) {
		logMessage("bad arguments to kickstart driverdisk command");
		break;
	    } 

	    if (strcmp(fsType, "nfs")) {
		devMakeInode(fs, "/tmp/disk");
		fs = "/tmp/disk";
	    } 

	    if (!strcmp(fsType, "vfat"))
		mlLoadModule("vfat", NULL, modLoaded, modDeps, NULL, flags);

	    if (doPwMount(fs, "/tmp/drivers", fsType, 1, 0, NULL, NULL)) {
		logMessage("failed to mount %s", fs);
		break;
	    } 

	    if (devCopyDriverDisk(modInfo, modLoaded, modDeps, flags,
				  "/tmp/drivers")) {
		logMessage("driver information missing!");
	    }

	    umount("/tmp/drivers");
	} while (0);
    }

    while (!ksGetCommand(KS_CMD_DEVICE, ksArgv, &ksArgc, &ksArgv)) {
	opts = NULL;

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

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

	type = poptGetArg(optCon);
	device = poptGetArg(optCon);

	if (!type || !device || poptGetArg(optCon)) {
	    logMessage("bad arguments to kickstart device command");
	    poptFreeContext(optCon);
	    continue;
	}

        if (!isysFindModuleInfo(modInfo, device)) {
	    logMessage("unknown module %s", device);
	    continue;
	}

        if (opts)
	    poptParseArgvString(opts, &rc, &optv);
	else
	    optv = NULL;

	rc = mlLoadModule(device, NULL, modLoaded, modDeps, optv, flags);
	if (optv) free(optv);

	if (rc)
	    logMessage("module %s failed to insert", device);
	else
	    logMessage("module %s inserted successfully", device);
    }

    kdFindScsiList(kd);
    kdFindNetList(kd);

    return 0;
}

static char * setupKickstart(char * location, struct knownDevices * kd,
    		             moduleInfoSet modInfo,
			     moduleList modLoaded,
		             moduleDeps modDeps, int * flagsPtr) {
    char ** ksArgv;
    char * device = NULL;
    int ksArgc;
    int ksType;
    int i, rc;
    int flags = *flagsPtr;
    enum deviceClass ksDeviceType;
    struct poptOption * table;
    poptContext optCon;
    char * dir = NULL;
    char * imageUrl;
#ifdef INCLUDE_NETWORK
    static struct networkDeviceConfig netDev;
    char * host = NULL, * url = NULL, * proxy = NULL, * proxyport = NULL;
    char * fullPath;

    struct poptOption ksNfsOptions[] = {
	    { "server", '\0', POPT_ARG_STRING, &host, 0 },
	    { "dir", '\0', POPT_ARG_STRING, &dir, 0 },
	    { 0, 0, 0, 0, 0 }
	};
    
    struct poptOption ksUrlOptions[] = {
	    { "url", '\0', POPT_ARG_STRING, &url, 0 },
	    { "proxy", '\0', POPT_ARG_STRING, &proxy, 0 },
	    { "proxyport", '\0', POPT_ARG_STRING, &proxyport, 0 },
	    { 0, 0, 0, 0, 0 }
	};
#endif
#ifdef INCLUDE_LOCAL
    int partNum, fd;
    char * partname = NULL;
    struct partitionTable partTable;
    struct poptOption ksHDOptions[] = {
	    { "dir", '\0', POPT_ARG_STRING, &dir, 0 },
	    { "partition", '\0', POPT_ARG_STRING, &partname, 0 },
	    { 0, 0, 0, 0, 0 }
    };
#endif

    kickstartDevices(kd, modInfo, modLoaded, modDeps, flags);

    if (0) {
#ifdef INCLUDE_NETWORK
    } else if (ksHasCommand(KS_CMD_NFS)) {
	ksDeviceType = CLASS_NETWORK;
	ksType = KS_CMD_NFS;
	table = ksNfsOptions;
    } else if (ksHasCommand(KS_CMD_URL)) {
	ksDeviceType = CLASS_NETWORK;
	ksType = KS_CMD_URL;
	table = ksUrlOptions;
#endif
#ifdef INCLUDE_LOCAL
    } else if (ksHasCommand(KS_CMD_CDROM)) {
	ksDeviceType = CLASS_CDROM;
	ksType = KS_CMD_CDROM;
	table = NULL;
    } else if (ksHasCommand(KS_CMD_HD)) {
	ksDeviceType = CLASS_UNSPEC;
	ksType = KS_CMD_HD;
	table = ksHDOptions;
#endif
    } else {
	logMessage("no install method specified for kickstart");
	return NULL;
    }

    if (ksDeviceType != CLASS_UNSPEC) {
	for (i = 0; i < kd->numKnown; i++)
	    if (kd->known[i].class == ksDeviceType) break;

	if (i == kd->numKnown) {
	    logMessage("no appropriate device for kickstart method is "
		       "available");
	    return NULL;
	}

	device = kd->known[i].name;
	logMessage("kickstarting through device %s", device);
    }

    if (!ksGetCommand(KS_CMD_XDISPLAY, NULL, &ksArgc, &ksArgv)) {
	setenv("DISPLAY", ksArgv[1], 1);
    }

    if (!ksGetCommand(KS_CMD_TEXT, NULL, &ksArgc, &ksArgv))
	(*flagsPtr) = (*flagsPtr) | LOADER_FLAGS_TEXT;

    if (table) {
	ksGetCommand(ksType, NULL, &ksArgc, &ksArgv);

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

	if ((rc = poptGetNextOpt(optCon)) < -1) {
	    logMessage("bad argument to kickstart method command %s: %s",
		       poptBadOption(optCon, POPT_BADOPTION_NOALIAS), 
		       poptStrerror(rc));
	    return NULL;
	}
    }

#ifdef INCLUDE_NETWORK
    if (ksType == KS_CMD_NFS || ksType == KS_CMD_URL) {
	startNewt(flags);
	if (kickstartNetwork(device, &netDev, NULL, flags)) return NULL;
	writeNetInfo("/tmp/netinfo", &netDev);
    } else if (ksType == KS_CMD_URL) {
	abort();
    }
#endif

    imageUrl = NULL;

#ifdef INCLUDE_NETWORK
    if (ksType == KS_CMD_NFS) {
	mlLoadModule("nfs", NULL, modLoaded, modDeps, NULL, flags);
	fullPath = alloca(strlen(host) + strlen(dir) + 2);
	sprintf(fullPath, "%s:%s", host, dir);

	logMessage("mounting nfs path %s", fullPath);

	if (doPwMount(fullPath, "/mnt/source", "nfs", 1, 0, NULL, NULL)) 
	    return NULL;
	    
	symlink("/mnt/source/RedHat/instimage", "/mnt/runtime");

	imageUrl = "dir://mnt/source/.";
    }
#endif

#ifdef INCLUDE_LOCAL
    if (ksType == KS_CMD_CDROM) {
	imageUrl = setupCdrom(NULL, location, kd, modInfo, modLoaded, modDeps, 
			  flags, 1);
    } else if (ksType == KS_CMD_HD) {
	char * fsType;
	logMessage("partname is %s", partname);

	for (i = 0; i < kd->numKnown; i++) {
	    if (kd->known[i].class != CLASS_HD) continue;
	    if (!strncmp(kd->known[i].name, partname, strlen(partname) - 1))
		break;
	}
	if (i == kd->numKnown) {
	    logMessage("unknown partition %s", partname);
	    return NULL;
	}

	devMakeInode(kd->known[i].name, "/tmp/hddevice");
	if ((fd = open("/tmp/hddevice", O_RDONLY)) < 0) {
	    logMessage("failed to open device %s", kd->known[i].name);
	    return NULL;
	}

	if ((rc = balkanReadTable(fd, &partTable))) {
	    logMessage("failed to read partition partTable for "
		       "device %s: %d", kd->known[i].name, rc);
	    return NULL;
	}

	close (fd);
	
	partNum = atoi(partname + 3) - 1;
	if (partTable.maxNumPartitions < partNum ||
	    partTable.parts[partNum].type == -1) {
	    logMessage("partition %d on device %s does not exist", partNum,
		       kd->known[i].name);
	    return NULL;
	}

	switch (partTable.parts[partNum].type) {
	#ifdef __sparc__
	  case BALKAN_PART_UFS: fsType = "ufs"; break;
	#endif
	  case BALKAN_PART_EXT2: fsType = "ext2"; break;
	  default: fsType = "vfat"; break;
	}
	imageUrl = setupHardDrive(partname, fsType, dir, flags);
    } 
#endif

    kickstartDevices(kd, modInfo, modLoaded, modDeps, flags);

    return imageUrl;
}

static int parseCmdLineFlags(int flags, char * cmdLine, char ** ksSource) {
    int fd;
    char buf[500];
    int len;
    char ** argv;
    int argc;
    int i;

    logMessage("here with cmdLine %s", cmdLine);

    if (!cmdLine) {
	if ((fd = open("/proc/cmdline", O_RDONLY)) < 0) return flags;
	len = read(fd, buf, sizeof(buf) - 1);
	close(fd);
	if (len <= 0) return flags;

	buf[len] = '\0';
	cmdLine = buf;
    }

    if (poptParseArgvString(cmdLine, &argc, &argv)) return flags;

    for (i = 0; i < argc; i++) {
        if (!strcasecmp(argv[i], "expert"))
	    flags |= LOADER_FLAGS_EXPERT | LOADER_FLAGS_NOPROBE |
		     LOADER_FLAGS_MODDISK;
        else if (!strcasecmp(argv[i], "text"))
	    flags |= LOADER_FLAGS_TEXT;
        else if (!strcasecmp(argv[i], "updates"))
	    flags |= LOADER_FLAGS_UPDATES;
        else if (!strcasecmp(argv[i], "isa"))
	    flags |= LOADER_FLAGS_ISA;
        else if (!strcasecmp(argv[i], "dd"))
	    flags |= LOADER_FLAGS_MODDISK;
        else if (!strcasecmp(argv[i], "driverdisk"))
	    flags |= LOADER_FLAGS_MODDISK;
        else if (!strcasecmp(argv[i], "rescue"))
	    flags |= LOADER_FLAGS_RESCUE;
	else if (!strcasecmp(argv[i], "serial"))
	    flags |= LOADER_FLAGS_SERIAL;
        else if (!strcasecmp(argv[i], "ks"))
	    flags |= LOADER_FLAGS_KICKSTART;
        else if (!strcasecmp(argv[i], "ks=floppy"))
	    flags |= LOADER_FLAGS_KSFLOPPY;
	else if (!strncasecmp(argv[i], "display=", 8))
	    setenv("DISPLAY", argv[i] + 8, 1);
        else if (!strncasecmp(argv[i], "ks=hd:", 6)) {
	    flags |= LOADER_FLAGS_KSHD;
	    *ksSource = argv[i] + 6;
	} else if (!strncasecmp(argv[i], "lang=", 5)) {
	    setLanguage (argv[i] + 5);
#ifdef INCLUDE_KON
	    if (!strcmp (argv[i] + 5, "ja") && startKon) {
		char * args[5];

		args[0] = "kon";
		args[1] = "-e";
		args[2] = "/sbin/continue";
		args[3] = NULL;
		
		execv(FL_TESTING(flags) ? "./loader" : "/sbin/loader", args);
	    }
#endif /* INCLUDE_KON */
	}
    }

    return flags;
}

#ifdef INCLUDE_NETWORK
int kickstartFromNfs(char * location, moduleList modLoaded, moduleDeps modDeps, 
		     int flags) {
    struct networkDeviceConfig netDev;
    char * file, * fullFn;
    char * ksPath;

    if (kickstartNetwork("eth0", &netDev, "dhcp", flags)) {
        logMessage("no dhcp response received");
	return 1;
    }

    writeNetInfo("/tmp/netinfo", &netDev);

    if (!(netDev.dev.set & PUMP_INTFINFO_HAS_BOOTSERVER)) {
	logMessage("no bootserver was found");
	return 1;
    }

    if (!(netDev.dev.set & PUMP_INTFINFO_HAS_BOOTFILE)) {
	file = "/kickstart/";
	logMessage("bootp: no bootfile received");
    } else {
	file = netDev.dev.bootFile;
    }

    ksPath = alloca(strlen(file) + strlen(netDev.dev.hostname) + 70);
    strcpy(ksPath, inet_ntoa(netDev.dev.bootServer));
    strcat(ksPath, ":");
    strcat(ksPath, file);

    if (ksPath[strlen(ksPath) - 1] == '/') {
	ksPath[strlen(ksPath) - 1] = '\0';
	file = malloc(30);
	sprintf(file, "%s-kickstart", inet_ntoa(netDev.dev.ip));
    } else {
	file = strrchr(ksPath, '/');
	if (!file) {
	    file = ksPath;
	    ksPath = "/";
	} else {
	    *file++ = '\0';
	}
    }

    logMessage("ks server: %s file: %s", ksPath, file);

    mlLoadModule("nfs", NULL, modLoaded, modDeps, NULL, flags);

    if (doPwMount(ksPath, "/tmp/nfskd", "nfs", 1, 0, NULL, NULL)) {
	logMessage("failed to mount %s", ksPath);
	return 1;
    }

    fullFn = malloc(strlen(file) + 20);
    sprintf(fullFn, "/tmp/nfskd/%s", file);
    copyFile(fullFn, location);

    umount("/tmp/nfs");

    return 0;
}
#endif

int kickstartFromHardDrive(char * location, 
			   moduleList modLoaded, moduleDeps modDeps, 
			   char * source, int flags) {
    char * device;
    char * fileName;
    char * fullFn;

    mlLoadModule("vfat", NULL, modLoaded, modDeps, NULL, flags);
    #ifdef __sparc__
    mlLoadModule("ufs", NULL, modLoaded, modDeps, NULL, flags);
    #endif

    fileName = strchr(source, '/');
    *fileName = '\0';
    fileName++;
    device = source;

    if (devMakeInode(device, "/tmp/hddevice")) {
	logMessage("failed to make device %s", device);
	return 1;
    }

    if (doPwMount("/tmp/hddevice", "/mnt/hddrive", "ext2", 0, 0, 
		  NULL, NULL) &&
	doPwMount("/tmp/hddevice", "/mnt/hddrive", "vfat", 0, 0, 
		  NULL, NULL)) {
	logMessage("failed to mount %s", device);
    }

    fullFn = alloca(strlen(fileName) + 20);
    sprintf(fullFn, "/mnt/hddrive/%s", fileName);
    copyFile(fullFn, location);

    umount("/mnt/hddrive");

    return 0;
}

int kickstartFromFloppy(char * location, moduleList modLoaded,
			moduleDeps modDeps, int flags) {
    mlLoadModule("vfat", NULL, modLoaded, modDeps, NULL, flags);

    if (devMakeInode("fd0", "/tmp/fd0"))
	return 1;

    if (doPwMount("/tmp/fd0", "/tmp/ks", "vfat", 1, 0, NULL, NULL)) {
	logMessage("failed to mount floppy: %s", strerror(errno));
	return 1;
    }

    if (access("/tmp/ks/ks.cfg", R_OK)) {
	newtWinMessage(_("Error"), _("OK"), 
		_("Cannot find ks.cfg on boot floppy."));
	return 1;
    }

    copyFile("/tmp/ks/ks.cfg", location);

    umount("/tmp/ks");
    unlink("/tmp/fd0");

    logMessage("kickstart file copied to %s", location);

    return 0;
}

void readExtraModInfo(moduleInfoSet modInfo) {
    int num = 0;
    char fileName[80];
    char * dirName;

    sprintf(fileName, "/tmp/DD-%d/modinfo", num);
    while (!access(fileName, R_OK)) {
	dirName = malloc(50);
	sprintf(dirName, "/tmp/DD-%d", num);

	isysReadModuleInfo(fileName, modInfo, dirName);

	sprintf(fileName, "/tmp/DD-%d/modinfo", ++num);
    }
}

/* Recursive */
int copyDirectory(char * from, char * to) {
    DIR * dir;
    struct dirent * ent;
    int fd, outfd;
    char buf[4096];
    int i;
    struct stat sb;
    char filespec[256];
    char filespec2[256];
    char link[1024];

    mkdir(to, 0755);

    if (!(dir = opendir(from))) {
	newtWinMessage(_("Error"), _("OK"), 
		       _("Failed to read directory %s: %s"),
		       from, strerror(errno));
	return 1;
    }

    errno = 0;
    while ((ent = readdir(dir))) {
	if (ent->d_name[0] == '.') continue;

	sprintf(filespec, "%s/%s", from, ent->d_name);
	sprintf(filespec2, "%s/%s", to, ent->d_name);

	lstat(filespec, &sb);

	if (S_ISDIR(sb.st_mode)) {
	    logMessage("recursively copying %s", filespec);
	    if (copyDirectory(filespec, filespec2)) return 1;
	} else if (S_ISLNK(sb.st_mode)) {
	    i = readlink(filespec, link, sizeof(link) - 1);
	    link[i] = '\0';
	    if (symlink(link, filespec2)) {
		logMessage("failed to symlink %s to %s: %s",
		    filespec2, link, strerror(errno));
	    }
	} else {
	    fd = open(filespec, O_RDONLY);
	    if (fd < 0) {
		logMessage("failed to open %s: %s", filespec,
			   strerror(errno));
		return 1;
	    } 
	    outfd = open(filespec2, O_RDWR | O_TRUNC | O_CREAT, 0644);
	    if (outfd < 0) {
		logMessage("failed to create %s: %s", filespec2,
			   strerror(errno));
	    } else {
		fchmod(outfd, sb.st_mode & 07777);

		while ((i = read(fd, buf, sizeof(buf))) > 0)
		    write(outfd, buf, i);
		close(outfd);
	    }

	    close(fd);
	}

	errno = 0;
    }

    closedir(dir);

    return 0;
}

void loadUpdates(struct knownDevices *kd, moduleList modLoaded,
	         moduleDeps modDeps, int flags) {
    int done = 0;
    int rc;

    do { 
	rc = newtWinChoice(_("Devices"), _("OK"), _("Cancel"),
		_("Insert your updates disk and press \"OK\" to continue."));

	if (rc == 2) return;

	devMakeInode("fd0", "/tmp/fd0");
	if (doPwMount("/tmp/fd0", "/tmp/update-disk", "ext2", 1, 0, NULL, 
		      NULL)) {
	    newtWinMessage(_("Error"), _("OK"), 
			   _("Failed to mount floppy disk."));
	} else {
	    /* Copy everything to /tmp/updates so .so files don't get run
	       from /dev/fd0. We could (and probably should) get smarter about
	       this at some point. */
	    winStatus(40, 3, _("Updates"), _("Reading anaconda updates..."));
	    if (!copyDirectory("/tmp/update-disk", "/tmp/updates")) done = 1;
	    newtPopWindow();
	    umount("/tmp/update-disk");
	}
    } while (!done);

    chdir("/tmp/updates");

    return;
}

#ifdef __sparc__
/* Don't load the large ufs module if it will not be needed
   to save some memory on lowmem SPARCs. */
void loadUfs(struct knownDevices *kd, moduleList modLoaded,
	     moduleDeps modDeps, int flags) {
    int i, j, fd, rc;
    struct partitionTable table;
    int ufsloaded = 0;

    for (i = 0; i < kd->numKnown; i++) {
	if (kd->known[i].class == CLASS_HD) {
	    devMakeInode(kd->known[i].name, "/tmp/hddevice");
	    if ((fd = open("/tmp/hddevice", O_RDONLY)) >= 0) {
		if ((rc = balkanReadTable(fd, &table))) {
		    logMessage("failed to read partition table for "
			       "device %s: %d", kd->known[i].name, rc);
		} else {
		    for (j = 0; j < table.maxNumPartitions; j++) {
			if (table.parts[j].type == BALKAN_PART_UFS) {
			    if (!ufsloaded)
				mlLoadModule("ufs", NULL, modLoaded, modDeps, NULL, flags);
			    ufsloaded = 1;
			}
		    }
		}

		close(fd);
	    }
	    unlink("/tmp/hddevice");
	}
    }
}
#else
#define loadUfs(kd,modLoaded,modDeps,flags) do { } while (0)
#endif

int main(int argc, char ** argv) {
    char ** argptr;
    char * anacondaArgs[40];
    char * arg, * url = NULL;
    poptContext optCon;
    int probeOnly = 0;
    moduleList modLoaded;
    char * cmdLine = NULL;
    moduleDeps modDeps;
    int i, rc;
    int flags = 0;
    int testing = 0;
    char * lang = NULL;
    char * keymap = NULL;
    char * kbdtype = NULL;
    struct knownDevices kd;
    moduleInfoSet modInfo;
    char * where;
    struct moduleInfo * mi;
    char twelve = 12;
    char * ksFile = NULL, * ksSource = NULL;
    struct stat sb;
    struct poptOption optionTable[] = {
    	    { "cmdline", '\0', POPT_ARG_STRING, &cmdLine, 0 },
	    { "ksfile", '\0', POPT_ARG_STRING, &ksFile, 0 },
	    { "probe", '\0', POPT_ARG_NONE, &probeOnly, 0 },
	    { "test", '\0', POPT_ARG_NONE, &testing, 0 },
	    { 0, 0, 0, 0, 0 }
    };

    if (!strcmp(argv[0] + strlen(argv[0]) - 6, "insmod"))
	return ourInsmodCommand(argc, argv);
    else if (!strcmp(argv[0] + strlen(argv[0]) - 5, "rmmod"))
	return rmmod_main(argc, argv);
    else if (!strcmp(argv[0] + strlen(argv[0]) - 8, "modprobe"))
	return ourInsmodCommand(argc, argv);

#ifdef INCLUDE_KON
    else if (!strcmp(argv[0] + strlen(argv[0]) - 3, "kon")) {
	i = kon_main(argc, argv);
	return i;
    } else if (!strcmp(argv[0] + strlen(argv[0]) - 8, "continue"))
	startKon = 0;
#endif

#ifdef INCLUDE_PCMCIA
    else if (!strcmp(argv[0] + strlen(argv[0]) - 7, "cardmgr"))
	return cardmgr_main(argc, argv);
    else if (!strcmp(argv[0] + strlen(argv[0]) - 5, "probe"))
	return probe_main(argc, argv);
#endif

    /* The fstat checks disallows serial console if we're running through
       a pty. This is handy for Japanese. */
    fstat(0, &sb);
    if (major(sb.st_rdev) != 3) {
	if (ioctl (0, TIOCLINUX, &twelve) < 0)
	    flags |= LOADER_FLAGS_SERIAL;
    }

    optCon = poptGetContext(NULL, argc, argv, optionTable, 0);

    if ((rc = poptGetNextOpt(optCon)) < -1) {
	fprintf(stderr, "bad option %s: %s\n",
		       poptBadOption(optCon, POPT_BADOPTION_NOALIAS), 
		       poptStrerror(rc));
	exit(1);
    }

    if ((arg = poptGetArg(optCon))) {
	fprintf(stderr, "unexpected argument: %s\n", arg);
	exit(1);
    }

    if (testing) flags |= LOADER_FLAGS_TESTING;

    flags = parseCmdLineFlags(flags, cmdLine, &ksSource);

    if (FL_SERIAL(flags) && !getenv("DISPLAY"))
	flags |= LOADER_FLAGS_TEXT;

    arg = FL_TESTING(flags) ? "./module-info" : "/modules/module-info";
    modInfo = isysNewModuleInfoSet();
    if (isysReadModuleInfo(arg, modInfo, NULL)) {
        fprintf(stderr, "failed to read %s\n", arg);
	sleep(5);
	exit(1);
    }

    openLog(FL_TESTING(flags));

    kd = kdInit();
    mlReadLoadedList(&modLoaded);
    modDeps = mlNewDeps();
    mlLoadDeps(&modDeps, "/modules/modules.dep");

    if (FL_KSFLOPPY(flags)) {
	ksFile = "/tmp/ks.cfg";
	kickstartFromFloppy(ksFile, modLoaded, modDeps, flags);
	flags |= LOADER_FLAGS_KICKSTART;
    }

#ifdef INCLUDE_PCMCIA
    startNewt(flags);

    winStatus(40, 3, _("PC Card"), _("Initializing PC Card Devices..."));
    startPcmcia(modLoaded, modDeps, flags);
    newtPopWindow();
#endif

    kdFindIdeList(&kd);
    kdFindScsiList(&kd);
    kdFindNetList(&kd);

    if (((access("/proc/bus/pci/devices", X_OK) &&
	  access("/proc/openprom", X_OK)) || FL_MODDISK(flags)) 
	    && !ksFile) {
	startNewt(flags);
        devLoadDriverDisk(modInfo, modLoaded, modDeps, flags, 1);
    }

    busProbe(modInfo, modLoaded, modDeps, probeOnly, &kd, flags);
    if (probeOnly) exit(0);

    if (FL_KSHD(flags)) {
	ksFile = "/tmp/ks.cfg";
	kickstartFromHardDrive(ksFile, modLoaded, modDeps, ksSource, flags);
	flags |= LOADER_FLAGS_KICKSTART;
    } 
    
#ifdef INCLUDE_NETWORK
    if (FL_KICKSTART(flags) && !ksFile) {
	ksFile = "/tmp/ks.cfg";
	startNewt(flags);
	kickstartFromNfs(ksFile, modLoaded, modDeps, flags);
    }
#endif

    if (ksFile) {
	ksReadCommands(ksFile);
	url = setupKickstart("/mnt/source", &kd, modInfo, modLoaded, modDeps, 
			     &flags);
    }

    if (!url) {
	url = doMountImage("/mnt/source", &kd, modInfo, modLoaded, modDeps,
			   &lang, &keymap, &kbdtype,
			   flags);
    }

    if (!FL_TESTING(flags)) {
     
	symlink("mnt/runtime/usr", "/usr");
	symlink("mnt/runtime/lib", "/lib");

#ifndef __alpha__ /* the only modules we need for alpha are on the inired */
	unlink("/modules/modules.dep");
	unlink("/modules/module-info");
	unlink("/modules/pcitable");

	symlink("../mnt/runtime/modules/modules.dep",
		"/modules/modules.dep");
	symlink("../mnt/runtime/modules/module-info",
		"/modules/module-info");
	symlink("../mnt/runtime/modules/pcitable",
		"/modules/pcitable");

# ifndef __sparc__
	unlink("/modules/modules.cgz");

	symlink("../mnt/runtime/modules/modules.cgz",
		"/modules/modules.cgz");
# else
	/* All sparc32 modules are on the first stage image, if it is sparc64,
	   then we must keep both the old /modules/modules.cgz which may
	   either contain all modules, or the basic set + one of net or scsi
	   and we extend it with the full set of net + scsi modules. */
	symlink("../mnt/runtime/modules/modules64.cgz",
		"/modules/modules65.cgz");
# endif
#endif /* !__alpha__ */
    }

    spawnShell(flags);			/* we can attach gdb now :-) */

    /* XXX should free old Deps */
    modDeps = mlNewDeps();
    mlLoadDeps(&modDeps, "/modules/modules.dep");

    modInfo = isysNewModuleInfoSet();
    if (isysReadModuleInfo(arg, modInfo, NULL)) {
        fprintf(stderr, "failed to read %s\n", arg);
	sleep(5);
	exit(1);
    }

    readExtraModInfo(modInfo);

    busProbe(modInfo, modLoaded, modDeps, 0, &kd, flags);

    if (((access("/proc/bus/pci/devices", X_OK) &&
	  access("/proc/openprom", X_OK)) || 
	  FL_ISA(flags) || FL_NOPROBE(flags)) && !ksFile) {
	manualDeviceCheck(modInfo, modLoaded, modDeps, &kd, flags);
    }

    if (FL_UPDATES(flags))
        loadUpdates(&kd, modLoaded, modDeps, flags);

    loadUfs(&kd, modLoaded, modDeps, flags);

    if (!FL_TESTING(flags)) {
        int fd;

	fd = open("/tmp/conf.modules", O_WRONLY | O_CREAT, 0666);
	if (fd < 0) {
	    logMessage("error creating /tmp/conf.modules: %s\n", 
	    	       strerror(errno));
	} else {
	    mlWriteConfModules(modLoaded, modInfo, fd);
	    /* HACK - notting */
#ifdef __sparc__
	    write(fd,"alias parport_lowlevel parport_ax\n",34);
#else
	    write(fd,"alias parport_lowlevel parport_pc\n",34);
#endif
	    close(fd);
	}
    }

    mlLoadModule("raid0", NULL, modLoaded, modDeps, NULL, flags);
    mlLoadModule("raid1", NULL, modLoaded, modDeps, NULL, flags);
    mlLoadModule("raid5", NULL, modLoaded, modDeps, NULL, flags);

    stopNewt();
    closeLog();

#if 0
    for (i = 0; i < kd.numKnown; i++) {
    	printf("%-5s ", kd.known[i].name);
	if (kd.known[i].class == CLASS_CDROM)
	    printf("cdrom");
	else if (kd.known[i].class == CLASS_HD)
	    printf("disk ");
	else if (kd.known[i].class == CLASS_NETWORK)
	    printf("net  ");
    	if (kd.known[i].model)
	    printf(" %s\n", kd.known[i].model);
	else
	    printf("\n");
    }
#endif

    argptr = anacondaArgs;
    if (FL_RESCUE(flags)) {
	*argptr++ = "/bin/sh";
    } else {
	*argptr++ = "/usr/bin/anaconda";
	*argptr++ = "-m";
	*argptr++ = url;

	if (FL_SERIAL(flags))
	    *argptr++ = "--serial";
	if (FL_TEXT(flags))
	    *argptr++ = "-T";
	if (FL_EXPERT(flags))
	    *argptr++ = "--expert";

	if (FL_KICKSTART(flags)) {
	    *argptr++ = "--kickstart";
	    *argptr++ = ksFile;
	}

	if (!lang)
	    lang = getenv ("LC_ALL");
	
	if (lang) {
	    *argptr++ = "--lang";
	    *argptr++ = lang;
	}
	
	if (keymap) {
	    *argptr++ = "--keymap";
	    *argptr++ = keymap;
	}

	if (kbdtype) {
	    *argptr++ = "--kbdtype";
	    *argptr++ = kbdtype;
	}

	for (i = 0; i < modLoaded->numModules; i++) {
	    if (!modLoaded->mods[i].path) continue;

	    mi = isysFindModuleInfo(modInfo, modLoaded->mods[i].name);
	    if (!mi) continue;
	    if (mi->major == DRIVER_NET)
		where = "net";
	    else if (mi->major == DRIVER_SCSI)
		where = "scsi";
	    else
		continue;

	    *argptr++ = "--module";
	    *argptr = alloca(80);
	    sprintf(*argptr, "%s:%s:%s", modLoaded->mods[i].path, where,
		    modLoaded->mods[i].name);

	    argptr++;
	}
    }
    
    *argptr = NULL;

    if (!FL_TESTING(flags)) {
    	execv(anacondaArgs[0], anacondaArgs);
        perror("exec");
    }

    return 1;
}