#include <alloca.h>
#include <errno.h>
#include <fcntl.h>
#include <newt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/mtio.h>
#include <unistd.h>

#include "hd.h"
#include "fs.h"
#include "ftp.h"
#include "install.h"
#include "log.h"
#include "methods.h"
#include "net.h"
#include "scsi.h"
#include "windows.h"

struct ftpinfo {
    char * address;
    char * login;
    char * password;
    char * prefix;
    char * proxy;
    int sock;
    struct pkgSet ps;
};

struct hdinfo {
    char * device;
    char * type;
    char * dir;
};

struct tapeCatalogEntry {
    char * filename;
    int size;
};

struct tapeinfo {
    int fd;
    int offset;
    int curr;
    int catalogEntries;
    struct tapeCatalogEntry * catalog;
};

/* This was split into two pieces to keep the initial install program small */

static int ftpinstStartTransfer(struct ftpinfo * fi, char * filename);
static int ftpinstFinishTransfer(struct ftpinfo * fi);
static int ftpinstGetMappedFile(struct installMethod * method, char * name, 
		                char ** realName, int isPreskel);
static int imageGetFile(struct installMethod * method, char * name, 
			char ** realName, int isPreskel);
static int singleimageSetSymlinks(struct installMethod * method, 
	              		  struct partitionTable table,
	              		  struct netConfig * netc, 
				  struct netInterface * intf,
				  struct driversLoaded ** dl);
static int smbSetup(struct installMethod * method, struct partitionTable table,
	           struct netConfig * netc, struct netInterface * intf,
	           struct driversLoaded ** dl);
static int hdSetup(struct installMethod * method, struct partitionTable table,
	           struct netConfig * netc, struct netInterface * intf,
	           struct driversLoaded ** dl);
static int hdPrepareMedia(struct installMethod * method,
				struct fstab * fstab);
static int hdGetPackageSet(struct installMethod * method,
			     struct pkgSet * ps);
static int hdGetComponentSet(struct installMethod * method,
			       struct pkgSet * ps,
			       struct componentSet * cs);
static int ftpSetup(struct installMethod * method, struct partitionTable table,
	            struct netConfig * netc, struct netInterface * intf,
	            struct driversLoaded ** dl);
static int ftpGetPackageSet(struct installMethod * method,
			     struct pkgSet * ps);
static int ftpGetComponentSet(struct installMethod * method,
			       struct pkgSet * ps,
			       struct componentSet * cs);
static int imageGetPackageSet(struct installMethod * method,
			      struct pkgSet * ps);
static int imageGetComponentSet(struct installMethod * method,
			     struct pkgSet * ps,
			     struct componentSet * cs);
static int tapeSetup(struct installMethod * method, struct partitionTable table,
	             struct netConfig * netc, struct netInterface * intf,
	             struct driversLoaded ** dl);
static int tapeGetPackageSet(struct installMethod * method,
			     struct pkgSet * ps);
static int tapeGetComponentSet(struct installMethod * method,
			     struct pkgSet * ps,
			     struct componentSet * cs);
static int tapeGetMappedFile(struct installMethod * method, char * name, 
		                char ** realName, int isPreskel);
static int smbGetSetup(char ** hostptr, char ** dirptr, char ** acctptr,
			char ** pwptr);
static int smbGetPackageSet(struct installMethod * method,
			     struct pkgSet * ps);
static int smbGetComponentSet(struct installMethod * method,
			       struct pkgSet * ps,
			       struct componentSet * cs);

static struct installMethod methods[] = {
    { "Local CDROM", 		"cdrom", 0, NULL, singleimageSetSymlinks, 
		imageGetFile, 
		imageGetPackageSet, imageGetComponentSet, NULL, NULL },
    { "NFS image", 		"nfs", 0, NULL, singleimageSetSymlinks, 
		imageGetFile, 
		imageGetPackageSet, imageGetComponentSet, NULL, NULL },
    { "hard drive",		"hd",  0, NULL, hdSetup, imageGetFile,
		hdGetPackageSet, hdGetComponentSet, hdPrepareMedia, NULL },
    { "FTP",			"ftp", 1, NULL, ftpSetup, ftpinstGetMappedFile,
		ftpGetPackageSet, ftpGetComponentSet, NULL, NULL },
#ifdef __i386__
    { "SMB image", 		"smb", 0, NULL, smbSetup, 
		imageGetFile, 
		smbGetPackageSet, smbGetComponentSet, NULL, NULL },
#endif
    { "SCSI Tape", 		"tape", 1, NULL, tapeSetup, tapeGetMappedFile,
		tapeGetPackageSet, tapeGetComponentSet, NULL },
} ;
static int numMethods = sizeof(methods) / sizeof(struct installMethod);

struct installMethod * findInstallMethod(char * argptr) {
    int i;

    for (i = 0; i < numMethods; i++) 
	if (!strcmp(argptr, methods[i].abbrev)) return (methods + i);

    return NULL;
}

static int imageGetFile(struct installMethod * method, char * name, 
			char ** realName, int isPreskel) {
    static char buf[300];

    if (!strcmp(name, "skeleton.cgz") || !strcmp(name, "uglist") ||
	!strcmp(name, "rpmconvert"))
	strcpy(buf, "/tmp/rhimage/RedHat/base/");
    else
	strcpy(buf, "/tmp/rhimage/RedHat/RPMS/");

    strcat(buf, name);
    *realName = buf;

    return 0;
}

static int hdSetup(struct installMethod * method, struct partitionTable table,
	           struct netConfig * netc, struct netInterface * intf,
	           struct driversLoaded ** dl) {
    newtComponent okay, cancel, form, text, listbox, label, answer, dirEntry;
    int i, j;
    char buf[80];
    struct partition * part = NULL;
    char * type;
    char * dir;
    char * dest;
    char * defaultDevice;
    char * defaultDir;
    int done = 0;
    struct hdinfo * hdi;

    if (method->data) {
	hdi = method->data;
	defaultDir = strdup(hdi->dir);
	defaultDevice = hdi->device;
    } else {
	defaultDir = strdup("/");
	defaultDevice = "";
    }

    while (!done) {
	newtOpenWindow(10, 4, 64, 17, "Select Partition");
	text = newtTextbox(1, 1, 62, 4, NEWT_TEXTBOX_WRAP);
	newtTextboxSetText(text, "What partition and directory on that "
			    "partition hold the RedHat/RPMS and "
			    "RedHat/base directories?");
	okay = newtButton(15, 13, "Ok");
	cancel = newtButton(38, 13, "Cancel");

	form = newtForm(NULL, NULL, 0);

	listbox = newtListbox(7, 5, 5, NEWT_LISTBOX_RETURNEXIT);

	label = 
	    newtLabel(5, 4, "  Device          Begin        End   Size (k)");

	for (i = 0, j = 0; i < table.count; i++) {
	    if (table.parts[i].type == PART_EXT2 ||
		table.parts[i].type == PART_DOS) {
		sprintf(buf, "/dev/%-5s  %9d  %9d  %9d", table.parts[i].device, 
			table.parts[i].begin, table.parts[i].end, 
			table.parts[i].size);
		newtListboxAddEntry(listbox, buf, &table.parts[i]);
	
		if (!strcmp(table.parts[i].device, defaultDevice)) {
		    newtListboxSetCurrent(listbox, j);
		}

		j++;
	    }
	}

	newtFormAddComponent(form, 
				newtLabel(1, 11, "Directory holding Red Hat:"));
	dirEntry = newtEntry(28, 11, defaultDir, 28, &dir, NEWT_ENTRY_SCROLL);

	newtFormAddComponents(form, text, label, listbox, dirEntry,
				okay, cancel, NULL);
	answer = newtRunForm(form);
			    
	if (answer != cancel) {
	    part = newtListboxGetCurrent(listbox);
	}

	dir = strdup(dir);
	free(defaultDir);
	defaultDir = dir;
	defaultDevice = part->device;

	newtFormDestroy(form);
	newtPopWindow();

	if (answer == cancel) return INST_CANCEL;

	switch (part->type) {
	  case PART_EXT2:	type = "ext2"; 		break;
	  case PART_DOS:	type = "msdos"; 	break;
	  default:	continue;
	}

	if (doMount(part->device, "/tmp/hdimage", type, 1, 0))
	    continue;

	unlink("/tmp/rhimage");
	
	/* the physical device is mounted on /tmp/hdimage, but all
	   access are through /tmp/rhimage which points to the RedHat
	   directory in /tmp/hdimage */

	dest = alloca(strlen(dir) + 20);
	sprintf(dest, "/tmp/hdimage/%s", dir);

	if (symlink(dest, "/tmp/rhimage")) {
	    messageWindow("Error", "Failed to create /tmp/rhimage "
			    "symlink: %s", strerror(errno));
	    umount("/tmp/hdimage");
	    continue;
	} 

	if (access("/tmp/rhimage/RedHat/base/hdlist", R_OK)) {
	    messageWindow("Error", "Device %s does not appear to contain "
			"a Red Hat insallation tree.", part->device);
	    umount("/tmp/hdimage");
	    continue;
	} 

	if (method->data) {
	    hdi = method->data;
	    free(hdi->dir);
	}
    
	hdi = malloc(sizeof(*hdi));
	hdi->device = part->device;
	hdi->type = type;
	hdi->dir = strdup(dir);
	method->data = hdi;

	done = 1; 

	umount("/tmp/hdimage");
    }

    return 0;
}

static int hdGetPackageSet(struct installMethod * method,
			     struct pkgSet * ps) {
    struct hdinfo * hdi = method->data;
    int rc;

    if (doMount(hdi->device, "/tmp/hdimage", hdi->type, 1, 0))
	return INST_ERROR;

    rc = psUsingDirectory("/tmp/rhimage/RedHat/RPMS", ps);
    
    umount("/tmp/hdimage");
 
    return rc;
}

static int hdGetComponentSet(struct installMethod * method,
			       struct pkgSet * ps,
			       struct componentSet * cs) {
    struct hdinfo * hdi = method->data;
    int rc;

    if (doMount(hdi->device, "/tmp/hdimage", hdi->type, 1, 0))
	return INST_ERROR;

    rc = psReadComponentsFile("/tmp/rhimage/RedHat/base/comps", ps, cs);
    
    umount("/tmp/hdimage");
 
    return rc;
}

static int hdPrepareMedia(struct installMethod * method,
				struct fstab * fstab) {
    struct hdinfo * hdi = method->data;
    int i;
    char * buf;

    for (i = 0; i < fstab->numEntries; i++) {
	if (fstab->entries[i].isMounted &&
	    !strcmp(fstab->entries[i].device, hdi->device)) break;
    }

    if (i < fstab->numEntries) {
	logMessage("device %s is already mounted -- using symlink",
			fstab->entries[i].device);
	buf = alloca(strlen(fstab->entries[i].mntpoint) + 10);

	sprintf(buf, "/mnt/%s", fstab->entries[i].mntpoint);
	
	rmdir("/tmp/hdimage");
	if (symlink(buf, "/tmp/hdimage")) {
	    logMessage("failed to create symlink %s: %s\n", 
			buf, strerror(errno));
	    messageWindow("Error", "Failed to create symlink for package "
			"source.");
	    return INST_ERROR;
	}
    } else {
	logMessage("mounting device which contains packages");
	if (doMount(hdi->device, "/tmp/hdimage", hdi->type, 1, 0))
	    return INST_ERROR;
    }

    return 0;
}

static int imageGetPackageSet(struct installMethod * method,
			      struct pkgSet * ps) {
    return psFromHeaderListFile("/tmp/rhimage/RedHat/base/hdlist", ps);
}

static int imageGetComponentSet(struct installMethod * method,
			        struct pkgSet * ps,
			        struct componentSet * cs) {
    return psReadComponentsFile("/tmp/rhimage/RedHat/base/comps", ps, cs);
}

static int singleimageSetSymlinks(struct installMethod * method, 
	              		  struct partitionTable table,
	              		  struct netConfig * netc, 
				  struct netInterface * intf,
				  struct driversLoaded ** dl) {
    logMessage("making symlink from /tmp/rhimage to image");
    symlink("rhimage", "/tmp/image");
    return 0;
}

/* returns a socket file descriptor, a ftpFinishTransfer() must occur after 
   this call */
static int ftpinstStartTransfer(struct ftpinfo * fi, char * filename) {
    char * buf;
    newtComponent form;
    int fd;

    logMessage("ftping %s to a fd", filename);

    newtOpenWindow(10, 10, 60, 3, "Retrieving");

    buf = alloca(strlen(fi->prefix) + strlen(filename) + 30);
    sprintf(buf, "Retrieving %s...", filename);
    form = newtForm(NULL, NULL, 0);
    newtFormAddComponent(form, newtLabel(1, 1, buf));

    newtDrawForm(form);
    newtRefresh();

    strcpy(buf, fi->prefix);
    strcat(buf, "/RedHat/");
    strcat(buf, filename);
    fd = ftpGetFileDesc(fi->sock, buf);

    return fd;
}

static int ftpinstFinishTransfer(struct ftpinfo * fi) {
    newtPopWindow();

    return ftpGetFileDone(fi->sock);
}

static int ftpinstGetFile(struct ftpinfo * fi, char * filename, char * dest) {
    char * buf;
    newtComponent form;
    int fd, rc;

    logMessage("ftping %s as %s", filename, dest);

    newtOpenWindow(10, 10, 60, 3, "Retrieving");

    buf = alloca(strlen(fi->prefix) + strlen(filename) + 30);
    sprintf(buf, "Retrieving %s...", filename);
    form = newtForm(NULL, NULL, 0);
    newtFormAddComponent(form, newtLabel(1, 1, buf));

    newtDrawForm(form);
    newtRefresh();

    fd = open(dest, O_WRONLY | O_CREAT, 0644);
    if (fd < 0) {
	messageWindow("Error", "open of %s failed: %s\n", dest, 
			strerror(errno));
	return INST_ERROR;
    }

    strcpy(buf, fi->prefix);
    strcat(buf, "/RedHat/");
    strcat(buf, filename);
    rc = ftpGetFile(fi->sock, buf, fd);

    close(fd);

    newtFormDestroy(form);
    newtPopWindow();

    if (rc) {
	messageWindow("ftp", "I cannot get file %s: %s\n", buf,
			ftpStrerror(rc));
	return INST_ERROR;
    }

    return 0;
}

static int ftpMainSetupPanel(struct ftpinfo * fi, char * doSecondarySetup) {
    newtComponent form, okay, cancel, siteEntry, dirEntry, answer, text, cb;
    char * site, * dir;

    if (fi->address) {
	site = fi->address;
	dir = fi->prefix;
    } else {
	site = "";
	dir = "";
    }

    if (fi->login || fi->password || fi->login)
	*doSecondarySetup = 'X';
    else
	*doSecondarySetup = ' ';

    newtOpenWindow(17, 3, 50, 17, "FTP Setup");

    form = newtForm(NULL, NULL, 0);
    okay = newtButton(10, 13, "Ok");
    cancel = newtButton(30, 13, "Cancel");

    text = newtTextbox(1, 1, 47, 8, NEWT_TEXTBOX_WRAP);
    newtTextboxSetText(text,
	"Please enter the following information:\n"
	"\n"
	"    o the name or IP number of your FTP server\n"
	"    o the directory on that server containing\n"
	"      Red Hat Linux for your architecure\n");

    newtFormAddComponent(form, newtLabel(3, 8, "FTP site name    :"));
    newtFormAddComponent(form, newtLabel(3, 9, "Red Hat directory:"));

    siteEntry = newtEntry(22, 8, site, 24, &site, NEWT_ENTRY_SCROLL);
    dirEntry = newtEntry(22, 9, dir, 24, &dir, NEWT_ENTRY_SCROLL);

    cb = newtCheckbox(3, 11, "Use non-anonymous ftp or a proxy server",
			*doSecondarySetup, NULL, doSecondarySetup);

    newtFormAddComponents(form, text, siteEntry, dirEntry, cb, okay, cancel, 
			  NULL);

    answer = newtRunForm(form);
    if (answer == cancel) {
	newtFormDestroy(form);
	newtPopWindow();
	
	return INST_CANCEL;
    }

    if (fi->address) free(fi->address);
    fi->address = strdup(site);

    if (fi->prefix) free(fi->prefix);
    fi->prefix = strdup(dir);

    newtFormDestroy(form);
    newtPopWindow();

    return 0;
}

static int ftpSecondarySetupPanel(struct ftpinfo * fi) {
    newtComponent form, okay, cancel, answer, text, accountEntry;
    newtComponent passwordEntry, proxyEntry;
    char * account, * password, * proxy;

    newtOpenWindow(17, 4, 50, 15, "Further FTP Setup");

    form = newtForm(NULL, NULL, 0);
    okay = newtButton(10, 11, "Ok");
    cancel = newtButton(30, 11, "Cancel");

    text = newtTextbox(1, 1, 47, 8, NEWT_TEXTBOX_WRAP);
    newtTextboxSetText(text,
	"If you are using non anonymous ftp, enter the account "
	"name and password you wish to use below. If you are "
	"using an FTP proxy enter the name of the FTP proxy server "
	"to use.");

    newtFormAddComponent(form, newtLabel(3, 6, "Account name :"));
    newtFormAddComponent(form, newtLabel(3, 7, "Password     :"));

    newtFormAddComponent(form, newtLabel(3, 9, "FTP Proxy    :"));

    accountEntry = newtEntry(18, 6, fi->login, 24, &account, NEWT_ENTRY_SCROLL);
    passwordEntry = newtEntry(18, 7, NULL, 24, &password, 
				NEWT_ENTRY_SCROLL | NEWT_ENTRY_HIDDEN);
    proxyEntry = newtEntry(18, 9, fi->proxy, 24, &proxy, NEWT_ENTRY_SCROLL);

    newtFormAddComponents(form, text, accountEntry, passwordEntry, proxyEntry, 
			  okay, cancel, NULL);

    answer = newtRunForm(form);
    if (answer == cancel) {
	newtFormDestroy(form);
	newtPopWindow();
	
	return INST_CANCEL;
    }

    if (fi->login) free(fi->login);
    if (strlen(account))
	fi->login = strdup(account);
    else
	fi->login = NULL;

    if (fi->password) free(fi->password);
    if (strlen(password))
	fi->password = strdup(password);
    else
	fi->password = NULL;

    if (fi->proxy) free(fi->proxy);
    if (strlen(proxy))
	fi->proxy = strdup(proxy);
    else
	fi->proxy = NULL;

    newtFormDestroy(form);
    newtPopWindow();

    return 0;
}

static int ftpSetup(struct installMethod * method, struct partitionTable table,
	            struct netConfig * netc, struct netInterface * intf,
	            struct driversLoaded ** dl) {
    struct ftpinfo fi;
    enum { FTP_SETUP_NET, FTP_SETUP_FTP1, FTP_SETUP_FTP2, FTP_SETUP_CHECK, 
		FTP_SETUP_DONE } step = FTP_SETUP_NET;
    int rc, fd;
    char doMore;

    memset(&fi, 0, sizeof(fi));

    if (method->data)
	memcpy(&fi, method->data, sizeof(fi));
    else
	memset(&fi, 0, sizeof(fi));

    while (step != FTP_SETUP_DONE) {
	switch (step) {

	  case FTP_SETUP_NET:
	    rc = bringUpNetworking(intf, netc, dl);
	    if (rc) return rc;
	    step = FTP_SETUP_FTP1;
	    break;

	  case FTP_SETUP_FTP1:
	    rc = ftpMainSetupPanel(&fi, &doMore);
	    if (rc == INST_ERROR) 
		return rc;
	    else if (rc)
		step = FTP_SETUP_NET;
	    else if (doMore == ' ')
		step = FTP_SETUP_CHECK;
	    else 
		step = FTP_SETUP_FTP2;
	    break;

	  case FTP_SETUP_FTP2:
	    rc = ftpSecondarySetupPanel(&fi);
	    if (rc == INST_ERROR) 
		return rc;
	    else if (rc)
		step = FTP_SETUP_FTP1;
	    else
		step = FTP_SETUP_CHECK;
	    break;

	  case FTP_SETUP_CHECK:
	    if ((fi.sock = ftpOpen(fi.address, fi.login, 
				   fi.password ? fi.password : "rhinstall@", 
				   fi.proxy, -1)) < 0) {
		messageWindow("ftp", "I cannot log into machine: %s\n",
				ftpStrerror(fi.sock));
		step = FTP_SETUP_FTP1;
		break;
	    }

	    
	    fd = ftpinstStartTransfer(&fi, "base/hdlist");
	    if (fd < 0) {
		newtPopWindow();
		ftpClose(fi.sock);
		step = FTP_SETUP_FTP1;
		break;
	    }

	    if (psFromHeaderListDesc(fd, &fi.ps, 1)) {
		ftpClose(fi.sock);
		step = FTP_SETUP_FTP1;
		break;
	    }

	    ftpinstFinishTransfer(&fi);

	    if (ftpinstGetFile(&fi, "base/comps", "/tmp/comps")) {
		ftpClose(fi.sock);
		step = FTP_SETUP_FTP1;
		break;
	    }
	    
	    step = FTP_SETUP_DONE;
	    break;

	  case FTP_SETUP_DONE:
	    break;
	}
    }

    if (method->data) free(method->data);
    method->data = malloc(sizeof(fi));
    memcpy(method->data, &fi, sizeof(fi));

    return 0;
}

static int ftpGetPackageSet(struct installMethod * method,
			     struct pkgSet * ps) {
    struct ftpinfo * fi = method->data;

    *ps = fi->ps;
    return 0;
}

static int ftpGetComponentSet(struct installMethod * method,
			       struct pkgSet * ps,
			       struct componentSet * cs) {
    return psReadComponentsFile("/tmp/comps", ps, cs);
}

static int ftpinstGetMappedFile(struct installMethod * method, char * name, 
		                char ** realName, int isPreskel) {
    static char sbuf[300];
    char * buf;
    int rc;
    struct ftpinfo * fi = method->data;

    if (isPreskel)
	strcpy(sbuf, "/mnt/");
    else
	strcpy(sbuf, "/mnt/var/tmp/");

    strcat(sbuf, name);
    *realName = sbuf;

    buf = alloca(strlen(name) + 30);
    if (!strcmp(name, "skeleton.cgz") || !strcmp(name, "uglist") ||
	!strcmp(name, "rpmconvert"))
	strcpy(buf, "base/");
    else
	strcpy(buf, "RPMS/");
    strcat(buf, name);

    rc = ftpinstGetFile(fi, buf, *realName);
    if (!rc) return 0;

    /* Try again, and relogin */
    ftpClose(fi->sock);

    if ((fi->sock = ftpOpen(fi->address, fi->login, 
			    fi->password ? fi->password : "rhinstall@", 
			    fi->proxy, -1)) 
		< 0) {
	messageWindow("ftp", "I cannot log into machine: %s\n",
			ftpStrerror(fi->sock));
	free(fi);
	return INST_ERROR;
    }

    return ftpinstGetFile(fi, buf, *realName);
}

int tapeOp(int fd, int cmd, int count, int reportErr) {
    struct mtop mtcmd;

    mtcmd.mt_op = cmd;
    mtcmd.mt_count = count;

    if (ioctl(fd, MTIOCTOP, &mtcmd)) {
	if (reportErr)
	    messageWindow("Tape Error", "tape ioctl %d failed: %s",
			  cmd, strerror(errno));
	return 1;
    }
 
    return 0;
}

static int tapeSetup(struct installMethod * method, struct partitionTable table,
	             struct netConfig * netc, struct netInterface * intf,
	             struct driversLoaded ** dl) {
    int rc;
    int fd;
    int catalogAlloced;
    struct tapeinfo * info;
    char magic[5];
    int foundit = 0;
    char catalogBuf[16384];
    char * chptr, * start, * next;
    int offset = 0;

    /* eventually we should support devices other then /dev/nst0 */
    if ((rc = setupSCSIInterfaces(1, dl))) return rc;

    if ((rc = devMakeInode("nst0", "/tmp/nst0"))) return rc;

    if ((fd = open("/tmp/nst0", O_RDONLY)) < 0) {
	devRemoveInode("/tmp/nst0");
 
	if (errno == ENODEV) {
	    messageWindow("Error", "I couldn't find any SCSI tape drives.");
	} else {
	    errorWindow("Error opening device nst0: %s");
	}

	return INST_ERROR;
    }

    winStatus(45, 3, "Tape", "Looking for Red Hat tape image...");

    /* now, let's see if we can find the file on the tape */
    if ((rc = read(fd, magic, sizeof(magic))) == sizeof(magic)) {
	if (!strcmp(magic, "1234\n")) {
	    logMessage("found magic on first try");
	    foundit = 1;
	}
    } else if (rc < 0) {
	messageWindow("Error", "Error reading from tape: %s", strerror(errno));
    }

    if (!foundit) {
	logMessage("seeking to next file");
	if (tapeOp(fd, MTFSF, 1, 0)) {
	    logMessage("failed to find beginning of next file: %s",
			strerror(errno));
	} else {
	    if ((rc = read(fd, magic, sizeof(magic))) == sizeof(magic)) {
		if (!strcmp(magic, "1234\n")) {
		    logMessage("found magic on second try");
		    foundit = 1;
		    offset = 1;
		}
	    }
	}
    }

    if (!foundit) {
	errorWindow("I can't find the Red Hat tape installation image"
			"on that tape.");
	close(fd);
	newtPopWindow();

	return INST_ERROR;
    } 

    if ((rc = read(fd, catalogBuf, sizeof(catalogBuf) - 1)) < 0) {
	errorWindow("Error reading file catalog from tape: %s");
	newtPopWindow();
	close(fd);
	return INST_ERROR;
    }

    newtPopWindow();

    info = malloc(sizeof(*info));
    info->fd = fd;
    info->offset = offset;
    info->curr = -1;
    method->data = info;
        
    /* read in the catalog */
    catalogAlloced = 20;
    info->catalog = malloc(sizeof(*info->catalog) * catalogAlloced);
    info->catalogEntries = 0;

    start = catalogBuf;
    while (strncmp(start, "--done--", 8)) {
	chptr = strchr(start, '\n');
	next = chptr + 1;
	*chptr = '\0';
	while (*chptr != ' ') chptr--;
	*chptr = '\0';
	chptr++;

	if (info->catalogEntries == catalogAlloced) {
	    catalogAlloced += 20;
    	    info->catalog = realloc(info->catalog, 
				    sizeof(*info->catalog) * catalogAlloced);
	}
	
	info->catalog[info->catalogEntries].filename = strdup(start);
	info->catalog[info->catalogEntries].size = strtoul(chptr, NULL, 10);
	info->catalogEntries++;
	
	start = next;
    }
	
    return 0;
}

static int tapeGetPackageSet(struct installMethod * method,
			     struct pkgSet * ps) {
    struct tapeinfo * info = method->data;

    if (tapeOp(info->fd, MTFSF, 1, 1)) return INST_ERROR;
    info->curr++;
    return psFromHeaderListDesc(info->fd, ps, 1);
}

static int tapeCopyFile(struct tapeinfo * info, char * tapename, char * out) {
    int outfd;
    char buf[4096];
    int i;
    int total, amount;
    int done = 0;

    /* let's figure out where we need to go */
    for (i = 0; i < info->catalogEntries; i++) {
	if (!strcmp(tapename, info->catalog[i].filename)) break;
    }

    if (i < 0) {
	messageWindow("Error", "I can't find file %s on the tape", tapename);
	return INST_ERROR;	
    }

    if (i < info->curr) {
	messageWindow("Error", "The tape is not positioned properly");
	return INST_ERROR;	
    } else {
	logMessage("skipping %d files on the tape to get to %s", 
			i - info->curr, tapename);
	while (i > info->curr) {
	    if (tapeOp(info->fd, MTFSF, 1, 1)) return INST_ERROR;
	    logMessage("skipped file");
	    info->curr++;
	}
    }

    if ((outfd = open(out, O_RDWR | O_CREAT | O_TRUNC, 0666)) < 0) {
	messageWindow("Error", "failed to create %s: %s", out, 
			strerror(errno));
	return INST_ERROR;
    }

    total = 0;
    while (!done) {
	amount = read(info->fd, buf, sizeof(buf));

	if (amount < 0)
	    break;
	
	if (((total + amount) > info->catalog[i].size)) {
	    done = 1;
	    amount = info->catalog[i].size - total;
	}
	if (write(outfd, buf, amount) != amount) {
	    messageWindow("Error", "failed to write to %s: %s", out, 
			    strerror(errno));
	    close(outfd);
	    return INST_ERROR;
	}

	total += amount;
    }

    if (i < 0) {
	messageWindow("Error", "failed to read from tape: %s", out, 
			strerror(errno));
	close(outfd);
	return INST_ERROR;
    }

    close(outfd);

    return 0;
}

static int tapeGetComponentSet(struct installMethod * method,
			     struct pkgSet * ps,
			     struct componentSet * cs) {
    struct tapeinfo * info = method->data;
    int rc;
    
    if ((rc = tapeCopyFile(info, "comps", "/tmp/comps"))) return rc;

    return psReadComponentsFile("/tmp/comps", ps, cs);
}

static int tapeGetMappedFile(struct installMethod * method, char * name, 
		                char ** realName, int isPreskel) {
    static char sbuf[300];
    int rc;
    struct tapeinfo * info = method->data;

    if (isPreskel)
	strcpy(sbuf, "/mnt/");
    else
	strcpy(sbuf, "/mnt/var/tmp/");

    strcat(sbuf, name);
    *realName = sbuf;

    return (rc = tapeCopyFile(info, name, sbuf));
}

#ifdef __i386__
static int smbSetup(struct installMethod * method, struct partitionTable table,
	           struct netConfig * netc, struct netInterface * intf,
	           struct driversLoaded ** dl) {
    char * host = NULL, * dir = NULL, * acct = NULL, * pass = NULL;
    char * buf;
    static int moduleLoaded = 0;
    enum { SMB_STEP_NET, SMB_STEP_INFO, SMB_STEP_MOUNT, SMB_STEP_DONE }
		step = SMB_STEP_NET;
    int rc;

    while (step != SMB_STEP_DONE) {
	switch (step) {
	  case SMB_STEP_NET:
	    rc = bringUpNetworking(intf, netc, dl);
	    if (rc) return rc;
	    step = SMB_STEP_INFO;
	    break;

	  case SMB_STEP_INFO:
	    rc = smbGetSetup(&host, &dir, &acct, &pass);
	    if (rc == INST_CANCEL)
		step = SMB_STEP_NET;
	    else if (rc == INST_ERROR)
		return INST_ERROR;
	    else
		step = SMB_STEP_MOUNT;
	    break;

	  case SMB_STEP_MOUNT:
	    if (!strlen(host) || !strlen(dir))
		rc = INST_ERROR;
	    else {
		buf = malloc(strlen(host) + strlen(dir) + 10);
		strcpy(buf, host);
		strcat(buf, ":");
		strcat(buf, dir);

		if (!moduleLoaded) {
		    rc = loadModule("smbfs", DRIVER_FS, DRIVER_MINOR_NONE, dl);
		    if (rc) return rc;
		    moduleLoaded = 1;
		}

		rc = doPwMount(buf, "/tmp/rhimage", "smb", 1, 0, acct, pass);
		if (rc) removeModule("smbfs");

		free(buf);
	    }

	    if (rc) {
		step = SMB_STEP_INFO;
		messageWindow("Error", 
			"I could not mount that directory from the server");
	    } else {
	        if (access("/tmp/rhimage/RedHat", R_OK)) {
		    step = SMB_STEP_INFO;
		    messageWindow("Error", "That directory does not seem "
				  "to contain a Red Hat installation tree.");
		    umount("/tmp/rhimage");
		} else
		    step = SMB_STEP_DONE;
	    }

	    break;

	  case SMB_STEP_DONE:
	    break;
	}
    }

    free(host);
    free(dir);

    return 0;
}

static int smbGetSetup(char ** hostptr, char ** dirptr, char ** acctptr,
			char ** pwptr) {
    newtComponent form, okay, cancel, siteEntry, dirEntry, answer, text;
    newtComponent acctEntry, passEntry;
    char * site, * dir, * acct, * pass;

    if (*hostptr) {
	site = *hostptr;
	dir = *dirptr;
	acct = *acctptr;
	pass = *pwptr;
    } else {
	site = "";
	dir = "";
	acct = "";
	pass = "";
    }

    newtOpenWindow(15, 4, 50, 16, "SMB Setup");

    form = newtForm(NULL, NULL, 0);
    okay = newtButton(10, 12, "Ok");
    cancel = newtButton(30, 12, "Cancel");

    text = newtTextbox(1, 1, 47, 5, NEWT_TEXTBOX_WRAP);
    newtTextboxSetText(text,
	"Please enter the following information:\n"
	"\n"
	"    o the name or IP number of your SMB server\n"
	"    o the volume to share which contains\n"
	"      Red Hat Linux for your architecture");

    newtFormAddComponent(form, newtLabel(3, 7,  "SMB server name  :"));
    newtFormAddComponent(form, newtLabel(3, 8,  "Share volume     :"));
    newtFormAddComponent(form, newtLabel(3, 9,  "Account name     :"));
    newtFormAddComponent(form, newtLabel(3, 10, "Password         :"));

    siteEntry = newtEntry(22, 7, site, 24, &site, NEWT_ENTRY_SCROLL);
    dirEntry = newtEntry(22, 8, dir, 24, &dir, NEWT_ENTRY_SCROLL);
    acctEntry = newtEntry(22, 9, "guest", 24, &acct, NEWT_ENTRY_SCROLL);
    passEntry = newtEntry(22, 10, pass, 24, &pass, 
			  NEWT_ENTRY_SCROLL | NEWT_ENTRY_HIDDEN);

    newtFormAddComponents(form, text, siteEntry, dirEntry, acctEntry,
			  passEntry, okay, cancel, NULL);

    answer = newtRunForm(form);
    if (answer == cancel) {
	newtFormDestroy(form);
	newtPopWindow();
	
	return INST_CANCEL;
    }

    *hostptr = strdup(site);
    *dirptr = strdup(dir);
    *acctptr = strdup(acct);
    *pwptr = strdup(pass);

    newtFormDestroy(form);
    newtPopWindow();

    return 0;
}

static int smbGetPackageSet(struct installMethod * method,
			     struct pkgSet * ps) {
    return psUsingDirectory("/tmp/rhimage/RedHat/RPMS", ps);
}

static int smbGetComponentSet(struct installMethod * method,
			       struct pkgSet * ps,
			       struct componentSet * cs) {
    return psReadComponentsFile("/tmp/rhimage/RedHat/base/comps", ps, cs);
}


#endif
