/* @(#)tree.c	1.134 15/12/15 joerg */
#include <schily/mconfig.h>
#ifndef lint
static	UConst char sccsid[] =
	"@(#)tree.c	1.134 15/12/15 joerg";
#endif
/*
 * File tree.c - scan directory  tree and build memory structures for iso9660
 * filesystem
 *
 * Written by Eric Youngdale (1993).
 *
 * Copyright 1993 Yggdrasil Computing, Incorporated
 * Copyright (c) 1999,2000-2015 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.
 */
/* ADD_FILES changes made by Ross Biro biro@yggdrasil.com 2/23/95 */

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

#include "mkisofs.h"
#include "rock.h"
#include "match.h"
#include <schily/time.h>
#include <schily/errno.h>
#include <schily/fcntl.h>
#include <schily/device.h>
#include <schily/schily.h>

#ifdef UDF
#include "udf.h"
#endif

#ifdef VMS
#include <sys/file.h>
#include <vms/fabdef.h>
#include "vms.h"
#endif

LOCAL	Uchar	symlink_buff[PATH_MAX+1];

LOCAL	char	*filetype		__PR((int t));
LOCAL	char	*rstr			__PR((char *s1, char *s2));
LOCAL	void	stat_fix		__PR((struct stat *st));
EXPORT	int	stat_filter		__PR((char *path, struct stat *st));
EXPORT	int	lstat_filter		__PR((char *path, struct stat *st));
LOCAL	int	sort_n_finish		__PR((struct directory *this_dir));
LOCAL	void	generate_reloc_directory __PR((void));
EXPORT	void	attach_dot_entries	__PR((struct directory *dirnode,
						struct stat *this_stat,
						struct stat *parent_stat));
EXPORT	char	*find_rr_attribute	__PR((unsigned char *pnt, int len,
						char *attr_type));
EXPORT	void	finish_cl_pl_entries	__PR((void));
LOCAL	void	dir_nesting_warn	__PR((struct directory *this_dir,
						char *path, int contflag));
EXPORT	int	scan_directory_tree	__PR((struct directory *this_dir,
						char *path,
						struct directory_entry *de));
LOCAL	struct directory_entry *
		dup_relocated_dir	__PR((struct directory *this_dir,
					    struct directory_entry *s_entry,
					    char *whole_path,
					    char *short_name,
					    struct stat *statp));
EXPORT	int	insert_file_entry	__PR((struct directory *this_dir,
						char *whole_path,
						char *short_name,
						struct stat *statp, int have_rsrc));
EXPORT	struct directory_entry *
		dup_directory_entry	__PR((struct directory_entry *s_entry));
EXPORT	void	generate_iso9660_directories __PR((struct directory *node,
						FILE *outfile));

LOCAL	void	set_de_path		__PR((struct directory *parent,
						struct directory *this));

EXPORT	struct directory *
		find_or_create_directory __PR((struct directory *parent,
						char *path,
						struct directory_entry *de,
						int flag));
LOCAL	void	delete_directory	__PR((struct directory *parent,
						struct directory *child));
EXPORT	int	sort_tree		__PR((struct directory *node));
EXPORT	void	dump_tree		__PR((struct directory *node));
EXPORT	struct directory_entry *
		search_tree_file	__PR((struct directory *node,
						char *filename));
EXPORT	void	init_fstatbuf		__PR((void));

extern int	verbose;
struct stat	fstatbuf;		/* We use this for the artificial */
					/* entries we create		  */
struct stat	root_statbuf;		/* Stat buffer for root directory */
struct directory *reloc_dir;

LOCAL char *
filetype(t)
	int	t;
{
	static	char	unkn[32];

	if (S_ISFIFO(t))		/* 1 */
		return ("fifo");
	if (S_ISCHR(t))			/* 2 */
		return ("chr");
	if (S_ISMPC(t))			/* 3 */
		return ("multiplexed chr");
	if (S_ISDIR(t))			/* 4 */
		return ("dir");
	if (S_ISNAM(t))			/* 5 */
		return ("named file");
	if (S_ISBLK(t))			/* 6 */
		return ("blk");
	if (S_ISMPB(t))			/* 7 */
		return ("multiplexed blk");
	if (S_ISREG(t))			/* 8 */
		return ("regular file");
	if (S_ISCTG(t))			/* 9 */
		return ("contiguous file");
	if (S_ISLNK(t))			/* 10 */
		return ("symlink");
	if (S_ISSHAD(t))		/* 11 */
		return ("Solaris shadow inode");
	if (S_ISSOCK(t))		/* 12 */
		return ("socket");
	if (S_ISDOOR(t))		/* 13 */
		return ("door");
	if (S_ISWHT(t))			/* 14 */
		return ("whiteout");
	if (S_ISEVC(t))			/* 15 */
		return ("event count");

	/*
	 * Needs to be last in case somebody makes this
	 * a supported file type.
	 */
	if ((t & S_IFMT) == 0)		/* 0 (unallocated) */
		return ("unallocated");

	sprintf(unkn, "octal '%o'", t & S_IFMT);
	return (unkn);
}

/*
 * Check if s1 ends in strings s2
 */
LOCAL char *
rstr(s1, s2)
	char	*s1;
	char	*s2;
{
	int	l1;
	int	l2;

	l1 = strlen(s1);
	l2 = strlen(s2);
	if (l2 > l1)
		return ((char *)NULL);

	if (strcmp(&s1[l1 - l2], s2) == 0)
		return (&s1[l1 - l2]);
	return ((char *)NULL);
}

LOCAL void
stat_fix(st)
	struct stat	*st;
{
	int adjust_modes = 0;

	if (S_ISREG(st->st_mode))
		adjust_modes = rationalize_filemode;
	else if (S_ISDIR(st->st_mode))
		adjust_modes = rationalize_dirmode;
	else
		adjust_modes = (rationalize_filemode || rationalize_dirmode);

	/*
	 * If rationalizing, override the uid and gid, since the
	 * originals will only be useful on the author's system.
	 */
	if (rationalize_uid)
		st->st_uid = uid_to_use;
	if (rationalize_gid)
		st->st_gid = gid_to_use;

	if (adjust_modes) {

		if (S_ISREG(st->st_mode) && (filemode_to_use != 0)) {
			st->st_mode = filemode_to_use | S_IFREG;
		} else if (S_ISDIR(st->st_mode) && (dirmode_to_use != 0)) {
			st->st_mode = dirmode_to_use | S_IFDIR;
		} else {
			/*
			 * Make sure the file modes make sense.  Turn
			 * on all read bits.  Turn on all exec/search
			 * bits if any exec/search bit is set.  Turn
			 * off all write bits, and all special mode
			 * bits (on a r/o fs lock bits are useless,
			 * and with uid+gid 0 don't want set-id bits,
			 * either).
			 */

			st->st_mode |= 0444;
#if !defined(_WIN32) && !defined(__DJGPP__)	/* make all file "executable" */
			if (st->st_mode & 0111)
#endif
				st->st_mode |= 0111;
			st->st_mode &= ~07222;
		}
	}
}

EXPORT int
stat_filter(path, st)
	char		*path;
	struct stat	*st;
{
	int	result = stat(path, st);

	if (result >= 0 && rationalize)
		stat_fix(st);
	return (result);
}

EXPORT int
lstat_filter(path, st)
	char		*path;
	struct stat	*st;
{
	int	result = lstat(path, st);

	if (result >= 0 && rationalize)
		stat_fix(st);
	return (result);
}

LOCAL int
sort_n_finish(this_dir)
	struct directory	*this_dir;
{
	struct directory_entry *s_entry;
	struct directory_entry *s_entry1;
	struct directory_entry *table;
	int		count;
	int		d1;
	int		d2;
	int		d3;
	register int	new_reclen;
	char		*c;
	int		status = 0;
	int		tablesize = 0;
	char		newname[MAX_ISONAME+1];
	char		rootname[MAX_ISONAME+1];
	char		extname[MAX_ISONAME+1];

	/*
	 * Here we can take the opportunity to toss duplicate entries from the
	 * directory.
	 */
	/* ignore if it's hidden */
	if (this_dir->dir_flags & INHIBIT_ISO9660_ENTRY) {
		return (0);
	}
	table = NULL;

	init_fstatbuf();

	/*
	 * If we had artificially created this directory, then we might be
	 * missing the required '.' entries.  Create these now if we need
	 * them.
	 */
	if ((this_dir->dir_flags & (DIR_HAS_DOT | DIR_HAS_DOTDOT)) !=
		(DIR_HAS_DOT | DIR_HAS_DOTDOT)) {
		attach_dot_entries(this_dir, NULL, NULL);
	}
	flush_file_hash();
	s_entry = this_dir->contents;
	while (s_entry) {
#ifdef	USE_LARGEFILES
		/*
		 * Skip all but the last extent from a multi extent file,
		 * we like them all have the same name.
		 */
		if ((s_entry->de_flags & MULTI_EXTENT) &&
		    (s_entry->isorec.flags[0] & ISO_MULTIEXTENT)) {
			s_entry = s_entry->next;
			continue;
		}
#endif
		/* ignore if it's hidden */
		if (s_entry->de_flags & INHIBIT_ISO9660_ENTRY) {
			s_entry = s_entry->next;
			continue;
		}
		/*
		 * First assume no conflict, and handle this case
		 */
		if (!(s_entry1 = find_file_hash(s_entry->isorec.name))) {
			add_file_hash(s_entry);
			s_entry = s_entry->next;
			continue;
		}
#ifdef APPLE_HYB
		/*
		 * if the pair are associated, then skip (as they have the
		 * same name!)
		 */
		if (apple_both && s_entry1->assoc &&
						s_entry1->assoc == s_entry) {
			s_entry = s_entry->next;
			continue;
		}
#endif	/* APPLE_HYB */

		if (s_entry1 == s_entry) {
			comerrno(EX_BAD,
			_("Fatal goof, file '%s' already in hash table.\n"),
			s_entry->isorec.name);
		}
		/*
		 * OK, handle the conflicts.  Try substitute names until we
		 * come up with a winner
		 */
		strlcpy(rootname, s_entry->isorec.name, sizeof (rootname));
		/*
		 * Strip off the non-significant part of the name so that we
		 * are left with a sensible root filename.  If we don't find
		 * a '.', then try a ';'.
		 */
		c = strchr(rootname, '.');
		/*
		 * In case we ever allow more than on dot, only modify the
		 * section past the last dot if the file name starts with a
		 * dot.
		 */
		if (c != NULL && c == rootname && c != strrchr(rootname, '.')) {
			c = strrchr(rootname, '.');
		}
		extname[0] = '\0';		/* In case we have no ext.  */
		if (c) {
			strlcpy(extname, c, sizeof (extname));
			*c = 0;			/* Cut off complete ext.    */
		} else {
			/*
			 * Could not find any '.'.
			 */
			c = strchr(rootname, ';');
			if (c) {
				*c = 0;		/* Cut off version number    */
			}
		}
		c = strchr(extname, ';');
		if (c) {
			*c = 0;			/* Cut off version number    */
		}
		d1 = strlen(rootname);
		if (full_iso9660_filenames || iso9660_level > 1) {
			d2 = strlen(extname);
			/*
			 * 31/37 chars minus the 3 characters we are
			 * appending below to create unique filenames.
			 */
			if ((d1 + d2) > (iso9660_namelen - 3))
				rootname[iso9660_namelen - 3 - d2] = 0;
		} else {
			if (d1 > 5)
				rootname[5] = 0;
		}
		new_reclen = strlen(rootname);
		sprintf(newname, "%s000%s%s",
				rootname,
				extname,
				((s_entry->isorec.flags[0] & ISO_DIRECTORY) ||
				omit_version_number ? "" : ";1"));

		for (d1 = 0; d1 < 36; d1++) {
			for (d2 = 0; d2 < 36; d2++) {
				for (d3 = 0; d3 < 36; d3++) {
					newname[new_reclen + 0] =
					    (d1 <= 9 ? '0' + d1 : 'A' + d1 - 10);
					newname[new_reclen + 1] =
					    (d2 <= 9 ? '0' + d2 : 'A' + d2 - 10);
					newname[new_reclen + 2] =
					    (d3 <= 9 ? '0' + d3 : 'A' + d3 - 10);
					if (debug)
						error(_("NEW name '%s'\n"), newname);

#ifdef VMS
					/* Sigh.  VAXCRTL seems to be broken here */
					{
						int	ijk = 0;

						while (newname[ijk]) {
							if (newname[ijk] == ' ')
								newname[ijk] = '0';
							ijk++;
						}
					}
#endif

					if (!find_file_hash(newname))
						goto got_valid_name;
				}
			}
		}

		/*
		 * If we fell off the bottom here, we were in real trouble.
		 */
		comerrno(EX_BAD,
			_("Unable to generate unique name for file %s\n"),
			s_entry->name);

got_valid_name:
		/*
		 * OK, now we have a good replacement name.  Now decide which
		 * one of these two beasts should get the name changed
		 */
		if (s_entry->priority < s_entry1->priority) {
			if (verbose > 0) {
				fprintf(stderr, _("Using %s for  %s%s%s (%s)\n"),
					newname,
					this_dir->whole_name, SPATH_SEPARATOR,
					s_entry->name, s_entry1->name);
			}

			s_entry->isorec.name_len[0] = strlen(newname);
			new_reclen = offsetof(struct iso_directory_record,
				name[0]) +
				strlen(newname);
			if (use_XA || use_RockRidge) {
				if (new_reclen & 1)
					new_reclen++; /* Pad to an even byte */
				new_reclen += s_entry->rr_attr_size;
			}
			if (new_reclen & 1)
				new_reclen++;	/* Pad to an even byte */
			s_entry->isorec.length[0] = new_reclen;
			strcpy(s_entry->isorec.name, newname);
#ifdef	USE_LARGEFILES
			if (s_entry->de_flags & MULTI_EXTENT) {
				struct directory_entry	*s_e;

				/*
				 * Copy over the new name to all other entries
				 */
				for (s_e = s_entry->mxroot;
				    s_e && s_e->mxroot == s_entry->mxroot;
							s_e = s_e->next) {
					s_e->isorec.length[0] = new_reclen;
					s_e->isorec.name_len[0] = s_entry->isorec.name_len[0];
					strcpy(s_e->isorec.name, newname);
				}
			}
#endif
#ifdef APPLE_HYB
			/*
			 * Has resource fork - needs new name
			 */
			if (apple_both && s_entry->assoc) {
				struct directory_entry *s_entry2 =
								s_entry->assoc;

				/*
				 * resource fork name *should* be the same as
				 * the data fork
				 */
				s_entry2->isorec.name_len[0] =
						s_entry->isorec.name_len[0];
				strcpy(s_entry2->isorec.name,
						s_entry->isorec.name);
				s_entry2->isorec.length[0] = new_reclen;
			}
#endif	/* APPLE_HYB */
		} else {
			delete_file_hash(s_entry1);
			if (verbose > 0) {
				fprintf(stderr, _("Using %s for  %s%s%s (%s)\n"),
					newname,
					this_dir->whole_name, SPATH_SEPARATOR,
					s_entry1->name, s_entry->name);
			}
			s_entry1->isorec.name_len[0] = strlen(newname);
			new_reclen = offsetof(struct iso_directory_record,
					name[0]) +
					strlen(newname);
			if (use_XA || use_RockRidge) {
				if (new_reclen & 1)
					new_reclen++; /* Pad to an even byte */
				new_reclen += s_entry1->rr_attr_size;
			}
			if (new_reclen & 1)
				new_reclen++;	/* Pad to an even byte */
			s_entry1->isorec.length[0] = new_reclen;
			strcpy(s_entry1->isorec.name, newname);
#ifdef	USE_LARGEFILES
			if (s_entry1->de_flags & MULTI_EXTENT) {
				struct directory_entry	*s_e;

				/*
				 * Copy over the new name to all other entries
				 */
				for (s_e = s_entry1->mxroot;
				    s_e && s_e->mxroot == s_entry1->mxroot;
							s_e = s_e->next) {
					s_e->isorec.length[0] = new_reclen;
					s_e->isorec.name_len[0] = s_entry1->isorec.name_len[0];
					strcpy(s_e->isorec.name, newname);
				}
			}
#endif
			add_file_hash(s_entry1);
#ifdef APPLE_HYB
			/*
			 * Has resource fork - needs new name
			 */
			if (apple_both && s_entry1->assoc) {
				struct directory_entry *s_entry2 =
							s_entry1->assoc;

				/*
				 * resource fork name *should* be the same as
				 * the data fork
				 */
				s_entry2->isorec.name_len[0] =
						s_entry1->isorec.name_len[0];
				strcpy(s_entry2->isorec.name,
							s_entry1->isorec.name);
				s_entry2->isorec.length[0] = new_reclen;
			}
#endif	/* APPLE_HYB */
		}
		add_file_hash(s_entry);
		s_entry = s_entry->next;
	}

	if (generate_tables &&
	    !find_file_hash(trans_tbl) &&
	    (reloc_dir != this_dir) &&
	    (this_dir->extent == 0)) {
		/*
		 * First we need to figure out how big this table is
		 */
		for (s_entry = this_dir->contents; s_entry;
						s_entry = s_entry->next) {
			if (strcmp(s_entry->name, ".") == 0 ||
				strcmp(s_entry->name, "..") == 0)
				continue;
#ifdef APPLE_HYB
			/*
			 * Skip table entry for the resource fork
			 */
			if (apple_both &&
			    (s_entry->isorec.flags[0] & ISO_ASSOCIATED))
				continue;
#endif	/* APPLE_HYB */
			if (s_entry->de_flags & INHIBIT_ISO9660_ENTRY)
				continue;
			if (s_entry->table) {
				/*
				 * Max namelen, a space before and a space
				 * after the iso filename.
				 */
				tablesize += MAX_ISONAME + 2 +
						strlen(s_entry->table);
			}
		}
	}
	if (tablesize > 0) {
		table = (struct directory_entry *)
			e_malloc(sizeof (struct directory_entry));
		memset(table, 0, sizeof (struct directory_entry));
		table->table = NULL;
		table->next = this_dir->contents;
		this_dir->contents = table;

		table->filedir = root;
		table->isorec.flags[0] = ISO_FILE;
		table->priority = 32768;
		iso9660_date(table->isorec.date, fstatbuf.st_mtime);
		table->inode = TABLE_INODE;
		table->dev = UNCACHED_DEVICE;
		set_723(table->isorec.volume_sequence_number,
						volume_sequence_number);
		set_733((char *)table->isorec.size, tablesize);
		table->size = tablesize;
		table->filedir = this_dir;
		if (jhide_trans_tbl)
			table->de_flags |= INHIBIT_JOLIET_ENTRY;
		/*
		 * Always hide transtable from UDF tree.
		 */
		table->de_flags |= INHIBIT_UDF_ENTRY;
/*		table->name = e_strdup("<translation table>");*/
		table->name = e_strdup(trans_tbl);
		/*
		 * We use sprintf() to create the strings, for this reason
		 * we need to add one byte for the null character at the
		 * end of the string even though we don't use it.
		 */
		table->table = (char *)e_malloc(ISO_ROUND_UP(tablesize)+1);
		memset(table->table, 0, ISO_ROUND_UP(tablesize)+1);
		iso9660_file_length(trans_tbl, table, 0);

		if (use_XA || use_RockRidge) {
			fstatbuf.st_mode = 0444 | S_IFREG;
			fstatbuf.st_nlink = 1;
			generate_xa_rr_attributes("",
				trans_tbl, table,
				&fstatbuf, &fstatbuf, 0);
		}
	}
	/*
	 * We have now chosen the 8.3 names and we should now know the length
	 * of every entry in the directory.
	 */
	for (s_entry = this_dir->contents; s_entry; s_entry = s_entry->next) {
		/* skip if it's hidden */
		if (s_entry->de_flags & INHIBIT_ISO9660_ENTRY) {
			continue;
		}
		new_reclen = strlen(s_entry->isorec.name);

		/*
		 * First update the path table sizes for directories.
		 */
		if (s_entry->isorec.flags[0] & ISO_DIRECTORY) {
			if (strcmp(s_entry->name, ".") != 0 &&
					strcmp(s_entry->name, "..") != 0) {
				path_table_size += new_reclen +
						offsetof(struct iso_path_table,
						name[0]);
				if (new_reclen & 1)
					path_table_size++;
			} else {
				new_reclen = 1;
				if (this_dir == root && strlen(s_entry->name)
									== 1) {
					path_table_size += new_reclen +
						offsetof(struct iso_path_table,
						name[0]);
				}
			}
		}
		if (path_table_size & 1)
			path_table_size++;	/* For odd lengths we pad */
		s_entry->isorec.name_len[0] = new_reclen;

		new_reclen += offsetof(struct iso_directory_record, name[0]);

		if (new_reclen & 1)
			new_reclen++;

		new_reclen += s_entry->rr_attr_size;

		if (new_reclen & 1)
			new_reclen++;

		if (new_reclen > 0xff) {
			comerrno(EX_BAD,
				_("Fatal error - RR overflow (reclen %d) for file %s\n"),
				new_reclen,
				s_entry->name);
		}
		s_entry->isorec.length[0] = new_reclen;
	}

	status = sort_directory(&this_dir->contents, (reloc_dir == this_dir));
	if (status > 0) {
		errmsgno(EX_BAD, _("Unable to sort directory %s\n"),
			this_dir->whole_name);
		errmsgno(EX_BAD,
		_("If there was more than one directory type argument to mkisofs\n"));
		comerrno(EX_BAD,
		_("use -graft-points to create different target directory names.\n"));
	}
	/*
	 * If we are filling out a TRANS.TBL, generate the entries that will
	 * go in the thing.
	 */
	if (table) {
		count = 0;
		for (s_entry = this_dir->contents; s_entry;
						s_entry = s_entry->next) {
			if (s_entry == table)
				continue;
			if (!s_entry->table)
				continue;
			if (strcmp(s_entry->name, ".") == 0 ||
				strcmp(s_entry->name, "..") == 0)
				continue;
#ifdef APPLE_HYB
			/*
			 * Skip table entry for the resource fork
			 */
			if (apple_both &&
			    (s_entry->isorec.flags[0] & ISO_ASSOCIATED))
				continue;
#endif	/* APPLE_HYB */
			if (s_entry->de_flags & INHIBIT_ISO9660_ENTRY)
				continue;
			/*
			 * Warning: we cannot use the return value of sprintf
			 * because old BSD based sprintf() implementations
			 * will return a pointer to the result instead of a
			 * count.
			 * Old mkiofs introduced a space after the iso
			 * filename to make parsing TRANS.TBL easier.
			 */
			sprintf(table->table + count, "%c %-*s%s",
				s_entry->table[0],
				MAX_ISONAME + 1,
				s_entry->isorec.name, s_entry->table + 1);
			count += strlen(table->table + count);
			free(s_entry->table);
			/*
			 * for a memory file, set s_entry->table to the
			 * correct data - which is stored in
			 * s_entry->whole_name
			 */
			if (s_entry->de_flags & MEMORY_FILE) {
				s_entry->table = s_entry->whole_name;
				s_entry->whole_name = NULL;
			} else {
				s_entry->table = NULL;
			}
		}

		if (count != tablesize) {
			comerrno(EX_BAD,
				_("Translation table size mismatch %d %d\n"),
				count, tablesize);
		}
	}
	/*
	 * Now go through the directory and figure out how large this one will
	 * be. Do not split a directory entry across a sector boundary
	 */
	s_entry = this_dir->contents;
	this_dir->ce_bytes = 0;
	while (s_entry) {
		/* skip if it's hidden */
		if (s_entry->de_flags & INHIBIT_ISO9660_ENTRY) {
			s_entry = s_entry->next;
			continue;
		}
		new_reclen = s_entry->isorec.length[0];
		if ((this_dir->size & (SECTOR_SIZE - 1)) + new_reclen
								>= SECTOR_SIZE)

			this_dir->size = (this_dir->size + (SECTOR_SIZE - 1)) &
				~(SECTOR_SIZE - 1);
		this_dir->size += new_reclen;

		/*
		 * See if continuation entries were used on disc
		 */
		if (use_RockRidge &&
			s_entry->rr_attr_size != s_entry->total_rr_attr_size) {
			unsigned char	*pnt;
			int		len;
			int		nbytes;

			pnt = s_entry->rr_attributes;
			len = s_entry->total_rr_attr_size;
			pnt = parse_xa(pnt, &len, 0);
/*			pnt = parse_xa(pnt, &len, s_entry);*/

			/*
			 * We make sure that each continuation entry record is
			 * not split across sectors, but each file could in
			 * theory have more than one CE, so we scan through
			 * and figure out what we need.
			 */
			while (len > 3) {
				if (pnt[0] == 'C' && pnt[1] == 'E') {
					nbytes = get_733((char *)pnt + 20);

					if ((this_dir->ce_bytes & (SECTOR_SIZE - 1)) + nbytes >=
						SECTOR_SIZE)
						this_dir->ce_bytes =
							ISO_ROUND_UP(this_dir->ce_bytes);
					/*
					 * Now store the block in the
					 * ce buffer
					 */
					this_dir->ce_bytes += nbytes;
					if (this_dir->ce_bytes & 1)
						this_dir->ce_bytes++;
				}
				len -= pnt[2];
				pnt += pnt[2];
			}
		}
		s_entry = s_entry->next;
	}
	return (status);
}

LOCAL void
generate_reloc_directory()
{
	time_t		current_time;
	struct directory_entry *s_entry;

	/*
	 * Create an  entry for our internal tree
	 */
	time(&current_time);
	reloc_dir = (struct directory *)
		e_malloc(sizeof (struct directory));
	memset(reloc_dir, 0, sizeof (struct directory));
	reloc_dir->parent = root;
	reloc_dir->next = root->subdir;
	root->subdir = reloc_dir;
	reloc_dir->depth = 1;
	if (hide_rr_moved) {
		reloc_dir->whole_name = e_strdup("./.rr_moved");
		reloc_dir->de_name = e_strdup(".rr_moved");
	} else {
		reloc_dir->whole_name = e_strdup("./rr_moved");
		reloc_dir->de_name = e_strdup("rr_moved");
	}
	reloc_dir->de_path = reloc_dir->de_name;
	reloc_dir->extent = 0;


	/*
	 * Now create an actual directory entry
	 */
	s_entry = (struct directory_entry *)
		e_malloc(sizeof (struct directory_entry));
	memset(s_entry, 0, sizeof (struct directory_entry));
	s_entry->next = root->contents;
	reloc_dir->self = s_entry;

	/*
	 * The rr_moved entry will not appear in the Joliet nor the UDF tree.
	 */
	reloc_dir->dir_flags |= INHIBIT_JOLIET_ENTRY;
	s_entry->de_flags |= INHIBIT_JOLIET_ENTRY;

	reloc_dir->dir_flags |= INHIBIT_UDF_ENTRY;
	s_entry->de_flags |= INHIBIT_UDF_ENTRY;

	root->contents = s_entry;
	root->contents->name = e_strdup(reloc_dir->de_name);
	root->contents->filedir = root;
	root->contents->isorec.flags[0] = ISO_DIRECTORY;
	root->contents->priority = 32768;
	iso9660_date(root->contents->isorec.date, current_time);
	root->contents->inode = UNCACHED_INODE;
	root->contents->dev = UNCACHED_DEVICE;
	set_723(root->contents->isorec.volume_sequence_number,
						volume_sequence_number);
	iso9660_file_length(reloc_dir->de_name, root->contents, 1);

	init_fstatbuf();

	if (use_XA || use_RockRidge) {
		fstatbuf.st_mode = 0555 | S_IFDIR;
		fstatbuf.st_nlink = 2;
		generate_xa_rr_attributes("",
			hide_rr_moved ? ".rr_moved" : "rr_moved",
			s_entry, &fstatbuf, &fstatbuf, NEED_RE);
	};

	/*
	 * Now create the . and .. entries in rr_moved
	 * Now create an actual directory entry
	 */
	if (root_statbuf.st_nlink == 0)
		root_statbuf = fstatbuf;
	attach_dot_entries(reloc_dir, NULL, &root_statbuf);
}

/*
 * Function:		attach_dot_entries
 *
 * Purpose:		Create . and .. entries for a new directory.
 *
 * Notes:		Only used for artificial directories that
 *			we are creating.
 */
EXPORT void
attach_dot_entries(dirnode, this_stat, parent_stat)
	struct directory	*dirnode;
	struct stat		*this_stat;
	struct stat		*parent_stat;
{
	struct directory_entry *s_entry;
	struct directory_entry *orig_contents;
	int		deep_flag = 0;

	init_fstatbuf();
	fstatbuf.st_mode = new_dir_mode | S_IFDIR;
	fstatbuf.st_nlink = 2;
	if (parent_stat == NULL)
		parent_stat = &fstatbuf;
	if (this_stat == NULL)
		this_stat = &fstatbuf;

	orig_contents = dirnode->contents;

	if ((dirnode->dir_flags & DIR_HAS_DOTDOT) == 0) {
		if (dirnode->parent &&
		    dirnode->parent == reloc_dir) {
			deep_flag = NEED_PL;
		}
		s_entry = (struct directory_entry *)
			e_malloc(sizeof (struct directory_entry));
		memcpy(s_entry, dirnode->self,
			sizeof (struct directory_entry));
#ifdef	APPLE_HYB
		if (dirnode->self->hfs_ent) {
			s_entry->hfs_ent = (hfsdirent *)
				e_malloc(sizeof (hfsdirent));
			memcpy(s_entry->hfs_ent, dirnode->self->hfs_ent,
				sizeof (hfsdirent));
		}
#endif
		s_entry->name = e_strdup("..");
		s_entry->whole_name = NULL;
		s_entry->isorec.name_len[0] = 1;
		s_entry->isorec.flags[0] = ISO_DIRECTORY;
		iso9660_file_length("..", s_entry, 1);
		iso9660_date(s_entry->isorec.date, parent_stat->st_mtime);
		set_723(s_entry->isorec.volume_sequence_number,
						volume_sequence_number);
		set_733(s_entry->isorec.size, SECTOR_SIZE);
		memset(s_entry->isorec.extent, 0, 8);
		s_entry->filedir = dirnode->parent;

		dirnode->contents = s_entry;
		dirnode->contents->next = orig_contents;
		orig_contents = s_entry;

		if (use_XA || use_RockRidge) {
			generate_xa_rr_attributes("",
				"..", s_entry,
				parent_stat,
				parent_stat, deep_flag);
		}
		dirnode->dir_flags |= DIR_HAS_DOTDOT;
	}
	deep_flag = 0;
	if ((dirnode->dir_flags & DIR_HAS_DOT) == 0) {
		s_entry = (struct directory_entry *)
			e_malloc(sizeof (struct directory_entry));
		memcpy(s_entry, dirnode->self,
			sizeof (struct directory_entry));
#ifdef	APPLE_HYB
		if (dirnode->self->hfs_ent) {
			s_entry->hfs_ent = (hfsdirent *)
				e_malloc(sizeof (hfsdirent));
			memcpy(s_entry->hfs_ent, dirnode->self->hfs_ent,
				sizeof (hfsdirent));
		}
#endif
		s_entry->name = e_strdup(".");
		s_entry->whole_name = NULL;
		s_entry->isorec.name_len[0] = 1;
		s_entry->isorec.flags[0] = ISO_DIRECTORY;
		iso9660_file_length(".", s_entry, 1);
		iso9660_date(s_entry->isorec.date, this_stat->st_mtime);
		set_723(s_entry->isorec.volume_sequence_number,
						volume_sequence_number);
		set_733(s_entry->isorec.size, SECTOR_SIZE);
		memset(s_entry->isorec.extent, 0, 8);
		s_entry->filedir = dirnode;

		dirnode->contents = s_entry;
		dirnode->contents->next = orig_contents;

		if (use_XA || use_RockRidge) {
			if (dirnode == root) {
				deep_flag |= NEED_CE | NEED_SP;	/* For extension record */
			}
			generate_xa_rr_attributes("",
				".", s_entry,
				this_stat, this_stat, deep_flag);
		}
		dirnode->dir_flags |= DIR_HAS_DOT;
	}
}

EXPORT char *
find_rr_attribute(pnt, len, attr_type)
	unsigned char	*pnt;
	int		len;
	char		*attr_type;
{
	pnt = parse_xa(pnt, &len, 0);
	while (len >= 4) {
		if (pnt[3] != 1 && pnt[3] != 2) {
			errmsgno(EX_BAD,
				_("**BAD RRVERSION (%d) in '%c%c' field (%2.2X %2.2X).\n"),
				pnt[3], pnt[0], pnt[1], pnt[0], pnt[1]);
		}
		if (pnt[2] < 4) {
			errmsgno(EX_BAD,
				_("**BAD RRLEN (%d) in '%2.2s' field %2.2X %2.2X.\n"),
				pnt[2], pnt, pnt[0], pnt[1]);
			return (NULL);
		}
		if (strncmp((char *)pnt, attr_type, 2) == 0)
			return ((char *)pnt);
		else if (strncmp((char *)pnt, "ST", 2) == 0)
			return (NULL);
		len -= pnt[2];
		pnt += pnt[2];
	}
	return (NULL);
}

EXPORT void
finish_cl_pl_entries()
{
	struct directory_entry	*s_entry;
	struct directory_entry	*s_entry1;
	struct directory	*d_entry;

	/*
	 * If the reloc_dir is hidden (empty), then return
	 */
	if (reloc_dir->dir_flags & INHIBIT_ISO9660_ENTRY)
		return;

	s_entry = reloc_dir->contents;
	s_entry = s_entry->next->next;	/* Skip past . and .. */
	for (; s_entry; s_entry = s_entry->next) {
		/* skip if it's hidden */
		if (s_entry->de_flags & INHIBIT_ISO9660_ENTRY) {
			continue;
		}
		d_entry = reloc_dir->subdir;
		while (d_entry) {
			if (d_entry->self == s_entry)
				break;
			d_entry = d_entry->next;
		};
		if (!d_entry) {
			comerrno(EX_BAD,
					_("Unable to locate directory parent\n"));
		};

		if (s_entry->filedir != NULL && s_entry->parent_rec != NULL) {
			char	*rr_attr;

			/*
			 * First fix the PL pointer in the directory in the
			 * rr_reloc dir
			 */
			s_entry1 = d_entry->contents->next;

/*			set_733((char *) s_entry1->rr_attributes +*/
/*				s_entry1->total_rr_attr_size - 8,*/
/*				s_entry->filedir->extent); */
			/*
			 * The line above won't work when entry was read from
			 * the previous session, because if total_rr_attr_size
			 * was odd when recording previous session, now we have
			 * total_rr_attr_size off by 1 due to padding.
			 *
			 * So, just search for the attributes by name
			 */
			rr_attr = find_rr_attribute(s_entry1->rr_attributes,
				s_entry1->total_rr_attr_size, "PL");
			if (rr_attr != NULL)
				set_733(rr_attr + 4, s_entry->filedir->extent);


			/*
			 * Now fix the CL pointer
			 */
			s_entry1 = s_entry->parent_rec;

/*			set_733((char *) s_entry1->rr_attributes +*/
/*			s_entry1->total_rr_attr_size - 8, d_entry->extent); */
			rr_attr = find_rr_attribute(s_entry1->rr_attributes,
				s_entry1->total_rr_attr_size, "CL");
			if (rr_attr != NULL)
				set_733(rr_attr + 4, d_entry->extent);
		}
		s_entry->filedir = reloc_dir;	/* Now we can fix this */
	}
	/*
	 * We would need to modify the NLINK terms in the assorted root
	 * directory records to account for the presence of the RR_MOVED
	 * directory. We do not do this here because we do it later when
	 * we correct the link count for all direstories in the tree.
	 */


	finish_cl_pl_for_prev_session();
}

LOCAL void
dir_nesting_warn(this_dir, path, contflag)
	struct directory	*this_dir;
	char			*path;
	int			contflag;
{
	static	BOOL	did_hint = FALSE;

	errmsgno(EX_BAD,
		_("Directories too deep for '%s' (%d) max is %d%s.\n"),
		path, this_dir->depth, RR_relocation_depth,
		contflag?_("; ignored - continuing"):"");
	if (!did_hint) {
		did_hint = TRUE;
		errmsgno(EX_BAD, _("To include the complete directory tree,\n"));
		errmsgno(EX_BAD, _("use Rock Ridge extensions via -R or -r,\n"));
		errmsgno(EX_BAD, _("or allow deep ISO9660 directory nesting via -D.\n"));
	}
}

/*
 * Function:		scan_directory_tree
 *
 * Purpose:		Walk through a directory on the local machine
 *			filter those things we don't want to include
 *			and build our representation of a dir.
 *
 * Notes:
 */
EXPORT int
scan_directory_tree(this_dir, path, de)
	struct directory	*this_dir;
	char			*path;
	struct directory_entry	*de;
{
	DIR		*current_dir;
	char		whole_path[2*PATH_MAX];	/* Avoid stat buffer overflow */
	struct dirent	*d_entry;
	struct directory *parent;
	int		dflag;
	char		*old_path;
extern	BOOL		nodesc;

	if (nodesc)
		return (1);

	if (verbose > 1) {
		fprintf(stderr, _("Scanning %s\n"), path);
	}
/*#define	check_needed*/
#ifdef	check_needed
	/*
	 * Trying to use this to avoid directory loops from hard links
	 * or followed symlinks does not work. It would prevent us from
	 * implementing merge directories.
	 */
	if (this_dir->dir_flags & DIR_WAS_SCANNED) {
		fprintf(stderr, _("Already scanned directory %s\n"), path);
		return (1);	/* It's a directory */
	}
#endif
	this_dir->dir_flags |= DIR_WAS_SCANNED;

	errno = 0;	/* Paranoia */
	current_dir = opendir(path);
	d_entry = NULL;

	/*
	 * Apparently NFS sometimes allows you to open the directory, but then
	 * refuses to allow you to read the contents.  Allow for this
	 */
	old_path = path;

	if (current_dir) {
		errno = 0;
		d_entry = readdir(current_dir);
	}

	if (!current_dir || (!d_entry && errno != 0)) {
		int	ret = 1;

		errmsg(_("Unable to open directory %s\n"), path);

		if (errno == ENOTDIR) {
			/*
			 * Mark as not a directory
			 */
			de->isorec.flags[0] &= ~ISO_DIRECTORY;
			ret = 0;
		}
		if (current_dir)
			closedir(current_dir);
		return (ret);
	}

	parent = de->filedir;
	/*
	 * Set up the struct for the current directory, and insert it into
	 * the tree
	 */
#ifdef VMS
	vms_path_fixup(path);
#endif

	/*
	 * if entry for this sub-directory is hidden, then hide this directory
	 */
	if (de->de_flags & INHIBIT_ISO9660_ENTRY)
		this_dir->dir_flags |= INHIBIT_ISO9660_ENTRY;

	if (de->de_flags & INHIBIT_JOLIET_ENTRY)
		this_dir->dir_flags |= INHIBIT_JOLIET_ENTRY;

#ifdef SORTING
	/*
	 * set any sort weighting from it's own directory entry - if a
	 * directory is given a weighting, then all the contents will use
	 * this as the default weighting
	 */
	this_dir->sort = de->sort;
#endif /* SORTING */

	/*
	 * Now we scan the directory itself, and look at what is inside of it.
	 */
	whole_path[0] = '\0';
	dflag = -2;
	while (1 == 1) {
		char	*d_name;

		if (dflag < 0) {
			/*
			 * Some filesystems do not deliver "." and ".." at all,
			 * others (on Linux) deliver them in the wrong order.
			 * Make sure we add "." and ".." before all other
			 * entries.
			 */
			if (dflag < -1)
				d_name = ".";
			else
				d_name = "..";
			dflag++;
		} else {
			/*
			 * The first time through, skip this, since we already
			 * asked for the first entry when we opened the
			 * directory.
			 */
			if (dflag > 0) {
				d_entry = readdir(current_dir);
			} else {
				dflag++;
			}
			if (!d_entry)
				break;
			d_name = d_entry->d_name;
		}

		/*
		 * OK, got a valid entry
		 *
		 * If we do not want all files, then pitch the backups.
		 */
		if (!all_files) {
			if (strchr(d_name, '~') ||
			    strchr(d_name, '#') ||
			    rstr(d_name, ".bak")) {
				if (verbose > 0) {
					fprintf(stderr,
						_("Ignoring file %s\n"),
						d_name);
				}
				continue;
			}
		}
#ifdef APPLE_HYB
		if (apple_both) {
			/*
			 * exclude certain HFS type files/directories for the
			 * time being
			 */
			if (hfs_exclude(d_name))
				continue;
		}
#endif	/* APPLE_HYB */

		if (strlen(path) + strlen(d_name) + 2 > sizeof (whole_path)) {
			errmsgno(EX_BAD, _("Path name %s/%s too long.\n"),
					path, d_name);
			comerrno(EX_BAD, _("Overflow of stat buffer\n"));
		};

		/*
		 * Generate the complete ASCII path for this file
		 */
		strlcpy(whole_path, path, sizeof (whole_path));
#ifndef VMS
		if (whole_path[strlen(whole_path) - 1] != '/')
			strcat(whole_path, "/");
#endif
		strcat(whole_path, d_name);

		/*
		 * Should we exclude this file ?
		 * Do no check "." and ".."
		 */
		if (!(d_name[0] == '.' && (d_name[1] == '\0' ||
		    (d_name[1] == '.' && d_name[2] == '\0'))) &&
		    (matches(d_name) || matches(whole_path))) {
			if (verbose > 1) {
				fprintf(stderr,
					_("Excluded by match: %s\n"), whole_path);
			}
			continue;
		}
		if (generate_tables &&
		    strcmp(d_name, trans_tbl) == 0) {
			/*
			 * Ignore this entry.  We are going to be generating
			 * new versions of these files, and we need to ignore
			 * any originals that we might have found.
			 */
			if (verbose > 1) {
				fprintf(stderr, _("Excluded: %s\n"), whole_path);
			}
			continue;
		}
		/*
		 * If we already have a '.' or a '..' entry, then don't insert
		 * new ones.
		 */
		if (strcmp(d_name, ".") == 0 &&
		    this_dir->dir_flags & DIR_HAS_DOT) {
			continue;
		}
		if (strcmp(d_name, "..") == 0 &&
		    this_dir->dir_flags & DIR_HAS_DOTDOT) {
			continue;
		}
#if 0
		if (verbose > 1)
			fprintf(stderr, "%s\n", whole_path);
#endif
		/*
		 * This actually adds the entry to the directory in question.
		 */
		insert_file_entry(this_dir, whole_path, d_name, NULL, 0);
	}
	closedir(current_dir);

#ifdef APPLE_HYB
	/*
	 * if we cached the HFS info stuff for this directory, then delete it
	 */
	if (this_dir->hfs_info) {
		del_hfs_info(this_dir->hfs_info);
		this_dir->hfs_info = 0;
	}
#endif	/* APPLE_HYB */

	return (1);
}

LOCAL struct directory_entry *
dup_relocated_dir(this_dir, s_entry, whole_path, short_name, statp)
	struct directory	*this_dir;
	struct directory_entry	*s_entry;
	char			*whole_path;
	char			*short_name;
	struct stat		*statp;
{
	struct directory_entry *s_entry1;

	if (!reloc_dir)
		generate_reloc_directory();
	init_fstatbuf();

	/*
	 * Replicate the entry for this directory.  The old one will
	 * stay where it is, and it will be neutered so that it no
	 * longer looks like a directory. The new one will look like
	 * a directory, and it will be put in the reloc_dir.
	 */
	s_entry1 = (struct directory_entry *)
		e_malloc(sizeof (struct directory_entry));
	memcpy(s_entry1, s_entry, sizeof (struct directory_entry));
	s_entry1->table = NULL;
	s_entry1->name = e_strdup(short_name);
	s_entry1->whole_name = e_strdup(whole_path);
	s_entry1->next = reloc_dir->contents;
	reloc_dir->contents = s_entry1;
	s_entry1->priority = 32768;
	s_entry1->parent_rec = this_dir->contents;
	set_723(s_entry1->isorec.volume_sequence_number,
						volume_sequence_number);
	s_entry1->filedir = this_dir;
	iso9660_date(s_entry1->isorec.date, fstatbuf.st_mtime);

	if (use_XA || use_RockRidge) {
		generate_xa_rr_attributes(whole_path,
			short_name, s_entry1,
			statp, statp, NEED_RE);
	}

	statp->st_size = (off_t)0;
	statp->st_mode &= 0777;
	set_733((char *)s_entry->isorec.size, 0);
	s_entry->size = 0;
	s_entry->isorec.flags[0] = ISO_FILE;
	s_entry->inode = UNCACHED_INODE;
	s_entry->dev = UNCACHED_DEVICE;
	s_entry->de_flags |= RELOCATED_DIRECTORY;

	return (s_entry1);
}


/*
 * Function:		insert_file_entry
 *
 * Purpose:		Insert one entry into our directory node.
 *
 * Note:
 * This function inserts a single entry into the directory.  It
 * is assumed that all filtering and decision making regarding what
 * we want to include has already been made, so the purpose of this
 * is to insert one entry (file, link, dir, etc), into this directory.
 * Note that if the entry is a dir (or if we are following links,
 * and the thing it points to is a dir), then we will scan those
 * trees before we return.
 */
EXPORT int
insert_file_entry(this_dir, whole_path, short_name, statp, have_rsrc)
	struct directory	*this_dir;
	char			*whole_path;
	char			*short_name;
	struct stat		*statp;
	int			have_rsrc;
{
	struct stat	statbuf,
			lstatbuf;
	struct directory_entry *s_entry,
			*s_entry1;
	int		lstatus;
	int		status;
	int		deep_flag;
#ifdef	USE_NO_SCANDIR
	int		no_scandir = 0;
#endif

#ifdef APPLE_HYB
	int		x_hfs = 0;
	int		htype = TYPE_NONE;

#endif	/* APPLE_HYB */

	if (statp) {
		status = lstatus = 0;
		lstatbuf = *statp;
		if (S_ISLNK(lstatbuf.st_mode)) {
			status = stat_filter(short_name, &statbuf);
		} else {
			statbuf = *statp;
		}
	} else {
		status = stat_filter(whole_path, &statbuf);

		lstatus = lstat_filter(whole_path, &lstatbuf);
	}

	if ((status == -1) && (lstatus == -1)) {
		/*
		 * This means that the file doesn't exist, or isn't accessible.
		 * Sometimes this is because of NFS permissions problems.
		 */
		errmsg(_("Non-existent or inaccessible: %s\n"), whole_path);
		return (0);
	}
	if (S_ISDIR(statbuf.st_mode) &&
	    (this_dir->depth > RR_relocation_depth) && !use_RockRidge &&
	    strcmp(short_name, ".") != 0 && strcmp(short_name, "..") != 0) {
		dir_nesting_warn(this_dir, whole_path, TRUE);
		return (0);
	}
	if (this_dir == root && strcmp(short_name, ".") == 0)
		root_statbuf = statbuf;	/* Save this for later on */

	/*
	 * We do this to make sure that the root entries are consistent
	 */
	if (this_dir == root && strcmp(short_name, "..") == 0) {
		statbuf = root_statbuf;
		lstatbuf = root_statbuf;
	}
	if (S_ISLNK(lstatbuf.st_mode)) {
		/*
		 * Here we decide how to handle the symbolic links.  Here we
		 * handle the general case - if we are not following links or
		 * there is an error, then we must change something.  If RR
		 * is in use, it is easy, we let RR describe the file.  If
		 * not, then we punt the file.
		 */
		if ((status || !follow_links)) {
#ifdef UDF
			if (use_RockRidge || use_udf) {
#else
			if (use_RockRidge) {
#endif
				status = 0;
				STAT_INODE(statbuf) = UNCACHED_INODE;
				statbuf.st_dev = UNCACHED_DEVICE;
#ifdef UDF
				if (create_udfsymlinks) {
					char symlinkcontents[2048];
					off_t size = sizeof (symlinkcontents);

					if (udf_get_symlinkcontents(whole_path,
					    symlinkcontents, &size) == -1) {
						statbuf.st_size = (off_t)0;
						statbuf.st_mode =
							(statbuf.st_mode & ~S_IFMT) | S_IFREG;
					} else {
						statbuf.st_size = size;
						statbuf.st_mode = lstatbuf.st_mode;
					}
				} else {
#endif
					statbuf.st_size = (off_t)0;
					statbuf.st_mode =
						(statbuf.st_mode & ~S_IFMT) | S_IFREG;
#ifdef UDF
				}
#endif
			} else {
				if (follow_links) {
					/* XXX errno may be wrong! */
					errmsg(_("Unable to stat file %s - ignoring and continuing.\n"),
						whole_path);
				} else {
					errmsgno(EX_BAD,
						_("Symlink %s ignored - continuing.\n"),
						whole_path);
					return (0); /* Non Rock Ridge discs */
						    /* - ignore all symlinks */
				}
			}
		}
		/*
		 * Here we handle a different kind of case.  Here we have a
		 * symlink, but we want to follow symlinks.  If we run across
		 * a directory loop, then we need to pretend that we are not
		 * following symlinks for this file.  If this is the first
		 * time we have seen this, then make this seem as if there was
		 * no symlink there in the first place
		 */
		if (follow_links &&
		    S_ISDIR(statbuf.st_mode)) {
			if (strcmp(short_name, ".") != 0 &&
			    strcmp(short_name, "..") != 0) {
				if (find_directory_hash(statbuf.st_dev,
							STAT_INODE(statbuf))) {
					if (!use_RockRidge) {
						fprintf(stderr,
						_("Already cached directory seen (%s)\n"),
							whole_path);
						return (0);
					}
					lstatbuf = statbuf;
					/*
					 * XXX when this line was active,
					 * XXX mkisofs did not include all
					 * XXX files if it was called with '-f'
					 * XXX (follow symlinks).
					 * XXX Now scan_directory_tree()
					 * XXX checks if the directory has
					 * XXX already been scanned via the
					 * XXX DIR_WAS_SCANNED flag.
					 */
/*					no_scandir = 1;*/
				} else {
					lstatbuf = statbuf;
					add_directory_hash(statbuf.st_dev,
							STAT_INODE(statbuf));
				}
			}
		}
		/*
		 * For non-directories, we just copy the stat information over
		 * so we correctly include this file.
		 */
		if (follow_links &&
		    !S_ISDIR(statbuf.st_mode)) {
			lstatbuf = statbuf;
		}
	}
	/*
	 * Add directories to the cache so that we don't waste space even if
	 * we are supposed to be following symlinks.
	 */
	if (follow_links &&
	    strcmp(short_name, ".") != 0 &&
	    strcmp(short_name, "..") != 0 &&
	    S_ISDIR(statbuf.st_mode)) {
		add_directory_hash(statbuf.st_dev, STAT_INODE(statbuf));
	}
#ifdef VMS
	if (!S_ISDIR(lstatbuf.st_mode) && (statbuf.st_fab_rfm != FAB$C_FIX &&
			statbuf.st_fab_rfm != FAB$C_STMLF)) {
		fprintf(stderr,
		_("Warning - file %s has an unsupported VMS record format (%d)\n"),
			whole_path, statbuf.st_fab_rfm);
	}
#endif

	if (S_ISREG(lstatbuf.st_mode) &&
	    ((statp != NULL && (status = access(short_name, R_OK))) ||
	    (statp == NULL && (status = access(whole_path, R_OK))))) {
		errmsg(_("File %s is not readable - ignoring\n"),
			whole_path);
		return (0);
	}
#ifdef	USE_LARGEFILES
	if (S_ISREG(lstatbuf.st_mode) && (lstatbuf.st_size >= maxnonlarge) &&
	    !do_largefiles) {
#else
	/*
	 * >= is required by the large file summit standard.
	 */
	if (S_ISREG(lstatbuf.st_mode) && (lstatbuf.st_size >= (off_t)0x7FFFFFFF)) {
#endif
#ifdef	EOVERFLOW
		errno = EOVERFLOW;
#else
		errno = EFBIG;
#endif
		errmsg(_("File %s is too large for current mkisofs settings (-iso-level 3 or more required) - ignoring\n"),
			whole_path);
		return (0);
	}
	/*
	 * Add this so that we can detect directory loops with hard links.
	 * If we are set up to follow symlinks, then we skip this checking.
	 */
	if (!follow_links &&
	    S_ISDIR(lstatbuf.st_mode) &&
	    strcmp(short_name, ".") != 0 &&
	    strcmp(short_name, "..") != 0) {
		if (find_directory_hash(statbuf.st_dev, STAT_INODE(statbuf))) {
/*			comerrno(EX_BAD,*/
/*			_("Directory loop - fatal goof (%s %lx %lu).\n"),*/
			errmsgno(EX_BAD,
			_("Warning: Directory loop (%s dev: %lx ino: %lu).\n"),
				whole_path, (unsigned long) statbuf.st_dev,
				(unsigned long) STAT_INODE(statbuf));
		}
		add_directory_hash(statbuf.st_dev, STAT_INODE(statbuf));
	}
	if (!S_ISCHR(lstatbuf.st_mode) && !S_ISBLK(lstatbuf.st_mode) &&
		!S_ISFIFO(lstatbuf.st_mode) && !S_ISSOCK(lstatbuf.st_mode) &&
		!S_ISLNK(lstatbuf.st_mode) && !S_ISREG(lstatbuf.st_mode) &&
		!S_ISDIR(lstatbuf.st_mode)) {
		fprintf(stderr,
		_("Unknown file type (%s) %s - ignoring and continuing.\n"),
			filetype((int)lstatbuf.st_mode), whole_path);
		return (0);
	}
	/*
	 * Who knows what trash this is - ignore and continue
	 */
	if (status) {
		errmsg(_("Unable to stat file %s - ignoring and continuing.\n"),
			whole_path);
		return (0);
	}

#define	is_archive(st)	((st).st_dev == archive_dev && (st).st_ino == archive_ino)
	if (archive_isreg && is_archive(statbuf)) {
		errmsgno(EX_BAD,
			_("'%s' is the archive. Not dumped.\n"), whole_path);
		return (0);
	}

	/*
	 * Check to see if we have already seen this directory node. If so,
	 * then we don't create a new entry for it, but we do want to recurse
	 * beneath it and add any new files we do find.
	 */
	if (S_ISDIR(statbuf.st_mode)) {
		int	dflag;

		for (s_entry = this_dir->contents; s_entry;
						s_entry = s_entry->next) {
			if (strcmp(s_entry->name, short_name) == 0) {
				break;
			}
		}
		if (s_entry != NULL &&
		    strcmp(short_name, ".") != 0 &&
		    strcmp(short_name, "..") != 0) {
			struct directory *child;

			/*
			 * This should not create a new directory
			 */
			if ((s_entry->de_flags & RELOCATED_DIRECTORY) != 0) {
				for (s_entry = reloc_dir->contents; s_entry;
						s_entry = s_entry->next) {
					if (strcmp(s_entry->name, short_name)
									== 0) {
						break;
					}
				}
				child = find_or_create_directory(reloc_dir,
					whole_path,
					s_entry, 1);
			} else {
				child = find_or_create_directory(this_dir,
					whole_path,
					s_entry, 1);
				/*
				 * If unable to scan directory, mark this as a
				 * non-directory
				 */
			}
/*			if (no_scandir)*/
			if (0)
				dflag = 1;
			else
				dflag = scan_directory_tree(child,
							whole_path, s_entry);
			if (!dflag) {
				lstatbuf.st_mode =
					(lstatbuf.st_mode & ~S_IFMT) | S_IFREG;
			}
			return (0);
		}
	}
#ifdef APPLE_HYB
	/*
	 * Should we exclude this HFS file ? - only works with -hfs
	 */
	if (!have_rsrc && apple_hyb && strcmp(short_name, ".") != 0 &&
					strcmp(short_name, "..") != 0) {
		if ((x_hfs = (hfs_matches(short_name) ||
					hfs_matches(whole_path))) == 1) {
			if (verbose > 1) {
				fprintf(stderr, _("Hidden from HFS tree: %s\n"),
							whole_path);
			}
		}
	}
	/*
	 * check we are a file, using Apple extensions and have a .resource
	 * part and not excluded
	 */
	if (S_ISREG(lstatbuf.st_mode) && !have_rsrc && apple_both && !x_hfs) {
		char	rsrc_path[PATH_MAX];	/* rsrc fork filename */

		/*
		 * Construct the resource full path
		 */
		htype = get_hfs_rname(whole_path, short_name, rsrc_path);
		/*
		 * Check we can read the resouce fork
		 */
		if (htype) {
			struct stat	rstatbuf,
					rlstatbuf;

			/*
			 * Some further checks on the file
			 */
			status = stat_filter(rsrc_path, &rstatbuf);

			lstatus = lstat_filter(rsrc_path, &rlstatbuf);

/*			if (!status && !lstatus && S_ISREG(rlstatbuf.st_mode)*/
/*					&& rlstatbuf.st_size > (off_t)0) { */
			if (!status && !lstatus && S_ISREG(rstatbuf.st_mode) &&
					rstatbuf.st_size > (off_t)0) {

				/*
				 * have a resource file - insert it into the
				 * current directory but flag that we have a
				 * resource fork
				 */
				insert_file_entry(this_dir, rsrc_path,
							short_name, NULL, htype);
			}
		}
	}
#endif	/* APPLE_HYB */

	s_entry = (struct directory_entry *)
		e_malloc(sizeof (struct directory_entry));
	/* memset the whole struct, not just the isorec.extent part JCP */
	memset(s_entry, 0, sizeof (struct directory_entry));
	s_entry->next = this_dir->contents;
/*	memset(s_entry->isorec.extent, 0, 8); */
	this_dir->contents = s_entry;
	deep_flag = 0;
	s_entry->table = NULL;

	s_entry->name = e_strdup(short_name);
	s_entry->whole_name = e_strdup(whole_path);

	s_entry->de_flags = 0;
	if (S_ISLNK(lstatbuf.st_mode))
		s_entry->de_flags |= IS_SYMLINK;

	/*
	 * If the current directory is hidden, then hide all it's members
	 * otherwise check if this entry needs to be hidden as well
	 */
	if (this_dir->dir_flags & INHIBIT_ISO9660_ENTRY) {
		s_entry->de_flags |= INHIBIT_ISO9660_ENTRY;
	} else if (strcmp(short_name, ".") != 0 &&
		    strcmp(short_name, "..") != 0) {
		if (i_matches(short_name) || i_matches(whole_path)) {
			if (verbose > 1) {
				fprintf(stderr,
					_("Hidden from ISO9660 tree: %s\n"),
					whole_path);
			}
			s_entry->de_flags |= INHIBIT_ISO9660_ENTRY;
		}
		if (h_matches(short_name) || h_matches(whole_path)) {
			if (verbose > 1) {
				fprintf(stderr,
					_("Hidden ISO9660 attribute: %s\n"),
					whole_path);
			}
			s_entry->de_flags |= HIDDEN_FILE;
		}
	}
	if (this_dir != reloc_dir &&
				this_dir->dir_flags & INHIBIT_JOLIET_ENTRY) {
		s_entry->de_flags |= INHIBIT_JOLIET_ENTRY;
	} else if (strcmp(short_name, ".") != 0 &&
		    strcmp(short_name, "..") != 0) {
		if (j_matches(short_name) || j_matches(whole_path)) {
			if (verbose > 1) {
				fprintf(stderr,
					_("Hidden from Joliet tree: %s\n"),
					whole_path);
			}
			s_entry->de_flags |= INHIBIT_JOLIET_ENTRY;
		}
	}
	if (this_dir != reloc_dir &&
				this_dir->dir_flags & INHIBIT_UDF_ENTRY) {
		s_entry->de_flags |= INHIBIT_UDF_ENTRY;
	} else if (strcmp(short_name, ".") != 0 &&
		    strcmp(short_name, "..") != 0) {
		if (u_matches(short_name) || u_matches(whole_path)) {
			if (verbose > 1) {
				fprintf(stderr,
					_("Hidden from UDF tree: %s\n"),
					whole_path);
			}
			s_entry->de_flags |= INHIBIT_UDF_ENTRY;
		}
	}

#ifdef SORTING
	/*
	 * Inherit any sort weight from parent directory
	 */
	s_entry->sort = this_dir->sort;

#ifdef  DVD_AUD_VID
	/*
	 * No use at all to do a sort if we don't make a dvd video/audio
	 */
	/*
	 * Assign special weights to VIDEO_TS and AUDIO_TS files.
	 * This can't be done with sort_matches for two reasons:
	 * first, we need to match against the destination (DVD)
	 * path rather than the source path, and second, there are
	 * about 2400 different file names to check, each needing
	 * a different priority, and adding that many patterns to
	 * sort_matches would slow things to a crawl.
	 */

	if (dvd_aud_vid_flag) {
		s_entry->sort = assign_dvd_weights(s_entry->name, this_dir, s_entry->sort);
		/*
		 * Turn on sorting if necessary, regardless of cmd-line options
		 */
		if ((s_entry->sort != this_dir->sort) && do_sort == 0)
			do_sort++;
	}
#endif

	/*
	 * See if this entry should have a new weighting
	 */
	if (do_sort && strcmp(short_name, ".") != 0 &&
			strcmp(short_name, "..") != 0) {
		s_entry->sort = sort_matches(whole_path, s_entry->sort);
	}
#endif /* SORTING */

	s_entry->filedir = this_dir;
	s_entry->isorec.flags[0] = ISO_FILE;
	if (s_entry->de_flags & HIDDEN_FILE)
		s_entry->isorec.flags[0] |= ISO_EXISTENCE;
	s_entry->isorec.ext_attr_length[0] = 0;
	iso9660_date(s_entry->isorec.date, statbuf.st_mtime);
	s_entry->isorec.file_unit_size[0] = 0;
	s_entry->isorec.interleave[0] = 0;

#ifdef APPLE_HYB
	if (apple_both && !x_hfs) {
		s_entry->hfs_ent = NULL;
		s_entry->assoc = NULL;
		s_entry->hfs_off = (off_t)0;
		s_entry->hfs_type = htype;
		if (have_rsrc) {
			/* associated (rsrc) file */
			s_entry->isorec.flags[0] |= ISO_ASSOCIATED;
			/* set the type of HFS file */
			s_entry->hfs_type = have_rsrc;
			/*
			 * don't want the rsrc file to be included in any
			 * Joliet/UDF tree
			 */
			s_entry->de_flags |= INHIBIT_JOLIET_ENTRY;
			s_entry->de_flags |= INHIBIT_UDF_ENTRY;
		} else if (s_entry->next) {
			/*
			 * if previous entry is an associated file,
			 * then "link" it to this file i.e. we have a
			 * data/resource pair
			 */
			if (s_entry->next->isorec.flags[0] & ISO_ASSOCIATED) {
				s_entry->assoc = s_entry->next;
				/*
				 * share the same HFS parameters
				 */
				s_entry->hfs_ent = s_entry->next->hfs_ent;
				s_entry->hfs_type = s_entry->next->hfs_type;
			}
		}
		/*
		 * Allocate HFS entry if required
		 */
		if (apple_both && strcmp(short_name, ".") != 0 &&
					strcmp(short_name, "..") != 0) {
			if (!s_entry->hfs_ent) {
				hfsdirent	*hfs_ent;

				hfs_ent =
				(hfsdirent *) e_malloc(sizeof (hfsdirent));

				/*
				 * Sill in the defaults
				 */
				memset(hfs_ent, 0, sizeof (hfsdirent));

				s_entry->hfs_ent = hfs_ent;
			}
			/*
			 * the resource fork is processed first, but the
			 * data fork's time info is used in preference
			 * i.e. time info is set from the resource fork
			 * initially, then it is set from the data fork
			 */
			if (have_rsrc) {
				/*
				 * Set rsrc size
				 */
				s_entry->hfs_ent->u.file.rsize = lstatbuf.st_size;
				/*
				 * This will be overwritten - but might as
				 * well set it here ...
				 */
				s_entry->hfs_ent->crdate = lstatbuf.st_ctime;
				s_entry->hfs_ent->mddate = lstatbuf.st_mtime;
			} else {
				/*
				 * Set data size
				 */
				s_entry->hfs_ent->u.file.dsize = lstatbuf.st_size;
				s_entry->hfs_ent->crdate = lstatbuf.st_ctime;
				s_entry->hfs_ent->mddate = lstatbuf.st_mtime;
			}
		}
	}
#endif	/* APPLE_HYB */

	if (strcmp(short_name, ".") == 0) {
		this_dir->dir_flags |= DIR_HAS_DOT;
	}
	if (strcmp(short_name, "..") == 0) {
		this_dir->dir_flags |= DIR_HAS_DOTDOT;
	}
	if (this_dir->parent &&
	    this_dir->parent == reloc_dir &&
	    strcmp(short_name, "..") == 0) {
		s_entry->inode = UNCACHED_INODE;
		s_entry->dev = UNCACHED_DEVICE;
		deep_flag = NEED_PL;
	} else
#ifdef APPLE_HYB
	if (have_rsrc) {
		/*
		 * don't want rsrc files to be cached
		 */
		s_entry->de_flags |= RESOURCE_FORK;
		s_entry->inode = UNCACHED_INODE;
		s_entry->dev = UNCACHED_DEVICE;
	} else
#endif	/* APPLE_HYB */
	{
		s_entry->inode = STAT_INODE(statbuf);
		s_entry->dev = statbuf.st_dev;
	}
	set_723(s_entry->isorec.volume_sequence_number,
						volume_sequence_number);
	iso9660_file_length(short_name, s_entry, S_ISDIR(statbuf.st_mode));
	s_entry->rr_attr_size = 0;
	s_entry->total_rr_attr_size = 0;
	s_entry->rr_attributes = NULL;

	/*
	 * Directories are assigned sizes later on
	 */
	if (!S_ISDIR(statbuf.st_mode)) {
		if (S_ISCHR(lstatbuf.st_mode) || S_ISBLK(lstatbuf.st_mode) ||
			S_ISFIFO(lstatbuf.st_mode) ||
				S_ISSOCK(lstatbuf.st_mode) ||
#ifdef	UDF
				(S_ISLNK(lstatbuf.st_mode) && !create_udfsymlinks)) {
#else
				FALSE) {
#endif
			s_entry->size = (off_t)0;
			statbuf.st_size = (off_t)0;
		} else {
			s_entry->size = statbuf.st_size;
		}

		set_733((char *)s_entry->isorec.size, statbuf.st_size);
	} else {
		s_entry->isorec.flags[0] |= ISO_DIRECTORY;
	}
#ifdef APPLE_HYB
	/*
	 * If the directory is HFS excluded, then we don't have an hfs_ent
	 */
	if (apple_both && s_entry->hfs_ent &&
				(s_entry->isorec.flags[0] & ISO_DIRECTORY)) {
		/*
		 * Get the Mac directory name
		 */
		get_hfs_dir(whole_path, short_name, s_entry);

		/*
		 * If required, set ISO directory name from HFS name
		 */
		if (use_mac_name)
			iso9660_file_length(s_entry->hfs_ent->name, s_entry, 1);
	}
#endif	/* APPLE_HYB */

	if (strcmp(short_name, ".") != 0 && strcmp(short_name, "..") != 0 &&
		S_ISDIR(statbuf.st_mode) &&
				this_dir->depth > RR_relocation_depth) {
		struct directory *child;

		/*
		 * Replicate the entry for this directory.  The old one will
		 * stay where it is, and it will be neutered so that it no
		 * longer looks like a directory. The new one will look like
		 * a directory, and it will be put in the reloc_dir.
		 */
		s_entry1 = dup_relocated_dir(this_dir, s_entry,
					whole_path, short_name, &statbuf);

		/*
		 * We need to set this temporarily so that the parent to this
		 * is correctly determined.
		 */
		s_entry1->filedir = reloc_dir;
		child = find_or_create_directory(reloc_dir, whole_path,
			s_entry1, 0);
		free(child->de_path);		/* allocated in this case */
		set_de_path(this_dir, child);


/*		if (!no_scandir)*/
		if (!0)
			scan_directory_tree(child, whole_path, s_entry1);
		s_entry1->filedir = this_dir;
		deep_flag = NEED_CL;
	}
	if (generate_tables &&
	    strcmp(s_entry->name, ".") != 0 &&
	    strcmp(s_entry->name, "..") != 0) {

		char	buffer[SECTOR_SIZE];
		int	nchar;

		switch (lstatbuf.st_mode & S_IFMT) {
		case S_IFDIR:
			sprintf(buffer, "D\t%s\n",
				s_entry->name);
			break;

/*
 * extra for WIN32 - if it doesn't have the major/minor defined, then
 * S_IFBLK and S_IFCHR type files are unlikely to exist anyway ...
 * code similar to that in rock.c
 */
#if 0
/*
 * Use the device handling code from <schily/device.h>
 */
#ifndef major
#define	major(dev) (sizeof (dev_t) <= 2 ? ((dev) >> 8) : \
	(sizeof (dev_t) <= 4 ? (((dev) >> 8) >> 8) : \
	(((dev) >> 16) >> 16)))
#define	minor(dev) (sizeof (dev_t) <= 2 ? (dev) & 0xff : \
	(sizeof (dev_t) <= 4 ? (dev) & 0xffff : \
	(dev) & 0xffffffff))
#endif
#endif

#ifdef S_IFBLK
		case S_IFBLK:
			sprintf(buffer, "B\t%s\t%lu %lu\n",
				s_entry->name,
				(unsigned long) major(statbuf.st_rdev),
				(unsigned long) minor(statbuf.st_rdev));
			break;
#endif
#ifdef S_IFIFO
		case S_IFIFO:
			sprintf(buffer, "P\t%s\n",
				s_entry->name);
			break;
#endif
#ifdef S_IFCHR
		case S_IFCHR:
			sprintf(buffer, "C\t%s\t%lu %lu\n",
				s_entry->name,
				(unsigned long) major(statbuf.st_rdev),
				(unsigned long) minor(statbuf.st_rdev));
			break;
#endif
#ifdef S_IFLNK
		case S_IFLNK:
#ifdef	HAVE_READLINK
			nchar = readlink(statp?short_name:whole_path,
				(char *)symlink_buff,
				sizeof (symlink_buff)-1);
			if (nchar < 0) {
				errmsg(_("Cannot read link '%s'.\n"),
					statp?short_name:whole_path);
			}
#else
			nchar = -1;
#endif
			symlink_buff[nchar < 0 ? 0 : nchar] = 0;
			sprintf(buffer, "L\t%s\t%s\n",
				s_entry->name, symlink_buff);
			break;
#endif
#ifdef S_IFSOCK
		case S_IFSOCK:
			sprintf(buffer, "S\t%s\n",
				s_entry->name);
			break;
#endif
		case S_IFREG:
		default:
			sprintf(buffer, "F\t%s\n",
				s_entry->name);
			break;
		};
		s_entry->table = e_strdup(buffer);
	}
	if (S_ISDIR(statbuf.st_mode)) {
		int	dflag;

		if (strcmp(short_name, ".") != 0 &&
		    strcmp(short_name, "..") != 0) {
			struct directory *child;

			child = find_or_create_directory(this_dir, whole_path,
				s_entry, 1);
#ifdef	USE_NO_SCANDIR
			if (no_scandir)
				dflag = 1;
			else
#endif
				dflag = scan_directory_tree(child, whole_path,
								s_entry);

			if (!dflag) {
				lstatbuf.st_mode =
					(lstatbuf.st_mode & ~S_IFMT) | S_IFREG;
				if (child->contents == NULL) {
					delete_directory(this_dir, child);
				}
			}
		}
		/* If unable to scan directory, mark this as a non-directory */
	}
	if (use_RockRidge && this_dir == root &&
	    strcmp(s_entry->name, ".") == 0) {
		deep_flag |= NEED_CE | NEED_SP;	/* For extension record */
	}
	/*
	 * Now figure out how much room this file will take in the directory
	 */

#ifdef APPLE_HYB
	/*
	 * Ff the file is HFS excluded, then we don't have an hfs_ent
	 */
	if (apple_both && !have_rsrc && s_entry->hfs_ent) {
		if (S_ISREG(lstatbuf.st_mode)) { /* it's a regular file */

			/*
			 * Fill in the rest of the HFS entry
			 */
			get_hfs_info(whole_path, short_name, s_entry);

			/*
			 * If required, set ISO directory name from HFS name
			 */
			if (use_mac_name)
				iso9660_file_length(s_entry->hfs_ent->name,
								s_entry, 0);

			/*
			 * Print details about the HFS file
			 */
			if (verbose > 2)
				print_hfs_info(s_entry);

			/*
			 * copy the new ISO9660 name to the rsrc fork
			 * - if it exists
			 */
			if (s_entry->assoc)
				strcpy(s_entry->assoc->isorec.name,
							s_entry->isorec.name);

			/*
			 * we can't handle hard links in the hybrid case, so we
			 * "uncache" the file. The downside to this is that
			 * hard linked files are added to the output image
			 * more than once (we've already done this for rsrc
			 * files)
			 */
			if (apple_hyb && have_rsrc) {
				s_entry->de_flags |= RESOURCE_FORK;
				s_entry->inode = UNCACHED_INODE;
				s_entry->dev = UNCACHED_DEVICE;
			}
		} else if (!(s_entry->isorec.flags[0] & ISO_DIRECTORY)) {
			/* not a directory .. */

			/*
			 * no mac equivalent, so ignore - have to be careful
			 * here, the hfs_ent may be also be for a relocated
			 * directory
			 */
			if (s_entry->hfs_ent &&
			    !(s_entry->de_flags & RELOCATED_DIRECTORY) &&
			    (s_entry->isorec.flags[0] & ISO_MULTIEXTENT) == 0) {
				free(s_entry->hfs_ent);
			}
			s_entry->hfs_ent = NULL;
		}
		/*
		 * if the rsrc size is zero, then we don't need the entry, so
		 * we might as well delete it - this will only happen if we
		 * didn't know the rsrc size from the rsrc file size
		 */
		if (s_entry->assoc && s_entry->assoc->size == 0)
			delete_rsrc_ent(s_entry);
	}
	if (apple_ext && s_entry->assoc) {
		/*
		 * Need Apple extensions for the resource fork as well
		 */
		generate_xa_rr_attributes(whole_path,
			short_name, s_entry->assoc,
			&statbuf, &lstatbuf, deep_flag | (statp?DID_CHDIR:0));
	}
	/* leave out resource fork for the time being */
	/*
	 * XXX This is most likely wrong and should just be:
	 * XXX if (use_XA || use_RockRidge) {
	 */
/*	if ((use_XA || use_RockRidge) && !have_rsrc) {*/
	if (use_XA || use_RockRidge) {
#else
	if (use_XA || use_RockRidge) {
#endif	/* APPLE_HYB */
		generate_xa_rr_attributes(whole_path,
			short_name, s_entry,
			&statbuf, &lstatbuf, deep_flag | (statp?DID_CHDIR:0));

	}
#ifdef UDF
	/* set some info used for udf */
	s_entry->mode = lstatbuf.st_mode;
	s_entry->rdev = lstatbuf.st_rdev;
	s_entry->uid = lstatbuf.st_uid;
	s_entry->gid = lstatbuf.st_gid;
	s_entry->atime.tv_sec = lstatbuf.st_atime;
	s_entry->atime.tv_nsec = stat_ansecs(&lstatbuf);
	s_entry->mtime.tv_sec = lstatbuf.st_mtime;
	s_entry->mtime.tv_nsec = stat_mnsecs(&lstatbuf);
	s_entry->ctime.tv_sec = lstatbuf.st_ctime;
	s_entry->ctime.tv_nsec = stat_cnsecs(&lstatbuf);
#endif

#ifdef	USE_LARGEFILES
#ifndef	MAX_EXTENT
	/*
	 * Allow to #define MAX_EXTENT from outside for debug purposes.
	 */
#ifdef	PROTOTYPES
#define	LARGE_EXTENT	((off_t)0xFFFFF800UL)
#define	MAX_EXTENT	((off_t)0xFFFFFFFEUL)
#else
#define	LARGE_EXTENT	((off_t)0xFFFFF800L)
#define	MAX_EXTENT	((off_t)0xFFFFFFFEL)
#endif
#else	/* MAX_EXTENT */
#define	LARGE_EXTENT	MAX_EXTENT & ~(off_t)2047L
#endif	/* !MAX_EXTENT */
	/*
	 * Break up files greater than (4GB -2) into multiple extents.
	 * The original entry, with ->size untouched, remains for UDF.
	 * Each of the new file sections will get its own entry.
	 * The file sections are the only entries actually written out to the
	 * disk. The UDF entry will use "mxroot" to get the same start
	 * block as the first file section, and all the sections will end up
	 * in the ISO9660 directory in the correct order by "mxpart",
	 * which the directory sorting routine knows about.
	 *
	 * If we ever need to be able to find mxpart == 1 after sorting,
	 * we need to add another pointer to s_entry or to be very careful
	 * with the loops above where the ISO-9660 name is copied back to
	 * all multi-extent parts.
	 */
	if (s_entry->size > MAX_EXTENT) {
		off_t	size;

		s_entry->de_flags |= MULTI_EXTENT;
		s_entry->isorec.flags[0] |= ISO_MULTIEXTENT;
		s_entry->mxroot = s_entry;
		s_entry->mxpart = 0;
		set_733((char *)s_entry->isorec.size, LARGE_EXTENT);
		s_entry1 = dup_directory_entry(s_entry);
		s_entry->next = s_entry1;

		/*
		 * full size UDF version
		 */
		s_entry->de_flags |= INHIBIT_ISO9660_ENTRY|INHIBIT_JOLIET_ENTRY;
		if (s_entry->size > (((off_t)190)*0x3FFFF800)) {
#ifndef	EOVERFLOW
#define	EOVERFLOW	EFBIG
#endif
			errmsgno(EOVERFLOW,
			_("File %s is too large - hiding from UDF tree.\n"),
							whole_path);
			s_entry->de_flags |= INHIBIT_UDF_ENTRY;
		}

		/*
		 * Prepare the first file multi-extent section of the file.
		 */
		s_entry = s_entry1;
		s_entry->de_flags |= INHIBIT_UDF_ENTRY;
		size = s_entry->size;
		s_entry->size = LARGE_EXTENT;
		s_entry->mxpart++;

		/*
		 * Additional extents, as needed
		 */
		while (size > MAX_EXTENT) {
			s_entry1 = dup_directory_entry(s_entry);
			s_entry->next = s_entry1;

			s_entry = s_entry1;
			s_entry->mxpart++;
			size -= LARGE_EXTENT;
		}
		/*
		 * That was the last one.
		 */
		s_entry->isorec.flags[0] &= ~ISO_MULTIEXTENT;
		s_entry->size = size;
		set_733((char *)s_entry->isorec.size, (UInt32_t)s_entry->size);
	}
#endif /* USE_LARGEFILES */

	return (1);
}

EXPORT struct directory_entry *
dup_directory_entry(s_entry)
	struct directory_entry	*s_entry;
{
	struct directory_entry	*s_entry1;

	s_entry1 = (struct directory_entry *)
		e_malloc(sizeof (struct directory_entry));
	memcpy(s_entry1, s_entry, sizeof (struct directory_entry));

	if (s_entry->rr_attributes) {
		s_entry1->rr_attributes =
				e_malloc(s_entry->total_rr_attr_size);
		memcpy(s_entry1->rr_attributes, s_entry->rr_attributes,
					s_entry->total_rr_attr_size);
	}
	if (s_entry->name)
		s_entry1->name = e_strdup(s_entry->name);
	if (s_entry->whole_name)
		s_entry1->whole_name = e_strdup(s_entry->whole_name);
#ifdef	APPLE_HYB
	/*
	 * If we also duplicate s_entry->hfs_ent, we would need to change
	 * free_one_directory() and other calls to free(s_entry->hfs_ent) too.
	 */
#endif
	return (s_entry1);
}

EXPORT void
generate_iso9660_directories(node, outfile)
	struct directory	*node;
	FILE			*outfile;
{
	struct directory *dpnt;

	dpnt = node;

	while (dpnt) {
		if (dpnt->extent > session_start) {
			generate_one_directory(dpnt, outfile);
		}
		if (dpnt->subdir)
			generate_iso9660_directories(dpnt->subdir, outfile);
		dpnt = dpnt->next;
	}
}

/*
 * XXX This may need some work for the MVS port.
 */
LOCAL void
set_de_path(parent, this)
	struct directory	*parent;
	struct directory	*this;
{
	char	*p;
	size_t	len;

	if (parent == NULL) {			/* We are just creating root */
		this->de_path = this->whole_name;
		return;
	} else if (parent == root) {		/* full path == component    */
		this->de_path = this->de_name;
		return;
	}
	len = strlen(parent->de_path)+1+strlen(this->de_name)+1;
	p = e_malloc(len);
	js_snprintf(p, len, "%s/%s", parent->de_path, this->de_name);
	this->de_path = p;
}

/*
 * Function:	find_or_create_directory
 *
 * Purpose:	Locate a directory entry in the tree, create if needed.
 *
 * Arguments:	parent & de are never NULL at the same time.
 */
EXPORT struct directory *
find_or_create_directory(parent, path, de, flag)
	struct directory	*parent;
	char			*path;
	struct directory_entry	*de;
	int			flag;
{
	struct directory *child = 0;
	struct directory *dpnt;
	struct directory_entry *orig_de;
	struct directory *next_brother;
	const char	*cpnt;
	const char	*pnt;
	int		deep_flag = 0;

	orig_de = de;

	/*
	 * XXX It seems that the tree that has been read from the
	 * XXX previous session does not carry whole_name entries.
	 * XXX We provide a hack in multi.c:find_or_create_directory()
	 * XXX that should be removed when a reasonable method could
	 * XXX be found.
	 */
	if (path == NULL) {
		error(_("Warning: missing whole name for: '%s'\n"), de->name);
		path = de->name;
		if (path == NULL)
			comerrno(EX_BAD, _("Panic no node name.\n"));
	}
	pnt = strrchr(path, PATH_SEPARATOR);
	if (pnt == NULL) {
		pnt = path;
	} else {
		pnt++;
	}

	if (parent != NULL) {

		dpnt = parent->subdir;
		if (dpnt == NULL) {
			struct directory_entry *s_entry;

			for (s_entry = parent->contents; s_entry;
						s_entry = s_entry->next) {
				if ((strcmp(s_entry->name, pnt) == 0) &&
				    (s_entry->de_flags & RELOCATED_DIRECTORY)) {
					return (find_or_create_directory(
							reloc_dir, path, de,
									flag));
				}
			}
		}

		while (dpnt) {
			/*
			 * Weird hack time - if there are two directories by
			 * the same name in the reloc_dir, they are not
			 * treated as the same thing unless the entire path
			 * matches completely.
			 */
			if (flag && strcmp(dpnt->de_name, pnt) == 0) {

				/*
				 * XXX Remove test?
				 * XXX dpnt->de_path should always be != NULL
				 */
				if (dpnt->de_path != NULL &&
				    strcmp(dpnt->de_path, path) == 0)
					return (dpnt);

				if (parent != reloc_dir &&
				    strcmp(dpnt->de_name, pnt) == 0)
					return (dpnt);
			}
			dpnt = dpnt->next;
		}
	}
	/*
	 * We don't know if we have a valid directory entry for this one yet.
	 * If not, we need to create one.
	 */
	if (de == NULL) {
		de = (struct directory_entry *)
			e_malloc(sizeof (struct directory_entry));
		memset(de, 0, sizeof (struct directory_entry));
		de->next = parent->contents;
		parent->contents = de;
		de->name = e_strdup(pnt);
		de->whole_name = e_strdup(path);
		de->filedir = parent;
		de->isorec.flags[0] = ISO_DIRECTORY;
		de->priority = 32768;
		de->inode = UNCACHED_INODE;
		de->dev = UNCACHED_DEVICE;
		set_723(de->isorec.volume_sequence_number,
						volume_sequence_number);
		iso9660_file_length(pnt, de, 1);

		init_fstatbuf();
#ifdef APPLE_HYB
		if (apple_both) {
			/*
			 * Give the directory an HFS entry
			 */
			hfsdirent	*hfs_ent;

			hfs_ent = (hfsdirent *) e_malloc(sizeof (hfsdirent));

			/*
			 * Fill in the defaults
			 */
			memset(hfs_ent, 0, sizeof (hfsdirent));
			hfs_ent->crdate = fstatbuf.st_ctime;
			hfs_ent->mddate = fstatbuf.st_mtime;

			de->hfs_ent = hfs_ent;

			/*
			 * Get the Mac directory name
			 */
			get_hfs_dir((char *)path, (char *)pnt, de);
		}
#endif	/* APPLE_HYB */
	}
	/*
	 * If we don't have a directory for this one yet, then allocate it now,
	 * and patch it into the tree in the appropriate place.
	 */
	dpnt = (struct directory *)e_malloc(sizeof (struct directory));
	memset(dpnt, 0, sizeof (struct directory));
	dpnt->next = NULL;
	dpnt->subdir = NULL;
	dpnt->self = de;
	dpnt->contents = NULL;
	dpnt->whole_name = e_strdup(path);
	cpnt = strrchr(path, PATH_SEPARATOR);
	if (cpnt)
		cpnt++;
	else
		cpnt = path;
	dpnt->de_name = e_strdup(cpnt);
	dpnt->de_path = NULL;
	set_de_path(parent, dpnt);
	dpnt->size = 0;
	dpnt->extent = 0;
	dpnt->jextent = 0;
	dpnt->jsize = 0;
#ifdef APPLE_HYB
	dpnt->hfs_ent = de->hfs_ent;
#endif	/* APPLE_HYB */

	if (orig_de == NULL) {
		struct stat	xstatbuf;
		int		sts;

		/*
		 * Now add a . and .. entry in the directory itself. This is a
		 * little tricky - if the real directory exists, we need to
		 * stat it first. Otherwise, we use the fictitious fstatbuf
		 * which points to the time at which mkisofs was started.
		 */
		if (parent == NULL || parent->whole_name[0] == '\0')
			sts = -1;
		else
			sts = stat_filter(parent->whole_name, &xstatbuf);
		if (debug && parent) {
			error(_("stat parent->whole_name: '%s' -> %d.\n"),
				parent->whole_name, sts);
		}
		if (sts == 0) {
			attach_dot_entries(dpnt, NULL, &xstatbuf);
		} else {
			attach_dot_entries(dpnt, NULL, NULL);
		}
	}
	if (!parent || parent == root) {
		if (!root) {
			root = dpnt;	/* First time through for root	*/
					/* directory only		*/
			root->depth = 0;
			root->parent = root;
		} else {
			dpnt->depth = 1;
			if (!root->subdir) {
				root->subdir = dpnt;
			} else {
				next_brother = root->subdir;
				while (next_brother->next)
					next_brother = next_brother->next;
				next_brother->next = dpnt;
			}
			dpnt->parent = parent;
		}
	} else {
		struct directory_entry *s_entry1;

		/*
		 * Come through here for  normal traversal of  tree
		 */
#ifdef DEBUG
		fprintf(stderr, "%s(%d) ", path, dpnt->depth);
#endif

		if (parent->depth > RR_relocation_depth && use_RockRidge) {
			/*
			 * We come here in case that a graft-point needs to
			 * create a new relocated (deep) directory.
			 *
			 * Replicate the entry for this directory.  The old one
			 * will stay where it is, and it will be neutered so
			 * that it no longer looks like a directory. The new
			 * one will look like a directory, and it will be put
			 * in the reloc_dir.
			 */
			s_entry1 = dup_relocated_dir(parent, de, path,
						dpnt->de_name, &fstatbuf);
			child = find_or_create_directory(reloc_dir, path,
								s_entry1, 0);
			free(child->de_path);	/* allocated in this case */
			set_de_path(parent, child);

			deep_flag |= NEED_CL;
		}
		if (parent->depth > RR_relocation_depth && !use_RockRidge) {
			dir_nesting_warn(parent, path, FALSE);
			exit(EX_BAD);
		}
		dpnt->parent = parent;
		dpnt->depth = parent->depth + 1;

		if ((deep_flag & NEED_CL) == 0) {
			/*
			 * Do not add this directory to the list of subdirs if
			 * this is a relocated directory.
			 */
			if (!parent->subdir) {
				parent->subdir = dpnt;
			} else {
				next_brother = parent->subdir;
				while (next_brother->next)
					next_brother = next_brother->next;
				next_brother->next = dpnt;
			}
		}
	}
	/*
	 * It doesn't exist for real, so we cannot add any
	 * XA or Rock Ridge attributes.
	 */
	if (orig_de == NULL || (parent == NULL && path[0] == '\0')) {
		init_fstatbuf();
		fstatbuf.st_mode = new_dir_mode | S_IFDIR;
		fstatbuf.st_nlink = 2;
		if ((use_XA || use_RockRidge) &&
		    !(parent == NULL && path[0] == '\0')) {
			/*
			 * We cannot set up RR attrubutes for the real
			 * ISO-9660 root directory. This is why we
			 * check for parent == NULL && path[0] == '\0'.
			 */
			generate_xa_rr_attributes("",
				(char *)pnt, de,
				&fstatbuf,
				&fstatbuf, deep_flag);
		}
#ifdef UDF
		/* set some info used for udf */
		de->mode = fstatbuf.st_mode;
		de->uid =  fstatbuf.st_uid;
		de->gid =  fstatbuf.st_gid;
		de->atime.tv_sec = fstatbuf.st_atime;
		de->atime.tv_nsec = stat_ansecs(&fstatbuf);
		de->mtime.tv_sec = fstatbuf.st_mtime;
		de->mtime.tv_nsec = stat_mnsecs(&fstatbuf);
		de->ctime.tv_sec = fstatbuf.st_ctime;
		de->ctime.tv_nsec = stat_cnsecs(&fstatbuf);
#endif
		iso9660_date(de->isorec.date, fstatbuf.st_mtime);
	}
	if (child)
		return (child);	/* Return reloaction target */

	return (dpnt);
}

/*
 * Function:	delete_directory
 *
 * Purpose:	Locate a directory entry in the tree, create if needed.
 *
 * Arguments:
 */
LOCAL void
delete_directory(parent, child)
	struct directory	*parent;
	struct directory	*child;
{
	struct directory *tdir;

	if (child == NULL)
		return;
	if (child->contents != NULL) {
		comerrno(EX_BAD, _("Unable to delete non-empty directory\n"));
	}
	free(child->whole_name);
	child->whole_name = NULL;

	free(child->de_name);
	child->de_name = NULL;

#ifdef APPLE_HYB
	if (apple_both && child->hfs_ent)
		free(child->hfs_ent);
#endif	/* APPLE_HYB */

	if (parent->subdir == child) {
		parent->subdir = child->next;
	} else {
		for (tdir = parent->subdir; tdir != NULL && tdir->next != NULL;
							tdir = tdir->next) {
			if (tdir->next == child) {
				tdir->next = child->next;
				break;
			}
		}
		if (tdir == NULL || tdir->next != child->next) {
			comerrno(EX_BAD,
			_("Unable to locate child directory in parent list\n"));
		}
	}
	free(child);
}

EXPORT int
sort_tree(node)
	struct directory	*node;
{
	struct directory *dpnt;
	int		ret = 0;

	dpnt = node;

	while (dpnt) {
		ret = sort_n_finish(dpnt);
		if (ret) {
			break;
		}
		if (dpnt->subdir)
			sort_tree(dpnt->subdir);
		dpnt = dpnt->next;
	}
	return (ret);
}

EXPORT void
dump_tree(node)
	struct directory *node;
{
	struct directory *dpnt;

	dpnt = node;

	while (dpnt) {
		fprintf(stderr, "%4d %5d %s\n",
				dpnt->extent, dpnt->size, dpnt->de_name);
		if (dpnt->subdir)
			dump_tree(dpnt->subdir);
		dpnt = dpnt->next;
	}
}

/*
 * something quick and dirty to locate a file given a path
 * recursively walks down path in filename until it finds the
 * directory entry for the desired file
 */
EXPORT struct directory_entry *
search_tree_file(node, filename)
	struct directory *node;
	char		*filename;
{
	struct directory_entry *depnt;
	struct directory *dpnt;
	char		*p1;
	char		*rest;
	char		*subdir;

	/*
	 * Strip off next directory name from filename:
	 */
	subdir = e_strdup(filename);

	if ((p1 = strchr(subdir, '/')) == subdir) {
		fprintf(stderr,
		_("call to search_tree_file with an absolute path, stripping\n"));
		fprintf(stderr,
		_("initial path separator. Hope this was intended...\n"));
		memmove(subdir, subdir + 1, strlen(subdir) - 1);
		p1 = strchr(subdir, '/');
	}
	/*
	 * Do we need to find a subdirectory?
	 */
	if (p1) {
		*p1 = '\0';

#ifdef DEBUG_TORITO
		fprintf(stderr, _("Looking for subdir called %s\n"), p1);
#endif

		rest = p1 + 1;

#ifdef DEBUG_TORITO
		fprintf(stderr, _("Remainder of path name is now %s\n"), rest);
#endif

		dpnt = node->subdir;
		while (dpnt) {
#ifdef DEBUG_TORITO
			fprintf(stderr,
				"%4d %5d %s\n", dpnt->extent, dpnt->size,
				dpnt->de_name);
#endif
			if (strcmp(subdir, dpnt->de_name) == 0) {
#ifdef DEBUG_TORITO
				fprintf(stderr,
				_("Calling next level with filename = %s\n"), rest);
#endif
				return (search_tree_file(dpnt, rest));
			}
			dpnt = dpnt->next;
		}

		/*
		 * If we got here means we couldn't find the subdir.
		 */
		return (NULL);
	} else {
		/*
		 * Look for a normal file now
		 */
		depnt = node->contents;
		while (depnt) {
#ifdef DEBUG_TORITO
			fprintf(stderr, "%4d %5d %s\n", depnt->isorec.extent,
				depnt->size, depnt->name);
#endif
			if (strcmp(filename, depnt->name) == 0) {
#ifdef DEBUG_TORITO
				fprintf(stderr, _("Found our file %s"), filename);
#endif
				return (depnt);
			}
			depnt = depnt->next;
		}
		/*
		 * If we got here means we couldn't find the subdir.
		 */
		return (NULL);
	}
#ifdef	ERIC_FUN
	fprintf(stderr, _("We cant get here in search_tree_file :-/ \n"));
#endif
}

EXPORT void
init_fstatbuf()
{
	struct timeval	current_time;

	if (fstatbuf.st_ctime == 0) {
		gettimeofday(&current_time, NULL);
		if (rationalize_uid)
			fstatbuf.st_uid = uid_to_use;
		else
			fstatbuf.st_uid = getuid();
		if (rationalize_gid)
			fstatbuf.st_gid = gid_to_use;
		else
			fstatbuf.st_gid = getgid();

		current_time.tv_usec *= 1000;
		fstatbuf.st_ctime = current_time.tv_sec;
		stat_set_ansecs(&fstatbuf, current_time.tv_usec);
		fstatbuf.st_mtime = current_time.tv_sec;
		stat_set_mnsecs(&fstatbuf, current_time.tv_usec);
		fstatbuf.st_atime = current_time.tv_sec;
		stat_set_cnsecs(&fstatbuf, current_time.tv_usec);
	}
}
