/*
 * install.c
 * 
 * This is the first half of the install. It does just enough to get the
 * second half going. It, and everything it needs, has to fit on one floppy
 * along with a kernel and modules. Needless to say, it's a bit tight.
 *
 * Erik Troan (ewt@redhat.com)
 *
 * Copyright 1999 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.
 *
 */

#include <alloca.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>		/* for ntohl */
#include <popt.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/wait.h>
#include <unistd.h>
#include <zlib.h>

#include "cpio.h"
#include "devices.h"
#include "fs.h"
#include "install.h"
#include "otherinsmod.h"
#include "intl.h"
#include "kbd.h"
#include "kickstart.h"
#include "lang.h"
#include "log.h"
#include "methods.h"
#include "net.h"
#include "newt.h"
#include "run.h"
#include "windows.h"
#include "pcmcia-probing/pcmcia-probe.h"

#define KS_NONE		0
#define KS_FLOPPY	1
#define KS_BOOTP	2
#define KS_FILE		3

#ifdef SINGLE_INSTALL
#define main install_main
#else
int testing = 0;
int expert = 0;
int kickstart = 0;
#endif
int debug = 1;
char * ksFile;

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

void welcome(void) {
    if (!testing && !kickstart) {
	newtWinMessage("Red Hat Linux", _("Ok"), 
		_("Welcome to Red Hat Linux!\n\n"
		  "This installation process is outlined in detail in the "
		  "Official Red Hat Linux Installation Guide available from "
		  "Red Hat Software. If you have access to this manual, you "
		  "should read the installation section before continuing.\n\n"
		  "If you have purchased Official Red Hat Linux, be sure to "
		  "register your purchase through our web site, "
		  "http://www.redhat.com."));
    }
}

void mountFloppy() {
    if (!testing) {
	logMessage("mounting ext2 fs on floppy");
	doMount("fd0", "/tmp/bootdisk", "ext2", 1, 0);
	logMessage("floppy filesystem mounted on /tmp/bootdisk");
    }
}

int expandPcmciaArchive() {
    CFD_t cfd;
    char * failedFile;
    int rc;

    cfd.cpioIoType = cpioIoTypeGzFd;
    cfd.cpioGzFd = gzdOpen("/tmp/image/pcmcia.cgz", "r");
    
    if (gzdFileno(cfd.cpioGzFd) == NULL) {
	logMessage("gzdOpen failed to read pcmcia.cgz: %s", strerror(errno));
	return INST_ERROR;
    }

    rc = cpioInstallArchive(&cfd, NULL, 0, NULL, NULL, &failedFile);
    if (rc) {
	logMessage("cpio expansion failed on file %s, error %d\n",
		   failedFile, rc);
	gzdClose(cfd.cpioGzFd);
	return INST_ERROR;
    }

    gzdClose(cfd.cpioGzFd);

    return 0;
}

#ifdef __i386__
extern int loadPCMCIADisk(void);

int setupPCMCIA(char ** arg, int direction) {
    int rc;
    struct driversLoaded * dl = NULL;
    int status;
    char * probeOutput;
    static char pcic[20];
    int argc;
    char **argv;
    poptContext optCon;
    struct poptOption ksPcOptions[] = {
	{ 0, 0, 0, 0, 0 }
    };

    /* just probe and if pcmcia controller exists we load support
       automatically */
    probeOutput = pcmciaProbeController();
    if (probeOutput == NULL) {
	if (direction < 0) return INST_CANCEL;
	return INST_OKAY;
    }

    logMessage("pcmcia probe returned: %s", probeOutput);

    if (strstr(probeOutput, "TCIC")) {
	strcpy(pcic, "tcic");
    } else
	strcpy(pcic, "i82365");
    logMessage("pcmcia pcic type: %s", pcic);

    /* go ahead and tell the rest of the install we found a
       PCMCIA controller */
    *arg = pcic;

    if (kickstart) {
	if (!ksGetCommand(KS_CMD_PCMCIA, argv, &argc, &argv)) {
	    char *t;
	    
	    optCon = poptGetContext(NULL, argc, argv, ksPcOptions, 0);
	    poptGetNextOpt(optCon);
	    t = poptGetArg(optCon);
	    if (t && *t && (!strcasecmp(t,"no") || !strcasecmp(t,"off") ||
			    !strcmp(t, "0")))
		return INST_OKAY;
	    else if (!(t && *t && (!strcasecmp(t,"yes") ||
				   !strcasecmp(t,"on") || !strcmp(t, "1")))) {
		newtWinMessage("PCMCIA Kickstart", "Ok",
			       "bad argument to kickstart "
			       "pcmcia command: %s.\nMust be "
			       "'on', '1', or 'yes' to enable, "
			       "or 'off', '0', or 'no' to disable.",t);
                kickstart=0;
	    }
	}
    } else {
	rc = newtWinTernary(_("PCMCIA Support"), _("Yes"), _("No"), _("Back"),
			    _("Do you need to use PCMCIA devices during the "
			      "install? Answer no to this question if only "
			      "need PCMCIA support after the install. You do "
			      "not need install-time PCMCIA support if you "
			      "are installing Red Hat Linux on a laptop with "
			      "a built-in CDROM drive."));

	if (rc == 2) return INST_OKAY;
	if (rc == 3) return INST_CANCEL;
    }
    do {
	if (!kickstart) {
	    rc = newtWinChoice(_("PCMCIA Support Disk"), _("Ok"), _("Back"),
		       _("PCMCIA support requires a PCMCIA support disk. "
			 "Please remove the boot disk currently in your "
			 "drive and replace it with the Red Hat "
			 "PCMCIA support disk."));
	    if (rc == 2) return INST_CANCEL;
	}
	if ((rc = loadPCMCIADisk()))
	    kickstart = 0;
    } while (rc);

    winStatus(40, 3, "PCMCIA", _("Starting PCMCIA services..."));

    loadModule("pcmcia_core", DRIVER_PCMCIA, DRIVER_MINOR_NONE, &dl);
    loadModule(pcic, DRIVER_PCMCIA, DRIVER_MINOR_NONE, &dl);
    loadModule("ds", DRIVER_PCMCIA, DRIVER_MINOR_NONE, &dl);

    if (!fork()) {
	pid_t child;

	if (!(child = fork())) {
	    execl("/sbin/cardmgr", "/sbin/cardmgr", "-m",
		  "/tmp/pcmcia/lib/modules/preferred", NULL);
	    exit(-1);
	}
	exit(child);
    }

    wait(&status);

    /* if cardmgr a chance to get going */
    sleep(5);

    newtPopWindow();

    return 0;
}
#endif

#ifdef SINGLE_INSTALL
extern void doSuspend(void);
#else
void doSuspend(void) {
    pid_t pid;
    int status;

    if (testing) {
	newtFinished();
	exit(1);
    } else if (access("/bin/sh", X_OK)) {
	return;
    }

    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();
}
#endif

void doKickstart(struct intfInfo * intf, struct netInfo * netc,
		 struct driversLoaded ** dl) {
    char * file;
    char * ksPath;
    int rc;

#ifndef DISABLE_NETWORK
    if (kickstart == KS_BOOTP) {
	if ((bringUpNetworking(intf, netc, dl, 1))) {
	    kickstart = 0;
	} else if (!(intf->set & INTFINFO_HAS_BOOTSERVER)) {
	    newtWinMessage(_("Kickstart Error"), _("Ok"), _("No kickstart "
			   "configuration file server can be found."));
	    kickstart = 0;
	} else {
	    if (!(intf->set & INTFINFO_HAS_BOOTFILE)) {
		file = "/kickstart/";
		logMessage("bootp: no bootfile received");
	    } else {
		file = intf->bootFile;
	    }

	    ksPath = malloc(strlen(file) + strlen(netc->hostname) + 70);
	    strcpy(ksPath, inet_ntoa(intf->bootServer));
	    strcat(ksPath, ":");
	    strcat(ksPath, file);

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

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

	    loadFilesystem("nfs", "nfs", dl);

	    if ((rc = doMount(ksPath, "/tmp/ks", "nfs", 1, 0))) {
		newtWinMessage(_("Error"), _("Ok"), 
			_("I could not mount the kickstart path %s.\n"),
			ksPath);
		kickstart = 0;
	    } else {
		ksFile = malloc(strlen(file) + 20);
		sprintf(ksFile, "/tmp/ks/%s", file);

		if (ksReadCommands(ksFile)) 
		    kickstart = 0;
	    }
	}
    }
#endif

    if (kickstart)
	chooseLanguage();
}

#ifdef SPAWN_SHELL
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("/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);
    }
}
#endif

int main(int argc, char ** argv) {
    char ** argptr, * arg;
    struct installMethod * method;
    int rc;
    char * pcmciaArg = NULL;
    int isSerial;
    int force = 0;
    struct stat sb;
    char * childArgs[30];
    struct intfInfo intf;
    struct netInfo netc;
    struct driversLoaded * dl = NULL;
    char * ksMode = NULL;
    int stage, direction;
    char * keymap = NULL;
    char * kbdtype = NULL;
    poptContext optCon;
    int cont = 0;
    int forceSupp = 0;
    int network = 0;
    int local = 0;
    struct poptOption optionTable[] = {
	    { "expert", '\0', POPT_ARG_NONE, &expert, 0 },
	    { "force", '\0', POPT_ARG_NONE, &force, 0 },
	    { "forcesupp", '\0', POPT_ARG_NONE, &forceSupp, 0 },
	    { "kickstart", '\0', POPT_ARG_STRING, &ksMode, 0 },
	    { "ks", '\0', POPT_ARG_STRING, &ksMode, 0 },
	    { "test", '\0', POPT_ARG_NONE, &testing, 0 },
	    { "local", '\0', POPT_ARG_NONE, &local, 0 },
	    { "network", '\0', POPT_ARG_NONE, &network, 0 },
	    { 0, 0, 0, 0, 0 }
    };

#ifdef SPAWN_SHELL
    spawnShell();
#endif

#ifndef __alpha__
    if (!strcmp(argv[0] + strlen(argv[0]) - 6, "insmod") ||
	!strcmp(argv[0] + strlen(argv[0]) - 8, "modprobe")) {
	return ourInsmodCommand(argc, argv);
    } else if (!strcmp(argv[0] + strlen(argv[0]) - 5, "rmmod")) {
	return rmmod_main(argc, argv);
    } else
#endif
      if (!strcmp(argv[0], "install-continue")) {
	cont = 1;
    }

    memset(&intf, 0, sizeof(intf));
    memset(&netc, 0, sizeof(netc));

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

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

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

    if (ksMode) {
	if (testing) {
	    kickstart = KS_FILE;
	    ksFile = ksMode;
	} else if (!strcmp(ksMode, "floppy"))
	    kickstart = KS_FLOPPY;
	else if (!strcmp(ksMode, "bootp"))
	    kickstart = KS_BOOTP;
	else {
	    fprintf(stderr, "unknown kickstart option %s\n", ksMode);
	    exit(1);
	}
    }

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

    openLog(testing);

    /* see if we're on a serial console -- if so, don't setup a keymap */
    if (fstat(0, &sb)) {
	logMessage("error stat'ing stdin: %s", strerror(errno));
	return 1;
    }

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

    isSerial = (major(sb.st_rdev) == 4 && minor(sb.st_dev) >= 64) ||
               (major(sb.st_rdev) == 5 && minor(sb.st_dev) >= 64);

    logMessage("welcome to the Red Hat install "
	       "(first stage, version " INSTALL_VERSION " built " __DATE__ " "
	       __TIME__")");

    newtInit();
    newtCls();
    newtSetSuspendCallback(doSuspend);
    setDefaultLanguage(1);
    newtDrawRootText(0, 0, _("Welcome to Red Hat Linux"));

    newtPushHelpLine(_("  <Tab>/<Alt-Tab> between elements  | <Space> selects | <F12> next screen "));
    
#if defined(__i386__)
    loadModuleDep("/modules/modules.dep");
#endif
    
    /* kickstart from floppy needs to be handled before PCMCIA */
    if (!cont && kickstart == KS_FLOPPY) {
	loadFilesystem("vfat", "vfat", &dl);
	if (devMakeInode("fd0", "/tmp/fd0"))
	    kickstart = 0;
	else if (doMount("/tmp/fd0", "/tmp/ks", "vfat", 1, 0)) {
	    newtWinMessage(_("Error"), _("Ok"), 
		    _("I could not mount the boot floppy."));
	    kickstart = 0;
	} else if (access("/tmp/ks/ks.cfg", R_OK)) {
	    newtWinMessage(_("Error"), _("Ok"), 
		    _("Cannot find ks.cfg on boot floppy."));
	    kickstart = 0;
	} else {
	    int infd = -1, outfd = -1;
	    char buf[4096];
	    int i;

	    if ((outfd = open("/tmp/ks.cfg", O_CREAT | O_RDWR, 0666)) < 0 
	         || (infd = open("/tmp/ks/ks.cfg", O_RDONLY)) < 0) {
		newtWinMessage(_("Error"), _("Ok"), _("Error opening files "
			       "for kickstart copy: %s\n"), strerror(errno));
		kickstart = 0;
	    } else {
		while ((i = read(infd, buf, sizeof(buf))) > 0) {
		    if (write(outfd, buf, i) != i) break;
		}

		if (infd >= 0) close(infd);
		if (outfd >= 0) close(outfd);

		if (!i) {
		    ksFile = alloca(30);
		    strcpy(ksFile, "/tmp/ks.cfg");

		    if (ksReadCommands(ksFile)) 
			kickstart = 0;
		} else {
		    newtWinMessage(_("Error"), _("Ok"), 
			       _("Error copying kickstart file from floppy."));
		    kickstart = 0;
		}
	    }

	    umount("/tmp/ks");
	    devRemoveInode("/tmp/fd0");
	}
    } else if (cont && kickstart == KS_FLOPPY) {
	 /* continue install after language re-exec - all data was lost */
	ksFile = strdup("/tmp/ks.cfg");

	if (ksReadCommands(ksFile)) 
	    kickstart = 0;
    } else if (kickstart == KS_FILE) {
	rc = 0;
	while (!rc)
	    if ((rc = ksReadCommands(ksFile))) 
		kickstart = 0;
    }

    direction = 1;
    if (cont)
	stage = 1;
    else
	stage = 0;

    do {
	switch (stage) {
	  case 0:
	    if (!kickstart) {
		welcome();
		/* kickstart installs do this later */
		chooseLanguage();
	    }
	    newtFinished();
	    argv[0] = "install-continue";
	    execv(testing ? "./install" : "/sbin/install", argv);
	    break;

	  case 1:
	    setDefaultLanguage(1);
	    if (isSerial || kickstart) {
		direction = 1, stage++;
		break;
	    }
	    rc = setupKeyboard(&keymap, &kbdtype);
	    direction = (rc == INST_CANCEL) ? -1 : 1;
	    stage += direction;
	    break;

	  case 2:
#if defined(__i386__)
	    rc = setupPCMCIA(&pcmciaArg, direction);
	    direction = (rc == INST_CANCEL) ? -1 : 1;
#endif
	    stage += direction;
	    break;

	  case 3:
	    doKickstart(&intf, &netc, &dl);
	    stage += direction;
	    break;

	  case 4:
	    if (!network && !local) /* if they didn't say, assume both */
		network = local = 1;
	    rc = chooseInstallMethod(forceSupp, local, network,
				     &method, &netc, &intf, &dl);
	    direction = (rc == INST_CANCEL) ? -1 : 1;
	    stage += direction;
	    break;
	}
    } while (stage < 5);

    logMessage("method selection completed");

#ifndef DISABLE_NETWORK
    /* FIXME: is this a decent test? */
    if (intf.set) {
	writeNetInterfaceConfig("/tmp", &intf);
	writeNetConfig("/tmp", &netc, &intf, 1);
    }
#endif
    if (dl) writeModuleConf("/tmp", dl, 0);

    logMessage("state saved to /tmp");

    newtFinished();

    closeLog();

    if (testing) exit(0);

    argptr = childArgs;
    *argptr++ = "/usr/bin/runinstall2";
    *argptr++ = "--method";
    *argptr++ = method->abbrev;

    if (expert)
	*argptr++ = "--expert";

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

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

    *argptr++ = NULL;

    execv(childArgs[0], childArgs);

    rc = errno;
    openLog(testing);
    logMessage("error in exec of second stage loader :-(");
    logMessage("\terror:%s", strerror(rc));
 
    while (1) ;

    return 0;
}
