/*
 * mknbi.c  -  MaKe NetBoot Image for DOS
 *
 * Copyright (C) 1996-2007 Gero Kuhlmann   <gero@gkminix.han.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: mknbi.c,v 1.25 2007/01/06 18:31:22 gkminix Exp $
 */

#define NEED_BINARY 1
#include <common.h>
#include <math.h>
#include <nblib.h>
#include <system/nbloader.h>
#include "mknbi.h"
#include "headers/fat.h"
#include "headers/rd.h"

#ifdef HAVE_LZO
# include <lzo1x.h>
#endif

#ifndef MKNBI_H_DOS
#error Included wrong header file
#endif



/*
 * Forward declarations
 */
static void proc_keep __P((struct cmdopt *opt, char *arg));



/*
 * Copyright information
 */
static const char *copyright[] = {
	COPYRIGHT,
#ifdef HAVE_LZO
	"",
	"Compression code:",
	"Copyright (C) 1996-2002 Markus F.X.J. Oberhumer",
#endif
	NULL
};



/*
 * Check if we have lzo1x_999 available. Compression time is not as important
 * as compression ratio.
 */
#ifdef LZO1X_MEM_COMPRESS
# undef LZO1X_MEM_COMPRESS
#endif
#ifndef LZO_999_UNSUPPORTED
# define LZO1X_MEM_COMPRESS	LZO1X_999_MEM_COMPRESS
# define lzo1x_compress		lzo1x_999_compress
#else
# define LZO1X_MEM_COMPRESS	LZO1X_1_MEM_COMPRESS
# define lzo1x_compress		lzo1x_1_compress
#endif



/*
 * Variables private to this module
 */
static struct sysdef sysdata;		/* System data definition */
static char *batchname = NULL;		/* Name of system to batch process */
static char *savename = NULL;		/* Name of system to use for save */
static int outfile;			/* File handle for output file */
static int rdimage;			/* File handle for ramdisk image */

static int cur_rec_num = -1;		/* Number of current load record */
static struct load_header header;	/* Load header */
static struct load_record *cur_rec;	/* Pointer to current load record */



/*
 * Command line options and arguments
 */
static struct cmdopt opts[] = {
	{ NULL, 0, copyrightmsg, {copyright}, NULL, NULL		},
	{ "batch-sys", 'b', strval, {&batchname},
	  "name of system to process", "SYSTEM"				},
	{ "save-sys", 'S', strval, {&savename},
	  "name of system for saving into database", "SYSTEM"		},
	{ "outfile", 'o', strval, {&sysdata.outname},
	  "name of boot image output file", "FILE"			},
	{ "ramdisk-image", 'r', strval, {&sysdata.rdname},
	  "ramdisk image source file or directory", "FILE|DIR"		},
	{ "ramdisk-size", 's', intval, {&sysdata.rdsize},
	  "size in kB of ramdisk image", "SIZE"				},
	{ "no-hard-disk", 'n', boolval, {&sysdata.nohd},
	  "do not allow hard disk accesses in client", NULL		},
	{ "no-rpl", 'l', boolval, {&sysdata.norpl},
	  "do not allow RPL memory protection", NULL			},
	{ "use-int-15", 'i', boolval, {&sysdata.useint15},
	  "use interrupt 15h ramdisk protection", NULL			},
	{ "single-fat", 'F', boolval, {&sysdata.singlefat},
	  "use only one FAT for ramdisk", NULL				},
	{ "simulate-hard-disk", 'c', boolval, {&sysdata.usehd},
	  "ramdisk simulates hard disk instead of floppy", NULL		},
	{ "compress", 'z', boolval, {&sysdata.compress},
	  "compress ramdisk image", NULL				},
	{ "volume-name", 'V', strval, {&sysdata.volumename},
	  "name of ramdisk volume", "STRING"				},
	{ "keep", 'k', procval, {(voidstar)&proc_keep},
	  "name of module to keep after loading DOS:\n"
	  "  STRING syntax: none|all|undi", "STRING"			},
	{ "rdimage", 0, nonopt, {&sysdata.rdname},
	  "ramdisk image (if not given as option)", NULL		},
	{ "outfile", 0, nonopt, {&sysdata.outname},
	  "output file (if not given as option)", NULL			},
	{ NULL, 0, noval, {NULL}, NULL, NULL				}
};



/*
 * Write a buffer into the output file and update the load record
 */
static void putrec __F((recnum, src, size),
				int recnum AND
				__u8 *src AND
				size_t size)
{
  unsigned long l;

  assert(cur_rec_num == recnum);
  (void)nbwrite(src, size, outfile);
  l = get_long(cur_rec->ilength) + size;
  assign(cur_rec->ilength.low, htot(low_word(l)));
  assign(cur_rec->ilength.high, htot(high_word(l)));
  l = get_long(cur_rec->mlength) + size;
  assign(cur_rec->mlength.low, htot(low_word(l)));
  assign(cur_rec->mlength.high, htot(high_word(l)));
}



/*
 * Initialize a load record
 */
static void initrec __F((recnum, segment, flags, vendor_size),
				int recnum AND
				unsigned long segment AND
				int flags AND
				int vendor_size)
{
  cur_rec_num++;
  assert(cur_rec_num == recnum);
  if (cur_rec_num > 0)
	cur_rec = (struct load_record *)((__u8 *)cur_rec +
					((cur_rec->rlength << 2) & 0x3c) +
					((cur_rec->rlength >> 2) & 0x3c));
  cur_rec->rlength      = (((sizeof(struct load_record) -
#ifdef USE_VENDOR
		             sizeof(union vendor_data)
#else
		             0
#endif
		                                      ) >> 2) |
                           (((vendor_size + 3) & 0x3c) << 2)) & 0xff;
  cur_rec->rtag1        = (recnum + LOADER_TAG) & 0xff;
  cur_rec->rflags       = flags & 0xff;
  assign(cur_rec->address.low, htot(low_word(segment << 4)));
  assign(cur_rec->address.high, htot(high_word(segment << 4)));
}



/*
 * Process boot loader image
 */
static void do_loader __F((loadflags), int loadflags)
{
  __u8 *dataptr;
  unsigned long tsize, dsize, ssize, msize;
  size_t osize, lsize;
  struct nbld_header *hp;

  /*
   * Determine which of the two images to load, it's load size and
   * it's memory size.
   */
  osize = (debug ? firstd_data_size : first_data_size);
  lsize = roundup(osize, SECTSIZE);
  dataptr = (__u8 *)nbmalloc(lsize);
  memcpy(dataptr, (debug ? firstd_data : first_data), osize);

  hp = (struct nbld_header *)&(dataptr[NBLD_OFFSET]);
  tsize = get_word(hp->textsize);
  dsize = get_word(hp->datasize);
  ssize = get_word(hp->heapsize) + get_word(hp->stacksize);
  msize = roundup(tsize, 16) + roundup((dsize + ssize), 16);

  /*
   * Check that the size values are within range. We can use assert()
   * here because an error should never happen. The images are stored
   * within this program and can never change.
   */
  assert(lsize <= LOADERLSIZE);
  assert(msize <= LOADERMSIZE && msize >= lsize);

  /*
   * If the ramdisk image gets compressed, the loader needs some
   * additional memory for the decompression code.
   */
  if ((loadflags & BOOT_FLAG_COMPR) == BOOT_FLAG_COMPR)
	msize = (msize < 65536 ? 65536 : msize) + LDRADDSIZE;

  /*
   * Finally copy the image into the output file and set the load record
   * according to the sizes determined above.
   */
  initrec(LOADERNUM, LOADERSEG, 0, sizeof(cur_rec->vendor_data.loadflags));
  cur_rec->vendor_data.loadflags = loadflags & 0xff;
  putrec(LOADERNUM, dataptr, lsize);
  assign(cur_rec->mlength.low, htot(low_word(msize)));
  assign(cur_rec->mlength.high, htot(high_word(msize)));

  /*
   * Set the image entry address correctly.
   */
  assign(header.execute.segment, htot(LOADERSEG));
  assign(header.execute.offset, getval(hp->nbsetup));
  free(dataptr);
}



/*
 * Process ramdisk image file
 */
static void do_ramdisk __F((geometry, imgsize, loadflags),
				struct disk_geometry *geometry AND
				unsigned long imgsize AND
				int loadflags)
{
  __u8 *inbuf;
#ifdef HAVE_LZO
  __u8 *outbuf;
  __u8 *rdhdr;
  __u16 *blkptr;
  lzo_voidp wrkmem;
  long hdrpos;
  size_t hdrsize = 0;
  size_t blknum = 0;
#endif
  unsigned long rdsize, maxsize, outsize, l;
  size_t bufsize;
  int i;

  /*
   * Check for correct ramdisk image size. When we have to compress
   * the ramdisk image, we have to take care for some overhead in case
   * the image data is incompressible, and for the compression header.
   */
  rdsize = roundup(get_long(geometry->num_sectors) * SECTSIZE, 1024);
  maxsize = MIN((unsigned long)MAX_RDSIZE * 1024, rdsize);
  outsize = 0;
  if ((loadflags & BOOT_FLAG_COMPR) == BOOT_FLAG_COMPR) {
#ifdef HAVE_LZO
	outsize = maxsize;
	if ((imgsize / RDBLKSIZE) < (RDMAXBLKNUM - 1)) {
		blknum = (size_t)howmany(imgsize, RDBLKSIZE);
		hdrsize = roundup(sizeof(struct rdheader), 2) + blknum * 2 + 2;
		outsize = hdrsize + (imgsize / 64 + 16 + 3);
	}
#else
	prnerr("internal error: ramdisk compression not supported");
	nbexit(EXIT_INTERNAL);
#endif
  }
  if (outsize >= maxsize || imgsize > (maxsize - outsize)) {
	prnerr("ram disk image too large");
	nbexit(EXIT_DOS_RDSIZE);
  }

  /*
   * Generate the load record which contains the disk geometry, and
   * then write the partition table, all hidden sectors and the new
   * boot record.
   * If the ramdisk gets protected via the interrupt 15h mechanism,
   * it has to be loaded at the end of physical memory.
   */
  i = BOOT_FLAG_EOF;
  if ((loadflags & BOOT_FLAG_INT15) == BOOT_FLAG_INT15)
	i |= BOOT_FLAG_B1;
  initrec(RAMDISKNUM, 0, i, sizeof(struct disk_geometry));
  memcpy(&(cur_rec->vendor_data.geometry), geometry,
					sizeof(struct disk_geometry));

  /*
   * Allocate header for compressed ramdisk and block buffer, and then
   * setup the header and write it into the output file.
   */
#ifdef HAVE_LZO
  if ((loadflags & BOOT_FLAG_COMPR) == BOOT_FLAG_COMPR) {
	struct rdheader *hdrptr;

	rdhdr = (__u8 *)nbmalloc(hdrsize);
	hdrptr = (struct rdheader *)rdhdr;
	bytecpy(RDHDRSIG, hdrptr->sig, RDSIGLEN);
	l = imgsize - (blknum - 1) * RDBLKSIZE;
	assert(l <= RDBLKSIZE);
	assign(hdrptr->lastsize, htot(l & 0xffff));
	assign(hdrptr->blknum, htot(blknum & 0xffff));
	assign(hdrptr->blksize, htot(RDBLKSIZE));
	assign(hdrptr->hdrsize, htot(hdrsize & 0xffff));
	blkptr = (__u16 *)(rdhdr + sizeof(struct rdheader));

	if ((hdrpos = lseek(outfile, 0, SEEK_CUR)) == -1) {
		prnerr("unable to determine current position of %s",
							sysdata.outname);
		nbexit(EXIT_SEEK);
	}
	putrec(RAMDISKNUM, rdhdr, hdrsize);
	outsize = hdrsize;
  } else {
	rdhdr = NULL;
	blkptr = NULL;
	hdrpos = -1;
	outsize = 0;
  }
#else
  outsize = 0;
#endif

  /*
   * Create input, output and work buffers
   */
  inbuf = (__u8 *)nbmalloc(RDBLKSIZE);
#ifdef HAVE_LZO
  if ((loadflags & BOOT_FLAG_COMPR) == BOOT_FLAG_COMPR) {
	if (lzo_init() != LZO_E_OK) {
		prnerr("unable to initialize compression routines");
		nbexit(EXIT_INTERNAL);
	}
	outbuf = (__u8 *)nbmalloc(RDBLKSIZE + RDBLKSIZE / 64 + 16 + 3);
	wrkmem = (lzo_voidp)nbmalloc(LZO1X_MEM_COMPRESS);
  } else {
	outbuf = NULL;
	wrkmem = NULL;
  }
#endif

  /*
   * Copy the ramdisk image from the temporary file into the final
   * output file. This will also compress all input data if requested.
   */
  do {
	bufsize = nbread(inbuf, RDBLKSIZE, rdimage);
	if (bufsize > 0) {
#ifdef HAVE_LZO
		if ((loadflags & BOOT_FLAG_COMPR) == BOOT_FLAG_COMPR) {
			lzo_uint outlen, optlen;
			__u8 *cp;

			outlen = RDBLKSIZE + RDBLKSIZE / 64 + 16 + 3;
			i = lzo1x_compress(inbuf, bufsize,
						outbuf, &outlen, wrkmem);
			if (i != LZO_E_OK) {
				/* This should never happen */
				prnerr("unable to compress ramdisk");
				nbexit(EXIT_INTERNAL);
			}
			if (outlen >= (lzo_uint)bufsize) {
				/*
				 * If the block is not compressible, copy it
				 * into the output file uncompressed. The de-
				 * compressor can identify such blocks by
				 * inspecting the compressed block size. If it
				 * equals RDBLKSIZE, the block is uncompressed.
				 */
				cp = inbuf;
				outlen = (lzo_uint)bufsize;
			} else {
				/*
				 * Optimize the compressed data. This does not
				 * shrink the data even a bit, but it speeds
				 * decompression a little.
				 */
				optlen = (lzo_uint)bufsize;
				i = lzo1x_optimize(outbuf, outlen,
						inbuf, &optlen, NULL);
				if (i != LZO_E_OK ||
				    optlen != (lzo_uint)bufsize) {
					/* This should never happen */
					prnerr("unable to optimize ramdisk compression");
					nbexit(EXIT_INTERNAL);
				}
				cp = outbuf;
			}
			assert(outlen <= 0xffff);
			putrec(RAMDISKNUM, cp, (size_t)outlen);
			assign(*blkptr, htot(outlen & 0xffff));
			outsize += outlen;
			blkptr++;
		} else {
#endif
			putrec(RAMDISKNUM, inbuf, bufsize);
			outsize += bufsize;
#ifdef HAVE_LZO
		}
#endif
	}
  } while (bufsize == RDBLKSIZE);

  /*
   * Fillup the ramdisk image to a multiple of 512 bytes, and update the
   * size parameters in the load record.
   */
  bufsize = (size_t)(roundup(outsize, SECTSIZE) - outsize);
  if (bufsize > 0) {
	memzero(inbuf, bufsize);
	putrec(RAMDISKNUM, inbuf, bufsize);
	outsize += bufsize;
  }
#ifdef HAVE_LZO
  if ((loadflags & BOOT_FLAG_COMPR) == BOOT_FLAG_COMPR) {
	int ratio;

	if (imgsize < 10000UL)
		ratio = (int)howmany(outsize * 100, imgsize);
	else if (imgsize < 100000UL)
		ratio = (int)howmany(outsize * 10, (imgsize + 5) / 10);
	else
		ratio = (int)howmany(outsize, (imgsize + 50) / 100);
	prnlog(LOGLEVEL_NOTICE, "Compression ratio   = %d%%\n", ratio);
  }
#endif
  assign(cur_rec->mlength.low,  htot(low_word(rdsize)));
  assign(cur_rec->mlength.high, htot(high_word(rdsize)));
  l = (((loadflags & BOOT_FLAG_INT15) == BOOT_FLAG_INT15) ? rdsize : RDADDR);
  assign(cur_rec->address.low,  htot(low_word(l)));
  assign(cur_rec->address.high, htot(high_word(l)));

  /*
   * Compute the checksum of the compressed header and write it into the
   * output file
   */
#ifdef HAVE_LZO
  if ((loadflags & BOOT_FLAG_COMPR) == BOOT_FLAG_COMPR) {
	unsigned int chksum = 0;

	blkptr = (__u16 *)rdhdr;
	while (blkptr < (__u16 *)(rdhdr + hdrsize - 2)) {
		chksum = (chksum + get_word(*blkptr)) & 0xffff;
		blkptr++;
	}
	assign(*blkptr, htot((0 - chksum) & 0xffff));

	if (lseek(outfile, hdrpos, SEEK_SET) == -1) {
		prnerr("unable to seek in %s", sysdata.outname);
		nbexit(EXIT_SEEK);
	}
	(void)nbwrite(rdhdr, hdrsize, outfile);
  }
#endif

  /*
   * Clear all previously allocated memory blocks
   */
#ifdef HAVE_LZO
  if (rdhdr != NULL)
	free(rdhdr);
  if (outbuf != NULL)
	free(outbuf);
  if (wrkmem != NULL)
	free(wrkmem);
#endif
  if (inbuf != NULL)
	free(inbuf);
}


/*
 * Dump the load record information to stderr
 */
static void dump_header __F((lh), const struct load_header *lh)
{
  static const char *s_tags[] = { /* LOADERNUM */  "primary boot loader",
				  /* RAMDISKNUM */ "ramdisk image"};
  static const char *s_flags[]= { "absolute address",
				  "after previous segment",
				  "at end of memory",
				  "before previous segment"};

  struct load_record *lr;
  char *vendstr = NULL;
  int i, num = 0;

  if (logfd(LOGLEVEL_INFO) == NULL)
	return;

  vendstr = bytestr(lh->dummy, (lh->hlength >> 2) & 0x3c);
  prnlog(LOGLEVEL_INFO,
          "\nLoad record information:\n"
	  "  Magic number:     0x%08lX\n"
	  "  Length of header: %d bytes (standard) + %d bytes (vendor)\n"
	  "  Flags:            0x%08lX\n"
	  "  Location address: %04X:%04X\n"
	  "  Execute address:  %04X:%04X\n"
	  "  Vendor data:      %s\n",
	  get_long(lh->magic),
	  (lh->hlength << 2) & 0x3c,
	  (lh->hlength >> 2) & 0x3c,
	  (unsigned long)lh->hflags1 +
		((unsigned long)lh->hflags2 << 8) +
		((unsigned long)lh->hflags3 << 16),
	  get_word(lh->locn.segment), get_word(lh->locn.offset),
	  get_word(lh->execute.segment), get_word(lh->execute.offset),
	  vendstr);

  i  = ((lh->hlength >> 2) & 0x3c) + ((lh->hlength << 2) & 0x3c);
  lr = (struct load_record *)&(((__u8 *)lh)[i]);

  while (TRUE) {
	prnlog(LOGLEVEL_INFO,
	    "\nRecord #%d:\n"
	    "  Length of header: %d bytes (standard) + %d bytes (vendor)\n"
	    "  Vendor tag:       0x%02X (%s)\n"
	    "  Reserved flags:   0x%02X\n"
	    "  Flags:            0x%02X (%s%s)\n"
	    "  Load address:     0x%08lX%s\n"
	    "  Image length:     0x%08lX bytes\n"
	    "  Memory length:    0x%08lX bytes\n",
	    ++num,
	    (lr->rlength << 2) & 0x3c,
	    (lr->rlength >> 2) & 0x3c,
	    (int)lr->rtag1,
	    (lr->rtag1 < (__u8)LOADER_TAG) ||
		(lr->rtag1 - (__u8)LOADER_TAG >= (__u8)NUM_RECORDS) ?
		"unknown" : s_tags[(unsigned)(lr->rtag1 - LOADER_TAG)],
	    (int)lr->rtag2,
	    (int)lr->rflags, s_flags[(unsigned)(lr->rflags & 0x03)],
	    lr->rflags & BOOT_FLAG_EOF ? ", last record" : "",
	    get_long(lr->address),
	    get_long(lr->address) >= 0x100000L &&
	    (lr->rflags & 0x03) == 0? " (high memory)" : "",
	    get_long(lr->ilength),
	    get_long(lr->mlength));

	if (lr->rtag1 - (__u8)LOADER_TAG == (__u8)RAMDISKNUM) {
		int heads = get_word(lr->vendor_data.geometry.num_heads);
		int cyls = get_word(lr->vendor_data.geometry.cylinders);
		int spt = get_word(lr->vendor_data.geometry.sect_per_track);
		int diskid = get_word(lr->vendor_data.geometry.boot_drive);

		prnlog(LOGLEVEL_INFO,
		       "  Vendor data:      "
		       "%d cylinders; %d heads; %d sectors; disk id: 0x%02X\n",
	               cyls,
	               heads,
	               spt,
		       diskid);
	} else if (lr->rtag1 - (__u8)LOADER_TAG == (__u8)LOADERNUM) {
		int flags = lr->vendor_data.loadflags;

		prnlog(LOGLEVEL_INFO,
		       "  Vendor data:       "
		       "NO-RPL flag %s\n"
		       "                     "
		       "USE-INT-15 flag %s\n"
		       "                     "
		       "COMPRESS flag %s\n",
		       flags & BOOT_FLAG_NORPL ? "true" : "false",
		       flags & BOOT_FLAG_INT15 ? "true" : "false",
		       flags & BOOT_FLAG_COMPR ? "true" : "false");
	} else {
		prnlog(LOGLEVEL_INFO,
		       "  Vendor data:      %s\n",
	               lr->rlength & 0xf0 ? "unknown" : "none");
	}

	if (lr->rflags & BOOT_FLAG_EOF)
		break;

	i  = ((lr->rlength >> 2) & 0x3c) + ((lr->rlength << 2) & 0x3c);
	lr = (struct load_record *)&(((__u8 *)lr)[i]);
  }
  free(vendstr);
}



/*
 * Process keep command line option
 */
static void proc_keep __F((opt, arg),
				struct cmdopt *opt AND
				char *arg)
{
  char *cp;

  if ((cp = getkeepflag(arg, &sysdata)) != NULL) {
	prnerr(cp);
	nbexit(EXIT_USAGE);
  }
}



/*
 * Main program
 */
int main __F((argc, argv), int argc AND char **argv)
{
  struct disk_geometry geometry;
  unsigned long imgsize;
  int loadflags;

  /* Initialize parameters */
  memzero(&sysdata, sizeof(sysdata));
  sysdata.keepflag = KEEP_MISSING;

  /* Parse options and read configuration file */
  nbsetup(argc, argv, opts, NULL);
  if (batchname != NULL) {
	if (savename != NULL) {
		prnerr("-b and -S options cannot get used together");
		nbexit(EXIT_USAGE);
	}
	getdb(batchname, &sysdata);
  }
  if (sysdata.rdsize > MAX_RDSIZE) {
	prnerr("ramdisk size must be < %d kB", MAX_RDSIZE);
	nbexit(EXIT_USAGE);
  }
  if (sysdata.rdname == NULL) {
	prnerr("need ramdisk image file or directory name");
	nbexit(EXIT_USAGE);
  }
  if (sysdata.outname == NULL) {
	prnerr("need output file name");
	nbexit(EXIT_USAGE);
  }
  if ((sysdata.volumename != NULL) &&
      ((int)strlen(sysdata.volumename) > MAX_VOLNAME_LEN)) {
	prnerr("volume name longer than %d characters", MAX_VOLNAME_LEN);
	nbexit(EXIT_USAGE);
  }
  if (sysdata.keepflag == KEEP_MISSING)
	sysdata.keepflag = KEEP_NONE;
  if (sysdata.usehd & sysdata.nohd) {
	prnerr("options -c and -n can't be used simultaneously");
	nbexit(EXIT_USAGE);
  }
#ifndef HAVE_LZO
  if (sysdata.compress) {
	prnerr("ramdisk compression not supported - option ignored");
	sysdata.compress = FALSE;
  }
#endif

  /* Prepare volume name */
  if (sysdata.volumename != NULL) {
	int i, len;
	char *buf = NULL;

	len = strlen(sysdata.volumename);
	if (len > 0) {
		buf = (char *)nbmalloc(MAX_VOLNAME_LEN + 1);
		for (i = 0; i < MAX_VOLNAME_LEN; i++) {
			if (i < len)
				buf[i] = toupper(sysdata.volumename[i]);
			else
				buf[i] = ' ';
		}
		buf[i] = '\0';
	}
	free(sysdata.volumename);
	sysdata.volumename = buf;
  }

  /* Open the input files */
  loadflags = (sysdata.useint15 ? BOOT_FLAG_INT15 : 0) |
			(sysdata.norpl ? BOOT_FLAG_NORPL : 0) |
			(sysdata.compress ? BOOT_FLAG_COMPR : 0);
  rdimage = openrd(&sysdata, &imgsize, &geometry, &loadflags);
  prnlog(LOGLEVEL_NOTICE, "Output filename     = %s\n", sysdata.outname);
  prnlog(LOGLEVEL_NOTICE, "Ramdisk filename    = %s\n", sysdata.rdname);
  prnlog(LOGLEVEL_NOTICE, "Ramdisk image size  = %lu\n", imgsize);

  /* Open the output file */
  if ((outfile = creat(sysdata.outname, 0644)) < 0) {
	prnerr("unable to create output file %s", sysdata.outname);
	nbexit(EXIT_DOS_IMGCREATE);
  }

  /* Initialize the boot header */
  memzero(&header, sizeof(header));
  assign(header.magic.low,       htot(low_word(NBHEADER_MAGIC)));
  assign(header.magic.high,      htot(high_word(NBHEADER_MAGIC)));
  assign(header.locn.segment,    htot(HEADERSEG));
  assign(header.locn.offset,     htot(0));
  assign(header.bootsig,         htot(NBHEADER_SIG));
  header.hlength = ((__u8)((int)(header.dummy - (__u8 *)&header) /
				sizeof(__u32)) & 0x0f) |
				((__u8)(VENDOR_SIZE << 4) & 0xf0);
  header.hflags1 = (sysdata.keepflag == KEEP_ALL ? FLAG1_KEEP_ALL :
                   (sysdata.keepflag == KEEP_UNDI ? FLAG1_KEEP_UNDI : 0));
  bytecpy(VENDOR_MAGIC, header.dummy, VENDOR_SIZE * sizeof(__u32));
  (void)nbwrite((__u8 *)&header, sizeof(header), outfile);

  /* Initialize pointer to first load record */
  cur_rec = (struct load_record *)&(header.dummy[VENDOR_SIZE * sizeof(__u32)]);

  /* Process the boot loader record */
  do_loader(loadflags);

  /* Process the ramdisk image */
  do_ramdisk(&geometry, imgsize, loadflags);

  /* After writing out all this stuff, finally update the boot header */
  if (lseek(outfile, 0, SEEK_SET) != 0) {
	prnerr("unable to seek to beginning of %s", sysdata.outname);
	nbexit(EXIT_SEEK);
  }
  (void)nbwrite((__u8 *)&header, sizeof(header), outfile);

  /* Close the input and output files */
  close(outfile);
  closetemp(rdimage);

  /*
   * If user asked for detailed output, parse the header and output all of
   * the load record information
   */
  dump_header(&header);
  prnlog(LOGLEVEL_NOTICE, "Wrote image file \"%s\"\n", sysdata.outname);

  /* Write system definition into systems database */
  if (savename != NULL) {
	putdb(savename, &sysdata);
	prnlog(LOGLEVEL_NOTICE, "Wrote database entry \"%s\"\n\n", savename);
  }
  nbexit(EXIT_SUCCESS);
#if !defined(__GNUC__) || __GNUC__ < 3
  /* Make the compiler happy, GCC knows that nbexit() never returns */
  return(0);
#endif
}

