/*
 * 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 1997 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 <stdlib.h>
#include <string.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 "install.h"
#include "kbd.h"
#include "kernel.h"
#include "kickstart.h"
#include "lilo.h"
#include "log.h"
#include "methods.h"
#include "mkswap.h"
#include "net.h"
#include "perror.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_LILO		13

#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_FINISHNET	6
#define STEP_UPG_LILO		7

#define STEP_DONE	1000

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

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

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

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

struct installStep upgradeSteps[] = { 
    { "Select installation path",	-1,		STEP_UPG_SCSI,
	selectPath, 0, 0 },
    { "Setup SCSI", 			STEP_PATH, 	STEP_UPG_PKGS,
	setupSCSI, 0, 0 },
    { "Find installation files",	STEP_PATH,	STEP_UPG_MTAB,
	findInstallFiles, 1, 0 },
    { "Find current installation",	STEP_UPG_PKGS,	STEP_UPG_FFILES,
	upgrFindInstall, 0, 0 },
    { "Choose packages to upgrade",	STEP_UPG_FFILES,STEP_UPG_DOIT,
	upgrChoosePackages, 0, 0 },
    { "Upgrade system",			-1,		STEP_UPG_FINISHNET,
	doInstallStep, 0, 0 },
    { "Install bootloader",		STEP_UPG_FINISHNET,	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();

	    execl("/bin/sh", "-/bin/sh", NULL);
	    logMessage(perrorstr("exec of /bin/sh failed"));
	}

	close(fd);
    }
}

static int setupSCSI(struct installState * state) {
    return setupSCSIInterfaces(0, &state->dl);
}

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

    rc = newtWinTernary("Disk Setup", "Disk Druid", "fdisk", "Cancel",
	    "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;
}

static int partitionDisks(struct installState * state) {
    int rc = 0;
    char ** drives;
    int useNew;
    int numDrives;
    
    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

    do {
#if defined(__i386__) || defined(__alpha__)
	if ((rc = useNewFdisk(&useNew))) return rc;
#else
	useNew = 0;
#endif

	if (useNew) {
#if defined(__i386__) || defined(__alpha__)
	    if ((rc = getDriveList(&drives, &numDrives))) return rc;
	    if ((rc = FSEditPartitions(&state->table, &state->fstab,
					drives, &state->intf,
				       &state->netc, &state->dl))) return rc;
#endif
	} else {
	    do {
		if ((rc = partitionDrives())) return rc;
		if ((rc = findAllPartitions(NULL, &state->table))) return rc;

		rc = setupMountTable(state->table, &state->fstab, &state->intf,
				   &state->netc, &state->dl);
		if (rc) return rc;
	    } while (rc);
	}
    } while (rc);

    return rc;
}

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

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

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

    return 0;
}

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) {
	    rc = newtWinChoice("Install log", "Ok", "Cancel", "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 == 1) 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) {
	    rc = newtWinChoice("Upgrade log", "Ok", "Cancel", "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 == 1) 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;
    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;
    char ** argv;
    int argc;
    poptContext optCon;
    int skipCrypt = 0;
    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);

	newtCenteredWindow(50, 14, "Root Password");

	form = newtForm(NULL, NULL, 0);

	text = newtTextbox(1, 1, 47, 5, NEWT_TEXTBOX_WRAP);
	newtTextboxSetText(text,
	    "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!");

	newtFormAddComponent(form, newtLabel(3, 7, "Password        :"));
	newtFormAddComponent(form, newtLabel(3, 8, "Password (again):"));

	pw1Entry = newtEntry(21, 7, "", 24, &pw1, NEWT_ENTRY_HIDDEN);
	pw2Entry = newtEntry(21, 8, "", 24, &pw2, NEWT_ENTRY_HIDDEN);

	newtFormAddComponents(form, text, pw1Entry, pw2Entry, NULL);

	newtFormAddComponent(form, newtButton(20, 10, "Ok"));

	do {
	    newtFormSetCurrent(form, pw1Entry);
	    newtRunForm(form);
	    
	    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) < 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();
    }

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

    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");

    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
    int rc;
    int append = 0;
    char * version;
    int i;
#endif

    if (!state->isUpgrade && first) {
	writeFstab(&state->fstab);
	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;
	}

	writeModuleConf("/mnt/etc", state->dl, 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);

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

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

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

    rc = checkNetConfig(&state->intf, &state->netc, &state->dl);
    if (rc) return rc;

    writeNetConfig("/mnt/etc/sysconfig", &state->netc, &state->intf, 0);
    writeNetInterfaceConfig("/mnt/etc/sysconfig/network-scripts", &state->intf);
    writeResolvConf("/mnt/etc", &state->netc);
    writeHosts("/mnt/etc", &state->netc, &state->intf);

    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 2.0 or later?");
   
    if (result == 1) {
	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, 1)) {
		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];

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

    form = newtForm(NULL, NULL, 0);

    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?");

    text = newtTextbox(1, 1, 48, 10, NEWT_TEXTBOX_WRAP);
    newtTextboxSetText(text, textBuf);
  
    if (testing) {
	previous = newtButton(1, 12, "Previous");
	retry = newtButton(15, 12, "Retry");
	menu = newtButton(28, 12, "Menu");
	exitb = newtButton(39, 12, "Exit");
	newtFormAddComponents(form, text, previous, retry, menu, exitb, NULL);
    } else {
	previous = newtButton(5, 12, "Previous");
	retry = newtButton(20, 12, "Retry");
	menu = newtButton(38, 12, "Menu");
	newtFormAddComponents(form, text, previous, retry, menu, NULL);
    }

    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) {
    newtComponent form, listbox, okay, text;
    int firstStep = currStep;
    long i;
    int numChoices, listHeight;
    char buf[200];

    newtCenteredWindow(50, 16, "Installation Steps");

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

    form = newtForm(NULL, NULL, 0);

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

    if (numChoices > 6)
	listHeight = 6;
    else
	listHeight = 0;

    listbox = newtListbox(10, 4, listHeight, NEWT_LISTBOX_RETURNEXIT);

    text = newtTextbox(1, 1, 48, 3, NEWT_TEXTBOX_WRAP);
    newtTextboxSetText(text, 
		"What step would you like to run? Steps with a * next "
		"to them have already been completed.");

    newtListboxAddEntry(listbox, "  Continue with install", 
			(void *) STEP_DONE + 1);
    for (i = firstStep; i < (firstStep + numChoices); i++) {
	if (state->steps[i].completed)
	   strcpy(buf, "* ");
	else
	   strcpy(buf, "  ");

	strcat(buf, state->steps[i].name);
	newtListboxAddEntry(listbox, buf, (void *) i);
    }

    okay = newtButton(23, 11, "Ok");
    newtFormAddComponents(form, text, listbox, okay, NULL);

    newtRunForm(form);

    i = (long) newtListboxGetCurrent(listbox);

    newtFormDestroy(form);
    newtPopWindow();

    if (i == STEP_DONE + 1)
	return -1;

    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) {
	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;
	}
	break;

      case DO_RETRY:
	nextStep = lastStep;
	break;

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

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

      case DO_NEXT: default:
        nextStep = state->steps[lastStep].next;
	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("/");
    } else if (!access("/mnt/usr/bin/lpr", X_OK)) {
	return doConfigurePrinters("/mnt");
    }

    return INST_NOP;
}

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, "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);
    }

    /* 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");
    
    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) 1997 Red Hat Software");
    newtPushHelpLine(NULL);

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

    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.");

    umountFilesystems(&state.fstab);

    newtFinished();

    return 0;
}