/* 
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 *
 * Copyright (C) Hewlett-Packard (Paul Bame) paul_bame@hp.com
 */

#if !(defined hpux || defined __hpux)
# define LONG_OPTIONS 1
#endif

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#if LONG_OPTIONS
# define _GNU_SOURCE
# include <getopt.h>
#endif
#include "load.h"
#include "palo.h"


#define B32(x)	__be32_to_cpu((x))

/* maximum allowed HP boot loader (IPL) size */
#define MAXBLSIZE (256 * 1024)

static int Install = 0;
int verbose = 0;

/* compute the sum of words in an 4-byte aligned region */
int
checksum(void *p, size_t len)
{
    int xsum = 0;
    int *x = (int *)p;
    int i;

    if (verbose) printf("checksum(%p, %u) = ", p, len);
    len /= 4;

    for (i = 0; i < len; i++)
    {
	xsum += B32(x[i]);
    }

    if (verbose) printf("0x%08x\n", xsum);

    return (xsum);
}

static void
fb_init(struct firstblock *f)
{
    f->lifmagic0 = 0x80;
    f->lifmagic1 = 0x00;
    f->rd_sz = f->kern32_sz = f->kern64_sz = 0;
    f->kern32_native_sz = f->kern64_native_sz = 0;
    f->rd_offset = f->kern32_offset = f->kern64_offset = 0;
    strcpy(f->palomagic, PALOMAGIC);
    f->version = PALOHDRVERSION;
    f->flags = 0;
    if (Install)
    	f->flags |= PFLAG_INSTALL;
}

int
check_bootloader(int media, int line)
{
    struct firstblock f;
    int ipl_words;
    int xsum;
    unsigned int *ipl;

    if (verbose) printf("check_bootloader %d\n", line);

    STRUCTREAD(media, f, 0);

    if (f.lifmagic0 != 0x80 || f.lifmagic1 != 0x00)
    {
	fprintf(stderr, "0x%x 0x%x\n", f.lifmagic0, f.lifmagic1);
	error(6, "LIF header");
    }

    if (B32(f.ipl_addr) < 2048 ||
	(B32(f.ipl_addr) % 2048) != 0)
	error(6, "ipl_addr");

    if (B32(f.ipl_size) <= 0 ||
	B32(f.ipl_size) >= MAXBLSIZE ||
	(B32(f.ipl_size) % 2048) != 0)
	error(6, "ipl_size");

    if (B32(f.ipl_entry) >= B32(f.ipl_size) ||
	B32(f.ipl_entry) < 0 ||
	(B32(f.ipl_entry) % 4) != 0)
	error(6, "ipl_entry");

    ipl_words = B32(f.ipl_size) / sizeof ipl[0];

    /* seekread the IPL */
    ipl = calloc(ipl_words, sizeof ipl[0]);

    /* would be a good idea to check this call? */
    if (seekread(media, (char *)ipl, B32(f.ipl_size), B32(f.ipl_addr)) == -1)
	error(6, "read failed");

    /* calculate xsum */
    xsum = checksum(ipl, B32(f.ipl_size));

    if (xsum != 0)
    {
	fprintf(stderr, "calculated xsum 0x%x got 0x%x\n",
		-xsum, ipl[ipl_words - 1]);
	xsum -= B32(ipl[ipl_words - 1]);
	fprintf(stderr, "calculated xsum 0x%x got 0x%x\n",
		-xsum, ipl[ipl_words - 1]);
	exit(2);
    }

    printf("ipl: addr %d size %d entry 0x%x\n",
			B32(f.ipl_addr),
			B32(f.ipl_size),
			B32(f.ipl_entry));

    if (strncmp(f.palomagic, PALOMAGIC, 4) != 0)
    {
	error(6, "bad magic");
    }

    if (f.version != PALOHDRVERSION)
    {
	fprintf(stderr, "Warning!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
			"Boot loader header version is %d, I only know how\n"
			"to handle version %d.  It MIGHT work anyway.\n",
			f.version, PALOHDRVERSION);
    }

#if 1
    printf(" ko 0x%x ksz %d knsz %d k64o 0x%x k64sz %d k64nsz %d rdo %d rdsz %d\n<%s>\n",
	B32(f.kern32_offset), B32(f.kern32_sz), B32(f.kern32_native_sz),
	B32(f.kern64_offset), B32(f.kern64_sz), B32(f.kern64_native_sz),
	B32(f.rd_offset), B32(f.rd_sz),
	f.cmdline[0] ? f.cmdline : f.cmdline_old);
#endif

    return 1;
}


unsigned
write_bootloader(int media, int bootloader,
	unsigned where,
	unsigned f0end,
	struct firstblock *f)
{
    size_t rblsize;		/* sector-rounded boot loader size */
    struct loadable loadable;
    int xsum1;
    void *blimage;
    int r;
    int wide;

    /* We want prepare_ to fail, otherwise we've been given the output */
    /* of the linker instead of mkbootable */
    r = prepare_loadable(bootloader, &loadable, &wide);
    if (r)
	error(12);

    /* make sure max size boot loader would fit */
    if (where + MAXBLSIZE > f0end) {
	printf("where %d, where+MAX=%d f0end=%d\n",
	       where, where + MAXBLSIZE, f0end);
	error(9);
    }
    /* load the boot loader into RAM */
    rblsize = fsize(bootloader);
    assert((rblsize % FW_BLOCKSIZE) == 0);
    blimage = (void *)calloc(1, rblsize);
    assert(blimage != NULL);
    if ((r = seekread(bootloader, blimage, rblsize, 0)) != rblsize)
	error(13);

    /* Is it really a boot loader? */
#define BLWORD1 0xe8000002
    if (BLWORD1 != __be32_to_cpu(*(unsigned *)blimage))
    {
	fprintf(stderr, "ERROR: first word of boot loader was 0x%08x,"
		" expected 0x%08x\n", __be32_to_cpu(*(unsigned *)blimage),
		BLWORD1);
	exit(2);
    }

    /* write it out */
    seekwrite(media, blimage, rblsize, where);
    close(bootloader);

    f->ipl_addr = __cpu_to_be32(where);
    f->ipl_size = __cpu_to_be32(rblsize);
    /* entry point relative to where IPL is loaded in RAM by firmware */
    f->ipl_entry = __cpu_to_be32(loadable.entry - loadable.first);

    STRUCTWRITE(media, *f, 0);

    check_bootloader(media, __LINE__);

    free(blimage);
    return where + rblsize;
}

unsigned
write_kernel32(int media, int kernel, unsigned where, unsigned end,
	struct firstblock *f, int native_size)
{
    /* always allow max size boot loader */
    f->kern32_offset = __cpu_to_be32(where);
    f->kern32_sz = __cpu_to_be32(fsize(kernel));
    f->kern32_native_sz = __cpu_to_be32(native_size);

    if (B32(f->kern32_sz) + where >= end)
	error(14, "32-bit-kernel");
    lseek(media, where, SEEK_SET);
    return where + cat(media, kernel);
}

unsigned
write_kernel64(int media, int kernel, unsigned where, unsigned end,
	struct firstblock *f, int native_size)
{
    /* always allow max size boot loader */
    f->kern64_offset = __cpu_to_be32(where);
    f->kern64_sz = __cpu_to_be32(fsize(kernel));
    f->kern64_native_sz = __cpu_to_be32(native_size);

    if (B32(f->kern64_sz) + where >= end)
	error(14, "64-bit-kernel");
    lseek(media, where, SEEK_SET);
    return where + cat(media, kernel);
}

unsigned
write_ramdisk(int media, int rd, unsigned where, unsigned end,
	struct firstblock *f)
{
    /* always allow max size boot loader */
    f->rd_offset = __cpu_to_be32(where);
    f->rd_sz = __cpu_to_be32(fsize(rd));

    if (B32(f->rd_sz) + where >= end)
	error(14, "ramdisk");

    lseek(media, where, SEEK_SET);
    where += cat(media, rd);

    return where;
}

/* assumes the previous contents of the file are to be overwritten, */
/* that kernel and bootloader are valid file pointers, and that */
/* commandline will fit given size in fixed_pm. */
void
do_sequential(int media, int kernel32, int kernel64,
	const char *commandline, int bootloader, int ramdisk,
	int kern32_nsz, int kern64_nsz)
{
    struct firstblock f;
    /* always load IPL at second 2k block on sequential media */
    int where = 1 * FW_BLOCKSIZE;
    unsigned end = 800 * 1024 * 1024;

    /* must have a boot loader for initialization */
    assert(bootloader != -1);
    assert(kernel32 != -1 || kernel64 != 1);

    memset(&f, 0, sizeof f);
    fb_init(&f);

    /* Update the boot loader, ignore size in this case */
    where = write_bootloader(media, bootloader, where, end, &f);

    if (kernel32 != -1)
	where = write_kernel32(media, kernel32, where, end, &f, kern32_nsz);

    /* write some padding to the 2k boundary */
    where += write(media, &f, FW_BLOCKSIZE - (where % FW_BLOCKSIZE));

    if (kernel64 != -1)
	where = write_kernel64(media, kernel64, where, end, &f, kern64_nsz);

    /* write some padding to the 2k boundary */
    where += write(media, &f, FW_BLOCKSIZE - (where % FW_BLOCKSIZE));

    if (ramdisk != -1)
	where = write_ramdisk(media, ramdisk, where, end, &f);

    /* write some padding to the 2k boundary */
    where += write(media, &f, FW_BLOCKSIZE - (where % FW_BLOCKSIZE));

    if (commandline != 0)
	strncpy(f.cmdline, commandline, sizeof(f.cmdline)-1);

    STRUCTWRITE(media, f, 0);

    check_bootloader(media, __LINE__);
}

/* look on each 2k boundary for the file 'target' on 'media'.  Assumes
 * that 'target' is at least 2k in length.
 */
static int
lookfor(int media, int target)
{
    char *targetptr;
    char *mbuf;
    int offset;
    int r = -1;
    int targetsize = fsize(target);
    float mediasize = fsize(media);

    targetptr = mmap(0, targetsize, PROT_READ, MAP_SHARED, target, 0);
    if ((long) targetptr == -1)
    {
	perror("mmap(targetptr)");
	return -1;
    }

    mbuf = malloc(targetsize);
    assert(mbuf != NULL);

    for (offset = 0; 1; offset += 2048)
    {
	int n;
	const int PREVIEW = 512;

	n = seekread(media, mbuf, PREVIEW, offset);
	if (n < PREVIEW)
	    break;
	if (memcmp(mbuf, targetptr, PREVIEW) == 0)
	{
	    n = seekread(media, mbuf, targetsize, offset);
	    if (n < targetsize)
		break;
	    if (memcmp(mbuf, targetptr, targetsize) == 0)
	    {
		r = offset;
		break;
	    }
	}
    }

    munmap(targetptr, targetsize);
    free(mbuf);

    return r;
}

/* Given a writeable ISO9660 CD image containing within its file system
 * both the boot loader and kernel specified also on the palo command line,
 * write a "sequential" or "F0" type boot header.
 */
void
do_cdrom(int media, int kernel32, int kernel64,
	const char *commandline, int bootloader, int ramdisk,
	int kern32_nsz, int kern64_nsz)
{
    struct firstblock f;
    /* always load IPL at second 2k block on sequential media */
    int where = 1 * FW_BLOCKSIZE;
    unsigned end = 800 * 1024 * 1024;
    /* int is safe since CD-ROM is < 2G */
    int bootloader_offset;
    int kernel32_offset;
    int kernel64_offset;
    int ramdisk_offset;

    /* must have a boot loader and kernel */
    assert(bootloader != -1);
    assert(kernel32 != -1 || kernel64 != 1);

    /* read the first block/sector, which is FW_BLOCKSIZE, which also */
    /* conveniently is the CD block size too */
    /* STRUCTREAD(media, f, 0); Probably don't need to read it... */
    memset(&f, 0, sizeof f);
    fb_init(&f);

    /* search the ISO image for the boot loader and kernel files */
    if ((bootloader_offset = lookfor(media, bootloader)) == -1)
    {
	error(15, "boot loader");
    }

    if (kernel32 != -1 && (kernel32_offset = lookfor(media, kernel32)) == -1)
	error(15, "32-bit-kernel image");

    if (kernel64 != -1 && (kernel64_offset = lookfor(media, kernel64)) == -1)
	error(15, "64-bit-kernel image");

    if (ramdisk != -1)
    {
	if ((ramdisk_offset = lookfor(media, ramdisk)) == -1)
	    error(15, "ramdisk image");
    }

    /* Overwrite the boot loader because write_bootloader() is easy to use */
    write_bootloader(media, bootloader, bootloader_offset, end, &f);

    /* Overwrite the kernel because write_kernel() is easy to use */
    if (kernel32 != -1)
    	write_kernel32(media, kernel32, kernel32_offset, end, &f, kern32_nsz);

    if (kernel64 != -1)
	write_kernel64(media, kernel64, kernel64_offset, end, &f, kern64_nsz);

    if (ramdisk != -1)
    {
	write_ramdisk(media, ramdisk, ramdisk_offset, end, &f);
    }

    if (commandline != 0)
	strncpy(f.cmdline, commandline, sizeof(f.cmdline)-1);

    STRUCTWRITE(media, f, 0);

    check_bootloader(media, __LINE__);
}

#define KSIZE (2 * 1024 * 1024)

/* Blocksize for the ext2/3 fs in the palo partition */
#define EXT2_BLOCKSIZE	1024

/* size in ext2 blocks of hole for bootloader */
#define EXT2_HOLE	((MAXBLSIZE + 1) / EXT2_BLOCKSIZE)

/* offset in bytes before start of hole,  ext2 doesn't allow holes at
 * to cover the first four blocks of the filesystem */
#define EXT2_OFFSET	(4*EXT2_BLOCKSIZE)

int
do_formatted(int init, int media, const char *medianame, int partition,
	     int f0start, int f0length, int bootloaderfd, int do_format,
	     const char *commandline)
{
    char partitionname[256];
    struct firstblock f;
    int partitionfd;

    /* FIXME: assuming we can simply add the partition to the end
     * of the whole disc device isn't always true */
    snprintf(partitionname, sizeof(partitionname), "%s%d", medianame,
	     partition);

    if ((partitionfd = open(partitionname, O_RDWR)) < 0) {
	perror(partitionname);
	exit(1);
    }
    close(partitionfd);

    if (init) {
	/* make the bootloader align at 256k */
	unsigned int holestart = (f0start + 0x3ffff + EXT2_OFFSET) & ~0x3ffff;
	unsigned int partition_offset = holestart - f0start;
	char badblockfilename[256];
	int fd, i;
	char cmd[512];

	printf("Initializing %s as ext%d\n", partitionname, do_format);

	if (verbose)
	    printf("f0 partition starts %d, hole %d-%d, end %d\n",
		   f0start, holestart, holestart + EXT2_HOLE*EXT2_BLOCKSIZE,
		   f0start + f0length);

	if(partition_offset + EXT2_HOLE > f0length)
	    error(14, "bootloader");

	sprintf(badblockfilename, "/tmp/paloblk-%d", getpid());
	if ((fd = open(badblockfilename, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)) < 0) {
	    perror(badblockfilename);
	    exit(1);
	}
    
	for (i = (holestart - f0start)/EXT2_BLOCKSIZE;
	     i < (holestart - f0start)/EXT2_BLOCKSIZE + EXT2_HOLE; i++) {
	    char buf[128];
	    int len;
	    sprintf(buf, "%d\n", i);
	    len = strlen(buf);
	    if (len != write(fd, buf, len)) {
		perror("Error writing badblock file");
		exit(1);
	    }
	}

	sprintf(cmd, "mke2fs %s -b %d -l %s %s", do_format == 3 ? "-j" : "",
		EXT2_BLOCKSIZE, badblockfilename, partitionname);

	if (verbose)
	    printf("Executing: %s\n", cmd);
	else
	    strcat(cmd, " > /dev/null 2>&1");

	i = system(cmd);
	unlink(badblockfilename);

	if(WEXITSTATUS(i) != 0)
	    error(19, WEXITSTATUS(i));

	

	STRUCTREAD(media, f, 0);
	fb_init(&f);
	f.flags |= PFLAG_EXT2;

	if(commandline)
	    strncpy(f.cmdline, commandline, sizeof(f.cmdline)-1);

	write_bootloader(media, bootloaderfd, holestart,
			 holestart + EXT2_HOLE*EXT2_BLOCKSIZE, &f);
	
	STRUCTWRITE(media, f, 0);

    } else {
	STRUCTREAD(media, f, 0);

	if(verbose)
	    printf("Updating formatted ver=%d, start=%d\n",
		   f.version, f.ipl_addr);

	if ((f.version < 4) || (!f.flags & PFLAG_EXT2)) {
		printf("Can not update %s. Please initialize first.\n",
				partitionname);
		return -1;
	}

	if(commandline)
	    strncpy(f.cmdline, commandline, sizeof(f.cmdline)-1);

	write_bootloader(media, bootloaderfd, f.ipl_addr,
			 f.ipl_addr + EXT2_HOLE*EXT2_BLOCKSIZE, &f);

	STRUCTWRITE(media, f, 0);
    }
    return 0;
}

void
do_randomaccess(int init, int media, int kernel32, int kernel64,
	const char *commandline, int bootloader, int ramdisk,
	unsigned f0start, unsigned f0length, int kern32_nsz, int kern64_nsz)
{
    struct firstblock f;
    int bstart = 0;

    if (verbose) printf("do_ra(%d, %d, %d, %d, '%s', %d, %d, %u, %u)\n",
	init, media, kernel32, kernel64, commandline, bootloader, ramdisk, f0start,
	f0length);

    assert((f0start % 512) == 0);

    /* figure out where the boot loader should be */
    /* f0start is a sector (512-byte) multiple.  IPL must be aligned 2k */
    bstart = (f0start + 2047) & ~2047;

    if (init)
    {
	/* initialize a partitioned medium */
	unsigned where = bstart;
	unsigned end = f0start + f0length;

	/* must have a boot loader for initialization */
	assert(bootloader != -1);

	STRUCTREAD(media, f, 0);
	fb_init(&f);

	/* Update the boot loader, ignore size in this case */
	write_bootloader(media, bootloader, where, end, &f);

	where += MAXBLSIZE;

	if (kernel32 != -1)
	{
	    unsigned p, ksize;
	    p = write_kernel32(media, kernel32, where, end, &f, kern32_nsz);

	    ksize = p - where;

	    /* allow for bigger kernels */
	    if (ksize < KSIZE)
		ksize = KSIZE;

	    where = p;
	}

	if (kernel64 != -1)
	{
	    unsigned p, ksize;
	    p = write_kernel64(media, kernel64, where, end, &f, kern64_nsz);

	    ksize = p - where;

	    /* allow for bigger kernels */
	    if (ksize < KSIZE)
		ksize = KSIZE;

	    where = p;
	}

	if (ramdisk != -1)
	    where = write_ramdisk(media, ramdisk, where, end, &f);

	if (commandline != 0)
	    strncpy(f.cmdline, commandline, sizeof(f.cmdline)-1);

	STRUCTWRITE(media, f, 0);
    }
    else	/* update */
    {
	unsigned end = f0start + f0length;

	fprintf(stderr, "palo -U on unformatted (non-ext2) palo partitions doesn't work yet\n"); exit(2);
#if 0

	/* make sure we have a good bootloader on there now */
	check_bootloader(media, __LINE__);

	/* update a partitioned medium */
	STRUCTREAD(media, f, 0);

	if (bootloader != -1)
	{
	    write_bootloader(media, bootloader, bstart, B32(f.kern_offset), &f);
	}

	if (kernel != -1)
	{
	    write_kernel(media, kernel, B32(f.kern_offset),
		B32(f.rd_offset), &f);
	}

	if (kernel != -1)
	{
	    write_kernel(media, kernel, B32(f.kern_offset),
		B32(f.rd_offset), &f);
	}

	if (ramdisk != -1)
	{
	    write_ramdisk(media, ramdisk, B32(f.rd_offset), end, &f);
	}

	if (commandline != 0)
	    strncpy(f.cmdline, commandline, sizeof(f.cmdline)-1);

	STRUCTWRITE(media, f, 0);
#endif
    }

    check_bootloader(media, __LINE__);
}

#define MAXARGS 200

#if LONG_OPTIONS
static struct option Longopts[] =
{
    {"configfile", 1, 0, 'f'},
    {"commandline", 1, 0, 'c'},
    {"recoverykernel", 1, 0, 'k'},
    {"bootloader", 1, 0, 'b'},
    {"ramdisk", 1, 0, 'r'},
    {"init-partitioned", 1, 0, 'I'},
    {"format-as", 1, 0, 'e'},
    {"update-partitioned", 1, 0, 'U'},
    {"init-tape", 1, 0, 's'},
    {"init-cdrom", 1, 0, 'C'},
    {"verbose", 0, 0, 'v'},
    {"help", 0, 0, '?'},
    {0, 0, 0, 0}
};
# define GETOPT(argc, argv, optstring) \
	getopt_long(argc, argv, optstring, Longopts, 0)
#else
# define GETOPT(argc, argv, optstring) getopt(argc, argv, optstring)
#endif

int
main(int argc, char *argv[])
{
    int c;
    enum {SEQUENTIAL, PARTITIONED, CDROM} mediatype = PARTITIONED;
    int bootloader = -1;
    int kernel32 = -1;
    int kernel64 = -1;
    int kern32_nsz = 0, kern64_nsz = 0;
    int fd;
    struct loadable loadable;
    int wide;
    char *commandline = 0;
    int media = -1;
    int ramdisk = -1;
    int cdrom = 0;
    char *medianame = NULL;
    char *bootloaderfile = "/usr/share/palo/iplboot";
    int init = 0;
    extern const char bld_info[];
    const char gargs[] = "f:C:s:b:k:c:r:I:e:U:v?";
    char *config_file = "/etc/palo.conf";
    char *newargv[MAXARGS];
    int newargc, format_as = 0;
    FILE *fconfig;

    assert(sizeof (struct firstblock) == 2048);
    {
	struct firstblock f;
	assert((unsigned long)&f.kern32_offset - (unsigned long)&f == 8);
	assert((unsigned long)&f.ipl_addr - (unsigned long)&f == 0xf0);
    }

    fputs("palo version " PALOVERSION " ", stdout);
    puts(bld_info);

    /* do we have a -f? */
    while ((c = GETOPT(argc, argv, gargs)) != EOF)
    {
	switch(c)
	{
	case 'f':
	    config_file = optarg;
	    break;
	case '?':
	    error(0, argv[0]);
	}
    }

    newargc = 0;
    newargv[newargc++] = argv[0];
    /* grab args from the config file */
    if ((fconfig = fopen(config_file, "r")) == NULL)
    {
	fprintf(stderr, "Warning: ");
        perror(config_file);
    }
    else
    {
        char buf[256];

	while(newargc < MAXARGS && fgets(buf, sizeof buf, fconfig) != NULL)
	{
	    char *ptr = buf;
	    char *end;

	    /* skip over leading whitespace */
	    while (isspace(*ptr))
	    	ptr++;

	    /* skip comment */
	    if (*ptr == '#')
	    	continue;

	    /* strip trailing whitespace */
	    end = ptr + strlen(ptr) - 1;
	    while (isspace(*end))
	    	end--;
	    *++end = '\0';

	    /* skip blank lines */
	    if (*ptr == '\0')
	        continue;

	    newargv[newargc++] = strdup(buf);
	}
	fclose(fconfig);
    }

    /* add the original command-line args */
    for (c = 1; c < argc && newargc < MAXARGS; c++)
    {
        newargv[newargc++] = argv[c];
    }

    /* NULL pointer for good measure */
    if (newargc < MAXARGS)
        newargv[newargc] = 0;

    /* convince getopt to re-start */
    optind = 1;

    while ((c = GETOPT(newargc, newargv, gargs)) != EOF)
    {
	switch(c)
	{
	case 'f':
	    break;
	case 'C':
	    mediatype = CDROM;
	    medianame = optarg;
	    break;
	case 'I':
	    init = 1;
	    mediatype = PARTITIONED;
	    medianame = optarg;
	    break;
	case 'U':
	    init = 0;
	    mediatype = PARTITIONED;
	    medianame = optarg;
	    break;
	case 's':
	    mediatype = SEQUENTIAL;
	    medianame = optarg;
	    break;
	case 'b':
	    bootloaderfile = optarg;
	    break;
	case 'k':
	    if ((fd = open(optarg, O_RDONLY)) == -1)
	    {
		perror(optarg);
		return 2;
	    }
	    if (!prepare_loadable(fd, &loadable, &wide))
	    	error(16, optarg);
	    if ((wide && kernel64 != -1) || (!wide && kernel32 != -1))
	        error(17, optarg);
	    lseek(fd, 0, SEEK_SET); /* rewind */
	    if (wide) {
	        kernel64 = fd;
		kern64_nsz = loadable.uncompressed_size;
	    } else {
	        kernel32 = fd;
		kern32_nsz = loadable.uncompressed_size;
	    }
	    break;
	case 'r':
	    if ((ramdisk = open(optarg, O_RDONLY)) == -1)
	    {
		perror(optarg);
		return 2;
	    }
	    break;
	case 'c':
	    commandline = optarg;
	    if (strlen(commandline) >= CMDLINELEN)
		error(3,CMDLINELEN-1,strlen(commandline));
	    break;
	case 'e':
	    if(strcmp(optarg, "2") == 0)
		format_as = 2;
	    else if(strcmp(optarg, "3") == 0)
		format_as = 3;
	    else
		error(0, argv[0]);
	    break;
	case 'v':
	    verbose = 1;
	    break;
	default:
	    error(0, argv[0]);
	    break;
	}
    }

    if ((bootloader = open(bootloaderfile, O_RDONLY)) == -1)
    {
	perror(bootloaderfile);
	return 2;
    }

    if (optind < argc)
    {
	error(0, argv[0]);
    }

    if (medianame == NULL)
    {
	fprintf(stderr, "%s: Need one of -CIs or -U\n", argv[0]);
        error(0, argv[0]);
    }

    switch (mediatype)
    {
    case SEQUENTIAL:
	if ((media = open(medianame, O_RDWR|O_CREAT|O_TRUNC, 0666)) == -1)
	{
	    perror(medianame);
	    return 2;
	}
	do_sequential(media, kernel32, kernel64, commandline, bootloader,
			ramdisk, kern32_nsz, kern64_nsz);
	break;
    case PARTITIONED:
    {
	    struct diskpartition ptab[MAXPARTS];
	    int partitioned;
	    int f0;
	    struct firstblock f;

	    memset(ptab, 0, sizeof ptab);

	    /* non-sequential boot creation requires a file/device */
	    if ((media = open(medianame, O_RDWR)) == -1)
	    {
		perror(medianame);
		if (errno == ENOENT)	/* file probably doesn't exist */
		{
		    error(1);
		}
		return 2;
	    }

	    /* it better have a partition table too... */
	    partitioned = load_partitions(media, ptab, MAXPARTS);
	    if (!partitioned)
	    {
		error(10);
	    }

	    /* find the F0 partition */
	    for (f0 = 0; f0 < MAXPARTS; f0++)
	    {
		if (ptab[f0].id == PALO_PARTITION)
		    break;
	    }
	    if (f0 == MAXPARTS)
		error(11);
	    if(verbose) {
		print_ptab_pretty(ptab, MAXPARTS);

		printf("F0 partition start sector %d length %d\n",
		    ptab[f0].start, ptab[f0].length);
	    }

	    if (format_as) {
		/* if we're going to be a formatted partition, we can't
		 * load anything into it, so check we haven't been asked
		 * to */
		if(kernel32 != -1 || kernel64 != -1 || ramdisk != -1)
			error(18);

		if (do_formatted(init, media, medianame, f0 + 1,
			     ptab[f0].start * 512, ptab[f0].length * 512,
			     bootloader, format_as, commandline))
			error(20);
	    } else
		do_randomaccess(init, media, kernel32, kernel64, commandline,
				bootloader, ramdisk, ptab[f0].start * 512,
				ptab[f0].length * 512,
				kern32_nsz, kern64_nsz);
	}
	break;
    case CDROM:
	if ((media = open(medianame, O_RDWR)) == -1)
	{
	    perror(medianame);
	    return 2;
	}
	do_cdrom(media, kernel32, kernel64, commandline, bootloader, ramdisk,
		kern32_nsz, kern64_nsz);
	break;
    }

    fsync(media);
    close(media);

    return 0;
}
