/* @(#)write.c	1.44 00/06/05 joerg */
#ifndef lint
static	char sccsid[] =
	"@(#)write.c	1.44 00/06/05 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,2000 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 "config.h"
#include <strdefs.h>
#include <stdxlib.h>
#include "mkisofs.h"
#include "iso9660.h"
#ifdef SORTING
#include "match.h"
#endif /* SORTING */
#include <time.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <unixstd.h>

#ifdef	USE_LIBSCHILY
#include <standard.h>
#include <schily.h>
#endif

#ifdef __SVR4
extern char    *strdup	__PR((const char *));
#endif

#ifdef VMS
extern char    *strdup	__PR((const char *));
#endif


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

/* Counters for statistics */

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

static int	is_rr_dir = 0;

struct output_fragment *out_tail;
struct output_fragment *out_list;

struct iso_primary_descriptor vol_desc;

	void	set_721		__PR((char *pnt, unsigned int i));
	void	set_722		__PR((char *pnt, unsigned int i));
	void	set_723		__PR((char *pnt, unsigned int i));
	void	set_731		__PR((char *pnt, unsigned int i));
	void	set_732		__PR((char *pnt, unsigned int i));
	void	set_733		__PR((char *pnt, unsigned int i));
	int	get_731		__PR((char *p));
	int	get_732		__PR((char *p));
	int	get_733		__PR((char *p));
	void	xfwrite		__PR((void *buffer, int count, int size,
						FILE *file));
static	int	assign_directory_addresses __PR((struct directory *node));
#ifdef APPLE_HYB
static	void	write_one_file	__PR((char *filename, unsigned int size,
					FILE *outfile, unsigned int off));
#else
static	void	write_one_file	__PR((char *filename, unsigned int size,
					FILE *outfile));
#endif
static	void	write_files	__PR((FILE *outfile));
#if 0
static	void	dump_filelist	__PR((void));
#endif
static	int	compare_dirs	__PR((const void *rr, const void *ll));
	int	sort_directory	__PR((struct directory_entry **sort_dir,
						int rr));
static	int	root_gen	__PR((void));
static	void	assign_file_addresses __PR((struct directory *dpnt));
static	void	free_one_directory  __PR((struct directory *dpnt));
static	void	free_directories __PR((struct directory *dpnt));
	void	generate_one_directory __PR((struct directory *dpnt,
						FILE *outfile));
static	void	build_pathlist	__PR((struct directory *node));
static	int	compare_paths	__PR((void const *r, void const *l));
static	int	generate_path_tables __PR((void));
	void	memcpy_max	__PR((char *to, char *from, int max));
	void	outputlist_insert __PR((struct output_fragment *frag));
static	int	file_write	__PR((FILE *outfile));
static	int	pvd_write	__PR((FILE *outfile));
static	int	evd_write	__PR((FILE *outfile));
static	int	vers_write	__PR((FILE *outfile));
static	int	pathtab_write	__PR((FILE *outfile));
static	int	exten_write	__PR((FILE *outfile));
	int	oneblock_size	__PR((int starting_extent));
static	int	pathtab_size	__PR((int starting_extent));
static	int	padblock_size	__PR((int starting_extent));
static	int	padend_size	__PR((int starting_extent));
static	int	file_gen	__PR((void));
static	int	dirtree_dump	__PR((void));
static	int	dirtree_fixup	__PR((int starting_extent));
static	int	dirtree_size	__PR((int starting_extent));
static	int	ext_size	__PR((int starting_extent));
static	int	dirtree_write	__PR((FILE *outfile));
static	int	dirtree_cleanup	__PR((FILE *outfile));
static	int	padblock_write	__PR((FILE *outfile));
static	int	padend_write	__PR((FILE *outfile));
#ifdef APPLE_HYB
static	int	hfs_pad;
static	void	hfs_file_gen	__PR((int start_extent));
static	void	gen_prepboot	__PR((void));
	int	get_adj_size	__PR((int Csize));
	int	adj_size	__PR((int Csize, int start_extent, int extra));
	void	adj_size_other	__PR((struct directory *dpnt));
static	int	hfs_hce_write	__PR((FILE * outfile));
	int	insert_padding_file __PR((int size));
#endif	/* APPLE_HYB */

#ifdef SORTING
static	int	compare_sort	__PR((const void * rr, const void * ll));
static	void	reassign_link_addresses	__PR((struct directory * dpnt));
static	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))

void
set_721(pnt, i)
	char		*pnt;
	unsigned int	i;
{
	pnt[0] = i & 0xff;
	pnt[1] = (i >> 8) & 0xff;
}

void
set_722(pnt, i)
	char		*pnt;
	unsigned int	i;
{
	pnt[0] = (i >> 8) & 0xff;
	pnt[1] = i & 0xff;
}

void
set_723(pnt, i)
	char		*pnt;
	unsigned int	i;
{
	pnt[3] = pnt[0] = i & 0xff;
	pnt[2] = pnt[1] = (i >> 8) & 0xff;
}

void
set_731(pnt, i)
	char		*pnt;
	unsigned int	i;
{
	pnt[0] = i & 0xff;
	pnt[1] = (i >> 8) & 0xff;
	pnt[2] = (i >> 16) & 0xff;
	pnt[3] = (i >> 24) & 0xff;
}

void
set_732(pnt, i)
	char		*pnt;
	unsigned int	i;
{
	pnt[3] = i & 0xff;
	pnt[2] = (i >> 8) & 0xff;
	pnt[1] = (i >> 16) & 0xff;
	pnt[0] = (i >> 24) & 0xff;
}

void
set_733(pnt, i)
	char		*pnt;
	unsigned int	i;
{
	pnt[7] = pnt[0] = i & 0xff;
	pnt[6] = pnt[1] = (i >> 8) & 0xff;
	pnt[5] = pnt[2] = (i >> 16) & 0xff;
	pnt[4] = pnt[3] = (i >> 24) & 0xff;
}

int
get_731(p)
	char	*p;
{
	return ((p[0] & 0xff)
		| ((p[1] & 0xff) << 8)
		| ((p[2] & 0xff) << 16)
		| ((p[3] & 0xff) << 24));
}

int
get_732(p)
	char	*p;
{
	return ((p[3] & 0xff)
		| ((p[2] & 0xff) << 8)
		| ((p[1] & 0xff) << 16)
		| ((p[0] & 0xff) << 24));
}

int
get_733(p)
	char	*p;
{
	return ((p[0] & 0xff)
		| ((p[1] & 0xff) << 8)
		| ((p[2] & 0xff) << 16)
		| ((p[3] & 0xff) << 24));
}

void
xfwrite(buffer, count, size, file)
	void	*buffer;
	int	count;
	int	size;
	FILE	*file;
{
	/*
	 * 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;

	if (split_output != 0 &&
		(idx == 0 || ftell(file) >= (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) {
#ifdef	USE_LIBSCHILY
			comerr("Cannot open '%s'.\n", nbuf);
#else
			fprintf(stderr, "Cannot open '%s'.\n", nbuf);
			exit(1);
#endif
		}
	}
	while (count) {
		int		got = fwrite(buffer, size, count, file);

		if (got <= 0) {
#ifdef	USE_LIBSCHILY
			comerr("cannot fwrite %d*%d\n", size, count);
#else
			fprintf(stderr, "cannot fwrite %d*%d\n", size, count);
			exit(1);
#endif
		}
		count -= got, *(char **) &buffer += size * got;
	}
}

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

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

unsigned int	last_extent_written = 0;
static int	path_table_index;
static 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
 */
static 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 = (dpnt->size + (SECTOR_SIZE - 1)) >> 11;

			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_ROUND_UP(dpnt->ce_bytes) >> 11;
			}
		}
		if (dpnt->subdir) {
			assign_directory_addresses(dpnt->subdir);
		}
		dpnt = dpnt->next;
	}
	return 0;
}

#ifdef APPLE_HYB
static void
write_one_file(filename, size, outfile, off)
	char		*filename;
	unsigned int	size;
	FILE		*outfile;
	unsigned int	off;
#else
static void
write_one_file(filename, size, outfile)
	char		*filename;
	unsigned int	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;
	int		remain;
	int	use;


	if ((infile = fopen(filename, "rb")) == NULL) {
#ifdef	USE_LIBSCHILY
		comerr("cannot open '%s'\n", filename);
#else
#if defined(sun) || defined(_AUX_SOURCE)
		fprintf(stderr, "cannot open %s: (%d)\n",
				filename, errno);
#else
		fprintf(stderr, "cannot open %s: %s\n",
				filename, strerror(errno));
#endif
		exit(1);
#endif
	}
#ifdef APPLE_HYB
	fseek(infile, off, SEEK_SET);
#endif	/* APPLE_HYB */
	remain = size;

	while (remain > 0) {
		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);
		if (fread(buffer, 1, use, infile) == 0) {
#ifdef	USE_LIBSCHILY
			comerr("cannot read from %s\n", filename);
#else
			fprintf(stderr, "cannot read from %s\n", filename);
			exit(1);
#endif
		}
		xfwrite(buffer, 1, use, outfile);
		last_extent_written += use / SECTOR_SIZE;
#if 0
		if ((last_extent_written % 1000) < use / SECTOR_SIZE) {
			fprintf(stderr, "%d..", last_extent_written);
		}
#else
		if ((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;
	}
	fclose(infile);
}/* write_one_file(... */

static void
write_files(outfile)
	FILE	*outfile;
{
	struct deferred_write	*dwpnt,
				*dwnext;

	dwpnt = dw_head;
	while (dwpnt) {
		if (dwpnt->table) {
			xfwrite(dwpnt->table, 1, ISO_ROUND_UP(dwpnt->size),
								outfile);
			last_extent_written +=
				ISO_ROUND_UP(dwpnt->size) / SECTOR_SIZE;
			table_size += dwpnt->size;
/*			fprintf(stderr,"Size %d ", dwpnt->size); */
			free(dwpnt->table);
			dwpnt->table = NULL;
		} else {

#ifdef VMS
			vms_write_one_file(dwpnt->name, dwpnt->size, outfile);
#else
#ifdef APPLE_HYB
			write_one_file(dwpnt->name, dwpnt->size, outfile,
								dwpnt->off);
#else
			write_one_file(dwpnt->name, dwpnt->size, outfile);
#endif	/* APPLE_HYB */
#endif
			free(dwpnt->name);
			dwpnt->name = NULL;
		}

#ifdef APPLE_HYB
		if (apple_hyb) {
			/*
			 * we may have to pad out ISO files to work with HFS
			 * clump sizes
			 */
			char	blk[SECTOR_SIZE];
			int	i;

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

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

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

#if 0
static 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

static 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) {
#ifdef	USE_LIBSCHILY
		errmsgno(EX_BAD,
			"Error: %s and %s have the same ISO9660 name\n",
			(*r)->whole_name, (*l)->whole_name);
#else
		fprintf(stderr,
			"Error: %s and %s have the same ISO9660 name\n",
			(*r)->whole_name, (*l)->whole_name);
#endif
		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))) {
#ifdef	USE_LIBSCHILY
			errmsgno(EX_BAD,
			"Error: %s and %s have the same Rock Ridge name\n",
				(*r)->whole_name, (*l)->whole_name);
#else
			fprintf(stderr,
			"Error: %s and %s have the same Rock Ridge name\n",
				(*r)->whole_name, (*l)->whole_name);
#endif
			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.
 */
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) {
#ifdef	USE_LIBSCHILY
		errmsgno(EX_BAD,
			"Directory size too small (. or .. missing ???)\n");
#else
		fprintf(stderr,
			"Directory size too small (. or .. missing ???)\n");
#endif
		sort_goof = 1;

	} else {
		/* only sort the non-hidden entries */
		sort_goof = 0;
		is_rr_dir = rr;
#ifdef __STDC__
		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;
}

static 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
 */
static 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;

	return (r_sort < l_sort ? 1 : -1);
}

/*
 *	reassign start extents to files that are "hard links" to
 *	files that may have been sorted
 */
static 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
 */
static int
sort_file_addresses()
{
	struct deferred_write	*dwpnt;
	struct deferred_write	**sortlist;
	struct directory_entry	*s_entry;
	int			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 __STDC__
	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_ROUND_UP(s_entry->size) >> 11;
		/* cache start extents for any linked files */
		add_hash(s_entry);
	}

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


static void
assign_file_addresses(dpnt)
	struct directory	*dpnt;
{
	struct directory *finddir;
	struct directory_entry *s_entry;
	struct file_hash *s_hash;
	struct deferred_write *dwpnt;
	char		whole_path[1024];

	while (dpnt) {
		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 (isonum_733((unsigned char *)
						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	USE_LIBSCHILY
						comerrno(EX_BAD,
							"Fatal goof - could not find dir entry for '%s'\n",
							s_entry->name);
#else
						fprintf(stderr,
							"Fatal goof - could not find dir entry for '%s'\n",
							s_entry->name);
						exit(1);
#endif
					}
				}
				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;
#ifdef APPLE_HYB
				/*
				 * maybe an offset to start of the real
				 * file/fork
				 */
				dwpnt->off = s_entry->hfs_off;
#else
				dwpnt->off = 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;
					strcpy(whole_path,
							s_entry->whole_name);
					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_ROUND_UP(s_entry->size) >> 11;
				if (verbose > 2) {
					fprintf(stderr, "%d %d %s\n",
						s_entry->starting_block,
						last_extent - 1, whole_path);
				}
#ifdef DBG_ISO
				if ((ISO_ROUND_UP(s_entry->size) >> 11) >
									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 %d extents\n",
						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 %d extents\n",
								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) {
			assign_file_addresses(dpnt->subdir);
		}
		dpnt = dpnt->next;
	}
}/* assign_file_addresses(... */

static 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(... */

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

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 = (dpnt->size + (SECTOR_SIZE - 1)) & ~(SECTOR_SIZE - 1);
	directory_buffer = (char *) e_malloc(total_size);
	memset(directory_buffer, 0, total_size);
	dir_index = 0;

	ce_size = (dpnt->ce_bytes + (SECTOR_SIZE - 1)) & ~(SECTOR_SIZE - 1);
	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 = (dir_index + (SECTOR_SIZE - 1)) &
				~(SECTOR_SIZE - 1);
		}
		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) {
				unsigned char	*pnt;
				int		len,
						nbytes;

				/*
				 * Go through the entire record and 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;
				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) {
#ifdef	USE_LIBSCHILY
		errmsgno(EX_BAD,
			"Unexpected directory length %d expected: %d '%s'\n",
			dpnt->size,
			dir_index, dpnt->de_name);
#else
		fprintf(stderr,
			"Unexpected directory length %d expected: %d '%s'\n",
			dpnt->size,
			dir_index, dpnt->de_name);
#endif
	}
	xfwrite(directory_buffer, 1, total_size, outfile);
	last_extent_written += total_size >> 11;
	free(directory_buffer);
	directory_buffer = NULL;

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

static 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(... */

static 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(... */

static 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
	 */
	if (next_path_index > 0xffff) {
#ifdef	USE_LIBSCHILY
		comerrno(EX_BAD,
		"Unable to generate sane path tables - too many directories (%d)\n",
			next_path_index);
#else
		fprintf(stderr,
		"Unable to generate sane path tables - too many directories (%d)\n",
			next_path_index);
		exit(1);
#endif
	}
	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 __STDC__
		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) {
#ifdef	USE_LIBSCHILY
			comerrno(EX_BAD, "Entry %d not in path tables\n", j);
#else
			fprintf(stderr, "Entry %d not in path tables\n", j);
			exit(1);
#endif
		}
		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) {
#ifdef	USE_LIBSCHILY
			comerrno(EX_BAD,
			"Fatal ISO9660 goof - directory has amnesia\n");
#else
			fprintf(stderr,
			"Fatal ISO9660 goof - directory has amnesia\n");
			exit(1);
#endif
		}
		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;

		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) {
#ifdef	USE_LIBSCHILY
		errmsgno(EX_BAD,
			"Path table lengths do not match %d expected: %d\n",
			path_table_index,
			path_table_size);
#else
		fprintf(stderr,
			"Path table lengths do not match %d expected: %d\n",
			path_table_index,
			path_table_size);
#endif
	}
	return 0;
}/* generate_path_tables(... */

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(... */

void
outputlist_insert(frag)
	struct output_fragment *frag;
{
	if (out_tail == NULL) {
		out_list = out_tail = frag;
	} else {
		out_tail->of_next = frag;
		out_tail = frag;
	}
}

static int
file_write(outfile)
	FILE	*outfile;
{
	int	should_write;

#ifdef APPLE_HYB
	char	buffer[SECTOR_SIZE];

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

	if (apple_hyb) {

		int	i;

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

		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 = %d\n",
							last_extent);
#endif

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

			fprintf(stderr,
				"Total extents scheduled to be written = %d\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) {

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

		/* round up to a whole CD block */
		if (HFS_ROUND_UP(hce->hfs_tot_size) -
					hce->hfs_tot_size * HFS_BLOCKSZ) {
			xfwrite(buffer, 1,
				HFS_ROUND_UP(hce->hfs_tot_size) -
				hce->hfs_tot_size * HFS_BLOCKSZ, outfile);
		}
		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);
	}
#endif	/* APPLE_HYB */

	/* The rest is just fluff. */
	if (verbose == 0) {
		return 0;
	}
#ifdef APPLE_HYB
	if (apple_hyb) {
		fprintf(stderr,
			"Total extents actually written (inc HFS) = %d\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.
 */
static int
pvd_write(outfile)
	FILE	*outfile;
{
	char		iso_time[17];
	int		should_write;
	struct tm	local;
	struct tm	gmt;


	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;
	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 not a bootable cd do it the old way */
	xfwrite(&vol_desc, 1, SECTOR_SIZE, outfile);
	last_extent_written++;
	return 0;
}

/*
 * Function to write the EVD for the disc.
 */
static 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, 1, SECTOR_SIZE, outfile);
	last_extent_written += 1;
	return 0;
}

/*
 * Function to write the version information for the disc.
 */
static int
vers_write(outfile)
	FILE	*outfile;
{
	char		vers[SECTOR_SIZE];
	int		X_ac;
	char		**X_av;
	char		*cp;
	int		i;
	int		idx = 4;
	int		len;
	extern char	version_string[];

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

	cp = vers;
	X_ac = saved_ac();
	X_av = saved_av();
	strcpy(&cp[idx], ctime(&begun));
	idx += 25;
	strcpy(&cp[idx], version_string);
	idx += strlen(version_string);
	for (i = 1; i < X_ac; i++) {
		len = strlen(X_av[i]);
		if ((idx + len + 1) >= SECTOR_SIZE)
			break;
		cp[idx++] = ' ';
		strcpy(&cp[idx], X_av[i]);
		idx += len;
	}

	cp[SECTOR_SIZE - 1] = '\0';
	xfwrite(vers, 1, SECTOR_SIZE, outfile);
	last_extent_written += 1;
	return 0;
}

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

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

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

/*
 * Functions to describe path table size.
 */
static int
pathtab_size(starting_extent)
	int	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.
 */
static int
padblock_size(starting_extent)
	int	starting_extent;
{
	last_extent = session_start + 16;
	return 0;
}

/*
 * Functions to describe padding blocks at end of disk.
 */
static int
padend_size(starting_extent)
	int	starting_extent;
{
	int	emod = 0;

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

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

#endif	/* APPLE_HYB */

	assign_file_addresses(root);

#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)
		hfs_file_gen(start_extent);
#ifdef PREP_BOOT
	else if (use_prep_boot)
		gen_prepboot();
#endif	/* PREP_BOOT */
#endif	/* APPLE_HYB */

	return 0;
}

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

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

	if (use_RockRidge)
		update_nlink_field(root);
	return 0;
}

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

static int
ext_size(starting_extent)
	int	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;
}

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

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

static int
padblock_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, 1, sizeof(buffer), outfile);
	}

	last_extent_written += npad;
	return 0;
}

static int
padend_write(outfile)
	FILE	*outfile;
{
	char	buffer[SECTOR_SIZE];
	int	i;
	int	npad;

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

	npad = 16;
	if ((i = last_extent_written % 16) != 0)
		npad += 16 - i;

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

	last_extent_written += npad;
	return 0;
}

#ifdef APPLE_HYB

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

	/* 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)
#else
	if (gen_pt)
#endif	/* PREP_BOOT */
		hce->hfs_map_size = HFS_MAP_SIZE;
	else
		hce->hfs_map_size = 0;

	/* set the intial factor to increase Catalog file size */
	hce->ctc_size = CTC;

	/*
	 * "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
static 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
 */
int
get_adj_size(Csize)
	int	Csize;
{
	struct deferred_write *dw;
	int		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);
		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
 */
int
adj_size(Csize, start_extent, extra)
	int	Csize;
	int	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
 */
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
 */
static 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));


	/* 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, tot_size, HFS_BLOCKSZ, outfile);

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

/*
 *	insert_padding_file : insert a dumy file to make volume at least
 *				800k
 */
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 = 0;

	/*
	 * 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_ROUND_UP(size) >> 11;

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

struct output_fragment hfs_desc = {NULL, NULL, NULL, hfs_hce_write};

#endif	/* APPLE_HYB */

struct output_fragment padblock_desc  = {NULL, padblock_size, NULL,     padblock_write};
struct output_fragment voldesc_desc   = {NULL, oneblock_size, root_gen, pvd_write};
struct output_fragment end_vol	      = {NULL, oneblock_size, NULL,     evd_write};
struct output_fragment version_desc   = {NULL, oneblock_size, NULL,     vers_write};
struct output_fragment pathtable_desc = {NULL, pathtab_size,  generate_path_tables,     pathtab_write};
struct output_fragment dirtree_desc   = {NULL, dirtree_size,  NULL,     dirtree_write};
struct output_fragment dirtree_clean  = {NULL, dirtree_fixup, dirtree_dump,     dirtree_cleanup};
struct output_fragment extension_desc = {NULL, ext_size,      NULL,     exten_write};
struct output_fragment files_desc     = {NULL, NULL,          file_gen, file_write};
struct output_fragment padend_desc    = {NULL, padend_size,   NULL,     padend_write};
