/*
 * mountpar: bring up a screen asking if you want to mount specific disk
 * partitions.
 *
 * ALL HOPE ABANDON, YE WHO ENTER HERE  -- dante
 *
 * Copyright (c) 1997-1999 David L Parsons 
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by David L Parsons (orc@pell.chi.il.us).  My name may not be used
 * to endorse or promote products derived from this software without
 * specific prior written permission.  THIS SOFTWARE IS PROVIDED
 * ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE.
 */

static const char rcsid[] = "$Id: s.mountpar.c 1.10 99/11/24 01:00:01-00:00 orc $";

/*
 * mountpar asks where to mount partitions and whether we want to
 * format them.
 */
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <sys/mount.h>
#include <linux/hdreg.h>
#include <linux/fs.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <mntent.h>
#include <linux/cdrom.h>

#include <string.h>
#include <dialog.h>
#include <ndialog.h>

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

static int devidx;		/* pointer at current menu item in parts[] */
static char mountpoint[80];	/* the latest mountpoint entered by the user */
static char format_flag;	/* should we format this partition */
static char **parts = 0;	/* the menu of partitions */
static int  nrparts = 0;
static int  noroot = 0;		/* true if we automatically set up drive C: */

static void *format_p;		/* pointer to the [ ]format partition? item
				 * on the AddDisk menu
				 */

static struct {
    char *undo;			/* old label, for undoing commitments */
    char format;		/* should we format this partition? */
    char bootable;		/* is this partition BIOS-bootable? */
    enum partype fstype;	/* the internal fstype */
    unsigned long blocks;	/* number of 1k blocks in the partition */
} *parinfo;			/* information on each partition */

static int chk_mountpoint(void*, void*);
static int chk_dos(void*, void*);
static int finalcheck(void*, void*);

/* 
 * newAddMenu() generates the "Add a partition" menu
 */
void *
newAddMenu()
{
    void *tmp;

    tmp = newString(0,0,30,sizeof mountpoint, mountpoint, 0, "Mountpoint|",chk_mountpoint,"mount.html#linux");

    format_p = newCheck(0,4, 0, "|Format this partition", &format_flag, 0, "mount.html#linux");
    tmp = ObjChain(tmp, format_p);

    tmp = ObjChain(tmp,
		newOKButton(1, "OK", finalcheck, "mount.html#linux"));
    tmp = ObjChain(tmp,
		newCancelButton(2, "CANCEL", 0, "mount.html#linux"));
    return tmp;
} /* newAddMenu */

#define deleteAddMenu(x)	deleteObjChain(x)


/*
 * newDosAddMenu() generates the menu for adding a dos partition
 */
void *
newDosAddMenu()
{
    void * tmp;

    tmp = newString(0,0,30, sizeof mountpoint, mountpoint,
				  0, "Mountpoint|", chk_dos, "mount.html#dos");
    tmp = ObjChain(tmp, newOKButton(1, "OK", finalcheck, "mount.html#dos"));
    tmp = ObjChain(tmp, newCancelButton(2, "CANCEL", 0, "mount.html#dos"));

    return tmp;
} /* newDosAddMenu */

#define deleteDosAddMenu(x)	deleteObjChain(x)


/*
 * checkalloc() checks to see if a possible mountpoint or device has
 * already been allocated.
 * Returns:
 *	0 if okay
 *	1 if device already allocated (can't happen?)
 *	2 if mountpoint already allocated
 */
static int
checkalloc(char *dev, char *mount)
{
    int x;

    for (x=0; x<nrparts; x++)
	if (parinfo[x].undo) {
	    if (strcmp(dev, parts[x+x]) == 0)
		return 1;
	    else if (strcmp(mount, parts[x+x+1]) == 0)
		return 2;
	}
    return checkDevList(dev,mount);
} /* checkalloc */


/*
 * chk_mountpoint() makes certain that a mountpoint is okay
 */
static int
chk_mountpoint(void* obj, void* display)
{
    int rc;
    char *mountpoint = (char*)objDataPtr(obj);

    /* when filling things in, let the user not set a mountpoint.
     * we only force the mountpoint to be set when the user attempts
     * to get out of the form.
     */
    if (mountpoint[0] == 0)
	return 1;

    /* check and see if the mountpoint is already assigned.
     */
    switch (rc = checkalloc(parts[devidx+devidx], mountpoint)) {
    case 1:	Error("          CAN'T HAPPEN?\n"
		      "device %s is already allocated?",
			parts[devidx+devidx]);
		return 0;
    case 2:
		Error("another partition is already mounted on %s",
				mountpoint);
		return 0;
    }

    /* Do special case handling of / and /boot; if / is in a forbidden
     * partition (last cyl > 1023) and /boot has not been assigned, get
     * confirmation that this is what the user really wants to do.  If
     * /boot is in a forbidden partition, simply refuse to accept the
     * input.
     */
    if (strcmp(mountpoint, "/") == 0) {
	if (!parinfo[devidx].bootable && checkalloc("", "/boot") != 2) {
	    if (dialog_yesno("WARNING!",
			      "The last cylinder on this partition is >1023\n"
			      "You may be unable to boot Linux unless you\n"
			      "assign a partition to /boot.\n"
			      "\n"
			      "Do you still want to use this partition for /?",
			      -1, -1))
		return 0;
	}
	else if (parinfo[devidx].blocks < 200) {
	    Error("This partition is too small to install Linux onto; please\n"
		  "choose a partition that's at least %s",
						      Size(200*(ONE_MB/ONE_K)));
	    return 0;
	}
	/* ALWAYS format / for now (if we do upgraders, this won't be
	 * the case.)  We won't be needing to ask if the formatting
	 * should be done here.
	 */
	format_flag = 1;
	setReadonly(format_p);
	touchObj(format_p);
	return 1;
    }

    /* if we're not assigning something to root, let the user decide
     * whether to format the partition.
     */
    format_flag = 0;
    setWritable(format_p);
    touchObj(format_p);

    /* And, as mentioned above, protect /boot from unauthorised
     * locations.
     */
    if (strcmp(mountpoint, "/boot") == 0) {
	if (!parinfo[devidx].bootable) {
	    Error("The last cylinder on this partition is >1023\n"
		  "If you use this partition for /boot, you will not be\n"
		  "able to boot Linux");
	    mountpoint[0] = 0;
	    return 0;
	}
	else if (parinfo[devidx].blocks < 10) {
	    Error("You need to use at least a %s partition for /boot",
						Size(10*(ONE_MB/ONE_K)));
	    mountpoint[0] = 0;
	    return 0;
	}
    }
    return 1;
} /* chk_mountpoint */


/*
 * chk_dos() makes certain that a DOS mountpoint is okay
 */
static int
chk_dos(void* obj, void* display)
{
    char *mountpoint = objDataPtr(obj);

    static struct {
	char *root;
	int size;
    } forbidden[] = { 
	{"/boot", 5 },
	{"/usr",  4 },
	{"/var",  4 },
	{"/etc",  4 },
	{"/lib",  4 },
    };
#define NRFORBIDDEN 	(sizeof forbidden / sizeof forbidden[0])

    int rc, x;

    /* when filling things in, let the user not set a mountpoint.
     * we only force the mountpoint to be set when the user attempts
     * to get out of the form.
     */
    if (mountpoint[0] == 0)
	return 1;

    /* check and see if the mountpoint is already assigned.
     */
    switch (rc = checkalloc(parts[devidx+devidx], mountpoint)) {
    case 1:	Error("          CAN'T HAPPEN?\n"
		      "device %s is already allocated?",
			parts[devidx+devidx]);
		return 0;
    case 2:
		Error("another partition is already mounted on %s",
				mountpoint);
		return 0;
    }

    /* DOS filesystems are not allowed to be on / or under /usr, /lib,
     * /etc, /var, or /boot.  Otherwise people can go wild.
     */
    if (strcmp(mountpoint, "/") == 0) {
	Error("You cannot use a dos partition\n"
	      "as the root partition!");
	return 0;
    }
    for (x=0; x<NRFORBIDDEN; x++)
	if (strncmp(mountpoint, forbidden[x].root, forbidden[x].size) == 0) {
	    if (mountpoint[forbidden[x].size] == '/') {
		Error("You cannot mount a dos partition\n"
		      "under the %s directory", forbidden[x].root);
		return 0;
	    }
	    else if (mountpoint[forbidden[x].size] == 0) {
		Error("You cannot mount a dos partition\n"
		      "as the %s directory", forbidden[x].root);
		return 0;
	    }
	    else
		return 1;
	}

    return 1;
} /* chk_dos */


/*
 * finalcheck() makes certain that we entered a mountpoint before leaving
 */
static int
finalcheck(void* obj, void* display)
{
    if (mountpoint[0] == 0) {
	Error("You must specify a mountpoint for this partition");
	return 0;
    }
    return 1;
} /* finalcheck */


/*
 * mountpar spits up the partition mounting menu and lets people play around
 * with it.
 *
 * THIS IS DONE IN A REALLY GROSS FASHION:  What we do is use a menu dialog
 * to hold the list of partitions, and we toggle the description of the
 * menu item from the FS type (for an unused partition) to the mountpoint.
 * To be able to back a mountpoint out, we have a shadow menu that holds
 * the description of a partition, complete with a `undo' slot that's the
 * original description of an allocated partition.  We populate that when
 * we allocate a partition, and we back it out when we deallocate that
 * partition.
 *
 * variables we use here, for future reference:
 *
 *	parts	-- the menu for dialog_menu; <partition> <description> duples
 *	nrparts	-- the number of partitions in the menu
 *	partab	-- the raw partition list, from fslist (and originally from
 *		   fdisk -l)
 *	parinfo	-- the partition list, with inappropriate partitions weeded
 *		   out.
 *	ch,sc,result
 *		-- all dialog_menu() flags.
 *	x	-- the all-purpose index.
 *	st	-- completion status (1 for okay, 0 for ESCAPE; does this
 *		   matter?)
 *	rc	-- the all-purpose return code.
 *	msg	-- buffer for hand-generated prompts.
 */
int
mountpar(int rootdisk)
{
    char *desc = (rootdisk != EOF)
	    ?   "If you wish to mount any other partitions on your Linux\n"
		"system, select them here."
	    :   "Please choose the partition that you'd like to install\n"
		"Linux on, as well as any other partitions you wish to\n"
		"have accessable from Linux.";

    char msg[200];
    struct raw_par *partab;
    int nrpar;
    int x, y, size;
    int ch=0, sc=0;
    char result[80];
    int rc;

    parts   = malloc(1);
    nrparts = 0;

    noroot = (rootdisk != EOF);

    get_par_list(disklist, &nrpar, &partab);

    if (nrpar < 1)
	return 0;

    fprintf(stderr, "mountpar: available partitions:\n");

    /* first of all, we build a menu of filesystems we care about.
     * We only care about swap, Linux native, and various permutations
     * of DOS partitions.  People who use Minix FSes and the like are
     * simply out of luck.
     */

    /* We'll do two passes across the table; the first pass finds out
     * how many partitions we've got, so we can populate the shadow
     * arrays
     */
    for (size=x=0; x < nrpar; x++) {
	switch (partab[x].par_fstype) {
	case 0x82:
	case 0x83:
	case 0x01:
	case 0x04:
	case 0x06:
		size++;
		break;
	}
    }
    parinfo  = malloc(size * sizeof *parinfo);
    memset(parinfo, 0, size * sizeof *parinfo);

    for (y=x=0; x < nrpar; x++) {
	switch (partab[x].par_fstype) {
	case 0x82:	/* SWAP */
		snprintf(msg, sizeof msg, "swap (%s)",
					      Size(partab[x].par_blocks));
		fprintf(stderr, "%s: %s\n", partab[x].par_dev, msg);
		addToMenu(partab[x].par_dev, msg, &parts, &nrparts);
		parinfo[y].fstype = I_SWAP;
		parinfo[y].blocks = partab[x].par_blocks;
		y++;
		break;
	case 0x83:	/* Linux native */
		snprintf(msg, sizeof msg, "Linux (%s)",
					      Size(partab[x].par_blocks));
		fprintf(stderr, "%s: %s\n", partab[x].par_dev, msg);
		addToMenu(partab[x].par_dev, msg, &parts, &nrparts);
		parinfo[y].fstype = I_LINUX;
		parinfo[y].bootable = (partab[x].par_lastcyl < 1024);
		parinfo[y].blocks = partab[x].par_blocks;
		y++;
		break;
	case 0x01:	/* DOS, 12bit fat */
	case 0x04:	/* small DOS */
	case 0x06:	/* large DOS */
		snprintf(msg, sizeof msg, "DOS (%s)",
				    Size(partab[x].par_blocks));
		fprintf(stderr, "%s: %s\n", partab[x].par_dev, msg);
		if (partab[x].par_bootflag)
		    strcat(msg, " bootable");
		addToMenu(partab[x].par_dev, msg, &parts, &nrparts);
		parinfo[y].fstype = I_DOS;
		parinfo[y].bootable = (partab[x].par_lastcyl < 1024);
		parinfo[y].blocks = partab[x].par_blocks;
		y++;
		break;
	}
    }

    /* keep looping through the partition map until the user decides it's
     * time to move on.
     */
    while (1) {
	rc = ndialog_list(INSTALLER, desc, -1, -1,
                              (nrparts>8)?8:nrparts, nrparts, parts,
			      result, &ch, &sc, ND_A_MENU,
			    "1:FINISH", 0/*"2:BACK"*/);

	if (rc != 0) {
	    deleteMenu(parts, nrparts);
	    free(parinfo);
	    delete_par_list(nrpar, partab);
	    return -1;
	}
	strtok(result, "\n");
	if (rc == 0 && result[0] == 0) {
	    if (rootdisk == EOF && checkalloc("", "/") != 2) {
		Error("You did not mount a partition as /");
		continue;
	    }
	    break;
	}
	for (x=0; x<nrparts; x++) {
	    if (strcasecmp(parts[x+x], result) == 0) {
		if (parinfo[x].undo) {
		    /* already mounted; unmount? */
		    if (parinfo[x].fstype == I_SWAP)
			snprintf(msg, sizeof msg, "don't use swapspace on %s",
					parts[x+x]);
		    else
			snprintf(msg, sizeof msg, "don't mount %s on %s",
				parts[x+x], parts[x+x+1]);
		    if (dialog_yesno(INSTALLER, msg, -1, -1) == 0) {
			free(parts[x+x+1]);
			parts[x+x+1] = parinfo[x].undo;
			parinfo[x].undo = 0;
			parinfo[x].format = 0;
		    }
		}
		else {
		    /* not yet mounted; shall we mount? */
		    switch (parinfo[x].fstype) {
		    case I_SWAP:
			    snprintf(msg, sizeof msg, "use swapspace on %s", parts[x+x]);
			    if (dialog_yesno(INSTALLER, msg, -1, -1) == 0) {
				parinfo[x].undo = parts[x+x+1];
				parts[x+x+1] = strdup("used as swapspace");
			    }
			    break;
		    case I_LINUX:
		    case I_DOS:
			    /* ask about mounting a filesystem */
			    devidx = x;
			    format_flag = 0;
			    memset(mountpoint, 0, sizeof mountpoint);
			    if (parinfo[x].fstype == I_DOS) {
				void *chain = newDosAddMenu();

				rc = MENU(chain, -1, -1, INSTALLER, 0, 0);
				deleteDosAddMenu(chain);
			    }
			    else {
				void *chain = newAddMenu();
				setWritable(format_p);
				rc = MENU(chain, -1, -1, INSTALLER, 0, 0);
				deleteAddMenu(chain);
			    }
			    if (rc == 0) {
				parinfo[x].undo = parts[x+x+1];
				parts[x+x+1] = strdup(mountpoint);
				parinfo[x].format = format_flag;
			    }
			    break;
		    default:Error("bogus partition type %d", parinfo[x].fstype);break;
		    }
		}
	    }
	}
    } /* dialog_menu loop */

    for (x=0; x<nrparts; x++)
	if (parinfo[x].undo) {
	    char *mountpt = (parinfo[x].fstype == I_SWAP)
				    ? "swap"
				    : parts[x+x+1];
	    if (strcmp(mountpt, "/") == 0)
		parinfo[x].fstype = I_ROOT;
	    else if (strcmp(mountpt, "/boot") == 0)
		parinfo[x].fstype = I_BOOT;
	    addDevToList(parts[x+x], mountpt,
			    parinfo[x].fstype, parinfo[x].format, 0);
	    free(parinfo[x].undo);
	}

    deleteMenu(parts, nrparts);
    free(parinfo);
    delete_par_list(nrpar, partab);

    return 0;
} /* mountpar */
