/*
 * mknbi.c  -  MaKe NetBoot Image for Linux
 *
 * Copyright (C) 1995-2007 Gero Kuhlmann   <gero@gkminix.han.de>
 * Copyright (C) 1996,1997 Gero Kuhlmann   <gero@gkminix.han.de>
 *                and Markus Gutschke <gutschk@math.uni-muenster.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.24 2007/01/06 18:31:26 gkminix Exp $
 */

#define NEED_BINARY 1
#include <common.h>
#ifdef HAVE_INET
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#include <nblib.h>
#include <system/nbloader.h>
#include "mknbi.h"

#ifndef MKNBI_H_LINUX
#error Included wrong header file
#endif


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 char *rootdev = NULL;		/* Name of root device */
static char *initprog = NULL;		/* Path name of init program */
static int kimage = -1;			/* File handle for kernel image */
static int rdimage = -1;		/* File handle for ramdisk image */
static int outfile;			/* File handle for output file */

static struct setup_header setup_hdr;	/* Setup header of Linux kernel */
static int kernel_ver_major;		/* Linux kernel major version number */
static int kernel_ver_minor;		/* Linux kernel minor version number */
static int kernel_flags = 0;		/* Flags for use by the Linux kernel */

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 */

static __u8 copyrec_buf[SECTSIZE];



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



/*
 * Copyright information
 */
static const char *copyright[] = {
	COPYRIGHT,
	"Copyright (C) 1996,1997 Markus Gutschke",
	NULL
};



/*
 * 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", "FILE"				},
	{ "append", 'a', strval, {&sysdata.append},
	  "append string to end of kernel command line", "STRING"	},
	{ "root-dir", 'd', strval, {&sysdata.nfsdir},
	  "NFS mounted root dir:\n"
	  "  STRING syntax: rom|ram|initrd|kernel|<dir>", "STRING"				},
	{ "ip-addrs", 'i', strval, {&sysdata.addrs},
	  "IP addresses for Linux kernel:\n"
	  "  STRING syntax: rom|kernel"
#ifdef HAVE_INET
	  "|<IP-addrs>"
#endif
							, "STRING"	},
	{ "kernel", 'k', strval, {&sysdata.kname},
	  "path to Linux kernel image", "FILE"				},
	{ "ramdisk-mode", 'l', procval, {(voidstar)&proc_rdmode},
	  "loading mode for ramdisk:\n"
	  "  STRING syntax: auto|eom|<fixed address>", "STRING"		},
	{ "vga-mode", 'm', procval, {(voidstar)&proc_vgamode},
	  "VGA mode when Linux kernel starts:\n"
	  "  STRING syntax: normal|extended|ask|\n"
	  "                 default|<number>", "STRING"			},
	{ "kernel", 0, nonopt, {&sysdata.kname},
	  "Linux kernel 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				}
};



/*
 * Get setup header from Linux kernel
 */
static void get_setup __F_NOARGS
{
  unsigned int i;
  char version_buf[128];
  
  /* Seek to kernel header */
  assert(kimage >= 0);
  if (lseek(kimage, SECTSIZE, SEEK_SET) != SECTSIZE) {
	prnerr("unable to seek to Linux kernel header");
	nbexit(EXIT_SEEK);
  }

  /* Read kernel header */
  i = nbread((__u8 *)&setup_hdr, sizeof(setup_hdr), kimage);
  if (i != sizeof(setup_hdr) ||
      !bytecmp(SETUP_MAGIC_STR, setup_hdr.magic, sizeof(setup_hdr.magic)) ||
      get_word(setup_hdr.version) < SETUP_VERSION_OLD) {
	prnerr("invalid linux kernel or kernel too old");
	nbexit(EXIT_LINUX_INVKERN);
  }

  /* Seek to kernel version string */
  i = get_word(setup_hdr.kernel_version);
  if (i > (unsigned int)0x8000 ||
      (unsigned int)lseek(kimage, i + SECTSIZE, SEEK_SET) != (i + SECTSIZE)) {
	prnerr("unable to seek to Linux kernel version string");
	nbexit(EXIT_SEEK);
  }

  /* Read kernel version string */
  i = nbread((__u8 *)version_buf, sizeof(version_buf) - 1, kimage);
  version_buf[i] = '\0';
  i = strlen(version_buf);
  if ((int)strlen(version_buf) < 3 ||
      sscanf(version_buf, "%d.%d", &kernel_ver_major, &kernel_ver_minor) != 2) {
	prnerr("invalid Linux kernel version string");
	nbexit(EXIT_LINUX_INVKERN);
  }

  /* Seek back to start of kernel file */
  if (lseek(kimage, 0, SEEK_SET) != 0) {
	prnerr("unable to seek to Linux kernel header");
	nbexit(EXIT_SEEK);
  }

  /* Setup kernel flags according to kernel version and flags */
  if (kernel_ver_major >= 2) {
	if (kernel_ver_minor >= 2)
		kernel_flags |= KRN_USE_IP;
	if (kernel_ver_minor >= 4)
		kernel_flags |= KRN_NFS_IP;
  }
  if (!(setup_hdr.loadflags & SETUP_LDFLAGS_HIGH))
	kernel_flags |= KRN_LOW;
}


  
/*
 * 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)));
}



/*
 * Copy a certain number of bytes from the kernel image file into the
 * boot file
 */
static unsigned long copyrec __F((recnum, remaining, image),
				int recnum AND
				unsigned long remaining AND
				int image)
{
  unsigned long bytes_read = 0;
  size_t size = SECTSIZE;
  size_t i = SECTSIZE;

  /* Check if we have to read until EOF */
  if (remaining == 0)
	remaining = ULONG_MAX;

  /* Copy data in SECTSIZE chunks */
  while (remaining > 0 && i == size) {
	if (remaining < SECTSIZE)
		size = remaining;
	if ((i = nbread(copyrec_buf, size, image)) > 0) {
		putrec(recnum, copyrec_buf, i);
		bytes_read += i;
		remaining -= i;
	}
  }

  /* Roundup the last chunk to a full SECTSIZE */
  if (i > 0 && i < SECTSIZE) {
	size = SECTSIZE - i;
	memzero(&(copyrec_buf[i]), size);
	putrec(recnum, &(copyrec_buf[i]), size);
  }
  return(bytes_read);
}



/*
 * 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_NOARGS
{
  __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) + 1;

  /*
   * 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 <= BOOTLLSIZE);
  assert(msize <= BOOTLMSIZE && msize >= lsize);

  /*
   * Finally copy the image into the output file and set the load record
   * according to the sizes determined above.
   */
  initrec(BOOTLNUM, DEF_BOOTLSEG, 0, 0);
  putrec(BOOTLNUM, 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(DEF_BOOTLSEG));
  assign(header.execute.offset, getval(hp->nbsetup));
  free(dataptr);
}



/*
 * Process command line image
 */
static void do_cmdline __F((cmdline), char *cmdline)
{
  size_t ilen, olen;
  __u8 *bufp;

  /*
   * Copy the command line into a temporary buffer. This is necessary in
   * case our host uses a different character set as the Linux client.
   * bytecpy() cares for the necessary character set conversion.
   */
  ilen = bytelen(cmdline);
  olen = roundup(ilen + 2, SECTSIZE);
  bufp = (__u8 *)nbmalloc(olen * sizeof(__u8));
  bytecpy(cmdline, bufp, ilen);
  bufp[ilen] = 0;
  initrec(CMDLNUM, DEF_CMDLSEG, 0, 0);
  putrec(CMDLNUM, bufp, olen);
  assign(cur_rec->mlength.low, htot(low_word(CMDLMSIZE)));
  assign(cur_rec->mlength.high, htot(high_word(CMDLMSIZE)));
  free(bufp);
}



/*
 * Process kernel image file
 */
static void do_kernel __F_NOARGS
{
  int flags, setup_sects;
  unsigned long l;

  /* Process the floppy boot loader code */
  initrec(INITNUM, DEF_INITSEG, 0, 0);
  if (copyrec(INITNUM, INITLSIZE, kimage) != INITLSIZE) {
	prnerr("unexpected end of kernel image file");
	nbexit(EXIT_LINUX_KERNEOF);
  }
  if (getval(*((__u16 *)&(copyrec_buf[BOOT_SIG_OFF]))) !=
							htot(BOOT_SIGNATURE)){
	prnerr("could not find magic number in kernel boot sector");
	nbexit(EXIT_LINUX_INVKERN);
  }
  setup_sects = copyrec_buf[BOOT_SETUP_SEGS];
  if (setup_sects == 0)
	/* See /usr/src/linux/Documentation/i386/boot.txt for explanation */
	setup_sects = 4;
  assign(cur_rec->mlength.low, htot(low_word(INITMSIZE)));
  assign(cur_rec->mlength.high, htot(high_word(INITMSIZE)));

  /* Process the setup code. It is always a multiple of SECTSIZE. */
  if ((l = setup_sects * SECTSIZE) > SETUPLSIZE) {
	prnerr("setup code of kernel image too large");
	nbexit(EXIT_LINUX_INVKERN);
  }
  initrec(SETUPNUM, DEF_SETUPSEG, 0, 0);
  if (copyrec(SETUPNUM, l, kimage) != l) {
	prnerr("unexpected end of kernel image file");
	nbexit(EXIT_LINUX_KERNEOF);
  }
  assign(cur_rec->mlength.low, htot(low_word(SETUPMSIZE)));
  assign(cur_rec->mlength.high, htot(high_word(SETUPMSIZE)));

  /* Process the kernel */
  flags = (rdimage >= 0) ? 0 : BOOT_FLAG_EOF;
  initrec(KERNELNUM, DEF_SYSSEG, flags, sizeof(cur_rec->vendor_data));
  cur_rec->vendor_data.krnflags = kernel_flags & 0x00FF;
  (void)copyrec(KERNELNUM, 0, kimage);
  assign(cur_rec->mlength.low, cur_rec->ilength.low);
  assign(cur_rec->mlength.high, cur_rec->ilength.high);

  /* Process the ramdisk image */
  if (rdimage >= 0) {
	unsigned long imagestart = get_long(cur_rec->address) +
						get_long(cur_rec->mlength);

	/* Determine position of ram disk image */
	if (imagestart < 0x100000L)
		imagestart = 0x100000L;
	if (sysdata.rdmode != RD_EOM) {
		if (sysdata.rdmode == RD_AUTO)
			sysdata.rdlocation = imagestart;
		else if (sysdata.rdmode == RD_FIXED)
			/* always align to 4kB page boundary */
			sysdata.rdlocation &= ~0xfffL;
		if (sysdata.rdlocation < imagestart) {
			prnerr("ramdisk location within linux kernel");
			nbexit(EXIT_LINUX_RDLOC);
		}
	}

	/* Process the ram disk image, length has to be a multiple of 4kB */
	flags = (sysdata.rdmode == RD_EOM ? BOOT_FLAG_B1 : 0) | BOOT_FLAG_EOF;
	initrec(RAMDISKNUM, 0, flags, sizeof(cur_rec->vendor_data));
	cur_rec->vendor_data.rdflags = sysdata.rdmode & 0xff;
	l = (copyrec(RAMDISKNUM, 0, rdimage) + 0xfffL) & ~0xfffL;
	assign(cur_rec->mlength.low, htot(low_word(l)));
	assign(cur_rec->mlength.high, htot(high_word(l)));
	if (sysdata.rdmode != RD_EOM)
		l = sysdata.rdlocation;
	assign(cur_rec->address.low, htot(low_word(l)));
	assign(cur_rec->address.high, htot(high_word(l)));
  }
}



/*
 * Remove duplicate and contradictory entries from the commandline
 */
static void cleanup_cmdline __F((cmdline), char *cmdline)
{
  static const char *entries[] = { " " ROOT_ARG,
                                   " " INIT_ARG,
                                   " " NFS_IP,
                                   " " NFS_ROOT,
                                   " " NFS_ADDRS,
                                   NULL };

  const char **cur_entry;
  char *ptr1, *ptr2;

  /*
   * This code assumes, that the first entry in the command line is a word
   * followed by a space character. This word must not contain the letter 'r'.
   * This assumptions holds, because the standard command line adds the word
   * "auto " to the beginning of the command line!
   */
  assert(!strncmp(cmdline, "auto ", 5));
  for (cur_entry = entries; *cur_entry; cur_entry++) {
	while ((ptr1 = strstr(cmdline, *cur_entry)) != NULL &&
	       strstr(ptr1+1, *cur_entry) != NULL) {
		ptr2 = strchr(ptr1+1, ' ');
		memmove(ptr1+1, ptr2+1, strlen(ptr2) + 1);
	}
  }

again: /* Remove multiple occurrences of "ro" and "rw" */
  for (ptr1 = strchr(cmdline, 'r'); ptr1 != NULL; ptr1 = strchr(ptr1+1, 'r')) {
	if (ptr1[-1] == ' ' && (ptr1[1] == 'o' || ptr1[1] == 'w') &&
	    ptr1[2] == ' ') {
		for (ptr2 = strchr(ptr1+3, 'r'); ptr2 != NULL;
		     ptr2 = strchr(ptr2+1, 'r')) {
			if (ptr2[-1] == ' ' &&
			    (ptr2[1] == 'o' || ptr2[1] == 'w') &&
			    (ptr2[2] == ' ' || ptr2[2] == '\0')) {
				memmove(ptr1, ptr1+3, strlen(ptr1+2) + 1);
				goto again;
			}
		}
	}
  }

  /* Remove the "auto" at the beginning if "noauto" found */
  if ((ptr1 = strstr(cmdline, " noauto")) != NULL &&
      (ptr1[7] == ' ' || ptr1[7] == '\000')) {
	/* For this code we assume "auto " at the beginning of the cmd line */
	memmove(cmdline, cmdline+5, (ptr1 - cmdline));
	memmove(ptr1-5, ptr1+7, strlen(ptr1+7) + 1);
  }
}



/*
 * Dump load record vendor information
 */
static char *decode_vendor_info __F((lr), const struct load_record *lr)
{
  static char *s_rd[]   = { /* RD_AUTO */  "RD: auto positioning",
			    /* RD_EOM */   "RD: positioned by Boot-Rom",
			    /* RD_FIXED */ "RD: fixed memory location" };

  if ((lr->rlength & 0xf0) == 0)
	return("none");
  else if ((lr->rtag1 - (__u8)LOADER_TAG) == (__u8)RAMDISKNUM)
	return(lr->vendor_data.rdflags <= (__u8)RD_MAXFLAG ?
			s_rd[(unsigned)(lr->vendor_data.rdflags)] : "illegal");
  else if ((lr->rtag1 - (__u8)LOADER_TAG) == (__u8)KERNELNUM)
	return(lr->vendor_data.krnflags & (__u8)KRN_USE_IP ?
			"use IP string" : "use NFSADDRS string");
  else
	return("unknown");
}


 
/*
 * Dump the load record information to stderr
 */
static void dump_header __F((lh), const struct load_header *lh)
{
  static const char *s_tags[] = { /* BOOTLNUM */   "primary boot loader",
				  /* CMDLNUM */    "command line",
				  /* INITNUM */    "floppy boot sector",
				  /* SETUPNUM */   "kernel setup",
				  /* KERNELNUM */  "kernel image",
				  /* RAMDISKNUM */ "ramdisk image"};
  static const char *s_flags[]= { "absolute address",
				  "after previous segment",
				  "at end of memory",
				  "before previos 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"
	    "  Vendor data:      %s\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 ? " (high memory)" : "",
	    get_long(lr->ilength),
	    get_long(lr->mlength),
	    decode_vendor_info(lr));

	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 ramdisk mode command line option
 */
static void proc_rdmode __F((opt, arg), struct cmdopt *opt AND const char *arg)
{
  char *cp;

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



/*
 * Process VGA mode command line option
 */
static void proc_vgamode __F((opt, arg), struct cmdopt *opt AND const char *arg)
{
  char *cp;

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



/*
 * Main program
 */
int main __F((argc, argv), int argc AND char **argv)
{
  size_t len;
  char *cmdline;
  char *nfsaddrs;
  char *vgamode;
  char *cp, *ip;
  int initrd = FALSE;
  int i;

  /* Initialize parameters */
  memzero(&sysdata, sizeof(sysdata));
  sysdata.vgamode = VGA_NONE;
  sysdata.rdmode = RD_NONE;
  copystr(&rootdev, DFLT_DEV);

  /* 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.vgamode == VGA_NONE)
	sysdata.vgamode = VGA_DEFAULT;
  if (sysdata.rdmode == RD_NONE)
	sysdata.rdmode = RD_AUTO;
  if (sysdata.rdmode == RD_AUTO || sysdata.rdmode == RD_EOM)
	sysdata.rdlocation = 0L;
  if (sysdata.kname == NULL)
	copystr(&sysdata.kname, DFLT_IMAGE);
  if (sysdata.nfsdir == NULL)
	copystr(&sysdata.nfsdir, DFLT_DIR);
  if (sysdata.outname == NULL) {
	prnerr("need output file name");
	nbexit(EXIT_USAGE);
  }

  /* Open the input files */
  if ((kimage = open(sysdata.kname, O_RDONLY | O_BINARY)) < 0) {
	prnerr("unable to open kernel file %s", sysdata.kname);
	nbexit(EXIT_LINUX_KERNOPEN);
  }
  if (sysdata.rdname != NULL &&
      (rdimage = open(sysdata.rdname, O_RDONLY | O_BINARY)) < 0) {
	prnerr("unable to open ramdisk image file %s", sysdata.rdname);
	nbexit(EXIT_LINUX_RDOPEN);
  }
  prnlog(LOGLEVEL_NOTICE, "Kernel image file name  = %s\n", sysdata.kname);
  prnlog(LOGLEVEL_NOTICE, "Output file name        = %s\n", sysdata.outname);
  if (sysdata.rdname != NULL)
	prnlog(LOGLEVEL_NOTICE, "Ramdisk image file name = %s\n",
							sysdata.rdname);

  /* Read kernel setup header and get kernel version */
  get_setup();
  prnlog(LOGLEVEL_NOTICE, "Kernel version          = %d.%d\n",
			kernel_ver_major, kernel_ver_minor);

  /* Decode VGA mode */
  if (sysdata.vgamode == VGA_DEFAULT)
	vgamode = NULL;
  else {
	vgamode = (char *)nbmalloc(16);
	sprintf(vgamode, "%d", sysdata.vgamode);
  }

  /* Parse the IP address option */
#ifdef HAVE_INET
  if (sysdata.addrs != NULL && *sysdata.addrs &&
      strcmp(sysdata.addrs, "rom") &&
      strcmp(sysdata.addrs, "kernel")) {
	struct hostent *hp;
	char *buf, *bp;

	/* Allocate memory for 7 address strings, each with max 15 chars */
	i = 0;
	ip = sysdata.addrs;
	bp = buf = (char *)nbmalloc((MAX_ADDR_SIZE + 1) * 7);
	while (ip != NULL && *ip) {
		if ((cp = strchr(ip, ':')) != NULL) *cp++ = '\0';
		if ((int)strlen(ip) > 0) {
			if ((hp = gethostbyname(ip)) == NULL) {
				prnerr("invalid hostname %s", ip);
				nbexit(EXIT_HOSTNAME);
			}
			if (hp->h_length != sizeof(struct in_addr)) {
				prnerr("invalid host address type");
				nbexit(EXIT_HOSTADDR);
			}
			/* Result from ntoa is smaller than MAX_ADDR_SIZE */
			strcpy(bp, inet_ntoa(*((struct in_addr *)(hp->h_addr))));
			bp += strlen(bp);
		}
		ip = cp;
		if (i < 3)
			*bp++ = ':';
		else if (i >= 3)
			break;
		i++;
	}
	for (; i < 4; i++)
		*bp++ = ':';

	/* Finally copy host, network interface and protocol name */
	if (ip != NULL) {
		if ((cp = strchr(ip, ':')) != NULL) *cp++ = '\0';
		if ((i = strlen(ip)) > MAX_ADDR_SIZE) {
			prnerr("client name too long in IP address string");
			nbexit(EXIT_LINUX_INVCLNTNAME);
		}
		strncpy(bp, ip, i);
		bp += i;
		if (cp != NULL) {
			ip = cp;
			if ((cp = strchr(ip, ':')) != NULL) *cp++ = '\0';
			if ((i = strlen(ip)) > 0 &&
			    (i != 4 || strncmp(ip, "eth", 3) ||
			     ip[3] < '0' || ip[3] > '9')) {
				prnerr("invalid ethernet device name");
				nbexit(EXIT_LINUX_INVETH);
			}
			*bp++ = ':';
			strncpy(bp, ip, i);
			bp += i;
			if (cp != NULL) {
				ip = cp;
				i = strlen(ip);
				if ((kernel_ver_major < 2 ||
				     (kernel_ver_major == 2 &&
				      kernel_ver_minor < 2)) ||
				    (strcmp(ip, "off") &&
				     strcmp(ip, "none") &&
				     strcmp(ip, "on") &&
				     strcmp(ip, "any") &&
				     strcmp(ip, "dhcp") &&
				     strcmp(ip, "bootp") &&
				     strcmp(ip, "rarp") &&
				     strcmp(ip, "both"))) {
					prnerr("invalid protocol name");
					nbexit(EXIT_LINUX_INVPROTO);
				}
				*bp++ = ':';
				strncpy(bp, ip, i);
				bp += i;
			}
		}
	}
	*bp = '\0';
	free(sysdata.addrs);
	sysdata.addrs = buf;
  }
#else
  if (sysdata.addrs != NULL && *sysdata.addrs &&
      strcmp(sysdata.addrs, "rom") &&
      strcmp(sysdata.addrs, "kernel")) {
	prnerr("no INET support for -i option");
	nbexit(EXIT_INET);
  }
#endif
  if (sysdata.addrs == NULL)
	copystr(&sysdata.addrs, DFLT_ADDRS);

  /* Determine root device for kernel */
  if (!strncmp(sysdata.nfsdir, "ram", 3) || !strcmp(sysdata.nfsdir, "initrd")) {
	if (sysdata.rdname == NULL) {
		prnerr("boot from ramdisk but no image specified");
		nbexit(EXIT_LINUX_NORD);
	}
	if (kernel_ver_major < 1 ||
	    (kernel_ver_major == 1 && kernel_ver_minor < 4)) {
		prnerr("kernel too old to boot from ramdisk");
		nbexit(EXIT_LINUX_INVKERN);
	}
	if (rootdev != NULL)
		free(rootdev);
	if (!strcmp(sysdata.nfsdir, "initrd")) {
		initrd = TRUE;
		rootdev = (char *)nbmalloc(sizeof(ROOT_ARG) + 11);
		sprintf(rootdev, "%s/dev/ram0", ROOT_ARG);
		copystr(&initprog, INITRD_INIT);
	} else {
		for (i = 3; i < 6 && sysdata.nfsdir[i] != '\0'; i++)
			if (!isdigit((int)(sysdata.nfsdir[i])))
				break;
		if (sysdata.nfsdir[i] != '\0') {
			prnerr("invalid ramdisk device name");
			nbexit(EXIT_LINUX_INVRDDEV);
		}
		rootdev = (char *)nbmalloc(strlen(sysdata.nfsdir) +
							sizeof(ROOT_ARG) + 7);
		sprintf(rootdev, "%s/dev/%s", ROOT_ARG, sysdata.nfsdir);
	}
	/*
	 * If the nfsroot option is missing on the command line, the
	 * boot loader will add a default option, which might not be what
	 * we want. Instead, we want to have no nfsroot option at all
	 * on the command line, because we are using a ramdisk. We set
	 * nfsdir to a value of "kernel" which means to the boot loader
	 * to remove this option from the final command line.
	 */
	copystr(&sysdata.nfsdir, "kernel");
  } else if (!strncmp(sysdata.nfsdir, "/dev/", 5)) {
	if (rootdev != NULL)
		free(rootdev);
	rootdev = (char *)nbmalloc(strlen(sysdata.nfsdir) +
							sizeof(ROOT_ARG) + 1);
	sprintf(rootdev, "%s%s", ROOT_ARG, sysdata.nfsdir);
	copystr(&sysdata.nfsdir, "kernel");
  }

  /* Adjust command line to kernel version */
  if (kernel_flags & KRN_USE_IP) {
	/*
	 * If we have to use "ip=" instead of "nfsaddrs=" on the kernel
	 * command line, this means we have a newer kernel. With the
	 * old kernel a missing "nfsaddrs=" means to let the kernel
	 * determine the IP addresses by itself. With the newer kernel
	 * versions a missing "ip=" option just leaves the interface
	 * uninitialized. We therefore have to use an empty IP address
	 * string in this case.
	 */
	if (!initrd && sysdata.addrs != NULL &&
	    !strcmp(sysdata.addrs, "kernel"))
		copystr(&sysdata.addrs, KERNEL_ADDRS);
	nfsaddrs = NFS_IP;
  } else
	nfsaddrs = NFS_ADDRS;

  /* Construct command line to pass to the kernel */
  len = strlen(DFLT_CMDL) + strlen(rootdev) + 2;
  if (sysdata.append != NULL && *sysdata.append)
	len += strlen(sysdata.append) + 1;
  if (sysdata.nfsdir != NULL && *sysdata.nfsdir)
	len += strlen(NFS_ROOT) + strlen(sysdata.nfsdir) + 1;
  if (sysdata.addrs != NULL && *sysdata.addrs)
	len += strlen(nfsaddrs) + strlen(sysdata.addrs) + 1;
  if (vgamode != NULL && *vgamode)
	len += strlen(VGA_ARG) + strlen(vgamode) + 1;
  if (initprog != NULL && *initprog)
	len += strlen(INIT_ARG) + strlen(initprog) + 1;
  cmdline = (char *)nbmalloc(len + 2);

  sprintf(cmdline, "%s %s", DFLT_CMDL, rootdev);
  if (initprog != NULL && *initprog) {
	cp = cmdline + strlen(cmdline);
	sprintf(cp, " %s%s", INIT_ARG, initprog);
  }
  if (sysdata.nfsdir != NULL && *sysdata.nfsdir) {
	cp = cmdline + strlen(cmdline);
	sprintf(cp, " %s%s", NFS_ROOT, sysdata.nfsdir);
  }
  if (sysdata.addrs != NULL && *sysdata.addrs) {
	cp = cmdline + strlen(cmdline);
	sprintf(cp, " %s%s", nfsaddrs, sysdata.addrs);
  }
  if (vgamode != NULL && *vgamode) {
	cp = cmdline + strlen(cmdline);
	sprintf(cp, " %s%s", VGA_ARG, vgamode);
  }
  if (sysdata.append != NULL && *sysdata.append) {
	cp = cmdline + strlen(cmdline);
	sprintf(cp, " %s", sysdata.append);
  }
  cleanup_cmdline(cmdline);
  if ((int)strlen(cmdline) > CMDLLSIZE) {
	prnerr("kernel command line too long");
	nbexit(EXIT_LINUX_CMDLSIZE);
  }
  prnlog(LOGLEVEL_NOTICE, "Kernel command line     = \"%s\"\n", cmdline);

  /* Open the output file */
  if ((outfile = creat(sysdata.outname, 0644)) < 0) {
	prnerr("unable to create output file %s", sysdata.outname);
	nbexit(EXIT_LINUX_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(DEF_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);
  bytecpy(VENDOR_MAGIC, header.dummy, VENDOR_SIZE * sizeof(__u32));
  (void)nbwrite((__u8 *)&header, sizeof(header), outfile);

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

  /* Process all load records. The order of these calls is important! */
  do_loader();
  do_cmdline(cmdline);
  do_kernel();

  /* 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 output file");
	nbexit(EXIT_SEEK);
  }
  (void)nbwrite((__u8 *)&header, sizeof(header), outfile);

  /* Close output file */
  (void)close(outfile);

  /*
   * If the 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
}

