/*
 * install2.c
 * 
 * This is the second half of the install. It is exec'd from the first half
 * once the secondary media has been mounted. It does a bunch of argv
 * processing to figure out what the first half did. It's a bit of a hack, but
 * it gives us a nice install as far as the user can see.
 *
 * Erik Troan (ewt@redhat.com)
 *
 * Copyright 1998 Red Hat Software 
 *
 * 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.
 *
 */

/*
 * We assume the following:
 *
 *	/usr/bin -> any binaries we might need
 *
 * it's up to the first stage installer to make sure this happens.
 *
 */

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <newt.h>
#include <popt.h>
#include <rpm/rpmlib.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>

#include "commands.h"
#include "config.h"
#include "devices.h"
#include "doit.h"
#include "fs.h"
#include "fsedit.h"
#include "hd.h"
#include "otherinsmod.h"
#include "install.h"
#include "intl.h"
#include "kbd.h"
#include "kernel.h"
#include "kickstart.h"
#include "lang.h"
#include "lilo.h"
#include "log.h"
#include "methods.h"
#include "mkswap.h"
#include "net.h"
#include "pkgs.h"
#include "printercfg.h"
#include "run.h"
#include "scsi.h"
#include "upgrade.h"
#include "windows.h"

int testing = 0;
int expert = 0;
int kickstart = 0;

#define STEP_FIRST	0

#define STEP_PATH		0
#define STEP_SCSI		1
#define STEP_FDISKMTAB		2
#define STEP_SWAP		3
#define STEP_FINDPKGS 		4
#define STEP_FORMAT 		5
#define STEP_PICKPKGS 		6
#define STEP_DOIT		7
#define STEP_FINISHNET		8
#define STEP_TIMECONFIG		9
#define STEP_SERVICES		10
#define STEP_PRINTER		11
#define STEP_ROOTPW		12
#define STEP_BOOTDISK		13
#define STEP_LILO		14

#define STEP_UPG_SCSI		1
#define STEP_UPG_PKGS		2
#define STEP_UPG_MTAB		3
#define STEP_UPG_FFILES		4
#define STEP_UPG_DOIT		5
#define STEP_UPG_BOOTDISK	6
#define STEP_UPG_LILO		7

#define STEP_DONE	1000

struct installState {
    int isUpgrade, lastChoice, direction;
    char * pcmcia, * kernel, * keyboard;
    struct partitionTable table;
    struct fstab fstab;
    struct pkgSet ps;
    struct componentSet cs;
    struct installMethod * method;
    struct intfInfo intf, intfFinal;
    struct netInfo netc, netcFinal;
    struct driversLoaded * dl;
    struct installStep * steps;
} ;

typedef int (*installStepFn)(struct installState * state);

/* hack */
int rmmod_main(int argc, char ** argv);

static int setupSCSI(struct installState * state);
static int partitionDisks(struct installState * state);
static int setupSwap(struct installState * state);
static int findInstallFiles(struct installState * state);
static int formatPartitions(struct installState * state);
static int choosePackages(struct installState * state);
static int doInstallStep(struct installState * state);
static int setRootPassword(struct installState * state);
static int createBootdisk(struct installState * state);
static int configureTimezone(struct installState * state);
static int configureServices(struct installState * state);
static int configurePrinter(struct installState * state);
static int setupBootloader(struct installState * state);
static int finishNetworking(struct installState * state);
static int selectPath(struct installState * state);
static int upgrChoosePackages(struct installState * state);
static int upgrFindInstall(struct installState * state);
static void setupSerialConsole(void);

struct installStep {
    char * name;
    int prev, next;
    installStepFn fn;
    int skipOnCancel;
    int completed;
};

/* this table is translated at run time */
static struct installStep installSteps[] = { 
    { N_("Select installation path"),	-1,		STEP_SCSI,
	selectPath, 0, 0 },
    { N_("Setup SCSI"), 			STEP_PATH, 	STEP_FDISKMTAB,
	setupSCSI, 0, 0 },
    { N_("Setup filesystems"), 		STEP_SCSI, 	STEP_SWAP,
	partitionDisks, 0, 0 },
    { N_("Setup swap space"), 		STEP_FDISKMTAB, STEP_FINDPKGS,
	setupSwap, 0, 0 },
    { N_("Find installation files"),	STEP_SWAP,	STEP_FORMAT,
	findInstallFiles, 1, 0 },
    { N_("Choose partitions to format"),STEP_SWAP,	STEP_PICKPKGS,
	formatPartitions, 0, 0 },
    { N_("Choose packages to install"),	STEP_FORMAT,	STEP_DOIT,
	choosePackages, 0, 0 },
    { N_("Install system"),		STEP_PICKPKGS,	STEP_FINISHNET,
	doInstallStep, 0, 0 },
    { N_("Configure networking"),	-1,		STEP_TIMECONFIG,
	finishNetworking, 0, 0 },
    { N_("Configure timezone"),		STEP_FINISHNET,	STEP_SERVICES,
	configureTimezone, 0, 0 },
    { N_("Configure services"),		STEP_TIMECONFIG,STEP_PRINTER,
	configureServices, 0, 0 },
    { N_("Configure printer"),		STEP_SERVICES,	STEP_ROOTPW,
	configurePrinter, 0, 0 },
    { N_("Set root password"),		STEP_PRINTER,	STEP_BOOTDISK,
	setRootPassword, 0, 0 },
    { N_("Create bootdisk"),		STEP_ROOTPW,	STEP_LILO,
	createBootdisk, 0, 0 },
    { N_("Install bootloader"),		STEP_BOOTDISK,	STEP_DONE,
	setupBootloader, 0, 0 },
};

/* this table is translated at run time */
struct installStep upgradeSteps[] = { 
    { N_("Select installation path"),	-1,		STEP_UPG_SCSI,
	selectPath, 0, 0 },
    { N_("Setup SCSI"), 		STEP_PATH, 	STEP_UPG_PKGS,
	setupSCSI, 0, 0 },
    { N_("Find installation files"),	STEP_PATH,	STEP_UPG_MTAB,
	findInstallFiles, 1, 0 },
    { N_("Find current installation"),	STEP_UPG_PKGS,	STEP_UPG_FFILES,
	upgrFindInstall, 0, 0 },
    { N_("Choose packages to upgrade"),	STEP_UPG_FFILES,STEP_UPG_DOIT,
	upgrChoosePackages, 0, 0 },
    { N_("Upgrade system"),		-1,		STEP_UPG_BOOTDISK,
	doInstallStep, 0, 0 },
    { N_("Create bootdisk"),		-1,		STEP_UPG_LILO,
	createBootdisk, 0, 0 },
    { N_("Install bootloader"),		STEP_UPG_BOOTDISK,	STEP_DONE,
	setupBootloader, 0, 0 },
};

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

    if (!testing) {
	fd = open("/dev/tty2", O_RDWR);
	if (fd < 0) {
	    logMessage("cannot open /dev/tty2 -- no shell will be provided");
	    return;
	} else if (access("/usr/bin/sh",  X_OK))  {
	    logMessage("cannot open shell - /usr/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)) {
	       perror("could not set new controlling tty");
	    }

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

	close(fd);
    }
}

static int setupSCSI(struct installState * state) {
    if (state->direction < 0 && scsiDeviceAvailable())
	return INST_CANCEL;

    return setupSCSIInterfaces(0, &state->dl);
}

static int useNewFdisk(int * useNew) {
    int rc;

    rc = newtWinTernary(_("Disk Setup"), _("Disk Druid"), _("fdisk"), 
			_("Back"),
	    _("Disk Druid is a tool for partitioning and setting up mount "
	      "points. It is designed to be easier to use than Linux's "
	      "traditional disk partitioning sofware, fdisk, as well "
	      "as more powerful. However, there are some cases where fdisk "
	      "may be preferred.\n\n"
	      "Which tool would you like to use?"));

    if (rc == 3)
	return INST_CANCEL;

    if (rc == 0 || rc == 1)
	*useNew = 1;
    else
	*useNew = 0;

    return 0;
}

#define PART_METHOD	0
#define PART_DDRUID	1
#define PART_FDISK	2
#define PART_FSTAB	3
#define PART_DONE	10

static int partitionDisks(struct installState * state) {
    int rc = 0;
    char ** drives;
    static int useNew;
    int numDrives;
    int stage;
    
    if (state->isUpgrade) 
	return findAllPartitions(NULL, &state->table);

#if defined(__i386__) || defined(__alpha__)
    if (kickstart) {
	if ((rc = getDriveList(&drives, &numDrives))) return rc;
	return kickstartPartitioning(&state->table, &state->fstab, drives);
    }
#endif

    if (state->direction > 0) 
	stage = PART_METHOD;
    else 
	stage = PART_DDRUID;

    while (stage != 10) {
	switch (stage) {
	  case PART_METHOD:
	    #if defined(__i386__) || defined(__alpha__)
		if ((rc = useNewFdisk(&useNew))) return rc;
		stage = useNew ? PART_DDRUID : PART_FDISK;
	    #else
		useNew = 0, stage = PART_FDISK;
	    #endif
	    break;

	  case PART_DDRUID:
	    if ((rc = getDriveList(&drives, &numDrives))) return rc;
	    rc = FSEditPartitions(&state->table, &state->fstab, drives, 
				  &state->intf, &state->netc, &state->dl, 
				  !useNew);
	    if (rc == INST_ERROR) return rc;
	    stage = PART_DONE;
	    if (rc && useNew) 
		stage = PART_METHOD;
	    else if (rc)
		stage = PART_FDISK;
	    break;

	  case PART_FDISK:
	    if ((rc = partitionDrives()) == INST_ERROR) return rc;
	    stage = PART_DDRUID;
	    if (rc) stage = PART_METHOD;
	    break;
	}
    } 

    return 0;
}

static int findInstallFiles(struct installState * state) {
    int rc;
    char * trFile;

    if (!state->table.parts) { 
	rc = findAllPartitions(NULL, &state->table);
	if (rc) return rc;
    }

    if (state->method->prepareRoot) {
	rc = state->method->prepareRoot(state->method, state->table,
					&state->netc, &state->intf,
					&state->dl);
	if (rc) return rc;
    }

    /* We can now load the third bit of the install. FIXME: This leaks
       memory if we keep redoing it. I suspect much of this code does
       though! */
    if (state->method->getFile(state->method, "install3.tr", &trFile)) {
	logMessage("getFile method failed for %s", "install3.tr");
    } else {
	loadLanguageStage(3, trFile);
	if (state->method->rmFiles) unlink(trFile);
    }

    /* XXX Hack as we did this after freezing the translations */
    if (!getenv("LANG"))
	winStatus(25, 3, "Scanning", _("Scanning packages..."));

    rc = state->method->getPackageSet(state->method, &state->ps);
    if (!rc)
	rc = state->method->getComponentSet(state->method, &state->ps, 
					    &state->cs);

    if (!getenv("LANG"))
	newtPopWindow();

    return rc;
}

static int formatPartitions(struct installState * state) {
    int i;

    if (kickstart) {
	for (i = 0; i < state->fstab.numEntries; i++) {
	    if (state->fstab.entries[i].type == PART_EXT2)
		state->fstab.entries[i].doFormat = 1;
	}

	return 0;
    }

    return queryFormatFilesystems(&state->fstab);
}

static int setupSwap(struct installState * state) {
    return activeSwapSpace(&state->table, &state->fstab);
}

static int choosePackages(struct installState * state) {
    return psSelectPackages(&state->ps, &state->cs, 0, 0);
}

static int doInstallStep(struct installState * state) {
    int rc;
    char * netSharedPath = NULL;
    FILE * f;
    int netSharedLength;
    int i;

    if (!state->isUpgrade) {
	if (!kickstart && !expert) {
	    rc = newtWinChoice(_("Install log"), _("Ok"), _("Back"), 
			_("A complete log of your installation will be in "
			  "/tmp/install.log after rebooting your system. You "
			  "may want to keep this file for later reference."));
	    if (rc == 2) return INST_CANCEL;
	}

	rc = formatFilesystems(&state->fstab);
	if (rc) return rc;

	rc = mountFilesystems(&state->fstab);
	if (rc) return rc;

	if (state->method->prepareMedia) {
	    rc = state->method->prepareMedia(state->method, &state->fstab);
	    if (rc) {
		umountFilesystems(&state->fstab);
		return rc;
	    }
	}
    } else {
	if (!kickstart && !expert) {
	    rc = newtWinChoice(_("Upgrade log"), _("Ok"), _("Back"), 
			_("A complete log of your upgrade will be in "
			  "/tmp/upgrade.log when the upgrade is finished. "
			  "After rebooting, please read it to ensure "
			  "configuration files are properly updated."));
	    if (rc == 2) return INST_CANCEL;
	}
    }

    /* FIXME: should this read the net shared path from /etc/rpmrc
       during upgrades??? Probably. */
    for (i = netSharedLength = 0; i < state->fstab.numEntries; i++) 
	if (state->fstab.entries[i].type == PART_NFS)
	    netSharedLength += 1 + strlen(state->fstab.entries[i].mntpoint);

    if (netSharedLength) {
	netSharedPath = alloca(netSharedLength);
	*netSharedPath = '\0';
	for (i = netSharedLength = 0; i < state->fstab.numEntries; i++) {
	    if (state->fstab.entries[i].type == PART_NFS) {
		if (*netSharedPath) strcat(netSharedPath, ":");
		strcat(netSharedPath, state->fstab.entries[i].mntpoint);
	    }
        }

	logMessage("netSharedPath is: %s\n", netSharedPath);
    }

    rc = doInstall(state->method, &state->ps, 
		   netSharedPath, state->keyboard, state->isUpgrade);

    if (netSharedPath && access("/mnt/etc/rpmrc", X_OK)) {
	logMessage("creating /etc/rpmrc for netshared info (as none exists)");
	f = fopen("/mnt/etc/rpmrc", "w");
	if (!f) {
	    errorWindow("error creating /mnt/etc/rpmrc: %s");
	} else {
	    fprintf(f, "netsharedpath: %s\n", netSharedPath);
	    fclose(f);
	}
    }

    sync();
    sync();

    if (!rc) psFreeComponentSet(&state->cs);

    configPCMCIA(state->pcmcia);

    return rc;
}


static char mksalt(int seed) {
    int num = seed % 64;

    if (num < 26)
	return 'a' + num;
    else if (num < 52)
	return 'A' + (num - 26);
    else if (num < 62)
	return '0' + (num - 52);
    else if (num == 63)
	return '.';
    else
	return '/';
}

static int setRootPassword(struct installState * state) {
    newtComponent form = NULL, text, pw1Entry, pw2Entry, okay, cancel, answer;
    char * pw1 = NULL, * pw2;
    int done = 0;
    char salt[3];
    char cmd[200];
    struct timeval time1, time2;
    char * pw;
    pid_t pid;
    int status, rc;
    static int beenSet = 0;
    char ** argv;
    int argc;
    poptContext optCon;
    int skipCrypt = 0;
    char * reflowedText;
    int height, width;
    newtGrid grid, subgrid, buttons;
    struct poptOption ksOptions[] = {
	    { "iscrypted", '\0', POPT_ARG_NONE, &skipCrypt, 0 },
	    { 0, 0, 0, 0, 0 }
	};

    if (kickstart) {
	if (!ksGetCommand(KS_CMD_ROOTPW, NULL, &argc, &argv)) {
	    optCon = poptGetContext(NULL, argc, argv, ksOptions, 0);

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

	    if (!(pw1 = poptGetArg(optCon))) {
		newtWinMessage(_("rootpw command"),  _("Ok"),
			   _("Missing password"));
		skipCrypt = 0;
	    }

	    if (poptGetArg(optCon))
		newtWinMessage(_("rootpw command"),  _("Ok"),
			   _("Unexpected arguments"));

	    poptFreeContext(optCon);
	}
    }	

    if (!pw1) {
	gettimeofday(&time1, NULL);

	subgrid = newtCreateGrid(2, 2);
	newtGridSetField(subgrid, 0, 0, NEWT_GRID_COMPONENT,
			 newtLabel(-1, -1, _("Password:")),
			 0, 0, 1, 0, NEWT_ANCHOR_LEFT, 0);
	newtGridSetField(subgrid, 0, 1, NEWT_GRID_COMPONENT,
			 newtLabel(-1, -1, _("Password (again):")),
			 0, 0, 1, 0, NEWT_ANCHOR_LEFT, 0);

	pw1Entry = newtEntry(-1, -1, "", 24, &pw1, NEWT_ENTRY_HIDDEN);
	pw2Entry = newtEntry(-1, -1, "", 24, &pw2, NEWT_ENTRY_HIDDEN);

	newtGridSetField(subgrid, 1, 0, NEWT_GRID_COMPONENT, pw1Entry,
			 0, 0, 0, 0, 0, 0);
	newtGridSetField(subgrid, 1, 1, NEWT_GRID_COMPONENT, pw2Entry,
			 0, 0, 0, 0, 0, 0);
			
  	reflowedText = newtReflowText(
	  _("Pick a root password. You must type it twice to ensure you know "
	    "what it is and didn't make a mistake in typing. Remember that the "
	    "root password is a critical part of system security!"), 37,
	    5, 5, &width, &height);

	text = newtTextbox(1, 1, width, height, NEWT_TEXTBOX_WRAP);
	newtTextboxSetText(text, reflowedText);
	free(reflowedText);
	buttons = newtButtonBar(_("Ok"), &okay, _("Back"), &cancel, NULL);

	grid = newtGridBasicWindow(text, subgrid, buttons);

	form = newtForm(NULL, NULL, 0);

	newtGridAddComponentsToForm(grid, form, 1);
	newtGridWrappedWindow(grid, _("Root Password"));
	newtGridFree(grid, 1);

	do {
	    newtFormSetCurrent(form, pw1Entry);
	    answer = newtRunForm(form);

	    if (answer == cancel) {
		newtPopWindow();
		newtFormDestroy(form);
		return INST_CANCEL;
	    }
	    
	    if (testing) {
		done = 1;
	    } else if (strcmp(pw1, pw2)) {
		newtWinMessage(_("Password Mismatch"), _("Ok"),
			  _("The passwords you entered were different. Please "
			  "try again."));
		newtEntrySet(pw1Entry, "", 0);
		newtEntrySet(pw2Entry, "", 0);
	    } else if (!strlen(pw1) && beenSet) {
		newtFormDestroy(form);
		newtPopWindow();
		return INST_OKAY;
	    } else if (strlen(pw1) < 6)  {
		newtWinMessage(_("Password Mismatch"), _("Ok"),
			  _("The root password must be at least 6 characters "
			    "long."));
		newtEntrySet(pw1Entry, "", 0);
		newtEntrySet(pw2Entry, "", 0);
	    } else
		done = 1;
	} while (!done);

	newtPopWindow();

	pw2 = alloca(strlen(pw1) + 1);
	strcpy(pw2, pw1);
	pw1 = pw2;

	newtFormDestroy(form);
    }

    if (testing) return 0;

    if (!skipCrypt) {
	gettimeofday(&time2, NULL);

	salt[0] = mksalt(time1.tv_usec);
	salt[1] = mksalt(time2.tv_usec);
	salt[2] = '\0';

	pw = crypt(pw1, salt);
    } else {
	pw = pw1;
    }

    sprintf(cmd, "/bin/sed 's&root:[^:]*:&root:%s:&' < /etc/passwd > "
	    "/etc/passwd.new", pw);

    if (!(pid = fork())) {
	chroot("/mnt");
	chdir("/mnt");

	exit(system(cmd));
    }

    waitpid(pid, &status, 0);

    unlink("/mnt/etc/passwd");
    rename("/mnt/etc/passwd.new", "/mnt/etc/passwd");

    beenSet = 1;

    return 0;
}
    
static int configureTimezone(struct installState * state) {
    return timeConfig();
}

static int configureServices(struct installState * state) {
    return servicesConfig();
}

static int setupBootloader(struct installState * state) {
    static int first = 1;
#ifdef __alpha
    int rc;
#else
    char * versionString;
    int rc;
    int append = 0;
    char * version, * release;
    int i;
#endif

    if (!state->isUpgrade && first) {
	setupSerialConsole();
    }

    #ifdef __alpha__
	if (first) {
	    first = 0;
	    rc = kernelCopy(state->kernel);
	    if (rc) return rc;
	}

	return INST_NOP;
    #else
	first = 0;

	if (state->isUpgrade && !access("/mnt/etc/conf.modules", X_OK)) {
	    rc = readModuleConfPersist("/mnt/etc", state->dl);
	    if (rc) return rc;
	    append = 1;
	}

	for (i = 0; i < state->ps.numPackages; i++) {
	    if (!strncmp(state->ps.packages[i]->name, "kernel", 6)) break;
	}

	if (i == state->ps.numPackages) {
	    errorWindow("I couldn't find a kernel!");
	    return INST_ERROR;
	} 

	headerGetEntry(state->ps.packages[i]->h, RPMTAG_VERSION, NULL, 
		       (void *) &version, NULL);
	headerGetEntry(state->ps.packages[i]->h, RPMTAG_RELEASE, NULL, 
		       (void *) &release, NULL);
	
	versionString = alloca(strlen(version) + strlen(release) + 2);
	sprintf(versionString, "%s-%s", version, release);

	logMessage("installed kernel version %s", versionString);

	/* installLilo installs silo on the SPARC */
	return installLilo("/mnt/etc", state->table, state->fstab, 
			   versionString);
    #endif
}

static int finishNetworking(struct installState * state) {
    int rc;

    rc = checkNetConfig(&state->intf, &state->netc, &state->intfFinal,
			&state->netcFinal, &state->dl, state->direction);

    if (rc) return rc;

    writeNetConfig("/mnt/etc/sysconfig", &state->netcFinal, 
		   &state->intfFinal, 0);
    writeNetInterfaceConfig("/mnt/etc/sysconfig/network-scripts", 
			    &state->intfFinal);
    writeResolvConf("/mnt/etc", &state->netcFinal);

    /* this is a bit of a hack */
    writeHosts("/mnt/etc", &state->netcFinal, 
	       &state->intfFinal, !state->isUpgrade);

    return 0;
}

static int selectPath(struct installState * state) {
    int result;

    memset(state, 0, sizeof(state));

    if (kickstart) {
	if (!ksGetCommand(KS_CMD_UPGRADE, NULL, NULL, NULL)) {
	    state->steps = upgradeSteps;
	    state->isUpgrade = 1;
	} else {
	    state->steps = installSteps;
	}

	return 0;
    }

    result = newtWinChoice(_("Installation Path"), _("Install"), _("Upgrade"),
	_("Would you like to install a new system or upgrade a system which "
	  "already contains Red Hat Linux 2.0 or later?"));
   
    if (result == 2) {
	state->steps = upgradeSteps;
	state->isUpgrade = 1;
    } else
	state->steps = installSteps;

    return 0;
}

static int upgrFindInstall(struct installState * state) {
    int rc;

    /* this also turns on swap for us */
    rc = readMountTable(state->table, &state->fstab);
    if (rc) return rc;

    if (!testing) {
	mountFilesystems(&state->fstab);

	if (state->method->prepareMedia) {
	    rc = state->method->prepareMedia(state->method, &state->fstab);
	    if (rc) {
		umountFilesystems(&state->fstab);
		return rc;
	    }
	}
    }

    return 0;
}

static int upgrChoosePackages(struct installState * state) {
    int firstTime = 1;
    char * rpmconvertbin;
    int rc;
    char * path;
    char * argv[] = { NULL, NULL };

    if (testing)
	path = "/";
    else
	path = "/mnt";

    if (firstTime) {
	if (access("/mnt/var/lib/rpm/packages.rpm", R_OK)) {
	    if (access("/mnt/var/lib/rpm/packages", R_OK)) {
		errorWindow("No RPM database exists!");
		return INST_ERROR;
	    }

	    if (state->method->getFile(state->method, "rpmconvert", 
		    &rpmconvertbin)) {
		return INST_ERROR;
	    }
	
	    symlink("/mnt/var", "/var");
	    winStatus(35, 3, _("Upgrade"), _("Converting RPM database..."));
	    chmod(rpmconvertbin, 0755);
	    argv[0] = rpmconvertbin;
	    rc = runProgram(RUN_LOG, rpmconvertbin, argv);
	    if (state->method->rmFiles)
		unlink(rpmconvertbin);

	    newtPopWindow();
	    if (rc) return INST_ERROR;
	}
	
	winStatus(35, 3, "Upgrade", _("Finding packages to upgrade..."));
	rc = ugFindUpgradePackages(&state->ps, path);
	newtPopWindow();
	if (rc) return rc;
	firstTime = 0;
	psVerifyDependencies(&state->ps, 1);
    }

    return psSelectPackages(&state->ps, &state->cs, 0, 1);
}

#define DO_RETRY	1
#define DO_NEXT		2
#define DO_PREV		3
#define DO_MENU		4

static int errcanChoices(char * name, int wasCancelled) {
    newtComponent form, retry, previous, menu, text, exitb, answer;
    char textBuf[1000];
    newtGrid grid, buttons;
    char * reflowedText;
    int width, height;
    char * title;

    if (wasCancelled) {
	sprintf(textBuf, _("You cancelled step \"%s\".\n\n"), name);
	title = _("Cancelled");
    } else {
	sprintf(textBuf, _("An error occured during step \"%s\" of the "
		"install.\n\n"), name);
	title = _("Error");
    }

    strcat(textBuf, _("You may retry that step, return to the previous step "
		      "in the install, or see a menu of installation steps "
		      "which will allow you to move around in the install "
		      "more freely. It is not recommended to use the menu "
		      "unless you are already familiar with Red Hat Linux. "
		      "What would you like to do?"));
    reflowedText = newtReflowText(textBuf, 50, 5, 5, &width, &height);

    text = newtTextbox(-1, -1, width, height, NEWT_TEXTBOX_WRAP);
    newtTextboxSetText(text, textBuf);
  
    if (testing) {
	buttons = newtButtonBar("Previous", &previous, "Retry", &retry,
				"Menu", &menu, "Exit", &exitb, NULL);
    } else {
	buttons = newtButtonBar("Previous", &previous, "Retry", &retry,
				"Menu", &menu, NULL);
	exitb = NULL;
    }

    grid = newtCreateGrid(1, 2);
    newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, text, 0, 0, 0, 0, 0, 0);
    newtGridSetField(grid, 0, 1, NEWT_GRID_SUBGRID, buttons, 
		     0, 1, 0, 0, 0, NEWT_GRID_FLAG_GROWX);

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

    answer = newtRunForm(form);
 
    newtPopWindow();
    newtFormDestroy(form);

    if (answer == previous)
	return DO_PREV;
    else if (answer == retry)
	return DO_RETRY;
    else if (answer == menu)
	return DO_MENU;

    newtFinished();
    exit(0); 
}

static int stepMenu(struct installState * state, int currStep) {
    int firstStep = currStep;
    long i;
    int numChoices, whichStep = 0;
    char ** steps;

    while (state->steps[firstStep].prev != -1)
	firstStep = state->steps[firstStep].prev;

    i = firstStep, numChoices = 0;
    do {
	numChoices++;
	i = state->steps[i].next;
    } while (state->steps[i].prev != -1 && state->steps[i].next != STEP_DONE);
    numChoices++;

    steps = alloca(sizeof(char *) * (numChoices + 2));
    steps[0] = _("  Continue with install");
    for (i = firstStep; i < (firstStep + numChoices); i++) {
	steps[i - firstStep + 1] = alloca(50);
	if (state->steps[i].completed)
	   strcpy(steps[i - firstStep + 1], "* ");
	else
	   strcpy(steps[i - firstStep + 1], "  ");

	strcat(steps[i - firstStep + 1], state->steps[i].name);
    }
    steps[numChoices + 1] = NULL;

    newtWinMenu(_("Installation Steps"), _("What step would you like to run? "
		"Steps with a * next to them have already been completed."), 
		48, 5, 5, 6, steps, &whichStep, _("Ok"), NULL);

    if (whichStep == 0)
	return -1;
    else
	return (whichStep - 1) + firstStep;

    return i;
}

static int getNextStep(struct installState * state, int lastStep, int lastrc) {
    int choice;
    int nextStep;

    if (state->lastChoice == DO_MENU) {
	choice = DO_MENU;
    } else if (lastrc == INST_ERROR) {
	choice = errcanChoices(state->steps[lastStep].name, 0);
	kickstart = 0;
    } else if (lastrc == INST_CANCEL) {
	if (!expert) 
	    choice = DO_PREV;
	else
	    choice = errcanChoices(state->steps[lastStep].name, 1);
    } else if (lastrc == INST_NOP) {
	choice = state->lastChoice;
    } else {
	choice = DO_NEXT;
    }

    switch (choice) {
      case DO_PREV:
	nextStep = state->steps[lastStep].prev;
	while (nextStep != -1 && state->steps[nextStep].skipOnCancel && 
	       state->steps[nextStep].prev != -1)
	    nextStep = state->steps[nextStep].prev;

	if (nextStep == -1 || nextStep == lastStep) {
	    newtWinMessage(_("Cancelled"), _("Ok"), 
			   _("I can't go to the previous step"
			     " from here. You will have to try again."));
	    nextStep = lastStep;
	}
	state->direction = -1;
	break;

      case DO_RETRY:
	nextStep = lastStep;
	state->direction = -1;
	break;

      case DO_MENU:
	nextStep = stepMenu(state, lastStep);
	if (nextStep == -1) {
	    choice = DO_NEXT;

	    if (lastrc)
		nextStep = lastStep;
	    else
		nextStep = state->steps[lastStep].next;
	}
	state->direction = 1;
	break;

      case DO_NEXT: default:
        nextStep = state->steps[lastStep].next;
	state->direction = 1;
	break;
    }

    state->lastChoice = choice;

    return nextStep;
}

void doSuspend(void) {
    pid_t pid;
    int status;

    if (testing) {
	newtFinished();
	exit(1);
    }

    newtSuspend();
    if (!(pid = fork())) {
	printf(_("\n\nType <exit> to return to the install program.\n\n"));
	execl("/bin/sh", "-/bin/sh", NULL);
	perror("error execing /bin/sh");
	sleep(5);
	exit(1);
    }
    waitpid(pid, &status, 0);
    newtResume();
}

static void setupSerialConsole(void) {
    int first = 1;
    struct stat sb;
    char * argv[10] = { "/usr/sbin/setconsole", "--speed", NULL, NULL, NULL };
    struct termios tos;
    speed_t speed;

    if (!first) return;
    first = 0;

    if (fstat(0, &sb)) {
	logMessage("error stat'ing stdin: %s", strerror(errno));
	return;
    }

    if (!S_ISCHR(sb.st_mode)) {
	logMessage("stdin isn't a character device!!! ack!");
	return;
    }

    if (major(sb.st_rdev) != 4) {
	if (minor(sb.st_rdev) == 64)
	    argv[3] = "ttya";
	else
	    argv[3] = "ttyb";

        tcgetattr(0, &tos);
	speed = cfgetospeed(&tos);
	switch (speed) {
	    case B38400:	argv[2] = "38400"; break;
	    case B19200:	argv[2] = "19200"; break;
	    default:		argv[2] = "9600";  break;
	}

	if (access("/mnt/usr/sbin/setconsole", X_OK)) {
	    logMessage("/mnt/usr/sbin/setconsole does not exist -- skipping");
	    return;
	}

	logMessage("setting up %s as serial console, speed is %s", argv[3], 
			argv[2]);
	runProgramRoot(RUN_LOG, "/mnt", "/usr/sbin/setconsole", argv);
    }
}

static int configurePrinter(struct installState * state) {
    if (kickstart)
	return 0;
    else if (testing) {
	return doConfigurePrinters("/", state->direction);
    } else if (!access("/mnt/usr/bin/lpr", X_OK)) {
	return doConfigurePrinters("/mnt", state->direction);
    }

    return INST_NOP;
}

static int createBootdisk(struct installState * state) {
    int rc;
    int i;
    char * version, * release;
    char * versionString;
    int stage = 1;
    static int beenHere = 0;

    if (!state->isUpgrade && !beenHere) {
	writeFstab(&state->fstab);
	writeModuleConf("/mnt/etc", state->dl, 1);
    }
    beenHere = 1;

    if (kickstart) return INST_NOP;

#ifndef __i386__
    return INST_NOP;
#else
    if (!testing && (access("/mnt/sbin/mkbootdisk", X_OK) || 
	access("/mnt/sbin/mkinitrd", X_OK))) return INST_NOP;

    while (stage < 4) {
	switch (stage) {
	  case 1:
	    rc = newtWinTernary(_("Bootdisk"), _("Yes"), _("No"), _("Back"),
		    _("A custom bootdisk provides a way of booting into your "
		  "Linux system without depending on the normal bootloader. "
		  "This is useful if you don't want to install lilo on your "
		  "system, another operating system removes lilo, or lilo "
		  "doesn't work with your hardware configuration. A custom "
		  "bootdisk can also be used with the Red Hat rescue image, "
		  "making is much easier to recover from severe system "
		  "failures.\n\n"
		  "Would you like to create a bootdisk for your system?"));

	    if (rc == 3) return INST_CANCEL;
	    if (rc == 2) return INST_OKAY;
	    stage = 2;
	    break;

	  case 2:
	    rc = newtWinChoice(_("Bootdisk"), _("Ok"), _("Back"),
		    _("Insert a blank floppy in the first drive "
		      "/dev/fd0."));
	    if (rc == 2) 
		stage = 1;
	    else
		stage = 3;
	    break;

	  case 3:
	    for (i = 0; i < state->ps.numPackages; i++) {
		if (!strncmp(state->ps.packages[i]->name, "kernel", 6)) break;
	    }

	    if (i == state->ps.numPackages) {
		errorWindow("I couldn't find a kernel!");
		return INST_ERROR;
	    } 

	    headerGetEntry(state->ps.packages[i]->h, RPMTAG_VERSION, NULL, 
			   (void *) &version, NULL);
	    headerGetEntry(state->ps.packages[i]->h, RPMTAG_RELEASE, NULL, 
			   (void *) &release, NULL);
	    versionString = alloca(strlen(version) + strlen(release) + 2);
	    sprintf(versionString, "%s-%s", version, release);

	    rc = makeBootdisk("/mnt", versionString);
	    if (rc == INST_ERROR) return INST_ERROR;
	    stage++;
	    break;
	}
    }

    return INST_OKAY;
#endif
}

int main(int argc, char ** argv) {
    char ** argptr;
    int step = STEP_FIRST;
    int rc = 0;
    struct installState state;
    int i;
    int isForce = 0;
    int len = strlen(argv[0]);
    char * spaces;
    int isRescue = 0;
    DIR * dir;
    struct dirent * ent;
    char * kickstartFile = NULL;

    spaces = strdup("                                                      ");

    if (!strcmp(argv[0] + len - 6, "umount")) {
	return umountCommand(argc, argv);
    } else if (!strcmp(argv[0] + len - 5, "mount")) {
	return mountCommand(argc, argv);
    } else if (!strcmp(argv[0] + len - 5, "mkdir")) {
	return mkdirCommand(argc, argv);
    } else if (!strcmp(argv[0] + len - 5, "mknod")) {
	return mknodCommand(argc, argv);
    } else if (!strcmp(argv[0] + len - 3, "cat")) {
	return catCommand(argc, argv);
    } else if (!strcmp(argv[0] + len - 2, "ls")) {
	return lsCommand(argc, argv);
    } else if (!strcmp(argv[0] + len - 2, "ln")) {
	return lnCommand(argc, argv);
    } else if (!strcmp(argv[0] + len - 2, "rm")) {
	return rmCommand(argc, argv);
    } else if (!strcmp(argv[0] + len - 5, "chmod")) {
	return chmodCommand(argc, argv);
    } else if (!strcmp(argv[0] + len - 5, "lsmod")) {
	return lsmodCommand(argc, argv);
    } else if (!strcmp(argv[0] + len - 6, "mkswap")) {
	return mkswapCommand(argc, argv);
    } else if (!strcmp(argv[0] + len - 6, "swapon")) {
	return swaponCommand(argc, argv);
    } else if (!strcmp(argv[0] + len - 6, "uncpio")) {
	return uncpioCommand(argc, argv);
    } else if (!strcmp(argv[0] + len - 6, "insmod")) {
	return ourInsmodCommand(argc, argv);
    } else if (!strcmp(argv[0] + len - 5, "rmmod")) {
	return rmmod_main(argc, argv);
    }

    /* if this fails, it's okay -- it might help with free space though */
    unlink("/sbin/install");

    newtSetSuspendCallback(doSuspend);

    memset(&state, 0, sizeof(state));
    state.steps = installSteps;			/* blind guess */

    argptr = argv + 1;
    while (*argptr) {
	if (!strcmp(*argptr, "--method")) {
	    argptr++;
	    if (!*argptr) {
		fprintf(stderr, "--method requires argument\n");
		exit(1);
	    }
	    state.method = findInstallMethod(*argptr);
	    if (!state.method) {
		fprintf(stderr, "unknown install method: %s\n", *argptr);
		exit(1);
	    }
	} else if (!strcmp(*argptr, "--force")) {
	    isForce = 1;
	} else if (!strcmp(*argptr, "--kickstart") || 
		   !strcmp(*argptr, "--ks")) {
	    argptr++;
	    if (!*argptr) 
		fprintf(stderr, "--kickstart requires argument\n");
	    kickstartFile = *argptr;
	} else if (!strcmp(*argptr, "--rescue")) {
	    isRescue = 1;
	} else if (!strcmp(*argptr, "--expert")) {
	    expert = 1;
	} else if (!strcmp(*argptr, "--test")) {
	    testing = 1;
	} else if (!strcmp(*argptr, "--pcmcia")) {
	    argptr++;
	    if (!*argptr) {
		fprintf(stderr, "--pcmcia requires argument\n");
		exit(1);
	    }
	    state.pcmcia = *argptr;
	} else if (!strcmp(*argptr, "--kernel")) {
	    argptr++;
	    if (!*argptr) {
		fprintf(stderr, "--kernel requires argument\n");
		exit(1);
	    }
	    state.kernel = *argptr;
	} else {
	    /* skipping unknown arguments allows for future expansion */
	    fprintf(stderr, "unknown argument: %s\n", *argptr);
	}
	argptr++;
    }

    if (!isRescue && !state.method) {
	fprintf(stderr, "--method argument is required\n");
	exit(1);
    }

    if (!testing && !isForce && (getpid() > 50)) {
	fprintf(stderr, "you're running me on a live system! that's ");
	fprintf(stderr, "incredibly stupid.\n");
	exit(1);
    }

    fprintf(stderr, "in second stage install\n");

    /* translate some tables */
    for (i = 0; installSteps[i].next != STEP_DONE; i++)
	installSteps[i].name = _(installSteps[i].name);
    installSteps[i].name = _(installSteps[i].name);

    for (i = 0; upgradeSteps[i].next != STEP_DONE; i++)
	upgradeSteps[i].name = _(upgradeSteps[i].name);
    upgradeSteps[i].name = _(upgradeSteps[i].name);
    
    openLog();

    logMessage("second stage install running (version " VERSION " built "
		__DATE__ " " __TIME__ ")");

    logDebugMessage(("extra log messages are enabled"));

    spawnShell();

    newtInit();
    newtCls();

    newtDrawRootText(0, 0, "Red Hat Linux (C) 1998 Red Hat Software");
    newtPushHelpLine(NULL);

    setDefaultLanguage(2);

    if (isRescue) {
	do {
	    rc = setupSCSIInterfaces(0, &state.dl);
	} while (rc);

	/* cut! we're out of here */

	newtFinished();

	execl("/bin/sh", "-/bin/sh", NULL);
	fprintf(stderr, "ack! I couldn't manage to execl() /bin/sh: %s",
		    strerror(errno));
	while (1);
    }

    readNetConfig("/tmp", &state.netc);

    dir = opendir("/tmp");
    if (!dir) 
	logMessage("failed to open directory /tmp: %s", strerror(errno));
    else {
	errno = 0;
	while ((ent = readdir(dir))) {
	    if (!strncmp("ifcfg-", ent->d_name, 6)) break;
	}

	if (!ent && errno) {
	    logMessage("error reading directory entry: %s", strerror(errno));
	} else if (ent) {
	    logMessage("found network config file %s", ent->d_name);
	    readNetInterfaceConfig("/tmp", ent->d_name + 6, &state.intf);
	}
    }
    closedir(dir);

    logMessage("reading /usr/lib/rpmrc");
    rpmReadConfigFiles(NULL, NULL, NULL, 0);
    logMessage("\tdone");

    readModuleConf("/tmp", &state.dl);

    /* make sure we don't pick up any gunk from the outside world */
    putenv("PATH=/usr/bin:/bin:/sbin:/usr/sbin");
    putenv("LD_LIBRARY_PATH=");

    if (kickstartFile) {
        if (ksReadCommands(kickstartFile)) 
	    kickstartFile = NULL;
	else
	    kickstart = 1;

	#ifndef __sparc__ 
	setupKeyboard(&state.keyboard);
	#endif
    } else {
	#ifndef __sparc__ 
	readKbdConfig("/tmp", &state.keyboard);
	#endif
    }

    while (step != STEP_DONE) {
	i = strlen(state.steps[step].name);
	newtDrawRootText(0, 0 - i, state.steps[step].name);
	newtRefresh();
	rc = state.steps[step].fn(&state);
	if (!rc)
	    state.steps[step].completed = 1;

	spaces[i] = '\0';
	newtDrawRootText(0, 0 - i, spaces);
	spaces[i] = ' ';

	step = getNextStep(&state, step, rc);
    }

    if (kickstart)
	ksRunPost();

    newtDrawRootText(0, 72, _("Complete"));
    newtWinMessage(_("Done"), _("Ok"), 
	_("Congratulations, installation is complete.\n\n"
	  "Remove the floppy from the drive and "
	  "press return to reboot. For information on fixes which are "
	  "available for this release of Red Hat Linux, consult the "
	  "Errata available from http://www.redhat.com.\n\n"
	  "Information on configuring your system is available in the post "
	  "install chapter of the Official Red Hat Linux User's Guide."));

    newtFinished();

    return 0;
}
