/*
 * openrd.c  -  Open or create ramdisk image file
 *
 * 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: openrd.c,v 1.22 2007/01/06 18:31:22 gkminix Exp $
 */

#define NEED_BINARY 1
#define NEED_DIR 1
#define NEED_TIME 1
#include <common.h>
#include <nblib.h>
#include <system/partition.h>
#include "mknbi.h"
#include "dir.h"
#include "headers/fat.h"
#include "headers/boot.h"

#ifndef MKNBI_H_DOS
#error Included wrong header file
#endif



/*
 * List of possible output file formats
 */
struct disklayout {
	unsigned long size;		/* size of floppy in kB */
	unsigned int  diskid;		/* disk ID byte */
	unsigned int  clustsize;	/* size of one cluster in bytes */
	unsigned int  maxcluster;	/* maximum cluster number */
	unsigned int  fatsects;		/* number of sector per FAT */
	unsigned int  rootentries;	/* number of entries in root dir */
	unsigned int  spt;		/* sectors per track */
	unsigned int  heads;		/* number of heads */
	unsigned int  tracks;		/* number of tracks */
	unsigned int  fat16;		/* TRUE if FAT16 */
};

static const struct disklayout floppylist[] = {
	/*
	 * List of floppy formats. This list has to be organized from lowest
	 * to highest size. All formats assume two FATs. The number of sectors
	 * per FAT have to be so that it is possible to include enough
	 * clusters when just one FAT is requested.
	 */
	{    320L, 0xFF, SECTSIZE*2,   315,   1,  112,  8,  2,  40, FALSE },
	{    360L, 0xFD, SECTSIZE*2,   354,   2,  112,  9,  2,  40, FALSE },
	{    720L, 0xF9, SECTSIZE*2,   713,   3,  112,  9,  2,  80, FALSE },
	{   1200L, 0xF9, SECTSIZE,    2371,   7,  224, 15,  2,  80, FALSE },
	{   1440L, 0xF0, SECTSIZE,    2847,   9,  224, 18,  2,  80, FALSE },
	{   2880L, 0xF0, SECTSIZE*2,  2863,   9,  224, 36,  2,  80, FALSE },
	{      0L,    0,        0,       0,   0,    0,  0,  0,   0, FALSE }
};

static const struct disklayout hdlist[] = {
	/*
	 * These values are the max available when simulating a hard disk.
	 * They have to be adjusted before the actual image is constructed.
	 */
	{   4096L, 0xF8, SECTSIZE*2,   3659,  12,  256, 32,  2, 128, FALSE },
	{   8192L, 0xF8, SECTSIZE*4,   3672,  12,  256, 32,  2, 256, FALSE },
	{  16384L, 0xF8, SECTSIZE*8,   3677,  12,  512, 32,  2, 512, FALSE },
	{  32736L, 0xF8, SECTSIZE*16,  3677,  12,  512, 48,  4, 341, FALSE },
	{  65472L, 0xF8, SECTSIZE*16,  7359,  32,  512, 48,  4, 682, TRUE  },
	{ 130944L, 0xF8, SECTSIZE*16, 14723,  64,  512, 48,  8, 682, TRUE  },
	{ 262080L, 0xF8, SECTSIZE*16, 29469, 128, 1024, 52, 16, 630, TRUE  },
	{      0L,    0,        0,        0,   0,    0,  0,  0,   0, FALSE }
};

static struct disklayout *curformat = NULL;

#define DEFAULT_FORMAT	4		/* Default format if none specified */
#define MAX_CLUST_SIZE	SECTSIZE*16	/* Maximum cluster size in table */




/***************************************************************************

			Miscellaneous routines

 ***************************************************************************/

/* Pointer to IO.SYS file */
static struct file_struct *io_sys = NULL;
static unsigned int io_sys_sects;
static unsigned int io_sys_seg;



/*
 * Write a simple partition table into the ramdisk image file
 */
static unsigned long putpart __F((outfile), int outfile)
{
  static __u8 part_buf[SECTSIZE];
  struct partition *part;
  unsigned int sector, cylinder;
  unsigned long sectnum;
  unsigned long writecnt;
  unsigned int i;

  /* Setup the partition table */
  memzero(part_buf, SECTSIZE);
  assign(*((__u16 *)&(part_buf[PART_SIG_OFS])), PART_SIG);
  part = (struct partition *)&(part_buf[PART_TABLE_OFS]);
  part->status = PART_ACTIVE;
  part->type = curformat->fat16 ? 
		(curformat->size > 32767 ? PART_TYPE_16_LARGE :
		                           PART_TYPE_16_SMALL) : PART_TYPE_12;
  sectnum = curformat->size * SECTS_PER_KB - curformat->spt;
  assign(part->number_sectors.low, htot(low_word(sectnum)));
  assign(part->number_sectors.high, htot(high_word(sectnum)));
  assign(part->first_abs_sector.low, htot(low_word(curformat->spt)));
  assign(part->first_abs_sector.high, htot(high_word(curformat->spt)));

  /* Determine starting sector of partition */
  part->first_sector = 1;
  part->first_track = 0;
  part->first_head = 1;

  /* Determine last sector of partition */
  sector = (curformat->spt + sectnum - 1) % curformat->spt;
  cylinder = (curformat->spt + sectnum - 1) / curformat->spt;
  part->last_sector = ((sector + 1) & 0x003f) |
			(((cylinder / curformat->heads) & 0x0300) << 6);
  part->last_track = (cylinder / curformat->heads) & 0x00ff;
  part->last_head = (cylinder % curformat->heads) & 0x00ff;

  /* Write partition table */
  writecnt = nbwrite(part_buf, SECTSIZE, outfile);

  /* Insert all hidden sectors */
  memzero(part_buf, SECTSIZE);
  for (i = 0; i < (curformat->spt - 1); i++)
	writecnt += nbwrite(part_buf, SECTSIZE, outfile);
  return(writecnt);
}



/*
 * Setup boot record with parameters from disk format table
 */
static void set_boot_block __F((sysentry, filesys),
				struct sysdef *sysentry AND
				struct fs_struct *filesys)
{
  struct boot_record *bp = (struct boot_record *)(filesys->boot_block);
  unsigned long l;

  /*
   * Setup the disk parameter block within boot sector with the disk
   * geometry values.
   */
  assert((bp != NULL) && ((filesys->boot_size % SECTSIZE) == 0));
  assign(bp->bytes_per_sect, htot(SECTSIZE));
  assign(bp->dir_num, htot(curformat->rootentries));
  assign(bp->sect_per_fat, htot(curformat->fatsects));
  assign(bp->sect_per_track, htot(curformat->spt));
  assign(bp->head_num, htot(curformat->heads));
  bp->sect_per_cluster = (__u8)((curformat->clustsize / SECTSIZE) & 0xff);
  bp->boot_id = (__u8)(sysentry->usehd ? DISKID_HD : DISKID_FLOPPY);
  bp->media_id = (__u8)(curformat->diskid);
  bp->fat_num = (sysentry->singlefat ? 1 : 2);
  l = curformat->size * SECTS_PER_KB - (sysentry->usehd ? curformat->spt : 0);
  assign(bp->sect_num, htot(l & 0xffff));
  assign(bp->sect_num_32.low, htot(low_word(l)));
  assign(bp->sect_num_32.high, htot(high_word(l)));
  if (sysentry->usehd) {
	assign(bp->hidden_num.low, htot(low_word(curformat->spt)));
	assign(bp->hidden_num.high, htot(high_word(curformat->spt)));
  } else {
	assign(bp->hidden_num.low, htot(0));
	assign(bp->hidden_num.high, htot(0));
  }
  if (curformat->fat16)
	bytecpy(BOOT_FAT16_NAME, bp->fat_name, sizeof(bp->fat_name));
  else
	bytecpy(BOOT_FAT12_NAME, bp->fat_name, sizeof(bp->fat_name));
  if (sysentry->volumename == NULL)
	bytecpy(BOOT_VOL_NAME, bp->vol_name, sizeof(bp->vol_name));
  else
	bytecpy(sysentry->volumename, bp->vol_name, sizeof(bp->vol_name));

  /*
   * If we use our own private boot sector, we have to set some additional
   * values, for example the number of sectors occupied by IO.SYS and the
   * loading segment.
   */
  if (getval(*((__u16 *)&(filesys->boot_block[BOOT_SIG_OFS]))) ==
							htot(BOOT_SIG_PRIV)) {
	struct priv_boot *pbp;

	pbp = (struct priv_boot *)(filesys->boot_block);
	if (io_sys != NULL) {
		assign(pbp->io_sys_sects, htot(io_sys_sects));
		assign(pbp->io_sys_ofs, htot(io_sys_seg * 16));
	}
	assign(*((__u16 *)&(filesys->boot_block[BOOT_SIG_OFS])),
							htot(BOOT_SIG));
  }
}





/***************************************************************************

			Directory handling routines

 ***************************************************************************/


/*
 * Determine required number of root directory sectors. First compute
 * the multiple of the disk format root dir entry number which holds
 * all root dir entries, and then compute the number of sectors required
 * to hold the root directory.
 * Note that this routine gets used quite often.
 */
static unsigned int get_root_sects __F((filesys, diskformat),
				const struct fs_struct *filesys AND
				const struct disklayout *diskformat)
{
  unsigned int rootentries;

  rootentries = roundup((DIR_ENTRIES(filesys->root_dir) + 1),
						diskformat->rootentries);
  return(howmany(rootentries * sizeof(struct dos_dir), SECTSIZE));
}



/*
 * Move the given file into the first position in the file list. This
 * is required for the MS-DOS system files.
 */
static void movefirst __F((dsp, fsp),
				struct dir_struct *dsp AND
				struct file_struct *fsp)
{
  struct file_struct *cur = dsp->files;
  struct file_struct *prev = NULL;

  while (cur != NULL && cur != fsp) {
	prev = cur;
	cur = cur->next;
  }
  if (cur == fsp && prev != NULL) {
	prev->next = fsp->next;
	fsp->next = dsp->files;
	dsp->files = fsp;
  }
}



/*
 * Delete a file entry
 */
static void delfile __F((dsp, fsp),
				struct dir_struct *dsp AND
				struct file_struct *fsp)
{
  struct file_struct *cur = dsp->files;
  struct file_struct *prev = NULL;

  while (cur != NULL && cur != fsp) {
	prev = cur;
	cur = cur->next;
  }
  if (cur == fsp && prev != NULL) {
	prev->next = fsp->next;
	if (fsp->name.lfn_name != NULL)
		free(fsp->name.lfn_name);
	free(fsp);
  }
}



/*
 * Delete full directory tree
 */
static void deltree __F((dsp), struct dir_struct *dsp)
{
  struct dir_struct *tmpdsp;
  struct file_struct *fsp;

  /* Check if there is anything to do */
  if (dsp == NULL)
	return;

  /* Delete all files in current directory entry */
  while (dsp->files != NULL) {
	fsp = dsp->files;
	dsp->files = fsp->next;
	if (fsp->name.lfn_name != NULL)
		free(fsp->name.lfn_name);
	free(fsp);
  }

  /* Delete all subdirectories */
  while (dsp->next != NULL) {
	tmpdsp = dsp->next;
	dsp->next = tmpdsp->next;
	if (dsp->name.lfn_name != NULL)
		free(dsp->name.lfn_name);
	deltree(dsp->subdirs);
	free(tmpdsp);
  }
}





/***************************************************************************

			Routines to handle DOS configuration files

 ***************************************************************************/

/* List of configuration files to check for */
static const struct {
	char  *name;			/* File name */
	char  *ext;			/* File extension */
} config_files[] = {
	{ "CONFIG",   "SYS" },
	{ "AUTOEXEC", "BAT" },
	{ "MSDOS",    "SYS" },
	{ NULL,       NULL  }
};



/*
 * Search configuration files within root directory
 */
static void do_config_files __F((sysentry, filesys),
				struct sysdef *sysentry AND
				struct fs_struct *filesys)
{
  struct file_struct *fsp[3];
  int i;

  /*
   * Search for various DOS configuration files, and if specific versions
   * exist for the type of ramdisk, remove all other files and make the
   * specific file current by copying the extension.
   */
  for (i = 0; config_files[i].name != NULL; i++) {
	fsp[0] = findfname(filesys->root_dir, FALSE,
				config_files[i].name, config_files[i].ext);
	fsp[1] = findfname(filesys->root_dir, FALSE,
				config_files[i].name, "$$A");
	fsp[2] = findfname(filesys->root_dir, FALSE,
				config_files[i].name, "$$C");
	if (sysentry->usehd && (fsp[2] != NULL)) {
		if (fsp[1] != NULL)
			delfile(filesys->root_dir, fsp[1]);
		if (fsp[0] != NULL)
			delfile(filesys->root_dir, fsp[0]);
		(void)cvtstr(fsp[2]->name.ext, config_files[i].ext, 3);
	} else if (!sysentry->usehd && (fsp[1] != NULL)) {
		if (fsp[2] != NULL)
			delfile(filesys->root_dir, fsp[2]);
		if (fsp[0] != NULL)
			delfile(filesys->root_dir, fsp[0]);
		(void)cvtstr(fsp[1]->name.ext, config_files[i].ext, 3);
	} else {
		if (fsp[1] != NULL)
			delfile(filesys->root_dir, fsp[1]);
		if (fsp[2] != NULL)
			delfile(filesys->root_dir, fsp[2]);
	}
  }
}





/***************************************************************************

			Routines to handle DOS system files

 ***************************************************************************/

/* List of DOS system files */
static struct {
	char          *bio_name;	/* Name and extension of BIOS file */
	char          *bio_ext;
	char          *dos_name;	/* Name and extension of DOS file */
	char          *dos_ext;
	char          *cmd_name;	/* Name and extension of COMMAND.COM */
	char          *cmd_ext;
	char          *himem_name;	/* Name and extension of XMS driver */
	char          *himem_ext;
	unsigned long  rpl_size;	/* Size for old non-RPL BIOS file */
	unsigned int   sys_seg;		/* Load segment of BIOS file */
	int            use_size;	/* TRUE if to load full BIOS file */
        int            flags;		/* Flags for primary boot loader */
} const system_files[] = {
	/* PC-DOS or OpenDOS / Caldera-DOS / DR-DOS */
		{ "IBMBIO  ", "COM",
		  "IBMDOS  ", "COM",
		  "COMMAND ", "COM",
		  "HIMEM   ", "SYS",
		  24800, 0x0070, TRUE, 0				},
	/* MS-DOS */
		{ "IO      ", "SYS",
		  "MSDOS   ", "SYS",
		  "COMMAND ", "COM",
		  "HIMEM   ", "SYS",
		  28500, 0x0070, FALSE, 0				},
	/* Early DR-DOS */
		{ "DRBIOS  ", "SYS",
		  "DRBDOS  ", "SYS",
		  "COMMAND ", "COM",
		  NULL      , NULL ,
		  0, 0x0070, FALSE, BOOT_FLAG_NORPL | BOOT_FLAG_INT15	},
	/* FreeDOS */
		{ "KERNEL  ", "SYS",
		  NULL      , NULL ,
		  "COMMAND ", "COM",
		  "HIMEM   ", "EXE",
		  0, 0x0060, TRUE, BOOT_FLAG_NORPL | BOOT_FLAG_INT15	},
	/* PTS-DOS */
		{ "PTSBIO  ", "SYS",
		  "PTSDOS  ", "SYS",
		  "COMMAND ", "COM",
		  "HIMEM   ", "SYS",
		  0, 0x0280, FALSE, BOOT_FLAG_NORPL			},
	/* End marker */
		{ NULL      , NULL ,
		  NULL      , NULL ,
		  NULL      , NULL ,
		  NULL      , NULL ,
		  0, 0x0000, FALSE, 0					}
};



/*
 * Handle DOS system files
 */
static void do_system_files __F((filesys), struct fs_struct *filesys)
{
#define FILE_BIO	0
#define FILE_DOS	1
#define FILE_CMD	2
#define FILE_HIMEM	3
#define FILE_NUM	4

#define FILE_NOT_NEEDED	((struct file_struct *)(NULL + 1))


  struct file_struct *fsp[FILE_NUM];
  int i, flags;

  /* Initialize file array to make the compiler happy*/
  for (i = 0; i < FILE_NUM; i++)
	fsp[i] = NULL;

  /* Search for various DOS system files */
  for (i = 0; system_files[i].bio_name != NULL; i++) {
	fsp[FILE_BIO] = findfname(filesys->root_dir, FALSE,
					system_files[i].bio_name,
					system_files[i].bio_ext);
	if (system_files[i].dos_name != NULL)
		fsp[FILE_DOS] = findfname(filesys->root_dir, FALSE,
					system_files[i].dos_name,
					system_files[i].dos_ext);
	else
		fsp[FILE_DOS] = FILE_NOT_NEEDED;
	if (system_files[i].cmd_name != NULL)
		fsp[FILE_CMD] = findfname(filesys->root_dir, TRUE,
					system_files[i].cmd_name,
					system_files[i].cmd_ext);
	else
		fsp[FILE_CMD] = FILE_NOT_NEEDED;
	if (system_files[i].himem_name != NULL)
		fsp[FILE_HIMEM] = findfname(filesys->root_dir, TRUE,
					system_files[i].himem_name,
					system_files[i].himem_ext);
	else
		fsp[FILE_HIMEM] = NULL;
	if ((fsp[FILE_BIO] != NULL) &&
	    (fsp[FILE_DOS] != NULL) &&
	    (fsp[FILE_CMD] != NULL))
		break;
  }
  if ((fsp[FILE_BIO] == NULL) ||
      (fsp[FILE_DOS] == NULL) ||
      (fsp[FILE_CMD] == NULL)) {
	prnerr("one of the DOS system files is missing");
	nbexit(EXIT_DOS_INVSYS);
  }

  /*
   * Move them within the root directory so that IO.SYS comes first, then
   * MSDOS.SYS and finally COMMAND.COM.
   */
  if (fsp[FILE_CMD] != FILE_NOT_NEEDED)
	movefirst(filesys->root_dir, fsp[FILE_CMD]);
  if (fsp[FILE_DOS] != FILE_NOT_NEEDED)
	movefirst(filesys->root_dir, fsp[FILE_DOS]);
  movefirst(filesys->root_dir, fsp[FILE_BIO]);

  /*
   * Handle various system flags. The NORPL flag has some magic:
   * Various types of DOS don't support the RPL mechanism of protecting
   * the memory occupied by the ramdisk driver. These are FreeDOS and
   * PTS-DOS. However, also older versions of MS-DOS and IBM's PC-DOS
   * don't support it. We try to identify those old DOS versions by
   * checking the size of IO.SYS or IBMBIO.COM against some magic value
   * which is a little bit below the size of IO.SYS of the first version
   * which does support RPL protection. Unfortunately, Caldera's OpenDOS
   * does support RPL, but it's IBMBIO.COM file is a lot smaller than
   * that of IBM's PC-DOS. I hope that the values selected in the table
   * above are at least a little bit reasonable.
   * The INT15 flag will be used when it is not possible to protect the
   * ramdisk via HIMEM.SYS. The XMS driver which comes with FreeDOS does
   * not work properly, so we set this flag automatically for FreeDOS. Also,
   * if we can't find an XMS driver in one of the ramdisk directories, we
   * can't use it. Of course even if we find an XMS driver it doesn't mean
   * that it gets called by CONFIG.SYS, but it is at least a little bit of
   * error protection.
   */
  flags = system_files[i].flags |
		(fsp[FILE_HIMEM] == NULL ? BOOT_FLAG_INT15 : 0) |
		(fsp[FILE_BIO]->size < system_files[i].rpl_size ?
							BOOT_FLAG_NORPL : 0);
  flags &= BOOT_FLAG_INT15 | BOOT_FLAG_NORPL;
  filesys->loadflags |= flags;

  /*
   * Set some boot sector information about IO.SYS. Some operating systems
   * require to load IO.SYS (or equiv.) completely by the boot sector, so we
   * have to set the total number of sectors occupied by IO.SYS in this case.
   * Otherwise only the first 4 sectors of IO.SYS needs to get loaded.
   */
  io_sys = fsp[FILE_BIO];
  io_sys_seg = system_files[i].sys_seg;
  if (system_files[i].use_size)
	io_sys_sects = howmany(io_sys->size, SECTSIZE);
  else
	io_sys_sects = 4;
}





/***************************************************************************

			Routines to generate the FAT

 ***************************************************************************/

/* Static variables for generating the FAT */
static unsigned short cur_cluster;	/* current cluster number */
static __u8 *fat_buf;			/* FAT buffer */



/*
 * Generate 12 or 16 bit FAT entries for a given cluster range
 */
static void genclusters __F((start, length),
				unsigned short start AND
				unsigned short length)
{
  unsigned short cur = start;
  unsigned short value;
  unsigned int i;

  while (cur < (unsigned short)(start + length)) {
	if (cur > curformat->maxcluster) {
		prnerr("not enough space on ramdisk");
		nbexit(EXIT_DOS_RDSPACE);
	}
	if (curformat->fat16) {
		value = (cur == (start + length - 1)) ? 0xffff : cur + 1;
		i = cur * 2;
		fat_buf[i]   = (unsigned char)(value & 0x00ff);
		fat_buf[i+1] = (unsigned char)((value & 0xff00) >> 8);
	} else {
		value = (cur == (start + length - 1)) ? 0xfff : cur + 1;
		i = (cur * 3) / 2;
		if ((cur * 3) % 2 == 0) {
			fat_buf[i]    = (unsigned char)(value & 0x0ff);
			fat_buf[i+1] |= (unsigned char)((value & 0xf00) >> 8);
		} else {
			fat_buf[i]   |= (unsigned char)((value & 0x00f) << 4);
			fat_buf[i+1]  = (unsigned char)((value & 0xff0) >> 4);
		}
	}
	cur++;
  }
}



/*
 * Scan through the directory tree to generate the FAT
 */
static void genfatdir __F((filesys, dsp),
				struct fs_struct *filesys AND
				struct dir_struct *dsp)
{
  struct file_struct *fsp;

  /* First compute the number of clusters for current directory */
  if (dsp != filesys->root_dir) {
	dsp->clustnum = howmany(DIR_ENTRIES(dsp) * sizeof(struct dos_dir),
						curformat->clustsize);
	dsp->cluster = cur_cluster;
	cur_cluster += dsp->clustnum;
	genclusters(dsp->cluster, dsp->clustnum);
  }

  /* Determine the number of clusters for each file in current directory */
  fsp = dsp->files;
  while (fsp != NULL) {
	fsp->clustnum = howmany(fsp->size, curformat->clustsize);
	fsp->cluster = cur_cluster;
	cur_cluster += fsp->clustnum;
	genclusters(fsp->cluster, fsp->clustnum);
	fsp = fsp->next;
  }

  /* Scan through all subdirectories recursively */
  dsp = dsp->subdirs;
  while (dsp != NULL) {
	genfatdir(filesys, dsp);
	dsp = dsp->next;
  }
}



/*
 * Generate the FAT for the files and directories in the tree.
 */
static void genfat __F((filesys), struct fs_struct *filesys)
{
  if (verbose > 1)
	printf("Generating FATs\n");

  fat_buf = (__u8 *)nbmalloc(curformat->fatsects * SECTSIZE);
  fat_buf[0] = (__u8)(curformat->diskid & 0xff);
  fat_buf[1] = 0xff;					/* filler byte */
  fat_buf[2] = 0xff;
  if (curformat->fat16)
	fat_buf[3] = 0xff;
  cur_cluster = FIRST_CLUSTER;

  genfatdir(filesys, filesys->root_dir);
}





/***************************************************************************

		Write directories and files into output file

 ***************************************************************************/

/*
 * Write a long file name into the directory buffer
 */
static void writelfn __F((dirp, namep),
				struct dos_dir **dirp AND
				struct fname_struct *namep)
{
  struct lfn_dir *lfnp = (struct lfn_dir *)(*dirp);
  ucs_t *cp;
  int chksum, i, j;

  /* Check if we have anything to do at all */
  if (namep->lfn_num == 0 || namep->lfn_name == NULL)
	return;

  /* Compute checksum of short file name */
  chksum = 0;
  for (i = 0; i < 8; i++) {
	chksum = (chksum >> 1) | ((chksum & 0x01) << 7);
	chksum = (chksum + chartotarget(namep->name[i])) & 0xff;
  }
  for (i = 0; i < 3; i++) {
	chksum = (chksum >> 1) | ((chksum & 0x01) << 7);
	chksum = (chksum + chartotarget(namep->ext[i])) & 0xff;
  }

  /* Now create all long file name directory entries */
  i = 1;
  j = 0;
  cp = namep->lfn_name;
  lfnp->sequence = (i & LFN_SEQ_MASK);
  lfnp->checksum = (chksum & 0xff);
  lfnp->attrib = ATTR_LFN;
  while (i < (namep->lfn_num + 1)) {
	if (j < 5)
		assign(lfnp->first_part[j], htot(*cp));
	else if (j < 11)
		assign(lfnp->second_part[j - 5], htot(*cp));
	else
		assign(lfnp->third_part[j - 11], htot(*cp));
	j++;
	cp++;
	if (j >= LFN_CHARS) {
		i++;
		if (i < (namep->lfn_num + 1)) {
			lfnp++;
			lfnp->sequence = (i & LFN_SEQ_MASK);
			lfnp->checksum = (chksum & 0xff);
			lfnp->attrib = ATTR_LFN;
			j = 0;
		}
	}
  }
  lfnp->sequence |= LFN_SEQ_END;

  /* Set pointer of next free directory entry */
  *dirp = (struct dos_dir *)(++lfnp);
}



/*
 * Write a short file name into the directory buffer
 */
static void writesfn __F((dirp, namep),
				struct dos_dir *dirp AND
				struct fname_struct *namep)
{
  int i;

  for (i = 0; i < 8; i++)
	dirp->name[i] = chartotarget(namep->name[i]);
  for (i = 0; i < 3; i++)
	dirp->ext[i] = chartotarget(namep->ext[i]);
}



/*
 * Recursively write each subdirectory
 */
static unsigned long writedir __F((sysentry, handle, dsp, parent, fatfs),
				struct sysdef *sysentry AND
				int handle AND
				struct dir_struct *dsp AND
				struct dir_struct *parent AND
				int fatfs)
{
  struct dos_dir *dirp;
  struct dir_struct *subdsp;
  struct file_struct *fsp;
  unsigned long writecnt;
  __u8 *buf;
  size_t size;

  /* Allocate memory for directory */
  if (parent == NULL)
	size = curformat->rootentries * sizeof(struct dos_dir);
  else
	size = dsp->clustnum * curformat->clustsize;
  buf = (__u8 *)nbmalloc(size);

  /* Generate the entries for current and parent directories and volume name */
  dirp = (struct dos_dir *)buf;
  if (parent != NULL) {
	bytecpy(".       ", dirp->name, sizeof(dirp->name));
	bytecpy("   ", dirp->ext, sizeof(dirp->ext));
	dirp->attrib = dsp->attrib;
	assign(dirp->time, htot(dsp->time));
	assign(dirp->date, htot(dsp->date));
	assign(dirp->cluster, htot(dsp->cluster));
	assign(dirp->size.low, htot(0));
	assign(dirp->size.high, htot(0));
	dirp++;
	bytecpy("..      ", dirp->name, sizeof(dirp->name));
	bytecpy("   ", dirp->ext, sizeof(dirp->ext));
	dirp->attrib = parent->attrib;
	assign(dirp->time, htot(parent->time));
	assign(dirp->date, htot(parent->date));
	assign(dirp->cluster, htot(parent->cluster));
	assign(dirp->size.low, htot(0));
	assign(dirp->size.high, htot(0));
	dirp++;
  } else if (sysentry->volumename != NULL) {
	bytecpy(sysentry->volumename, dirp->name,
				sizeof(dirp->name) + sizeof(dirp->ext));
	dirp->attrib = ATTR_LABEL;
	dirp++;
  }

  /* Write the file entries into directory. The files have to come first! */
  fsp = dsp->files;
  while (fsp != NULL) {
	writelfn(&dirp, &(fsp->name));
	writesfn(dirp, &(fsp->name));
	dirp->attrib = fsp->attrib;
	assign(dirp->time, htot(fsp->time));
	assign(dirp->date, htot(fsp->date));
	assign(dirp->cluster, htot(fsp->cluster));
	assign(dirp->size.low, htot(low_word(fsp->size)));
	assign(dirp->size.high, htot(high_word(fsp->size)));
	dirp++;
	fsp = fsp->next;
  }

  /* Write the subdir entries into directory */
  subdsp = dsp->subdirs;
  while (subdsp != NULL) {
	writelfn(&dirp, &(subdsp->name));
	writesfn(dirp, &(subdsp->name));
	dirp->attrib = subdsp->attrib;
	assign(dirp->time, htot(subdsp->time));
	assign(dirp->date, htot(subdsp->date));
	assign(dirp->cluster, htot(subdsp->cluster));
	assign(dirp->size.low, htot(0));
	assign(dirp->size.high, htot(0));
	dirp++;
	subdsp = subdsp->next;
  }

  /* Write the directory into output file and free directory memory */
  writecnt = nbwrite(buf, size, handle);
  free(buf);

  /* Write all files in the same order as setup in the FAT */
  fsp = dsp->files;
  while (fsp != NULL) {
	if (fatfs)
		writecnt += fatcopy(curformat->clustsize, handle, fsp);
	else
		writecnt += hostcopy(curformat->clustsize, handle, fsp);
	fsp = fsp->next;
  }

  /* Finally write all subdirectories into the output file */
  subdsp = dsp->subdirs;
  while (subdsp != NULL) {
	writecnt += writedir(sysentry, handle, subdsp, dsp, fatfs);
	subdsp = subdsp->next;
  }
  return(writecnt);
}





/***************************************************************************

		Create new ramdisk image from directory

 ***************************************************************************/

/*
 * When creating a hard disk image, adjust the format parameters to
 * match the actual ramdisk size.
 */
static struct disklayout *adjust_format __F((sysentry, filesys, oldformat, rdsize),
				struct sysdef *sysentry AND
				struct fs_struct *filesys AND
				const struct disklayout *oldformat AND
				unsigned long rdsize)
{
  struct disklayout *newformat;
  unsigned int rootsects;
  unsigned int fatsects;
  unsigned long l, m;

  /*
   * Allocate memory for new disk geometry record
   */
  if (verbose > 1)
	printf("Adjusting image parameters\n");
  newformat = (struct disklayout *)nbmalloc(sizeof(struct disklayout));
  *newformat = *oldformat;

  /*
   * From requested ramdisk size compute the disk geometry. Note that
   * we use the next largest number of tracks needed for the requested
   * ramdisk size, even if the ramdisk image in memory isn't large
   * enough. The runtime module will check if the maximum size gets
   * exceeded in a request. However, the ramdisk size in the format
   * record has to be the exact size, so that the partition table
   * gets constructed correctly.
   */
  newformat->size = rdsize;
  newformat->tracks = howmany(rdsize * SECTS_PER_KB,
					(oldformat->heads * oldformat->spt));
  assert(newformat->tracks <= MAX_CYLS);

  /*
   * Determine the number of root directory entries, and from this the
   * number of sectors required for the root directory.
   */
  rootsects = get_root_sects(filesys, oldformat);
  newformat->rootentries = (rootsects * SECTSIZE) / sizeof(struct dos_dir);

  /*
   * Determine the number of sectors per FAT and the maximum cluster number.
   * For this computation we use the real ramdisk size, e.g. the size of the
   * ramdisk "partition".
   */
  l = (rdsize * SECTS_PER_KB) - newformat->spt - rootsects -
					(filesys->boot_size / SECTSIZE);
  fatsects = 0;
  do {
	fatsects++;
	if (newformat->fat16)
		m = ((fatsects * SECTSIZE * 8) / 16 - FIRST_CLUSTER - 16) *
				(newformat->clustsize / SECTSIZE);
	else
		m = ((fatsects * SECTSIZE * 8) / 12 - FIRST_CLUSTER - 16) *
				(newformat->clustsize / SECTSIZE);
	m += fatsects * (sysentry->singlefat ? 1 : 2);
  } while (m < l);
  assert(fatsects <= newformat->fatsects);
  l -= fatsects * (sysentry->singlefat ? 1 : 2);
  newformat->fatsects = fatsects;
  newformat->maxcluster = (l + FIRST_CLUSTER) /
				(newformat->clustsize / SECTSIZE);

  /*
   * Let the user know what we got
   */
  if (verbose > 2) {
	printf("New image values:\n");
	printf("  Total size in kB:            %lu\n", newformat->size);
	printf("  Cluster size in Bytes:       %u\n", newformat->clustsize);
	printf("  Maximum cluster number:      %u\n", newformat->maxcluster);
	printf("  Number of FAT sectors:       %u\n", newformat->fatsects);
	printf("  Number of sectors per track: %u\n", newformat->spt);
	printf("  Number of cylinders:         %u\n", newformat->tracks);
	printf("  Number of heads:             %u\n", newformat->heads);
  }
  return(newformat);
}



/*
 * Read all directories and create a new ramdisk image with a 12 or
 * 16 bit FAT.
 */
static int rdcreate __F((sysentry, imgsize, loadflags, fatfs),
				struct sysdef *sysentry AND
				unsigned long *imgsize AND
				int *loadflags AND
				int fatfs)
{
  struct fs_struct filesys;
  unsigned long rdsize = (unsigned long)(sysentry->rdsize);
  unsigned long l, m;
  unsigned long writecnt = 0;
  int rdfile, i;

  /*
   * Read the root directory and setup the directory tree
   */
  if (verbose > 0)
	printf("Scanning directory tree of %s\n", sysentry->rdname);
  filesys.boot_size = 0;
  filesys.boot_block = NULL;
  filesys.root_dir = NULL;
  filesys.loadflags = *loadflags;
  if (fatfs)
	fatopen(sysentry, &filesys);
  else
	hostopen(sysentry, &filesys);

  /*
   * Handle configuration files
   */
  do_config_files(sysentry, &filesys);

  /*
   * Handle DOS system files
   */
  do_system_files(&filesys);

  /*
   * Determine the size of the image in the temporary file. For a hard
   * disk select the format which leaves at least 20% space, if a size
   * hasn't been specified already.
   */
  l = filesys.root_dir->totsize * SECTS_PER_KB;
  if (sysentry->usehd) {
	if (rdsize == 0) {
		m = 0;
		for (i = 0; hdlist[i].size != 0; i++) {
			m = ((l * 12L) / 10L) +
			    hdlist[i].spt +
			    ((sysentry->singlefat ? 1 : 2) * hdlist[i].fatsects) +
			    get_root_sects(&filesys, &hdlist[i]) +
			    (filesys.boot_size / SECTSIZE);
			if (hdlist[i].size >= (m / SECTS_PER_KB))
				break;
		}
		if (hdlist[i].size == 0) {
			prnerr("too much contents for ramdisk");
			nbexit(EXIT_DOS_RDSIZE);
		}
		rdsize = m / SECTS_PER_KB;
	} else {
		if (rdsize < MIN_RDSIZE) {
			prnerr("requested ramdisk size too small");
			nbexit(EXIT_DOS_RDSIZE);
		}
		for (i = 0; hdlist[i].size != 0; i++)
			if (hdlist[i].size >= rdsize)
				break;
		if (hdlist[i].size == 0) {
			prnerr("specified ramdisk size too large");
			nbexit(EXIT_DOS_RDSIZE);
		}
		m = l + hdlist[i].spt +
		    ((sysentry->singlefat ? 1 : 2) * hdlist[i].fatsects) +
		    get_root_sects(&filesys, &hdlist[i]) +
		    (filesys.boot_size / SECTSIZE);
		if (rdsize < (m / SECTS_PER_KB)) {
			prnerr("too much contents for specified ramdisk size");
			nbexit(EXIT_DOS_RDSIZE);
		}
	}
	curformat = adjust_format(sysentry, &filesys, &hdlist[i], rdsize);
  } else {
	if (rdsize == 0) {
		i = DEFAULT_FORMAT;
		rdsize = floppylist[i].size;
	} else {
		for (i = 0; floppylist[i].size != 0; i++)
			if (floppylist[i].size == rdsize)
				break;
		if (floppylist[i].size == 0) {
			prnerr("invalid ramdisk size given");
			nbexit(EXIT_DOS_RDSIZE);
		}
	}
	/* Floppies have a fixed number of root dir entries */
	if ((DIR_ENTRIES(filesys.root_dir) + 1) > floppylist[i].rootentries) {
		prnerr("too many root directory entries");
		nbexit(EXIT_DOS_RDSIZE);
	}
	m = l + ((sysentry->singlefat ? 1 : 2) * floppylist[i].fatsects) +
	    get_root_sects(&filesys, &floppylist[i]) +
					(filesys.boot_size / SECTSIZE) + 1;
	if (rdsize < (m / SECTS_PER_KB)) {
		prnerr("too much contents for ramdisk");
		nbexit(EXIT_DOS_RDSIZE);
	}
	/* Adjust format if necessary */
	curformat = (struct disklayout *)nbmalloc(sizeof(struct disklayout));
	*curformat = floppylist[i];
	if (sysentry->singlefat)
		curformat->maxcluster += curformat->fatsects /
					(curformat->clustsize / SECTSIZE);
  }
  if (verbose > 0)
	printf("Generating %lu kB ramdisk image\n", curformat->size);

  /*
   * Generate the FAT.
   */
  genfat(&filesys);

  /*
   * Create a temporary file to receive the ramdisk image.
   */
  if ((rdfile = opentemp(verbose < 3)) == -1)
	nbexit(-1);

  /*
   * If creating a hard disk image, write out the partition table first.
   */
  if (sysentry->usehd) {
	if (verbose > 0)
		printf("Writing partition table into temporary ramdisk image\n");
	writecnt += putpart(rdfile);
  }

  /*
   * Setup the floppy boot sector.
   */
  set_boot_block(sysentry, &filesys);

  /*
   * Write the floppy boot sector, both FATs and the directory trees and
   * files.
   */
  if (verbose > 0)
	printf("Writing temporary ramdisk image file\n");
  writecnt += nbwrite(filesys.boot_block, filesys.boot_size, rdfile);
  writecnt += nbwrite(fat_buf, curformat->fatsects * SECTSIZE, rdfile);
  if (!sysentry->singlefat)
	writecnt += nbwrite(fat_buf, curformat->fatsects * SECTSIZE, rdfile);
  writecnt += writedir(sysentry, rdfile, filesys.root_dir, NULL, fatfs);

  /*
   * Close source image/directory. This will delete all intermediate
   * structures including the boot block buffer and the directory tree.
   */
  if (fatfs)
	fatclose(&filesys);
  else
	hostclose(&filesys);
  deltree(filesys.root_dir);

  /* Prepare return values */
  *imgsize = writecnt;
  *loadflags = filesys.loadflags;
  return(rdfile);
}





/***************************************************************************

		Main routine to open/create ramdisk image file

 ***************************************************************************/

/*
 * Open ram disk image file. If it's not a file but a directory, generate
 * an image file using this directory.
 */
int openrd __F((sysentry, imgsize, geom, loadflags),
				struct sysdef *sysentry AND
				unsigned long *imgsize AND
				struct disk_geometry *geom AND
				int *loadflags)
{
  struct stat sbuf;
  unsigned long sectnum;
  int rdfile;

  /*
   * Check whether we have a block device, an image file or a
   * directory.
   */
  if (stat(sysentry->rdname, &sbuf) != 0) {
	prnerr("unable to find ramdisk \"%s\"", sysentry->rdname);
	nbexit(EXIT_DOS_RDSTAT);
  }
  if (S_ISREG(sbuf.st_mode) || S_ISBLK(sbuf.st_mode))
	rdfile = rdcreate(sysentry, imgsize, loadflags, TRUE);
  else if (S_ISDIR(sbuf.st_mode))
	rdfile = rdcreate(sysentry, imgsize, loadflags, FALSE);
  else {
	prnerr("\"%s\" is not a file, block device or directory", sysentry->rdname);
	nbexit(EXIT_DOS_INVRD);
  }

  /*
   * Return the new ramdisk geometry
   */
  sectnum = curformat->size * SECTS_PER_KB;
  assign(geom->num_sectors.low, htot(low_word(sectnum)));
  assign(geom->num_sectors.high, htot(high_word(sectnum)));
  assign(geom->sect_per_track, htot(curformat->spt));
  assign(geom->cylinders, htot(curformat->tracks));
  assign(geom->num_heads, htot(curformat->heads));
  geom->no_hd_flag = sysentry->nohd & 0xff;
  if (sysentry->usehd)
	geom->boot_drive = DISKID_HD;
  else
	geom->boot_drive = DISKID_FLOPPY;
  if (curformat != NULL)
	free(curformat);

  /*
   * Rewind output file and return its file handle.
   */
  if (lseek(rdfile, 0L, 0) != 0) {
	prnerr("unable to seek to beginning of ramdisk file");
	nbexit(EXIT_SEEK);
  }
  return(rdfile);
}

