/* Newt based fdisk program
 *
 * Michael Fulbright (msf@redhat.com)
 *
 * Copyright 1998 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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <malloc.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>

#include <newt.h>

#include "kickstart.h"
#include "fs.h"
#include "fsedit.h"
#include "hd.h"
#include "install.h"
#include "intl.h"
#include "libfdisk/libfdisk.h"
#include "devices.h"
#include "windows.h"

#include <popt.h>

#define VERSION_STR "1.00"

extern int testing;

/* static char version[] = "0.01"; */

struct attemptedPartition normalPartitioning[] = {
#if defined(__i386__)
	{ "/boot",	16,	LINUX_NATIVE_PARTITION,	0, -1 },
#elif defined(__alpha__)
	{ "/dos",	2,	DOS_PRIMARY_lt32MEG_PARTITION,	0, 1 },
#endif
	{ "/",		400,	LINUX_NATIVE_PARTITION,	1, -1 },
	{ "Swap-auto",	32,	LINUX_SWAP_PARTITION,	0, -1 },
	{ NULL, 0, 0, 0 }
};

/* need to move somewhere else eventually! */
/* mostly gleamed from fdisk.[ch]          */
#define SUNPARTTYPE	0x100
#define NONSUNPARTTYPE	0x200
struct parttypes {
      unsigned int index;
      char *name;
};

/* this isn't const, as we have a loop which does i18n conversion on these */
static struct parttypes allparttypes[] = {
    {0, "Empty"},
    {SUNPARTTYPE|1, "Boot"},
    {SUNPARTTYPE|2, "SunOS root"},
    {SUNPARTTYPE|3, "SunOS swap"},
    {SUNPARTTYPE|4, "SunOS usr"},
    {SUNPARTTYPE|5, "Whole disk"},
    {SUNPARTTYPE|6, "SunOS stand"},
    {SUNPARTTYPE|7, "SunOS var"},
    {SUNPARTTYPE|8, "SunOS home"},
    {NONSUNPARTTYPE|1, "DOS 12-bit FAT"},
    {NONSUNPARTTYPE|2, "XENIX root"},
    {NONSUNPARTTYPE|3, "XENIX usr"},
    {NONSUNPARTTYPE|4, "DOS 16-bit <32M"},
    {NONSUNPARTTYPE|5, "Extended"},
    {NONSUNPARTTYPE|6, "DOS 16-bit >=32M"},
    {NONSUNPARTTYPE|7, "OS/2 HPFS"},               /* or QNX? */
    {NONSUNPARTTYPE|8, "AIX"},
    {9, "AIX bootable"},
    {10, "OS/2 Boot Manager"},
    {0xb, "Win95 FAT32"},
    {0xc, "Win95 FAT32"},
    {0xe, "Win95 FAT32"},
    {0x40, "Venix 80286"},
    {0x51, "Novell?"},
    {0x52, "Microport"},            /* or CPM? */
    {0x63, "GNU HURD"},             /* or System V/386? */
    {0x64, "Novell Netware 286"},
    {0x65, "Novell Netware 386"},
    {0x75, "PC/IX"},
    {0x80, "Old MINIX"},            /* Minix 1.4a and earlier */

    {0x81, "Linux/MINIX"}, /* Minix 1.4b and later */
    {0x82, "Linux swap"},
    {0x83, "Linux native"},

    {0x93, "Amoeba"},
    {0x94, "Amoeba BBT"},           /* (bad block table) */
    {0xa5, "BSD/386"},
    {0xb7, "BSDI fs"},
    {0xb8, "BSDI swap"},
    {0xc7, "Syrinx"},
    {0xdb, "CP/M"},                 /* or Concurrent DOS? */
    {0xe1, "DOS access"},
    {0xe3, "DOS R/O"},
    {0xf2, "DOS secondary"},
    {0xff, "BBT"}                   /* (bad track table) */
};

int nparttypes = sizeof (allparttypes) / sizeof (struct parttypes);

/* hardcoded, maybe someday we'll get these from tty ? */
int screen_width=80;
int screen_height=25;

#define MAX_HARDDRIVES  16

#define SECPERMEG 2048

/* clean up that string */
static void TrimWhitespace( char *s ) {
    char *f, *l, *p, *q;

    if (!(*s))
        return;
    
    for (f=s; *f && isspace(*f); f++) ;

    if (!*f) {
        *s = '\0';
        return;
    }
    
    for (l=f+strlen(f)-1; isspace(*l) ; l--)
    *l = '\0';
        
    q = s, p = f;
    while (*p)
        *q++ = *p++;
    
    *q = '\0';
}

/* fill in the                                          */
/* position in the status line, up to length characters */
/* if cen=1, center it                                    */
static void BuildTableField( char *line, char *val,
			     int pos, int length, int cen ) {
    char *p, *q;
    int i;
    int c;

    /* lets center field value in the field */
    if (cen) {
	c = strlen(val);
	c = (length-c)/2;
	if (c < 0)
	    c = 0;
    } else
	c=0;

    /* first setup device name */
    for (p=val, q=line+pos+c, i=c; *p && i<length; p++, q++, i++)
	*q = *p;
}

#if 0
/* some fdisk functions which don't have homes yet */
static int fdiskPartitionIsBootable( HardDrive *hd, Partition *p ) {
    unsigned int i, bootable;

    /* see if drive #1 or #2 is a possible drive for partition */
    bootable = fdiskThisDriveSetIsActive( &p->drive, 1 ) ||
	fdiskThisDriveSetIsActive( &p->drive, 2 );

    /* if so, see if rest are EXCLUDED */
    if (bootable) {
	for (i=3; i<MAX_DRIVESET_NUM; i++) {
	    if (fdiskThisDriveSetIsActive( &p->drive, i)) {
		bootable = 0;
		break;
	    }
	}
    }
    
    if (bootable) {
	if (p->endcyl.active && p->endcyl.max < 1024) {
	    bootable = 1;
	} else if (p->immutable) {
	    unsigned int end, c, h, s;
	    end = p->start.current + p->size.current - 1;
	    fdiskSectorToCHS( hd, end, &c, &h, &s );
	    if (c < 1024)
		bootable = 1;
	    else
		bootable = 0;
	} else {
	    bootable = 0;
	}
    }

    return bootable;
}
#endif

/* check a mount point to make sure its valid */
/* returns non-zero if bad mount point        */
static int badMountPoint(unsigned int type, char * item) {
    char * chptr = item;

    if (!strncmp(item, "/dev", 4) ||
        !strncmp(item, "/bin", 4) ||
        !strncmp(item, "/sbin", 5) ||
        !strncmp(item, "/etc", 4) ||
#ifdef __sparc__
        !strncmp(item, "/boot", 5) ||
#endif
        !strncmp(item, "/lib", 4)) {
        newtWinMessage(_("Bad Mount Point"), _("Ok"),
                    _("The %s directory must be on the root filesystem."),
		    item);
	return 1;
    }

    if (*chptr != '/') {
        newtWinMessage(_("Bad Mount Point"), _("Ok"),
                    _("The mount point %s is illegal.\n\n"
		    "Mount points must begin with a leading /."), item);
        return 1;
    } 

    if (*(chptr + 1) && *(chptr + strlen(chptr) - 1) == '/') {
        newtWinMessage(_("Bad Mount Point"), _("Ok"),
                    _("The mount point %s is illegal.\n\n"
                      "Mount points may not end with a /."), item);
        return 1;
    } 

    while (*chptr && isprint(*chptr)) chptr++;

    if (*chptr) {
        newtWinMessage(_("Bad Mount Point"), _("Ok"),
                    _("The mount point %s is illegal.\n\n"
                    "Mount points may only printable characters."), item);
        return 1;
    }

    if (type != LINUX_NATIVE_PARTITION && (
         !strncmp(item, "/var", 4) ||
         !strncmp(item, "/tmp", 4) ||
         !strncmp(item, "/boot", 4) ||
         !strcmp(item, "/") ||
         !strncmp(item, "/root", 4))) {
        newtWinMessage(_("Bad Mount Point"), _("Ok"),
                    _("The mount point %s is illegal.\n\n"
                      "System partitions must be on Linux Native "
                      "partitions."), item);
        return 1;
    }

    if (type != LINUX_NATIVE_PARTITION &&
	type != NFS_REMOTE_PARTITION &&
        !strncmp(item, "/usr", 4)) {
        newtWinMessage(_("Bad Mount Point"), _("Ok"),
                    _("The mount point %s is illegal.\n\n"
                      "/usr must be on a Linux Native partition "
                      "or an NFS volume."), item);
        return 1;
    }

    return 0;
}

/*                              */
/* NEWT/screen related routines */
/*                              */

/* handles standard fdisk type errors */
/* returns non-zero if user picked "yes" response, and zero if "no" */
static int ErrorDialog(char *title, char *errbody, char *errmsg,
		       char *yesmsg, char *nomsg) {

    int retcode;
    char *buf;


    /* I don't know if this is what msf intended, but it is a whole lot 
       easier. */
    buf = alloca(strlen(errbody)+strlen(errmsg) + 10);
    sprintf(buf, "%s: %s", errbody, errmsg);
    retcode = newtWinChoice(title, yesmsg, nomsg, buf);

    if (retcode == 2) return 0; else return 1;
}

static int HandleFdiskError( int status, char *errbody, char *y, char *n ) {
    char errmsg[250];
    char yesmsg[]="Yes";
    char nomsg[] ="No";
    
    if (status < 0) {
	if (errno < sys_nerr-1)
	    strncpy(errmsg,sys_errlist[errno],sizeof(errmsg));
	else
	    snprintf(errmsg,sizeof(errmsg), _("System error %d"), errno);
    } else {
	if (status < fdisk_nerr)
	    strcpy(errmsg, fdisk_errlist[status]);
	else
	    snprintf(errmsg,sizeof(errmsg), "libfdisk error %d",errno);
    }

    if (y != NULL && n != NULL)
	return ErrorDialog( _("Fdisk Error"), errbody, errmsg, y, n);
    else
	return ErrorDialog( _("Fdisk Error"), errbody,errmsg,yesmsg,nomsg);
}

/* give summary of why partitions weren't allocated */
static void showReasons( PartitionSpec *spec ) {
    newtComponent tbox, form, ok, lbox;
    int i;
    
    for (i=0; i<spec->num; i++)
	if (spec->entry[i].status == REQUEST_DENIED)
	    break;

    /* nothing going on here, keep moving along */
    if (i == spec->num)
	return;
    
    /* build list of why they all failed */
    newtCenteredWindow(60, 18, _("Unallocated Partitions"));
    form = newtForm(NULL,NULL,0);

    tbox = newtTextbox(5, 1, 50, 5, NEWT_FLAG_WRAP );
    newtTextboxSetText(tbox, _("There are currently unallocated partition(s) "
		       "present in the list of requested partitions. The "
		       "unallocated partition(s) are shown below, along with "
		       "the reason they were not allocated."));

    lbox = newtListbox(10, 6, 5, NEWT_FLAG_RETURNEXIT | NEWT_FLAG_SCROLL );
    for (i=0; i<spec->num; i++)
	if (spec->entry[i].status == REQUEST_DENIED) {
	    char tmpstr[80];
	    char *pname = spec->entry[i].name;
	    char *s, *t;
	    
	    memset(tmpstr, ' ', 80);
	    if (strncmp("Exist", pname, 5) && strncmp("Swap", pname, 4) &&
		strncmp("Dos", pname, 3))
		t = pname;
	    else
		t = NULL;
	    for (s=tmpstr;t && *t; t++,s++)
		*s = *t;
	    
	    t = GetReasonString(spec->entry[i].reason);
	    for (s=tmpstr+20;t && *t; t++,s++)
		*s = *t;
	    *s = '\0';
	    newtListboxAddEntry(lbox, tmpstr, NULL);
	}

    ok = newtButton(25, 13, _("Ok"));
    newtFormAddComponents(form, tbox, lbox, ok, NULL);
    newtFormSetCurrent(form, ok);

    newtRunForm(form);

    newtPopWindow();
    newtFormDestroy(form);
}

/* read in the requested drives                                 */
/* pass an array of names of block devices, returns 0 if ok     */
static int ReadDrives( char **drives, int numdrives,
		       HardDrive **hdarr, unsigned int *numhd,
		       int forcezero, int readOnly) {

    char errbody[250];
    int  i, done, status;
    
    /* loop over all specified block devices */
    *numhd = 0;
    for (i=0; i < numdrives; ) {
	status = fdiskOpenDevice(drives[i], *numhd+1, &hdarr[*numhd]);
	if (status != FDISK_SUCCESS) {

	  /* HORRIBLE HACK               XXX*/

#ifdef __sparc__
	  if (status == FDISK_ERR_CORRUPT) { /* bad Sun disklabel */
	    snprintf(errbody, sizeof(errbody),
		     _("A disk with a corrupt Sun disklabel has been "
		       "found while reading block device %s.  You must "
                       "use fdisk to create and write a new label to "
		       "this device."), drives[i]);
	    if (newtWinChoice(_("Corrupt Sun disklabel"),
			      _("Back"), _("Skip Drive"), errbody) == 1)
	      return INST_CANCEL;
	    else {
	      i++;
		continue;
	    }
	  }
#endif
	    snprintf(errbody, sizeof(errbody),
		     _("An error occurred reading the partition table for the "
		       "block device %s.  The error was:"), drives[i]);
	    if (HandleFdiskError( status, errbody, "Retry", "Skip Drive" ))
		continue;
	    else {
		i++;
		continue;
	    }
	} else {
	    done = 0;
	    while (!done) {
		status = fdiskReadPartitions( hdarr[*numhd] );
		if (status != FDISK_SUCCESS) {
		    int rc;
		    
		    if (status == FDISK_ERR_BADMAGIC) {
			if (forcezero) {
			    if (!testing)
				#ifdef sparc
				    fdiskInitSunLabel(hdarr[*numhd]);
				#else
				    fdiskZeroMBR(hdarr[*numhd]);
				#endif
			    fdiskCloseDevice(hdarr[*numhd]);
			    done = 1;
			} else {
			    if (kickstart) {
				newtWinMessage(_("Bad Partition Table"), 
				       _("Ok"),
				       _("The partition table on device %s is "
				         "corrupted.  To create new partitions "
				         "it must be initialized. You can "
				         "specify \"zerombr yes\" in the "
				         "kickstart file to have this done "
				         "automatically"), drives[i]+5);
				return INST_ERROR;
			    }
			    
			    rc = newtWinChoice(_("Bad Partition Table"),
					       _("Initialize"), _("Skip Drive"),
				       _("The partition table on device %s is "
				         "corrupted.  To create new partitions "
				         "it must be initialized,"
				         " causing the loss of ALL DATA on "
				         "this drive."), drives[i]+5);
			    
			    if (rc != 2) {
				if (!testing)
				    #ifdef sparc
					fdiskInitSunLabel(hdarr[*numhd]);
				    #else
					fdiskZeroMBR(hdarr[*numhd]);
				    #endif
				fdiskCloseDevice(hdarr[*numhd]);
				done = 1;
			    } else {
				i++;
				fdiskCloseDevice(hdarr[*numhd]);
				done = 1;
			    }
			}
		    } else {			
			snprintf(errbody, sizeof(errbody),
			 _("An error occurred reading the partition table "
			   "for the block device %s.  The error was:"),
				 drives[i]+5);
			if (HandleFdiskError(status,errbody,
					     _("Retry"), _("Skip Drive"))){
			    fdiskCloseDevice(hdarr[*numhd]);
			    done = 1;
			} else {
			    i++;
			    fdiskCloseDevice(hdarr[*numhd]);
			    done = 1;
			}
		    }
		/* THIS IS A HORRIBLE NASTY HACK */
		#ifdef __alpha__
		} else if (hdarr[i]->limits.maxPrimary > 4 && !readOnly) {
		    newtWinMessage(_("BSD Disklabel"), _("Ok"), _("A disk with "
				"a BSD disklabel has been found. The Red Hat "
				"installation only supports BSD Disklabels in "
				"read-only mode, so you must use a custom install "
				"and fdisk (instead of Disk Druid) for "
				"machines with BSD Disklabels."));
		    return INST_CANCEL;
		#endif
		} else {
		    *numhd += 1;
		    i++;
		    done = 1;
		}
	    }
	}
    }

    return FDISK_SUCCESS;
    
}

/* see if anything really changed */
int DisksChanged( HardDrive **oldhd, HardDrive **newhd, unsigned int numhd ) {

    int i, j;
    
    /* see if partition tables are identical */
    for (i=0; i<numhd; i++)
	for (j=0; j<MAX_PARTITIONS; j++) {
	    if (memcmp(&oldhd[i]->table[j],&newhd[i]->table[j],
		       sizeof(Partition)))
		return 1;
	    if (memcmp(&oldhd[i]->eptable[j],&newhd[i]->eptable[j],
		       sizeof(Partition)))
		return 1;
	}

    return 0;
}
		
		
/* edit an existing partition spec */
/* callback for type listbox */
struct typebox_cbstruct {
    newtComponent *entry;
    char          *val;
};

static char typebox_mp[100];
static int  inswapentry;

static void typebox_scroll(newtComponent box, struct typebox_cbstruct *s ) {
    int type;
    
    type = (long) newtListboxGetCurrent(box);
    if (type == LINUX_SWAP_PARTITION && !inswapentry) {
	strncpy(typebox_mp, s->val, 100);
	newtEntrySetFlags(*s->entry, _("Swap Partition"), 0);
	inswapentry = 1;
	newtEntrySetFlags(*s->entry, NEWT_FLAG_DISABLED, NEWT_FLAGS_SET);
    } else if (inswapentry) {
	newtEntrySetFlags(*s->entry, NEWT_FLAG_DISABLED, NEWT_FLAGS_RESET);
	if (typebox_mp[0] == -1) /* just clear string if it isnt initialized */
	    typebox_mp[0] = '\0';
	newtEntrySet(*s->entry, typebox_mp, 1);
	inswapentry = 0;
    }
}

struct driveentry_struct {
    newtComponent cb;
    char          state;
};

struct entrybox_cbstruct {
    newtComponent *form;
    char          *val;
    unsigned char *val2;
    DriveSet      curds, origds;
    int           numhd;
    HardDrive     **hdarr;
    struct driveentry_struct *de;
    int           dobootable;
};

static void entrybox_cb(newtComponent box, struct entrybox_cbstruct *s) {
    unsigned char boot;
    int           j;
    
    if (s->dobootable) {
	boot = (!strcmp(s->val, "/") || !strcmp(s->val, "/boot")) ? '*' : ' ';
	if (boot == '*' && *(s->val2) == ' ')
	    memcpy(&s->origds, &s->curds, sizeof(DriveSet));
        *(s->val2) = boot;
	if (boot == '*') {
	    fdiskDeactivateAllDriveSet( &s->curds );
	    fdiskActivateDriveSet( &s->curds, 1 );
	    fdiskActivateDriveSet( &s->curds, 2 );
	} else {
	    memcpy(&s->curds, &s->origds, sizeof(DriveSet));
	}

	for (j=0; j<s->numhd; j++)
	    s->de[j].state=fdiskThisDriveSetIsActive(&s->curds,
						     s->hdarr[j]->num)?'*':' ';
	
	newtDrawForm(*s->form);
    }
}    

#define NEW_PARTSPEC "NewPartition"

static int EditPartitionSpec(HardDrive **hdarr, unsigned int numhd,
		      PartitionSpec      *spec, 
		      PartitionSpecEntry *entry) {
    int j, l;
    unsigned int hdidx, tmpuint;
    int          tmpint;
    char tmpstr[80];
    
    Partition *p;
    newtComponent form, mntptentry;
    newtComponent sizeentry, growentry, bootentry, typeentry;
    newtComponent sb, driveform;
    newtComponent ok, cancel, answer;

    struct typebox_cbstruct  cb1;
    struct entrybox_cbstruct cb2;
    struct driveentry_struct driveentry[MAX_HARDDRIVES];
    
    char *mntpt=NULL, *size=NULL, *eptr;
    char titlestr[80];
    unsigned char boot, grow;

    int row, col;
    int status=0;
    int done;
    int newpartition;
    int cval;
    int   numfstypes   = 4;
    char  fstypesnames[][20] = { "Linux Swap", "Linux Native",
	                         "DOS 16-bit <32M", "DOS 16-bit >=32M"};
    int   fstypes[] = {0x82, 0x83, 0x4, 0x6};

    p = (Partition *) alloca(sizeof(Partition));
    memcpy(p, &entry->partition, sizeof(Partition));

    newpartition = (strcmp(entry->name, NEW_PARTSPEC) == 0);

    if (p->immutable)
	cval = -2;
    else
	cval = ((numhd > 3) ? 4 : numhd);

    /* make title line a little more descriptive */
    if (newpartition) {
	strcpy(titlestr, "Edit New Partition");
    } else if (p->immutable) {
	for (hdidx=0; hdidx < numhd &&
		 hdarr[hdidx]->num != p->drive.current; hdidx++);
	if (hdidx != numhd) {
	    snprintf(titlestr, 80, "%s: /dev/%s%d", _("Edit Partition"),
		     hdarr[hdidx]->name+5, p->num.current);
	    if (entry->name && *entry->name && strncmp(entry->name, "Exist", 5))
		snprintf(titlestr+strlen(titlestr), 80-strlen(titlestr),
			 " (%s)", entry->name);
	} else {
	    strcpy(titlestr, _("Edit Partition"));
	}
    } else {
	if (entry->name && *entry->name)
	    snprintf(titlestr, 80, "%s: %s", _("Edit Partition"), entry->name);
	else
	    strcpy(titlestr, _("Edit Partition"));
    }	    
	
    newtCenteredWindow(70, 13+cval, titlestr );
    form = newtForm(NULL,NULL,0);

    /* mount point goes at top and is centered */
    row = 1;
    col = 3;
    newtFormAddComponent(form, newtLabel(col, row, "Mount Point:"));
    if (p->type.current != LINUX_SWAP_PARTITION) {
	if (!newpartition && strncmp("Exist", entry->name, 5) &&
	    strncmp("Dos", entry->name, 3)) {
	    mntptentry = newtEntry(22, row, entry->name, 30,
				   &mntpt, NEWT_FLAG_RETURNEXIT);
	} else {
	    mntptentry = newtEntry(22, row, "", 30,
				   &mntpt, NEWT_FLAG_RETURNEXIT);
	}
    } else {
	mntptentry = newtEntry(22, row, "Swap Partition", 30, &mntpt,
			       NEWT_FLAG_RETURNEXIT | NEWT_FLAG_DISABLED);
    }

    /* size, grow and boot flags on left under mount point */
    row = 3;
    newtFormAddComponent(form, newtLabel(col, row, "Size (Megs):"));
    if (p->immutable) {
	sizeentry = NULL;
	snprintf(tmpstr,sizeof(tmpstr),"%d", p->size.current/SECPERMEG);
	newtFormAddComponent(form, newtLabel(22, row, tmpstr));
    } else {
	snprintf(tmpstr,sizeof(tmpstr),"%d", p->size.min/SECPERMEG);
	sizeentry = newtEntry(22, row, tmpstr, 8,
			  &size, NEWT_FLAG_RETURNEXIT);
    }
    row++;
    
    if (!newpartition) {
	grow = p->size.min != p->size.max;
    } else {
	grow = 0;
    }
    grow = (grow) ? '*' : ' ';
    
    newtFormAddComponent(form, newtLabel(col, row, "Growable?:"));
    if (p->immutable) {
	growentry = NULL;
	newtFormAddComponent(form, newtLabel(22, row, "[ ]"));
    } else {
	growentry = newtCheckbox(22, row, "", grow, NULL, &grow);
    }
    row++;

    /* give status */
    if (!newpartition) {
	newtFormAddComponent(form, newtLabel(col, row, 
				_("Allocation Status:")));
	if (entry->status != REQUEST_DENIED)
	    newtFormAddComponent(form, newtLabel(22, row, _("Successful")));
	else
	    newtFormAddComponent(form, newtLabel(22, row, _("Failed")));
	row++;

	if (entry->status == REQUEST_DENIED) {
	    newtFormAddComponent(form, newtLabel(col, row, 
				 _("Failure Reason:")));
	    newtFormAddComponent(form,
			 newtLabel(22,row,GetReasonString(entry->reason)));
	}
	row++;
    }
    
    /* blow this bootable stuff for now, its confusing */
    bootentry = NULL;

    /* type goes on right side under the mount point */
    row = 3;
    newtFormAddComponent(form, newtLabel(43, row, "Type:"));
    if (p->immutable) {
	typeentry = NULL;

	l = p->type.current | NONSUNPARTTYPE;
	for (hdidx = 0; hdidx < numhd; hdidx++)
	    if (hdarr[hdidx]->num == p->drive.current &&
		hdarr[hdidx]->part_type == FDISK_PART_TYPE_SUN)
		    l = p->type.current | SUNPARTTYPE;

	for (j=0; j<nparttypes; j++) {
	    if (allparttypes[j].index == p->type.current)
		break;
	}

	if (j != nparttypes)
	    snprintf(tmpstr, sizeof(tmpstr), "%s", allparttypes[j].name);
	else
	    snprintf(tmpstr,sizeof(tmpstr),"%6s (0x%x)",
		     "Unknown", p->type.current);
	    
	newtFormAddComponent(form, newtLabel(48, row, tmpstr));
	row++;
    } else {
	typeentry = newtListbox( 48, row, 4, 
				 NEWT_FLAG_RETURNEXIT | NEWT_FLAG_SCROLL);
	for (j=0; j<numfstypes; j++) {
	    snprintf(tmpstr,sizeof(tmpstr),"%s", fstypesnames[j]);
	    newtListboxAddEntry(typeentry, tmpstr,
				(void *) (long)fstypes[j]);
	    if (fstypes[j] == p->type.current)
		newtListboxSetCurrent(typeentry, j);
	    else if (p->type.current == 0 &&
		     fstypes[j] == LINUX_NATIVE_PARTITION)
		newtListboxSetCurrent(typeentry, j);
	}
    }

    /* have to fix this later */
    /* allowable drives goes in center under rest */
    row = 8;
    driveform = NULL;
    if (!p->immutable) {
	newtFormAddComponent(form, newtLabel(col, row, "Allowable Drives:"));

	sb = newtVerticalScrollbar(40, row, 4, 9, 10);
	driveform = newtForm(sb, NULL, 0);
        newtFormSetBackground(driveform, NEWT_COLORSET_CHECKBOX);

	for (j=0; j<numhd; j++) {
	    driveentry[j].state = fdiskThisDriveSetIsActive(&p->drive,
							     hdarr[j]->num);
	    driveentry[j].cb = newtCheckbox(22, row+j, hdarr[j]->name+5,
					    (driveentry[j].state) ? '*' : ' ',
					    NULL,
					    &driveentry[j].state);
	    newtFormAddComponent(driveform, driveentry[j].cb);
	}
	if (j > 4) {
	    newtFormSetHeight(driveform, 4);
	    newtFormAddComponent(driveform, sb);
	} else {
	    newtFormSetWidth(driveform, 10);
	}
    }
    
    /* setup type box callback */
    if (typeentry) {
	cb1.entry = &mntptentry;
	cb1.val   = mntpt;

	/* yuck but it works */
	typebox_mp[0] = -1;
	inswapentry = (p->type.current == LINUX_SWAP_PARTITION);
	newtComponentAddCallback(typeentry,(newtCallback) typebox_scroll,&cb1);
    }

    /* setup mount point callback */
    if (!p->immutable) {
	cb2.form      = &form;
	cb2.val       = mntpt;
	cb2.val2      = &boot;
	memset(&cb2.curds, 0, sizeof(DriveSet));
	memcpy(&cb2.origds, &p->drive, sizeof(DriveSet));
	cb2.numhd     = numhd;
	cb2.hdarr     = hdarr;
	cb2.de        = driveentry;
	cb2.dobootable = (fdiskIndexPartitionSpec(spec, "/boot", &j) !=
			  FDISK_SUCCESS);
	
	newtComponentAddCallback(mntptentry,(newtCallback) entrybox_cb,&cb2);
    }
				 
    row = 9+cval;
    ok = newtButton( 20, row, _("Ok"));
    cancel  = newtButton( 40, row, _("Cancel"));
    if (mntptentry)
	newtFormAddComponents( form,  mntptentry, NULL );
    if (sizeentry)
	newtFormAddComponents( form, sizeentry, NULL);
    if (growentry)
	newtFormAddComponents( form, growentry, NULL);
    if (typeentry)
	newtFormAddComponents( form, typeentry, NULL );
    if (driveform)
	newtFormAddComponents( form, driveform, NULL );
    newtFormAddComponents( form, ok, cancel, NULL);

    done = 0;
    while (!done) {
	answer = newtRunForm(form);

	if (answer != cancel) {
	    /* modify partition request based on the entry boxes */
	    if (typeentry) {
		tmpuint = (long) newtListboxGetCurrent( typeentry );
		fdiskSetConstraint(&p->type, tmpuint, tmpuint, tmpuint, 1);
	    }
	    
	    /* make sure mount point is valid */
	    if (p->type.current != LINUX_SWAP_PARTITION) {
		int valid=1;
		int skiprest=0;

		TrimWhitespace(mntpt);
		
		/* see if they even gave the partition a name  */
		/* we will ask them if they really want to not */
		/* assign the partition a name at this time if */
		/* they have just created a non-ext2 partition */
		if (!*mntpt && p->type.current != LINUX_NATIVE_PARTITION) {
		    if (newtWinChoice(_("No Mount Point"), _("Yes"), _("No"),
				      _("You have not selected a mount point "
				        "for this partition. Are you sure you "
				        "want to do this?")) == 2)
			continue;
		    else {
			/* we need a name for this partition    */
			/* we'll name them like swap partitions */
			/* except use 'DOSxxx'                  */
			if (strncmp("Dos", entry->name, 4)) {
			    char *t;
			    fdiskMakeUniqSpecName( spec, "Dos", &t );
			    fdiskRenamePartitionSpec(spec, entry->name, t);
			}
			skiprest = 1;
		    }
		}
			
		
		/* do old test first */
		if (!skiprest) {
		    if (entry->status != REQUEST_ORIGINAL || *mntpt)
			if (badMountPoint(p->type.current, mntpt))
			    continue;
		    
		
		    if (entry->status == REQUEST_ORIGINAL) {
			/* this is an original partition, should have a */
			/* mount point of "" or a valid path            */
			if (*mntpt && 
			    (*mntpt != '/' || ((strcmp(entry->name, mntpt) &&
			!fdiskIndexPartitionSpec(spec, mntpt, &tmpuint))))) {
			    valid = 0;
			}
		    } else if (*mntpt != '/' || (strcmp(entry->name, mntpt) &&
			 !fdiskIndexPartitionSpec(spec, mntpt, &tmpuint))) {
			valid = 0;
		    }
		}

		if (!valid) {
		    newtWinMessage(_("Mount Point Error"), _("Ok"),
			   _("The mount point requested is either an illegal "
			     "path or is already in use. Please select a "
			     "valid mount point."));
		    
		    continue;
		}
	    }

	    if (sizeentry) {
		tmpint=strtol(size, &eptr, 10);
		if (eptr != size && *eptr == 0 && tmpint > 0) {
		    tmpint *= SECPERMEG;
		    if (growentry && grow != ' ')
			fdiskSetConstraint(&p->size,0,tmpint,FDISK_SIZE_MAX,1);
		    else
			fdiskSetConstraint(&p->size,0,tmpint,tmpint,1);
		} else {
		    newtWinMessage(_("Size Error"), _("Ok"),
			   _("The size requested is illegal. Make sure the "
			     "size is greater and zero (0), and is specified "
			     "int decimal (base 10) format."));
		    continue;
		}
	    }

	    if (driveform) {
		fdiskDeactivateAllDriveSet( &p->drive );
		for (j=0; j<numhd; j++)
		    if (driveentry[j].state == '*')
			fdiskActivateDriveSet( &p->drive, hdarr[j]->num );
	    }
	    
	    /* fdiskHandleSpecialPartitions() will do this for us */
	    /* so I'm taking the boot entry out for now           */

	    if (p->type.current == LINUX_SWAP_PARTITION) {
		/* make sure we have a valid swap partition name */
		if (strncmp("Swap", entry->name, 4)) {
		    char *t;
		    fdiskMakeSwapSpecName( spec, &t );
		    fdiskRenamePartitionSpec(spec, entry->name, t);
		    free(t);
		}
	    }
	    
	    /* first see if they changed the mount point    */
	    /* we only worry about ext2 and dos partitions  */
	    /* which have a valid mntpt                     */
	    /* LOGIC is not the word for how all this works */
	    if (p->type.current != LINUX_SWAP_PARTITION &&
		strncmp("Dos", mntpt, 3)) {
		TrimWhitespace(mntpt);
		if (p->immutable)
		    status = REQUEST_ORIGINAL;
		else
		    status = REQUEST_PENDING;
	    
		if (strcmp(mntpt, entry->name)) {
		    /* if this is an original partition which we just set  */
		    /* the name back to '' from a real name, set name back */
		    /* to the 'Existxxxxx' name                            */
		    if (entry->status == REQUEST_ORIGINAL && !*mntpt) {
			for (hdidx=0; hdidx < numhd; hdidx++) 
			    if (hdarr[hdidx]->num == p->drive.current)
				break;
			
			if (hdidx != numhd)
			    sprintf(tmpstr, "Exist%03d%03d",
				    hdarr[hdidx]->num, p->num.current);
			else
			    strcpy(tmpstr,"Exist999999");
			
			fdiskRenamePartitionSpec( spec, entry->name, tmpstr );
			fdiskModifyPartitionSpec( spec, tmpstr, p, status);
		    } else {
			fdiskRenamePartitionSpec( spec, entry->name, mntpt );

			/*  this is a big kludge! */
			/* reset bootable partition handling so if we   */
			/* rename '/' to '/usr', we don't enforce rules */
			fdiskSetConstraint(&p->endcyl,
			       0,FDISK_ENDCYL_MIN,FDISK_ENDCYL_MAX,0);
			fdiskModifyPartitionSpec( spec, mntpt, p, status);
		    }
		} else {
		    fdiskModifyPartitionSpec( spec, mntpt, p, status);
		}	    
	    } else {
		fdiskModifyPartitionSpec( spec, entry->name, p, status);
	    }

	    fdiskHandleSpecialPartitions( spec );
	    status = FDISK_SUCCESS;
	    done = 1;
	} else {
	    status = FDISK_ERR_USERABORT;
	    done = 1;
	}
    }
    
    newtPopWindow();
    newtFormDestroy(form);

    return status;
}

/* add a partition spec */
static int AddPartitionSpec(HardDrive **hdarr, unsigned int numhd,
		     PartitionSpec *spec) {

    Partition template;
    int      status;
    unsigned int i;
    
    /* create a template partitionspec to send to editpartition */
    memset(&template, 0, sizeof(Partition));
    template.size.min = SECPERMEG;

    /* insert with a name we know to mean its a new partition */
    fdiskInsertPartitionSpec(spec, NEW_PARTSPEC, &template, REQUEST_PENDING);
    fdiskIndexPartitionSpec( spec, NEW_PARTSPEC, &i );
    status = EditPartitionSpec(hdarr, numhd, spec, &spec->entry[i]);
    if (status == FDISK_SUCCESS) {
	return FDISK_SUCCESS;
    } else {
	fdiskDeletePartitionSpec(spec, NEW_PARTSPEC);
	return FDISK_ERR_USERABORT;
    }
}


/* delete a partition spec */
static int DeletePartitionSpec( HardDrive **hdarr, unsigned int numhd,
			 PartitionSpec *spec, PartitionSpecEntry *entry,
				int force) {

    Partition *p;
    int      status;
    unsigned int c, l, m, n, t;
    char     *tmpstr;

    p = &entry->partition;

    tmpstr=strdup(entry->name);
    
    if (!force && newtWinChoice(_("Delete Partition"), _("Yes"), _("No"),
			        _("Are you sure you want to delete "
			          "this partiton?")) == 2)
	return FDISK_ERR_USERABORT;
    
    if (p->immutable) {
	fdiskGetCurrentConstraint(&p->num, &c);
	fdiskGetCurrentConstraint(&p->type, &t);
	fdiskGetCurrentDriveSet(&p->drive, &l);
	for (m=0; m<numhd; m++)
	    if (hdarr[m]->num == l)
		break;
	
	fdiskRemovePartition(hdarr[m], c);

	/* make it so we can delete this partition now */
	p->immutable = 0;
	fdiskModifyPartitionSpec( spec, tmpstr, p, REQUEST_PENDING );

	/* ok, see if this was the last immutable logical partition */
	/* in an immutable primary extended partition               */
	/* we pray that fdiskCleanOriginal... will get rid of the   */
	/* spec entry for the pep                                   */
	if (c > 4) {
	    if (fdiskLastLogical( hdarr[m], &n ) != FDISK_SUCCESS) {
		/* all logicals are gone, blow away pep */
		if (hdarr[m]->pep && hdarr[m]->table[hdarr[m]->pep].immutable){
		    fdiskRemovePartition(hdarr[m], hdarr[m]->pep);
		}
	    }
	}
    }
    
    status = fdiskDeletePartitionSpec( spec, tmpstr );
    fdiskHandleSpecialPartitions( spec );
    free(tmpstr);

    return FDISK_SUCCESS;
}

/* edit/add an NFS partition -> set index to -1 if adding new */
int EditNFSMount( struct fstab *remotefs, int index, 
		  struct intfInfo *intf, struct netInfo *netc,
		  struct driversLoaded **dl ) {

    int rc;
    struct fstabEntry entry;

    if (!(intf->set & INTFINFO_HAS_BOOTPROTO)) { 
	rc = bringUpNetworking(intf, netc, dl, 1);
    } else {
	rc = 0;
    }

    if (rc)
	return 1;

    if (index == -1) {
	initFstabEntry(&entry);
	entry.type = PART_NFS;
	entry.tagName = "NFS Mount";
	entry.device = NULL;
	entry.mntpoint = NULL;
	rc = editNetMountPoint(&entry);
	if (!rc)
	    addFstabEntry(remotefs, entry);
    } else {
	rc = editNetMountPoint(&remotefs->entries[index]);
    }

    return rc;
}


/* remote fstab entry */
int DeleteNFSMount(struct fstab *remotefs, int index) {
    int i;

    if (remotefs->numEntries < 1)
	return 0;
    
    for (i=index; i<remotefs->numEntries-1; i++)
	remotefs->entries[i] = remotefs->entries[i+1];

    remotefs->numEntries -= 1;
    return 0;
}

/* used for each line in partbox - tells us what is on that line */
enum partbox_types {PARTBOX_COMMENT, PARTBOX_NFS, PARTBOX_PART};
struct partbox_entry {
    enum partbox_types type;    /* what is on this line */
    int                index;   /* index in nfs or partition arrays */
    int               hilite;   /* element in drive window to hilight */
};

/* simple callback for scrollbox skipping non-entries */
struct partbox_struct {
    unsigned int  len;       /* total entries allocated */
    unsigned int  num;       /* number in use           */
    newtComponent *dbox;     /* drive box */
    struct partbox_entry *entry; /* describes use of this line */
};

/* this is some ugly sh*t, don't try this at home kids */
static void partbox_scroll(newtComponent list, struct partbox_struct *status) {

    static int last=-1;
    static int dontforce=0;
    int sel;
    int i;
    int odir, dir;
    int done;
    int lasttry;

    /* get the index into the partbox_struct array */
    sel = (long) newtListboxGetCurrent(list);

    /* see if this callback occurred because we were forcing */
    /* listbox to scroll                                     */
    if (dontforce) {
	dontforce = 0;
	return;
    }

    /* if the element is ok then just return */
    if (status->entry[sel].type != PARTBOX_COMMENT) {
	if (status->entry[sel].type == PARTBOX_PART &&
	    status->entry[sel].hilite >= 0 && status->dbox != NULL)
	    newtListboxSetCurrent(*status->dbox, status->entry[sel].hilite);
	return;
    }

    /* see which direction we're heading , >0 means down, < 0 means up */
    if (last == -1)
	dir = 1;
    else {
	if (sel > last)
	    dir = 1;
	else
	    dir = -1;
    }

    odir    = dir;
    done    = 0;
    lasttry = 0;
    while (!done) {
	if (dir > 0) {
	    for (i=sel; i < status->num; i++)
		if (status->entry[i].type != PARTBOX_COMMENT)
		    break;

	    if (i!=status->num) {
		dontforce = 1;
		newtListboxSetCurrent(list, i);
		last = i;
		done = 1;

		if (lasttry) {
		    /* scroll to top, since this is last try so original */
		    /* direction was going up                            */
		    dontforce = 1;
		    newtListboxSetCurrent(list, 0);
		    dontforce = 1;
		    newtListboxSetCurrent(list,last);
		}
	    } else {
		if (!lasttry) {
		    dir = -1;
		    lasttry = 1;
		} else {
		    done = 1;
		}
	    }
	} else {
	    for (i=sel; i >= 0; i--)
		if (status->entry[i].type != PARTBOX_COMMENT)
		    break;

	    if (i >= 0) {
		dontforce = 1;
		newtListboxSetCurrent(list, i);
		last = i;
		done = 1;

		if (lasttry) {
		    /* scroll to bottom, since this is last try so original */
		    /* direction was going up                               */
		    dontforce = 1;
		    newtListboxSetCurrent(list, status->num-1);
		    dontforce = 1;
		    newtListboxSetCurrent(list,last);
		}
	    } else {
		if (!lasttry) {
		    dir = 1;
		    lasttry = 1;
		} else {
		    done = 1;
		}
	    }
	}
    }

    /* if we found a valid line then move drive box selection too */
    sel = (long) newtListboxGetCurrent(list);
    if (status->entry[sel].type == PARTBOX_PART &&
	status->entry[sel].hilite >= 0 && status->dbox != NULL)
	newtListboxSetCurrent(*status->dbox, status->entry[sel].hilite);
}


static int MakeDriveBox( HardDrive **hdarr, unsigned int numhd,
			 unsigned int *drvused, int dheight,
			 newtComponent *dbox ) {
    int hdidx, i, per;
    char tmpstr[80];
    
    *dbox = newtListbox( -1, -1, dheight, NEWT_FLAG_SCROLL );
    newtComponentTakesFocus( *dbox, 0 );
    for (hdidx=0; hdidx < numhd; hdidx++) {
	snprintf(tmpstr,sizeof(tmpstr),
		 "   %s      [%5d/%3d/%2d]   "
		 "%6dM %6dM %6dM"
		 "                       ",
		 hdarr[hdidx]->name+5,
		 hdarr[hdidx]->geom.cylinders,
		 hdarr[hdidx]->geom.heads,
		 hdarr[hdidx]->geom.sectors,
		 hdarr[hdidx]->totalsectors/SECPERMEG,
		 drvused[hdidx]/SECPERMEG,
		 hdarr[hdidx]->totalsectors/SECPERMEG-drvused[hdidx]/SECPERMEG
		 );
	
	tmpstr[58]='[';
	tmpstr[69]=']';
	per = (100*drvused[hdidx])/hdarr[hdidx]->totalsectors;
	if (per >= 99)
	    per = 10;
	else
	    per = per/10;
	
	for (i=0; i < per; i++)
	    tmpstr[59+i] = '#';

	tmpstr[74]=0;
	newtListboxAddEntry(*dbox, tmpstr, (void *) 0);
    }

    return FDISK_SUCCESS;
}			 

/* given partitoins/hard drives, returns a listbox for use */
/* includes the callback function to skip over headings    */
static int MakePartBox( HardDrive **hdarr, unsigned int numhd,
			PartitionSpec *spec, struct fstab *remotefs,
			int x, int y, int pheight, int dheight,
			newtComponent *list, struct partbox_struct *status,
			newtComponent *dbox) {
    
    newtComponent partbox;

    unsigned int drivenum;
    unsigned int totalused;
    int col;
    int i, k, l, hdidx;
    
    unsigned int listlen;
    unsigned int foundpart;
    char         tmpstr[80];
    int		 num;

    unsigned int *drvused=alloca(numhd*sizeof(unsigned int));

    memset(drvused, 0, numhd*sizeof(unsigned int));
    
    /* check if there are *any* partitions to display */
    for (i = 0, num = 0; i < spec->num; i++) {
	if (fdiskIsExtended(spec->entry[i].partition.type.current))
	    continue;
	num++;
    }
    num += remotefs->numEntries;
    if (!num) {
	*list = NULL;
	MakeDriveBox( hdarr, numhd, drvused, dheight, dbox );
	return FDISK_ERR_BADNUM;
    }

    partbox = newtListbox(-1, -1, pheight, 
			  NEWT_FLAG_RETURNEXIT  | NEWT_FLAG_SCROLL);
    
    listlen = 0;
    status->entry=(struct partbox_entry *)malloc(100*
						 sizeof(struct partbox_entry));
    status->len   = 100;
    memset(status->entry, 0, status->len*sizeof(struct partbox_entry));

    status->dbox = NULL;
    for (hdidx=0; hdidx < numhd; hdidx++) {
	drivenum = hdarr[hdidx]->num;

	/* display all spec'd partitions for this drive */
	foundpart = 0;
	totalused = 0;
	for (i=0; i<spec->num; i++) {
	    unsigned int num, minsize, actsize, drive, totsize;
	    char         statstr[80];
	    char         *pname, *devname;
	    Partition    *p;

	    if (spec->entry[i].status == REQUEST_DENIED)
		continue;
	    
	    p = &spec->entry[i].partition;
	    if ((drive = p->drive.current) != drivenum)
		continue;

	    if (fdiskIsExtended(p->type.current))
		continue;
	    
	    num     = p->num.current;
	    actsize = p->size.current;
	    minsize = p->size.min;
	    pname   = spec->entry[i].name;
	    devname = hdarr[hdidx]->name;
	    totsize = hdarr[hdidx]->totalsectors;
	    
	    if (!foundpart)
		foundpart = 1;

	    /* increment amount of space used */
	    totalused += actsize;
	    
	    /* mount point  */
	    col = 3;
	    memset(statstr, ' ', sizeof(statstr));
	    if (strncmp("Exist", pname, 5) && strncmp("Swap", pname, 4) &&
		strncmp("Dos", pname, 3))
		BuildTableField( statstr, pname, col, 16, 0 );
	    
	    /* Block device */
	    snprintf(tmpstr, sizeof(tmpstr), "%s%d", devname+5, num );
	    col += 22;
	    BuildTableField( statstr, tmpstr, col, 10, 0 );
	    
	    /* Size */
	    snprintf(tmpstr, sizeof(tmpstr), "%5dM", minsize/SECPERMEG);
	    col += 10;
	    BuildTableField( statstr, tmpstr, col, 9, 1 );

	    snprintf(tmpstr, sizeof(tmpstr), "%5dM", actsize/SECPERMEG);
	    col += 10;
	    BuildTableField( statstr, tmpstr, col, 9, 1 );

	    /* we dont want to see all that stuff, just English label */
	    /* for the type                                           */
	    /* Type */
	    col += 13;
	    if (hdarr[hdidx]->part_type == FDISK_PART_TYPE_SUN)
	    	l = p->type.current | SUNPARTTYPE;
	    else
	    	l = p->type.current | NONSUNPARTTYPE;
	    for (k=0; k<nparttypes; k++)
		if (allparttypes[k].index == p->type.current ||
		    allparttypes[k].index == l)
		    break;

	    if (k != nparttypes)
		snprintf(tmpstr, sizeof(tmpstr), "%s", allparttypes[k].name);
	    else
		snprintf(tmpstr, sizeof(tmpstr), "0x%02x", p->type.current);
	    
	    BuildTableField( statstr, tmpstr, col, 18, 0);

	    /* now stick it in listbox */
	    statstr[73]=0;
	    status->entry[listlen].type = PARTBOX_PART;
	    status->entry[listlen].index = i;
	    status->entry[listlen].hilite = hdidx;
	    newtListboxAddEntry(partbox, statstr,(void *) (long) listlen);
	    listlen++;
	}

	drvused[hdidx] = totalused;
    }

    for (i=0; i<remotefs->numEntries; i++) {
	char         statstr[80];

	/* mount point  */
	col = 2;
	memset(statstr, ' ', sizeof(statstr));
	BuildTableField( statstr, remotefs->entries[i].mntpoint, col, 16, 0 );
	    
	/* Block device */
	col += 17;
	snprintf(tmpstr, sizeof(tmpstr), "%s:%s",
		 remotefs->entries[i].netHost, remotefs->entries[i].netPath);
	BuildTableField( statstr, tmpstr, col, 40, 0 );
	    
	/* Size */
/* snprintf(tmpstr, sizeof(tmpstr), "%5dM/NA           ", minsize/SECPERMEG);*/
	*tmpstr=0;
	col += 12;
	BuildTableField( statstr, "", col, 15, 0 );

	/* Type */
	col += 27;
	BuildTableField( statstr, "NFS", col, 18, 0);
	
	/* now stick it in listbox */
	statstr[70]=0;
	status->entry[listlen].type  = PARTBOX_NFS;
	status->entry[listlen].index = i;
	newtListboxAddEntry(partbox, statstr,(void *) (long) (listlen));
	listlen++;
    }
	
    
    /* now display any partition specs which WERE NOT allocated */
    foundpart = 0;
    for (i=0; i<spec->num && !foundpart; i++)
	if (spec->entry[i].status == REQUEST_DENIED)
	    foundpart = 1;

    if (foundpart) {
	status->entry[listlen].type = PARTBOX_COMMENT;
	newtListboxAddEntry(partbox,"", (void *)(long)listlen);
	listlen++;
	status->entry[listlen].type = PARTBOX_COMMENT;
	newtListboxAddEntry(partbox,"Unallocated requested partitions",
			    (void *)(long)listlen);
	listlen++;
	status->entry[listlen].type = PARTBOX_COMMENT;
	newtListboxAddEntry(partbox,"--------------------------------",
			    (void *)(long)listlen);
	listlen++;

	for (i=0; i<spec->num; i++) {
	    if (spec->entry[i].status == REQUEST_DENIED) {
		unsigned int num, minsize, actsize;
		char         statstr[80];
		char         *pname;
		Partition    *p;
		
		foundpart = 1;
		p = &spec->entry[i].partition;
		if (fdiskIsExtended(p->type.current))
		    continue;
		
		num     = p->num.current;
		minsize = p->size.min;
		actsize = p->size.current;  
		pname   = spec->entry[i].name;
		
		/* mount point  */
		col = 3;
		memset(statstr, ' ', sizeof(statstr));
		if (strncmp("Exist", pname, 5) && strncmp("Swap", pname, 4) &&
		    strncmp("Dos", pname, 3))
		    BuildTableField( statstr, pname, col, 16, 0 );
	    
		/* Reasons */
		col += 17;
		BuildTableField( statstr, 
				GetReasonString(spec->entry[i].reason),
				col, 25, 0 );
		
		/* Size */
		snprintf(tmpstr, sizeof(tmpstr), "%5dM/NA           ",
			 minsize/SECPERMEG);
		col += 23;
		BuildTableField( statstr, tmpstr, col, 15, 0 );
		
		/* we dont want to see all that stuff, just English label */
		/* for the type                                           */
		/* Type */
		col += 15;
		
		l = p->type.current | NONSUNPARTTYPE;
		for (hdidx = 0; hdidx < numhd; hdidx++)
		    if (hdarr[hdidx]->num == p->drive.current &&
		        hdarr[hdidx]->part_type == FDISK_PART_TYPE_SUN)
		    	l = p->type.current | SUNPARTTYPE;
		for (k=0; k<nparttypes; k++)
		    if (allparttypes[k].index == p->type.current ||
		    	allparttypes[k].index == l)
			break;
		
		if (k != nparttypes)
		    snprintf(tmpstr, sizeof(tmpstr),"%s",allparttypes[k].name);
		else
		    snprintf(tmpstr, sizeof(tmpstr),"0x%02x", p->type.current);
	    
		BuildTableField( statstr, tmpstr, col, 18, 0);
		
		/* now stick it in listbox */
		statstr[70]=0;
		status->entry[listlen].type  = PARTBOX_PART;
		status->entry[listlen].index = i;
		status->entry[listlen].hilite = -1;
		newtListboxAddEntry(partbox, statstr,(void *)(long)(listlen));
		listlen++;
	    }
	}
    }
    
    /* mark the VERY end of listbox */
    status->num = listlen;
    
    /* setup the callback for the listbox */
    newtComponentAddCallback(partbox, (newtCallback) partbox_scroll, status);
    for (i=0; i<status->num-1;i++)
	if (status->entry[i].type != PARTBOX_COMMENT)
	    break;

    if (i!=status->num)
	newtListboxSetCurrent(partbox,i);

    *list = partbox;

    /* now make the drive box IF desired */
    MakeDriveBox( hdarr, numhd, drvused, dheight, dbox );
    
    if (partbox)
	status->dbox = dbox;

    
    return 0;
}



/* do a operation on a partition */
static int  DoMenuFunction( char *function,
			    HardDrive **orighd, unsigned int numhd,
			    HardDrive **curhd,
			    newtComponent partbox,
			    struct partbox_struct *partbox_status,
			    PartitionSpec *spec ) {
    
    unsigned int sel;
    int          num=0;
    int          status;
    int          i;
    HardDrive    *tmphdarr[MAX_HARDDRIVES];
    
    if (partbox) {
	sel = (long) newtListboxGetCurrent(partbox);
	if (partbox_status->entry[sel].type != PARTBOX_COMMENT)
	    num = partbox_status->entry[sel].index;
	else
	    num = -1;
    } else {
	num = -1;
    }
    
    for (i=0; i<numhd; i++) {
	tmphdarr[i] = (HardDrive *) alloca(sizeof(HardDrive));
	memcpy(tmphdarr[i], orighd[i], sizeof(HardDrive));
    }
    
    if (!strcmp("ADD", function)) {
	status = AddPartitionSpec(tmphdarr, numhd, spec);
    } else if (num >= 0 && !strcmp("EDIT", function)) {
	status = EditPartitionSpec(tmphdarr, numhd, spec, &spec->entry[num]);
    } else if (num >= 0 && !strcmp("DEL", function)) {
	status = DeletePartitionSpec(orighd, numhd, spec, &spec->entry[num],0);
    } else {
	status = FDISK_ERR_BADNUM;
    }

    if (status == FDISK_SUCCESS) {
	fdiskAutoInsertPartitions(orighd, numhd, tmphdarr, spec );
	showReasons( spec );
	fdiskGrowPartitions(orighd, numhd, tmphdarr, spec);

	/* if any original partitions were REMOVED we have to */
	/* sync up their entries in the partition spec table  */
	/* with their actual representation in 'orighd'.      */
	/* Mainly fixes up logical partition #'s which change */
	/* when other logical partitions are removed          */
	if (!strcmp("DEL", function))
	    fdiskCleanOriginalSpecs( orighd, numhd, spec );
	
	for (i=0; i<numhd; i++)
	    memcpy(curhd[i],tmphdarr[i], sizeof(HardDrive));
			    
	return FDISK_SUCCESS;
    } else {
	return FDISK_ERR_BADNUM;
    }
	
}

/* main loop of the program, builds the display of all drives/partitions */
static int StartMaster( HardDrive **hdarr, unsigned int numhd,
			PartitionSpec *spec,
			struct fstab *remotefs,
			struct intfInfo *intf, struct netInfo *netc,
			struct driversLoaded **dl,
			int dontPartition,
			int *writeChanges) {
    newtComponent form, add, addnfs, del, edit, reset, ok, cancel, answer;
    newtComponent partbox, curcomponent;
    newtGrid buttons, grid;
    newtComponent dbox, partlabel, drivelabel, driveinfolabel;
    HardDrive *newhdarr[MAX_HARDDRIVES];
    HardDrive *prestinehdarr[MAX_HARDDRIVES];
    int formdone, totallydone, i, status;
    int currentselection;
    struct newtExitStruct event;
    char tmpstr[80];
    unsigned int width, height;
    int          changesmade=0;
    int retcode = FDISK_SUCCESS; /* i'm optimistic */
    struct partbox_struct partbox_status;
    enum mywidgets {PARTBOX, ADD, ADDNFS, EDIT, DELETE,
		    RESET, OK, CANCEL, NONE};
    enum mywidgets curwidget;
    
    width  = 78;
    height = 20;

    /* FIXME: hack! */
    addnfs = (void *) 1;
    reset = (void *) 2;

    /* copy original hard drive configurations into work spaces */
    for (i=0; i<numhd; i++) {
	newhdarr[i] = (HardDrive *) alloca(sizeof(HardDrive));
	memcpy(newhdarr[i], hdarr[i], sizeof(HardDrive));
	prestinehdarr[i] = (HardDrive *) alloca(sizeof(HardDrive));
	memcpy(prestinehdarr[i], hdarr[i], sizeof(HardDrive));
    }

    /* build the main list of installed/probed devices */
    totallydone = 0;
    currentselection = -1;
    curwidget = NONE;

    newtCenteredWindow(width, height, _("Current Disk Partitions"));

    while (!totallydone) {
	/* are there ANY defined partitions ? */
	partlabel = newtLabel(-1, -1,
         "   Mount Point          Device     Requested   Actual         Type");
	MakePartBox(newhdarr, numhd, spec, remotefs,
		    1, 2, height-12, 4,
		    &partbox, &partbox_status, &dbox);

	form = newtForm(NULL, NULL, 0);

	if (dontPartition) {
	    buttons = newtButtonBar(
			_("Edit"), &edit, _("Delete"), &del, 
			_("Ok"), &ok, _("Back"), &cancel, NULL);
	    add = reset = NULL;
	    snprintf(tmpstr, sizeof(tmpstr),
		     "    F2-Add NFS    F3-Edit    F4-Delete"
		     "    F12-Ok   v%5s", VERSION_STR);
	} else {
	    buttons = newtButtonBar(_("Add"), &add, 
			_("Edit"), &edit, _("Delete"), &del,
			_("Ok"), &ok, _("Back"), &cancel, NULL);

	    newtFormAddHotKey(form, NEWT_KEY_F1);
	    newtFormAddHotKey(form, NEWT_KEY_F5);

	    snprintf(tmpstr, sizeof(tmpstr),
		     "    F1-Add    F2-Add NFS    F3-Edit   "
		     "F4-Delete    F5-Reset    F12-Ok   v%5s", VERSION_STR);
	}

	drivelabel = newtLabel(-1, -1, _("Drive Summaries"));
	driveinfolabel = newtLabel(-1, -1,
	       "  Drive      Geom [C/H/S]      Total    Used    Free");


	grid = newtCreateGrid(1, 6);
	newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, partlabel,
			 0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);
	if (partbox)
	    newtGridSetField(grid, 0, 1, NEWT_GRID_COMPONENT, partbox,
			     0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);
	newtGridSetField(grid, 0, 2, NEWT_GRID_COMPONENT, drivelabel,
			 0, partbox ? 0 : 8, 0, 0, NEWT_ANCHOR_LEFT, 0);
	newtGridSetField(grid, 0, 3, NEWT_GRID_COMPONENT, driveinfolabel,
			 0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);
	newtGridSetField(grid, 0, 4, NEWT_GRID_COMPONENT, dbox,
			 0, 0, 0, 0, 0, 0);
	newtGridSetField(grid, 0, 5, NEWT_GRID_SUBGRID, buttons,
			 0, 1, 0, 0, 0, NEWT_GRID_FLAG_GROWX);

	newtGridPlace(grid, 1, 0);
	newtGridAddComponentsToForm(grid, form, 1);
	newtGridFree(grid, 1);

	/* and the hotkeys */
	newtFormAddHotKey(form, NEWT_KEY_F2);
	newtFormAddHotKey(form, NEWT_KEY_F3);
	newtFormAddHotKey(form, NEWT_KEY_F4);

	/* and how to use this plus info on version */
	newtPushHelpLine(tmpstr);
	
	if (partbox) {
	    if (currentselection < 0)
		currentselection = (long) newtListboxGetCurrent(partbox);
	    else
		newtListboxSetCurrent(partbox, currentselection);
	}

	/* see if no partitions are present */
	/* we can't Add if we are in Read Only mode! */
	if (spec->num == 0 && !dontPartition)
	    curwidget = ADD;
	
	/* set current widget if necessary */

	if (partbox != NULL && curwidget == PARTBOX)
	    newtFormSetCurrent(form, partbox);
	else if (curwidget == ADD)
	    newtFormSetCurrent(form, add);
	else if (curwidget == EDIT)
	    newtFormSetCurrent(form, edit);
	else if (curwidget == DELETE)
	    newtFormSetCurrent(form, del);
	
	/* setup main screen */
	formdone = 0;
	answer = NULL;
	while (!formdone) {
	    newtFormRun(form, &event);

	    /* store the current widget so we can reset it */
	    /* if we have to destroy and recreate form     */
	    /* GetCurrent(), SetCurrent() use a ptr to     */
	    /* newtcomponent, so when we recreate form it  */
	    /* will be meaningless!                        */
	    curcomponent = newtFormGetCurrent( form );
	    curwidget = NONE;
	    if (partbox != NULL && curcomponent == partbox)
		curwidget = PARTBOX;
	    else if (curcomponent == add)
		curwidget = ADD;
	    else if (curcomponent == addnfs)
		curwidget = ADDNFS;
	    else if (curcomponent == edit)
		curwidget = EDIT;
	    else if (curcomponent == del)
		curwidget = DELETE;
	    else if (curcomponent == reset)
		curwidget = RESET;
	    else
		curwidget = NONE;
	    
	    if (event.reason == NEWT_EXIT_HOTKEY) {
		event.reason = NEWT_EXIT_COMPONENT;
		if (event.u.key == NEWT_KEY_F12)
		    event.u.co = ok;
		else if (event.u.key == NEWT_KEY_F1)
		    event.u.co = add;
		else if (event.u.key == NEWT_KEY_F2)
		    event.u.co = addnfs;
		else if (event.u.key == NEWT_KEY_F3)
		    event.u.co = edit;
		else if (event.u.key == NEWT_KEY_F4)
		    event.u.co = del;
		else if (event.u.key == NEWT_KEY_F5)
		    event.u.co = reset;
		else
		    continue;
	    }

	    if (event.reason == NEWT_EXIT_COMPONENT) {
		if (event.u.co != partbox) {
		    if (event.u.co == ok || event.u.co == cancel) {
			if (event.u.co == ok) {
			    *writeChanges = 1;
			    /* make sure '/' is defined */
			    status = fdiskIndexPartitionSpec(spec, "/", &i);
			    if (!status)
				if (spec->entry[i].partition.type.current !=
				    LINUX_NATIVE_PARTITION ||
				    spec->entry[i].status == REQUEST_DENIED)
				    status = 1;
			    
			    if (status) {
				newtWinMessage(_("No Root Partition"), _("Ok"),
				      _("You must assign a root (/) "
					"partition "
					"to a Linux native partition (ext2) "
					"for the install to proceed."));
				continue;
			    }

			    /* make sure some swapspace is defined */
			    status = 1;
			    for (i=0; i < spec->num; i++)
				if (spec->entry[i].partition.type.current ==
				    LINUX_SWAP_PARTITION &&
				    spec->entry[i].status != REQUEST_DENIED) {
				    status = 0;
				    break;
				}

			    if (status) {
				newtWinMessage(_("No Swap Partition"), _("Ok"),
					  _("You must assign a swap "
					    "partition "
					    "for the install to proceed."));
				continue;
			    }
			}

			/* make sure there are no unallocated partitions */
			status = 0;
			for (i=0; i < spec->num; i++)
			    if (spec->entry[i].status == REQUEST_DENIED) {
				status = 1;
				break;
			    }
			
			if (status) {
			    if (newtWinChoice(_("Unallocated Partitions"),
					      _("Yes"), _("No"),
				   _("There are unallocated partitions "
				     "left. If you quit now they will "
				     "not be written to the disk.\n\n"
				     "Are you sure you want to exit?")) == 2)
			    continue;
			}

			/* dont trust the changes made I keep up with */
			/* above, compute it straight from the hd's   */
			changesmade = DisksChanged( prestinehdarr,
						   newhdarr,
						   numhd );
			/* the dontPartition here isn't necessary, but it
			   does make a reassuring sanity check */
			if (changesmade && !dontPartition) {
			    int rc;
			    rc =  newtWinTernary(_("Save Changes"),
						 _("Yes"), _("No"), _("Cancel"),
						 _("Save changes to "
						   "partition table(s)?"));
			    if (rc == 1) {
				retcode = FDISK_SUCCESS;
				*writeChanges = 1;

				/* copy changes */
				if (changesmade) {
				    for (i=0; i<numhd; i++) {
					memcpy(hdarr[i],
					       newhdarr[i],
					       sizeof(HardDrive));
				    }
				}
			    } else if (rc == 2) {
				retcode = FDISK_ERR_USERABORT;
				*writeChanges = 0;
			    } else {
				continue;
			    }
			} else {
			    retcode = FDISK_SUCCESS; /* no changes made */
			}

			/* make sure we indicate they canceled */
			if (event.u.co == cancel)
			    retcode = FDISK_ERR_USERABORT;

			formdone = 1;
			totallydone = 1;
		    } else if (event.u.co == add) {
			status=DoMenuFunction("ADD", hdarr, numhd, newhdarr,
				       partbox, &partbox_status, spec );
			if (status == FDISK_SUCCESS) {
			    formdone = 1;
			    changesmade = 1;
			}
		    } else if (event.u.co == del && partbox != NULL) {
			/* see if its NFS or local mount */
			int sel;
			sel = (long) newtListboxGetCurrent(partbox);
			if (partbox_status.entry[sel].type == PARTBOX_PART)
			    if (dontPartition) 
				newtWinMessage(_("Error"), _("Ok"),
					_("You may only delete NFS mounts."));
			    else 
				status=DoMenuFunction("DEL", hdarr, numhd,
						      newhdarr,
						      partbox, &partbox_status,
						      spec );
			else
			    status=DeleteNFSMount(remotefs, 
					partbox_status.entry[sel].index);
		    
			if (status == FDISK_SUCCESS) {
			    formdone = 1;
			    changesmade = 1;
			}
		    } else if (event.u.co == edit && partbox != NULL) {
			/* see if its NFS or local mount */
			int sel;
			sel = (long) newtListboxGetCurrent(partbox);
			if (partbox_status.entry[sel].type == PARTBOX_PART)
			    status=DoMenuFunction("EDIT", hdarr, numhd,
						  newhdarr,
						  partbox, &partbox_status,
						  spec );
			else
			    status=EditNFSMount(remotefs,
					partbox_status.entry[sel].index,
						intf, netc, dl);
			
			    
			if (status == FDISK_SUCCESS) {
			    formdone = 1;
			    changesmade = 1;
			}
		    } else if (event.u.co == reset) {
			if (newtWinChoice(_("Reset Partition Table"),
					  _("Yes"), _("No"),
					_("Reset partition table to original "
					  "contents? ")) != 2) {
			    for (i=0; i<numhd; i++)
				memcpy(hdarr[i],prestinehdarr[i],
				       sizeof(HardDrive));

			    fdiskWipePartitionSpec(spec);
			    fdiskSetupPartitionSpec( hdarr, numhd, spec );

			    changesmade = 0;
			    formdone = 1;
			}
		    } else if (event.u.co == addnfs) {
			status=EditNFSMount(remotefs, -1, intf, netc, dl);

			if (status == FDISK_SUCCESS) {
			    formdone = 1;
			    changesmade = 1;
			}
		    }
		} else {
		    /* see if its NFS or local mount */
		    int sel;
		    sel = (long) newtListboxGetCurrent(partbox);
		    if (partbox_status.entry[sel].type == PARTBOX_PART)
			status=DoMenuFunction("EDIT", hdarr, numhd, newhdarr,
					      partbox, &partbox_status, spec );
		    else
			status=EditNFSMount(remotefs,
					    partbox_status.entry[sel].index,
					    intf, netc, dl);
		    
		    if (status == FDISK_SUCCESS) {
			formdone = 1;
			changesmade = 1;
		    }
		}
	    }

	    /* save current line if leaving */
	    if (partbox)
		currentselection = (long) newtListboxGetCurrent(partbox);
	}

	newtPopHelpLine();
	if (partbox)
	    free(partbox_status.entry);
	newtFormDestroy(form);
    }

    newtPopWindow();

    return retcode;
}

/* converts a PartionSpec to an equivalent struct fstab */
/* Creates fstab from scratch                           */
static int PartitionSpecToFstab( HardDrive **hdarr, int numhd,
			  PartitionSpec *spec, struct fstab *fstab ) {

    int i, j;
    struct fstabEntry entry;
    
    fstab->entries = malloc(sizeof(*fstab->entries) * spec->num);
    fstab->numEntries = 0;
    for (i = 0; i < spec->num; i++) {
	if (!spec->entry[i].name) continue;
	
	if (spec->entry[i].status != REQUEST_ORIGINAL &&
	    spec->entry[i].status != REQUEST_GRANTED)
	    continue;
	
	/* FIXME: hack, hack, hack */
	if (*spec->entry[i].name != '/' &&
	    *spec->entry[i].name != 'S') continue;
	
	for (j=0; j<numhd; j++)
	    if (hdarr[j]->num == spec->entry[i].partition.drive.current)
		break;
	
	if (j == numhd)
	    continue;

	initFstabEntry(&entry);
	entry.mntpoint = strdup(spec->entry[i].name);
	entry.size = spec->entry[i].partition.size.current / 2;

	entry.device = malloc(6);
	sprintf(entry.device, "%s%d",
		hdarr[j]->name + 5, spec->entry[i].partition.num.current);
	
	switch (spec->entry[i].partition.type.current) {
	  case LINUX_NATIVE_PARTITION:
	    entry.type = PART_EXT2;
	    entry.tagName = "Linux native";
	    break;
	    
	  case LINUX_SWAP_PARTITION:
	    entry.type = PART_SWAP;
	    entry.tagName = "Linux swap";
	    break;
	    
	  case DOS_PRIMARY_lt32MEG_PARTITION:
	  case DOS_PRIMARY_gt32MEG_PARTITION:
	    entry.type = PART_DOS;
	    entry.tagName = "DOS 16-bit >=32";
	    break;
	    
	  default:
	    entry.type = PART_OTHER;
	    entry.tagName = "Other";
	    break;
	}

	addFstabEntry(fstab, entry);
    }
    
    fstabSort(fstab);
    return 0;
}


/* merges mount point info from existing struct fstab into a PartionSpec     */
/* PartitionSpec should already exist and be primed with existing partitions */
/* Note - remote fs ARE NOT stored in the PartitionSpec                      */
static int MergeFstabEntries( HardDrive **hdarr, int numhd,
			      PartitionSpec *spec, struct fstab *fstab ) {

    int i, j, k;
    char device[6];
    
    for (j = 0; j < spec->num; j++) {
	for (k=0; k<numhd; k++)
	    if (hdarr[k]->num == spec->entry[j].partition.drive.current)
		break;
	
	if (k == numhd)
	    continue;

	sprintf(device, "%s%d",
		hdarr[k]->name + 5, spec->entry[j].partition.num.current);
	
	for (i = 0; i < fstab->numEntries; i++)
	    if (!strcmp(fstab->entries[i].device, device))
		break;

	if ( i == fstab->numEntries )
	    continue;

	/* we found a matching entry in the PartitionSpec */
	/* see if the old fstab file had any info we need */
	/* to use (like mount point, etc)                 */
	if (spec->entry[j].name)
	    free(spec->entry[j].name);

	spec->entry[j].name = strdup(fstab->entries[i].mntpoint);
    }

    return 0;
}

/* suck out just the remote fs entries from an fstab */
/* pretty much CopyFstab, with filter on type        */
struct fstab copyRemoteFSFstab(struct fstab * fstab) {
    struct fstab newfstab;
    int i, j;

    if (!fstab->numEntries) {
        newfstab.numEntries = 0;
        newfstab.entries = malloc(1);
        return newfstab;
    }

    /* duplicate the current fstab */
    newfstab.numEntries = fstab->numEntries;
    newfstab.entries = malloc(fstab->numEntries * sizeof(struct fstabEntry));
    for (i = j = 0; i < newfstab.numEntries; i++) {
	if (fstab->entries[i].type != PART_NFS)
	    continue;
	
        if (fstab->entries[i].mntpoint) {
            newfstab.entries[j] = fstab->entries[i];
            newfstab.entries[j].mntpoint=nstrdup(fstab->entries[i].mntpoint);
            newfstab.entries[j].device = nstrdup(fstab->entries[i].device);
            newfstab.entries[j].netPath = nstrdup(fstab->entries[i].netPath);
            newfstab.entries[j].netHost = nstrdup(fstab->entries[i].netHost);
            j++;
        }
    }

    newfstab.numEntries = j;

    /* return the memory we don't actually need */
    newfstab.entries=realloc(newfstab.entries, j * sizeof(struct fstabEntry));

    return newfstab;
}


/* suck out just the remote fs entries from an fstab */
/* pretty much CopyFstab, with filter on type        */
void MergeRemoteFSFstab(struct fstab *oldfstab, struct fstab *newfstab) {
    int i, j;

    /* copy remote fs entries */
    for (i = 0; i < oldfstab->numEntries; i++) {
	if (oldfstab->entries[i].type != PART_NFS)
	    continue;
	
        if (oldfstab->entries[i].mntpoint) {
	    j = newfstab->numEntries;
	    newfstab->entries = realloc(newfstab->entries,
				     (j+1)*sizeof(struct fstabEntry));
	    newfstab->entries[j] = oldfstab->entries[i];
            newfstab->entries[j].mntpoint=nstrdup(oldfstab->entries[i].mntpoint);
            newfstab->entries[j].device=nstrdup(oldfstab->entries[i].device);
            newfstab->entries[j].netPath=nstrdup(oldfstab->entries[i].netPath);
            newfstab->entries[j].netHost=nstrdup(oldfstab->entries[i].netHost);
	    newfstab->numEntries = j+1;
        }
    }
}

static int deletePartitionClass(HardDrive ** hd, int numhd, 
				PartitionSpec * spec, int justLinux) {
    int deleteit;
    int type;
    int i;

    i = 0;
    while (i < spec->num) {
	type = spec->entry[i].partition.type.current;

	deleteit = !justLinux;
	if (justLinux && (type == LINUX_SWAP_PARTITION || 
			  type == LINUX_NATIVE_PARTITION))
	    deleteit = 1;

	if (deleteit) {
	    DeletePartitionSpec(hd, numhd, spec, &spec->entry[i], 1);
	    fdiskCleanOriginalSpecs( hd, numhd, spec );
	    i=0; /* restart cause entries changed */
	    continue;
	} else {
	    i++;
	}
    }

    return 0;
}

int kickstartPartitioning(struct partitionTable * parts, struct fstab * fstab, 
			  char **drives) {

    HardDrive     *hd[MAX_HARDDRIVES];
    HardDrive     *tmphd[MAX_HARDDRIVES]; /* ORIGINAL HD partition data    */
    unsigned int  numhd;                  /* total # of drives to consider */
    Partition     template;
    PartitionSpec spec;
    unsigned int  i;
    struct fstab  remotefstab;
    char ** deviceList;
    int rc;
    int numDrives;

    int size, maxsize, grow, clearall, clearlinux, zerombr;
    int argc;
    char *mntpt;
    char *eptr;
    char *sizestr, *maxsizestr;
    char **argv;
    poptContext optCon;
    struct poptOption ksPartOptions[] = {
	{ "size", '\0', POPT_ARG_STRING, &sizestr, 0 },
	{ "maxsize",  '\0', POPT_ARG_STRING, &maxsize, 0 },
	{ "grow", '\0', POPT_ARG_NONE, &grow, 0 },
	{ 0, 0, 0, 0, 0 }
    };

    struct poptOption ksClearOptions[] = {
	{ "linux", '\0', POPT_ARG_NONE, &clearlinux, 0},
	{ "all", '\0', POPT_ARG_NONE, &clearall, 0},
	{ 0, 0, 0, 0, 0 }
    };

    struct poptOption ksZeroMBROptions[] = {
	{ 0, 0, 0, 0, 0 }
    };

    numDrives = 0;
    for (i = 0; drives[i]; i++, numDrives++);

    if (numDrives >= MAX_HARDDRIVES) {
	newtWinMessage(_("Too Many Drives"), _("Ok"),
		  _("You have more drives than this program supports. "
		    "Please use the standard fdisk program to setup your "
		    "drives and please notify Red Hat Software that you "
		    "saw this message."));
	return INST_ERROR;
    }
    
    deviceList = alloca(numDrives * sizeof(char *));

    for (i = 0; i < numDrives; i++) {
	deviceList[i] = alloca(15);
	strcpy(deviceList[i], "/tmp/");
	strcat(deviceList[i], drives[i]);

	if ((rc = devMakeInode(drives[i], deviceList[i]))) return INST_ERROR;
    }
    
    /* see if they want bad partition tables automatically zero'd */
    argv = NULL;
    zerombr = 0;
    while (1) {
	if (!ksGetCommand(KS_CMD_ZEROMBR, argv, &argc, &argv)) {
	    char *t;
	    
	    optCon = poptGetContext(NULL, argc, argv, ksZeroMBROptions, 0);

	    /* no options, but just a 'on' or 'off' command */
	    /* have to parse options anyway to make popt see the leftover? */
	    poptGetNextOpt(optCon);
	    t = poptGetArg(optCon);
	    if (*t && (!strcasecmp(t,"no") || !strcasecmp(t,"off") || !strcmp(t,"0")))
		zerombr = 0;
	    else if (*t && (!strcasecmp(t,"yes") || !strcasecmp(t,"on") || !strcmp(t,"1")))
		zerombr = 1;
	    else
		newtWinMessage(_("Zero Partition Table"), _("Ok"),
			       _("bad argument to kickstart "
			         "zerombr command: %s.\nMust be "
                                 "'on', '1', or 'yes' to enable, "
			         "or 'off', '0', or 'no' to disable."),t);

	    poptFreeContext(optCon);
	} else {
	    break;
	}
    }

    /* Read in the partition tables from the requested drive(s) */
    /* first clear all entries                                  */
    memset(hd, 0, MAX_HARDDRIVES*sizeof(HardDrive *));
    ReadDrives(deviceList, numDrives, hd, &numhd, zerombr, 0);

    if (numhd <1) {
	newtWinMessage(_("No Drives Found"), _("Ok"),
		  _("An error has occurred - no valid devices were found "
		    "on which to create new filesystems.  Please check "
		    "your hardware for the cause of this problem."));

	for (i = 0; i < numDrives; i++)
	    unlink(deviceList[i]);
	return INST_ERROR;
    }

   
    /* Translate into a PartitionSpec */
    memset(&spec, 0, sizeof(PartitionSpec));
    fdiskSetupPartitionSpec( hd, numhd, &spec );

    /* copy the fstab they passed, we'll use it until we're done */
    /* if they dont cancel it will be new fstab                  */
    remotefstab = copyRemoteFSFstab( fstab );
    MergeFstabEntries( hd, numhd, &spec, fstab );

    /* see if they want anything removed from disk */
    argv = NULL;
    clearall   = 0;
    clearlinux = 0;
    if (!ksGetCommand(KS_CMD_CLEARPART, argv, &argc, &argv)) {
	optCon = poptGetContext(NULL, argc, argv, ksClearOptions, 0);
	
	rc = poptGetNextOpt(optCon);
	if ( rc < -1) {
	    newtWinMessage(_("Clear Partition Command"), _("Ok"),
			   _("bad argument to kickstart "
			     "clearpart command %s: %s"),
			   poptBadOption(optCon, POPT_BADOPTION_NOALIAS), 
			   poptStrerror(rc));

	    for (i=0; i<numhd; i++)
		fdiskCloseDevice(hd[i]);
	    
	    for (i = 0; i < numDrives; i++)
		unlink(deviceList[i]);

	    return INST_ERROR; /* may not clear what they intended */
	}

	if (clearall || clearlinux) 
	    deletePartitionClass(hd, numhd, &spec, clearlinux);
	poptFreeContext(optCon);
    }	    
	
    /* copy original hard drive configurations into work spaces */
    for (i=0; i<numhd; i++) {
	tmphd[i] = (HardDrive *) alloca(sizeof(HardDrive));
	memcpy(tmphd[i], hd[i], sizeof(HardDrive));
    }

    
    /* now stick their requests into the partition spec and allocate */
    /* the partitions                                                */
    argv = NULL;
    while (1) {
	sizestr = NULL;
	maxsizestr = NULL;
	grow = 0;
	if (!ksGetCommand(KS_CMD_PART, argv, &argc, &argv)) {
	    optCon = poptGetContext(NULL, argc, argv, ksPartOptions, 0);

	    rc = poptGetNextOpt(optCon);
	    if ( rc < -1) {
		newtWinMessage(_("Partition Command"), _("Ok"),
			       _("bad argument to kickstart "
			         "part command %s: %s"),
			       poptBadOption(optCon, POPT_BADOPTION_NOALIAS), 
			       poptStrerror(rc));
	    }

	    mntpt  = poptGetArg(optCon);

	    if (sizestr) {
		size=strtol(sizestr,&eptr, 10);
		if (eptr != sizestr && *eptr == 0 && size > 0)
		    size *= SECPERMEG;
		else
		    size = 0;
	    } else {
		size=0;
	    }

	    if (maxsizestr) {
		maxsize=strtol(maxsizestr,&eptr, 10);
		if (eptr != maxsizestr && *eptr == 0 && maxsize > 0)
		    maxsize *= SECPERMEG;
		else
		    maxsize = 0;
	    } else {
		maxsize=0;
	    }

	    if (maxsizestr && !maxsize)
		newtWinMessage(_("Option Ignored"), _("Ok"),
			       _("The --maxsize option for the partition "
			         "%s was ignored. Check that it is larger "
			         "than the --size option."), mntpt);
	    
	    /* all done with current context */
	    poptFreeContext(optCon);
	    
	    if (!mntpt || size == 0) {
		for (i=0; i<numhd; i++)
		    fdiskCloseDevice(hd[i]);
		
		for (i = 0; i < numDrives; i++)
		    unlink(deviceList[i]);
		return INST_ERROR;
	    }

	    /* fill in the requested partition */
	    memset(&template, 0, sizeof(Partition));

	    /* set the size */
	    if (grow)
		fdiskSetConstraint(&template.size,
				   0,
				   size,
				   (maxsize) ? maxsize : FDISK_SIZE_MAX,
				   1);
	    else
		fdiskSetConstraint(&template.size,
				   0,
				   size,
				   size,
				   1);

	    /* set the type */
	    if (!strcasecmp(mntpt, "swap"))
		fdiskSetConstraint(&template.type,
				   LINUX_SWAP_PARTITION,
				   LINUX_SWAP_PARTITION,
				   LINUX_SWAP_PARTITION,
				   1);
	    else {
		fdiskSetConstraint(&template.type,
				   LINUX_NATIVE_PARTITION,
				   LINUX_NATIVE_PARTITION,
				   LINUX_NATIVE_PARTITION,
				   1);
		/* test mntpt */
		if (badMountPoint(template.type.current, mntpt)) {
		    for (i=0; i<numhd; i++)
			fdiskCloseDevice(hd[i]);
		    
		    for (i = 0; i < numDrives; i++)
			unlink(deviceList[i]);

		    return INST_ERROR;
		}
		
		if (!fdiskIndexPartitionSpec(&spec, mntpt, &rc)) {
		    newtWinMessage(_("Mount Point Error"), _("Ok"),
			   _("The mount point %s is already in use."), mntpt);
		    for (i=0; i<numhd; i++)
			fdiskCloseDevice(hd[i]);
		    
		    for (i = 0; i < numDrives; i++)
			unlink(deviceList[i]);
		    return INST_ERROR;
		}

	    }

	    /* make sure we have a valid swap partition name */
	    if (template.type.current == LINUX_SWAP_PARTITION) {
		if (strcasecmp("swap", mntpt)) {
		    fdiskMakeSwapSpecName( &spec, &mntpt );
		}
	    }

	    /* make an entry */
	    fdiskInsertPartitionSpec(&spec, mntpt,&template, REQUEST_PENDING);
	    fdiskHandleSpecialPartitions( &spec );
	} else {
	    break;
	}
    }

    /* insert the new partition spec */
    fdiskAutoInsertPartitions(hd, numhd, tmphd, &spec);
    showReasons( &spec );
    fdiskGrowPartitions(hd, numhd, tmphd, &spec);

    rc = REQUEST_GRANTED;
    for (i=0; i<spec.num; i++)
	if (spec.entry[i].status == REQUEST_DENIED) {
	    rc = REQUEST_DENIED;
	    newtWinMessage(_("Failed Allocation"), _("Ok"),
			   _("The partition %s could not be allocated."),
			   spec.entry[i].name);
	}


    if (rc == REQUEST_DENIED) {
	for (i=0; i<numhd; i++)
	    fdiskCloseDevice(hd[i]);
	
	for (i = 0; i < numDrives; i++)
	    unlink(deviceList[i]);

	return INST_ERROR;
    }

    /* Write partitions to disk */
    for (i=0; i<numhd; i++)
	memcpy(hd[i], tmphd[i], sizeof(HardDrive));
    
    if (!testing) {
	for (i=0; i<numhd; i++)
	    hd[i]->write_f(hd[i]);
    }
    
    if ((rc = findAllPartitions(NULL, parts))) {
	for (i=0; i<numhd; i++)
	    fdiskCloseDevice(hd[i]);
	
	for (i = 0; i < numDrives; i++)
	    unlink(deviceList[i]);

	return INST_ERROR;
    }
    
    /* free up old fstab */
    if (fstab)
	freeFstab(*fstab);
    PartitionSpecToFstab( hd, numhd, &spec, fstab );
    MergeRemoteFSFstab( &remotefstab, fstab );
    freeFstab(remotefstab);
    
    for (i=0; i<numhd; i++)
	fdiskCloseDevice(hd[i]);

    for (i = 0; i < numDrives; i++)
	unlink(deviceList[i]);

    return 0;
}

static int addNewPartition(PartitionSpec * spec, 
		           char * where, int megs, int grow, int bootable, 
		           unsigned char type, int startCyl) {
    Partition template;
    
    /* create a template partitionspec to send to editpartition */
    memset(&template, 0, sizeof(Partition));
    fdiskSetFixedConstraint(&template.type, type);

    if (bootable) {
	fdiskDeactivateAllDriveSet( &template.drive );
	fdiskActivateDriveSet(&template.drive, 1);
	fdiskActivateDriveSet(&template.drive, 2);
	fdiskSetConstraint(&template.endcyl, 0, 0, 1023, 1);
    }

/*  For now we'll make this alpha only code  */
#ifdef __alpha__

    if (startCyl != -1) {
	/* XXX HACK */
	/* Add a little buffer in case we need to skip a head */
	fdiskSetConstraint(&template.start, 0, startCyl, startCyl + 100, 1);
    }

#endif

    fdiskSetConstraint(&template.size, SECPERMEG * megs, SECPERMEG * megs,
			grow ? FDISK_SIZE_MAX : SECPERMEG * megs, 1);

    /* insert with a name we know to mean its a new partition */
    fdiskInsertPartitionSpec(spec, where, &template, 
				  REQUEST_PENDING);
    
    return 0;
}

static int tryGoal(HardDrive ** hdarr, HardDrive ** tmphdarr, int numhd, 
		   PartitionSpec * spec, struct attemptedPartition * goals) {
    int i;

    for (i = 0; goals[i].mount; i++)
	addNewPartition(spec, goals[i].mount, goals[i].size, 
			goals[i].grow, !strcmp(goals[i].mount, "/boot"),
			goals[i].type, goals[i].start);

    /* insert the new partition spec */
    fdiskAutoInsertPartitions(hdarr, numhd, tmphdarr, spec );
    fdiskGrowPartitions(hdarr, numhd, tmphdarr, spec);

    for (i = 0; i < spec->num; i++)
	if (spec->entry[i].status == REQUEST_DENIED)
	    break;

    return (i < spec->num);
}

static int guessAtPartitioning(HardDrive ** hdarr, int numhd,
			       PartitionSpec * spec, int * runDruid,
			       int dir, int flags,
			       struct attemptedPartition * goals) {
    HardDrive * tmparr[MAX_HARDDRIVES];
    int i, rc;
    static int mayDisplay = 1;
    int erasedLinux = 0;
    int erasedAll = 0;

    *runDruid = 1;

    if (!mayDisplay) {
	if (dir < 0) return INST_CANCEL;
	return 0;
    }

    for (i=0; i<numhd; i++) {
	tmparr[i] = (HardDrive *) alloca(sizeof(HardDrive));
	memcpy(tmparr[i], hdarr[i], sizeof(HardDrive));
    }

    if (fdiskIndexPartitionSpec(spec, "/", &i ) == FDISK_SUCCESS) {
	return 0;
    } else if (flags & (FSEDIT_CLEARLINUX | FSEDIT_CLEARALL)) {
	i = spec->num;
	deletePartitionClass(hdarr, numhd, spec, 
			     flags & FSEDIT_CLEARLINUX);
	if (spec->num != i) {
	    erasedLinux = flags & FSEDIT_CLEARLINUX;
	    erasedAll = flags & FSEDIT_CLEARALL;
	}

	rc = tryGoal(hdarr, tmparr, numhd, spec, goals);
	if (rc && (flags & FSEDIT_CLEARALL)) {
	    i = spec->num;
	    deletePartitionClass(hdarr, numhd, spec, 0);
	    if (spec->num != i) {
		erasedAll = 1;
		memset(spec, 0, sizeof(PartitionSpec));
		fdiskSetupPartitionSpec( hdarr, numhd, spec );
		rc = tryGoal(hdarr, tmparr, numhd, spec, goals);
	    }
	}

	if (!rc) {
	    char message[2000];

	    while (1) {
		strcpy(message, erasedAll ?
		    "All of the partitions on your hard drive(s) will be "
		    "erased.\n\n"
		    "This means that all of the data on your system will be "
		    "destroyed.\n"
		    "\n"
		    "If you do not want to lose all of your partitions, "
		    "select \"Cancel\" now, and perform a \"Custom\" install."
		    :
		    "All of the Linux partitions on your hard drive(s) will be "
		    "erased.\n\n"
		    "This means that all of your previous Linux installations "
		    "will be destroyed.\n"
		    "\n"
		    "If you do not want to lose all of your Linux partitions, "
		    "select \"Cancel\" now, and perform a \"Custom\" install."
		);
			
		rc = newtWinChoice(_("Warning"), _("Ok"), _("Cancel"), message);
		if (rc == 2) return INST_CANCEL;

		rc = newtWinChoice(_("Warning"), _("Yes"), _("No"),
			_("You are about to lose data! Are you sure you want "
			  "to do this?"));
		if (rc != 2) break;
	    }

	    *runDruid = 0, rc = 0;
	} else {
	    newtWinMessage(_("Disk Space"), _("Ok"),
			    _("There is not enough disk space for this type "
				"of installation."));
	    return INST_CANCEL;
	}
    }

    if (*runDruid) {
	/* Put everything back */
	memset(spec, 0, sizeof(PartitionSpec));
	fdiskSetupPartitionSpec( hdarr, numhd, spec );
    } else {
	for (i=0; i<numhd; i++) {
	    *hdarr[i] = *tmparr[i];
	}
    }

    mayDisplay = !(*runDruid);

    return 0;
}
    
/* main program */
int FSEditPartitions(struct partitionTable * parts, struct fstab * fstab, 
		     char **drives,
		     struct intfInfo * intf, struct netInfo * netc,
		     struct driversLoaded ** dl, 
		     struct attemptedPartition * goals,
		     int flags) {
    HardDrive     *prestinehdarr[MAX_HARDDRIVES];
    HardDrive     *hdarr[MAX_HARDDRIVES]; /* ORIGINAL HD partition data    */
    unsigned int  numhd;                  /* total # of drives to consider */
    PartitionSpec spec;
    unsigned int  i;
    struct fstab  remotefstab;
    char ** deviceList;
    int rc, ourrc = 0;
    int numDrives;
    static int beenManual = 0;
    int runDruid = 0;
    int writeChanges = 0;
    static int where = 0;
    int done = 0;
    int dir;

    if (where == 0)
	dir = 1;
    else
	dir = -1;

    numDrives = 0;
    for (i = 0; drives[i]; i++, numDrives++);

    if (numDrives >= MAX_HARDDRIVES) {
	newtWinMessage(_("Too Many Drives"), _("Ok"),
		  _("You have more drives than this program supports. "
		    "Please use the standard fdisk program to setup your "
		    "drives and please notify Red Hat Software that you "
		    "saw this message."));
	return INST_ERROR;
    }
    
    deviceList = alloca(numDrives * sizeof(char *));

    for (i = 0; i < numDrives; i++) {
	deviceList[i] = alloca(15);
	strcpy(deviceList[i], "/tmp/");
	strcat(deviceList[i], drives[i]);

	if ((rc = devMakeInode(drives[i], deviceList[i]))) return rc;
    }
    
    /* Read in the partition tables from the requested drive(s) */
    /* first clear all entries                                  */
    memset(hdarr, 0, MAX_HARDDRIVES*sizeof(HardDrive *));
    rc = ReadDrives(deviceList, numDrives, hdarr, &numhd,0, 
		    flags & FSEDIT_READONLY);
    if (rc) return rc;

    if (numhd <1) {
	newtWinMessage(_("No Drives Found"), _("Ok"),
		  _("An error has occurred - no valid devices were found "
		    "on which to create new filesystems.  Please check "
		    "your hardware for the cause of this problem."));

	for (i = 0; i < numDrives; i++)
	    unlink(deviceList[i]);
	return INST_ERROR;
    }

    /* make backup of hdarr before user mucked with it */
    for (i=0; i<numhd; i++) {
	prestinehdarr[i] = (HardDrive *) alloca(sizeof(HardDrive));
	*prestinehdarr[i] = *hdarr[i];
    }
    
    /* Translate into a PartitionSpec */
    memset(&spec, 0, sizeof(PartitionSpec));
    fdiskSetupPartitionSpec( hdarr, numhd, &spec );

    /* copy the fstab they passed, we'll use it until we're done */
    /* if they dont cancel it will be new fstab                  */
    remotefstab = copyRemoteFSFstab( fstab );
    MergeFstabEntries( hdarr, numhd, &spec, fstab );

    if (flags & (FSEDIT_CLEARLINUX | FSEDIT_CLEARALL) && !beenManual) {

    if ((flags & FSEDIT_READONLY) || (!where && expert)) where = 1;
	rc = guessAtPartitioning(hdarr, numhd, &spec, &runDruid, dir, 
			    flags, goals ? goals : normalPartitioning);
	if (runDruid) 
	    return INST_CANCEL;
	else
	    writeChanges = 1;
    } else 
	beenManual = 1;

    if (beenManual) {
	/* Goto master screen */
	rc = StartMaster(hdarr, numhd, &spec,
			      &remotefstab, intf, netc, dl,
			      flags & FSEDIT_READONLY,
			      &writeChanges);
	if (rc == FDISK_ERR_USERABORT)
	    ourrc = INST_CANCEL;
	else if (rc) 
	    ourrc = INST_ERROR;
	else
	    ourrc = 0;
    }

    if (writeChanges) {

	/* Write partitions to disk */
	if (!testing) {
	    if (DisksChanged(prestinehdarr, hdarr, numhd)) {
		rc = 0;
		for (i=0; i<numhd; i++)
		    rc |= hdarr[i]->write_f(hdarr[i]);
		if (rc) needReboot();
	    }
	}

	if ((rc = findAllPartitions(NULL, parts))) {
	    
	    for (i=0; i<numhd; i++)
		fdiskCloseDevice(hdarr[i]);
	    
	    for (i = 0; i < numDrives; i++)
		unlink(deviceList[i]);

	    return INST_ERROR;
	}

	/* free up old fstab */
	if (fstab)
	    freeFstab(*fstab);
	PartitionSpecToFstab( hdarr, numhd, &spec, fstab );
	MergeRemoteFSFstab( &remotefstab, fstab );
	freeFstab(remotefstab);
    }

    for (i=0; i<numhd; i++)
	fdiskCloseDevice(hdarr[i]);

    for (i = 0; i < numDrives; i++)
	unlink(deviceList[i]);

    return ourrc;
}
