#include <alloca.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <newt.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>

#include "devices.h"
#include "hd.h"
#include "intl.h"
#include "install.h"
#include "log.h"
#include "scsi.h"

static int scsiChoicePanel(int * addSCSI);

static int scsiChoicePanel(int * addSCSI) {
    int rc;
    DIR * dir;
    struct dirent * ent;
    char buffer[4000];
    int foundSome;

    dir = opendir("/proc/scsi"), foundSome = 0;
    if (dir) {
	strcpy(buffer, _("I have found the following types of SCSI "
		"adapters on your system:\n\n"));
	errno = 0;
	while ((ent = readdir(dir))) {
	    if (*ent->d_name == '.') continue;
	    if (!strcmp("scsi", ent->d_name)) continue;

	    strcat(buffer, "\t");
	    strcat(buffer, getModuleDescription(ent->d_name));
	    strcat(buffer, "\n");
	    foundSome = 1;
	}

	closedir(dir);

	strcat(buffer, 
		_("\nDo you have any more SCSI adapters on your system?"));
    } 

    if (!foundSome) {
	strcpy(buffer, _("Do you have any SCSI adapters?"));
    }

    rc = newtWinTernary(_("SCSI Configuration"), _("No"), _("Yes"), 
			_("Back"), buffer);

    if (rc == 3)
	return INST_CANCEL;
    else if (rc == 2)
	*addSCSI = 1;
    else
	*addSCSI = 0;
 
    return 0;
}


int setupSCSIInterfaces(int forceConfig, struct driversLoaded ** dl,
			int automatic, int dir) {
    int rc;
    int hasscsi = 1;

    /* First of all, autodetect all the SCSI devices we can. */
    rc = loadDeviceDriver(DRIVER_SCSI, dl, DEVICE_JUSTPROBE | DEVICE_NOPAUSE);
    if (automatic) return dir < 0 ? INST_CANCEL : INST_OKAY;
    if (rc == INST_CANCEL) return INST_CANCEL;

#if defined(__sparc__) || defined(__alpha__)
    rc = dir < 0 ? INST_CANCEL : INST_OKAY;
#else
    /* There could be undetected (ISA) SCSI adapters sitting around
       still (or we could be in expert mode where we don't autodetect
       these things. So, give the user a chance for manual configuration
       now. */

    while (hasscsi) {
	if (forceConfig) {
	    forceConfig = 0;
	    hasscsi = 1;
	} else {
	    if (scsiChoicePanel(&hasscsi) == INST_CANCEL) return INST_CANCEL;
	}

	if (hasscsi) {
	    rc = loadDeviceDriver(DRIVER_SCSI, dl, DEVICE_NOPROBE);
	    if (rc == INST_ERROR) hasscsi = 0;	/* break out */
	} else {
	    rc = 0;
	}
    } 
#endif

    return rc;
}

int scsiDeviceAvailable(void) {
    int fd;
    char buf[80];
    int i;

    fd = open("/proc/scsi/scsi", O_RDONLY);
    if (fd < 0) {
	logMessage("failed to open /proc/scsi/scsi: %s", strerror(errno));
	return 0;
    }
    
    i = read(fd, buf, sizeof(buf) - 1);
    if (i < 1) {
	logMessage("failed to read /proc/scsi/scsi: %s", strerror(errno));
	return 0;
    }
    close(fd);
    buf[i] = '\0';

    logMessage("/proc/scsi/scsi: %s", buf);

    if (strstr(buf, "devices: none")) {
	logMessage("no scsi devices are available");
	return 0;
    }

    logMessage("scsi devices are available");
    return 1;
}

#define SCSISCSI_TOP	0
#define SCSISCSI_HOST 	1
#define SCSISCSI_VENDOR 2
#define SCSISCSI_TYPE 	3

int scsiGetDevices(struct deviceInfo ** sdiPtr) {
    int fd;
    char buf[16384];
    char linebuf[80];
    char typebuf[10];
    int i, state = SCSISCSI_TOP;
    char * start, * chptr, * next, *end;
    char driveName = 'a';
    char cdromNum = '0';
    char tapeNum = '0';
    int numMatches = 0;
    struct deviceInfo * sdi;
    int id = 0;

    /* FIXME: this should be big enough */
    sdi = malloc(sizeof(*sdi) * 65);

    fd = open("/proc/scsi/scsi", O_RDONLY);
    if (fd < 0) {
	logMessage("failed to open /proc/scsi/scsi: %s", strerror(errno));
	return 1;
    }
    
    i = read(fd, buf, sizeof(buf) - 1);
    if (i < 1) {
	logMessage("failed to read /proc/scsi/scsi: %s", strerror(errno));
	return 1;
    }
    close(fd);
    buf[i] = '\0';

    start = buf;
    while (*start) {
	chptr = start;
 	while (*chptr != '\n') chptr++;
	*chptr = '\0';
	next = chptr + 1;

	switch (state) {
	  case SCSISCSI_TOP:
	    if (strcmp("Attached devices: ", start)) {
		logMessage("unexpected line in /proc/scsi/scsi: %s", start);
		free(sdi);
		return INST_ERROR;
	    }
	    state = SCSISCSI_HOST;
	    break;

	  case SCSISCSI_HOST:
	    if (strncmp("Host: ", start, 6)) {
		logMessage("unexpected line in /proc/scsi/scsi: %s", start);
		free(sdi);
		return INST_ERROR;
	    }

	    start = strstr(start, "Id: ");
	    if (!start) {
		logMessage("Id: missing in /proc/scsi/scsi");
		return INST_ERROR;
	    }
	    start += 4;

	    id = strtol(start, NULL, 10);

	    state = SCSISCSI_VENDOR;
	    break;

	  case SCSISCSI_VENDOR:
	    if (strncmp("  Vendor: ", start, 10)) {
		logMessage("unexpected line in /proc/scsi/scsi: %s", start);
		free(sdi);
		return INST_ERROR;
	    }

	    start += 10;
	    end = chptr = strstr(start, "Model:");
	    if (!chptr) {
		logMessage("Model missing in /proc/scsi/scsi");
		free(sdi);
		return INST_ERROR;
	    }

	    chptr--;
	    while (*chptr == ' ') chptr--;
	    *(chptr + 1) = '\0';

	    strcpy(linebuf, start);
	    *linebuf = toupper(*linebuf);
	    chptr = linebuf + 1;
	    while (*chptr) {
		*chptr = tolower(*chptr);
		chptr++;
	    }

	    start = end;  /* beginning of "Model:" */
	    start += 7;
		
	    chptr = strstr(start, "Rev:");
	    if (!chptr) {
		logMessage("Rev missing in /proc/scsi/scsi");
		free(sdi);
		return INST_ERROR;
	    }
	   
	    chptr--;
	    while (*chptr == ' ') chptr--;
	    *(chptr + 1) = '\0';

	    strcat(linebuf, " ");
	    strcat(linebuf, start);

	    state = SCSISCSI_TYPE;

	    break;

	  case SCSISCSI_TYPE:
	    if (strncmp("  Type:", start, 7)) {
		logMessage("unexpected line in /proc/scsi/scsi: %s", start);
		free(sdi);
		return INST_ERROR;
	    }
	    *typebuf = '\0';
	    if (strstr(start, "Direct-Access")) {
		sprintf(typebuf, "sd%c", driveName++);
		sdi[numMatches].type = DEVICE_HD;
	    } else if (strstr(start, "Sequential-Access")) {
		sprintf(typebuf, "st%c", tapeNum++);
		sdi[numMatches].type = DEVICE_TAPE;
	    } else if (strstr(start, "CD-ROM")) {
		sprintf(typebuf, "scd%c", cdromNum++);
		sdi[numMatches].type = DEVICE_CDROM;
	    }

	    if (*typebuf) {
		/*sdi = realloc(sdi, sizeof(*sdi) * (numMatches + 2));*/
		sdi[numMatches].deviceName = strdup(typebuf);
		sdi[numMatches].info = strdup(linebuf);
		sdi[numMatches].bus = 0;
		sdi[numMatches++].id = id;
	    }

	    state = SCSISCSI_HOST;
	}

	start = next;
    }

    sdi[numMatches].deviceName = NULL;
    sdi[numMatches].info = NULL;

    sdi = realloc(sdi, sizeof(*sdi) * (numMatches + 1));

    *sdiPtr = sdi;

    return 0;
}

int ideGetDevices(struct deviceInfo ** idiPtr) {
    struct deviceInfo * idi;
    struct stat sb;
    char * filename;
    char * buf, * end;
    char * absend;
    int numMatches = 0;
    int fd;
    int base = testing ? 0 : 3;

    idi = malloc(sizeof(*idi) * 9);

    if (!access("/proc/ide", R_OK)) {
    	/* Great. 2.2 kernel, things are much easier and less error prone. */
    	char b[20];
    	FILE *f;
    	
    	strcpy(b, "/proc/ide/hda");
    	buf = alloca (256);
    	/* Maybe we should use readdir here instead... */
    	for (; b[12] <= 'm'; b[12]++) {
    	    b[13] = '\0';
    	    if (access(b, R_OK))
		continue;
	    strcpy(b + 13, "/media");
	    f = fopen(b, "r");
	    if (!f) {
		newtWinMessage("Error", "Ok", "Failed to open %s: %s\n", b,
				strerror(errno));
		return INST_ERROR;
	    }
	    fgets(buf, 256, f);
	    fclose(f);
	    if (!strcmp(buf, "disk\n"))
	    	idi[numMatches].type = DEVICE_HD;
	    else if (!strcmp(buf, "cdrom\n"))
	    	idi[numMatches].type = DEVICE_CDROM;
	    else if (!strcmp(buf, "tape\n"))
	    	idi[numMatches].type = DEVICE_TAPE;
	    else if (!strcmp(buf, "floppy\n"))
	    	idi[numMatches].type = DEVICE_FD;
	    else
	    	continue;
	    idi[numMatches].deviceName = malloc(4);
	    strcpy(idi[numMatches].deviceName, "hda");
	    idi[numMatches].deviceName[2] = b[12];
	    strcpy(b + 13, "/model");
	    f = fopen(b, "r");
	    if (!f)
	    	idi[numMatches].info = strdup("(none)");
	    else {
	    	fgets(buf, 256, f);
	    	fclose(f);
	    	end = strchr (buf, '\n');
	    	if (end) *end = '\0';
	    	idi[numMatches].info = strdup(buf);
	    }
	    idi[numMatches].bus = (b[12] - 'a') / 2;
	    idi[numMatches].id = (b[12] - 'a') % 2;
	    numMatches++;
    	}
    } else {
/* normal string handling doesn't work here as the syslog can contain 
   binary 0's! */
	if (!access("/var/log/dmesg", R_OK))
	    filename = "/var/log/dmesg";
	else
	    filename = "/tmp/syslog";
    
	if (stat(filename, &sb)) {
	    newtWinMessage("Error", "Ok", "Failed to stat %s: %s\n", filename,
			    strerror(errno));
	    return 0;
	}
    
	if ((fd = open(filename, O_RDONLY)) < 0) {
	    newtWinMessage("Error", "Ok", "Failed to open %s: %s\n", filename,
			    strerror(errno));
	    return INST_ERROR;
	}
       
	buf = alloca(sb.st_size);
	read(fd, buf, sb.st_size);
	close(fd);
    
	absend = buf + sb.st_size;
    
	while (buf && buf < absend) {
	    if (buf[0 + base] == 'h' && buf[1 + base] == 'd' && 
		    buf[3 + base] == ':') {
		idi[numMatches].deviceName = malloc(4);
		strcpy(idi[numMatches].deviceName, "hda");
		idi[numMatches].deviceName[2] = buf[2 + base];
    
		buf += 5 + base;
		end = buf;
		while (*end != '\n' && *end != ',') end++;
    
		if (*end == ',') {
		    idi[numMatches].info = malloc(end - buf + 1);
		    strncpy(idi[numMatches].info, buf, end - buf);
		    idi[numMatches].info[end - buf] = '\0';
    
		    /* see if this is a cdrom or not */
		    while (*end != '\n' && 
				strncmp(end, "CDROM", 5) &&
				strncmp(end, "TAPE", 4) &&
				strncmp(end, "FLOPPY", 6) &&
				strncmp(end, "CHS", 3))
				end++;
    
		    if (!strncmp(end, "CDROM", 5)) 
			idi[numMatches].type = DEVICE_CDROM;
		    else if (!strncmp(end, "TAPE", 4)) 
			idi[numMatches].type = DEVICE_TAPE;
		    else if (!strncmp(end, "FLOPPY", 6)) 
			idi[numMatches].type = DEVICE_FD;
		    else if (!strncmp(end, "CHS", 3)) 
			idi[numMatches].type = DEVICE_HD;
		    else
			idi[numMatches].type = DEVICE_NONE;
    
		    /* we could do better here */
		    idi[numMatches].bus = idi[numMatches].id = 0;
    
		    if (idi[numMatches].type != DEVICE_NONE)
			numMatches++;
		}
	    }
    
	    end = memchr(buf, '\n', absend - buf);
	    if (!end)
	       buf = NULL;
	    else
	       buf = end + 1;
	}
    }
       
    idi[numMatches].deviceName = NULL;
    idi[numMatches].info = NULL;

    idi = realloc(idi, sizeof(*idi) * (numMatches + 1));

    *idiPtr = idi;

    return 0;
}

int CompaqSmartArrayDeviceAvailable(void) {
    int fd;

    fd = open("/proc/array/ida0", O_RDONLY);
    if (fd < 0) {
	return 0;
    }
    close (fd);
    logMessage("Compaq Smart Array controllers available");

    return 1;
}

int CompaqSmartArrayGetDevices(struct deviceInfo ** idiPtr) {
    struct deviceInfo * idi;
    FILE *f;
    char buf[256];
    char *ptr;
    int numMatches = 0, ctlNum = 0;
    char ctl[20];
    
    idi = malloc(sizeof(*idi) * 30);
    sprintf(ctl, "/proc/array/ida%d", ctlNum++);
		
    while ((f = fopen(ctl, "r"))){
	while (fgets(buf, sizeof(buf) - 1, f)) {
	    if (!strncmp(buf, "ida/", 4)) {
		ptr = strchr(buf, ':');
		*ptr = '\0';
		idi[numMatches].deviceName = strdup(buf);
		idi[numMatches].info = strdup("Compaq RAID logical disk");
		idi[numMatches++].type = DEVICE_HD;
	    }
	}
	sprintf(ctl, "/proc/array/ida%d", ctlNum++);
    }
    idi[numMatches].deviceName = NULL;
    idi[numMatches].info = NULL;
    
    idi = realloc(idi, sizeof(*idi) * (numMatches + 1));
    
    *idiPtr = idi;
    
    return 0;
}

int dac960GetDevices(struct deviceInfo ** idiPtr) {
    struct deviceInfo * idi = NULL;
    const char *filename;
    char buf[256];
    char *ptr, *iptr;
    int numMatches = 0;
    int numIDIs = 0;
    FILE* f;

    if (!access("/var/log/dmesg", R_OK))
	filename = "/var/log/dmesg";
    else
	filename = "/tmp/syslog";

    if (!(f = fopen(filename, "r"))) {
	newtWinMessage("Error", "Ok", "Failed to open %s: %s\n", filename,
			strerror(errno));
	return INST_ERROR;
    }

    /* We are looking for lines of this format:
DAC960#0:     /dev/rd/c0d0: RAID-7, Online, 17928192 blocks, Write Thru
0123456790123456789012
    */
    buf [sizeof(buf) - 1] = '\0';
    while (fgets(buf, sizeof(buf) - 1, f)) {
	ptr = strstr (buf, "/dev/rd/");
	if (ptr) {
	    iptr = strchr (ptr, ':');
	    if (iptr) {
		if (idi == NULL) {
		    numIDIs = 16;
		    idi = malloc(sizeof(*idi) * numIDIs);
		}
		else if (numMatches >= numIDIs) {
		    numIDIs += 16;
		    idi = realloc (idi, sizeof(*idi) * numIDIs);
		}

		memset (&idi [numMatches], 0, sizeof (*idi));

		/* put a NULL at the ':' */
		*iptr = '\0';
		idi[numMatches].deviceName = strdup(ptr + 5);

		ptr = iptr;
		while (*ptr != ',')
		    ptr++;
		*ptr = '\0';
		idi[numMatches].info = strdup(iptr + 2);
		idi[numMatches].type = DEVICE_HD;

		logMessage("DAC960: %s: %s", idi[numMatches].deviceName,
			   idi[numMatches].info);

		numMatches++;
	    }
	}
    }

    if (numMatches > 0) {
	idi[numMatches].deviceName = NULL;
	idi[numMatches].info = NULL;

	idi = realloc(idi, sizeof(*idi) * (numMatches + 1));
    }

    *idiPtr = idi;

    return 0;
}
