/*
 * mknbi.c  -  MaKe NetBoot Image for Linux
 *
 * Copyright (C) 1995-2003 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.9 2003/03/09 00:43:10 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 "mknbi.h"

#ifndef _MKNBI_H_LINUX_
#error Included wrong header file
#endif


static char *batchname = NULL;		/* name of system to batch process */
static char *outname = NULL;		/* Name of output file */
static char *rdname = NULL;		/* Name of ramdisk image file */
static char *nfsdir = NULL;		/* Directory to mount as root via NFS */
static char *rootdev = NULL;		/* Name of root device */
static char *kname = NULL;		/* Name of kernel image file */
static char *append = NULL;		/* String to append to cmd line end */
static char *addrs = NULL;		/* String with various addresses */
static char *vgamode = NULL;		/* Buffer for VGA mode string */
static char *rdmodebuf = NULL;		/* Buffer for ramdisk mode string */
static int rdmode = RD_AUTO;		/* Ramdisk load mode */
static int debug = FALSE;		/* Use debugging module for testing */
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 unsigned long rdlocation;	/* Memory location of ramdisk image */

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];



/*
 * Command line options and arguments
 */
static struct cmdopt opts[] = {
	{ "batch-sys", 'b', strval, {(char **)&batchname}, NULL,
	  "name of system to process", "SYSTEM"				},
	{ "outfile", 'o', strval, {(char **)&outname}, NULL,
	  "name of boot image output file", "FILE"			},
	{ "ramdisk-image", 'r', strval, {(char **)&rdname}, NULL,
	  "ramdisk image source file", "FILE"				},
	{ "append", 'a', strval, {(char **)&append}, NULL,
	  "append string to end of kernel command line", "STRING"	},
	{ "root-dir", 'd', strval, {(char **)&nfsdir}, NULL,
	  "NFS mounted root dir:\n"
	  "  STRING syntax: rom|ram|kernel|<dir>", "STRING"				},
	{ "ip-addrs", 'i', strval, {(char **)&addrs}, NULL,
	  "IP addresses for Linux kernel:\n"
	  "  STRING syntax: rom|kernel"
#ifdef HAVE_INET
	  "|<IP-addrs>"
#endif
							, "STRING"	},
	{ "kernel", 'k', strval, {(char **)&kname}, NULL,
	  "path to Linux kernel image", "FILE"				},
	{ "ramdisk-mode", 'l', strval, {(char **)&rdmodebuf}, NULL,
	  "loading mode for ramdisk:\n"
	  "  STRING syntax: auto|eom|<fixed address>", "STRING"		},
	{ "vga-mode", 'm', strval, {(char **)&vgamode}, NULL,
	  "VGA mode when Linux kernel starts:\n"
	  "  STRING syntax: normal|extended|ask|\n"
	  "                 default|<number>", "STRING"			},
	{ "debug", 't', boolval, {(char **)&debug}, NULL,
	  "Use debugging kernel loader", NULL				},
	{ "kernel", 0, nonopt, {(char **)&kname}, NULL,
	  "Linux kernel image (if not given as option)", NULL		},
	{ "outfile", 0, nonopt, {(char **)&outname}, NULL,
	  "output file (if not given as option)", NULL			},
	{ NULL, 0, noval, {NULL}, NULL, NULL, NULL			}
};



/*
 * Parameters in each section of database file
 */
static struct paramdef dbparams[] = {
  { "outfile",	 	par_string,	NULL,	{&outname}},
  { "kernel",		par_string,	NULL,	{&kname}},
  { "ramdisk-image", 	par_string,	NULL,	{&rdname}},
  { "ramdisk-mode",	par_string,	NULL,	{&rdmodebuf}},
  { "vga-mode",		par_string,	NULL,	{&vgamode}},
  { "append",		par_string,	NULL,	{&append}},
  { "root-dir",		par_string,	NULL,	{&nfsdir}},
  { "ip-addrs",		par_string,	NULL,	{&addrs}},
  { "debug",		par_bool,	NULL,	{(char **)&debug}},
  { NULL,	 	par_null,	NULL,	{NULL}}
};



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

  /* Read kernel header */
  i = nbread((char *)&setup_hdr, sizeof(setup_hdr), kimage);
  if (i != sizeof(setup_hdr) ||
      !bytecmp(SETUP_MAGIC, setup_hdr.magic, strlen(SETUP_MAGIC)) ||
      ttoh(getval(setup_hdr.version)) < SETUP_VERSION) {
	prnerr0("invalid linux kernel or kernel too old");
	exit(EXIT_LINUX_INVKERN);
  }

  /* Seek to kernel version string */
  i = ttoh(getval(setup_hdr.kernel_version));
  if (i < 0 || i > 0x8000 || lseek(kimage, i + SECTSIZE, 0) != (i + SECTSIZE)) {
	prnerr0("unable to seek to Linux kernel version string");
	exit(EXIT_SEEK);
  }

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

  /* Seek back to start of kernel file */
  if (lseek(kimage, 0, 0) != 0) {
	prnerr0("unable to seek to Linux kernel header");
	exit(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_HIGH))
	kernel_flags |= KRN_LOW;
}


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

  assert(cur_rec_num == recnum);
  isize = ((size / (SECTSIZE + 1)) + 1) * SECTSIZE;
  buf = (__u8 *)nbmalloc(isize);
  memcpy(buf, src, size);
  (void)nbwrite(buf, isize, outfile);
  free(buf);
  l = get_long(cur_rec->ilength) + isize;
  assign(cur_rec->ilength.low, htot(low_word(l)));
  assign(cur_rec->ilength.high, htot(high_word(l)));
  l = get_long(cur_rec->mlength) + isize;
  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 int copyrec(recnum, remaining, image, name)
int recnum;
long remaining;
int image;
char *name;
{
  long size, bytes_read;
  int i = 1;

  assert(cur_rec_num == recnum);
  for (bytes_read = 0L; remaining > 0L && i > 0;) {
	size = (remaining > SECTSIZE) ? SECTSIZE : remaining;
	if ((i = nbread(copyrec_buf, size, image))) {
		putrec(recnum, copyrec_buf, i);
		bytes_read += i;
		remaining -= i;
	}
  }
  return(bytes_read);
}



/*
 * Initialize a load record
 */
static void initrec(recnum, segment, flags, vendor_size)
int recnum;
unsigned long segment;
int flags;
int vendor_size;
{
  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) -
		             sizeof(union vendor_data)) >> 2) |
                           (((vendor_size + 3) & 0x3c) << 2)) & 0xff;
  cur_rec->rtag1        = (recnum + VENDOR_OFF) & 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()
{
  __u8 *dataptr;
  unsigned long lsize, tsize, dsize, msize;

  /*
   * Determine which of the two images to load, it's load size and
   * it's memory size, which is a word value stored at offset 0x0002
   * of the image.
   */
  dataptr = (debug ? firstd_data : first_data);
  lsize = (debug ? firstd_data_size : first_data_size);
  tsize = (int)(dataptr[0x0002] & 0x00FF) +
		((int)(dataptr[0x0003] & 0x00FF) << 8);
  dsize = (int)(dataptr[0x0004] & 0x00FF) +
		((int)(dataptr[0x0005] & 0x00FF) << 8);
  msize = roundup(tsize, 16) + dsize + 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)));
}



/*
 * Process command line image
 */
static void do_cmdline(cmdline)
char *cmdline;
{
  size_t len;
  __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.
   */
  len = strlen(cmdline);
  bufp = (__u8 *)nbmalloc(len + 2);
  bytecpy(cmdline, bufp, len);
  bufp[len] = 0;
  initrec(CMDLNUM, DEF_CMDLSEG, 0, 0);
  putrec(CMDLNUM, bufp, len + 1);
  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()
{
  int flags, setup_sects;
  unsigned long l;

  /* Process the floppy boot loader code */
  initrec(INITNUM, DEF_INITSEG, 0, 0);
  if (copyrec(INITNUM, INITLSIZE, kimage, kname) != INITLSIZE) {
	prnerr0("unexpected end of kernel image file");
	exit(EXIT_LINUX_KERNEOF);
  }
  if (getval(*((__u16 *)&(copyrec_buf[BOOT_SIG_OFF]))) !=
							htot(BOOT_SIGNATURE)){
	prnerr0("could not find magic number in kernel boot sector");
	exit(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. */
  initrec(SETUPNUM, DEF_SETUPSEG, 0, 0);
  while (setup_sects--) {
	if (copyrec(SETUPNUM, SECTSIZE, kimage, kname) != SECTSIZE) {
		prnerr0("unexpected end of kernel image file");
		exit(EXIT_LINUX_KERNEOF);
	}
  }
  if (get_long(cur_rec->ilength) > SETUPLSIZE) {
	prnerr0("setup code of kernel image too large");
	exit(EXIT_LINUX_INVKERN);
  }
  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 : FLAG_EOF;
  initrec(KERNELNUM, DEF_SYSSEG, flags, sizeof(cur_rec->vendor_data));
  cur_rec->vendor_data.krnflags = kernel_flags & 0x00FF;
  while (copyrec(KERNELNUM, SECTSIZE, kimage, kname) == SECTSIZE) ;
  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 (rdmode != RD_EOM) {
		if (rdmode == RD_AUTO)
			rdlocation = imagestart;
		else if (rdmode == RD_FIXED)
			/* always align to 4kB page boundary */
			rdlocation &= ~0xfffL;
		if (rdlocation < imagestart) {
			prnerr0("ramdisk location within linux kernel");
			exit(EXIT_LINUX_RDLOC);
		}
	}

	/* Process the ram disk image */
	flags = (rdmode == RD_EOM ? FLAG_B1 : 0) | FLAG_EOF;
	initrec(RAMDISKNUM, 0, flags, sizeof(cur_rec->vendor_data));
	cur_rec->vendor_data.rdflags = rdmode & 0xff;
	while (copyrec(RAMDISKNUM, SECTSIZE, rdimage, rdname) == SECTSIZE) ;

	/* Memory length has to be a multiple of 4kB */
	l = (get_long(cur_rec->ilength) + 0xfffL) & ~0xfffL;
	assign(cur_rec->mlength.low, htot(low_word(l)));
	assign(cur_rec->mlength.high, htot(high_word(l)));
	if (rdmode != RD_EOM)
		l = 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(cmdline)
char *cmdline;
{
  static char *entries[] = { " root=",
                             " ip=",
                             " nfsroot=",
                             " nfsaddrs=",
                             NULL };
  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] == '\000')) {
				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(lr)
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 - 16) == RAMDISKNUM)
	return(lr->vendor_data.rdflags < 3 ?
			s_rd[lr->vendor_data.rdflags] : "illegal");
  else if ((lr->rtag1 - 16) == KERNELNUM)
	return(lr->vendor_data.krnflags & KRN_USE_IP ?
			"use IP string" : "use NFSADDRS string");
  else
	return("unknown");
}


 
/*
 * Dump the load record information to stderr
 */
static void dump_header(lh)
struct load_header *lh;
{
  static char *s_tags[] = { /* BOOTLNUM */   "primary boot loader",
			    /* CMDLNUM */    "command line",
			    /* INITNUM */    "floppy boot sector",
			    /* SETUPNUM */   "kernel setup",
			    /* KERNELNUM */  "kernel image",
			    /* RAMDISKNUM */ "ramdisk image"};
  static 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;

  i = (lh->hlength >> 2) & 0x3c;
  vendstr = (char *)nbmalloc(i + 2);
  while (i > 0) {
	i--;
	vendstr[i] = lh->dummy[i];
  }

  printf("\n"
	  "Load 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"
	  "\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),
	  ttoh(getval(lh->locn.segment)), ttoh(getval(lh->locn.offset)),
	  ttoh(getval(lh->execute.segment)), ttoh(getval(lh->execute.offset)),
	  vendstr);

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

  while (TRUE) {
  	printf("Record #%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"
	    "\n",
	    ++num,
	    (lr->rlength << 2) & 0x3c,
	    (lr->rlength >> 2) & 0x3c,
	    (int)lr->rtag1,
	    (lr->rtag1 < 16) || (lr->rtag1-16 >= NUM_RECORDS) ?
		"unknown" : s_tags[lr->rtag1-16],
	    (int)lr->rtag2,
	    (int)lr->rflags, s_flags[lr->rflags & 0x03],
	    lr->rflags & 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 & FLAG_EOF)
		break;

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



/*
 * Read system database
 */
static void getdb(name)
char *name;
{
  struct sectdef sect;
  char *namebuf;
  size_t len;

  /* Read one entry from database file */
  len = strlen(name) + 11;
  namebuf = (char *)nbmalloc(len);
  sprintf(namebuf, "%s:mknbi-linux", name);
  sect.name = namebuf;
  sect.params = dbparams;
  sect.startsect = NULL;
  sect.endsect = NULL;
  readdb(&sect, dbname);

  /* Check that parameters are correct */
  if (kname == NULL) {
	prnerr1("need kernel image file name in section <%s>", namebuf);
	exit(EXIT_DB);
  }
  if (outname == NULL) {
	prnerr1("need output file name in section <%s>", namebuf);
	exit(EXIT_DB);
  }
  free(namebuf);
}



/*
 * Main program
 */
int main(argc, argv)
int argc;
char **argv;
{
  size_t len;
  char *cmdline;
  char *nfsaddrs;
  char *cp, *ip;
  int vendor_size;
  int i;

  /* Initialize option argments */
  copystr(&nfsdir, DFLT_DIR);
  copystr(&rootdev, DFLT_DEV);
  copystr(&kname, DFLT_IMAGE);

  /* Parse options and read configuration file */
  nbsetup(argc, argv, opts, NULL);
  if (batchname != NULL)
	getdb(batchname);
  if (kname == NULL) {
	prnerr0("need kernel image file name");
	exit(EXIT_USAGE);
  }
  if (outname == NULL) {
	prnerr0("need output file name");
	exit(EXIT_USAGE);
  }

  /* Open the input and output files */
  if ((kimage = open(kname, O_RDONLY | O_BINARY)) < 0) {
	prnerr1("unable to open kernel file %s", kname);
	exit(EXIT_LINUX_KERNOPEN);
  }
  if ((outfile = creat(outname, 0644)) < 0) {
	prnerr1("unable to create output file %s", outname);
	exit(EXIT_LINUX_IMGCREATE);
  }
  if (rdname != NULL && (rdimage = open(rdname, O_RDONLY | O_BINARY)) < 0) {
	prnerr1("unable to open ramdisk image file %s", rdname);
	exit(EXIT_LINUX_RDOPEN);
  }
  if (verbose > 0) {
	printf("Kernel image file name  = %s\n", kname);
	printf("Output file name        = %s\n", outname);
	if (rdname != NULL)
		printf("Ramdisk image file name = %s\n", rdname);
  }

  /* Read kernel setup header and get kernel version */
  get_setup();
  if (verbose > 0)
	printf("Kernel version          = %d.%d\n",
			kernel_ver_major, kernel_ver_minor);

  /* Decode ramdisk loading mode */
  if (rdmodebuf == NULL || !strcmp("auto", rdmodebuf))
	rdmode = RD_AUTO;
  else if (!strcmp("eom", rdmodebuf))
	rdmode = RD_EOM;
  else {
	rdmode = RD_FIXED;
	if ((rdlocation = strtol(rdmodebuf, &cp, 0)) < 0x100000L) {
		prnerr0("ramdisk image load location must be above 1M");
		exit(EXIT_LINUX_RDLOC);
	}
	if (*cp) {
		prnerr0("invalid ramdisk location");
		exit(EXIT_LINUX_RDLOC);
	}
  }

  /*
   * Decode VGA mode string, the VGA mode value is only valid if the
   * pointer to string buffer is not NULL.
   */
  if (vgamode != NULL) {
	if (!strcmp("normal", vgamode))
		i = VGA_NORMAL;
	else if (!strcmp("extended", vgamode))
		i = VGA_EXTENDED;
	else if (!strcmp("ask", vgamode))
		i = VGA_ASK;
	else if (!strcmp("default", vgamode))
		i = VGA_DEFAULT;
	else {
		i = (int)strtol(vgamode, &cp, 0);
		if (i < VGA_MIN || i > VGA_MAX || *cp) {
			prnerr0("invalid VGA mode");
			exit(EXIT_LINUX_VGAMODE);
		}
	}
	if (i == VGA_DEFAULT)
		cp = NULL;
	else {
		cp = (char *)nbmalloc(16);
		sprintf(cp, "%d", i);
	}
	free(vgamode);
	vgamode = cp;
  }

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

	/* Allocate memory for 7 address strings, each with max 15 chars */
	i = 0;
	ip = addrs;
	bp = buf = (char *)nbmalloc((MAX_ADDR_SIZE + 1) * 7);
	while (ip != NULL && *ip) {
		if ((cp = strchr(ip, ':')) != NULL) *cp++ = '\0';
		if (strlen(ip) > 0) {
			if ((hp = gethostbyname(ip)) == NULL) {
				prnerr1("invalid hostname %s", ip);
				exit(EXIT_HOSTNAME);
			}
			if (hp->h_length != sizeof(struct in_addr)) {
				prnerr0("invalid host address type");
				exit(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++ = ':';
		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) {
			prnerr0("client name too long in IP address string");
			exit(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)) != 4 ||
			    strncmp(ip, "eth", 3) ||
			    ip[3] < '0' || ip[3] > '9') {
				prnerr0("invalid ethernet device name");
				exit(EXIT_LINUX_INVETH);
			}
			*bp++ = ':';
			strncpy(bp, ip, i);
			bp += i;
			if (cp != NULL) {
				ip = cp;
				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"))) {
					prnerr0("invalid protocol name");
					exit(EXIT_LINUX_INVPROTO);
				}
				*bp++ = ':';
				strncpy(bp, ip, i);
				bp += i;
			}
		}
	}
	*bp = '\0';
	free(addrs);
	addrs = buf;
  }
#else
  if (addrs != NULL && *addrs && strcmp(addrs, "rom") &&
      strcmp(addrs, "kernel")) {
	prnerr0("no INET support for -i option");
	exit(EXIT_INET);
  }
#endif
  if (addrs == NULL)
	copystr(&addrs, DFLT_ADDRS);

  /* Determine root device for kernel */
  if (!strncmp(nfsdir, "ram", 3) || !strcmp(nfsdir, "initrd")) {
	if (rdname == NULL) {
		prnerr0("boot from ramdisk but no image specified");
		exit(EXIT_LINUX_NORD);
	}
	if (kernel_ver_major < 1 ||
	    (kernel_ver_major == 1 && kernel_ver_minor < 4)) {
		prnerr0("kernel too old to boot from ramdisk");
		exit(EXIT_LINUX_INVKERN);
	}
	if (rootdev != NULL)
		free(rootdev);
	rootdev = (char *)nbmalloc(strlen(nfsdir) + 16);
	if (!strcmp(nfsdir, "initrd"))
		sprintf(rootdev, "root=/dev/ram");
	else {
		for (i = 3; i < 6 && nfsdir[i] != '\0'; i++)
			if (!isdigit(nfsdir[i]))
				break;
		if (nfsdir[i] != '\0') {
			prnerr0("invalid ramdisk device name");
			exit(EXIT_LINUX_INVRDDEV);
		}
		sprintf(rootdev, "root=/dev/%s", 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(&nfsdir, "kernel");
  } else if (!strncmp(nfsdir, "/dev/", 5)) {
	if (rootdev != NULL)
		free(rootdev);
	rootdev = (char *)nbmalloc(strlen(nfsdir) + 7);
	sprintf(rootdev, "root=%s", nfsdir);
	copystr(&nfsdir, "kernel");
  }

  /* Construct command line to pass to the kernel */
  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 (addrs != NULL && !strcmp(addrs, "kernel"))
		copystr(&addrs, KERNEL_ADDRS);
	nfsaddrs = NFS_IP;
  } else
	nfsaddrs = NFS_ADDRS;

  len = strlen(DFLT_CMDL) + strlen(rootdev) + 2;
  if (append != NULL && *append)
	len += strlen(append) + 1;
  if (nfsdir != NULL && *nfsdir)
	len += strlen(NFS_ROOT) + strlen(nfsdir) + 1;
  if (addrs != NULL && *addrs)
	len += strlen(nfsaddrs) + strlen(addrs) + 1;
  if (vgamode != NULL && *vgamode)
	len += strlen(VGA_ARG) + strlen(vgamode) + 1;
  cmdline = (char *)nbmalloc(len + 2);

  sprintf(cmdline, "%s %s", DFLT_CMDL, rootdev);
  if (nfsdir != NULL && *nfsdir) {
	cp = cmdline + strlen(cmdline);
	sprintf(cp, " %s%s", NFS_ROOT, nfsdir);
  }
  if (addrs != NULL && *addrs) {
	cp = cmdline + strlen(cmdline);
	sprintf(cp, " %s%s", nfsaddrs, addrs);
  }
  if (vgamode != NULL && *vgamode) {
	cp = cmdline + strlen(cmdline);
	sprintf(cp, " %s%s", VGA_ARG, vgamode);
  }
  if (append != NULL && *append) {
	cp = cmdline + strlen(cmdline);
	sprintf(cp, " %s", append);
  }
  cleanup_cmdline(cmdline);
  if (strlen(cmdline) > CMDLLSIZE) {
	prnerr0("command line too long");
	exit(EXIT_LINUX_CMDLSIZE);
  }
  if (verbose > 0)
	printf("Kernel command line     = \"%s\"\n", cmdline);

  /* Initialize the boot header */
  vendor_size = ((strlen(VENDOR_ID) + sizeof(__u32) - 1) / sizeof(__u32)) *
								sizeof(__u32);
  memset(&header, 0, sizeof(header));
  assign(header.magic.low,       htot(low_word(HEADER_MAGIC)));
  assign(header.magic.high,      htot(high_word(HEADER_MAGIC)));
  assign(header.locn.segment,    htot(DEF_HEADERSEG));
  assign(header.locn.offset,     htot(0));
  assign(header.execute.segment, htot(DEF_BOOTLSEG));
  assign(header.execute.offset,  htot(0));
  assign(header.bootsig,         htot(BOOT_SIGNATURE));
  header.hlength = ((__u8)((int)(header.dummy - (__u8 *)&header) /
                    sizeof(__u32)) & 0x0f) |
                   ((__u8)((vendor_size / sizeof(__u32)) << 4) & 0xf0);
  bytecpy(VENDOR_ID, header.dummy, strlen(VENDOR_ID));
  (void)nbwrite((__u8 *)&header, sizeof(header), outfile);

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

  /* 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, 0) != 0) {
	prnerr0("unable to seek to beginning of output file");
	exit(EXIT_SEEK);
  }
  (void)nbwrite((__u8 *)&header, sizeof(header), outfile);

  /*
   * If the user asked for detailed output, parse the header and output
   * all of the load record information.
   */
  if (verbose > 1)
	dump_header(&header);

  return(EXIT_SUCCESS);
}



syntax highlighted by Code2HTML, v. 0.9.1