/*
 *   Copyright (c) 1996-1999 David Parsons. All rights reserved.
 *   
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *  3. All advertising materials mentioning features or use of this
 *     software must display the following acknowledgement:
 *     
 *   This product includes software developed by David Parsons
 *   (orc@pell.chi.il.us)
 *
 *  4. My name may not be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *     
 *  THIS SOFTWARE IS PROVIDED BY DAVID PARSONS ``AS IS'' AND ANY
 *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAVID
 *  PARSONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 *  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 *  IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 *  THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/wait.h>

#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>

#include <linux/fs.h>

#include <dialog.h>
#include <ndialog.h>
#include <basis/rpopen.h>

#include "partition.h"
#include "pathnames.h"
#include "util.h"
#include "builddisk.h"
#include "part.h"
#include "media.h"
#include "packages.h"


static int DEFER = 0;		/* if we can't fork after writing required
				 * stuff, wait until after we've loaded
				 * everything and try again.
				 */

/* file system types corresponding to enum par_type (part.h) */
static char* FSTYPE[] = { "swap", "ext2", "ext2", "ext2", "msdos", "other" };
static char *rootpartition = 0;	/* what partition to boot   */

static void write_fstab();
static void write_lilo_conf();
static void wrapup();
static void doformat();

/*
 * things that need to be done before required packages are installed
 */
void
before_required()
{
    doformat();			/* format everything that needs formatting */
    domount(0);			/* mount all the partitions */

    if (chdir(TARGET) != 0)	/* CD to install target */
	die("finding system root: %s", strerror(errno));

    /* build /tmp so that tar has someplace to scribble scratch info into
     */
    mkdir("tmp", 01777);
} /* before_required */


/*
 * things that need to be done after required packages are installed
 */
void
after_required()
{
    pid_t child, ret;
    int status;

    if ((child=fork()) < 0)
	DEFER = 1;
    else if (child == 0) {
	wrapup();
	exit(0);
    }
    else while (waitpid(child, &status, 0) == -1 && errno == EINTR)
	;
} /* after_required */


void
before_selected()
{
}


void
after_selected()
{
    int rc;
    int status;
    pid_t child, ret;

    while ( (child = waitpid(-1, 0, WNOHANG)) > 0)
	fprintf(stderr, "reaped child %d\n", child);

    /* unmount the install media */
    progress_message(EOF, "Unmounting %s", MEDIANAME(mdesc));
    umount(SOURCE);

    close_media(mdesc);
    eject_media(mdesc);
    sync();

    if ((child = fork()) == 0) {
	chdir(TARGET);
	if (DEFER)
	    wrapup();
	else if ((rc = chroot(TARGET)) != 0)
	    die("Configuring");
	else {
	    progress_message(EOF, "configuring shared libraries");
	    spawn(0, 0, "/sbin/ldconfig", "ldconfig", (char*)0);
	}
	exit(0);
    }
    else if (child < 0)
	die("Configuring");
    else while (waitpid(child, &status, 0) == -1 && errno == EINTR)
	;
    
    sync();
    progress_message(EOF, "Loading system configurator");
}


/*
 * progress_message() pops up a message inside a gauge box
 */
void
progress_message(int percentage, char *message, ...)
{
    static int perc = 0;
    char prompt[60];
    va_list ptr;

    va_start(ptr, message);
    vsnprintf(prompt, sizeof prompt, message, ptr);
    va_end(ptr);

    if (percentage != perc && percentage != EOF)
	perc = percentage;

    dialog_gauge(INSTALLER, prompt, 0, 0, 0, sizeof prompt, perc);
} /* progress_message */


/*
 * write_fstab() writes a prototype /etc/fstab.
 */
static void
write_fstab()
{
    int x;
    FILE *f;

    if ((f = fopen(TARGET "/etc/fstab", "w")) != (FILE*)0) {
	fprintf(f, "# fstab created during installation\n#\n\n");
	fprintf(f, "%-15s %-15s proc    defaults 1 1\n", "/proc", "/proc");

	for (x=0; x<nrmountpoints; x++) {
	    if (mountpoints[x].par_type == I_OTHER)
		continue;
	    fprintf(f, "%-15s %-15s %-7s defaults 0 %d\n",
		    mountpoints[x].par_dev, mountpoints[x].par_mount,
		    FSTYPE[mountpoints[x].par_type],
		    (mountpoints[x].par_type == I_DOS) ? 0 : (x+1) );
	    if (mountpoints[x].par_type == I_ROOT)
		rootpartition = mountpoints[x].par_dev;
	}
	/* Supermount /cdrom and /dev/fd0
	 */
	if (mdesc && MEDIATYPE(mdesc) == M_CDROM)
	    fprintf(f, "%-15s %-15s %-7s ro,user,noauto 0 0\n",
			MEDIA(mdesc), "/cdrom", "iso9660");
	fprintf(f, "%-15s %-15s %-7s rw,fs=vfat 0 0\n",
		    "/dev/fd0", "/floppy", "supermount");
	fclose(f);
    }
    else
	die("Could not write disk configuration file");

    /* also write the current mtab, so stage2 can pull the
     * plug on all the mounted filesystems
     */
    if ((f = fopen(TARGET "/etc/mtab", "w")) != (FILE*)0) {
	for (x=0; x < nrmountpoints; x++)
	    if (mountpoints[x].par_type != I_OTHER
	     && mountpoints[x].par_type != I_SWAP)
		fprintf(f, "%s %s %s rw 1 %d\n",
		    mountpoints[x].par_dev, mountpoints[x].par_mount,
		    FSTYPE[mountpoints[x].par_type],
		    (mountpoints[x].par_type == I_ROOT) ? 1 : 2);
	fprintf(f, "/proc /proc proc rw 0 0\n");
	fclose(f);
    }
} /* write_fstab */


/*
 * write_lilo_conf() writes a prototype lilo.conf
 */
static void
write_lilo_conf(int install_method)
{
    FILE *f;
    int mesg=0;

    if (withdosC) {
	/* attempt to write a message file to tell the user about
	 * the options they have at boot time
	 */
	if ((f = fopen(TARGET "/etc/.welcome", "w")) != (FILE*)0) {
	    fprintf(f, "At the LILO: prompt, you may enter LINUX to run\n"
		       "your Linux system, or WINDOWS (this is the default)\n"
		       "to run Windows from your C: drive.\n"
		       "\n"
		       "When running Linux, your Windows files can be found\n"
		       "under /C\n\n");
	    fclose(f);
	    mesg=1;
	}
    }
    if ((f = fopen(TARGET "/etc/lilo.conf", "w")) != (FILE*)0) {
	fprintf(f, "# lilo created during installation\n#\n\n");
	fprintf(f, "install = /boot/boot.b\n"
	           "boot = %s\n"
		   "root = %s\n", Disk[diskC].device, rootpartition);
#if 0
	if (root_is_ide)
	    fprintf(f, "linear\n");
#endif

	if (mesg)
	    fprintf(f, "\n"
		       "message=/etc/.welcome\n");

	fprintf(f, "\n"
		   "prompt\n"
		   "timeout = 300   # 5 seconds\n"
		   "vga = normal    # force sane state\n"
		   "ramdisk = 0     # paranoia setting\n"
		   "read-only\n");

	if (withdosC)
	    fprintf(f, "\n"
		       "other = %s%d\n"
		       "  label = Windows\n"
		       "  table = %s\n",
		       Disk[diskC].device, withdosC, Disk[diskC].device);

	fprintf(f, "\n"
		   "image = /boot/LINUX\n"
		   "  label = LINUX\n");
	if (total_memory)
	    fprintf(f, "  append = \"panic=90 mem=%dM\"\n", total_memory);
	else
	    fprintf(f, "  append = \"panic=90\"\n");

	/* drop in a dummy stanza so people will be given a hint on how to
	 * add new kernels with minimal surprises
	 */
	fprintf(f, "\n"
		   "# If you build your own kernels, uncomment the following\n"
		   "# stanza and replace KERNEL with your kernel name.  It's\n"
		   "# your system, so you may do as you wish, but leaving the\n"
		   "# default kernel to a known-bootable one is A Really Good\n"
		   "# Idea when something goes wrong!\n"
		   "#\n"
		   "#image = /boot/KERNEL\n"
		   "#  label = KERNEL\n"
		   "#  optional\n"
		   "#  append = \"panic=90");
	if (total_memory)
	    fprintf(f, " mem=%dM\"\n", total_memory);
	else
	    fprintf(f, "\"\n");

	fclose(f);
    }
    else
	die("could not write bootstrap configuration");
} /* write_lilo_conf */


/*
 * wrapup does some bookkeeping before stage2 is launched
 */
static void
wrapup()
{
    int rc;
    extern void write_mouse_conf(int, char*);

    progress_message(EOF, "Writing configuration tables");
    write_fstab();
    write_lilo_conf(withdosC);

    chdir(TARGET);
    if ((rc = chroot(TARGET)) != 0)
	die("Configuring");

    progress_message(EOF, "configuring shared libraries");
    spawn(0, 0, "/sbin/ldconfig", "ldconfig", (char*)0);

    progress_message(EOF, "Installing bootstrap");
    spawn(0, 0, "/sbin/lilo", "lilo", (char*)0);

    tcflush(0, TCIFLUSH);

    progress_message(EOF, "Installing miscellaneous configuration info");
    write_mouse_conf(mousetype, mousedev);
} /* wrapup */


/*
 * makemountpath() builds a fully-qualified pathname for mounting or
 *                 unmounting a disk partition.
 */
static void
makemountpath(char *bfr, int bfrsize, char *path)
{
    int len;

    strncpy(bfr, TARGET, bfrsize);

    if (path == 0)
	return;

    len = strlen(path);
    if (len == 0 || (len == 1 && path[0] == '/'))
	return;

    snprintf(bfr, bfrsize, TARGET "%s", path);

    len = strlen(bfr);
} /* makemountpath */


/*
 * domount() mounts all the filesystems that we want.
 */
void
domount(remount)
{
    int x;
    int rc;
    char imount[200];
    struct par *p;
    struct stat st;

    for (x=0; x<nrmountpoints; x++) {
	p = &mountpoints[x];

	if (p->par_type == I_ROOT || p->par_type == I_BOOT
				  || p->par_type == I_LINUX) {
	    if (!remount)
		progress_message(EOF, "Mounting %s", p->par_mount);

	    if (getpid() != 1)
		continue;

	    makemountpath(imount, sizeof imount, p->par_mount);

	    if (remount || makemountdir(p->par_mount)) {
		fprintf(stderr, "mount %s\n", imount);
		rc = mount(p->par_dev,
			    imount,
			    FSTYPE[p->par_type], 0, (char*)0);
		if (rc != 0) {
		    if (stat(imount, &st) != 0)
			fprintf(stderr, "mountpoint %s does not exist\n",
					    imount);
		    else if (stat(p->par_dev, &st) != 0) {
			fprintf(stderr, "mount device %s does not exist\n",
				    p->par_dev);
		    }
		    else {
			fprintf(stderr,
			"failed mount: %s,%s,%s,0,0\n",
			p->par_dev, imount,
			FSTYPE[p->par_type]);
		    }

		    if (!remount)
			die("Mounting %s: %s", 
				p->par_mount,
				strerror(errno));
		}
	    }
	    else
		die("Could not make mountpoint for %s: %s",
			p->par_mount,
			strerror(errno));
	}
    }
} /* domount */


/*
 * dounmount() unmounts all the partitions we made
 */
void
dounmount()
{
    int x;
    struct par *p;
    char imount[200];

    fprintf(stderr, "dounmount\n");
    for (x=nrmountpoints; x > 0; --x) {
	p = &mountpoints[x-1];

	makemountpath(imount, sizeof imount, p->par_mount);

	if (p->par_type == I_SWAP || p->par_type == I_OTHER)
	    continue;

	fprintf(stderr, "unmount (%s)\n", imount);
	if (getpid() == 1)
	    umount(imount);
    }
    for (x=0; x < nrmountpoints; x++) {
	p =&mountpoints[x];

	makemountpath(imount, sizeof imount, p->par_mount);

	if (p->par_type == I_SWAP || p->par_type == I_OTHER)
	    continue;
	fprintf(stderr, "unmount (%s)\n", imount);
	if (getpid() == 1)
	    umount(imount);
    }
} /* dounmount */


/*
 * doformat() formats all the disk partitions that we said we wanted to
 * format.
 */
static void
doformat()
{
    int x;

    /* Two passes:  first initialize and mount swap partitions...
     */
    for (x=0; x <nrmountpoints;x++) {
	if (mountpoints[x].par_type != I_SWAP)
	    continue;
	progress_message(EOF, "making swap on %s", mountpoints[x].par_mount);
	if (getpid() == 1) {
	    spawn(1, 0, PATH_MKSWAP, "mkswap",
			mountpoints[x].par_dev, (char*)0);
	    if (swapon(mountpoints[x].par_dev) != 0)
		Error("adding swap on %s", mountpoints[x].par_dev); 
	}
	else
	    sleep(1);
    }

    /* ... then initialize filesystems
     */
    for (x=0; x<nrmountpoints; x++)
	if (mountpoints[x].par_format) {

	    if (mountpoints[x].par_type == I_SWAP)
		continue;

	    progress_message(EOF, "Initialize %s", mountpoints[x].par_mount);
	    if (getpid() == 1)
		    spawn(1, 0, PATH_MKFS, "mke2fs",
					    "-L", mountpoints[x].par_mount,
					    "-M", mountpoints[x].par_mount,
					    "-v", mountpoints[x].par_dev,
					     (char*)0);
	    else
		sleep(1);
	}
} /* doformat */
