#include <ctype.h>
#include <fcntl.h>
#include <linux/hdreg.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 "intl.h"
#include "log.h"
#include "newt.h"
#include "run.h"
#include "scsi.h"
#include "windows.h"

extern int testing;

/* shame we can't get this from any header files */
#define BLKRRPART  _IO(0x12,95)	/* re-read partition table */

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 },
	{ 0x0b,	PART_FAT32 },
	{ 0x82,	PART_SWAP },
	{ 0x83,	PART_EXT2 },
	{ 0, 0 },
};
#endif

#define MAX_NUM_DRIVES 40

static int findDrivesPresent(struct deviceInfo * drives, int * numPtr);

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;
    partitions[*numPartitions].defaultBoot = 0;

    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;
    partitions[*numPartitions].defaultBoot = 0;

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

    (*numPartitions)++;
}

static int findPartitions(char * hdname, 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;
    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", hdname);
    
    if (devMakeInode(hdname, devBuf)) {
	return 0;
    }

    fd = open(devBuf, O_RDONLY);
    if (fd < 0) {
	unlink(devBuf);
	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", hdname);
	else
	    logMessage("/dev/%s is fdisked", hdname);
    #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(hdname, start, numPartitions, partitions);
	else
	    parseFdiskLine(hdname, start, numPartitions, partitions);
	
	start = end + 1;
    }
    free(fdiskOutput);

    return 0;
}

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

    if (!devices) {
	/* FIXME: memory leak for internal structures */
	devices = alloca(sizeof(*devices) * MAX_NUM_DRIVES);
	if (findDrivesPresent(devices, NULL)) return INST_ERROR;
    }

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

    for (i = 0; devices[i].deviceName; i++) {
	findPartitions(devices[i].deviceName, &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;
}

static int findDrivesPresent(struct deviceInfo * drives, int * numPtr) {
    struct deviceInfo * scsi = NULL, * ide = NULL;
    int num = 0; 
    int i;

    /* FIXME: this results in memory leaks via the strings inside of
       the decice structures! */

    if (scsiDeviceAvailable()) {
	if (scsiGetDevices(&scsi)) return INST_ERROR;
    }

    if (ideGetDevices(&ide)) return INST_ERROR;

    i = 0, num = 0;
    while (ide[i].deviceName) {
	if (ide[i].type == DEVICE_HD) 
	    drives[num++] = ide[i];
	i++;
    }

    i = 0;
    while (scsi && scsi[i].deviceName) {
	if (scsi[i].type == DEVICE_HD) 
	    drives[num++] = scsi[i];
	i++;
    }

    drives[num].deviceName = NULL;

    if (numPtr) *numPtr = num;

    return 0;
}

int getDriveList(char *** drives, int * num) {
    struct deviceInfo drivesPresent[MAX_NUM_DRIVES];
    int rc;
    int i;

    if ((rc = findDrivesPresent(drivesPresent, num))) return rc;

    *drives = malloc(sizeof(char *) * (*num + 1));

    for (i = 0; i < *num; i++) {
	(*drives)[i] = drivesPresent[i].deviceName;
    }

    (*drives)[i] = NULL;

    return 0;
}
    
int partitionDrives(void) {
    struct deviceInfo drivesPresent[MAX_NUM_DRIVES];
    int numDrivesPresent = 0;
    int childpid;
    int i, fd;
    int haveEdited = 0;
    char devBuf[100], idBuf[3];
    char * driveText[50];
    struct deviceInfo * currhd;
    int status;
    int reboot = 0;
    char * cmd;
    char * text;
    int rc, driveNum = 0;
 
    if (testing)
	cmd = "/sbin/fdisk";
    else
	cmd = "/usr/bin/fdisk";

    findDrivesPresent(drivesPresent, &numDrivesPresent);

    if (!numDrivesPresent) {
	newtWinMessage(_("Hard Drives"), _("Ok"), 
			   _("You don't have any hard drives available! "
			     "You probably forgot to configure a SCSI "
			     "controller."));

	return INST_ERROR;
    }

#ifdef __i386__
    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
    text = _("To install Red Hat Linux, you must have at least one parition "
	     "of 50 MB dedicated to Linux.");
#endif

    for (i = 0; i < numDrivesPresent; i++) {
	sprintf(devBuf, "/dev/%s", drivesPresent[i].deviceName);

	if (!strncmp(drivesPresent[i].deviceName, "sd", 2)) {
	    sprintf(idBuf, "%d", drivesPresent[i].id);
	    strcat(devBuf, " - SCSI ID ");
	    strcat(devBuf, idBuf);
	} 

	strcat(devBuf, " - Model ");
	strcat(devBuf, drivesPresent[i].info);

	/* truncate at 50 columns for now */
	devBuf[50] = '\0';

	driveText[i] = alloca(strlen(devBuf));
	strcpy(driveText[i], devBuf);
    }
    driveText[i] = NULL;

    do {
	rc = newtWinMenu(_("Partition Disks"), text, 56, 5, 6, 5,
			 driveText, &driveNum, _("Done"), _("Edit"),
			 _("Back"), NULL);

	if (rc == 2) {
	    haveEdited = 1;
	    currhd = drivesPresent + driveNum;

	    sprintf(devBuf, "/tmp/%s", currhd->deviceName);
	    if (devMakeInode(currhd->deviceName, devBuf)) return 0;

	    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->deviceName);

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

	    waitpid(childpid, &status, 0);

	    newtResume();
	}
    } while (rc == 2);

    if (haveEdited) {
	for (i = 0; i < numDrivesPresent; i++) {
	    sprintf(devBuf, "/tmp/%s", drivesPresent[i].deviceName);
	    if (devMakeInode(drivesPresent[i].deviceName, devBuf)) 
		return INST_ERROR;
	    fd = open(devBuf, O_RDONLY);
	    unlink(devBuf);
	    if (fd < 0) reboot = 1;

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

    if (reboot) {
	newtWinMessage("Reboot Needed", "Ok",
			"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.\n\n"
			"If you have a ZIP or JAZ drive, make sure there is "
			"a disk in the drive as an empty SCSI drive can also "
			"cause this problem.");
	newtFinished();
	exit(0);
    }

    if (rc == 3)
	return INST_CANCEL;

    return 0;
}
