/*
 * init.c
 * 
 * This is the install type init 
 *
 * Erik Troan (ewt@redhat.com)
 *
 * Copyright 1996 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.
 *
 */

#if USE_MINILIBC
#include "minilibc.h"
#ifndef SOCK_STREAM
# define SOCK_STREAM 1
#endif 
#else
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <net/if.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/klog.h>
#include <sys/mount.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/reboot.h>
#include <termios.h>

/* Need to tell loop.h what the actual dev_t type is. */
#undef dev_t
#if defined(__alpha) || (defined(__sparc__) && defined(__arch64__))
#define dev_t unsigned int
#else
#define dev_t unsigned short
#endif
#include <linux/loop.h>
#undef dev_t
#define dev_t dev_t

#define syslog klogctl
#endif

struct unmountInfo {
    char * name;
    int mounted;
    int loopDevice;
    enum { FS, LOOP } what;
} ;

#include <linux/cdrom.h>

#define KICK_FLOPPY     1
#define KICK_BOOTP	2

#define MS_REMOUNT      32

#define ENV_PATH 		0
#define ENV_LD_LIBRARY_PATH 	1
#define ENV_HOME		2
#define ENV_TERM		3
#define ENV_DEBUG		4

char * env[] = {
    "PATH=/usr/bin:/bin:/sbin:/usr/sbin:/mnt/sysimage/bin:"
    "/mnt/sysimage/usr/bin:/mnt/sysimage/usr/sbin:/mnt/sysimage/sbin",
    /* we set a nicer ld library path specifically for bash -- a full
       one makes anaconda unhappy */
    "LD_LIBRARY_PATH=/lib:/usr/lib:/usr/X11R6/lib",
    "HOME=/",
    "TERM=linux",
    "DEBUG=",
    "TERMINFO=/etc/linux-terminfo",
    "PYTHONPATH=/tmp/updates",
    NULL
};


/* 
 * this needs to handle the following cases:
 *
 *	1) run from a CD root filesystem
 *	2) run from a read only nfs rooted filesystem
 *      3) run from a floppy
 *	4) run from a floppy that's been loaded into a ramdisk 
 *
 */

int testing;

void printstr(char * string) {
    write(1, string, strlen(string));
}

void fatal_error(int usePerror) {
/* FIXME */
#if 0
    if (usePerror) 
	perror("failed:");
    else
#endif
	printf("failed.\n");

    printf("\nI can't recover from this.\n");
    if (testing) exit(0);
    while (1) ;
}

int doMke2fs(char * device, char * size) {
    char * args[] = { "/usr/sbin/mke2fs", NULL, NULL, NULL };
    int pid, status;

    args[1] = device;
    args[2] = size;

    if (!(pid = fork())) {
	/* child */
	execve("/usr/sbin/mke2fs", args, env);
	fatal_error(1);
    }

    wait4(-1, &status, 0, NULL);
    
    return 0;
}

int hasNetConfiged(void) {
    int rc;
    int s;
    struct ifconf configs;
    struct ifreq devs[10];

    #ifdef __i386__
	return 0;
    #endif

    s = socket(AF_INET, SOCK_STREAM, 0);
    if (s < 0) {
	/* FIXME was perror*/
	printf("error creating socket: %d\n", errno);
	return 0;
    } else {
	/* this is just good enough to tell us if we have anything 
	   configured */
	configs.ifc_len = sizeof(devs);
	configs.ifc_buf = (void *) devs;

	rc = ioctl(s, SIOCGIFCONF, &configs);
	if (rc < 0) {
	    /* FIXME was perror*/
	    printstr("SIOCGIFCONF");
	    return 0;
	}
	if (configs.ifc_len == 0) {
	    return 0;
	}

	return 1;
    }

    return 0;
}

void doklog(char * fn) {
    fd_set readset, unixs;
    int in, out, i;
    int log;
    int s;
    int sock = -1;
    struct sockaddr_un sockaddr;
    char buf[1024];
    int readfd;

    in = open("/proc/kmsg", O_RDONLY,0);
    if (in < 0) {
	/* FIXME: was perror */
	printstr("open /proc/kmsg");
	return;
    }

    out = open(fn, O_WRONLY, 0);
    if (out < 0) 
	printf("couldn't open %s for syslog -- still using /tmp/syslog\n", fn);

    log = open("/tmp/syslog", O_WRONLY | O_CREAT, 0644);
    if (log < 0) {
	/* FIXME: was perror */
	printstr("error opening /tmp/syslog");
	sleep(5);
	
	close(in);
	return;
    }

    /* if we get this far, we should be in good shape */

    if (fork()) {
	/* parent */
	close(in);
	close(out);
	close(log);
	return;
    }
    close(0); 
    close(1);
    close(2);

    dup2(1, log);

#if defined(USE_LOGDEV)
    /* now open the syslog socket */
    sockaddr.sun_family = AF_UNIX;
    strcpy(sockaddr.sun_path, "/dev/log");
    sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sock < 0) {
	printf("error creating socket: %d\n", errno);
	sleep(5);
    }
    printstr("got socket\n");
    if (bind(sock, (struct sockaddr *) &sockaddr, sizeof(sockaddr.sun_family) + 
			strlen(sockaddr.sun_path))) {
	printf("bind error: %d\n", errno);
	sleep(5);
    }
    printstr("bound socket\n");
    chmod("/dev/log", 0666);
    if (listen(sock, 5)) {
	printf("listen error: %d\n", errno);
	sleep(5);
    }
#endif

    syslog(8, NULL, 1);

    FD_ZERO(&unixs);
    while (1) {
	memcpy(&readset, &unixs, sizeof(unixs));

	if (sock >= 0) FD_SET(sock, &readset);
	FD_SET(in, &readset);

	i = select(20, &readset, NULL, NULL, NULL);
	if (i <= 0) continue;

	if (FD_ISSET(in, &readset)) {
	    i = read(in, buf, sizeof(buf));
	    if (i > 0) {
		if (out >= 0) write(out, buf, i);
		write(log, buf, i);
	    }
	} 

	for (readfd = 0; readfd < 20; ++readfd) {
	    if (FD_ISSET(readfd, &readset) && FD_ISSET(readfd, &unixs)) {
		i = read(readfd, buf, sizeof(buf));
		if (i > 0) {
		    if (out >= 0) {
			write(out, buf, i);
			write(out, "\n", 1);
		    }

		    write(log, buf, i);
		    write(log, "\n", 1);
		} else if (i == 0) {
		    /* socket closed */
		    close(readfd);
		    FD_CLR(readfd, &unixs);
		}
	    }
	}

	if (sock >= 0 && FD_ISSET(sock, &readset)) {
	    s = sizeof(sockaddr);
	    readfd = accept(sock, (struct sockaddr *) &sockaddr, &s);
	    if (readfd < 0) {
		if (out >= 0) write(out, "error in accept\n", 16);
		write(log, "error in accept\n", 16);
		close(sock);
		sock = -1;
	    } else {
		FD_SET(readfd, &unixs);
	    }
	}
    }    
}

int setupTerminal(int fd) {
    struct winsize winsize;

    if (ioctl(fd, TIOCGWINSZ, &winsize)) {
	printf("failed to get winsize");
	fatal_error(1);
    }

    winsize.ws_row = 24;
    winsize.ws_col = 80;

    if (ioctl(fd, TIOCSWINSZ, &winsize)) {
	printf("failed to set winsize");
	fatal_error(1);
    }

    env[ENV_TERM] = "TERM=vt100";

    return 0;
}

void undoLoop(struct unmountInfo * fs, int numFs, int this);

void undoMount(struct unmountInfo * fs, int numFs, int this) {
    int len = strlen(fs[this].name);
    int i;

    if (!fs[this].mounted) return;
    fs[this].mounted = 0;

    /* unmount everything underneath this */
    for (i = 0; i < numFs; i++) {
	if (fs[i].name[len] == '/' && 
		!strncmp(fs[this].name, fs[i].name, len)) {
	    if (fs[i].what == LOOP)
		undoLoop(fs, numFs, i);
	    else
		undoMount(fs, numFs, i);
	}
    }

    printf("\t%s", fs[this].name);
    /* don't need to unmount /tmp.  it is busy anyway. */
    if (!testing) {
	if (umount(fs[this].name) < 0) {
	    printf(" umount failed (%d)", errno);
	} else {
	    printf(" done");
	}
    }
    printf("\n");
}

void undoLoop(struct unmountInfo * fs, int numFs, int this) {
    int i;
    int fd;

    if (!fs[this].mounted) return;
    fs[this].mounted = 0;

    /* find the device mount */
    for (i = 0; i < numFs; i++) {
	if (fs[i].what == FS && (fs[i].loopDevice == fs[this].loopDevice))
	    break;
    }

    if (i < numFs) {
	/* the device is mounted, unmount it (and recursively, anything
	 * underneath) */
	undoMount(fs, numFs, i);
    }

    unlink("/tmp/loop");
    mknod("/tmp/loop", 0600 | S_IFBLK, (7 << 8) | fs[this].loopDevice);
    printf("\tdisabling /dev/loop%d", fs[this].loopDevice);
    if ((fd = open("/tmp/loop", O_RDONLY, 0)) < 0) {
	printf(" failed to open device: %d", errno);
    } else {
	if (!testing && ioctl(fd, LOOP_CLR_FD, 0))
	    printf(" LOOP_CLR_FD failed: %d", errno);
	close(fd);
    }

    printf("\n");
}

void unmountFilesystems(void) {
    int fd, size;
    char buf[65535];			/* this should be big enough */
    char * chptr, * start;
    struct unmountInfo filesystems[500];
    int numFilesystems = 0;
    int i;
    struct loop_info li;
    char * device;
    struct stat sb;

    fd = open("/proc/mounts", O_RDONLY, 0);
    if (fd < 1) {
	/* FIXME: was perror */
	printstr("failed to open /proc/mounts");
	sleep(2);
	return;
    }

    size = read(fd, buf, sizeof(buf) - 1);
    buf[size] = '\0';

    close(fd);

    chptr = buf;
    while (*chptr) {
	device = chptr;
	while (*chptr != ' ') chptr++;
	*chptr++ = '\0';
	start = chptr;
	while (*chptr != ' ') chptr++;
	*chptr++ = '\0';

	if (strcmp(start, "/") && strcmp(start, "/tmp")) {
	    filesystems[numFilesystems].name = alloca(strlen(start) + 1);
	    strcpy(filesystems[numFilesystems].name, start);
	    filesystems[numFilesystems].what = FS;
	    filesystems[numFilesystems].mounted = 1;

	    stat(start, &sb);
	    if ((sb.st_dev >> 8) == 7) {
		filesystems[numFilesystems].loopDevice = sb.st_dev & 0xf;
	    } else {
		filesystems[numFilesystems].loopDevice = -1;
	    }

	    numFilesystems++;
	}

	while (*chptr != '\n') chptr++;
	chptr++;
    }

    for (i = 0; i < 7; i++) {
	unlink("/tmp/loop");
	mknod("/tmp/loop", 0600 | S_IFBLK, (7 << 8) | i);
	if ((fd = open("/tmp/loop", O_RDONLY, 0)) >= 0) {
	    if (!ioctl(fd, LOOP_GET_STATUS, &li) && li.lo_name[0]) {
		filesystems[numFilesystems].name = alloca(strlen(li.lo_name) 
								+ 1);
		strcpy(filesystems[numFilesystems].name, li.lo_name);
		filesystems[numFilesystems].what = LOOP;
		filesystems[numFilesystems].mounted = 1;
		filesystems[numFilesystems].loopDevice = i;
		numFilesystems++;
	    }

	    close(fd);
	}
    }

    for (i = 0; i < numFilesystems; i++) {
	if (filesystems[i].what == LOOP) {
	    undoLoop(filesystems, numFilesystems, i);
	}
    }

    for (i = 0; i < numFilesystems; i++) {
	if (filesystems[i].mounted) {
	    undoMount(filesystems, numFilesystems, i);
	}
    }
}

void disableSwap(void) {
    int fd;
    char buf[4096];
    int i;
    char * start;
    char * chptr;

    if ((fd = open("/proc/swaps", O_RDONLY, 0)) < 0) return;

    i = read(fd, buf, sizeof(buf) - 1);
    close(fd);
    if (i < 0) return;
    buf[i] = '\0';

    start = buf;
    while (*start) {
	while (*start != '\n' && *start) start++;
	if (!*start) return;

	start++;
	if (*start != '/') return;
	chptr = start;
	while (*chptr && *chptr != ' ') chptr++;
	if (!(*chptr)) return;
	*chptr = '\0';
	printf("\t%s", start);
	if (swapoff(start)) 
	    printf(" failed (%d)", errno);
	printf("\n");

	start = chptr + 1;
    }
}

void ejectCdrom(void) {
  int ejectfd;
  struct stat sb;

  stat("/tmp/cdrom", &sb);

  if ((sb.st_mode & S_IFBLK) == S_IFBLK) {
    printf("ejecting /tmp/cdrom...");
    if ((ejectfd = open("/tmp/cdrom", O_RDONLY | O_NONBLOCK, 0)) >= 0) {
      if (ioctl(ejectfd, CDROMEJECT, 0))
	printf("eject failed %d ", errno);
      close(ejectfd);
    } else {
      printf("eject failed %d ", errno);
    }
    printf("\n");
  }
}

int main(int argc, char **argv) {
    pid_t installpid, childpid;
    int waitStatus;
    int fd;
    int nfsRoot = 0;
    int roRoot = 0;
    int cdRoot = 0;
    int doReboot = 0;
    int doShutdown =0;
    int isSerial = 0;
#ifdef __alpha__
    char * kernel;
#endif
    char * argvc[15];
    char ** argvp = argvc;
    char twelve = 12;
    int i;

    testing = (getppid() != 0) && (getppid() != 1);

    if (!testing) {
	/* turn off screen blanking */
	printstr("\033[9;0]");
	printstr("\033[8]");
    } else {
	printstr("(running in test mode).\n");
    }

#if 0
    printf("unmounting filesystems...\n"); 
    unmountFilesystems();
    exit(0);
#endif

    umask(022);

    printstr("Greetings.\n");

    printf("Vine Linux install init version %s starting\n", VERSION);

    printf("mounting /proc filesystem... "); 
    if (!testing) {
	if (mount("/proc", "/proc", "proc", 0, NULL))
	    fatal_error(1);
    }
    printf("done\n");

    printf("mounting /dev/pts (unix98 pty) filesystem... "); 
    if (!testing) {
	if (mount("/dev/pts", "/dev/pts", "devpts", 0, NULL))
	    fatal_error(1);
    }
    printf("done\n");

    signal(SIGINT, SIG_IGN);
    signal(SIGTSTP, SIG_IGN);

    for (i = 1; i < argc; i++)
	if (!strcmp (argv[i], "serial")) {
	    isSerial = 1;
	    break;
	}

    if (ioctl (0, TIOCLINUX, &twelve) < 0)
	isSerial = 2;
    
    if (isSerial) {
	char *device = "/dev/ttyS0";

	printf("Vine Linux install init version %s using a serial console\n", 
		VERSION);

	printf("remember, cereal is an important part of a nutritionally "
	       "balanced breakfast.\n\n");

	if (isSerial == 2)
	    device = "/dev/console";
	fd = open(device, O_RDWR, 0);
	if (fd < 0)
	    device = "/dev/tts/0";

	if (fd < 0) {
	    printf("failed to open %s\n", device);
	    fatal_error(1);
	}

	setupTerminal(fd);
    } else {
	fd = open("/dev/tty1", O_RDWR, 0);
	if (fd < 0)
	    fd = open("/dev/vc/1", O_RDWR, 0);

	if (fd < 0) {
	    printf("failed to open /dev/tty1 and /dev/vc/1");
	    fatal_error(1);
	}
    }

    if (testing)
	exit(0);

    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2);
    close(fd);

    setsid();
    if (ioctl(0, TIOCSCTTY, NULL)) {
	printf("could not set new controlling tty\n");
    }

    if (!testing) {
	sethostname("localhost.localdomain", 21);
	/* the default domainname (as of 2.0.35) is "(none)", which confuses 
	   glibc */
	setdomainname("", 0);
    }

    printf("checking for NFS root filesystem...");
    if (hasNetConfiged()) {
	printf("yes\n");
	roRoot = nfsRoot = 1;
    } else {
	printf("no\n");
    }

    if (!nfsRoot) {
	printf("trying to remount root filesystem read write... ");
	if (mount("/", "/", "ext2", MS_REMOUNT | MS_MGC_VAL, NULL)) {
	    printf("failed (but that's okay)\n");
	
	    roRoot = 1;
	} else {
	    printf("done\n");

	    /* 2.0.18 (at least) lets us remount a CD r/w!! */
	    printf("checking for writeable /tmp... ");
	    fd = open("/tmp/tmp", O_WRONLY | O_CREAT, 0644);
	    if (fd < 0) {
		printf("no (probably a CD rooted install)\n");
		roRoot = 1;
	    } else {
		close(fd);
		unlink("/tmp/tmp");
		printf("yes\n");
	    }
	}
    }

    if (!testing && roRoot) {
	printf("creating 300k of ramdisk space... ");
	if (doMke2fs("/dev/ram", "300"))
	    fatal_error(0);

	printf("done\n");
	
	printf("mounting /tmp from ramdisk... ");
	if (mount("/dev/ram", "/tmp", "ext2", 0, NULL))
	    fatal_error(1);

	printf("done\n");

	if (!nfsRoot) cdRoot = 1;
    }

    /* Now we have some /tmp space set up, and /etc and /dev point to
       it. We should be in pretty good shape. */

    if (!testing) 
	doklog("/dev/tty4");

    /* Go into normal init mode - keep going, and then do a orderly shutdown
       when:

	1) /bin/install exits
	2) we receive a SIGHUP 
    */

    printf("running install...\n"); 

    setsid();

    if (!(installpid = fork())) {
	/* child */
	*argvp++ = "/sbin/loader";
	*argvp++ = NULL;

	printf("running %s\n", argvc[0]);
	execve(argvc[0], argvc, env);
	
	exit(0);
    }

    while (!doShutdown) {
	childpid = wait4(-1, &waitStatus, 0, NULL);

	if (childpid == installpid) 
	    doShutdown = 1;
    }

    if (!WIFEXITED(waitStatus) || WEXITSTATUS(waitStatus)) {
	printf("install exited abnormally ");
	if (WIFSIGNALED(waitStatus)) {
	    printf("-- received signal %d", WTERMSIG(waitStatus));
	}
	printf("\n");
    } else {
	doReboot = 1;
    }

    if (testing)
        exit(0);

    sync(); sync();

    if (!testing) {
	printf("sending termination signals...");
	kill(-1, 15);
	sleep(2);
	printf("done\n");

	printf("sending kill signals...");
	kill(-1, 9);
	sleep(2);
	printf("done\n");
    }

    printf("disabling swap...\n");
    disableSwap();

    printf("unmounting filesystems...\n"); 
    unmountFilesystems();

    ejectCdrom();

    if (doReboot) {
	printf("rebooting system\n");
	sleep(2);

#if USE_MINILIBC
	reboot(0xfee1dead, 672274793, 0x1234567);
#else
# ifdef __alpha__
	reboot(RB_HALT_SYSTEM);
# else
	reboot(RB_AUTOBOOT);
# endif
#endif
    } else {
	printf("you may safely reboot your system\n");
	while (1);
    }

    exit(0);

    return 0;
}
