/*
 * 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 <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 "hd.h"
#include "install.h"
#include "kbd.h"
#include "kernel.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;

#define STEP_FIRST	0

#define STEP_PATH		0
#define STEP_SCSI		1
#define STEP_FDISK		2
#define STEP_SWAP		3
#define STEP_FINDPKGS 		4
#define STEP_MTAB 		5
#define STEP_FORMAT 		6
#define STEP_PICKPKGS 		7
#define STEP_DOIT		8
#define STEP_FINISHNET		9
#define STEP_TIMECONFIG		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 setupFilesystem(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 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_FDISK,
	setupSCSI, 0, 0 },
    { "Partition disks", 		STEP_PATH, 	STEP_SWAP,
	partitionDisks, 0, 0 },
    { "Setup swap space", 		STEP_FDISK, 	STEP_FINDPKGS,
	setupSwap, 0, 0 },
    { "Find installation files",	STEP_SWAP,	STEP_MTAB,
	findInstallFiles, 1, 0 },
    { "Setup filesystems",		STEP_SWAP,	STEP_FORMAT,
	setupFilesystem, 0, 0 },
    { "Choose partitions to format",	STEP_MTAB,	STEP_PICKPKGS,
	formatPartitions, 0, 0 },
    { "Choose packages to install",	STEP_FORMAT,	STEP_DOIT,
	choosePackages, 0, 0 },
    { "Install system",			-1,		STEP_FINISHNET,
	doInstallStep, 0, 0 },
    { "Configure networking",		-1,		STEP_TIMECONFIG,
	finishNetworking, 0, 0 },
    { "Configure timezone",		STEP_FINISHNET,	STEP_PRINTER,
	configureTimezone, 0, 0 },
    { "Configure printer",		STEP_TIMECONFIG, 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 partitionDisks(struct installState * state) {
    int rc;
    
    if (!state->isUpgrade) {
	rc = partitionDrives();
	if (rc) return rc;
    }

    return findAllPartitions(&state->table);
}

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

    if (!state->table.parts) { 
	rc = findAllPartitions(&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 setupFilesystem(struct installState * state) {
    return setupMountTable(state->table, &state->fstab, &state->intf,
			   &state->netc, &state->dl);
}

static int formatPartitions(struct installState * state) {
    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) {
	winMessage(15, 7, 50, 10, "Install log", "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.");

	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 {
	winMessage(15, 7, 50, 10, "Upgrade log", "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.");
    }

    /* 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->cs.preskel, &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, text, pw1Entry, pw2Entry;
    char * pw1, * pw2;
    int done = 0;
    char salt[3];
    char cmd[200];
    struct timeval time1, time2;
    char * pw;

    gettimeofday(&time1, NULL);

    newtOpenWindow(15, 4, 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)) {
	    winMessage(40, 11, 30, 9, "Password Mismatch",
			"The passwords you entered were different. Please "
			"try again.");
	    newtEntrySet(pw1Entry, "", 0);
	    newtEntrySet(pw2Entry, "", 0);
	} else if (strlen(pw1) < 6)  {
	    winMessage(40, 10, 30, 9, "Password Mismatch",
			"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;

    gettimeofday(&time2, NULL);

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

    pw = crypt(pw1, salt);

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

    newtFormDestroy(form);

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

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

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) {
    newtComponent install, upgrade, text, f, answer;

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

    newtOpenWindow(15, 6, 50, 10, "Installation Path");

    text = newtTextbox(1, 1, 47, 3, NEWT_TEXTBOX_WRAP);
    newtTextboxSetText(text,
	"Would you like to install a new system or upgrade a system which "
	"already contains Red Hat 2.0 or later?");
   
    install = newtButton(10, 6, "Install");
    upgrade = newtButton(30, 6, "Upgrade");
    
    f = newtForm(NULL, NULL, 0);
    newtFormAddComponents(f, text, install, upgrade, NULL);

    answer = newtRunForm(f);
    if (answer == f)
	answer = newtFormGetCurrent(f);

    newtFormDestroy(f);
    newtPopWindow();

    if (answer == upgrade) {
	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 * upgradeList, * 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...");
	if (state->method->getFile(state->method, "uglist", &upgradeList, 1))
	    return INST_ERROR;
	rc = ugFindUpgradePackages(&state->ps, path, upgradeList);
	if (state->method->rmFiles)
	    unlink(upgradeList);
	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);
	newtOpenWindow(15, 3, 50, 16, "Cancelled");
    } else {
	sprintf(textBuf, "An error occured during step \"%s\" of the "
		"install.\n\n", name);
	newtOpenWindow(15, 3, 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];

    newtOpenWindow(15, 3, 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);
    } 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) {
	    messageWindow("Cancelled", "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;

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

    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, "--rescue")) {
	    isRescue = 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__ ")");

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

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

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

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

    newtDrawRootText(0, 72, "Complete");
    winMessage(10, 3, 60, 16, "Done", 
	"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;
}