/*
 * mknbi.c  -  MaKe NetBoot Image for DOS
 *
 * Copyright (C) 1996-2003 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.8 2003/03/09 00:43:09 gkminix Exp $
 */

#define NEED_BINARY 1
#include <common.h>
#include <nblib.h>
#include "mknbi.h"

#ifndef _MKNBI_H_DOS_
#error Included wrong header file
#endif



/*
 * Values for keep command line option
 */
#define KEEP_MISSING	0
#define KEEP_NONE	1
#define KEEP_ALL	2
#define KEEP_UNDI	3

static char *ktypes[] = {
  "none", "all", "undi", NULL
};



/*
 * Global variables
 */
int usehd = FALSE;		/* Non-zero if ramdisk is hard disk */
int nohd = FALSE;		/* Non-zero if hard disk accesses not allowed */
int norpl = FALSE;		/* Non-zero if RPL mechanism not allowed */
int useint15 = FALSE;		/* Non-zero if int 15h protection enabled */
int singlefat = FALSE;		/* Non-zero if only one FAT for ramdisk */
char *volumename = NULL;	/* volume name of ramdisk filesystem */



/*
 * Variables private to this module
 */
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 *keepname = NULL;		/* name of module to keep */
static int rdsize = 0;			/* size of ramdisk in kB */
static int outfile;			/* file handle for output file */
static int rdimage;			/* file handle for ramdisk image */
static int debug = FALSE;		/* Use debugging module for testing */
static int keepflag = KEEP_MISSING;	/* keep flag value for load header */

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[] = {
	{ "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 or directory", "FILE|DIR"		},
	{ "ramdisk-size", 's', intval, {(char **)&rdsize}, NULL,
	  "size in kB of ramdisk image", "SIZE"				},
	{ "no-hard-disk", 'n', boolval, {(char **)&nohd}, NULL,
	  "do not allow hard disk accesses in client", NULL		},
	{ "no-rpl", 'l', boolval, {(char **)&norpl}, NULL,
	  "do not allow RPL memory protection", NULL			},
	{ "use-int-15", 'i', boolval, {(char **)&useint15}, NULL,
	  "use interrupt 15h ramdisk protection", NULL			},
	{ "single-fat", 'F', boolval, {(char **)&singlefat}, NULL,
	  "use only one FAT for ramdisk", NULL				},
	{ "simulate-hard-disk", 'c', boolval, {(char **)&usehd}, NULL,
	  "ramdisk simulates hard disk instead of floppy", NULL		},
	{ "volume-name", 'V', strval, {(char **)&volumename}, NULL,
	  "name of ramdisk volume", "STRING"				},
	{ "keep", 'k', strval, {(char **)&keepname}, NULL,
	  "name of module to keep after loading DOS:\n"
	  "  STRING syntax: none|all|undi", "STRING"			},
	{ "rdimage", 0, nonopt, {(char **)&rdname}, NULL,
	  "ramdisk image (if not given as option)", NULL		},
	{ "debug", 't', boolval, {(char **)&debug}, NULL,
	  "Use debugging initial loader", 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[] = {
  { "volume-name",	par_string,	NULL,	{&volumename}},
  { "outfile",	 	par_string,	NULL,	{&outname}},
  { "ramdisk-image", 	par_string,	NULL,	{&rdname}},
  { "ramdisk-size",	par_int,	NULL,	{(char **)&rdsize}},
  { "no-hard-disk",	par_bool,	NULL,	{(char **)&nohd}},
  { "no-rpl",		par_bool,	NULL,	{(char **)&norpl}},
  { "use-int-15",	par_bool,	NULL,	{(char **)&useint15}},
  { "single-fat",	par_bool,	NULL,	{(char **)&singlefat}},
  { "simulate-hd",	par_bool,	NULL,	{(char **)&usehd}},
  { "debug",		par_bool,	NULL,	{(char **)&debug}},
  { "keep",		par_enum,	ktypes,	{(char **)&keepflag}},
  { NULL,	 	par_null,	NULL,	{NULL}}
};




/*
 * Write a buffer into the output file and update the load record
 */
static void putrec(recnum, src, size)
int recnum;
__u8 *src;
int 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)));
}



/*
 * Initialize a load record
 */
static void initrec(recnum, segment, flags, vendor_size)
int recnum;
int segment;
int flags;
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) -
                             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((unsigned long) segment << 4)));
  assign(cur_rec->address.high, htot(high_word((unsigned long) segment << 4)));
}



/*
 * Process boot loader image
 */
static void do_loader(loadflags)
int loadflags;
{
  __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) + 1;
  dsize = (int)(dataptr[0x0004] & 0x00FF) +
		((int)(dataptr[0x0005] & 0x00FF) << 8) + 1;
  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 <= LOADERLSIZE);
  assert(msize <= LOADERMSIZE && msize >= lsize);

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



/*
 * Process ramdisk image file
 */
static void do_ramdisk(geometry)
struct disk_geometry *geometry;
{
  static __u8 buf[SECTSIZE];
  unsigned long l;
  int i;

  /*
   * Generate the load record which contains the disk geometry, and
   * then write the partition table, all hidden sectors and the new
   * boot record.
   */
  initrec(RAMDISKNUM, 0, FLAG_EOF, sizeof(struct disk_geometry));
  memcpy(&(cur_rec->vendor_data.geometry), geometry,
					sizeof(struct disk_geometry));

  /*
   * Copy the ramdisk image from the temporary file into the final
   * output file.
   */
  do {
	i = nbread(buf, SECTSIZE, rdimage);
	if (i > 0)
		putrec(RAMDISKNUM, buf, i);
  } while (i == SECTSIZE);
  if (get_long(cur_rec->ilength) > ((unsigned long)MAX_RDSIZE * 1024)) {
	prnerr0("ram disk image too large");
	exit(EXIT_DOS_RDSIZE);
  }
  l = get_long(geometry->num_sectors) * SECTSIZE;
  assign(cur_rec->mlength.low,  htot(low_word(l)));
  assign(cur_rec->mlength.high, htot(high_word(l)));
  assign(cur_rec->address.low,  htot(low_word(RDADDR)));
  assign(cur_rec->address.high, htot(high_word(RDADDR)));
}


/*
 * Dump the load record information to stderr
 */
static void dump_header(lh)
struct load_header *lh;
{
  static char *s_tags[] = { /* LOADERNUM */  "primary boot loader",
			    /* 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("\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"
	  "\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",
	    ++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 &&
	    (lr->rflags & 0x03) == 0? " (high memory)" : "",
	    get_long(lr->ilength),
	    get_long(lr->mlength));

	if (lr->rtag1-16 == RAMDISKNUM) {
		int heads = ttoh(getval(lr->vendor_data.geometry.num_heads));
		int cyls = ttoh(getval(lr->vendor_data.geometry.cylinders));
		int spt = ttoh(getval(lr->vendor_data.geometry.sect_per_track));
		int diskid = ttoh(getval(lr->vendor_data.geometry.boot_drive));

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

		printf("  Vendor data:       "
		       "NO-RPL flag %s\n"
		       "                     "
		       "USE-INT-15 flag %s\n"
		       "\n",
		       flags & FLAG_NORPL ? "true" : "false",
		       flags & FLAG_INT15 ? "true" : "false");
	} else {
		printf("  Vendor data:      %s\n"
	               "\n",
	               lr->rlength & 0xf0 ? "unknown" : "none");
	}

	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-dos", name);
  sect.name = namebuf;
  sect.params = dbparams;
  sect.startsect = NULL;
  sect.endsect = NULL;
  readdb(&sect, dbname);

  /* Check that parameters are correct */
  if (rdsize > MAX_RDSIZE) {
	prnerr2("ramdisk size must be < %d kB in section <%s>",
							MAX_RDSIZE, namebuf);
	exit(EXIT_DB);
  }
  if (rdname == NULL) {
	prnerr1("need ramdisk image file or directory name in section <%s>",
								namebuf);
	exit(EXIT_DB);
  }
  if (outname == NULL) {
	prnerr1("need output file name in section <%s>", namebuf);
	exit(EXIT_DB);
  }
  if ((volumename != NULL) && (strlen(volumename) > 11)) {
	prnerr1("volume name too long in section <%s>", namebuf);
	exit(EXIT_DB);
  }
  if (usehd & nohd) {
	prnerr1("can't use 'simulate-hd' and 'no-hard-disk' "
				"simultaneously in section <%s>", namebuf);
	exit(EXIT_DB);
  }
  free(namebuf);
}



/*
 * Main program
 */
int main(argc, argv)
int argc;
char **argv;
{
  struct disk_geometry geometry;
  int vendor_size, loadflags;

  /* Parse options */
  nbsetup(argc, argv, opts, NULL);
  if (keepname != NULL) {
	if (!strcmp(ktypes[KEEP_NONE - 1], keepname))
		keepflag = KEEP_NONE;
	else if (!strcmp(ktypes[KEEP_ALL - 1], keepname))
		keepflag = KEEP_ALL;
	else if (!strcmp(ktypes[KEEP_UNDI - 1], keepname))
		keepflag = KEEP_UNDI;
	else {
		prnerr1("invalid keep argument %s", keepname);
		exit(EXIT_USAGE);
	}
	free(keepname);
	keepname = NULL;
  }

  /* Read configuration file */
  if (batchname != NULL)
	getdb(batchname);
  if (rdsize > MAX_RDSIZE) {
	prnerr1("ramdisk size must be < %d kB", MAX_RDSIZE);
	exit(EXIT_USAGE);
  }
  if (rdname == NULL) {
	prnerr0("need ramdisk image file or directory name");
	exit(EXIT_USAGE);
  }
  if (outname == NULL) {
	prnerr0("need output file name");
	exit(EXIT_USAGE);
  }
  if ((volumename != NULL) && (strlen(volumename) > 11)) {
	prnerr0("volume name longer than 11 characters");
	exit(EXIT_USAGE);
  }
  if (usehd & nohd) {
	prnerr0("options -h and -n can't be used simultaneously");
	exit(EXIT_USAGE);
  }

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

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

  /* Open the input and output files */
  if ((outfile = creat(outname, 0644)) < 0) {
	prnerr1("unable to create output file %s", outname);
	exit(EXIT_DOS_IMGCREATE);
  }
  rdimage = openrd(rdname, rdsize, &geometry, &loadflags);
  if (verbose > 0) {
	printf("Ramdisk filename = %s\n", rdname);
	printf("Output file name = %s\n", outname);
  }

  /* 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(HEADERSEG));
  assign(header.locn.offset,     htot(0));
  assign(header.execute.segment, htot(LOADERSEG));
  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);
  header.hflags1 = (keepflag == KEEP_ALL ? FLAG1_KEEP_ALL :
                   (keepflag == KEEP_UNDI ? FLAG1_KEEP_UNDI : 0));
  bytecpy(VENDOR_ID, header.dummy, strlen(VENDOR_ID));
  (void)nbwrite((__u8 *)&header, sizeof(header), outfile);

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

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

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

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

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