#include <ctype.h>
#include <fcntl.h>
#include <linux/hdreg.h>
#include <linux/fs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/wait.h>
#include <unistd.h>

#include "hd.h"
#include "install.h"
#include "log.h"
#include "newt.h"
#include "run.h"
#include "scsi.h"
#include "windows.h"

extern int testing;

struct fdiskTag {
    int tag;
    int type;
} ;

#if defined(__sparc__)
const struct fdiskTag fdiskTags[] =  {
	{ 0x05,	PART_IGNORE },
	{ 0x82,	PART_SWAP },
	{ 0x83,	PART_EXT2 },
	{ 0, 0 },
};
#else
const struct fdiskTag fdiskTags[] =  {
	{ 0x01,	PART_DOS },
	{ 0x04,	PART_DOS },
	{ 0x05,	PART_IGNORE },
	{ 0x06,	PART_DOS },
	{ 0x07,	PART_HPFS },
	{ 0x82,	PART_SWAP },
	{ 0x83,	PART_EXT2 },
	{ 0, 0 },
};
#endif

struct hd {
    char * device;
    int major, minor;
};

struct hd static possibleDrives[] = {
#ifndef __sparc__
    { "hda",	3,	0 },
    { "hdb",	3,	64 },
    { "hdc",	22,	0 },
    { "hdd",	22,	64 },
    { "hde",	33,	0 },
    { "hdf",	33,	64 },
    { "hdg",	34,	0 },
    { "hdh",	34,	64 },
#endif
    { "sda",	8,	0 },
    { "sdb",	8,	16 },
    { "sdc",	8,	32 },
    { "sdd",	8,	48 },
    { "sde",	8,	64 },
    { "sdf",	8,	80 },
    { "sdg",	8,	96 },
    { "sdh",	8,	112 },

}; 

static void parseLabelLine(char * device, char * start, int * numPartitions, 
			   struct partition partitions[15]) {
    char * str = start;
    char * chptr;
    char devbuf[2];

    if (*str++ != ' ') return;
    if (*str++ != ' ') return;

    if (*str < 'a' || *str > 'h') return;
    if (*(str + 1) != ':') return;

    strcpy(partitions[*numPartitions].device, device);
    devbuf[0] = *str - 'a' + '1';
    devbuf[1] = '\0';
    strcat(partitions[*numPartitions].device, devbuf);
    str += 2;

    while (*str && isspace(*str)) str++;
    if (!*str) return;

    partitions[*numPartitions].size = strtol(str, &chptr, 10);
    partitions[*numPartitions].size /= 2;	/* we want 1k blocks */
    if (!chptr || !isspace(*chptr)) return;
    str = chptr;

    while (isspace(*str)) str++;
    if (!*str) return;

    partitions[*numPartitions].begin = strtol(str, &chptr, 10);
    if (!chptr || !isspace(*chptr)) return;
    str = chptr;

    partitions[*numPartitions].end = partitions[*numPartitions].size * 2 +
					partitions[*numPartitions].begin;

    while (isspace(*str)) str++;
    if (!*str) return;

    if (!strncmp(str, "ext2", 4)) {
	partitions[*numPartitions].type = PART_EXT2;
	strcpy(partitions[*numPartitions].tagName, "Linux native");
    } else if (!strncmp(str, "swap", 4)) {
	partitions[*numPartitions].type = PART_SWAP;
	strcpy(partitions[*numPartitions].tagName, "Linux swap");
    } else {
	partitions[*numPartitions].type = PART_OTHER;
	strcpy(partitions[*numPartitions].tagName, "Other");
    }
    
    partitions[*numPartitions].bootLabel = NULL;

    logMessage("found partition %s", partitions[*numPartitions].device);

    (*numPartitions)++;
}

static void parseFdiskLine(char * device, char * start, int * numPartitions, 
			   struct partition partitions[15]) {
    int tag, i;
    char * str = start;
    char * chptr;

    if (*str != '/') return;

    if (strlen(str) < 60) return;

    /* grab the partition number */
    str += 8;
    chptr = str;
    
    if (!isdigit(*chptr)) return;
    while (*chptr && isdigit(*chptr)) chptr++;
    if (!*chptr) return;

    *chptr = '\0';
    strcpy(partitions[*numPartitions].device, device);
    strcat(partitions[*numPartitions].device, str);
    str = chptr + 1;

    /* go to the "Begin" field, skipping the '*' which marks a bootable
       partition and the 'u|r' fields which can occur on a Sun disklabel */
    while (*str && (isspace(*str) || *str == '*' || *str == 'u' ||
	    *str == 'r')) str++;
    if (!*str) return;

    /* skip the Begin: field (but make sure it's a number */
    if (!isdigit(*str)) return;
    while (isdigit(*str)) str++;
    if (!*str || !isspace(*str)) return;

    /* go to the first digit in the "Start" field */
    while (isspace(*str)) str++;
    if (!*str) return;

    partitions[*numPartitions].begin = strtol(str, &chptr, 10);
    if (!chptr || !isspace(*chptr)) return;
    str = chptr;

    /* go to the first digit in the "End" field */
    while (isspace(*str)) str++;
    if (!*str) return;

    partitions[*numPartitions].end = strtol(str, &chptr, 10);
    if (!chptr || !isspace(*chptr)) return;
    str = chptr;

    /* go to the first digit in the "Size" field */
    while (isspace(*str)) str++;
    if (!*str) return;

    partitions[*numPartitions].size = strtol(str, &chptr, 10);
    if (!chptr || (*chptr != '-' && *chptr != '+' && !isspace(*chptr)))
	return;

    str = chptr + 1;

    /* go to the first digit in the "Tag" field */
    while (isspace(*str)) str++;
    if (!*str) return;
    tag = strtol(str, &chptr, 16);

    partitions[*numPartitions].type = PART_OTHER;
    for (i = 0; fdiskTags[i].tag; i++) {
	if (fdiskTags[i].tag == tag) {
	    partitions[*numPartitions].type = fdiskTags[i].type;
	    break;
	}
    }
    
    str = chptr;
    while (isspace(*str)) str++;
    if (!*str) return;

    strcpy(partitions[*numPartitions].tagName, str);
    partitions[*numPartitions].bootLabel = NULL;

    logMessage("found partition %s", partitions[*numPartitions].device);

    (*numPartitions)++;
}

static int findPartitions(struct hd hd, int * numPartitions, 
			  struct partition partitions[15]) {
    char devBuf[20];
    int fd;
    char * fdiskOutput;
    char * fdiskArgs[] = { NULL, NULL, NULL };
    char * start, * end;
    int oldTesting;
    int len;
    char * cmd;
    struct hd_geometry geo;
    int labelMode = 0;
    #if defined(__alpha__)
	unsigned short magic;
	unsigned int fdiskMagic = 0xAA55;
    #endif

    if (testing)
	cmd = "/sbin/fdisk";
    else
	cmd = "/usr/bin/fdisk";

    fdiskArgs[0] = cmd;

    *numPartitions = 0;

    /* don't bother with any of this if the main device doesn't exist 
       or doesn't look like a hard drive */
    sprintf(devBuf, "/tmp/%s", hd.device);
    mknod(devBuf, S_IFBLK | 0600, makedev(hd.major, hd.minor));
    fd = open(devBuf, O_RDONLY);
    if (fd < 0) {
	unlink(devBuf);
	return 0;
    }

    /* make sure this isn't an IDE CD or tape drive */
    if (ioctl(fd, HDIO_GETGEO, &geo)) {
	logMessage("/dev/%s doesn't look like a hard drive", hd.device);
	unlink(devBuf);
	close(fd);
	return 0;
    }

    #if defined(__alpha__)
	/* is this a labeled disk? */
	/* This test used to just look for the disklabel magic, but that 
	   got left around on fdisks drives much of the time. This check
	   seems more reliable */
	lseek(fd, 510, SEEK_SET);
	if (read(fd, &magic, sizeof(magic)) == sizeof(magic))
	    labelMode = (magic != fdiskMagic);

	if (labelMode) 
	    logMessage("/dev/%s is disklabeled", hd.device);
	else
	    logMessage("/dev/%s is fdisked", hd.device);
    #endif

    close(fd);

    oldTesting = testing;
    testing = 0;
    fdiskArgs[1] = devBuf;
    if (labelMode)
	runProgramIO(RUN_NOLOG, cmd, fdiskArgs, "b\np\nq\n", &fdiskOutput);
    else
	runProgramIO(RUN_NOLOG, cmd, fdiskArgs, "p\nq\n", &fdiskOutput);
    unlink(devBuf);
    testing = oldTesting;

    start = fdiskOutput;
    len = strlen(start);
    while (((start - fdiskOutput) < len) && (end = strchr(start, '\n'))) {
	*end = '\0';
	if (labelMode)
	    parseLabelLine(hd.device, start, numPartitions, partitions);
	else
	    parseFdiskLine(hd.device, start, numPartitions, partitions);
	
	start = end + 1;
    }
    free(fdiskOutput);

    return 0;
}

int findAllPartitions(struct partitionTable * table) {
    int numPartitions;
    int i;
    struct partition parts[16];
    struct partition * allParts = NULL;
    int numAllParts = 0;

    winStatus(30, 3, "Hard Drives", "Scanning hard drives...");

    for (i = 0; i < (sizeof(possibleDrives) / sizeof(*possibleDrives)); i++) {
	findPartitions(possibleDrives[i], &numPartitions, parts);
	if (!numPartitions) continue;
	
	if (!numAllParts) {
	    numAllParts = numPartitions;
	    allParts = malloc(sizeof(*allParts) * numAllParts);
	    memcpy(allParts, parts, sizeof(*allParts) * numAllParts);
	} else {
	    allParts = realloc(allParts, sizeof(*allParts) * 
				(numAllParts + numPartitions));
	    memcpy(allParts + numAllParts, parts, 
		   sizeof(*allParts) * numPartitions);
	    numAllParts += numPartitions;
	}
    }

    table->count = numAllParts;
    table->parts = allParts;

    newtPopWindow();

    return 0;
}

int partitionDrives(void) {
    int drivesPresent[sizeof(possibleDrives) / sizeof(*possibleDrives)];
    int numDrivesPresent = 0;
    int childpid;
    int i, fd, rc;
    int haveEdited = 0;
    char devBuf[100], idBuf[3];
    newtComponent cancel, done, edit, text, listbox, f, answer, okay, form;
    struct hd_geometry geo;
    struct hd * currhd;
    char * cmd;
    int status;
    struct scsiDeviceInfo * sdi, * thissd;
    int reboot = 0;
    struct hd_driveid ideids[sizeof(possibleDrives) / sizeof(*possibleDrives)];

    if (testing)
	cmd = "/sbin/fdisk";
    else
	cmd = "/usr/bin/fdisk";

    for (i = 0; i < sizeof(possibleDrives) / sizeof(*possibleDrives); i++) {
	sprintf(devBuf, "/tmp/%s", possibleDrives[i].device);
	mknod(devBuf, S_IFBLK | 0600, 
		    makedev(possibleDrives[i].major, possibleDrives[i].minor));
	fd = open(devBuf, O_RDONLY);
	unlink(devBuf);
	if (fd < 0) {
	    continue;
	}

	logMessage("successfully opened: %s", devBuf);

	if (possibleDrives[i].device[0] == 'h') {
	    /* make sure this isn't an IDE CD or tape drive */
	    if (ioctl(fd, HDIO_GETGEO, &geo)) {
		logMessage("\tHDIO_GETGEO ioctl failed - probably cd or tape");
		close(fd);
		continue;
	    } else {
		if (ioctl(fd, HDIO_GET_IDENTITY, &ideids[numDrivesPresent])) {
		    ideids[numDrivesPresent].model[0] = '\0';
		}
	    }
	}

	close(fd);

	drivesPresent[numDrivesPresent++] = i;
    }

    if (!numDrivesPresent) {
	newtOpenWindow(18, 6, 44, 11, "Setup Swap");
	text = newtTextbox(1, 1, 42, 4, NEWT_TEXTBOX_WRAP);
	newtTextboxSetText(text, "You don't have any hard drives available! "
				 "You probably forgot to configure a SCSI "
				 "controller.");
	
	okay = newtButton(17, 7, "Ok");

	form = newtForm(NULL, NULL, 0);
	newtFormAddComponents(form, text, okay, NULL);
	
	newtRunForm(form);
	newtFormDestroy(form);
	newtPopWindow();

	return INST_ERROR;
    }

    if (scsiDeviceAvailable()) {
        if ((rc = scsiGetDevices(&sdi))) return rc;
    } else {
	sdi = NULL;
    }

    newtOpenWindow(10, 3, 60, 17, "Partition Disks");
    text = newtTextbox(1, 1, 56, 5, NEWT_TEXTBOX_WRAP);
#ifdef __i386__
    newtTextboxSetText(text, 
			"To install Red Hat Linux, you must have at least "
			"one parition of 50 MB dedicated to Linux. We suggest "
			"placing that partition on one of the first two hard "
			"drives in your system so you can boot into Linux "
			"with LILO.");
#else
    newtTextboxSetText(text, 
			"To install Red Hat Linux, you must have at least "
			"one parition of 50 MB dedicated to Linux.");
#endif
    
    listbox = newtListbox(10, 7, numDrivesPresent < 5 ? numDrivesPresent : 5,
			  (numDrivesPresent <= 5 ? NEWT_FLAG_NOSCROLL : 0) |
			      NEWT_LISTBOX_RETURNEXIT);
    
    for (i = 0; i < numDrivesPresent; i++) {
	sprintf(devBuf, "/dev/%s", possibleDrives[drivesPresent[i]].device);

	if (sdi && !strncmp(possibleDrives[drivesPresent[i]].device, "sd", 2))
	{
	    thissd = sdi;
	    while (thissd->info &&
		   strcmp(thissd->deviceName, 
			  possibleDrives[drivesPresent[i]].device)) thissd++;
	    if (thissd->info) {
		sprintf(idBuf, "%d", thissd->id);
		strcat(devBuf, " - SCSI ID ");
		strcat(devBuf, idBuf);
		strcat(devBuf, " ");
		strcat(devBuf, thissd->info);
	    } 
	} else if (ideids[i].model[0]) {
		strcat(devBuf, " - Model ");
		strcat(devBuf, ideids[i].model);
	}

	newtListboxAddEntry(listbox, devBuf, possibleDrives + drivesPresent[i]);
    }

    done = newtButton(7, 13, "Done");
    edit = newtButton(24, 13, "Edit");
    cancel = newtButton(41, 13, "Cancel");

    f = newtForm(NULL, NULL, 0);
    newtFormAddComponents(f, text, listbox, done, edit, cancel, NULL);
    newtFormSetCurrent(f, done);
  
    do {
	answer = newtRunForm(f);
	
	if (answer == edit || answer == listbox) {
	    haveEdited = 1;
	    currhd = newtListboxGetCurrent(listbox);

	    sprintf(devBuf, "/tmp/%s", currhd->device);
	    mknod(devBuf, S_IFBLK | 0600, 
			makedev(currhd->major, currhd->minor));

	    newtPopWindow();
	    newtSuspend();
	    for (i = 0; i < 25; i++) puts("");
	    printf("This is the fdisk program for partitioning your drive. It "
		   "is running\non /dev/%s.\n\n", currhd->device);

	    logMessage("running fdisk on %s", devBuf);
	    
	    if (!(childpid = fork())) {
		execl(cmd, cmd, devBuf, NULL);
	 	return -1;
	    }

	    waitpid(childpid, &status, 0);

	    newtResume();
	    newtOpenWindow(10, 3, 60, 17, "Partition Disks");
	}
    } while (answer != done && answer != cancel && answer != f);

    newtFormDestroy(f);
    newtPopWindow();

    if (haveEdited) {
	for (i = 0; i < numDrivesPresent; i++) {
	    sprintf(devBuf, "/tmp/%s", possibleDrives[drivesPresent[i]].device);
	    mknod(devBuf, S_IFBLK | 0600, 
			makedev(possibleDrives[drivesPresent[i]].major, 
				possibleDrives[drivesPresent[i]].minor)); 
	    fd = open(devBuf, O_RDONLY);
	    unlink(devBuf);
	    if (fd < 0) reboot = 1;

	    if (ioctl(fd, BLKRRPART, 0)) reboot = 1;
	    close(fd);
	}
    }

    if (reboot) {
	winMessage(14, 6, 52, 12, "Reboot Needed",
			"The kernel is unable to read your new partitioning "
			"information, probably because you modified extended "
			"partitions. While this is not critical, you must "
			"reboot your machine before proceeding. Insert the "
			"Red Hat boot disk now and press Return to reboot "
			"your system.");
	newtFinished();
	exit(0);
    }

    if (answer == cancel)
	return INST_CANCEL;

    return 0;
}