/* @(#)write.c	1.108 07/06/24 joerg */
#ifndef lint
static	char sccsid[] =
	"@(#)write.c	1.108 07/06/24 joerg";
#endif
/*
 * Program write.c - dump memory  structures to  file for iso9660 filesystem.
 *
 * Written by Eric Youngdale (1993).
 *
 * Copyright 1993 Yggdrasil Computing, Incorporated
 * Copyright (c) 1999-2007 J. Schilling
 *
 * 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, or (at your option)
 * 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.
 */

/* APPLE_HYB James Pearson j.pearson@ge.ucl.ac.uk 23/2/2000 */

#include <schily/mconfig.h>
#include "mkisofs.h"
#include <schily/time.h>
#include <schily/fcntl.h>
#ifdef SORTING
#include "match.h"
#endif /* SORTING */
#include <schily/errno.h>
#include <schily/schily.h>
#ifdef DVD_VIDEO
#include "dvd_reader.h"
#include "dvd_file.h"
#include "ifo_read.h"
#endif
#ifdef APPLE_HYB
#include <ctype.h>
#endif

#ifdef	VMS
#include "vms.h"
#endif

#define	SIZEOF_UDF_EXT_ATTRIBUTE_COMMON	50

/* Max number of sectors we will write at  one time */
#define	NSECT 16

#define	INSERTMACRESFORK 1

/* Counters for statistics */

LOCAL int	table_size = 0;
LOCAL int	total_dir_size = 0;
LOCAL int	rockridge_size = 0;
LOCAL struct directory **pathlist;
LOCAL int	next_path_index = 1;
LOCAL int	sort_goof;

LOCAL int	is_rr_dir = 0;

struct output_fragment *out_tail;
struct output_fragment *out_list;

EXPORT	struct iso_primary_descriptor	vol_desc;
LOCAL	int				vol_desc_sum;

LOCAL	int	xawrite		__PR((void *buffer, int size, int count,
					FILE *file, int submode, BOOL islast));
EXPORT	void	xfwrite		__PR((void *buffer, int size, int count,
					FILE *file, int submode, BOOL islast));
LOCAL 	int	assign_directory_addresses __PR((struct directory *node));
#ifdef APPLE_HYB
LOCAL 	void	write_one_file	__PR((char *filename, off_t size,
					FILE *outfile, off_t off,
					int isrfile, unsigned rba));
#else
LOCAL 	void	write_one_file	__PR((char *filename, off_t size,
					FILE *outfile));
#endif
LOCAL	void	write_udf_symlink	__PR((char *filename, off_t size,
					FILE *outfile));
LOCAL 	void	write_files	__PR((FILE *outfile));
#if 0
LOCAL 	void	dump_filelist	__PR((void));
#endif
LOCAL 	int	compare_dirs	__PR((const void *rr, const void *ll));
EXPORT	int	sort_directory	__PR((struct directory_entry **sort_dir,
						int rr));
LOCAL 	int	root_gen	__PR((void));
LOCAL 	BOOL	assign_file_addresses __PR((struct directory *dpnt, BOOL isnest));
LOCAL 	void	free_one_directory  __PR((struct directory *dpnt));
LOCAL 	void	free_directories __PR((struct directory *dpnt));
EXPORT	void	generate_one_directory __PR((struct directory *dpnt,
						FILE *outfile));
LOCAL 	void	build_pathlist	__PR((struct directory *node));
LOCAL 	int	compare_paths	__PR((void const *r, void const *l));
LOCAL 	int	generate_path_tables __PR((void));
EXPORT	void	memcpy_max	__PR((char *to, char *from, int max));
EXPORT	void	outputlist_insert __PR((struct output_fragment *frag));
LOCAL 	int	file_write	__PR((FILE *outfile));
LOCAL 	int	pvd_write	__PR((FILE *outfile));
LOCAL 	int	xpvd_write	__PR((FILE *outfile));
LOCAL 	int	evd_write	__PR((FILE *outfile));
LOCAL 	int	vers_write	__PR((FILE *outfile));
LOCAL 	int	graftcp		__PR((char *to, char *from, char *ep));
LOCAL 	int	pathcp		__PR((char *to, char *from, char *ep));
LOCAL 	int	pathtab_write	__PR((FILE *outfile));
LOCAL 	int	exten_write	__PR((FILE *outfile));
EXPORT	int	oneblock_size	__PR((UInt32_t starting_extent));
LOCAL 	int	pathtab_size	__PR((UInt32_t starting_extent));
LOCAL 	int	startpad_size	__PR((UInt32_t starting_extent));
LOCAL 	int	interpad_size	__PR((UInt32_t starting_extent));
LOCAL 	int	endpad_size	__PR((UInt32_t starting_extent));
LOCAL 	int	file_gen	__PR((void));
LOCAL 	int	dirtree_dump	__PR((void));
LOCAL 	int	dirtree_fixup	__PR((UInt32_t starting_extent));
LOCAL 	int	dirtree_size	__PR((UInt32_t starting_extent));
LOCAL 	int	ext_size	__PR((UInt32_t starting_extent));
LOCAL 	int	dirtree_write	__PR((FILE *outfile));
LOCAL 	int	dirtree_cleanup	__PR((FILE *outfile));
LOCAL 	int	startpad_write	__PR((FILE *outfile));
LOCAL 	int	interpad_write	__PR((FILE *outfile));
LOCAL 	int	endpad_write	__PR((FILE *outfile));
#ifdef APPLE_HYB
LOCAL 	int	hfs_pad;
LOCAL 	int	hfs_get_parms	__PR((char * key));
LOCAL 	void	hfs_file_gen	__PR((UInt32_t start_extent));
LOCAL 	void	gen_prepboot	__PR((void));
EXPORT	Ulong	get_adj_size	__PR((int Csize));
EXPORT	int	adj_size	__PR((int Csize, UInt32_t start_extent, int extra));
EXPORT	void	adj_size_other	__PR((struct directory *dpnt));
LOCAL 	int	hfs_hce_write	__PR((FILE * outfile));
EXPORT	int	insert_padding_file __PR((int size));
#endif	/* APPLE_HYB */

#ifdef SORTING
LOCAL 	int	compare_sort	__PR((const void * rr, const void * ll));
LOCAL 	void	reassign_link_addresses	__PR((struct directory * dpnt));
LOCAL 	int	sort_file_addresses __PR((void));
#endif /* SORTING */

/*
 * Routines to actually write the disc.  We write sequentially so that
 * we could write a tape, or write the disc directly
 */
#define	FILL_SPACE(X)	memset(vol_desc.X, ' ', sizeof (vol_desc.X))

EXPORT void
xfwrite(buffer, size, count, file, submode, islast)
	void	*buffer;
	int	size;
	int	count;
	FILE	*file;
	int	submode;
	BOOL	islast;
{
	/*
	 * This is a hack that could be made better.
	 * XXXIs this the only place?
	 * It is definitely needed on Operating Systems that do not allow to
	 * write files that are > 2GB. If the system is fast enough to be able
	 * to feed 1400 KB/s writing speed of a DVD-R drive, use stdout.
	 * If the system cannot do this reliable, you need to use this hacky
	 * option.
	 */
	static int	idx = 0;

#ifdef	XFWRITE_DEBUG
	if (count != 1 || (size % 2048) != 0)
		error("Count: %d, size: %d\n", count, size);
#endif
	if (count == 0 || size == 0) {
		errmsgno(EX_BAD,
		"Implementation botch, write 0 bytes (size %d count %d).\n",
		size, count);
		abort();
	}

	if (split_output != 0 &&
		(idx == 0 || ftell(file) >= ((off_t)1024 * 1024 * 1024))) {
		char		nbuf[512];
		extern char	*outfile;

		if (idx == 0)
			unlink(outfile);
		sprintf(nbuf, "%s_%02d", outfile, idx++);
		file = freopen(nbuf, "wb", file);
		if (file == NULL) {
			comerr("Cannot open '%s'.\n", nbuf);
		}
	}
	while (count) {
		int	got;

		seterrno(0);
		if (osecsize != 0)
			got = xawrite(buffer, size, count, file, submode, islast);
		else
			got = fwrite(buffer, size, count, file);

		if (got <= 0) {
			comerr("cannot fwrite %d*%d\n", size, count);
		}
		/*
		 * This comment is in hope to prevent silly people from
		 * e.g. SuSE (who did not yet learn C but believe that
		 * they need to patch other peoples code) from changing the
		 * next cast into an illegal lhs cast expression.
		 * The cast below is the correct way to handle the problem.
		 * The (void *) cast is to avoid a GCC warning like:
		 * "warning: dereferencing type-punned pointer will break \
		 * strict-aliasing rules"
		 * which is wrong this code. (void *) introduces a compatible
		 * intermediate type in the cast list.
		 */
		count -= got, *(char **)(void *)&buffer += size * got;
	}
}

LOCAL int
xawrite(buffer, size, count, file, submode, islast)
	void	*buffer;
	int	size;
	int	count;
	FILE	*file;
	int	submode;
	BOOL	islast;
{
	register char	*p = buffer;
	register int	amt = size * count;
	register int	n;
	struct xa_subhdr subhdr[2];

	if (osecsize == 2048)
		return (fwrite(buffer, size, count, file));

	if (amt % 2048)
		comerrno(EX_BAD,
			"Trying to write %d bytes (not a multiple of 2048).\n",
			amt);

	subhdr[0].file_number		= subhdr[1].file_number		= 0;
	subhdr[0].channel_number	= subhdr[1].channel_number	= 0;
	subhdr[0].coding		= subhdr[1].coding		= 0;

	while (amt > 0) {
#ifdef	LATER
		if (submode < 0)
			subhdr[0].sub_mode = subhdr[1].sub_mode = XA_SUBH_DATA;
		else
			subhdr[0].sub_mode = subhdr[1].sub_mode = submode;
#else
		subhdr[0].sub_mode = subhdr[1].sub_mode = XA_SUBH_DATA;
#endif

		if ((amt <= 2048) && islast) {
			subhdr[0].sub_mode = subhdr[1].sub_mode
						|= (XA_SUBH_EOR|XA_SUBH_EOF);
		}
		n = fwrite(&subhdr, sizeof (subhdr), 1, file);
		if (n <= 0)
			return (n);

		n = fwrite(p, 2048, 1, file);
		if (n <= 0)
			return (n);

		p += 2048;
		amt -= 2048;
	}
	return (1);
}

#ifdef APPLE_HYB
/*
 * use the deferred_write struct to store info about the hfs_boot_file
 */
LOCAL struct deferred_write mac_boot;

#endif	/* APPLE_HYB */
LOCAL struct deferred_write	*dw_head = NULL,
				*dw_tail = NULL;

UInt32_t	last_extent_written = 0;
LOCAL	Uint	path_table_index;
EXPORT	time_t	begun;

/*
 * We recursively walk through all of the directories and assign extent
 * numbers to them.  We have already assigned extent numbers to everything that
 * goes in front of them
 */
LOCAL int
assign_directory_addresses(node)
	struct directory	*node;
{
	int		dir_size;
	struct directory *dpnt;

	dpnt = node;

	while (dpnt) {
		/* skip if it's hidden */
		if (dpnt->dir_flags & INHIBIT_ISO9660_ENTRY) {
			dpnt = dpnt->next;
			continue;
		}
		/*
		 * If we already have an extent for this (i.e. it came from a
		 * multisession disc), then don't reassign a new extent.
		 */
		dpnt->path_index = next_path_index++;
		if (dpnt->extent == 0) {
			dpnt->extent = last_extent;
			dir_size = ISO_BLOCKS(dpnt->size);

			last_extent += dir_size;

			/*
			 * Leave room for the CE entries for this directory.
			 * Keep them close to the reference directory so that
			 * access will be quick.
			 */
			if (dpnt->ce_bytes) {
				last_extent += ISO_BLOCKS(dpnt->ce_bytes);
			}
		}
		if (dpnt->subdir) {
			assign_directory_addresses(dpnt->subdir);
		}
		dpnt = dpnt->next;
	}
	return (0);
}

#ifdef APPLE_HYB
LOCAL void
write_one_file(filename, size, outfile, off, isrfile, rba)
	char		*filename;
	off_t		size;
	FILE		*outfile;
	off_t		off;
	int		isrfile;
	unsigned	rba;
#else
LOCAL void
write_one_file(filename, size, outfile)
	char		*filename;
	off_t		size;
	FILE		*outfile;
#endif	/* APPLE_HYB */
{
	/*
	 * It seems that there are still stone age C-compilers
	 * around.
	 * The Metrowerks C found on BeOS/PPC does not allow
	 * more than 32kB of local vars.
	 * As we do not need to call write_one_file() recursively
	 * we make buffer static.
	 */
static	char		buffer[SECTOR_SIZE * NSECT];
	FILE		*infile;
	off_t		remain;
	int	use;
	int	unroundeduse;
	int	bytestowrite = 0;	/* Dummy init. to serve GCC bug */
	int	correctedsize = 0;


	if ((infile = fopen(filename, "rb")) == NULL) {
		comerr("cannot open '%s'\n", filename);
	}
#ifdef APPLE_HYB
	fseek(infile, off, SEEK_SET);
#if defined(INSERTMACRESFORK) && defined(UDF)
	if (isrfile && use_udf) {
		memset(buffer, 0, sizeof (buffer));
		udf_set_extattr_freespace((Uchar *)buffer, size, rba);
		xfwrite(buffer, SECTOR_SIZE, 1, outfile, XA_SUBH_DATA, 1);
		last_extent_written++;

		memset(buffer, 0, sizeof (buffer));
		udf_set_extattr_macresfork((Uchar *)buffer, size, rba);
		xfwrite(buffer, SIZEOF_UDF_EXT_ATTRIBUTE_COMMON, 1, outfile, XA_SUBH_DATA, 1);
		correctedsize = SIZEOF_UDF_EXT_ATTRIBUTE_COMMON;
	}
#endif
#endif	/* APPLE_HYB */
	remain = size;

	while (remain > 0) {
		int	amt;

		unroundeduse = use = (remain > SECTOR_SIZE * NSECT - 1 ?
				NSECT * SECTOR_SIZE : remain);
		use = ISO_ROUND_UP(use);	/* Round up to nearest sector */
						/* boundary */
		memset(buffer, 0, use);
		seterrno(0);
		amt = fread(buffer, 1, use, infile);
		if (amt < use && amt != remain) {
			/*
			 * Note that mkisofs is not star and no 100% archiver.
			 * We only detect file growth if the new size does not
			 * match 'use' at the last read.
			 */
			if (geterrno() == 0) {
				comerrno(EX_BAD,
					"File '%s' did %s.\n",
						filename,
						amt < remain ?
						"shrink":"grow");
			}
			comerr("Cannot read from '%s'\n", filename);
		}
#if defined(APPLE_HYB) && defined(INSERTMACRESFORK) && defined(UDF)
		if (unroundeduse == remain && isrfile && use_udf && correctedsize) {
			/* adjust the last block to write according to correctedsize */
			if (use - unroundeduse == correctedsize) {
				bytestowrite = use;
				correctedsize = 0;
			} else if (use - unroundeduse > correctedsize) {
				bytestowrite = use - correctedsize;
				correctedsize = 0;
			} else if (use - unroundeduse < correctedsize) {
				bytestowrite = unroundeduse;
				correctedsize -= use - unroundeduse;
			}
		} else {
			bytestowrite = use;
		}
#else
		bytestowrite = use;
#endif

		xfwrite(buffer, bytestowrite, 1, outfile,
				XA_SUBH_DATA, remain <= (SECTOR_SIZE * NSECT));
		last_extent_written += use / SECTOR_SIZE;
#if 0
		if ((last_extent_written % 1000) < use / SECTOR_SIZE) {
			fprintf(stderr, "%d..", last_extent_written);
		}
#else
		if (verbose > 0 &&
		    (int)(last_extent_written % (gui ? 500 : 5000)) <
							use / SECTOR_SIZE) {
			time_t	now;
			time_t	the_end;
			double	frac;

			time(&now);
			frac = last_extent_written / (1.0 * last_extent);
			the_end = begun + (now - begun) / frac;
#ifndef NO_FLOATINGPOINT
			fprintf(stderr, "%6.2f%% done, estimate finish %s",
				frac * 100., ctime(&the_end));
#else
			fprintf(stderr, "%3d.%-02d%% done, estimate finish %s",
				(int)(frac * 100.),
				(int)((frac+.00005) * 10000.)%100,
				ctime(&the_end));
#endif
			fflush(stderr);
		}
#endif
		remain -= use;
	}
#ifdef APPLE_HYB
#if defined(INSERTMACRESFORK) && defined(UDF)
	if (isrfile && use_udf && correctedsize) {
		if (ISO_ROUND_UP(size) < ISO_ROUND_UP(size + SIZEOF_UDF_EXT_ATTRIBUTE_COMMON)) {
			memset(buffer, 0, sizeof (buffer));
			xfwrite(buffer, SECTOR_SIZE - correctedsize, 1, outfile, XA_SUBH_DATA, 1);
			last_extent_written++;
		}
	}
#endif
#endif
	fclose(infile);
} /* write_one_file(... */

LOCAL void
write_udf_symlink(filename, size, outfile)
	char		*filename;
	off_t		size;
	FILE		*outfile;
{
	static	char	buffer[SECTOR_SIZE * NSECT];
	off_t		remain = sizeof (buffer);
	int		use;

	if (udf_get_symlinkcontents(filename, buffer, &remain) < 0) {
		comerr("Cannot open smylink '%s'\n", filename);
	}
	if (remain != size) {
		comerrno(EX_BAD, "Symlink '%s' did %s.\n",
					filename,
					size > remain ?
					"shrink":"grow");
	}
	use = (remain > SECTOR_SIZE * NSECT - 1 ?
			NSECT * SECTOR_SIZE : remain);
	use = ISO_ROUND_UP(use);
	xfwrite(buffer, use, 1, outfile,
			XA_SUBH_DATA, remain <= (SECTOR_SIZE * NSECT));
	last_extent_written += use / SECTOR_SIZE;

} /* write_udf_symlink(... */

LOCAL void
write_files(outfile)
	FILE	*outfile;
{
	struct deferred_write	*dwpnt,
				*dwnext;
	unsigned		rba = 0;

	dwpnt = dw_head;
	while (dwpnt) {
/*#define DEBUG*/
#ifdef DEBUG
		fprintf(stderr,
		"The file name is %s and pad is %d, size is %lld and extent is %d\n",
				dwpnt->name, dwpnt->pad,
				(Llong)dwpnt->size, dwpnt->extent);
#endif
		if (dwpnt->table) {
			xfwrite(dwpnt->table, ISO_ROUND_UP(dwpnt->size), 1,
							outfile,
							XA_SUBH_DATA, TRUE);
			last_extent_written += ISO_BLOCKS(dwpnt->size);
			table_size += dwpnt->size;
/*			fprintf(stderr, "Size %lld ", (Llong)dwpnt->size); */
			free(dwpnt->table);
			dwpnt->table = NULL;
		} else {

#ifdef VMS
			vms_write_one_file(dwpnt->name, dwpnt->size, outfile);
#else
#ifdef UDF
			if ((dwpnt->dw_flags & IS_SYMLINK) && use_udf && create_udfsymlinks) {
				write_udf_symlink(dwpnt->name, dwpnt->size, outfile);
			} else {
#endif	/* UDF */
#ifdef APPLE_HYB
#if defined(INSERTMACRESFORK) && defined(UDF)
				if (file_is_resource(dwpnt->name, dwpnt->hfstype) && (dwpnt->size > 0) && use_udf) {
					rba = dwpnt->extent;
				} else {
					rba = 0;
				}
#endif	/* INSERTMACRESFORK && UDF */
				write_one_file(dwpnt->name, dwpnt->size, outfile, dwpnt->off,
					file_is_resource(dwpnt->name, dwpnt->hfstype) && (dwpnt->size > 0), rba);
#else
				write_one_file(dwpnt->name, dwpnt->size, outfile);
#endif	/* APPLE_HYB */
#ifdef UDF
			}
#endif
#endif	/* VMS */
			free(dwpnt->name);
			dwpnt->name = NULL;
		}


#ifndef DVD_VIDEO
#define	dvd_video	0
#endif

#ifndef APPLE_HYB
#define	apple_hyb	0
#endif

#if	defined(APPLE_HYB) || defined(DVD_VIDEO)

		if ((apple_hyb && !donotwrite_macpart) || dvd_video) {
			/*
			 * we may have to pad out ISO files to work with HFS
			 * clump sizes
			 */
			char	blk[SECTOR_SIZE];
			Uint	i;

			for (i = 0; i < dwpnt->pad; i++)
				xfwrite(blk, SECTOR_SIZE, 1, outfile, 0, FALSE);

			last_extent_written += dwpnt->pad;
		}
#endif	/* APPLE_HYB || DVD_VIDEO */


		dwnext = dwpnt;
		dwpnt = dwpnt->next;
		free(dwnext);
		dwnext = NULL;
	}
} /* write_files(... */

#if 0
LOCAL void
dump_filelist()
{
	struct deferred_write *dwpnt;

	dwpnt = dw_head;
	while (dwpnt) {
		fprintf(stderr, "File %s\n", dwpnt->name);
		dwpnt = dwpnt->next;
	}
	fprintf(stderr, "\n");
}

#endif

LOCAL int
compare_dirs(rr, ll)
	const void	*rr;
	const void	*ll;
{
	char		*rpnt,
			*lpnt;
	struct directory_entry **r,
			**l;

	r = (struct directory_entry **) rr;
	l = (struct directory_entry **) ll;
	rpnt = (*r)->isorec.name;
	lpnt = (*l)->isorec.name;

#ifdef APPLE_HYB
	/*
	 * resource fork MUST (not sure if this is true for HFS volumes) be
	 * before the data fork - so force it here
	 */
	if ((*r)->assoc && (*r)->assoc == (*l))
		return (1);
	if ((*l)->assoc && (*l)->assoc == (*r))
		return (-1);
#endif	/* APPLE_HYB */

	/* If the entries are the same, this is an error. */
	if (strcmp(rpnt, lpnt) == 0) {
		errmsgno(EX_BAD,
			"Error: '%s' and '%s' have the same ISO9660 name '%s'.\n",
			(*r)->whole_name, (*l)->whole_name,
			rpnt);
		sort_goof++;
	}
	/* Check we don't have the same RR name */
	if (use_RockRidge && !is_rr_dir) {
		/*
		 * entries *can* have the same RR name in the "rr_moved"
		 * directory so skip checks if we're in reloc_dir
		 */
		if (strcmp((*r)->name, (*l)->name) == 0) {
			errmsgno(EX_BAD,
			"Error: '%s' and '%s' have the same Rock Ridge name '%s'.\n",
				(*r)->whole_name, (*l)->whole_name,
				(*r)->name);
			sort_goof++;
		}
	}
	/*
	 * Put the '.' and '..' entries on the head of the sorted list. For
	 * normal ASCII, this always happens to be the case, but out of band
	 * characters cause this not to be the case sometimes.
	 * FIXME(eric) - these tests seem redundant, in that the name is never
	 * assigned these values.  It will instead be \000 or \001, and thus
	 * should always be sorted correctly.   I need to figure out why I
	 * thought I needed this in the first place.
	 */
#if 0
	if (strcmp(rpnt, ".") == 0)
		return (-1);
	if (strcmp(lpnt, ".") == 0)
		return (1);

	if (strcmp(rpnt, "..") == 0)
		return (-1);
	if (strcmp(lpnt, "..") == 0)
		return (1);
#else
	/*
	 * The code above is wrong (as explained in Eric's comment), leading to
	 * incorrect sort order iff the -L option ("allow leading dots") is in
	 * effect and a directory contains entries that start with a dot.
	 *  (TF, Tue Dec 29 13:49:24 CET 1998)
	 */
	if ((*r)->isorec.name_len[0] == 1 && *rpnt == 0)
		return (-1);	/* '.' */
	if ((*l)->isorec.name_len[0] == 1 && *lpnt == 0)
		return (1);

	if ((*r)->isorec.name_len[0] == 1 && *rpnt == 1)
		return (-1);	/* '..' */
	if ((*l)->isorec.name_len[0] == 1 && *lpnt == 1)
		return (1);
#endif

	while (*rpnt && *lpnt) {
		if (*rpnt == ';' && *lpnt != ';')
			return (-1);
		if (*rpnt != ';' && *lpnt == ';')
			return (1);

		if (*rpnt == ';' && *lpnt == ';')
			return (0);

		if (*rpnt == '.' && *lpnt != '.')
			return (-1);
		if (*rpnt != '.' && *lpnt == '.')
			return (1);

		if ((unsigned char) *rpnt < (unsigned char) *lpnt)
			return (-1);
		if ((unsigned char) *rpnt > (unsigned char) *lpnt)
			return (1);
		rpnt++;
		lpnt++;
	}
	if (*rpnt)
		return (1);
	if (*lpnt)
		return (-1);
	return (0);
}

/*
 * Function:		sort_directory
 *
 * Purpose:		Sort the directory in the appropriate ISO9660
 *			order.
 *
 * Notes:		Returns 0 if OK, returns > 0 if an error occurred.
 */
EXPORT int
sort_directory(sort_dir, rr)
	struct directory_entry **sort_dir;
	int		rr;
{
	int		dcount = 0;
	int		xcount = 0;
	int		j;
	int		i,
			len;
	struct directory_entry *s_entry;
	struct directory_entry **sortlist;

	/* need to keep a count of how many entries are hidden */
	s_entry = *sort_dir;
	while (s_entry) {
		if (s_entry->de_flags & INHIBIT_ISO9660_ENTRY)
			xcount++;
		dcount++;
		s_entry = s_entry->next;
	}

	if (dcount == 0) {
		return (0);
	}
	/* OK, now we know how many there are.  Build a vector for sorting. */
	sortlist = (struct directory_entry **)
		e_malloc(sizeof (struct directory_entry *) * dcount);

	j = dcount - 1;
	dcount = 0;
	s_entry = *sort_dir;
	while (s_entry) {
		if (s_entry->de_flags & INHIBIT_ISO9660_ENTRY) {
			/* put any hidden entries at the end of the vector */
			sortlist[j--] = s_entry;
		} else {
			sortlist[dcount] = s_entry;
			dcount++;
		}
		len = s_entry->isorec.name_len[0];
		s_entry->isorec.name[len] = 0;
		s_entry = s_entry->next;
	}

	/* Each directory is required to contain at least . and .. */
	if (dcount < 2) {
		errmsgno(EX_BAD,
			"Directory size too small (. or .. missing ??%s)\n",
			"?");	/* Try to avoid a GCC trigraph warning */
		sort_goof = 1;

	} else {
		/* only sort the non-hidden entries */
		sort_goof = 0;
		is_rr_dir = rr;
#ifdef PROTOTYPES
		qsort(sortlist, dcount, sizeof (struct directory_entry *),
			(int (*) (const void *, const void *)) compare_dirs);
#else
		qsort(sortlist, dcount, sizeof (struct directory_entry *),
			compare_dirs);
#endif

		/*
		 * Now reassemble the linked list in the proper sorted order
		 * We still need the hidden entries, as they may be used in
		 * the Joliet tree.
		 */
		for (i = 0; i < dcount + xcount - 1; i++) {
			sortlist[i]->next = sortlist[i + 1];
		}

		sortlist[dcount + xcount - 1]->next = NULL;
		*sort_dir = sortlist[0];
	}

	free(sortlist);
	sortlist = NULL;
	return (sort_goof);
}

LOCAL int
root_gen()
{
	init_fstatbuf();

	root_record.length[0] = 1 +
			offsetof(struct iso_directory_record, name[0]);
	root_record.ext_attr_length[0] = 0;
	set_733((char *) root_record.extent, root->extent);
	set_733((char *) root_record.size, ISO_ROUND_UP(root->size));
	iso9660_date(root_record.date, root_statbuf.st_mtime);
	root_record.flags[0] = ISO_DIRECTORY;
	root_record.file_unit_size[0] = 0;
	root_record.interleave[0] = 0;
	set_723(root_record.volume_sequence_number, volume_sequence_number);
	root_record.name_len[0] = 1;
	return (0);
}

#ifdef SORTING
/*
 *	sorts deferred_write entries based on the sort weight
 */
LOCAL int
compare_sort(rr, ll)
	const void	*rr;
	const void	*ll;
{
	struct deferred_write	**r;
	struct deferred_write	**l;
	int			r_sort;
	int			l_sort;

	r = (struct deferred_write **) rr;
	l = (struct deferred_write **) ll;
	r_sort = (*r)->s_entry->sort;
	l_sort = (*l)->s_entry->sort;

	if (r_sort != l_sort)
		return (r_sort < l_sort ? 1 : -1);
	else
		return ((*r)->extent - (*l)->extent);
}

/*
 *	reassign start extents to files that are "hard links" to
 *	files that may have been sorted
 */
LOCAL void
reassign_link_addresses(dpnt)
	struct directory	*dpnt;
{
	struct directory_entry	*s_entry;
	struct file_hash	*s_hash;

	while (dpnt) {
		s_entry = dpnt->contents;
		for (s_entry = dpnt->contents; s_entry; s_entry = s_entry->next) {
			/* link files have already been given the weight NOT_SORTED */
			if (s_entry->sort != NOT_SORTED)
				continue;

			/* update the start extent */
			s_hash = find_hash(s_entry->dev, s_entry->inode);
			if (s_hash) {
				set_733((char *) s_entry->isorec.extent, s_hash->starting_block);
				s_entry->starting_block = s_hash->starting_block;
			}
		}
		if (dpnt->subdir) {
			reassign_link_addresses(dpnt->subdir);
		}

		dpnt = dpnt->next;
	}
}

/*
 *	sort files in order of the given sort weight
 */
LOCAL int
sort_file_addresses()
{
	struct deferred_write	*dwpnt;
	struct deferred_write	**sortlist;
	struct directory_entry	*s_entry;
	UInt32_t		start_extent;
	int			num = 0;
	int			i;

	/* need to store start extents for linked files */
	flush_hash();

	/* find out how many files we have */
	dwpnt = dw_head;
	while (dwpnt) {
		num++;
		dwpnt = dwpnt->next;
	}

	/* return if we have none */
	if (num == 0) {
		return (1);
	}

	/* save the start extent of the first file */
	start_extent = dw_head->extent;

	/* set up vector to store entries */
	sortlist = (struct deferred_write **)
		e_malloc(sizeof (struct deferred_write *) * num);

	for (i = 0, dwpnt = dw_head; i < num; i++, dwpnt = dwpnt->next)
		sortlist[i] = dwpnt;

	/* sort the list */
#ifdef PROTOTYPES
	qsort(sortlist, num, sizeof (struct deferred_write *),
		(int (*)(const void *, const void *))compare_sort);
#else
	qsort(sortlist, num, sizeof (struct deferred_write *), compare_sort);
#endif

	/* reconstruct the linked list */
	for (i = 0; i < num-1; i++) {
		sortlist[i]->next = sortlist[i+1];
	}

	sortlist[num-1]->next = NULL;
	dw_head = sortlist[0];

	free(sortlist);

	/* set the new start extents for the sorted list */
	for (i = 0, dwpnt = dw_head; i < num; i++, dwpnt = dwpnt->next) {
		s_entry = dwpnt->s_entry;
		dwpnt->extent = s_entry->starting_block = start_extent;
		set_733((char *) s_entry->isorec.extent, start_extent);

		start_extent += ISO_BLOCKS(s_entry->size);
#ifdef DVD_VIDEO
		/*
		 * Shouldn't this be done for every type of sort? Otherwise
		 * we will loose every pad info we add if we sort the files
		 */
		if (dvd_video) {
			start_extent += dwpnt->pad;
		}
#endif /* DVD_VIDEO */

		/* cache start extents for any linked files */
		add_hash(s_entry);
	}

	return (0);
}
#endif /* SORTING */



LOCAL BOOL
assign_file_addresses(dpnt, isnest)
	struct directory	*dpnt;
	BOOL			isnest;
{
	struct directory *finddir;
	struct directory_entry *s_entry;
	struct file_hash *s_hash;
	struct deferred_write *dwpnt;
	char		whole_path[PATH_MAX];
#ifdef DVD_VIDEO
	char		dvd_path[PATH_MAX];
	title_set_info_t * title_set_info = NULL;
	char	*p;
#endif
	BOOL	ret = FALSE;

	while (dpnt) {
#ifdef DVD_VIDEO
		if (dvd_video && root == dpnt->parent &&
		    ((p = strstr(dpnt->whole_name, "VIDEO_TS")) != 0)&&
					strcmp(p, "VIDEO_TS") == 0) {
			int	maxlen = strlen(dpnt->whole_name)-8+1;

			if (maxlen > sizeof (dvd_path))
				maxlen = sizeof (dvd_path);
			strlcpy(dvd_path, dpnt->whole_name, maxlen);
#ifdef DEBUG
			fprintf(stderr, "Found 'VIDEO_TS', the path is %s \n", dvd_path);
#endif
			title_set_info = DVDGetFileSet(dvd_path);
			if (title_set_info == 0) {
				/*
				 * Do not switch off -dvd-video but let is fail later.
				 */
/*				dvd_video = 0;*/
				errmsgno(EX_BAD, "Unable to parse DVD-Video structures.\n");
			} else {
				ret = TRUE;
			}
		}
#endif /* DVD_VIDEO */

		s_entry = dpnt->contents;
		for (s_entry = dpnt->contents; s_entry;
						s_entry = s_entry->next) {
			/*
			 * If we already have an  extent for this entry, then
			 * don't assign a new one.  It must have come from a
			 * previous session on the disc.  Note that we don't
			 * end up scheduling the thing for writing either.
			 */
			if (get_733(s_entry->isorec.extent) != 0) {
				continue;
			}
			/*
			 * This saves some space if there are symlinks present
			 */
			s_hash = find_hash(s_entry->dev, s_entry->inode);
			if (s_hash) {
				if (verbose > 2) {
					fprintf(stderr, "Cache hit for '%s%s%s'\n", s_entry->filedir->de_name,
						SPATH_SEPARATOR,
						s_entry->name);
				}
				set_733((char *) s_entry->isorec.extent,
						s_hash->starting_block);
				set_733((char *) s_entry->isorec.size,
						s_hash->size);
#ifdef SORTING
				/* check for non-directory files */
				if (do_sort && ((s_entry->isorec.flags[0] & ISO_DIRECTORY) == 0)) {
					/* make sure the real file has the highest weighting */
					s_hash->de->sort = MAX(s_entry->sort, s_hash->de->sort);
					/* flag this as a potential non-sorted file */
					s_entry->sort = NOT_SORTED;
				}
#endif /* SORTING */
				continue;
			}
			/*
			 * If this is for a directory that is not a . or
			 * a .. entry, then look up the information for the
			 * entry.  We have already assigned extents for
			 * directories, so we just need to fill in the blanks
			 * here.
			 */
			if (strcmp(s_entry->name, ".") != 0 &&
					strcmp(s_entry->name, "..") != 0 &&
					s_entry->isorec.flags[0] & ISO_DIRECTORY) {
				finddir = dpnt->subdir;
				while (1 == 1) {
					if (finddir->self == s_entry)
						break;
					finddir = finddir->next;
					if (!finddir) {
#ifdef	DVD_VIDEO
						if (title_set_info != 0) {
							DVDFreeFileSet(title_set_info);
						}
#endif
						comerrno(EX_BAD,
							"Fatal goof - could not find dir entry for '%s'\n",
							s_entry->name);
					}
				}
				set_733((char *) s_entry->isorec.extent,
						finddir->extent);
				s_entry->starting_block = finddir->extent;
				s_entry->size = ISO_ROUND_UP(finddir->size);
				total_dir_size += s_entry->size;
				add_hash(s_entry);
				set_733((char *) s_entry->isorec.size,
						ISO_ROUND_UP(finddir->size));
				continue;
			}
			/*
			 * If this is . or .., then look up the relevant info
			 * from the tables.
			 */
			if (strcmp(s_entry->name, ".") == 0) {
				set_733((char *) s_entry->isorec.extent,
								dpnt->extent);

				/*
				 * Set these so that the hash table has the
				 * correct information
				 */
				s_entry->starting_block = dpnt->extent;
				s_entry->size = ISO_ROUND_UP(dpnt->size);

				add_hash(s_entry);
				s_entry->starting_block = dpnt->extent;
				set_733((char *) s_entry->isorec.size,
						ISO_ROUND_UP(dpnt->size));
				continue;
			}
			if (strcmp(s_entry->name, "..") == 0) {
				if (dpnt == root) {
					total_dir_size += root->size;
				}
				set_733((char *) s_entry->isorec.extent,
							dpnt->parent->extent);

				/*
				 * Set these so that the hash table has the
				 * correct information
				 */
				s_entry->starting_block = dpnt->parent->extent;
				s_entry->size =
					ISO_ROUND_UP(dpnt->parent->size);

				add_hash(s_entry);
				s_entry->starting_block = dpnt->parent->extent;
				set_733((char *) s_entry->isorec.size,
					ISO_ROUND_UP(dpnt->parent->size));
				continue;
			}
			/*
			 * Some ordinary non-directory file.  Just schedule
			 * the file to be written.  This is all quite
			 * straightforward, just make a list and assign
			 * extents as we go.  Once we get through writing all
			 * of the directories, we should be ready write out
			 * these files
			 */
			if (s_entry->size) {
				dwpnt = (struct deferred_write *)
					e_malloc(sizeof (struct deferred_write));
				/* save this directory entry for later use */
				dwpnt->s_entry = s_entry;
				/* set the initial padding to zero */
				dwpnt->pad = 0;
				dwpnt->dw_flags = 0;
#ifdef DVD_VIDEO
				if (dvd_video && (title_set_info != 0)) {
					int pad;

					pad = DVDGetFilePad(title_set_info, s_entry->name);
					if (pad < 0) {
						errmsgno(EX_BAD,
						"Implementation botch. Video pad for file %s is %d\n",
						s_entry->name, pad),
						comerrno(EX_BAD,
						"Either the *.IFO file is bad or you found a mkisofs bug.\n");
					}
					dwpnt->pad = pad;
					if (verbose > 0 && pad != 0) {
						fprintf(stderr,
							"The pad was %d for file %s\n", dwpnt->pad, s_entry->name);
					}
				}
#endif /* DVD_VIDEO */
#ifdef APPLE_HYB
				/*
				 * maybe an offset to start of the real
				 * file/fork
				 */
				dwpnt->off = s_entry->hfs_off;
				dwpnt->hfstype = s_entry->hfs_type;
#else
				dwpnt->off = (off_t)0;
#endif	/* APPLE_HYB */
				if (dw_tail) {
					dw_tail->next = dwpnt;
					dw_tail = dwpnt;
				} else {
					dw_head = dwpnt;
					dw_tail = dwpnt;
				}
				if (s_entry->inode == TABLE_INODE) {
					dwpnt->table = s_entry->table;
					dwpnt->name = NULL;
					sprintf(whole_path, "%s%s%s",
						s_entry->filedir->whole_name,
						SPATH_SEPARATOR, trans_tbl);
				} else {
					dwpnt->table = NULL;
					strlcpy(whole_path,
						s_entry->whole_name,
						sizeof (whole_path));
					dwpnt->name = strdup(whole_path);
				}
				dwpnt->next = NULL;
				dwpnt->size = s_entry->size;
				dwpnt->extent = last_extent;
				set_733((char *) s_entry->isorec.extent,
								last_extent);
				s_entry->starting_block = last_extent;
				add_hash(s_entry);
				last_extent += ISO_BLOCKS(s_entry->size);
				dwpnt->dw_flags = s_entry->de_flags;
#ifdef APPLE_HYB
#if defined(INSERTMACRESFORK) && defined(UDF)
				if (file_is_resource(dwpnt->name, dwpnt->s_entry->hfs_type) && (dwpnt->size > 0) && use_udf) {
					last_extent++;
					if (ISO_ROUND_UP(dwpnt->size) <
					    ISO_ROUND_UP(dwpnt->size + SIZEOF_UDF_EXT_ATTRIBUTE_COMMON)) {
						last_extent++;
					}
				}
#endif
#endif	/* APPLE_HYB */
#ifdef DVD_VIDEO
				/* Shouldn't we always add the pad info? */
				if (dvd_video) {
					last_extent += dwpnt->pad;
				}
#endif /* DVD_VIDEO */
				if (verbose > 2) {
					fprintf(stderr, "%u %d %s\n",
						s_entry->starting_block,
						last_extent - 1, whole_path);
				}
#ifdef DBG_ISO
				if (ISO_BLOCKS(s_entry->size) > 500) {
					fprintf(stderr,
						"Warning: large file '%s'\n",
						whole_path);
					fprintf(stderr,
						"Starting block is %d\n",
						s_entry->starting_block);
					fprintf(stderr,
					"Reported file size is %lld\n",
						(Llong)s_entry->size);

				}
#endif
#ifdef	NOT_NEEDED	/* Never use this code if you like to create a DVD */

				if (last_extent > (800000000 >> 11)) {
					/* More than 800Mb? Punt */
					fprintf(stderr,
					"Extent overflow processing file '%s'\n",
						whole_path);
					fprintf(stderr,
						"Starting block is %d\n",
						s_entry->starting_block);
					fprintf(stderr,
					"Reported file size is %lld\n",
							(Llong)s_entry->size);
					exit(1);
				}
#endif
				continue;
			}
			/*
			 * This is for zero-length files.  If we leave the
			 * extent 0, then we get screwed, because many readers
			 * simply drop files that have an extent of zero.
			 * Thus we leave the size 0, and just assign the
			 * extent number.
			 */
			set_733((char *) s_entry->isorec.extent, last_extent);
		}
		if (dpnt->subdir) {
			if (assign_file_addresses(dpnt->subdir, TRUE))
				ret = TRUE;
		}
		dpnt = dpnt->next;
	}
#ifdef DVD_VIDEO
	if (title_set_info != NULL) {
		DVDFreeFileSet(title_set_info);
	}
	if (dvd_video && !ret && !isnest) {
		errmsgno(EX_BAD,
			"Could not find correct 'VIDEO_TS' directory.\n");
	}
#endif /* DVD_VIDEO */
	return (ret);
} /* assign_file_addresses(... */

LOCAL void
free_one_directory(dpnt)
	struct directory	*dpnt;
{
	struct directory_entry *s_entry;
	struct directory_entry *s_entry_d;

	s_entry = dpnt->contents;
	while (s_entry) {
		s_entry_d = s_entry;
		s_entry = s_entry->next;

		if (s_entry_d->rr_attributes) {
			free(s_entry_d->rr_attributes);
			s_entry_d->rr_attributes = NULL;
		}
		if (s_entry_d->name != NULL) {
			free(s_entry_d->name);
			s_entry_d->name = NULL;
		}
		if (s_entry_d->whole_name != NULL) {
			free(s_entry_d->whole_name);
			s_entry_d->whole_name = NULL;
		}
#ifdef APPLE_HYB
		if (apple_both && s_entry_d->hfs_ent && !s_entry_d->assoc)
			free(s_entry_d->hfs_ent);
#endif	/* APPLE_HYB */

		free(s_entry_d);
		s_entry_d = NULL;
	}
	dpnt->contents = NULL;
} /* free_one_directory(... */

LOCAL void
free_directories(dpnt)
	struct directory	*dpnt;
{
	while (dpnt) {
		free_one_directory(dpnt);
		if (dpnt->subdir)
			free_directories(dpnt->subdir);
		dpnt = dpnt->next;
	}
}

EXPORT void
generate_one_directory(dpnt, outfile)
	struct directory	*dpnt;
	FILE			*outfile;
{
	unsigned int	ce_address = 0;
	char		*ce_buffer;
	unsigned int	ce_index = 0;
	unsigned int	ce_size;
	unsigned int	dir_index;
	char		*directory_buffer;
	int		new_reclen;
	struct directory_entry *s_entry;
	struct directory_entry *s_entry_d;
	unsigned int	total_size;

	total_size = ISO_ROUND_UP(dpnt->size);
	directory_buffer = (char *) e_malloc(total_size);
	memset(directory_buffer, 0, total_size);
	dir_index = 0;

	ce_size = ISO_ROUND_UP(dpnt->ce_bytes);
	ce_buffer = NULL;

	if (ce_size > 0) {
		ce_buffer = (char *) e_malloc(ce_size);
		memset(ce_buffer, 0, ce_size);

		ce_index = 0;

		/* Absolute byte address of CE entries for this directory */
		ce_address = last_extent_written + (total_size >> 11);
		ce_address = ce_address << 11;
	}
	s_entry = dpnt->contents;
	while (s_entry) {
		/* skip if it's hidden */
		if (s_entry->de_flags & INHIBIT_ISO9660_ENTRY) {
			s_entry = s_entry->next;
			continue;
		}
		/*
		 * We do not allow directory entries to cross sector
		 * boundaries. Simply pad, and then start the next entry at
		 * the next sector
		 */
		new_reclen = s_entry->isorec.length[0];
		if ((dir_index & (SECTOR_SIZE - 1)) + new_reclen >=
								SECTOR_SIZE) {
			dir_index = ISO_ROUND_UP(dir_index);
		}
		memcpy(directory_buffer + dir_index, &s_entry->isorec,
			offsetof(struct iso_directory_record, name[0]) +
			s_entry->isorec.name_len[0]);
		dir_index += offsetof(struct iso_directory_record, name[0]) +
			s_entry->isorec.name_len[0];

		/* Add the Rock Ridge attributes, if present */
		if (s_entry->rr_attr_size) {
			if (dir_index & 1) {
				directory_buffer[dir_index++] = 0;
			}
			/*
			 * If the RR attributes were too long, then write the
			 * CE records, as required.
			 */
			if (s_entry->rr_attr_size != s_entry->total_rr_attr_size) {
				struct iso_xa_dir_record *xadp;
				unsigned char	*pnt;
				int		len,
						nbytes;

				/*
				 * Go through the entire record, first skip
				 * the XA record and then fix up the
				 * CE entries so that the extent and offset
				 * are correct
				 */
				pnt = s_entry->rr_attributes;
				len = s_entry->total_rr_attr_size;

				if (len >= 14) {
					xadp = (struct iso_xa_dir_record *)pnt;

					if (xadp->signature[0] == 'X' && xadp->signature[1] == 'A' &&
									xadp->reserved[0] == '\0') {
						len -= 14;
						pnt += 14;
					}
				}

				while (len > 3) {
#ifdef DEBUG
					if (ce_size <= 0) {
						fprintf(stderr,
						"Warning: ce_index(%d) && ce_address(%d) not initialized\n",
							ce_index, ce_address);
					}
#endif

					if (pnt[0] == 'C' && pnt[1] == 'E') {
						nbytes = get_733((char *) pnt + 20);

						if ((ce_index & (SECTOR_SIZE - 1)) + nbytes >=
							SECTOR_SIZE) {
							ce_index = ISO_ROUND_UP(ce_index);
						}
						set_733((char *) pnt + 4,
							(ce_address + ce_index) >> 11);
						set_733((char *) pnt + 12,
							(ce_address + ce_index) & (SECTOR_SIZE - 1));


						/*
						 * Now store the block in the
						 * ce buffer
						 */
						memcpy(ce_buffer + ce_index,
							pnt + pnt[2], nbytes);
						ce_index += nbytes;
						if (ce_index & 1) {
							ce_index++;
						}
					}
					len -= pnt[2];
					pnt += pnt[2];
				}

			}
			rockridge_size += s_entry->total_rr_attr_size;
			memcpy(directory_buffer + dir_index,
				s_entry->rr_attributes,
				s_entry->rr_attr_size);
			dir_index += s_entry->rr_attr_size;
		}
		if (dir_index & 1) {
			directory_buffer[dir_index++] = 0;
		}
		s_entry_d = s_entry;
		s_entry = s_entry->next;

		/*
		 * Joliet doesn't use the Rock Ridge attributes, so we free
		 * it here.
		 */
		if (s_entry_d->rr_attributes) {
			free(s_entry_d->rr_attributes);
			s_entry_d->rr_attributes = NULL;
		}
	}

	if (dpnt->size != dir_index) {
		errmsgno(EX_BAD,
			"Unexpected directory length %lld expected: %d '%s'\n",
			(Llong)dpnt->size,
			dir_index, dpnt->de_name);
	}
	xfwrite(directory_buffer, total_size, 1, outfile, 0, FALSE);
	last_extent_written += total_size >> 11;
	free(directory_buffer);
	directory_buffer = NULL;

	if (ce_size > 0) {
		if (ce_index != dpnt->ce_bytes) {
			errmsgno(EX_BAD,
			"Continuation entry record length mismatch %d expected: %d.\n",
				ce_index, dpnt->ce_bytes);
		}
		xfwrite(ce_buffer, ce_size, 1, outfile, 0, FALSE);
		last_extent_written += ce_size >> 11;
		free(ce_buffer);
		ce_buffer = NULL;
	}
} /* generate_one_directory(... */

LOCAL void
build_pathlist(node)
	struct directory	*node;
{
	struct directory *dpnt;

	dpnt = node;

	while (dpnt) {
		/* skip if it's hidden */
		if ((dpnt->dir_flags & INHIBIT_ISO9660_ENTRY) == 0)
			pathlist[dpnt->path_index] = dpnt;

		if (dpnt->subdir)
			build_pathlist(dpnt->subdir);
		dpnt = dpnt->next;
	}
} /* build_pathlist(... */

LOCAL int
compare_paths(r, l)
	void const	*r;
	void const	*l;
{
	struct directory const *ll = *(struct directory * const *) l;
	struct directory const *rr = *(struct directory * const *) r;

	if (rr->parent->path_index < ll->parent->path_index) {
		return (-1);
	}
	if (rr->parent->path_index > ll->parent->path_index) {
		return (1);
	}
	return (strcmp(rr->self->isorec.name, ll->self->isorec.name));

} /* compare_paths(... */

LOCAL int
generate_path_tables()
{
	struct directory_entry *de = NULL;
	struct directory *dpnt;
	int		fix;
	int		i;
	int		j;
	int		namelen;
	char		*npnt;
	char		*npnt1;
	int		tablesize;

	/* First allocate memory for the tables and initialize the memory */
	tablesize = path_blocks << 11;
	path_table_m = (char *) e_malloc(tablesize);
	path_table_l = (char *) e_malloc(tablesize);
	memset(path_table_l, 0, tablesize);
	memset(path_table_m, 0, tablesize);

	/*
	 * Now start filling in the path tables.  Start with root directory
	 */

	path_table_index = 0;
	pathlist = (struct directory **) e_malloc(sizeof (struct directory *)
		* next_path_index);
	memset(pathlist, 0, sizeof (struct directory *) * next_path_index);
	build_pathlist(root);

	do {
		fix = 0;
#ifdef PROTOTYPES
		qsort(&pathlist[1], next_path_index - 1,
			sizeof (struct directory *),
			(int (*) (const void *, const void *)) compare_paths);
#else
		qsort(&pathlist[1], next_path_index - 1,
			sizeof (struct directory *),
			compare_paths);
#endif

		for (j = 1; j < next_path_index; j++) {
			if (pathlist[j]->path_index != j) {
				pathlist[j]->path_index = j;
				fix++;
			}
		}
	} while (fix);

	for (j = 1; j < next_path_index; j++) {
		dpnt = pathlist[j];
		if (!dpnt) {
			comerrno(EX_BAD, "Entry %d not in path tables\n", j);
		}
		npnt = dpnt->de_name;

		/* So the root comes out OK */
		if ((*npnt == 0) || (dpnt == root)) {
			npnt = ".";
		}
		npnt1 = strrchr(npnt, PATH_SEPARATOR);
		if (npnt1) {
			npnt = npnt1 + 1;
		}
		de = dpnt->self;
		if (!de) {
			comerrno(EX_BAD,
			"Fatal ISO9660 goof - directory has amnesia\n");
		}
		namelen = de->isorec.name_len[0];

		path_table_l[path_table_index] = namelen;
		path_table_m[path_table_index] = namelen;
		path_table_index += 2;

		set_731(path_table_l + path_table_index, dpnt->extent);
		set_732(path_table_m + path_table_index, dpnt->extent);
		path_table_index += 4;

		if (dpnt->parent->path_index > 0xffff) {
			comerrno(EX_BAD,
			"Unable to generate sane path tables - too many directories (%d)\n",
				dpnt->parent->path_index);
		}

		set_721(path_table_l + path_table_index,
			dpnt->parent->path_index);
		set_722(path_table_m + path_table_index,
			dpnt->parent->path_index);
		path_table_index += 2;

		for (i = 0; i < namelen; i++) {
			path_table_l[path_table_index] = de->isorec.name[i];
			path_table_m[path_table_index] = de->isorec.name[i];
			path_table_index++;
		}
		if (path_table_index & 1) {
			path_table_index++;	/* For odd lengths we pad */
		}
	}

	free(pathlist);
	pathlist = NULL;
	if (path_table_index != path_table_size) {
		errmsgno(EX_BAD,
			"Path table lengths do not match %d expected: %d\n",
			path_table_index,
			path_table_size);
	}
	return (0);
} /* generate_path_tables(... */

EXPORT void
memcpy_max(to, from, max)
	char	*to;
	char	*from;
	int	max;
{
	int	n = strlen(from);

	if (n > max) {
		n = max;
	}
	memcpy(to, from, n);

} /* memcpy_max(... */

EXPORT void
outputlist_insert(frag)
	struct output_fragment *frag;
{
	struct output_fragment *nfrag;

	nfrag = e_malloc(sizeof (*frag));
	movebytes(frag, nfrag, sizeof (*frag));
	nfrag->of_start_extent = 0;

	if (out_tail == NULL) {
		out_list = out_tail = nfrag;
	} else {
		out_tail->of_next = nfrag;
		out_tail = nfrag;
	}
}

LOCAL int
file_write(outfile)
	FILE	*outfile;
{
	Uint	should_write;

#ifdef APPLE_HYB
	char	buffer[SECTOR_SIZE];

	memset(buffer, 0, sizeof (buffer));

	if (apple_hyb && !donotwrite_macpart) {

		int	i;

		/*
		 * write out padding to round up to HFS allocation block
		 */
		for (i = 0; i < hfs_pad; i++)
			xfwrite(buffer, sizeof (buffer), 1, outfile, 0, FALSE);

		last_extent_written += hfs_pad;
	}
#endif	/* APPLE_HYB */

	/*
	 * OK, all done with that crap.  Now write out the directories. This is
	 * where the fur starts to fly, because we need to keep track of each
	 * file as we find it and keep track of where we put it.
	 */
	should_write = last_extent - session_start;

	if (verbose > 2) {
#ifdef DBG_ISO
		fprintf(stderr,
			"Total directory extents being written = %u\n",
							last_extent);
#endif

#ifdef APPLE_HYB
		if (apple_hyb && !donotwrite_macpart)
			fprintf(stderr,
			"Total extents scheduled to be written (inc HFS) = %u\n",
				last_extent - session_start);
		else
#endif	/* APPLE_HYB */

			fprintf(stderr,
				"Total extents scheduled to be written = %u\n",
				last_extent - session_start);
	}
	/* Now write all of the files that we need. */
	write_files(outfile);

#ifdef APPLE_HYB
	/* write out extents/catalog/dt file */
	if (apple_hyb && !donotwrite_macpart) {

		xfwrite(hce->hfs_ce, HFS_BLOCKSZ, hce->hfs_tot_size, outfile, 0, FALSE);

		/* round up to a whole CD block */
		if (HFS_ROUND_UP(hce->hfs_tot_size) -
					hce->hfs_tot_size * HFS_BLOCKSZ) {
			xfwrite(buffer,
				HFS_ROUND_UP(hce->hfs_tot_size) -
				hce->hfs_tot_size * HFS_BLOCKSZ, 1, outfile, 0, FALSE);
		}
		last_extent_written += ISO_ROUND_UP(hce->hfs_tot_size *
						HFS_BLOCKSZ) / SECTOR_SIZE;

		/* write out HFS boot block */
		if (mac_boot.name)
			write_one_file(mac_boot.name, mac_boot.size, outfile,
								mac_boot.off, 0, 0);
	}
#endif	/* APPLE_HYB */

	/* The rest is just fluff. */
	if (verbose == 0) {
		return (0);
	}
#ifdef APPLE_HYB
	if (apple_hyb && !donotwrite_macpart) {
		fprintf(stderr,
			"Total extents actually written (inc HFS) = %u\n",
			last_extent_written - session_start);
		fprintf(stderr, "(Size of ISO volume = %d, HFS extra = %d)\n",
			last_extent_written - session_start - hfs_extra,
			hfs_extra);
	} else
#else
	fprintf(stderr, "Total extents actually written = %d\n",
		last_extent_written - session_start);
#endif	/* APPLE_HYB */

	/* Hard links throw us off here */
	if (should_write != (last_extent - session_start)) {
		fprintf(stderr,
		"Number of extents written not what was predicted.  Please fix.\n");
		fprintf(stderr, "Predicted = %d, written = %d\n",
						should_write, last_extent);
	}
	fprintf(stderr, "Total translation table size: %d\n", table_size);
	fprintf(stderr, "Total rockridge attributes bytes: %d\n",
						rockridge_size);
	fprintf(stderr, "Total directory bytes: %d\n", total_dir_size);
	fprintf(stderr, "Path table size(bytes): %d\n", path_table_size);

#ifdef DEBUG
	fprintf(stderr,
		"next extent, last_extent, last_extent_written %d %d %d\n",
		next_extent, last_extent, last_extent_written);
#endif

	return (0);

} /* iso_write(... */

/*
 * Function to write the PVD for the disc.
 */
LOCAL int
pvd_write(outfile)
	FILE	*outfile;
{
	char		iso_time[17];
	int		should_write;
	struct tm	local;
	struct tm	gmt;
	int		i;
	int		s;
	Uchar		*cp;


	time(&begun);

	local = *localtime(&begun);
	gmt = *gmtime(&begun);

	/*
	 * There was a comment here about breaking in the year 2000.
	 * That's not true, in 2000 tm_year == 100, so 1900+tm_year == 2000.
	 */
	sprintf(iso_time, "%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d00",
		1900 + local.tm_year,
		local.tm_mon + 1, local.tm_mday,
		local.tm_hour, local.tm_min, local.tm_sec);

	local.tm_min -= gmt.tm_min;
	local.tm_hour -= gmt.tm_hour;
	local.tm_yday -= gmt.tm_yday;
	local.tm_year -= gmt.tm_year;
	if (local.tm_year)		/* Hit new-year limit	*/
		local.tm_yday = local.tm_year;	/* yday = +-1	*/

	iso_time[16] = (local.tm_min + 60 *
				(local.tm_hour + 24 * local.tm_yday)) / 15;

	/* Next we write out the primary descriptor for the disc */
	memset(&vol_desc, 0, sizeof (vol_desc));
	vol_desc.type[0] = ISO_VD_PRIMARY;
	memcpy(vol_desc.id, ISO_STANDARD_ID, sizeof (ISO_STANDARD_ID));
	vol_desc.version[0] = 1;

	memset(vol_desc.system_id, ' ', sizeof (vol_desc.system_id));
	memcpy_max(vol_desc.system_id, system_id, strlen(system_id));

	memset(vol_desc.volume_id, ' ', sizeof (vol_desc.volume_id));
	memcpy_max(vol_desc.volume_id, volume_id, strlen(volume_id));

	should_write = last_extent - session_start;
	set_733((char *) vol_desc.volume_space_size, should_write);
	set_723(vol_desc.volume_set_size, volume_set_size);
	set_723(vol_desc.volume_sequence_number, volume_sequence_number);
	set_723(vol_desc.logical_block_size, SECTOR_SIZE);

	/*
	 * The path tables are used by DOS based machines to cache directory
	 * locations
	 */
	set_733((char *) vol_desc.path_table_size, path_table_size);
	set_731(vol_desc.type_l_path_table, path_table[0]);
	set_731(vol_desc.opt_type_l_path_table, path_table[1]);
	set_732(vol_desc.type_m_path_table, path_table[2]);
	set_732(vol_desc.opt_type_m_path_table, path_table[3]);

	/* Now we copy the actual root directory record */
	memcpy(vol_desc.root_directory_record, &root_record,
		offsetof(struct iso_directory_record, name[0]) + 1);

	/*
	 * The rest is just fluff.  It looks nice to fill in many of these
	 * fields, though.
	 */
	FILL_SPACE(volume_set_id);
	if (volset_id)
		memcpy_max(vol_desc.volume_set_id, volset_id, strlen(volset_id));

	FILL_SPACE(publisher_id);
	if (publisher)
		memcpy_max(vol_desc.publisher_id, publisher, strlen(publisher));

	FILL_SPACE(preparer_id);
	if (preparer)
		memcpy_max(vol_desc.preparer_id, preparer, strlen(preparer));

	FILL_SPACE(application_id);
	if (appid)
		memcpy_max(vol_desc.application_id, appid, strlen(appid));

	FILL_SPACE(copyright_file_id);
	if (copyright)
		memcpy_max(vol_desc.copyright_file_id, copyright,
			strlen(copyright));

	FILL_SPACE(abstract_file_id);
	if (abstract)
		memcpy_max(vol_desc.abstract_file_id, abstract,
			strlen(abstract));

	FILL_SPACE(bibliographic_file_id);
	if (biblio)
		memcpy_max(vol_desc.bibliographic_file_id, biblio,
			strlen(biblio));

	FILL_SPACE(creation_date);
	FILL_SPACE(modification_date);
	FILL_SPACE(expiration_date);
	FILL_SPACE(effective_date);
	vol_desc.file_structure_version[0] = 1;
	FILL_SPACE(application_data);

	memcpy(vol_desc.creation_date, iso_time, 17);
	memcpy(vol_desc.modification_date, iso_time, 17);
	memcpy(vol_desc.expiration_date, "0000000000000000", 17);
	memcpy(vol_desc.effective_date, iso_time, 17);

	if (use_XA) {
		char	*xap = &((char *)&vol_desc)[1024];

		memcpy(&xap[0], "CD-XA001", 8);			/* XA Sign.  */
		memcpy(&xap[8], "\0\0", 2);			/* XA flags  */
		memcpy(&xap[10], "\0\0\0\0\0\0\0\0", 8);	/* Start dir */
		memcpy(&xap[18], "\0\0\0\0\0\0\0\0", 8);	/* Reserved  */
	}

	/*
	 * Compute a checksum to be used as a fingerprint in case we
	 * include correct inode/link-count information in the current image.
	 */
	for (i = 0, s = 0, cp = (Uchar *)&vol_desc; i < SECTOR_SIZE; i++) {
		s += cp[i] & 0xFF;
	}
	vol_desc_sum = s;

	/* if not a bootable cd do it the old way */
	xfwrite(&vol_desc, SECTOR_SIZE, 1, outfile, 0, FALSE);
	last_extent_written++;
	return (0);
}

/*
 * Function to write the Extended PVD for the disc.
 */
LOCAL int
xpvd_write(outfile)
	FILE	*outfile;
{
	vol_desc.type[0] = ISO_VD_SUPPLEMENTARY;
	vol_desc.version[0] = 2;
	vol_desc.file_structure_version[0] = 2;

	/* if not a bootable cd do it the old way */
	xfwrite(&vol_desc, SECTOR_SIZE, 1, outfile, 0, FALSE);
	last_extent_written++;
	return (0);
}

/*
 * Function to write the EVD for the disc.
 */
LOCAL int
evd_write(outfile)
	FILE	*outfile;
{
	struct iso_primary_descriptor evol_desc;

	/*
	 * Now write the end volume descriptor.  Much simpler than the other
	 * one
	 */
	memset(&evol_desc, 0, sizeof (evol_desc));
	evol_desc.type[0] = (unsigned char) ISO_VD_END;
	memcpy(evol_desc.id, ISO_STANDARD_ID, sizeof (ISO_STANDARD_ID));
	evol_desc.version[0] = 1;
	xfwrite(&evol_desc, SECTOR_SIZE, 1, outfile, 0, TRUE);
	last_extent_written += 1;
	return (0);
}

/*
 * Function to write the version information for the disc.
 * Warning: Do not disable or change this function. The data created by this
 * function is used to tell the filesystem driver in the OS kernel that this
 * mkisofs version includes correct inode information.
 */
LOCAL int
vers_write(outfile)
	FILE	*outfile;
{
	char		vers[SECTOR_SIZE+1];
	int		X_ac;
	char		**X_av;
	char		*cp;
	int		i;
	int		idx = 4;
	int		len;
	extern char	version_string[];
	extern int	path_ind;

	/* Now write the version descriptor. */
	memset(vers, 0, sizeof (vers));
	strcpy(vers, "MKI ");			/* strcpy() OK here */

	cp = vers;
	X_ac = saved_ac();
	X_av = saved_av();
	strlcpy(&cp[idx], ctime(&begun), 26);
	idx += 25;
	strlcpy(&cp[idx], version_string, SECTOR_SIZE - idx);
	idx += strlen(version_string);
	for (i = 1; i < X_ac; i++) {
		len = strlen(X_av[i]);
		if ((idx + len + 2) >= SECTOR_SIZE)
			break;
		cp[idx++] = ' ';
		/*
		 * Do not give away secret information when not in debug mode.
		 */
		if (debug)
			strlcpy(&cp[idx], X_av[i], SECTOR_SIZE - idx);
		else if (i >= path_ind)
			len = graftcp(&cp[idx], X_av[i], &vers[SECTOR_SIZE-1]);
		else if (X_av[i][0] == '/')
			len = pathcp(&cp[idx], X_av[i], &vers[SECTOR_SIZE-1]);
		else
			strlcpy(&cp[idx], X_av[i], SECTOR_SIZE - idx);
		idx += len;
	}

	cp[SECTOR_SIZE - 1] = '\0';
	len = 0;
	if (correct_inodes) {
		/*
		 * Only add this fingerprint in case we include correct
		 * inode/link-count information in the current image.
		 */
		len = vol_desc_sum;
	}
	cp = &vers[SECTOR_SIZE - 1];
	*(Uchar *)cp = len % 256;
	len /= 256;
	*(Uchar *)--cp = len % 256;
	len /= 256;
	*(Uchar *)--cp = len % 256;

	xfwrite(vers, SECTOR_SIZE, 1, outfile, 0, TRUE);
	last_extent_written += 1;
	return (0);
}

/*
 * Avoid to write unwanted information into the version info string.
 */
LOCAL int
graftcp(to, from, ep)
	char	*to;
	char	*from;
	char	*ep;
{
	int	len = strlen(from);
	char	*node = NULL;

	if (use_graft_ptrs)
		node = findgequal(from);

	if (node == NULL) {
		len = 0;
		node = from;
	} else {
		len = node - from;
		*node = '\0';
		strncpy(to, from, ep - to);
		*node++ = '=';
		to += len++;
		*to++ = '=';
	}
	return (len + pathcp(to, node, ep));
}

LOCAL int
pathcp(to, from, ep)
	char	*to;
	char	*from;
	char	*ep;
{
	int	len = strlen(from);
	char	*p;

	p = strrchr(from, '/');
	if (p == NULL) {
		strncpy(to, from, ep - to);
	} else {
		if (p[1] == '\0') {
			--p;
			while (p > from && *p != '/')
				--p;
		}
		len = 0;
		if (*p == '/') {
			strncpy(to, "...", ep - to);
			to += 3;
			len = 3;
		}
		if (to < ep) {
			strncpy(to, p, ep - to);
			len += strlen(to);
		}
	}
	return (len);
}


/*
 * Function to write the path table for the disc.
 */
LOCAL int
pathtab_write(outfile)
	FILE	*outfile;
{
	/* Next we write the path tables */
	xfwrite(path_table_l, path_blocks << 11, 1, outfile, 0, FALSE);
	xfwrite(path_table_m, path_blocks << 11, 1, outfile, 0, FALSE);
	last_extent_written += 2 * path_blocks;
	free(path_table_l);
	free(path_table_m);
	path_table_l = NULL;
	path_table_m = NULL;
	return (0);
}

LOCAL int
exten_write(outfile)
	FILE	*outfile;
{
	xfwrite(extension_record, SECTOR_SIZE, 1, outfile, 0, FALSE);
	last_extent_written++;
	return (0);
}

/*
 * Functions to describe padding block at the start of the disc.
 */
EXPORT int
oneblock_size(starting_extent)
	UInt32_t	starting_extent;
{
	last_extent++;
	return (0);
}

/*
 * Functions to describe path table size.
 */
LOCAL int
pathtab_size(starting_extent)
	UInt32_t	starting_extent;
{
	path_table[0] = starting_extent;

	path_table[1] = 0;
	path_table[2] = path_table[0] + path_blocks;
	path_table[3] = 0;
	last_extent += 2 * path_blocks;
	return (0);
}

/*
 * Functions to describe padding blocks before PVD.
 */
LOCAL int
startpad_size(starting_extent)
	UInt32_t	starting_extent;
{
	last_extent = session_start + 16;
	return (0);
}

/*
 * Functions to describe padding blocks between sections.
 */
LOCAL int
interpad_size(starting_extent)
	UInt32_t	starting_extent;
{
	int	emod = 0;

#ifdef	needed
	starting_extent += 16;	/* First add 16 pad blocks */
#endif
	if ((emod = starting_extent % 16) != 0) {
		starting_extent += 16 - emod;	/* Now pad to mod 16 #	   */
	}
	last_extent = starting_extent;
	return (0);
}

/*
 * Functions to describe padding blocks at end of disk.
 */
LOCAL int
endpad_size(starting_extent)
	UInt32_t	starting_extent;
{
	starting_extent += 150;			/* 150 pad blocks (post gap) */
	last_extent = starting_extent;
	return (0);
}

LOCAL int
file_gen()
{
#ifdef APPLE_HYB
	UInt32_t	start_extent = last_extent;	/* orig ISO files start */

#endif	/* APPLE_HYB */

	if (!assign_file_addresses(root, FALSE)) {
#ifdef DVD_VIDEO
		if (dvd_video) {
			comerrno(EX_BAD, "Unable to make a DVD-Video image.\n");
		}
#else
		;
		/* EMPTY */
#endif
	}


#ifdef SORTING
	if (do_sort) {
		if (sort_file_addresses() == 0)
			reassign_link_addresses(root);
	}
#endif /* SORTING */

#ifdef APPLE_HYB
	/*
	 * put this here for the time being - may when I've worked out how to
	 * use Eric's new system for creating/writing parts of the image it
	 * may move to it's own routine
	 */
	if (apple_hyb && !donotwrite_macpart)
		hfs_file_gen(start_extent);
#ifdef PREP_BOOT
	else if (use_prep_boot || use_chrp_boot)
		gen_prepboot();
#endif	/* PREP_BOOT */
#endif	/* APPLE_HYB */

	/*
	 * Do inode/hard link related stuff for non-directory type files.
	 */
	do_inode(root);
	return (0);
}

LOCAL int
dirtree_dump()
{
	if (verbose > 2) {
		dump_tree(root);
	}
	return (0);
}

LOCAL int
dirtree_fixup(starting_extent)
	UInt32_t	starting_extent;
{
	if (use_RockRidge && reloc_dir)
		finish_cl_pl_entries();

	/*
	 * Set the link count for directories to 2 + number of sub-directories.
	 */
	if (use_RockRidge)
		do_dir_nlink(root);
	return (0);
}

LOCAL int
dirtree_size(starting_extent)
	UInt32_t	starting_extent;
{
	assign_directory_addresses(root);
	return (0);
}

LOCAL int
ext_size(starting_extent)
	UInt32_t	starting_extent;
{
	extern int		extension_record_size;
	struct directory_entry *s_entry;

	extension_record_extent = starting_extent;
	s_entry = root->contents;
	set_733((char *) s_entry->rr_attributes + s_entry->rr_attr_size - 24,
		extension_record_extent);
	set_733((char *) s_entry->rr_attributes + s_entry->rr_attr_size - 8,
		extension_record_size);
	last_extent++;
	return (0);
}

LOCAL int
dirtree_write(outfile)
	FILE	*outfile;
{
	generate_iso9660_directories(root, outfile);
	return (0);
}

LOCAL int
dirtree_cleanup(outfile)
	FILE	*outfile;
{
	free_directories(root);
	return (0);
}

LOCAL int
startpad_write(outfile)
	FILE	*outfile;
{
	char	buffer[SECTOR_SIZE];
	int	i;
	int	npad;

	memset(buffer, 0, sizeof (buffer));

	npad = session_start + 16 - last_extent_written;

	for (i = 0; i < npad; i++) {
		xfwrite(buffer, sizeof (buffer), 1, outfile, 0, FALSE);
	}

	last_extent_written += npad;
	return (0);
}

LOCAL int
interpad_write(outfile)
	FILE	*outfile;
{
	char	buffer[SECTOR_SIZE];
	int	i;
	int	npad = 0;

	memset(buffer, 0, sizeof (buffer));

#ifdef	needed
	npad = 16;
#endif
	if ((i = last_extent_written % 16) != 0)
		npad += 16 - i;

	for (i = 0; i < npad; i++) {
		xfwrite(buffer, sizeof (buffer), 1, outfile, 0, FALSE);
	}

	last_extent_written += npad;
	return (0);
}

LOCAL int
endpad_write(outfile)
	FILE	*outfile;
{
	char	buffer[SECTOR_SIZE];
	int	i;

	memset(buffer, 0, sizeof (buffer));

	for (i = 0; i < 150; i++) {
		xfwrite(buffer, sizeof (buffer), 1, outfile, 0, FALSE);
	}

	last_extent_written += 150;
	return (0);
}

#ifdef APPLE_HYB

/*
 *	hfs_get_parms:	get HFS parameters from the command line
 */

LOCAL int
hfs_get_parms(key)
	char	*key;
{
	int	ret = 0;
	char	*p;

	if (hfs_parms == NULL)
		return (ret);

	if ((p = strstr(hfs_parms, key)) != NULL) {
		p += strlen(key) + 1;
		sscanf(p, "%d", &ret);
	}

	return (ret);
}

/*
 *	hfs_file_gen:	set up "fake" HFS volume using the ISO9660 tree
 */
LOCAL void
hfs_file_gen(start_extent)
	UInt32_t	start_extent;
{
	int	Csize;	/* clump size for HFS vol */
	int	loop;
	UInt32_t last_extent_save = last_extent;
	char	*p;

	/* allocate memory for the libhfs/mkisofs extra info */
	hce = (hce_mem *) e_malloc(sizeof (hce_mem));

	hce->error = (char *) e_malloc(1024);

	/* mark as unallocated for use later */
	hce->hfs_ce = hce->hfs_hdr = hce->hfs_map = 0;

	/* reserve space for the label partition - if it is needed */
#ifdef PREP_BOOT
	/* a PReP bootable partition needs the map.. */
	if (gen_pt || use_prep_boot || use_chrp_boot)
#else
	if (gen_pt)
#endif	/* PREP_BOOT */
		hce->hfs_map_size = HFS_MAP_SIZE;
	else
		hce->hfs_map_size = 0;

	/* set the HFS parameter string to upper case */
	if (hfs_parms) {
		for (p = hfs_parms; *p; p++)
			*p = toupper(*p);
	}

	/* set the initial factor to increase Catalog file size */
	if ((hce->ctc_size = hfs_get_parms("CTC")) == 0)
		hce->ctc_size = CTC;

	/* set the max size of the Catalog file */
	if ((hce->max_XTCsize = hfs_get_parms("MAX_XTCSIZE")) == 0)
		hce->max_XTCsize = MAX_XTCSIZE;

	/* set the number of time to try to make an HFS volume */
	if ((loop = hfs_get_parms("CTC_LOOP")) == 0)
		loop = CTC_LOOP;

	/*
	 * "create" the HFS volume (just the header, catalog/extents files) if
	 * there's a problem with the Catalog file being too small, we keep on
	 * increasing the size (up to CTC_LOOP) times and try again.
	 * Unfortunately I don't know enough about the inner workings of HFS,
	 * so I can't workout the size of the Catalog file in advance (and I
	 * don't want to "grow" as is is normally allowed to), therefore, this
	 * approach is a bit over the top as it involves throwing away the
	 * "volume" we have created and trying again ...
	 */
	do {
		hce->error[0] = '\0';

		/* attempt to create the Mac volume */
		Csize = make_mac_volume(root, start_extent);

		/* if we have a problem ... */
		if (Csize < 0) {
			/*
			 * we've made too many attempts, or got some other
			 * error
			 */
			if (loop == 0 || errno != HCE_ERROR) {
				/* HCE_ERROR is not a valid errno value */
				if (errno == HCE_ERROR)
					errno = 0;

				/* exit with the error */
				if (*hce->error)
					fprintf(stderr, "%s\n", hce->error);
				perr(hfs_error);
			} else {
				/* increase Catalog file size factor */
				hce->ctc_size *= CTC;

				/*
				 * reset the initial "last_extent" and try
				 * again
				 */
				last_extent = last_extent_save;
			}
		} else {
			/* everything OK - just carry on ... */
			loop = 0;
		}
	}
	while (loop--);

	hfs_extra = HFS_ROUND_UP(hce->hfs_tot_size) / SECTOR_SIZE;

	last_extent += hfs_extra;

	/* generate the Mac label and HFS partition maps */
	mac_boot.name = hfs_boot_file;

	/*
	 * only generate the partition tables etc. if we are making a bootable
	 * CD - or if the -part option is given
	 */
	if (gen_pt) {
		if (gen_mac_label(&mac_boot)) {
			if (*hce->error)
				fprintf(stderr, "%s\n", hce->error);
			perr(hfs_error);
		}
	}
	/* set Autostart filename if required */
	if (autoname) {
		if (autostart())
			perr("Autostart filename must less than 12 characters");
	}
	/* finished with any HFS type errors */
	free(hce->error);
	hce->error = 0;

	/*
	 * the ISO files need to start on a multiple of the HFS allocation
	 * blocks, so find out how much padding we need
	 */

	/*
	 * take in accout alignment of files wrt HFS volume start - remove any
	 * previous session as well
	 */
	start_extent -= session_start;
	hfs_pad = ROUND_UP(start_extent*SECTOR_SIZE +
			(hce->hfs_hdr_size + hce->hfs_map_size) * HFS_BLOCKSZ,
							Csize) / SECTOR_SIZE;

	hfs_pad -= (start_extent + (hce->hfs_hdr_size + hce->hfs_map_size) /
							HFS_BLK_CONV);

#ifdef PREP_BOOT
	gen_prepboot_label(hce->hfs_map);
#endif	/* PREP_BOOT */

}

#ifdef PREP_BOOT
LOCAL void
gen_prepboot()
{
	/*
	 * we need to allocate the hce struct since hce->hfs_map is used to
	 * generate the fdisk partition map required for PReP booting
	 */
	hce = (hce_mem *) e_malloc(sizeof (hce_mem));

	/* mark as unallocated for use later */
	hce->hfs_ce = hce->hfs_hdr = hce->hfs_map = 0;

	/* reserve space for the label partition - if it is needed */
	hce->hfs_map_size = HFS_MAP_SIZE;

	hce->hfs_map = (unsigned char *) e_malloc(hce->hfs_map_size * HFS_BLOCKSZ);
	gen_prepboot_label(hce->hfs_map);
}

#endif	/* PREP_BOOT */

/*
 *	get_adj_size:	get the ajusted size of the volume with the HFS
 *			allocation block size for each file
 */
EXPORT Ulong
get_adj_size(Csize)
	int	Csize;
{
	struct deferred_write *dw;
	Ulong		size = 0;
	int		count = 0;

	/* loop through all the files finding the new total size */
	for (dw = dw_head; dw; dw = dw->next) {
		size += (ROUND_UP(dw->size, Csize)/HFS_BLOCKSZ);
		count++;
	}

	/*
	 * crude attempt to prevent overflows - HFS can only cope with a
	 * maximum of about 65536 forks (actually less) - this will trap cases
	 * when we have far too many files
	 */

	if (count >= 65536)
		return (-1);
	else
		return (size);
}

/*
 *	adj_size:	adjust the ISO record entries for all files
 *			based on the HFS allocation block size
 */
EXPORT int
adj_size(Csize, start_extent, extra)
	int	Csize;
	UInt32_t	start_extent;
	int	extra;
{
	struct deferred_write *dw;
	struct directory_entry *s_entry;
	int		size;

	/* get the adjusted start_extent (with padding) */
	/* take in accout alignment of files wrt HFS volume start */

	start_extent -= session_start;

	start_extent = ROUND_UP(start_extent*SECTOR_SIZE + extra*HFS_BLOCKSZ,
						Csize) / SECTOR_SIZE;

	start_extent -= (extra / HFS_BLK_CONV);

	start_extent += session_start;

	/* initialise file hash */
	flush_hash();

	/*
	 * loop through all files changing their starting blocks and finding
	 * any padding needed to written out latter
	 */
	for (dw = dw_head; dw; dw = dw->next) {
		s_entry = dw->s_entry;
		s_entry->starting_block = dw->extent = start_extent;
		set_733((char *) s_entry->isorec.extent, start_extent);
		size = ROUND_UP(dw->size, Csize) / SECTOR_SIZE;
		dw->pad = size - ISO_ROUND_UP(dw->size) / SECTOR_SIZE;

		/*
		 * cache non-HFS files - as there may be multiple links to
		 * these files (HFS files can't have multiple links). We will
		 * need to change the starting extent of the other links later
		 */
		if (!s_entry->hfs_ent)
			add_hash(s_entry);

		start_extent += size;
	}

	return (start_extent);
}

/*
 *	adj_size_other:	adjust any non-HFS files that may be linked
 *			to an existing file (i.e. not have a deferred_write
 *			entry of it's own
 */
EXPORT void
adj_size_other(dpnt)
	struct directory	*dpnt;
{
	struct directory_entry *s_entry;
	struct file_hash *s_hash;

	while (dpnt) {
		s_entry = dpnt->contents;
		for (s_entry = dpnt->contents; s_entry;
						s_entry = s_entry->next) {
			/*
			 * if it's an HFS file or a directory - then ignore
			 * (we're after non-HFS files)
			 */
			if (s_entry->hfs_ent ||
			    (s_entry->isorec.flags[0] & ISO_DIRECTORY))
				continue;

			/*
			 * find any cached entry and assign new starting
			 * extent
			 */
			s_hash = find_hash(s_entry->dev, s_entry->inode);
			if (s_hash) {
				set_733((char *) s_entry->isorec.extent,
						s_hash->starting_block);
				/* not vital - but tidy */
				s_entry->starting_block =
							s_hash->starting_block;
			}
		}
		if (dpnt->subdir) {
			adj_size_other(dpnt->subdir);
		}
		dpnt = dpnt->next;
	}

	/* clear file hash */
	flush_hash();
}

/*
 *	hfs_hce_write:	write out the HFS header stuff
 */
LOCAL int
hfs_hce_write(outfile)
	FILE	*outfile;
{
	char	buffer[SECTOR_SIZE];
	int	n = 0;
	int	r;	/* HFS hdr output */
	int	tot_size = hce->hfs_map_size + hce->hfs_hdr_size;

	memset(buffer, 0, sizeof (buffer));

	/*
	 * hack time ... if the tot_size is greater than 32Kb then
	 * it won't fit in the first 16 blank SECTORS (64 512 byte
	 * blocks, as most of this is padding, we just truncate this
	 * data to 64x4xHFS_BLOCKSZ ... hope this is OK ...
	 */

	if (tot_size > 64) tot_size = 64;

	/* get size in CD blocks == 4xHFS_BLOCKSZ == 2048 */
	n = tot_size / HFS_BLK_CONV;
	r = tot_size % HFS_BLK_CONV;

	/* write out HFS volume header info */
	xfwrite(hce->hfs_map, HFS_BLOCKSZ, tot_size, outfile, 0, FALSE);

	/* fill up to a complete CD block */
	if (r) {
		xfwrite(buffer, HFS_BLOCKSZ, HFS_BLK_CONV - r, outfile, 0, FALSE);
		n++;
	}
	last_extent_written += n;
	return (0);
}

/*
 *	insert_padding_file : insert a dumy file to make volume at least
 *				800k
 *
 *	XXX If we ever need to write more then 2 GB, make size off_t
 */
EXPORT int
insert_padding_file(size)
	int	size;
{
	struct deferred_write *dwpnt;

	/* get the size in bytes */
	size *= HFS_BLOCKSZ;

	dwpnt = (struct deferred_write *)
		e_malloc(sizeof (struct deferred_write));
	dwpnt->s_entry = 0;
	/* set the padding to zero */
	dwpnt->pad = 0;
	/* set offset to zero */
	dwpnt->off = (off_t)0;
	dwpnt->dw_flags = 0;
#ifdef	APPLE_HYB
	dwpnt->hfstype = TYPE_NONE;
#endif

	/*
	 * don't need to wory about the s_entry stuff as it won't be touched#
	 * at this point onwards
	 */

	/* insert the entry in the list */
	if (dw_tail) {
		dw_tail->next = dwpnt;
		dw_tail = dwpnt;
	} else {
		dw_head = dwpnt;
		dw_tail = dwpnt;
	}

	/* aloocate memory as a "Table" file */
	dwpnt->table = e_malloc(size);
	dwpnt->name = NULL;

	dwpnt->next = NULL;
	dwpnt->size = size;
	dwpnt->extent = last_extent;
	last_extent += ISO_BLOCKS(size);

	/* retune the size in HFS blocks */
	return (ISO_ROUND_UP(size) / HFS_BLOCKSZ);
}

struct output_fragment hfs_desc		= {NULL, NULL, NULL, hfs_hce_write, "HFS volume header"};

#endif	/* APPLE_HYB */

struct output_fragment startpad_desc	= {NULL, startpad_size,	NULL,	  startpad_write, "Initial Padblock"};
struct output_fragment voldesc_desc	= {NULL, oneblock_size,	root_gen, pvd_write,	  "Primary Volume Descriptor"};
struct output_fragment xvoldesc_desc	= {NULL, oneblock_size,	NULL,	  xpvd_write,	  "Enhanced Volume Descriptor"};
struct output_fragment end_vol		= {NULL, oneblock_size,	NULL,	  evd_write,	  "End Volume Descriptor" };
struct output_fragment version_desc	= {NULL, oneblock_size,	NULL,	  vers_write,	  "Version block" };
struct output_fragment pathtable_desc	= {NULL, pathtab_size,	generate_path_tables, pathtab_write, "Path table"};
struct output_fragment dirtree_desc	= {NULL, dirtree_size,	NULL,	  dirtree_write,  "Directory tree" };
struct output_fragment dirtree_clean	= {NULL, dirtree_fixup,	dirtree_dump, dirtree_cleanup, "Directory tree cleanup" };
struct output_fragment extension_desc	= {NULL, ext_size,	NULL,	  exten_write,	  "Extension record" };
struct output_fragment files_desc	= {NULL, NULL,		file_gen, file_write,	  "The File(s)"};
struct output_fragment interpad_desc	= {NULL, interpad_size,	NULL,	  interpad_write, "Intermediate Padblock"};
struct output_fragment endpad_desc	= {NULL, endpad_size,	NULL,	  endpad_write,	  "Ending Padblock"};
