/*
 *   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.
 */

static const char rcsid[] = "$Id: s.util.c 1.27 00/09/01 08:06:55-00:00 orc $";

#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <ctype.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>

/* for start_update */
#include <linux/unistd.h>
#include <linux/fs.h>

#include <termios.h>
#include <unistd.h>

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

#include "partition.h"
#include "pathnames.h"
#include "util.h"

/*
 * run a program, waiting for it to finish
 */
void
spawn(int die_on_error, char *title, char *pgm, ...)
{
#if DEBUG2
    int rc;
    int ac;
    char *arg;
    char bfr[200];
    va_list ptr;

    va_start(ptr, pgm);
    strcpy(bfr, pgm);
    if ((arg = va_arg(ptr, char*)) != (char*)0)
	while ((arg = va_arg(ptr, char*)) != (char*)0) {
	    strcat(bfr, " ");
	    strcat(bfr, arg);
	}
    va_end(ptr);

    rc = dialog_prgbox(bfr, bfr, 20, 80, 0, 0);

    if (rc > 1)
	if (die_on_error)
	    die("%s returned with status %d", bfr, WEXITSTATUS(rc));
	else
	    Error("%s returned with status %d", bfr, WEXITSTATUS(rc));
#else
    pid_t child;
    int rc;
    va_list ptr;
    char *program;
    char *arglist[100];
    int x;
    char *arg;
    struct stat st;

    if (stat(pgm, &st) != 0) {
	program = strdup(pgm);
	for (x=0; program[x]; x++)
	    program[x] = tolower(program[x]);

	if (stat(program, &st) != 0) {
	    if (die_on_error)
		die("could not find %s: %s", pgm, strerror(errno));
	    else {
		Error("could not find %s: %s", pgm, strerror(errno));
		return;
	    }
	}
    }
    else program = pgm;

    if (title)
	dialog_msgbox(0, title, -1, 50, 0);
    va_start(ptr, pgm);
    x = 0;
    while ((arg = va_arg(ptr, char *)) != (char*)0)
	arglist[x++] = arg;
    arglist[x] = 0;
    va_end(ptr);
    if ((child = fork()) > 0) {
	/* parent */
	while (waitpid(child, &rc, 0) == -1 && errno == EINTR)
	    ;

	if (WEXITSTATUS(rc) > 1) {
	    redrawwin(stdscr);
	    if (die_on_error)
		die("%s returned with status %d", arglist[0], WEXITSTATUS(rc));
	    else
		Error("%s returned with status %d", arglist[0], WEXITSTATUS(rc));
	}
    }
    else if (child == 0) {
	/* child */
	x = open("/dev/tty7", O_WRONLY);
	close(2);
	dup2(x, 2);
	dup2(x, 1);
	close(x);
	if (pgm == program)
	    printf("running %s (", pgm);
	else
	    printf("running %s [%s] (", program, pgm);
	for (x=0; arglist[x]; x++)
	    printf("%s,", arglist[x]);
	printf("0)\n");
	execv(program, arglist);
	exit(255);
    }
    else {
	redrawwin(stdscr);
	die("could not fork %s", pgm);
    }
    if (program != pgm)
	free(program);
#endif
} /* spawn */


/*
 * putch() -- putchar, guaranteed not to be a macro.
 */
static int
putch(int c)
{
    return putchar(c);
}

/*
 * run() runs a process without doing windowing stuff around it.
 */
void
run(int die_on_error, char *pgm, ...)
{
    pid_t child;
    int rc;
    va_list ptr;
    char *arglist[100];
    char *program;
    int x;
    char *arg;
    struct stat st;

    if (stat(pgm, &st) != 0) {
	program = strdup(pgm);
	for (x=0; program[x]; x++)
	    program[x] = tolower(program[x]);

	if (stat(program, &st) != 0) {
	    if (die_on_error)
		die("could not find %s: %s", pgm, strerror(errno));
	    else
		return;
	}
    }
    else program = pgm;

    va_start(ptr, pgm);
    x = 0;
    while ((arg = va_arg(ptr, char *)) != (char*)0)
	arglist[x++] = arg;
    arglist[x] = 0;
    va_end(ptr);

    if (program != pgm)
	fprintf(stderr, "running %s [%s] (", program, pgm);
    else
	fprintf(stderr, "running %s (", pgm);

    for (x=0; arglist[x]; x++)
	fprintf(stderr, "%s,", arglist[x]);
    fprintf(stderr, "0)\n");

    if ((child = fork()) > 0) {
	/* parent */
	while (waitpid(child, &rc, 0) == -1 && errno == EINTR)
	    ;
	tputs(clear_screen, lines > 0 ? lines : 1, putch);
	redrawwin(stdscr);
	if (WEXITSTATUS(rc) > 1) {
	    if (die_on_error)
		die("%s returned with status %d", arglist[0], WEXITSTATUS(rc));
	    else
		Error("%s returned with status %d", arglist[0], WEXITSTATUS(rc));
	}
    }
    else if (child == 0) {
	/* child */
	execv(program, arglist);
	exit(255);
    }
    else {
	redrawwin(stdscr);
	die("could not fork %s: %s", pgm, strerror(errno));
    }
    if (pgm != program)
	free(program);
} /* run */


/*
 * reboot the system
 */
void
falloverdead()
{
    if (getpid() == 1) {
	/* We only want to be able to reboot if we're process 1.  Children
	 * (and test processes) are NOT allowed to bring the system down
	 */
	reboot(0xfee1dead, 672274793, 0x01234567);
	while (1) sleep(6000);
    }
    kill(mini_init_pid, SIGHUP);
    stop_gpm();
    end_dialog();
    exit(1);
} /* falloverdead */


/*
 * describe what we're doing (for producing error and crash messages)
 */
char *whoweare = "dunno";
char *whatwedo = "dunno";

void
describe(char *myname, char *description)
{
    whoweare = myname;
    whatwedo = description;
} /* describe */


/*
 * give a message, then reboot the system
 */
void
die(char *reason, ...)
{
    char bfr[1000];
    va_list ptr;

    ndObject chain;

    use_helpline("");
    chain = ObjChain(0, newOKButton(0, "reboot", 0, 0));
    if (reason) {
	va_start(ptr, reason);
	vsnprintf(bfr, sizeof bfr, reason, ptr);
	va_end(ptr);
	snprintf(bfr+strlen(bfr), sizeof bfr - strlen(bfr),
		     "\n"
		     "\n"
		     "%s cannot continue; remove the %s\n"
		     "floppy and press any key to reboot",
		     whoweare, whatwedo);
	MENU(chain, -1, -1, "PANIC", bfr, ERROR_FLAG);
    }
    else {
	snprintf(bfr, sizeof bfr, "Remove the %s floppy\n"
	             "and press any key to reboot", whatwedo);
	MENU(chain, -1, -1, 0, bfr, ERROR_FLAG);
    }
    falloverdead();
} /* die */


/*
 * Check to see if a device is there and die if it isn't
 */
int
checkfor(char *device, char *description, void (*fail)(char *, ...))
{
    int fd;

    while ((fd = open(device, O_RDONLY)) < 0) {
	if (fail)
	    (*fail)("%s:%s", description, strerror(errno));
	return 1;
    }
    close(fd);
    return 0;
} /* checkfor */


/*
 * copyfile: copies a file onto the disk.
 */
void
copyfile(char *from, char *to, int extraflags)
{
    int ffd, tfd;
    char bfr[10240];
    int size;
    char *desttemp;

    if ((desttemp = malloc(strlen(to)+2)) == (char*)0)
	die("out of memory copying %s", from);
    snprintf(desttemp, strlen(to)+2, "%s$", to);
    if ((ffd = open(from, O_RDONLY)) < 0)
	die("cannot open %s", from);
    if ((tfd = open(desttemp, O_WRONLY|O_CREAT|extraflags, 0644)) < 0)
	die("cannot create %s", desttemp);
    while ((size = read(ffd, bfr, sizeof bfr)) > 0)
	if (write(tfd, bfr, size) != size)
	    die("%s write error: %s", from, strerror(errno));
    close(ffd);
    close(tfd);
    if (rename(desttemp, to) != 0)
	die("commit error on %s: %s", from, strerror(errno));
    free(desttemp);
} /* copyfile */


/*
 * Size() passed a size in kilobytes, returns a string describing that
 *        size in GB or MB
 */
char *
Size(unsigned long int size)
{
    static char bfr[20];

    const int ONE_GB_IN_K = ONE_GB/ONE_K;

    if (size >= ONE_GB_IN_K)
	snprintf(bfr, sizeof bfr, "%ld.%02ld GB",
		    size/ONE_GB_IN_K, (size%ONE_GB_IN_K)/(ONE_GB_IN_K/100));
    else
	snprintf(bfr, sizeof bfr, "%ld MB", size/ONE_K);

    return bfr;
} /* Size */


/*
 * okaydone() is a callback for lists which want you to pick a single
 *            item; it simply returns -1, to accept the input and exit
 *	      the form.
 */
int
okaydone(void *obj, void* display)
{
    return -1;
} /* okaydone */


/*
 * copykernel: copies the kernel off the install media onto the disk.
 */
void
copykernel()
{
    copyfile(MY_KERNEL, TARGET MY_KERNEL, 0);
    copyfile(MY_MAP, TARGET MY_MAP, 0);
} /* copykernel */


/*
 * checkDevList() tells us whether a candidate device is already in
 * the device list.
 *
 * Returns:
 *	0	-- everything is fine.
 *	1	-- device is already in the list.
 *	2	-- mountpoint is already in the list.
 */
int
checkDevList(char *dev, char *mount)
{
    extern struct par *mountpoints;
    extern int       nrmountpoints;
    int x;

    for (x=0; x < nrmountpoints; x++) {
	if (strcmp(dev, mountpoints[x].par_dev) == 0)
	    return 1;
	if (strcmp(mount, mountpoints[x].par_mount) == 0)
	    return 2;
    }
    return 0;
} /* checkDevList */


/*
 * addDevToList() adds a mountpoint to the list of mountpoints that
 * we need to write into /etc/fstab
 */
void
addDevToList(char *dev, char *mount, enum partype p_type, int formatme,
                                                          int bootme)
{
    extern struct par *mountpoints;
    extern int       nrmountpoints;
    int idx = nrmountpoints;

    if (nrmountpoints++ == 0)
	mountpoints = malloc(sizeof(struct par));
    else
	mountpoints = realloc(mountpoints, nrmountpoints * sizeof(struct par));
    if (mountpoints == 0)
	die("OUT OF MEMORY DURING CONFIGURATION");
    mountpoints[idx].par_dev    = strdup(dev);
    mountpoints[idx].par_mount  = strdup(mount);
    if (mountpoints[idx].par_dev == 0 || mountpoints[idx].par_mount == 0)
	die("OUT OF MEMORY DURING CONFIGURATION");
    mountpoints[idx].par_type   = p_type;
    mountpoints[idx].par_format = formatme;
    mountpoints[idx].par_boot   = bootme;
} /* addDevToList */


/*
 * sortDevList() sorts the device list, so that parents are mounted before
 * their children
 */
static int
compareDevList(struct par *a, struct par *b)
{
    return strcmp(a->par_mount, b->par_mount);
} /* compareDevList */


void
sortDevList()
{
    extern struct par *mountpoints;
    extern int       nrmountpoints;

    qsort(mountpoints, nrmountpoints, sizeof mountpoints[0], compareDevList);
} /* sortDevList */


/*
 * makemountdir() makes the directories needed to set up a mountpoint
 */
int
makemountdir(char *dir)
{
    char *tmpdir, *start, *end, *copy;

    fprintf(stderr, "makemountdir(%s)\n", dir);
    if (strcmp(dir, "/") == 0)
	return 1;

    tmpdir = malloc(strlen(dir) + strlen(TARGET) + 10);
    
    strcpy(tmpdir, TARGET);
    copy = tmpdir + strlen(tmpdir);

    start = 1+dir;
    while (*start) {
	*copy++ = '/';
	for (end=start; *end && *end != '/';)
	    *copy++ = *end++;
	*copy = 0;
	start = *end ? (1+end) : end;

	if (mkdir (tmpdir, 0755) != 0)
	    perror(tmpdir);
	else
	    fprintf(stderr, "+%s\n", tmpdir);
    }
    free(tmpdir);
    return 1;
} /* makemountdir */


/*
 * addToMenu() adds something to a dialog menu list.
 */
void
addToMenu(char *item, char *desc, char ***menu, int *nrmenu)
{
#define MENU	(*menu)
#define NRMENU	(*nrmenu)
    MENU = realloc(MENU, (1 + NRMENU) * 2 * (sizeof *MENU));
    if (MENU == 0)
	NRMENU = 0;
    else {
	MENU[  (2*NRMENU)] = strdup(item);
	MENU[1+(2*NRMENU)] = strdup(desc);
	NRMENU++;
    }
#undef MENU
#undef NRMENU
} /* addToMenu */


/*
 * deleteMenu() deletes a menu
 */
void
deleteMenu(char **menu, int nrmenu)
{
    int x;

    if (nrmenu > 0) {
	for (x=0; x<nrmenu; x++) {
	    free(menu[x+x]);
	    free(menu[x+x+1]);
	}
	free(menu);
    }
} /* deleteMenu */


/*
 * start_a_shell() starts up a shell
 */
pid_t
start_a_shell(char *tty)
{
    pid_t child;


    child = fork();

    if (child == 0) { /* child */
	int fd;

	close(0);
	close(1);
	close(2);
	setsid();
	fd = open(tty, O_RDWR);
	dup2(fd, 0);
	dup2(fd, 1);
	dup2(fd, 2);
	/* set the path for the shell to include the tooldir, plus
	 * directories on the installed disk
	 */
	putenv("PATH=" SOURCE TOOL_DIR ":"
		     "/bin:/sbin:/usr/bin:/usr/sbin:"
		     TARGET "/bin:"
		     TARGET "/sbin:"
		     TARGET "/usr/bin:"
		     TARGET "/usr/sbin:"
		     TARGET "/usr/X11R6/bin:"
		     "." );

	strcpy(av0, "shell                       ");
	putenv("PS1=MASTODON($PWD)# ");
	execl(PATH_SH, "-sh", 0);
	perror("sh");
	exit(0);
    }
    else if (child == -1) {
	fprintf(stderr, "cannot start " PATH_SH " on %s: %s\n",
			 tty, strerror(errno));
	return 0;
    }
    else {
	fprintf(stderr, "started " PATH_SH " on %s: pid %d\n", tty, child);
	return child;
    }
} /* start_a_shell */


/*
 * start_update() starts up update, to beat down the rampaging buffers.
 */
_syscall2(int, bdflush, int, func, long, data);

pid_t
start_update(char *tty)
{
    pid_t pid;

    if ((pid=fork()) == 0)
	setsid();
    else if (pid == -1) {
	fprintf(stderr, "couldn't start update process!\n");
	return 0;
    }
    else {
	fprintf(stderr, "started update: pid %d\n", pid);
	return pid;
    }


    if ((pid = fork()) == 0) {
	strcpy(av0, "bdflush                ");
	bdflush(0, 0);
	while (1) pause();
	exit(0);
    }
    strcpy(av0, "update                ");
    while (1) {
	if (bdflush(1, 0) != 0)
	    sync();
	sleep(5);
    }
    exit(0);		/* be paranoid */
} /* start_update */


/*
 * mini_init() starts a bunch of processes and keeps them running.
 */
pid_t mini_init_pid;	/* process id of the mini-init process */

pid_t
mini_init()
{
    static struct {
	pid_t pid;
	pid_t (*start)(char *);
	char *tty;
    } processes [] = {
	{ 0, start_update, 0 },
	{ 0, syslogger, "/dev/tty7" },
	{ 0, start_a_shell, "/dev/tty2" },
	{ 0, start_a_shell, "/dev/tty3" },
    };
#define NR_PROCESSES (sizeof processes / sizeof processes[0])

    pid_t child;
    int status;
    int i;

    if (getpid() != 1)	/* don't start the shell unless we're init */
	return 0;

    child = fork();

    if (child < 0) {
	Error("cannot start mini_init: %s", strerror(errno));
	return 0;
    }
    else if (child > 0) {
	fprintf(stderr, "started mini_init: pid %d\n", child);
	return child;
    }

    /* now we're the mini-init */
    setsid();	/* in our own process group */
    strcpy(av0, "mini-init               ");

    for (i=0; i < NR_PROCESSES; i++)	/* start everything up */
	processes[i].pid = (processes[i].start)(processes[i].tty);

    while (1) {				/* then just hang around a while */
	child = wait(&status);

	if (child != -1 && child != 0)
	    for (i = 0; i < NR_PROCESSES; i++)
		if (child == processes[i].pid)
		    processes[i].pid = (processes[i].start)(processes[i].tty);
    }
    exit(0);	/* be paranoid */
} /* mini_init */


/*
 * start_gpm() starts up gpm for mouse handling.   This requires a
 *             tweaked gpm;  '-Z' means to NOT run in daemon mode
 *                           (so we can kill gpm ourself even after
 *                            we've chrooted.)
 */
static pid_t gpmpid = 0;

void
start_gpm()
{
    extern char *gpmnames[];
    int x;

    static char *arglist[7] = { "gpm", "-t", 0, "-m", 0, "-Z", 0 };
#define MOUSETYPEARG	2
#define MOUSEDEVARG	4

    if (mousetype != NO_MOUSE && mousedev != 0) {
	arglist[MOUSETYPEARG] = gpmnames[mousetype];
	arglist[MOUSEDEVARG] = mousedev;

	fprintf(stderr, PATH_GPM);
	for (x=0; arglist[x]; x++)
	    fprintf(stderr, " %s", arglist[x]);
	
	if ((gpmpid=fork()) == 0) {
	    execv(PATH_GPM, arglist);
	    perror("gpm");
	    exit(1);
	}
	else if (gpmpid < 0)
	    Error("Could not start mouse driver");
	else
	    fprintf(stderr, " = %d", gpmpid);
	fputc('\n', stderr);
    }
    else
	gpmpid = 0;
} /* start_gpm */


/*
 * stop_gpm() stops gpm
 */
void
stop_gpm()
{
    int rc;

    if (gpmpid > 0) {
	fprintf(stderr, "killing gpm = %d\n", gpmpid);
	kill(gpmpid, SIGHUP);
	kill(gpmpid, SIGKILL);
	kill(gpmpid, SIGQUIT);
	
	while (waitpid(gpmpid, &rc, 0) == -1 && errno == EINTR)
	    ;	
	gpmpid = 0;
    }
} /* stop_gpm */


/*
 * mouse_reset() stops dialog, starts gpm, then restarts dialog
 */
void
mouse_reset()
{
    if (!mouse_was_reset)
	return;

    mouse_was_reset = 0;

#if CURSES_CAN_REINIT_MOUSE
    /* this only if curses can reinitialize the mouse */
    end_dialog();		/* bring dialog down */
    stop_gpm();			/* stop GPM */
    start_gpm();		/* restart it with current mouse settings */
    init_dialog();		/* restart dialog */
#else
    stop_gpm();			/* stop GPM */
    start_gpm();		/* restart GPM with new parameters */
    usleep(500000);
    Gpm_Close();		/* Tell GPM not to use cached connection
				 * information.
				 */
    usleep(500000);
    _nc_mouse_init(stdscr);	/* HORRIBLE KLUDGE: use an **UNDOCUMENTED**
				 * ncurses internal function to reconnect
				 * to gpm.  This should be fixed (or the
				 * function should be published)
				 */
#endif
} /* start_mouse */
