OpenSolaris_b135/cmd/fs.d/pcfs/mkfs/mkfs.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <ctype.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <libintl.h>
#include <locale.h>
#include <sys/fdio.h>
#include <sys/dktp/fdisk.h>
#include <sys/dkio.h>
#include <sys/sysmacros.h>
#include "mkfs_pcfs.h"
#include <sys/fs/pc_fs.h>
#include <sys/fs/pc_dir.h>
#include <sys/fs/pc_label.h>
#include <macros.h>

/*
 *	mkfs (for pcfs)
 *
 *	Install a boot block, FAT, and (if desired) the first resident
 *	of the new fs.
 *
 *	XXX -- floppy opens need O_NDELAY?
 */
#define	DEFAULT_LABEL "NONAME"

static char	*BootBlkFn = NULL;
static char	*DiskName = NULL;
static char	*FirstFn = NULL;
static char	*Label = NULL;
static char	Firstfileattr = 0x20;
static int	Outputtofile = 0;
static int	SunBPBfields = 0;
static int	GetFsParams = 0;
static int	Fatentsize = 0;
static int	Imagesize = 3;
static int	Notreally = 0;
static int	Verbose = 0;
static int	MakeFAT32 = 0;

/*
 * If there is an FDISK entry for the device where we're about to
 * make the file system, we ought to make a file system that has the
 * same size FAT as the FDISK table claims.  We track the size FDISK
 * thinks in this variable.
 */
static int	FdiskFATsize = 0;

static int	GetSize = 1;	/* Unless we're given as arg, must look it up */
static ulong_t	TotSize;	/* Total size of FS in # of sectors */
static int	GetSPC = 1;	/* Unless we're given as arg, must calculate */
static ulong_t	SecPerClust;	/* # of sectors per cluster */
static int	GetOffset = 1;	/* Unless we're given as arg, must look it up */
static ulong_t	RelOffset;	/* Relative start sector (hidden sectors) */
static int	GetSPT = 1;	/* Unless we're given as arg, must look it up */
static ushort_t	SecPerTrk;	/* # of sectors per track */
static int	GetTPC = 1;	/* Unless we're given as arg, must look it up */
static ushort_t	TrkPerCyl;	/* # of tracks per cylinder */
static int	GetResrvd = 1;	/* Unless we're given as arg, must calculate */
static int	Resrvd;		/* Number of reserved sectors */
static int	GetBPF = 1;	/* Unless we're given as arg, must calculate */
static int	BitsPerFAT;	/* Total size of FS in # of sectors */

static ulong_t	TotalClusters;	/* Computed total number of clusters */

/*
 * Unless we are told otherwise, we should use fdisk table for non-diskettes.
 */
static int	DontUseFdisk = 0;

/*
 * Function prototypes
 */
#ifndef i386
static void swap_pack_grabsebpb(bpb_t *wbpb, struct _boot_sector *bsp);
static void swap_pack_bpb32cpy(struct _boot_sector32 *bsp, bpb_t *wbpb);
static void swap_pack_sebpbcpy(struct _boot_sector *bsp, bpb_t *wbpb);
static void swap_pack_grabbpb(bpb_t *wbpb, struct _boot_sector *bsp);
static void swap_pack_bpbcpy(struct _boot_sector *bsp, bpb_t *wbpb);
#endif

static uchar_t *build_rootdir(bpb_t *wbpb, char *ffn, int fffd,
	ulong_t ffsize, pc_cluster32_t ffstart, ulong_t *rdirsize);
static uchar_t *build_fat(bpb_t *wbpb, struct fat_od_fsi *fsinfop,
	ulong_t bootblksize, ulong_t *fatsize, char *ffn, int *fffd,
	ulong_t *ffsize, pc_cluster32_t *ffstartclust);

static char *stat_actual_disk(char *diskname, struct stat *info, char **suffix);

static void compare_existing_with_computed(int fd, char *suffix,
	bpb_t *wbpb, int *prtsize, int *prtspc, int *prtbpf, int *prtnsect,
	int *prtntrk, int *prtfdisk, int *prthidden, int *prtrsrvd,
	int *dashos);
static void print_reproducing_command(int fd, char *actualdisk, char *suffix,
	bpb_t *wbpb);
static void compute_file_area_size(bpb_t *wbpb);
static void write_fat32_bootstuff(int fd, boot_sector_t *bsp,
	struct fat_od_fsi *fsinfop, off64_t seekto);
static void sanity_check_options(int argc, int optind);
static void compute_cluster_size(bpb_t *wbpb);
static void find_fixed_details(int fd, bpb_t *wbpb);
static void dirent_fname_fill(struct pcdir *dep, char *fn);
static void floppy_bpb_fillin(bpb_t *wbpb,
	int diam, int hds, int spt);
static void read_existing_bpb(int fd, bpb_t *wbpb);
static void warn_funky_fatsize(void);
static void warn_funky_floppy(void);
static void dirent_time_fill(struct pcdir *dep);
static void parse_suboptions(char *optsstr);
static void header_for_dump(void);
static void write_bootsects(int fd, boot_sector_t *bsp, bpb_t *wbpb,
	struct fat_od_fsi *fsinfop, off64_t seekto);
static void fill_bpb_sizes(bpb_t *wbpb, struct ipart part[],
	int partno, off64_t offset);
static void set_fat_string(bpb_t *wbpb, int fatsize);
static void partn_lecture(char *dn);
static void store_16_bits(uchar_t **bp, uint32_t v);
static void store_32_bits(uchar_t **bp, uint32_t v);
static void lookup_floppy(struct fd_char *fdchar, bpb_t *wbpb);
static void label_volume(char *lbl, bpb_t *wbpb);
static void mark_cluster(uchar_t *fatp, pc_cluster32_t clustnum,
	uint32_t value);
static void missing_arg(char *option);
static void dashm_bail(int fd);
static void dump_bytes(uchar_t *, int);
static void write_rest(bpb_t *wbpb, char *efn,
	int dfd, int sfd, int remaining);
static void write_fat(int fd, off64_t seekto, char *fn, char *lbl,
	char *ffn, bpb_t *wbpb);
static void bad_arg(char *option);
static void usage(void);

static int prepare_image_file(char *fn, bpb_t *wbpb);
static int verify_bootblkfile(char *fn, boot_sector_t *bs,
	ulong_t *blkfilesize);
static int open_and_examine(char *dn, bpb_t *wbpb);
static int verify_firstfile(char *fn, ulong_t *filesize);
static int lookup_FAT_size(uchar_t partid);
static int powerofx_le_y(int x, int y, int value);
static int open_and_seek(char *dn, bpb_t *wbpb, off64_t *seekto);
static int warn_mismatch(char *desc, char *src, int expect, int assigned);
static int copy_bootblk(char *fn, boot_sector_t *bootsect,
	ulong_t *bootblksize);
static int parse_drvnum(char *pn);
static int seek_nofdisk(int fd, bpb_t *wbpb, off64_t *seekto);
static int ask_nicely(char *special);
static int seek_partn(int fd, char *pn, bpb_t *wbpb, off64_t *seekto);
static int yes(void);

/*
 *  usage
 *
 *	Display usage message and exit.
 */
static
void
usage(void)
{
	(void) fprintf(stderr,
	    gettext("pcfs usage: mkfs [-F FSType] [-V] [-m] "
	    "[-o specific_options] special\n"));

	(void) fprintf(stderr,
	    gettext(" -V: print this command line and return\n"
	    " -m: dump command line used to create a FAT on this media\n"
	    "\t(other options are ignored if this option is chosen).\n"
	    " -o: pcfs_specific_options:\n"
	    "\t'pcfs_specific_options' is a comma separated list\n"
	    "\tincluding one or more of the following options:\n"
	    "\t    N,v,r,h,s,b=label,B=filename,i=filename,\n"
	    "\t    spc=n,fat=n,nsect=n,ntrack=n,nofdisk,size=n,\n"
	    "\t    reserve=n,hidden=n\n\n"));

	(void) fprintf(stderr,
	    gettext("'Special' should specify a raw diskette "
	    "or raw fixed disk device.  \"Fixed\"\n"
	    "disks (which include high-capacity removable "
	    "media such as Zip disks)\n"
	    "may be further qualified with a logical "
	    "drive specifier.\n"
	    "Examples are: /dev/rdiskette and "
	    "/dev/rdsk/c0t0d0p0:c\n"));
	exit(1);
}

static
int
yes(void)
{
	char *affirmative = gettext("yY");
	char *a = affirmative;
	int b;

	b = getchar();
	while (b == '\n' && b != '\0' && b != EOF)
		b = getchar();
	while (*a) {
		if (b == (int)*a)
			break;
		a++;
	}
	return (*a);
}

/*
 * powerofx_le_y
 *	args of x,y, and value to be checked
 *	returns 1 if x**n == value and n >= 0 and value <= y
 *	returns 0 otherwise
 */
static
int
powerofx_le_y(int x, int y, int value)
{
	int ispower = 0;
	int pow = 1;

	if (value < 1 || value > y)
		return (ispower);

	do {
		if (pow == value) {
			ispower = 1;
			break;
		}
		pow *= x;
	} while (pow <= y);

	return (ispower);
}

static
int
ask_nicely(char *special)
{
	/*
	 * 4228473 - No way to non-interactively make a pcfs filesystem
	 *
	 *	If we don't have an input TTY, or we aren't really doing
	 *	anything, then don't ask questions.  Assume a yes answer
	 *	to any questions we would ask.
	 */
	if (Notreally || !isatty(fileno(stdin)))
		return (1);

	(void) printf(
	    gettext("Construct a new FAT file system on %s: (y/n)? "), special);
	(void) fflush(stdout);
	return (yes());
}

/*
 * store_16_bits
 *	Save the lower 16 bits of a 32 bit value (v) into the provided
 *	buffer (pointed at by *bp), and increment the buffer pointer
 *	as well.  This way the routine can be called multiple times in
 *	succession to fill buffers.  The value is stored in little-endian
 *	order.
 */
static
void
store_16_bits(uchar_t **bp, uint32_t v)
{
	uchar_t *l = *bp;

	*l++ = v & 0xff;
	*l = (v >> 8) & 0xff;
	*bp += 2;
}

/*
 * store_32_bits
 * 	Save the 32 bit value (v) into the provided buffer (pointed
 *	at by *bp), and increment the buffer pointer as well.  This way
 *	the routine can be called multiple times in succession to fill
 *	buffers.  The value is stored in little-endian order.
 */
static
void
store_32_bits(uchar_t **bp, uint32_t v)
{
	uchar_t *l = *bp;
	int b;

	for (b = 0; b < 4; b++) {
		*l++ = v & 0xff;
		v = v >> 8;
	}
	*bp += 4;
}

/*
 *  dump_bytes  -- display bytes as hex numbers.
 *		   b is the pointer to the byte buffer
 *		   n is the number of bytes in the buffer
 */
/* Note: BPL = bytes to display per line */
#define	BPL 16

static
void
dump_bytes(uchar_t *b, int n)
{
	int cd = n;
	int cu = 0;
	int o = 0;
	int bl;
	int ac;

	/* Display offset, 16 bytes per line, and printable ascii version */
	while (cd > 0) {
		ac = 0;
		(void) printf("\n%06x: ", o);
		for (bl = 0; bl < BPL; bl++) {
			if (cu+bl < n) {
				(void) printf("%02x ", (b[cu+bl] & 0xff));
				ac++;
			}
			else
				(void) printf("   ");
		}
		for (bl = 0; bl < BPL; bl++) {
			if ((cu+bl < n) &&
			    ((b[cu+bl] >= ' ') && (b[cu+bl] <= '~')))
				(void) printf("%c", b[cu+bl]);
			else
				(void) printf(".");
		}
		cu += ac; o += ac; cd -= ac;
	}
	(void) printf("\n\n");
}

/*
 *  header_for_dump  --  display simple header over what will be output.
 */
static
void
header_for_dump(void)
{
	int bl;

	(void) printf("\n        ");
	for (bl = 0; bl < BPL; bl++)
		(void) printf("%02x ", bl);
	(void) printf("\n       ");
	bl = 3*BPL;
	while (bl-- > 0)
		(void) printf("-");
}

/*
 *  parse_drvnum
 *	Convert a partition name into a drive number.
 */
static
int
parse_drvnum(char *pn)
{
	int drvnum;

	/*
	 * Determine logical drive to seek after.
	 */
	if (strlen(pn) == 1 && *pn >= 'c' && *pn <= 'z') {
		drvnum = *pn - 'c' + 1;
	} else if (*pn >= '0' && *pn <= '9') {
		char *d;
		int v, m, c;

		v = 0;
		d = pn;
		while (*d && *d >= '0' && *d <= '9') {
			c = strlen(d);
			m = 1;
			while (--c)
				m *= 10;
			v += m * (*d - '0');
			d++;
		}

		if (*d || v > 24) {
			(void) fprintf(stderr,
			    gettext("%s: bogus logical drive specification.\n"),
			    pn);
			return (-1);
		}
		drvnum = v;
	} else if (strcmp(pn, "boot") == 0) {
		drvnum = 99;
	} else {
		(void) fprintf(stderr,
		    gettext("%s: bogus logical drive specification.\n"), pn);
		return (-1);
	}

	return (drvnum);
}

/*
 *  Define some special logical drives we use.
 */
#define	BOOT_PARTITION_DRIVE	99
#define	PRIMARY_DOS_DRIVE	1

/*
 * isDosDrive()
 *	Boolean function.  Give it the systid field for an fdisk partition
 *	and it decides if that's a systid that describes a DOS drive.  We
 *	use systid values defined in sys/dktp/fdisk.h.
 */
static int
isDosDrive(uchar_t checkMe)
{
	return ((checkMe == DOSOS12) || (checkMe == DOSOS16) ||
	    (checkMe == DOSHUGE) || (checkMe == FDISK_WINDOWS) ||
	    (checkMe == FDISK_EXT_WIN) || (checkMe == FDISK_FAT95) ||
	    (checkMe == DIAGPART));
}

/*
 * isDosExtended()
 *	Boolean function.  Give it the systid field for an fdisk partition
 *	and it decides if that's a systid that describes an extended DOS
 *	partition.
 */
static int
isDosExtended(uchar_t checkMe)
{
	return ((checkMe == EXTDOS) || (checkMe == FDISK_EXTLBA));
}

/*
 * isBootPart()
 *	Boolean function.  Give it the systid field for an fdisk partition
 *	and it decides if that's a systid that describes a Solaris boot
 *	partition.
 */
static int
isBootPart(uchar_t checkMe)
{
	return (checkMe == X86BOOT);
}

static
int
warn_mismatch(char *desc, char *src, int expect, int assigned)
{
	if (expect == assigned)
		return (assigned);

	/*
	 * 4228473 - No way to non-interactively make a pcfs filesystem
	 *
	 *	If we don't have an input TTY, or we aren't really doing
	 *	anything, then don't ask questions.  Assume a yes answer
	 *	to any questions we would ask.
	 */
	if (Notreally || !isatty(fileno(stdin))) {
		(void) printf(gettext("WARNING: User supplied %s is %d,"
		    "\nbut value obtained from the %s is %d.\n"
		    "Using user supplied value.\n"),
		    desc, assigned, src, expect);
		return (assigned);
	}

	(void) printf(gettext("User supplied %s is %d."
	    "\nThe value obtained from the %s is %d.\n"),
	    desc, assigned, src, expect);

	(void) printf(
	    gettext("Continue with value given on command line (y/n)? "));
	(void) fflush(stdout);
	if (yes())
		return (assigned);
	else
		exit(2);
	/*NOTREACHED*/
}

static
void
fill_fat32_bpb(bpb_t *wbpb)
{
	/*
	 * ExtFlags means (according to MSDN BPB (FAT32) document)
	 *
	 * Bit 8 indicates info written to the active FAT is written
	 * to all copies of the FAT.  (I think they mean bit 7, with
	 * numbering starting at 0)
	 *
	 * Lowest 4 bits of field are the 0 based FAT number of the
	 * Active FAT.  (only meaningful if bit 8 is set)
	 *
	 * Field contains combination of these values:
	 *
	 *	VALUE				DESCRIPTION
	 * BGBPB_F_ActiveFATMsk		Mask for low four bits
	 * (0x000F)
	 * BGBPB_F_NoFATMirror		If set FAT mirroring disabled.
	 * (0x0080)			If clear, FAT mirroring enabled.
	 *
	 * We set the value based on what I've seen on all the FAT32 drives
	 * I've seen created by Windows.
	 *
	 */
	wbpb->bpb32.ext_flags = 0x0;
	/*
	 * No real explanation of the fs_vers file in the BPB doc.  The
	 * high byte is supposed to be the major version and the low the
	 * minor version.  Again I set according to what I've seen on Windows.
	 */
	wbpb->bpb32.fs_vers_lo = '\0';
	wbpb->bpb32.fs_vers_hi = '\0';
	/*
	 * The convention appears to be to place the fs info sector
	 * immediately after the boot sector, and that the backup boot
	 * sector should be at sector 6. (based on what I see with
	 * Windows)
	 */
	wbpb->bpb32.fsinfosec = 1;
	wbpb->bpb32.backupboot = 6;
}

static
void
fill_bpb_sizes(bpb_t *wbpb, struct ipart part[], int partno, off64_t offset)
{
	ulong_t usesize;

	if (GetFsParams || GetSize) {
		usesize = ltohi(part[partno].numsect);
		if (Verbose) {
			(void) printf(
			    gettext("Partition size (from FDISK table) "
			    "= %d sectors.\n"), usesize);
		}
	} else {
		usesize = warn_mismatch(
		    gettext("length of partition (in sectors)"),
		    gettext("FDISK table"),
		    ltohi(part[partno].numsect), TotSize);
	}

	if (GetFsParams) {
		TotSize = usesize;
	} else {
		if (usesize > 0xffff)
			wbpb->bpb.sectors_in_volume = 0;
		else
			wbpb->bpb.sectors_in_volume = usesize;
		wbpb->bpb.sectors_in_logical_volume = usesize;
	}

	wbpb->bpb.hidden_sectors = offset;

	if (GetFsParams) {
		RelOffset = offset;
	} else {
		wbpb->sunbpb.bs_offset_high = offset >> 16;
		wbpb->sunbpb.bs_offset_low = offset & 0xFFFF;
	}
}

/*
 *  lookup_FAT_size
 *
 *	Given the FDISK partition file system identifier, return the
 *	expected FAT size for the partition.
 */
static
int
lookup_FAT_size(uchar_t partid)
{
	int rval;

	switch (partid) {
	case DOSOS12:
		rval = 12;
		break;
	case DOSOS16:
	case DOSHUGE:
	case FDISK_FAT95:
	case X86BOOT:
		rval = 16;
		break;
	case FDISK_WINDOWS:
	case FDISK_EXT_WIN:
		rval = 32;
		break;
	case EXTDOS:
	case FDISK_EXTLBA:
	default:
		rval = -1;
		break;
	}

	return (rval);
}

/*
 *  seek_partn
 *
 *	Seek to the beginning of the partition where we need to install
 *	the new FAT.  Zero return for any error, but print error
 *	messages here.
 */
static
int
seek_partn(int fd, char *pn, bpb_t *wbpb, off64_t *seekto)
{
	struct ipart part[FD_NUMPART];
	struct mboot extmboot;
	struct mboot mb;
	diskaddr_t xstartsect;
	off64_t nextseek = 0;
	off64_t lastseek = 0;
	int logicalDriveCount = 0;
	int extendedPart = -1;
	int primaryPart = -1;
	int bootPart = -1;
	uint32_t xnumsect = 0;
	int drvnum;
	int driveIndex;
	int i;
	/*
	 * Count of drives in the current extended partition's
	 * FDISK table, and indexes of the drives themselves.
	 */
	int extndDrives[FD_NUMPART];
	int numDrives = 0;
	/*
	 * Count of drives (beyond primary) in master boot record's
	 * FDISK table, and indexes of the drives themselves.
	 */
	int extraDrives[FD_NUMPART];
	int numExtraDrives = 0;

	if ((drvnum = parse_drvnum(pn)) < 0)
		return (PART_NOT_FOUND);

	if (read(fd, &mb, sizeof (mb)) != sizeof (mb)) {
		(void) fprintf(stderr,
		    gettext("Couldn't read a Master Boot Record?!\n"));
		return (PART_NOT_FOUND);
	}

	if (ltohs(mb.signature) != BOOTSECSIG) {
		(void) fprintf(stderr,
		    gettext("Bad Sig on master boot record!\n"));
		return (PART_NOT_FOUND);
	}

	*seekto = 0;

	/*
	 * Copy partition table into memory
	 */
	(void) memcpy(part, mb.parts, sizeof (part));

	/*
	 * Get a summary of what is in the Master FDISK table.
	 * Normally we expect to find one partition marked as a DOS drive.
	 * This partition is the one Windows calls the primary dos partition.
	 * If the machine has any logical drives then we also expect
	 * to find a partition marked as an extended DOS partition.
	 *
	 * Sometimes we'll find multiple partitions marked as DOS drives.
	 * The Solaris fdisk program allows these partitions
	 * to be created, but Windows fdisk no longer does.  We still need
	 * to support these, though, since Windows does.  We also need to fix
	 * our fdisk to behave like the Windows version.
	 *
	 * It turns out that some off-the-shelf media have *only* an
	 * Extended partition, so we need to deal with that case as
	 * well.
	 *
	 * Only a single (the first) Extended or Boot Partition will
	 * be recognized.  Any others will be ignored.
	 */
	for (i = 0; i < FD_NUMPART; i++) {
		if (isDosDrive(part[i].systid)) {
			if (primaryPart < 0) {
				logicalDriveCount++;
				primaryPart = i;
			} else {
				extraDrives[numExtraDrives++] = i;
			}
			continue;
		}
		if ((extendedPart < 0) && isDosExtended(part[i].systid)) {
			extendedPart = i;
			continue;
		}
		if ((bootPart < 0) && isBootPart(part[i].systid)) {
			bootPart = i;
			continue;
		}
	}

	if (drvnum == BOOT_PARTITION_DRIVE) {
		if (bootPart < 0) {
			(void) fprintf(stderr,
			    gettext("No boot partition found on drive\n"));
			return (PART_NOT_FOUND);
		}
		if ((*seekto = ltohi(part[bootPart].relsect)) == 0) {
			(void) fprintf(stderr, gettext("Bogus FDISK entry? "
			    "A boot partition starting\nat sector 0 would "
			    "collide with the FDISK table!\n"));
			return (PART_NOT_FOUND);
		}

		fill_bpb_sizes(wbpb, part, bootPart, *seekto);
		*seekto *= BPSEC;
		FdiskFATsize = lookup_FAT_size(part[bootPart].systid);
		if (Verbose)
			(void) printf(gettext("Boot partition's offset: "
			    "Sector %x.\n"), *seekto/BPSEC);
		if (lseek64(fd, *seekto, SEEK_SET) < 0) {
			(void) fprintf(stderr, gettext("Partition %s: "), pn);
			perror("");
			return (PART_NOT_FOUND);
		}
		return (PART_FOUND);
	}

	if (drvnum == PRIMARY_DOS_DRIVE && primaryPart >= 0) {
		if ((*seekto = ltohi(part[primaryPart].relsect)) == 0) {
			(void) fprintf(stderr, gettext("Bogus FDISK entry? "
			    "A partition starting\nat sector 0 would "
			    "collide with the FDISK table!\n"));
			return (PART_NOT_FOUND);
		}

		fill_bpb_sizes(wbpb, part, primaryPart, *seekto);
		*seekto *= BPSEC;
		FdiskFATsize = lookup_FAT_size(part[primaryPart].systid);
		if (Verbose)
			(void) printf(gettext("Partition's offset: "
			    "Sector %x.\n"), *seekto/BPSEC);
		if (lseek64(fd, *seekto, SEEK_SET) < 0) {
			(void) fprintf(stderr, gettext("Partition %s: "), pn);
			perror("");
			return (PART_NOT_FOUND);
		}
		return (PART_FOUND);
	}

	/*
	 * We are not looking for the C: drive (or there was no primary
	 * drive found), so we had better have an extended partition or
	 * extra drives in the Master FDISK table.
	 */
	if ((extendedPart < 0) && (numExtraDrives == 0)) {
		(void) fprintf(stderr,
		    gettext("No such logical drive "
		    "(missing extended partition entry)\n"));
		return (PART_NOT_FOUND);
	}

	if (extendedPart >= 0) {
		nextseek = xstartsect = ltohi(part[extendedPart].relsect);
		xnumsect = ltohi(part[extendedPart].numsect);
		do {
			/*
			 *  If the seek would not cause us to change
			 *  position on the drive, then we're out of
			 *  extended partitions to examine.
			 */
			if (nextseek == lastseek)
				break;
			logicalDriveCount += numDrives;
			/*
			 *  Seek the next extended partition, and find
			 *  logical drives within it.
			 */
			if (lseek64(fd, nextseek * BPSEC, SEEK_SET) < 0 ||
			    read(fd, &extmboot, sizeof (extmboot)) !=
			    sizeof (extmboot)) {
				perror(gettext("Unable to read extended "
				    "partition record"));
				return (PART_NOT_FOUND);
			}
			(void) memcpy(part, extmboot.parts, sizeof (part));
			lastseek = nextseek;
			if (ltohs(extmboot.signature) != MBB_MAGIC) {
				(void) fprintf(stderr,
				    gettext("Bad signature on "
				    "extended partition\n"));
				return (PART_NOT_FOUND);
			}
			/*
			 *  Count up drives, and track where the next
			 *  extended partition is in case we need it.  We
			 *  are expecting only one extended partition.  If
			 *  there is more than one we'll only go to the
			 *  first one we see, but warn about ignoring.
			 */
			numDrives = 0;
			for (i = 0; i < FD_NUMPART; i++) {
				if (isDosDrive(part[i].systid)) {
					extndDrives[numDrives++] = i;
					continue;
				} else if (isDosExtended(part[i].systid)) {
					if (nextseek != lastseek) {
						/*
						 * Already found an extended
						 * partition in this table.
						 */
						(void) fprintf(stderr,
						    gettext("WARNING: "
						    "Ignoring unexpected "
						    "additional extended "
						    "partition"));
						continue;
					}
					nextseek = xstartsect +
					    ltohi(part[i].relsect);
					continue;
				}
			}
		} while (drvnum > logicalDriveCount + numDrives);

		if (drvnum <= logicalDriveCount + numDrives) {
			/*
			 * The number of logical drives we've found thus
			 * far is enough to get us to the one we were
			 * searching for.
			 */
			driveIndex = logicalDriveCount + numDrives - drvnum;
			*seekto =
			    ltohi(part[extndDrives[driveIndex]].relsect) +
			    lastseek;
			if (*seekto == lastseek) {
				(void) fprintf(stderr,
				    gettext("Bogus FDISK entry?  A logical "
				    "drive starting at\nsector 0x%llx would "
				    "collide with the\nFDISK information in "
				    "that sector.\n"), *seekto);
				return (PART_NOT_FOUND);
			} else if (*seekto <= xstartsect ||
			    *seekto >= (xstartsect + xnumsect)) {
				(void) fprintf(stderr,
				    gettext("Bogus FDISK entry?  "
				    "Logical drive start sector (0x%llx)\n"
				    "not within extended partition! "
				    "(Expected in range 0x%x - 0x%x)\n"),
				    *seekto, xstartsect + 1,
				    xstartsect + xnumsect - 1);
				return (PART_NOT_FOUND);
			}
			fill_bpb_sizes(wbpb, part, extndDrives[driveIndex],
			    *seekto);
			*seekto *= BPSEC;
			FdiskFATsize = lookup_FAT_size(
			    part[extndDrives[driveIndex]].systid);
			if (Verbose)
				(void) printf(gettext("Partition's offset: "
				    "Sector 0x%x.\n"), *seekto/BPSEC);
			if (lseek64(fd, *seekto, SEEK_SET) < 0) {
				(void) fprintf(stderr,
				    gettext("Partition %s: "), pn);
				perror("");
				return (PART_NOT_FOUND);
			}
			return (PART_FOUND);
		} else {
			/*
			 * We ran out of extended dos partition
			 * drives.  The only hope now is to go
			 * back to extra drives defined in the master
			 * fdisk table.  But we overwrote that table
			 * already, so we must load it in again.
			 */
			logicalDriveCount += numDrives;
			(void) memcpy(part, mb.parts, sizeof (part));
		}
	}
	/*
	 *  Still haven't found the drive, is it an extra
	 *  drive defined in the main FDISK table?
	 */
	if (drvnum <= logicalDriveCount + numExtraDrives) {
		driveIndex = logicalDriveCount + numExtraDrives - drvnum;
		*seekto = ltohi(part[extraDrives[driveIndex]].relsect);
		if (*seekto == 0) {
			(void) fprintf(stderr, gettext("Bogus FDISK entry? "
			    "A partition starting\nat sector 0 would "
			    "collide with the FDISK table!\n"));
			return (PART_NOT_FOUND);
		}

		fill_bpb_sizes(wbpb, part, extraDrives[driveIndex], *seekto);
		*seekto *= BPSEC;
		FdiskFATsize =
		    lookup_FAT_size(part[extraDrives[driveIndex]].systid);
		if (Verbose)
			(void) printf(gettext("Partition's offset: "
			    "Sector %x.\n"), *seekto/BPSEC);
		if (lseek64(fd, *seekto, SEEK_SET) < 0) {
			(void) fprintf(stderr,
			    gettext("Partition %s: "), pn);
			perror("");
			return (PART_NOT_FOUND);
		}
		return (PART_FOUND);
	}
	(void) fprintf(stderr, gettext("No such logical drive\n"));
	return (PART_NOT_FOUND);
}

/*
 *  seek_nofdisk
 *
 *	User is asking us to trust them that they know best.
 *	We basically won't do much seeking here, the only seeking we'll do
 *	is if the 'hidden' parameter was given.
 */
static
int
seek_nofdisk(int fd, bpb_t *wbpb, off64_t *seekto)
{
	if (TotSize > 0xffff)
		wbpb->bpb.sectors_in_volume = 0;
	else
		wbpb->bpb.sectors_in_volume = (short)TotSize;
	wbpb->bpb.sectors_in_logical_volume = TotSize;

	*seekto = RelOffset * BPSEC;
	wbpb->bpb.hidden_sectors = RelOffset;
	wbpb->sunbpb.bs_offset_high = RelOffset >> 16;
	wbpb->sunbpb.bs_offset_low = RelOffset & 0xFFFF;

	if (Verbose)
		(void) printf(gettext("Requested offset: Sector %x.\n"),
		    *seekto/BPSEC);

	if (lseek64(fd, *seekto, SEEK_SET) < 0) {
		(void) fprintf(stderr,
		    gettext("User specified start sector %d"), RelOffset);
		perror("");
		return (PART_NOT_FOUND);
	}
	return (PART_FOUND);
}

/*
 * set_fat_string
 *
 *	Fill in the type string of the FAT
 */
static
void
set_fat_string(bpb_t *wbpb, int fatsize)
{
	if (fatsize == 12) {
		(void) strncpy((char *)wbpb->ebpb.type, FAT12_TYPE_STRING,
		    strlen(FAT12_TYPE_STRING));
	} else if (fatsize == 16) {
		(void) strncpy((char *)wbpb->ebpb.type, FAT16_TYPE_STRING,
		    strlen(FAT16_TYPE_STRING));
	} else {
		(void) strncpy((char *)wbpb->ebpb.type, FAT32_TYPE_STRING,
		    strlen(FAT32_TYPE_STRING));
	}
}

/*
 *  prepare_image_file
 *
 *	Open the file that will hold the image (as opposed to the image
 *	being written to the boot sector of an actual disk).
 */
static
int
prepare_image_file(char *fn, bpb_t *wbpb)
{
	int fd;
	char zerobyte = '\0';

	if ((fd = open(fn, O_RDWR | O_CREAT | O_EXCL, 0666)) < 0) {
		perror(fn);
		exit(2);
	}

	if (Imagesize == 5) {
		/* Disk image of a 1.2M floppy */
		wbpb->bpb.sectors_in_volume = 2 * 80 * 15;
		wbpb->bpb.sectors_in_logical_volume = 2 * 80 * 15;
		wbpb->bpb.sectors_per_track = 15;
		wbpb->bpb.heads = 2;
		wbpb->bpb.media = 0xF9;
		wbpb->bpb.num_root_entries = 224;
		wbpb->bpb.sectors_per_cluster = 1;
		wbpb->bpb.sectors_per_fat = 7;
	} else {
		/* Disk image of a 1.44M floppy */
		wbpb->bpb.sectors_in_volume = 2 * 80 * 18;
		wbpb->bpb.sectors_in_logical_volume = 2 * 80 * 18;
		wbpb->bpb.sectors_per_track = 18;
		wbpb->bpb.heads = 2;
		wbpb->bpb.media = 0xF0;
		wbpb->bpb.num_root_entries = 224;
		wbpb->bpb.sectors_per_cluster = 1;
		wbpb->bpb.sectors_per_fat = 9;
	}

	/*
	 * Make a holey file, with length the exact
	 * size of the floppy image.
	 */
	if (lseek(fd, (wbpb->bpb.sectors_in_volume * BPSEC)-1, SEEK_SET) < 0) {
		(void) close(fd);
		perror(fn);
		exit(2);
	}

	if (write(fd, &zerobyte, 1) != 1) {
		(void) close(fd);
		perror(fn);
		exit(2);
	}

	if (lseek(fd, 0, SEEK_SET) < 0) {
		(void) close(fd);
		perror(fn);
		exit(2);
	}

	Fatentsize = 12;  /* Size of fat entry in bits */
	set_fat_string(wbpb, Fatentsize);

	wbpb->ebpb.phys_drive_num = 0;

	wbpb->sunbpb.bs_offset_high = 0;
	wbpb->sunbpb.bs_offset_low = 0;

	return (fd);
}

/*
 *  partn_lecture
 *
 *	Give a brief sermon on dev_name user should pass to
 *	the program from the command line.
 *
 */
static
void
partn_lecture(char *dn)
{
	(void) fprintf(stderr,
	    gettext("\nDevice %s was assumed to be a diskette.\n"
	    "A diskette specific operation failed on this device.\n"
	    "If the device is a hard disk, provide the name of "
	    "the full physical disk,\n"
	    "and qualify that name with a logical drive specifier.\n\n"
	    "Hint: the device is usually something similar to\n\n"
	    "/dev/rdsk/c0d0p0 or /dev/rdsk/c0t0d0p0 (x86)\n"
	    "/dev/rdsk/c0t5d0s2 (sparc)\n\n"
	    "The drive specifier is appended to the device name."
	    " For example:\n\n"
	    "/dev/rdsk/c0t5d0s2:c or /dev/rdsk/c0d0p0:boot\n\n"), dn);
}

static
void
warn_funky_floppy(void)
{
	(void) fprintf(stderr,
	    gettext("Use the 'nofdisk' option to create file systems\n"
	    "on non-standard floppies.\n\n"));
	exit(4);
}

static
void
warn_funky_fatsize(void)
{
	(void) fprintf(stderr,
	    gettext("Non-standard FAT size requested for floppy.\n"
	    "The 'nofdisk' option must be used to\n"
	    "override the 12 bit floppy default.\n\n"));
	exit(4);
}

static
void
floppy_bpb_fillin(bpb_t *wbpb, int diam, int hds, int spt)
{
	switch (diam) {
	case 3:
		switch (hds) {
		case 2:
			switch (spt) {
			case 9:
				wbpb->bpb.media = 0xF9;
				wbpb->bpb.num_root_entries = 112;
				wbpb->bpb.sectors_per_cluster = 2;
				wbpb->bpb.sectors_per_fat = 3;
				break;
			case 18:
				wbpb->bpb.media = 0xF0;
				wbpb->bpb.num_root_entries = 224;
				wbpb->bpb.sectors_per_cluster = 1;
				wbpb->bpb.sectors_per_fat = 9;
				break;
			case 36:
				wbpb->bpb.media = 0xF0;
				wbpb->bpb.num_root_entries = 240;
				wbpb->bpb.sectors_per_cluster = 2;
				wbpb->bpb.sectors_per_fat = 9;
				break;
			default:
				(void) fprintf(stderr,
				    gettext("Unknown diskette parameters!  "
				    "3.5'' diskette with %d heads "
				    "and %d sectors/track.\n"), hds, spt);
				warn_funky_floppy();
			}
			break;
		case 1:
		default:
			(void) fprintf(stderr,
			    gettext("Unknown diskette parameters!  "
			    "3.5'' diskette with %d heads "), hds);
			warn_funky_floppy();
		}
		break;
	case 5:
		switch (hds) {
		case 2:
			switch (spt) {
			case 15:
				wbpb->bpb.media = 0xF9;
				wbpb->bpb.num_root_entries = 224;
				wbpb->bpb.sectors_per_cluster = 1;
				wbpb->bpb.sectors_per_fat = 7;
				break;
			case 9:
				wbpb->bpb.media = 0xFD;
				wbpb->bpb.num_root_entries = 112;
				wbpb->bpb.sectors_per_cluster = 2;
				wbpb->bpb.sectors_per_fat = 2;
				break;
			case 8:
				wbpb->bpb.media = 0xFF;
				wbpb->bpb.num_root_entries = 112;
				wbpb->bpb.sectors_per_cluster = 1;
				wbpb->bpb.sectors_per_fat = 2;
				break;
			default:
				(void) fprintf(stderr,
				    gettext("Unknown diskette parameters!  "
				    "5.25'' diskette with %d heads "
				    "and %d sectors/track.\n"), hds, spt);
				warn_funky_floppy();
			}
			break;
		case 1:
			switch (spt) {
			case 9:
				wbpb->bpb.media = 0xFC;
				wbpb->bpb.num_root_entries = 64;
				wbpb->bpb.sectors_per_cluster = 1;
				wbpb->bpb.sectors_per_fat = 2;
				break;
			case 8:
				wbpb->bpb.media = 0xFE;
				wbpb->bpb.num_root_entries = 64;
				wbpb->bpb.sectors_per_cluster = 1;
				wbpb->bpb.sectors_per_fat = 1;
				break;
			default:
				(void) fprintf(stderr,
				    gettext("Unknown diskette parameters! "
				    "5.25'' diskette with %d heads "
				    "and %d sectors/track.\n"), hds, spt);
				warn_funky_floppy();
			}
			break;
		default:
			(void) fprintf(stderr,
			    gettext("Unknown diskette parameters! "
			    "5.25'' diskette with %d heads."), hds);
			warn_funky_floppy();
		}
		break;
	default:
		(void) fprintf(stderr,
		    gettext("\nUnknown diskette type.  Only know about "
		    "5.25'' and 3.5'' diskettes.\n"));
		warn_funky_floppy();
	}
}

/*
 *  lookup_floppy
 *
 *	Look up a media descriptor byte and other crucial BPB values
 *	based on floppy characteristics.
 */
static
void
lookup_floppy(struct fd_char *fdchar, bpb_t *wbpb)
{
	ulong_t tsize;
	ulong_t cyls, spt, hds, diam;

	cyls = fdchar->fdc_ncyl;
	diam = fdchar->fdc_medium;
	spt = fdchar->fdc_secptrack;
	hds = fdchar->fdc_nhead;

	tsize = cyls * hds * spt;

	if (GetFsParams)
		TotSize = tsize;

	if (GetSize) {
		wbpb->bpb.sectors_in_logical_volume = tsize;
	} else {
		wbpb->bpb.sectors_in_logical_volume =
		    warn_mismatch(
		    gettext("length of partition (in sectors)"),
		    gettext("FDIOGCHAR call"), tsize, TotSize);
	}
	wbpb->bpb.sectors_in_volume =
	    (short)wbpb->bpb.sectors_in_logical_volume;

	if (GetSPT) {
		wbpb->bpb.sectors_per_track = spt;
	} else {
		wbpb->bpb.sectors_per_track =
		    warn_mismatch(
		    gettext("sectors per track"),
		    gettext("FDIOGCHAR call"), spt, SecPerTrk);
		spt = wbpb->bpb.sectors_per_track;
	}

	if (GetTPC) {
		wbpb->bpb.heads = hds;
	} else {
		wbpb->bpb.heads =
		    warn_mismatch(
		    gettext("number of heads"),
		    gettext("FDIOGCHAR call"), hds, TrkPerCyl);
		hds = wbpb->bpb.heads;
	}

	Fatentsize = 12;  /* Size of fat entry in bits */
	if (!GetBPF && BitsPerFAT != Fatentsize) {
		warn_funky_fatsize();
	}
	set_fat_string(wbpb, Fatentsize);

	wbpb->ebpb.phys_drive_num = 0;

	wbpb->bpb.hidden_sectors = 0;
	wbpb->sunbpb.bs_offset_high = 0;
	wbpb->sunbpb.bs_offset_low = 0;

	floppy_bpb_fillin(wbpb, diam, hds, spt);
}

/*
 *  compute_cluster_size
 *
 *	Compute an acceptable sectors/cluster value.
 *
 * 	Based on values from the Hardware White Paper
 *	from Microsoft.
 *	"Microsoft Extensible Firmware Initiative
 *	 FAT32 File System Specification
 *	 FAT: General Overview of On-Disk Format"
 *
 *	Version 1.03, December 6, 2000
 *
 */
static
void
compute_cluster_size(bpb_t *wbpb)
{
	ulong_t volsize;
	ulong_t spc;
	ulong_t rds, tmpval1, tmpval2;
	ulong_t fatsz;
	int newfat = 16;

#define	FAT12_MAX_CLUSTERS	0x0FF4
#define	FAT16_MAX_CLUSTERS	0xFFF4
#define	FAT32_MAX_CLUSTERS	0x0FFFFFF0
#define	FAT32_SUGGESTED_NCLUST	0x400000

	/* compute volume size in sectors. */
	volsize = wbpb->bpb.sectors_in_volume ? wbpb->bpb.sectors_in_volume :
	    wbpb->bpb.sectors_in_logical_volume;
	volsize -= wbpb->bpb.resv_sectors;

	if (GetSPC) {
		/*
		 * User indicated what sort of FAT to create,
		 * make sure it is valid with the given size
		 * and compute an SPC value.
		 */
		if (!MakeFAT32) { /* FAT16 */
			/* volsize is in sectors */
			if (volsize < FAT12_MAX_CLUSTERS) {
				(void) fprintf(stderr,
				    gettext("Requested size is too "
				    "small for FAT16.\n"));
				exit(4);
			}
			/* SPC must be a power of 2 */
			for (spc = 1; spc <= 64; spc = spc * 2) {
				if (volsize < spc * FAT16_MAX_CLUSTERS)
					break;
			}
			if (volsize > (spc * FAT16_MAX_CLUSTERS)) {
				(void) fprintf(stderr,
				    gettext("Requested size is too "
				    "large for FAT16.\n"));
				exit(4);
			}
		} else { /* FAT32 */
			/* volsize is in sectors */
			if (volsize < FAT16_MAX_CLUSTERS) {
				(void) fprintf(stderr,
				    gettext("Requested size is too "
				    "small for FAT32.\n"));
				exit(4);
			}
			/* SPC must be a power of 2 */
			for (spc = 1; spc <= 64; spc = spc * 2) {
				if (volsize < (spc * FAT32_SUGGESTED_NCLUST))
					break;
			}
			if (volsize > (spc * FAT32_MAX_CLUSTERS)) {
				(void) fprintf(stderr,
				    gettext("Requested size is too "
				    "large for FAT32.\n"));
				exit(4);
			}
		}
	} else {
		/*
		 * User gave the SPC as an explicit option,
		 * make sure it will work with the requested
		 * volume size.
		 */
		int nclust;

		spc = SecPerClust;
		nclust = volsize / spc;

		if (nclust <= FAT16_MAX_CLUSTERS && MakeFAT32) {
			(void) fprintf(stderr, gettext("Requested size is too "
			    "small for FAT32.\n"));
			exit(4);
		}
		if (!MakeFAT32) {
			/* Determine if FAT12 or FAT16 */
			if (nclust < FAT12_MAX_CLUSTERS)
				newfat = 12;
			else if (nclust < FAT16_MAX_CLUSTERS)
				newfat = 16;
			else {
				(void) fprintf(stderr,
				    gettext("Requested size is too "
				    "small for FAT32.\n"));
				exit(4);
			}
		}
	}

	/*
	 * RootDirSectors = ((BPB_RootEntCnt * 32) +
	 *	(BPB_BytsPerSec  1)) / BPB_BytsPerSec;
	 */
	rds = ((wbpb->bpb.num_root_entries * 32) +
	    (wbpb->bpb.bytes_sector - 1)) / wbpb->bpb.bytes_sector;

	if (GetBPF) {
		if (MakeFAT32)
			Fatentsize = 32;
		else
			Fatentsize = newfat;
	} else {
		Fatentsize = BitsPerFAT;

		if (Fatentsize == 12 &&
		    (volsize - rds) >= DOS_F12MAXC * spc) {
			/*
			 * If we don't have an input TTY, or we aren't
			 * really doing anything, then don't ask
			 * questions.  Assume a yes answer to any
			 * questions we would ask.
			 */
			if (Notreally || !isatty(fileno(stdin))) {
				(void) printf(
				gettext("Volume too large for 12 bit FAT,"
				    " increasing to 16 bit FAT size.\n"));
				(void) fflush(stdout);
				Fatentsize = 16;
			} else {
				(void) printf(
				gettext("Volume too large for a 12 bit FAT.\n"
				    "Increase to 16 bit FAT "
				    "and continue (y/n)? "));
				(void) fflush(stdout);
				if (yes())
					Fatentsize = 16;
				else
					exit(5);
			}
		}
	}
	wbpb->bpb.sectors_per_cluster = spc;

	if (!GetFsParams && FdiskFATsize < 0) {
		(void) printf(
		    gettext("Cannot verify chosen/computed FAT "
		    "entry size (%d bits) with FDISK table.\n"
		    "FDISK table has an unknown file system "
		    "type for this device.  Giving up...\n"),
		    Fatentsize, Fatentsize);
		exit(6);
	} else if (!GetFsParams && FdiskFATsize && FdiskFATsize != Fatentsize) {
		(void) printf(
		    gettext("Chosen/computed FAT entry size (%d bits) "
		    "does not match FDISK table (%d bits).\n"),
		    Fatentsize, FdiskFATsize);
		(void) printf(
		    gettext("Use -o fat=%d to build a FAT "
		    "that matches the FDISK entry.\n"), FdiskFATsize);
		exit(6);
	}
	set_fat_string(wbpb, Fatentsize);
	/*
	 * Compure the FAT sizes according to algorithm from Microsoft:
	 *
	 * RootDirSectors = ((BPB_RootEntCnt * 32) +
	 *	(BPB_BytsPerSec  1)) / BPB_BytsPerSec;
	 * TmpVal1 = DskSize - (BPB_ResvdSecCnt + RootDirSectors);
	 * TmpVal2 = (256 * BPB_SecPerClus) + BPB_NumFATs;
	 * If (FATType == FAT32)
	 * 	TmpVal2 = TmpVal2 / 2;
	 * FATSz = (TMPVal1 + (TmpVal2  1)) / TmpVal2;
	 * If (FATType == FAT32) {
	 * 	BPB_FATSz16 = 0;
	 *	BPB_FATSz32 = FATSz;
	 * } else {
	 *	BPB_FATSz16 = LOWORD(FATSz);
	 * 	// there is no BPB_FATSz32 in a FAT16 BPB
	 * }
	 */
	tmpval1 = volsize - (wbpb->bpb.resv_sectors + rds);

	tmpval2 = (256 * wbpb->bpb.sectors_per_cluster) + wbpb->bpb.num_fats;

	if (Fatentsize == 32)
		tmpval2 = tmpval2 / 2;

	fatsz = (tmpval1 + (tmpval2 - 1)) / tmpval2;

	/* Compute a sector/fat figure */
	switch (Fatentsize) {
	case 32:
		wbpb->bpb.sectors_per_fat = 0;
		wbpb->bpb32.big_sectors_per_fat = fatsz;
		if (Verbose)
			(void) printf("compute_cluster_size: Sectors per "
			    "FAT32 = %d\n", wbpb->bpb32.big_sectors_per_fat);
		break;
	case 12:
	default:	/* 16 bit FAT */
		wbpb->bpb.sectors_per_fat = (ushort_t)(fatsz & 0x0000FFFF);
		if (Verbose)
			(void) printf("compute_cluster_size: Sectors per "
			    "FAT16 = %d\n", wbpb->bpb.sectors_per_fat);
		break;
	}
}

static
void
find_fixed_details(int fd, bpb_t *wbpb)
{
	struct dk_geom dginfo;

	/*
	 *  Look up the last remaining bits of info we need
	 *  that is specific to the hard drive using a disk ioctl.
	 */
	if (GetSPT || GetTPC) {
		if (ioctl(fd, DKIOCG_VIRTGEOM, &dginfo) == -1 &&
		    ioctl(fd, DKIOCG_PHYGEOM, &dginfo) == -1 &&
		    ioctl(fd, DKIOCGGEOM, &dginfo) == -1) {
			(void) close(fd);
			perror(
			    gettext("Drive geometry lookup (need "
			    "tracks/cylinder and/or sectors/track"));
			exit(2);
		}
	}

	wbpb->bpb.heads = (GetTPC ? dginfo.dkg_nhead : TrkPerCyl);
	wbpb->bpb.sectors_per_track = (GetSPT ? dginfo.dkg_nsect : SecPerTrk);

	if (Verbose) {
		if (GetTPC) {
			(void) printf(
			    gettext("DKIOCG determined number of heads = %d\n"),
			    dginfo.dkg_nhead);
		}
		if (GetSPT) {
			(void) printf(
			    gettext("DKIOCG determined sectors per track"
			    " = %d\n"), dginfo.dkg_nsect);
		}
	}

	/*
	 * XXX - MAY need an additional flag (or flags) to set media
	 * and physical drive number fields.  That in the case of weird
	 * floppies that have to go through 'nofdisk' route for formatting.
	 */
	wbpb->bpb.media = 0xF8;
	if (MakeFAT32)
		wbpb->bpb.num_root_entries = 0;
	else
		wbpb->bpb.num_root_entries = 512;
	wbpb->ebpb.phys_drive_num = 0x80;
	compute_cluster_size(wbpb);
}

static
char *
stat_actual_disk(char *diskname, struct stat *info, char **suffix)
{
	char *actualdisk;

	if (stat(diskname, info)) {
		/*
		 *  Device named on command line doesn't exist.  That
		 *  probably means there is a partition-specifying
		 *  suffix attached to the actual disk name.
		 */
		actualdisk = strtok(strdup(diskname), ":");
		if (*suffix = strchr(diskname, ':'))
			(*suffix)++;

		if (stat(actualdisk, info)) {
			perror(actualdisk);
			exit(2);
		}
	} else {
		actualdisk = strdup(diskname);
	}

	return (actualdisk);
}

static
void
compute_file_area_size(bpb_t *wbpb)
{
	int FATSz;
	int TotSec;
	int DataSec;
	int RootDirSectors =
	    ((wbpb->bpb.num_root_entries * 32) + (wbpb->bpb.bytes_sector - 1)) /
	    wbpb->bpb.bytes_sector;

	if (wbpb->bpb.sectors_per_fat) {
		/*
		 * Good old FAT12 or FAT16
		 */
		FATSz = wbpb->bpb.sectors_per_fat;
		TotSec = wbpb->bpb.sectors_in_volume;
	} else {
		/*
		 *  FAT32
		 */
		FATSz = wbpb->bpb32.big_sectors_per_fat;
		TotSec = wbpb->bpb.sectors_in_logical_volume;
	}

	DataSec = TotSec -
	    (wbpb->bpb.resv_sectors + (wbpb->bpb.num_fats * FATSz) +
	    RootDirSectors);


	/*
	 * Now change sectors to clusters
	 */
	TotalClusters = DataSec / wbpb->bpb.sectors_per_cluster;

	if (Verbose)
		(void) printf(gettext("Disk has a file area of %d "
		    "allocation units,\neach with %d sectors = %d "
		    "bytes.\n"), TotalClusters, wbpb->bpb.sectors_per_cluster,
		    TotalClusters * wbpb->bpb.sectors_per_cluster * BPSEC);
}

#ifndef i386
/*
 *  swap_pack_{bpb,bpb32,sebpb}cpy
 *
 *	If not on an x86 we assume the structures making up the bpb
 *	were not packed and that longs and shorts need to be byte swapped
 *	(we've kept everything in host order up until now).  A new architecture
 *	might not need to swap or might not need to pack, in which case
 *	new routines will have to be written.  Of course if an architecture
 *	supports both packing and little-endian host order, it can follow the
 *	same path as the x86 code.
 */
static
void
swap_pack_bpbcpy(struct _boot_sector *bsp, bpb_t *wbpb)
{
	uchar_t *fillp;

	fillp = (uchar_t *)&(bsp->bs_filler[ORIG_BPB_START_INDEX]);

	store_16_bits(&fillp, wbpb->bpb.bytes_sector);
	*fillp++ = wbpb->bpb.sectors_per_cluster;
	store_16_bits(&fillp, wbpb->bpb.resv_sectors);
	*fillp++ = wbpb->bpb.num_fats;
	store_16_bits(&fillp, wbpb->bpb.num_root_entries);
	store_16_bits(&fillp, wbpb->bpb.sectors_in_volume);
	*fillp++ = wbpb->bpb.media;
	store_16_bits(&fillp, wbpb->bpb.sectors_per_fat);
	store_16_bits(&fillp, wbpb->bpb.sectors_per_track);
	store_16_bits(&fillp, wbpb->bpb.heads);
	store_32_bits(&fillp, wbpb->bpb.hidden_sectors);
	store_32_bits(&fillp, wbpb->bpb.sectors_in_logical_volume);

	*fillp++ = wbpb->ebpb.phys_drive_num;
	*fillp++ = wbpb->ebpb.reserved;
	*fillp++ = wbpb->ebpb.ext_signature;
	store_32_bits(&fillp, wbpb->ebpb.volume_id);
	(void) strncpy((char *)fillp, (char *)wbpb->ebpb.volume_label, 11);
	fillp += 11;
	(void) strncpy((char *)fillp, (char *)wbpb->ebpb.type, 8);
}

static
void
swap_pack_bpb32cpy(struct _boot_sector32 *bsp, bpb_t *wbpb)
{
	uchar_t *fillp;
	int r;

	fillp = (uchar_t *)&(bsp->bs_filler[ORIG_BPB_START_INDEX]);

	store_16_bits(&fillp, wbpb->bpb.bytes_sector);
	*fillp++ = wbpb->bpb.sectors_per_cluster;
	store_16_bits(&fillp, wbpb->bpb.resv_sectors);
	*fillp++ = wbpb->bpb.num_fats;
	store_16_bits(&fillp, wbpb->bpb.num_root_entries);
	store_16_bits(&fillp, wbpb->bpb.sectors_in_volume);
	*fillp++ = wbpb->bpb.media;
	store_16_bits(&fillp, wbpb->bpb.sectors_per_fat);
	store_16_bits(&fillp, wbpb->bpb.sectors_per_track);
	store_16_bits(&fillp, wbpb->bpb.heads);
	store_32_bits(&fillp, wbpb->bpb.hidden_sectors);
	store_32_bits(&fillp, wbpb->bpb.sectors_in_logical_volume);

	store_32_bits(&fillp, wbpb->bpb32.big_sectors_per_fat);
	store_16_bits(&fillp, wbpb->bpb32.ext_flags);
	*fillp++ = wbpb->bpb32.fs_vers_lo;
	*fillp++ = wbpb->bpb32.fs_vers_hi;
	store_32_bits(&fillp, wbpb->bpb32.root_dir_clust);
	store_16_bits(&fillp, wbpb->bpb32.fsinfosec);
	store_16_bits(&fillp, wbpb->bpb32.backupboot);
	for (r = 0; r < 6; r++)
		store_16_bits(&fillp, wbpb->bpb32.reserved[r]);

	*fillp++ = wbpb->ebpb.phys_drive_num;
	*fillp++ = wbpb->ebpb.reserved;
	*fillp++ = wbpb->ebpb.ext_signature;
	store_32_bits(&fillp, wbpb->ebpb.volume_id);
	(void) strncpy((char *)fillp, (char *)wbpb->ebpb.volume_label, 11);
	fillp += 11;
	(void) strncpy((char *)fillp, (char *)wbpb->ebpb.type, 8);
}

static
void
swap_pack_sebpbcpy(struct _boot_sector *bsp, bpb_t *wbpb)
{
	uchar_t *fillp;

	fillp = bsp->bs_sun_bpb;
	store_16_bits(&fillp, wbpb->sunbpb.bs_offset_high);
	store_16_bits(&fillp, wbpb->sunbpb.bs_offset_low);
}

static
void
swap_pack_grabbpb(bpb_t *wbpb, struct _boot_sector *bsp)
{
	uchar_t *grabp;

	grabp = (uchar_t *)&(bsp->bs_filler[ORIG_BPB_START_INDEX]);

	((uchar_t *)&(wbpb->bpb.bytes_sector))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb.bytes_sector))[0] = *grabp++;
	wbpb->bpb.sectors_per_cluster = *grabp++;
	((uchar_t *)&(wbpb->bpb.resv_sectors))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb.resv_sectors))[0] = *grabp++;
	wbpb->bpb.num_fats = *grabp++;
	((uchar_t *)&(wbpb->bpb.num_root_entries))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb.num_root_entries))[0] = *grabp++;
	((uchar_t *)&(wbpb->bpb.sectors_in_volume))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb.sectors_in_volume))[0] = *grabp++;
	wbpb->bpb.media = *grabp++;
	((uchar_t *)&(wbpb->bpb.sectors_per_fat))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb.sectors_per_fat))[0] = *grabp++;
	((uchar_t *)&(wbpb->bpb.sectors_per_track))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb.sectors_per_track))[0] = *grabp++;
	((uchar_t *)&(wbpb->bpb.heads))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb.heads))[0] = *grabp++;
	((uchar_t *)&(wbpb->bpb.hidden_sectors))[3] = *grabp++;
	((uchar_t *)&(wbpb->bpb.hidden_sectors))[2] = *grabp++;
	((uchar_t *)&(wbpb->bpb.hidden_sectors))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb.hidden_sectors))[0] = *grabp++;
	((uchar_t *)&(wbpb->bpb.sectors_in_logical_volume))[3] = *grabp++;
	((uchar_t *)&(wbpb->bpb.sectors_in_logical_volume))[2] = *grabp++;
	((uchar_t *)&(wbpb->bpb.sectors_in_logical_volume))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb.sectors_in_logical_volume))[0] = *grabp++;
	wbpb->ebpb.phys_drive_num = *grabp++;
	wbpb->ebpb.reserved = *grabp++;
	wbpb->ebpb.ext_signature = *grabp++;
	((uchar_t *)&(wbpb->ebpb.volume_id))[3] = *grabp++;
	((uchar_t *)&(wbpb->ebpb.volume_id))[2] = *grabp++;
	((uchar_t *)&(wbpb->ebpb.volume_id))[1] = *grabp++;
	((uchar_t *)&(wbpb->ebpb.volume_id))[0] = *grabp++;

	(void) strncpy((char *)wbpb->ebpb.volume_label, (char *)grabp, 11);
	grabp += 11;
	(void) strncpy((char *)wbpb->ebpb.type, (char *)grabp, 8);
}

static
void
swap_pack_grabsebpb(bpb_t *wbpb, struct _boot_sector *bsp)
{
	uchar_t *grabp;

	grabp = bsp->bs_sun_bpb;
	((uchar_t *)&(wbpb->sunbpb.bs_offset_high))[1] = *grabp++;
	((uchar_t *)&(wbpb->sunbpb.bs_offset_high))[0] = *grabp++;
	((uchar_t *)&(wbpb->sunbpb.bs_offset_low))[1] = *grabp++;
	((uchar_t *)&(wbpb->sunbpb.bs_offset_low))[0] = *grabp++;
}

static
void
swap_pack_grab32bpb(bpb_t *wbpb, struct _boot_sector *bsp)
{
	uchar_t *grabp;

	grabp = (uchar_t *)&(bsp->bs_filler[BPB_32_START_INDEX]);

	((uchar_t *)&(wbpb->bpb32.big_sectors_per_fat))[3] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.big_sectors_per_fat))[2] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.big_sectors_per_fat))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.big_sectors_per_fat))[0] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.ext_flags))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.ext_flags))[0] = *grabp++;
	wbpb->bpb32.fs_vers_lo = *grabp++;
	wbpb->bpb32.fs_vers_hi = *grabp++;
	((uchar_t *)&(wbpb->bpb32.root_dir_clust))[3] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.root_dir_clust))[2] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.root_dir_clust))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.root_dir_clust))[0] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.fsinfosec))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.fsinfosec))[0] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.backupboot))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.backupboot))[0] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.reserved[0]))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.reserved[0]))[0] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.reserved[1]))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.reserved[1]))[0] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.reserved[2]))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.reserved[2]))[0] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.reserved[3]))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.reserved[3]))[0] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.reserved[4]))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.reserved[4]))[0] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.reserved[5]))[1] = *grabp++;
	((uchar_t *)&(wbpb->bpb32.reserved[5]))[0] = *grabp++;
}
#endif	/* ! i386 */

static
void
dashm_bail(int fd)
{
	(void) fprintf(stderr,
	    gettext("This media does not appear to be "
	    "formatted with a FAT file system.\n"));
	(void) close(fd);
	exit(6);
}

/*
 *  read_existing_bpb
 *
 *	Grab the first sector, which we think is a bios parameter block.
 *	If it looks bad, bail.  Otherwise fill in the parameter struct
 *	fields that matter.
 */
static
void
read_existing_bpb(int fd, bpb_t *wbpb)
{
	boot_sector_t ubpb;

	if (read(fd, ubpb.buf, BPSEC) < BPSEC) {
		perror(gettext("Read BIOS parameter block "
		    "from previously formatted media"));
		(void) close(fd);
		exit(6);
	}

	if (ltohs(ubpb.mb.signature) != BOOTSECSIG) {
		dashm_bail(fd);
	}

#ifdef i386
	(void) memcpy(&(wbpb->bpb), &(ubpb.bs.bs_front.bs_bpb),
	    sizeof (wbpb->bpb));
	(void) memcpy(&(wbpb->ebpb), &(ubpb.bs.bs_ebpb), sizeof (wbpb->ebpb));
#else
	swap_pack_grabbpb(wbpb, &(ubpb.bs));
#endif
	if (SunBPBfields) {
#ifdef i386
		(void) memcpy(&(wbpb->sunbpb), &(ubpb.bs.bs_sebpb),
		    sizeof (wbpb->sunbpb));
#else
		swap_pack_grabsebpb(wbpb, &(ubpb.bs));
#endif
	}
	if (wbpb->bpb.bytes_sector != BPSEC) {
		(void) fprintf(stderr,
		    gettext("Bogus bytes per sector value.\n"));
		if (!powerofx_le_y(2, BPSEC * 8, wbpb->bpb.bytes_sector)) {
			(void) fprintf(stderr,
			    gettext("The device name may be missing a "
			    "logical drive specifier.\n"));
			(void) close(fd);
			exit(6);
		} else {
			(void) fprintf(stderr,
			    gettext("Do not know how to build FATs with a\n"
			    "non-standard sector size. Standard "
			    "size is %d bytes,\nyour sector size "
			    "is %d bytes.\n"), BPSEC,
			    wbpb->bpb.bytes_sector);
			(void) close(fd);
			exit(6);
		}
	}
	if (!(powerofx_le_y(2, 128, wbpb->bpb.sectors_per_cluster))) {
		(void) fprintf(stderr,
		    gettext("Bogus sectors per cluster value.\n"));
		(void) fprintf(stderr,
		    gettext("The device name may be missing a "
		    "logical drive specifier.\n"));
		(void) close(fd);
		exit(6);
	}

	if (wbpb->bpb.sectors_per_fat == 0) {
#ifdef i386
		(void) memcpy(&(wbpb->bpb32), &(ubpb.bs32.bs_bpb32),
		    sizeof (wbpb->bpb32));
#else
		swap_pack_grab32bpb(wbpb, &(ubpb.bs));
#endif
		compute_file_area_size(wbpb);
		if ((wbpb->bpb32.big_sectors_per_fat * BPSEC / 4) >=
		    TotalClusters) {
			MakeFAT32 = 1;
		} else {
			dashm_bail(fd);
		}
	} else {
		compute_file_area_size(wbpb);
	}
}

/*
 *  compare_existing_with_computed
 *
 *	We use this function when we the user specifies the -m option.
 *	We compute and look up things like we would if they had asked
 *	us to make the fs, and compare that to what's already layed down
 *	in the existing fs.  If there's a difference we can tell them what
 *	options to specify in order to reproduce their existing layout.
 *	Note that they still may not get an exact duplicate, because we
 *	don't, for example, preserve their existing boot code.  We think
 *	we've got all the fields that matter covered, though.
 *
 *	XXX - We're basically ignoring sbpb at this point.  I'm unsure
 *	if we'll ever care about those fields, in terms of the -m option.
 */
static
void
compare_existing_with_computed(int fd, char *suffix,
    bpb_t *wbpb, int *prtsize, int *prtspc, int *prtbpf, int *prtnsect,
    int *prtntrk, int *prtfdisk, int *prthidden, int *prtrsrvd, int *dashos)
{
	struct dk_geom	dginfo;
	struct fd_char	fdchar;
	bpb_t		compare;
	int		fd_ioctl_worked = 0;
	int		fatents;

	/*
	 *  For all non-floppy cases we expect to find a 16-bit FAT
	 */
	int expectfatsize = 16;

	compare = *wbpb;

	if (!suffix) {
		if (ioctl(fd, FDIOGCHAR, &fdchar) != -1) {
			expectfatsize = 12;
			fd_ioctl_worked++;
		}
	}

	if (fd_ioctl_worked) {
#ifdef sparc
		fdchar.fdc_medium = 3;
#endif
		GetSize = GetSPT = GetSPC = GetTPC = GetBPF = 1;
		lookup_floppy(&fdchar, &compare);
		if (compare.bpb.heads != wbpb->bpb.heads) {
			(*prtntrk)++;
			(*dashos)++;
		}
		if (compare.bpb.sectors_per_track !=
		    wbpb->bpb.sectors_per_track) {
			(*prtnsect)++;
			(*dashos)++;
		}
	} else {
		int dk_ioctl_worked = 1;

		if (!suffix) {
			(*prtfdisk)++;
			(*prtsize)++;
			*dashos += 2;
		}
		if (ioctl(fd, DKIOCG_VIRTGEOM, &dginfo) == -1 &&
		    ioctl(fd, DKIOCG_PHYGEOM, &dginfo) == -1 &&
		    ioctl(fd, DKIOCGGEOM, &dginfo) == -1) {
			*prtnsect = *prtntrk = 1;
			*dashos += 2;
			dk_ioctl_worked = 0;
		}
		if (dk_ioctl_worked) {
			if (dginfo.dkg_nhead != wbpb->bpb.heads) {
				(*prtntrk)++;
				(*dashos)++;
			}
			if (dginfo.dkg_nsect !=
			    wbpb->bpb.sectors_per_track) {
				(*prtnsect)++;
				(*dashos)++;
			}
		}
		GetBPF = GetSPC = 1;
		compute_cluster_size(&compare);
	}

	if (!*prtfdisk && TotSize != wbpb->bpb.sectors_in_volume &&
	    TotSize != wbpb->bpb.sectors_in_logical_volume) {
		(*dashos)++;
		(*prtsize)++;
	}

	if (compare.bpb.sectors_per_cluster != wbpb->bpb.sectors_per_cluster) {
		(*dashos)++;
		(*prtspc)++;
	}

	if (compare.bpb.hidden_sectors != wbpb->bpb.hidden_sectors) {
		(*dashos)++;
		(*prthidden)++;
	}

	if (compare.bpb.resv_sectors != wbpb->bpb.resv_sectors) {
		(*dashos)++;
		(*prtrsrvd)++;
	}

	/*
	 * Compute approximate Fatentsize.  It's approximate because the
	 * size of the FAT may not be exactly a multiple of the number of
	 * clusters.  It should be close, though.
	 */
	if (MakeFAT32) {
		Fatentsize = 32;
		(*dashos)++;
		(*prtbpf)++;
	} else {
		fatents = wbpb->bpb.sectors_per_fat * BPSEC * 2 / 3;
		if (fatents >= TotalClusters && wbpb->ebpb.type[4] == '2')
			Fatentsize = 12;
		else
			Fatentsize = 16;
		if (Fatentsize != expectfatsize) {
			(*dashos)++;
			(*prtbpf)++;
		}
	}
}

static
void
print_reproducing_command(int fd, char *actualdisk, char *suffix, bpb_t *wbpb)
{
	int needcomma = 0;
	int prthidden = 0;
	int prtrsrvd = 0;
	int prtfdisk = 0;
	int prtnsect = 0;
	int prtntrk = 0;
	int prtsize = 0;
	int prtbpf = 0;
	int prtspc = 0;
	int dashos = 0;
	int ll, i;

	compare_existing_with_computed(fd, suffix, wbpb,
	    &prtsize, &prtspc, &prtbpf, &prtnsect, &prtntrk,
	    &prtfdisk, &prthidden, &prtrsrvd, &dashos);

	/*
	 *  Print out the command line they can use to reproduce the
	 *  file system.
	 */
	(void) printf("mkfs -F pcfs");

	ll = min(11, (int)strlen((char *)wbpb->ebpb.volume_label));
	/*
	 * First, eliminate trailing spaces. Now compare the name against
	 * our default label.  If there's a match we don't need to print
	 * any label info.
	 */
	i = ll;
	while (wbpb->ebpb.volume_label[--i] == ' ')
		;
	ll = i;

	if (ll == strlen(DEFAULT_LABEL) - 1) {
		char cmpbuf[11];

		(void) strcpy(cmpbuf, DEFAULT_LABEL);
		for (i = ll; i >= 0; i--) {
			if (cmpbuf[i] !=
			    toupper((int)(wbpb->ebpb.volume_label[i]))) {
				break;
			}
		}
		if (i < 0)
			ll = i;
	}

	if (ll >= 0) {
		(void) printf(" -o ");
		(void) printf("b=\"");
		for (i = 0; i <= ll; i++) {
			(void) printf("%c", wbpb->ebpb.volume_label[i]);
		}
		(void) printf("\"");
		needcomma++;
	} else if (dashos) {
		(void) printf(" -o ");
	}

#define	NEXT_DASH_O	dashos--; needcomma++; continue

	while (dashos) {
		if (needcomma) {
			(void) printf(",");
			needcomma = 0;
		}
		if (prtfdisk) {
			(void) printf("nofdisk");
			prtfdisk--;
			NEXT_DASH_O;
		}
		if (prtsize) {
			(void) printf("size=%u", wbpb->bpb.sectors_in_volume ?
			    wbpb->bpb.sectors_in_volume :
			    wbpb->bpb.sectors_in_logical_volume);
			prtsize--;
			NEXT_DASH_O;
		}
		if (prtnsect) {
			(void) printf("nsect=%d", wbpb->bpb.sectors_per_track);
			prtnsect--;
			NEXT_DASH_O;
		}
		if (prtspc) {
			(void) printf("spc=%d", wbpb->bpb.sectors_per_cluster);
			prtspc--;
			NEXT_DASH_O;
		}
		if (prtntrk) {
			(void) printf("ntrack=%d", wbpb->bpb.heads);
			prtntrk--;
			NEXT_DASH_O;
		}
		if (prtbpf) {
			(void) printf("fat=%d", Fatentsize);
			prtbpf--;
			NEXT_DASH_O;
		}
		if (prthidden) {
			(void) printf("hidden=%u", wbpb->bpb.hidden_sectors);
			prthidden--;
			NEXT_DASH_O;
		}
		if (prtrsrvd) {
			(void) printf("reserve=%d", wbpb->bpb.resv_sectors);
			prtrsrvd--;
			NEXT_DASH_O;
		}
	}

	(void) printf(" %s%c%c\n", actualdisk,
	    suffix ? ':' : '\0', suffix ? *suffix : '\0');
}

/*
 *  open_and_examine
 *
 *	Open the requested 'dev_name'.  Seek to point where
 *	we'd expect to find boot sectors, etc., based on any ':partition'
 *	attachments to the dev_name.
 *
 *	Examine the fields of any existing boot sector and display best
 *	approximation of how this fs could be reproduced with this command.
 */
static
int
open_and_examine(char *dn, bpb_t *wbpb)
{
	struct stat di;
	off64_t ignored;
	char *actualdisk = NULL;
	char *suffix = NULL;
	int fd;
	struct dk_minfo dkminfo;

	if (Verbose)
		(void) printf(gettext("Opening destination device/file.\n"));

	actualdisk = stat_actual_disk(dn, &di, &suffix);

	/*
	 *  Destination exists, now find more about it.
	 */
	if (!(S_ISCHR(di.st_mode))) {
		(void) fprintf(stderr,
		    gettext("\n%s: device name must be a "
		    "character special device.\n"), actualdisk);
		exit(2);
	} else if ((fd = open(actualdisk, O_RDWR)) < 0) {
		perror(actualdisk);
		exit(2);
	}

	/*
	 * Check the media sector size
	 */
	if (ioctl(fd, DKIOCGMEDIAINFO, &dkminfo) != -1) {
		if (dkminfo.dki_lbsize != 0 &&
		    ISP2(dkminfo.dki_lbsize / DEV_BSIZE) &&
		    dkminfo.dki_lbsize != DEV_BSIZE) {
			(void) fprintf(stderr,
			    gettext("The device sector size %u is not "
			    "supported by pcfs!\n"), dkminfo.dki_lbsize);
			(void) close(fd);
			exit(1);
		}
	}

	/*
	 * Find appropriate partition if we were requested to do so.
	 */
	if (suffix && !(seek_partn(fd, suffix, wbpb, &ignored))) {
		(void) close(fd);
		exit(2);
	}

	read_existing_bpb(fd, wbpb);
	print_reproducing_command(fd, actualdisk, suffix, wbpb);

	return (fd);
}

/*
 *  open_and_seek
 *
 *	Open the requested 'dev_name'.  Seek to point where
 *	we'll write boot sectors, etc., based on any ':partition'
 *	attachments to the dev_name.
 *
 *	By the time we are finished here, the entire BPB will be
 *	filled in, excepting the volume label.
 */
static
int
open_and_seek(char *dn, bpb_t *wbpb, off64_t *seekto)
{
	struct fd_char fdchar;
	struct dk_geom dg;
	struct stat di;
	struct dk_minfo	dkminfo;
	char *actualdisk = NULL;
	char *suffix = NULL;
	int fd;

	if (Verbose)
		(void) printf(gettext("Opening destination device/file.\n"));

	/*
	 * We hold these truths to be self evident, all BPBs we create
	 * will have these values in these fields.
	 */
	wbpb->bpb.num_fats = 2;
	wbpb->bpb.bytes_sector = BPSEC;

	/*
	 * Assign or use supplied numbers for hidden and
	 * reserved sectors in the file system.
	 */
	if (GetResrvd)
		if (MakeFAT32)
			wbpb->bpb.resv_sectors = 32;
		else
			wbpb->bpb.resv_sectors = 1;
	else
		wbpb->bpb.resv_sectors = Resrvd;

	wbpb->ebpb.ext_signature = 0x29; /* Magic number for modern format */
	wbpb->ebpb.volume_id = 0;

	if (MakeFAT32)
		fill_fat32_bpb(wbpb);

	/*
	 * If all output goes to a simple file, call a routine to setup
	 * that scenario. Otherwise, try to find the device.
	 */
	if (Outputtofile)
		return (fd = prepare_image_file(dn, wbpb));

	actualdisk = stat_actual_disk(dn, &di, &suffix);

	/*
	 * Sanity check.  If we've been provided a partition-specifying
	 * suffix, we shouldn't also have been told to ignore the
	 * fdisk table.
	 */
	if (DontUseFdisk && suffix) {
		(void) fprintf(stderr,
		    gettext("Using 'nofdisk' option precludes "
		    "appending logical drive\nspecifier "
		    "to the device name.\n"));
		exit(2);
	}

	/*
	 *  Destination exists, now find more about it.
	 */
	if (!(S_ISCHR(di.st_mode))) {
		(void) fprintf(stderr,
		    gettext("\n%s: device name must indicate a "
		    "character special device.\n"), actualdisk);
		exit(2);
	} else if ((fd = open(actualdisk, O_RDWR)) < 0) {
		perror(actualdisk);
		exit(2);
	}

	/*
	 * Check the media sector size
	 */
	if (ioctl(fd, DKIOCGMEDIAINFO, &dkminfo) != -1) {
		if (dkminfo.dki_lbsize != 0 &&
		    ISP2(dkminfo.dki_lbsize / DEV_BSIZE) &&
		    dkminfo.dki_lbsize != DEV_BSIZE) {
			(void) fprintf(stderr,
			    gettext("The device sector size %u is not "
			    "supported by pcfs!\n"), dkminfo.dki_lbsize);
			(void) close(fd);
			exit(1);
		}
	}

	/*
	 * Find appropriate partition if we were requested to do so.
	 */
	if (suffix && !(seek_partn(fd, suffix, wbpb, seekto))) {
		(void) close(fd);
		exit(2);
	}

	if (!suffix) {
		/*
		 * We have one of two possibilities.  Chances are we have
		 * a floppy drive.  But the user may be trying to format
		 * some weird drive that we don't know about and is supplying
		 * all the important values.  In that case, they should have set
		 * the 'nofdisk' flag.
		 *
		 * If 'nofdisk' isn't set, do a floppy-specific ioctl to
		 * get the remainder of our info. If the ioctl fails, we have
		 * a good idea that they aren't really on a floppy.  In that
		 * case, they should have given us a partition specifier.
		 */
		if (DontUseFdisk) {
			if (!(seek_nofdisk(fd, wbpb, seekto))) {
				(void) close(fd);
				exit(2);
			}
			find_fixed_details(fd, wbpb);
		} else if (ioctl(fd, FDIOGCHAR, &fdchar) == -1) {
			/*
			 * It is possible that we are trying to use floppy
			 * specific FDIOGCHAR ioctl on USB floppy. Since sd
			 * driver, by which USB floppy is handled, doesn't
			 * support it, we can try to use disk DKIOCGGEOM ioctl
			 * to retrieve data we need. sd driver itself
			 * determines floppy disk by number of blocks
			 * (<=0x1000), then it sets geometry to 80 cylinders,
			 * 2 heads.
			 *
			 * Note that DKIOCGGEOM cannot supply us with type
			 * of media (e.g. 3.5" or 5.25"). We will set it to
			 * 3 (3.5") which is most probable value.
			 */
			if (errno == ENOTTY) {
				if (ioctl(fd, DKIOCGGEOM, &dg) != -1 &&
				    dg.dkg_ncyl == 80 && dg.dkg_nhead == 2) {
					fdchar.fdc_ncyl = dg.dkg_ncyl;
					fdchar.fdc_medium = 3;
					fdchar.fdc_secptrack = dg.dkg_nsect;
					fdchar.fdc_nhead = dg.dkg_nhead;
					lookup_floppy(&fdchar, wbpb);
				} else {
					partn_lecture(actualdisk);
					(void) close(fd);
					exit(2);
				}
			}
		} else {
#ifdef sparc
			fdchar.fdc_medium = 3;
#endif
			lookup_floppy(&fdchar, wbpb);
		}
	} else {
		find_fixed_details(fd, wbpb);
	}

	return (fd);
}

/*
 * The following is a copy of MS-DOS 4.0 boot block.
 * It consists of the BIOS parameter block, and a disk
 * bootstrap program.
 *
 * The BIOS parameter block contains the right values
 * for the 3.5" high-density 1.44MB floppy format.
 *
 * This will be our default boot sector, if the user
 * didn't point us at a different one.
 *
 */
static
uchar_t DefBootSec[512] = {
	0xeb, 0x3c, 0x90, 	/* 8086 short jump + displacement + NOP */
	'M', 'S', 'D', 'O', 'S', '4', '.', '0',	/* OEM name & version */
	0x00, 0x02, 0x01, 0x01, 0x00,
	0x02, 0xe0, 0x00, 0x40, 0x0b,
	0xf0, 0x09, 0x00, 0x12, 0x00,
	0x02, 0x00,
	0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00,
	0x00, 0x00,
	0x29, 0x00, 0x00, 0x00, 0x00,
	'N', 'O', 'N', 'A', 'M', 'E', ' ', ' ', ' ', ' ', ' ',
	'F', 'A', 'T', '1', '2', ' ', ' ', ' ',
	0xfa, 0x33,
	0xc0, 0x8e, 0xd0, 0xbc, 0x00, 0x7c, 0x16, 0x07,
	0xbb, 0x78, 0x00, 0x36, 0xc5, 0x37, 0x1e, 0x56,
	0x16, 0x53, 0xbf, 0x3e, 0x7c, 0xb9, 0x0b, 0x00,
	0xfc, 0xf3, 0xa4, 0x06, 0x1f, 0xc6, 0x45, 0xfe,
	0x0f, 0x8b, 0x0e, 0x18, 0x7c, 0x88, 0x4d, 0xf9,
	0x89, 0x47, 0x02, 0xc7, 0x07, 0x3e, 0x7c, 0xfb,
	0xcd, 0x13, 0x72, 0x7c, 0x33, 0xc0, 0x39, 0x06,
	0x13, 0x7c, 0x74, 0x08, 0x8b, 0x0e, 0x13, 0x7c,
	0x89, 0x0e, 0x20, 0x7c, 0xa0, 0x10, 0x7c, 0xf7,
	0x26, 0x16, 0x7c, 0x03, 0x06, 0x1c, 0x7c, 0x13,
	0x16, 0x1e, 0x7c, 0x03, 0x06, 0x0e, 0x7c, 0x83,
	0xd2, 0x00, 0xa3, 0x50, 0x7c, 0x89, 0x16, 0x52,
	0x7c, 0xa3, 0x49, 0x7c, 0x89, 0x16, 0x4b, 0x7c,
	0xb8, 0x20, 0x00, 0xf7, 0x26, 0x11, 0x7c, 0x8b,
	0x1e, 0x0b, 0x7c, 0x03, 0xc3, 0x48, 0xf7, 0xf3,
	0x01, 0x06, 0x49, 0x7c, 0x83, 0x16, 0x4b, 0x7c,
	0x00, 0xbb, 0x00, 0x05, 0x8b, 0x16, 0x52, 0x7c,
	0xa1, 0x50, 0x7c, 0xe8, 0x87, 0x00, 0x72, 0x20,
	0xb0, 0x01, 0xe8, 0xa1, 0x00, 0x72, 0x19, 0x8b,
	0xfb, 0xb9, 0x0b, 0x00, 0xbe, 0xdb, 0x7d, 0xf3,
	0xa6, 0x75, 0x0d, 0x8d, 0x7f, 0x20, 0xbe, 0xe6,
	0x7d, 0xb9, 0x0b, 0x00, 0xf3, 0xa6, 0x74, 0x18,
	0xbe, 0x93, 0x7d, 0xe8, 0x51, 0x00, 0x32, 0xe4,
	0xcd, 0x16, 0x5e, 0x1f, 0x8f, 0x04, 0x8f, 0x44,
	0x02, 0xcd, 0x19, 0x58, 0x58, 0x58, 0xeb, 0xe8,
	0xbb, 0x00, 0x07, 0xb9, 0x03, 0x00, 0xa1, 0x49,
	0x7c, 0x8b, 0x16, 0x4b, 0x7c, 0x50, 0x52, 0x51,
	0xe8, 0x3a, 0x00, 0x72, 0xe6, 0xb0, 0x01, 0xe8,
	0x54, 0x00, 0x59, 0x5a, 0x58, 0x72, 0xc9, 0x05,
	0x01, 0x00, 0x83, 0xd2, 0x00, 0x03, 0x1e, 0x0b,
	0x7c, 0xe2, 0xe2, 0x8a, 0x2e, 0x15, 0x7c, 0x8a,
	0x16, 0x24, 0x7c, 0x8b, 0x1e, 0x49, 0x7c, 0xa1,
	0x4b, 0x7c, 0xea, 0x00, 0x00, 0x70, 0x00, 0xac,
	0x0a, 0xc0, 0x74, 0x29, 0xb4, 0x0e, 0xbb, 0x07,
	0x00, 0xcd, 0x10, 0xeb, 0xf2, 0x3b, 0x16, 0x18,
	0x7c, 0x73, 0x19, 0xf7, 0x36, 0x18, 0x7c, 0xfe,
	0xc2, 0x88, 0x16, 0x4f, 0x7c, 0x33, 0xd2, 0xf7,
	0x36, 0x1a, 0x7c, 0x88, 0x16, 0x25, 0x7c, 0xa3,
	0x4d, 0x7c, 0xf8, 0xc3, 0xf9, 0xc3, 0xb4, 0x02,
	0x8b, 0x16, 0x4d, 0x7c, 0xb1, 0x06, 0xd2, 0xe6,
	0x0a, 0x36, 0x4f, 0x7c, 0x8b, 0xca, 0x86, 0xe9,
	0x8a, 0x16, 0x24, 0x7c, 0x8a, 0x36, 0x25, 0x7c,
	0xcd, 0x13, 0xc3, 0x0d, 0x0a, 0x4e, 0x6f, 0x6e,
	0x2d, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x20,
	0x64, 0x69, 0x73, 0x6b, 0x20, 0x6f, 0x72, 0x20,
	0x64, 0x69, 0x73, 0x6b, 0x20, 0x65, 0x72, 0x72,
	0x6f, 0x72, 0x0d, 0x0a, 0x52, 0x65, 0x70, 0x6c,
	0x61, 0x63, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20,
	0x70, 0x72, 0x65, 0x73, 0x73, 0x20, 0x61, 0x6e,
	0x79, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x77, 0x68,
	0x65, 0x6e, 0x20, 0x72, 0x65, 0x61, 0x64, 0x79,
	0x0d, 0x0a, 0x00, 0x49, 0x4f, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x53, 0x59, 0x53, 0x4d, 0x53,
	0x44, 0x4f, 0x53, 0x20, 0x20, 0x20, 0x53, 0x59,
	0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa
};

/*
 *  verify_bootblkfile
 *
 *	We were provided with the name of a file containing the bootblk
 *	to install.  Verify it has a valid boot sector as best we can. Any
 *	errors and we return a bad file descriptor.  Otherwise we fill up the
 *	provided buffer with the boot sector, return the file
 *	descriptor for later use and leave the file pointer just
 *	past the boot sector part of the boot block file.
 */
static
int
verify_bootblkfile(char *fn, boot_sector_t *bs, ulong_t *blkfilesize)
{
	struct stat fi;
	int bsfd = -1;

	if (stat(fn, &fi)) {
		perror(fn);
	} else if (fi.st_size < BPSEC) {
		(void) fprintf(stderr,
		    gettext("%s: Too short to be a boot sector.\n"), fn);
	} else if ((bsfd = open(fn, O_RDONLY)) < 0) {
		perror(fn);
	} else if (read(bsfd, bs->buf, BPSEC) < BPSEC) {
		(void) close(bsfd);
		bsfd = -1;
		perror(gettext("Boot block read"));
	} else {
		if ((bs->bs.bs_signature[0] != (BOOTSECSIG & 0xFF) &&
		    bs->bs.bs_signature[1] != ((BOOTSECSIG >> 8) & 0xFF)) ||
#ifdef i386
		    (bs->bs.bs_front.bs_jump_code[0] != OPCODE1 &&
		    bs->bs.bs_front.bs_jump_code[0] != OPCODE2)
#else
		    (bs->bs.bs_jump_code[0] != OPCODE1 &&
		    bs->bs.bs_jump_code[0] != OPCODE2)
#endif
		    /* CSTYLED */
		    ) {
			(void) close(bsfd);
			bsfd = -1;
			(void) fprintf(stderr,
			    gettext("Boot block (%s) bogus.\n"), fn);
		}
		*blkfilesize = fi.st_size;
	}
	return (bsfd);
}

/*
 *  verify_firstfile
 *
 *	We were provided with the name of a file to be the first file
 *	installed on the disk.  We just need to verify it exists and
 *	find out how big it is.  If it doesn't exist, we print a warning
 *	message about how the file wasn't found.  We don't exit fatally,
 *	though, rather we return a size of 0 and the FAT will be built
 *	without installing any first file.  They can then presumably
 *	install the correct first file by hand.
 */
static
int
verify_firstfile(char *fn, ulong_t *filesize)
{
	struct stat fi;
	int fd = -1;

	*filesize = 0;
	if (stat(fn, &fi) || (fd = open(fn, O_RDONLY)) < 0) {
		perror(fn);
		(void) fprintf(stderr,
		    gettext("Could not access requested file.  It will not\n"
		    "be installed in the new file system.\n"));
	} else {
		*filesize = fi.st_size;
	}

	return (fd);
}

/*
 *  label_volume
 *
 *	Fill in BPB with volume label.
 */
static
void
label_volume(char *lbl, bpb_t *wbpb)
{
	int ll, i;

	/* Put a volume label into our BPB. */
	if (!lbl)
		lbl = DEFAULT_LABEL;

	ll = min(11, (int)strlen(lbl));
	for (i = 0; i < ll; i++) {
		wbpb->ebpb.volume_label[i] = toupper(lbl[i]);
	}
	for (; i < 11; i++) {
		wbpb->ebpb.volume_label[i] = ' ';
	}
}

static
int
copy_bootblk(char *fn, boot_sector_t *bootsect, ulong_t *bootblksize)
{
	int bsfd = -1;

	if (Verbose && fn)
		(void) printf(gettext("Request to install boot "
		    "block file %s.\n"), fn);
	else if (Verbose)
		(void) printf(gettext("Request to install DOS boot block.\n"));

	/*
	 *  If they want to install their own boot block, sanity check
	 *  that block.
	 */
	if (fn) {
		bsfd = verify_bootblkfile(fn, bootsect, bootblksize);
		if (bsfd < 0) {
			exit(3);
		}
		*bootblksize = roundup(*bootblksize, BPSEC);
	} else {
		(void) memcpy(bootsect, DefBootSec, BPSEC);
		*bootblksize = BPSEC;
	}

	return (bsfd);
}

/*
 *  mark_cluster
 *
 *	This routine fills a FAT entry with the value supplied to it as an
 *	argument.  The fatp argument is assumed to be a pointer to the FAT's
 *	0th entry.  The clustnum is the cluster entry that should be updated.
 *	The value is the new value for the entry.
 */
static
void
mark_cluster(uchar_t *fatp, pc_cluster32_t clustnum, uint32_t value)
{
	uchar_t *ep;
	ulong_t idx;

	idx = (Fatentsize == 32) ? clustnum * 4 :
	    (Fatentsize == 16) ? clustnum * 2 : clustnum + clustnum/2;
	ep = fatp + idx;

	if (Fatentsize == 32) {
		store_32_bits(&ep, value);
	} else if (Fatentsize == 16) {
		store_16_bits(&ep, value);
	} else {
		if (clustnum & 1) {
			*ep = (*ep & 0x0f) | ((value << 4) & 0xf0);
			ep++;
			*ep = (value >> 4) & 0xff;
		} else {
			*ep++ = value & 0xff;
			*ep = (*ep & 0xf0) | ((value >> 8) & 0x0f);
		}
	}
}

static
uchar_t *
build_fat(bpb_t *wbpb, struct fat_od_fsi *fsinfop, ulong_t bootblksize,
    ulong_t *fatsize, char *ffn, int *fffd, ulong_t *ffsize,
    pc_cluster32_t *ffstartclust)
{
	pc_cluster32_t nextfree, ci;
	uchar_t *fatp;
	ushort_t numclust, numsect;
	int  remclust;

	/* Alloc space for a FAT and then null it out. */
	if (Verbose) {
		(void) printf(gettext("BUILD FAT.\n%d sectors per fat.\n"),
		    wbpb->bpb.sectors_per_fat ? wbpb->bpb.sectors_per_fat :
		    wbpb->bpb32.big_sectors_per_fat);
	}

	if (MakeFAT32) {
		*fatsize = BPSEC * wbpb->bpb32.big_sectors_per_fat;
	} else {
		*fatsize = BPSEC * wbpb->bpb.sectors_per_fat;
	}

	if (!(fatp = (uchar_t *)malloc(*fatsize))) {
		perror(gettext("FAT table alloc"));
		exit(4);
	} else {
		(void) memset(fatp, 0, *fatsize);
	}

	/* Build in-memory FAT */
	*fatp = wbpb->bpb.media;
	*(fatp + 1) = 0xFF;
	*(fatp + 2) = 0xFF;

	if (Fatentsize == 16) {
		*(fatp + 3) = 0xFF;
	} else if (Fatentsize == 32) {
		*(fatp + 3) = 0x0F;
		*(fatp + 4) = 0xFF;
		*(fatp + 5) = 0xFF;
		*(fatp + 6) = 0xFF;
		*(fatp + 7) = 0x0F;
	}

	/*
	 * Keep track of clusters used.
	 */
	remclust = TotalClusters;
	nextfree = 2;

	/*
	 * Get info on first file to install, if any.
	 */
	if (ffn)
		*fffd = verify_firstfile(ffn, ffsize);

	/*
	 * Compute number of clusters to preserve for bootblk overage.
	 * Remember that we already wrote the first sector of the boot block.
	 * These clusters are marked BAD to prevent them from being deleted
	 * or used.  The first available cluster is 2, so we always offset
	 * the clusters.
	 */
	numsect = idivceil((bootblksize - BPSEC), BPSEC);
	numclust = idivceil(numsect, wbpb->bpb.sectors_per_cluster);

	if (Verbose && numclust)
		(void) printf(gettext("Hiding %d excess bootblk cluster(s).\n"),
		    numclust);
	for (ci = 0; ci < numclust; ci++)
		mark_cluster(fatp, nextfree++,
		    MakeFAT32 ? PCF_BADCLUSTER32 : PCF_BADCLUSTER);
	remclust -= numclust;

	/*
	 * Reserve a cluster for the root directory on a FAT32.
	 */
	if (MakeFAT32) {
		mark_cluster(fatp, nextfree, PCF_LASTCLUSTER32);
		wbpb->bpb32.root_dir_clust = nextfree++;
		remclust--;
	}

	/*
	 * Compute and preserve number of clusters for first file.
	 */
	if (*fffd >= 0) {
		*ffstartclust = nextfree;
		numsect = idivceil(*ffsize, BPSEC);
		numclust = idivceil(numsect, wbpb->bpb.sectors_per_cluster);

		if (numclust > remclust) {
			(void) fprintf(stderr,
			    gettext("Requested first file too large to be\n"
			    "installed in the new file system.\n"));
			(void) close(*fffd);
			*fffd = -1;
			goto finish;
		}

		if (Verbose)
			(void) printf(gettext("Reserving %d first file "
			    "cluster(s).\n"), numclust);
		for (ci = 0; (int)ci < (int)(numclust-1); ci++, nextfree++)
			mark_cluster(fatp, nextfree, nextfree + 1);
		mark_cluster(fatp, nextfree++,
		    MakeFAT32 ? PCF_LASTCLUSTER32 : PCF_LASTCLUSTER);
		remclust -= numclust;
	}

finish:
	if (Verbose) {
		(void) printf(gettext("First sector of FAT"));
		header_for_dump();
		dump_bytes(fatp, BPSEC);
	}

	(void) memset(fsinfop, 0, sizeof (*fsinfop));
	fsinfop->fsi_leadsig = LE_32(FSI_LEADSIG);
	fsinfop->fsi_strucsig = LE_32(FSI_STRUCSIG);
	fsinfop->fsi_trailsig = LE_32(FSI_TRAILSIG);
	fsinfop->fsi_incore.fs_free_clusters = LE_32(remclust);
	fsinfop->fsi_incore.fs_next_free = LE_32(nextfree);
	return (fatp);
}

static
void
dirent_time_fill(struct pcdir *dep)
{
	struct  timeval tv;
	struct	tm	*tp;
	ushort_t	dostime;
	ushort_t	dosday;

	(void) gettimeofday(&tv, (struct timezone *)0);
	tp = localtime(&tv.tv_sec);
	/* get the time & day into DOS format */
	dostime = tp->tm_sec / 2;
	dostime |= tp->tm_min << 5;
	dostime |= tp->tm_hour << 11;
	dosday = tp->tm_mday;
	dosday |= (tp->tm_mon + 1) << 5;
	dosday |= (tp->tm_year - 80) << 9;
	dep->pcd_mtime.pct_time = htols(dostime);
	dep->pcd_mtime.pct_date = htols(dosday);
}

static
void
dirent_label_fill(struct pcdir *dep, char *fn)
{
	int nl, i;

	/*
	 * We spread the volume label across both the NAME and EXT fields
	 */
	nl = min(PCFNAMESIZE, strlen(fn));
	for (i = 0; i < nl; i++) {
		dep->pcd_filename[i] = toupper(fn[i]);
	}
	if (i < PCFNAMESIZE) {
		for (; i < PCFNAMESIZE; i++)
			dep->pcd_filename[i] = ' ';
		for (i = 0; i < PCFEXTSIZE; i++)
			dep->pcd_ext[i] = ' ';
		return;
	}
	nl = min(PCFEXTSIZE, strlen(fn) - PCFNAMESIZE);
	for (i = 0; i < nl; i++)
		dep->pcd_ext[i] = toupper(fn[i + PCFNAMESIZE]);
	if (i < PCFEXTSIZE) {
		for (; i < PCFEXTSIZE; i++)
			dep->pcd_ext[i] = ' ';
	}
}

static
void
dirent_fname_fill(struct pcdir *dep, char *fn)
{
	char *fname, *fext;
	int nl, i;

	if (fname = strrchr(fn, '/')) {
		fname++;
	} else {
		fname = fn;
	}

	if (fext = strrchr(fname, '.')) {
		fext++;
	} else {
		fext = "";
	}

	fname = strtok(fname, ".");

	nl = min(PCFNAMESIZE, (int)strlen(fname));
	for (i = 0; i < nl; i++) {
		dep->pcd_filename[i] = toupper(fname[i]);
	}
	for (; i < PCFNAMESIZE; i++) {
		dep->pcd_filename[i] = ' ';
	}

	nl = min(PCFEXTSIZE, (int)strlen(fext));
	for (i = 0; i < nl; i++) {
		dep->pcd_ext[i] = toupper(fext[i]);
	}
	for (; i < PCFEXTSIZE; i++) {
		dep->pcd_ext[i] = ' ';
	}
}

static
uchar_t *
build_rootdir(bpb_t *wbpb, char *ffn, int fffd,
    ulong_t ffsize, pc_cluster32_t ffstart, ulong_t *rdirsize)
{
	struct pcdir *rootdirp;
	struct pcdir *entry;

	/*
	 * Build a root directory.  It will have at least one entry,
	 * the volume label and a second if the first file was defined.
	 */
	if (MakeFAT32) {
		/*
		 * We devote an entire cluster to the root
		 * directory on FAT32.
		 */
		*rdirsize = wbpb->bpb.sectors_per_cluster * BPSEC;
	} else {
		*rdirsize = wbpb->bpb.num_root_entries * sizeof (struct pcdir);
	}
	if ((rootdirp = (struct pcdir *)malloc(*rdirsize)) == NULL) {
		perror(gettext("Root directory allocation"));
		exit(4);
	} else {
		entry = rootdirp;
		(void) memset((char *)rootdirp, 0, *rdirsize);
	}

	/* Create directory entry for first file, if there is one */
	if (fffd >= 0) {
		dirent_fname_fill(entry, ffn);
		entry->pcd_attr = Firstfileattr;
		dirent_time_fill(entry);
		entry->pcd_scluster_lo = htols(ffstart);
		if (MakeFAT32) {
			ffstart = ffstart >> 16;
			entry->un.pcd_scluster_hi = htols(ffstart);
		}
		entry->pcd_size = htoli(ffsize);
		entry++;
	}

	/* Create directory entry for volume label, if there is one */
	if (Label != NULL) {
		dirent_label_fill(entry, Label);
		entry->pcd_attr = PCA_ARCH | PCA_LABEL;
		dirent_time_fill(entry);
		entry->pcd_scluster_lo = 0;
		if (MakeFAT32) {
			entry->un.pcd_scluster_hi = 0;
		}
		entry->pcd_size = 0;
		entry++;
	}

	if (Verbose) {
		(void) printf(gettext("First two directory entries"));
		header_for_dump();
		dump_bytes((uchar_t *)rootdirp, 2 * sizeof (struct pcdir));
	}

	return ((uchar_t *)rootdirp);
}

/*
 * write_rest
 *
 *	Write all the bytes from the current file pointer to end of file
 *	in the source file out to the destination file.  The writes should
 *	be padded to whole clusters with 0's if necessary.
 */
static
void
write_rest(bpb_t *wbpb, char *efn, int dfd, int sfd, int remaining)
{
	char buf[BPSEC];
	ushort_t numsect, numclust;
	ushort_t wnumsect, s;
	int doneread = 0;
	int rstat;

	/*
	 * Compute number of clusters required to contain remaining bytes.
	 */
	numsect = idivceil(remaining, BPSEC);
	numclust = idivceil(numsect, wbpb->bpb.sectors_per_cluster);

	wnumsect = numclust * wbpb->bpb.sectors_per_cluster;
	for (s = 0; s < wnumsect; s++) {
		if (!doneread) {
			if ((rstat = read(sfd, buf, BPSEC)) < 0) {
				perror(efn);
				doneread = 1;
				rstat = 0;
			} else if (rstat == 0) {
				doneread = 1;
			}
			(void) memset(&(buf[rstat]), 0, BPSEC - rstat);
		}
		if (write(dfd, buf, BPSEC) != BPSEC) {
			(void) fprintf(stderr, gettext("Copying "));
			perror(efn);
		}
	}
}

static
void
write_fat32_bootstuff(int fd, boot_sector_t *bsp,
	struct fat_od_fsi *fsinfop, off64_t seekto)
{
	if (Verbose) {
		(void) printf(gettext("Dump of the fs info sector"));
		header_for_dump();
		dump_bytes((uchar_t *)fsinfop, sizeof (*fsinfop));
	}

	if (!Notreally) {
		/*
		 * FAT32's have an FS info sector, then a backup of the boot
		 * sector, and a modified backup of the FS Info sector.
		 */
		if (write(fd, fsinfop, sizeof (*fsinfop)) != BPSEC) {
			perror(gettext("FS info sector write"));
			exit(4);
		}
		if (lseek64(fd,	seekto + BKUP_BOOTSECT_OFFSET, SEEK_SET) < 0) {
			(void) close(fd);
			perror(gettext("Boot sector backup seek"));
			exit(4);
		}
		if (write(fd, bsp->buf, sizeof (bsp->buf)) != BPSEC) {
			perror(gettext("Boot sector backup write"));
			exit(4);
		}
	}

	/*
	 * Second copy of fs info sector is modified to have "don't know"
	 * as the number of free clusters
	 */
	fsinfop->fsi_incore.fs_next_free = LE_32(FSINFO_UNKNOWN);

	if (Verbose) {
		(void) printf(gettext("Dump of the backup fs info sector"));
		header_for_dump();
		dump_bytes((uchar_t *)fsinfop, sizeof (*fsinfop));
	}

	if (!Notreally) {
		if (write(fd, fsinfop, sizeof (*fsinfop)) != BPSEC) {
			perror(gettext("FS info sector backup write"));
			exit(4);
		}
	}
}

static
void
write_bootsects(int fd, boot_sector_t *bsp, bpb_t *wbpb,
	struct fat_od_fsi *fsinfop, off64_t seekto)
{
	if (MakeFAT32) {
		/* Copy our BPB into bootsec structure */
#ifdef i386
		(void) memcpy(&(bsp->bs32.bs_front.bs_bpb), &(wbpb->bpb),
		    sizeof (wbpb->bpb));
		(void) memcpy(&(bsp->bs32.bs_bpb32), &(wbpb->bpb32),
		    sizeof (wbpb->bpb32));
		(void) memcpy(&(bsp->bs32.bs_ebpb), &(wbpb->ebpb),
		    sizeof (wbpb->ebpb));
#else
		swap_pack_bpb32cpy(&(bsp->bs32), wbpb);
#endif
	} else {
		/* Copy our BPB into bootsec structure */
#ifdef i386
		(void) memcpy(&(bsp->bs.bs_front.bs_bpb), &(wbpb->bpb),
		    sizeof (wbpb->bpb));
		(void) memcpy(&(bsp->bs.bs_ebpb), &(wbpb->ebpb),
		    sizeof (wbpb->ebpb));
#else
		swap_pack_bpbcpy(&(bsp->bs), wbpb);
#endif

		/* Copy SUN BPB extensions into bootsec structure */
		if (SunBPBfields) {
#ifdef i386
			(void) memcpy(&(bsp->bs.bs_sebpb), &(wbpb->sunbpb),
			    sizeof (wbpb->sunbpb));
#else
			swap_pack_sebpbcpy(&(bsp->bs), wbpb);
#endif
		}
	}

	/* Write boot sector */
	if (!Notreally && write(fd, bsp->buf, sizeof (bsp->buf)) != BPSEC) {
		perror(gettext("Boot sector write"));
		exit(4);
	}

	if (Verbose) {
		(void) printf(gettext("Dump of the boot sector"));
		header_for_dump();
		dump_bytes(bsp->buf, sizeof (bsp->buf));
	}

	if (MakeFAT32)
		write_fat32_bootstuff(fd, bsp, fsinfop, seekto);
}

static
void
write_fat(int fd, off64_t seekto, char *fn, char *lbl, char *ffn, bpb_t *wbpb)
{
	struct fat_od_fsi fsinfo;
	pc_cluster32_t ffsc;
	boot_sector_t bootsect;
	uchar_t *fatp, *rdirp;
	ulong_t bootblksize, fatsize, rdirsize, ffsize;
	int bsfd = -1;
	int fffd = -1;

	compute_file_area_size(wbpb);

	bsfd = copy_bootblk(fn, &bootsect, &bootblksize);
	label_volume(lbl, wbpb);

	if (Verbose)
		(void) printf(gettext("Building FAT.\n"));
	fatp = build_fat(wbpb, &fsinfo, bootblksize, &fatsize,
	    ffn, &fffd, &ffsize, &ffsc);

	write_bootsects(fd, &bootsect, wbpb, &fsinfo, seekto);

	if (lseek64(fd,
	    seekto + (BPSEC * wbpb->bpb.resv_sectors), SEEK_SET) < 0) {
		(void) close(fd);
		perror(gettext("Seek to end of reserved sectors"));
		exit(4);
	}

	/* Write FAT */
	if (Verbose)
		(void) printf(gettext("Writing FAT(s). %d bytes times %d.\n"),
		    fatsize, wbpb->bpb.num_fats);
	if (!Notreally) {
		int nf, wb;
		for (nf = 0; nf < (int)wbpb->bpb.num_fats; nf++)
			if ((wb = write(fd, fatp, fatsize)) != fatsize) {
				perror(gettext("FAT write"));
				exit(4);
			} else {
				if (Verbose)
					(void) printf(
					    gettext("Wrote %d bytes\n"), wb);
			}
	}
	free(fatp);

	if (Verbose)
		(void) printf(gettext("Building root directory.\n"));
	rdirp = build_rootdir(wbpb, ffn, fffd, ffsize, ffsc, &rdirsize);

	/*
	 *  In non FAT32, root directory exists outside of the file area
	 */
	if (!MakeFAT32) {
		if (Verbose)
			(void) printf(gettext("Writing root directory. "
			    "%d bytes.\n"), rdirsize);
		if (!Notreally) {
			if (write(fd, rdirp, rdirsize) != rdirsize) {
				perror(gettext("Root directory write"));
				exit(4);
			}
		}
		free(rdirp);
	}

	/*
	 * Now write anything that needs to be in the file space.
	 */
	if (bootblksize > BPSEC) {
		if (Verbose)
			(void) printf(gettext("Writing remainder of "
			    "boot block.\n"));
		if (!Notreally)
			write_rest(wbpb, fn, fd, bsfd, bootblksize - BPSEC);
	}

	if (MakeFAT32) {
		if (Verbose)
			(void) printf(gettext("Writing root directory. "
			    "%d bytes.\n"), rdirsize);
		if (!Notreally) {
			if (write(fd, rdirp, rdirsize) != rdirsize) {
				perror(gettext("Root directory write"));
				exit(4);
			}
		}
		free(rdirp);
	}

	if (fffd >= 0) {
		if (Verbose)
			(void) printf(gettext("Writing first file.\n"));
		if (!Notreally)
			write_rest(wbpb, ffn, fd, fffd, ffsize);
	}
}

static
char *LegalOpts[] = {
#define	NFLAG 0
	"N",
#define	VFLAG 1
	"v",
#define	RFLAG 2
	"r",
#define	HFLAG 3
	"h",
#define	SFLAG 4
	"s",
#define	SUNFLAG 5
	"S",
#define	LABFLAG 6
	"b",
#define	BTRFLAG 7
	"B",
#define	INITFLAG 8
	"i",
#define	SZFLAG 9
	"size",
#define	SECTFLAG 10
	"nsect",
#define	TRKFLAG 11
	"ntrack",
#define	SPCFLAG 12
	"spc",
#define	BPFFLAG 13
	"fat",
#define	FFLAG 14
	"f",
#define	DFLAG 15
	"d",
#define	NOFDISKFLAG 16
	"nofdisk",
#define	RESRVFLAG 17
	"reserve",
#define	HIDDENFLAG 18
	"hidden",
	NULL
};

static
void
bad_arg(char *option)
{
	(void) fprintf(stderr,
	    gettext("Unrecognized option %s.\n"), option);
	usage();
	exit(2);
}

static
void
missing_arg(char *option)
{
	(void) fprintf(stderr,
	    gettext("Option %s requires a value.\n"), option);
	usage();
	exit(3);
}

static
void
parse_suboptions(char *optsstr)
{
	char *value;
	int c;

	while (*optsstr != '\0') {
		switch (c = getsubopt(&optsstr, LegalOpts, &value)) {
		case NFLAG:
			Notreally++;
			break;
		case VFLAG:
			Verbose++;
			break;
		case RFLAG:
			Firstfileattr |= 0x01;
			break;
		case HFLAG:
			Firstfileattr |= 0x02;
			break;
		case SFLAG:
			Firstfileattr |= 0x04;
			break;
		case SUNFLAG:
			SunBPBfields = 1;
			break;
		case LABFLAG:
			if (value == NULL) {
				missing_arg(LegalOpts[c]);
			} else {
				Label = value;
			}
			break;
		case BTRFLAG:
			if (value == NULL) {
				missing_arg(LegalOpts[c]);
			} else {
				BootBlkFn = value;
			}
			break;
		case INITFLAG:
			if (value == NULL) {
				missing_arg(LegalOpts[c]);
			} else {
				FirstFn = value;
			}
			break;
		case SZFLAG:
			if (value == NULL) {
				missing_arg(LegalOpts[c]);
			} else {
				TotSize = atoi(value);
				GetSize = 0;
			}
			break;
		case SECTFLAG:
			if (value == NULL) {
				missing_arg(LegalOpts[c]);
			} else {
				SecPerTrk = atoi(value);
				GetSPT = 0;
			}
			break;
		case TRKFLAG:
			if (value == NULL) {
				missing_arg(LegalOpts[c]);
			} else {
				TrkPerCyl = atoi(value);
				GetTPC = 0;
			}
			break;
		case SPCFLAG:
			if (value == NULL) {
				missing_arg(LegalOpts[c]);
			} else {
				SecPerClust = atoi(value);
				GetSPC = 0;
			}
			break;
		case BPFFLAG:
			if (value == NULL) {
				missing_arg(LegalOpts[c]);
			} else {
				BitsPerFAT = atoi(value);
				GetBPF = 0;
			}
			break;
		case NOFDISKFLAG:
			DontUseFdisk = 1;
			break;
		case RESRVFLAG:
			if (value == NULL) {
				missing_arg(LegalOpts[c]);
			} else {
				Resrvd = atoi(value);
				GetResrvd = 0;
			}
			break;
		case HIDDENFLAG:
			if (value == NULL) {
				missing_arg(LegalOpts[c]);
			} else {
				RelOffset = atoi(value);
				GetOffset = 0;
			}
			break;
		case FFLAG:
			if (value == NULL) {
				missing_arg(LegalOpts[c]);
			} else {
				DiskName = value;
				Outputtofile = 1;
			}
			break;
		case DFLAG:
			if (value == NULL) {
				missing_arg(LegalOpts[c]);
			} else {
				Imagesize = atoi(value);
			}
			break;
		default:
			bad_arg(value);
			break;
		}
	}
}

static
void
sanity_check_options(int argc, int optind)
{
	if (GetFsParams) {
		if (argc - optind != 1)
			usage();
		return;
	}

	if (DontUseFdisk && GetOffset) {
		/* Set default relative offset of zero */
		RelOffset = 0;
	}

	if (BitsPerFAT == 32)
		MakeFAT32 = 1;

	if (Outputtofile && (argc - optind)) {
		usage();
	} else if (Outputtofile && !DiskName) {
		usage();
	} else if (!Outputtofile && (argc - optind != 1)) {
		usage();
	} else if (SunBPBfields && !BootBlkFn) {
		(void) fprintf(stderr,
		    gettext("Use of the 'S' option requires that\n"
		    "the 'B=' option also be used.\n\n"));
		usage();
	} else if (Firstfileattr != 0x20 && !FirstFn) {
		(void) fprintf(stderr,
		    gettext("Use of the 'r', 'h', or 's' options requires\n"
		    "that the 'i=' option also be used.\n\n"));
		usage();
	} else if (!GetOffset && !DontUseFdisk) {
		(void) fprintf(stderr,
		    gettext("Use of the 'hidden' option requires that\n"
		    "the 'nofdisk' option also be used.\n\n"));
		usage();
	} else if (DontUseFdisk && GetSize) {
		(void) fprintf(stderr,
		    gettext("Use of the 'nofdisk' option requires that\n"
		    "the 'size=' option also be used.\n\n"));
		usage();
	} else if (!GetBPF &&
	    BitsPerFAT != 12 && BitsPerFAT != 16 && BitsPerFAT != 32) {
		(void) fprintf(stderr, gettext("Invalid Bits/Fat value."
		    " Must be 12, 16 or 32.\n"));
		exit(2);
	} else if (!GetSPC && !powerofx_le_y(2, 128, SecPerClust)) {
		(void) fprintf(stderr,
		    gettext("Invalid Sectors/Cluster value.  Must be a "
		    "power of 2 between 1 and 128.\n"));
		exit(2);
	} else if (!GetResrvd && (Resrvd < 1 || Resrvd > 0xffff)) {
		(void) fprintf(stderr,
		    gettext("Invalid number of reserved sectors.  "
		    "Must be at least 1 but\nno larger than 65535."));
		exit(2);
	} else if (!GetResrvd && MakeFAT32 &&
	    (Resrvd < 32 || Resrvd > 0xffff)) {
		(void) fprintf(stderr,
		    gettext("Invalid number of reserved sectors.  "
		    "Must be at least 32 but\nno larger than 65535."));
		exit(2);
	} else if (Imagesize != 3 && Imagesize != 5) {
		usage();
	}
}

int
main(int argc, char **argv)
{
	off64_t AbsBootSect = 0;
	bpb_t dskparamblk;
	char *string;
	int  fd;
	int  c;

	(void) setlocale(LC_ALL, "");

#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN "SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	while ((c = getopt(argc, argv, "F:Vmo:")) != EOF) {
		switch (c) {
		case 'F':
			string = optarg;
			if (strcmp(string, "pcfs") != 0)
				usage();
			break;
		case 'V':
			{
				char	*opt_text;
				int	opt_count;

				(void) fprintf(stdout,
				    gettext("mkfs -F pcfs "));
				for (opt_count = 1; opt_count < argc;
				    opt_count++) {
					opt_text = argv[opt_count];
					if (opt_text)
						(void) fprintf(stdout,
						    " %s ", opt_text);
				}
				(void) fprintf(stdout, "\n");
			}
			break;
		case 'm':
			GetFsParams++;
			break;
		case 'o':
			string = optarg;
			parse_suboptions(string);
			break;
		}
	}

	sanity_check_options(argc, optind);

	if (!Outputtofile)
		DiskName = argv[optind];

	(void) memset(&dskparamblk, 0, sizeof (dskparamblk));

	if (GetFsParams) {
		fd = open_and_examine(DiskName, &dskparamblk);
	} else {
		fd = open_and_seek(DiskName, &dskparamblk, &AbsBootSect);
		if (ask_nicely(DiskName))
			write_fat(fd, AbsBootSect, BootBlkFn, Label,
			    FirstFn, &dskparamblk);
	}
	(void) close(fd);
	return (0);
}