/* @(#)volume.c	1.13 04/03/04 joerg, Copyright 1997, 1998, 1999, 2000 James Pearson */
#ifndef lint
static	char sccsid[] =
	"@(#)volume.c	1.13 04/03/04 joerg, Copyright 1997, 1998, 1999, 2000 James Pearson";
#endif
/*
 *      Copyright (c) 1997, 1998, 1999, 2000 James Pearson
 *
 * 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; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *	volume.c: prepare HFS volume for mkhybrid
 *
 *	James Pearson 17/7/97
 *	modified JCP 29/7/97 to improve allocation sizes to cut
 *	down on wasted space. Now uses the HFS "allocation" size rounded
 *	up to the nearest 2048 bytes. Savings can be significant with
 *	a large volume containing lots of smallish files.
 *
 *	Updated for v1.12 - now uses the built in RELOCATED_DIRECTORY
 *	flag for finding the real directory location JCP 8/1/97
 */

#ifdef APPLE_HYB

#include <mconfig.h>
#include "mkisofs.h"
#include <errno.h>

#define	HFS_MIN_SIZE	1600	/* 800k == 1600 HFS blocks */

static hfsvol  *vol_save = 0;	/* used to "destroy" an HFS volume */

static int	AlcSiz		__PR((Ulong));
static int	XClpSiz		__PR((Ulong));
static int	get_vol_size	__PR((int));
	int	make_mac_volume	__PR((struct directory *, int));
static int	copy_to_mac_vol	__PR((hfsvol *, struct directory *));
static void	set_dir_info	__PR((hfsvol *, struct directory *));

/*
 *	AlcSiz: find allocation size for given volume size
 */
static int
AlcSiz(vlen)
	Ulong	vlen;
{
	int	lpa,
		drAlBlkSiz;

	/* code extracted from hfs_format() */
	lpa = 1 + vlen / 65536;
	drAlBlkSiz = lpa * HFS_BLOCKSZ;

	/*
	 * now set our "allocation size" to the allocation block rounded up to
	 * the nearest SECTOR_SIZE (2048 bytes)
	 */
	drAlBlkSiz = ROUND_UP(drAlBlkSiz, SECTOR_SIZE);

	return (drAlBlkSiz);
}

/*
 *	XClpSiz: find the default size of the catalog/extent file
 */
static int
XClpSiz(vlen)
	Ulong	vlen;
{
	int	olpa,
		lpa,
		drNmAlBlks,
		drAlBlkSiz;
	int	vbmsz,
		drXTClpSiz;

	/* code extracted from hfs_format() */

	/* get the lpa from our calculated allocation block size */
	drAlBlkSiz = AlcSiz(vlen);
	lpa = drAlBlkSiz / HFS_BLOCKSZ;

	vbmsz = (vlen / lpa + 4095) / 4096;
	drNmAlBlks = (vlen - 5 - vbmsz) / lpa;
	drXTClpSiz = drNmAlBlks / 128 * drAlBlkSiz;

	/* override the drXTClpSiz size for large volumes */
	if (drXTClpSiz > hce->max_XTCsize) {
		drXTClpSiz = hce->max_XTCsize;
	} else {
		/*
		 * make allowances because we have possibly rounded up the
		 * allocation size get the "original" lpa "
		 */
		olpa = 1 + vlen / 65536;

		/* adjust size upwards */
		drXTClpSiz = ((Ullong)drXTClpSiz * lpa) / olpa;
	}

	/* round up to the nearest allocation size */
	drXTClpSiz = ROUND_UP(drXTClpSiz, drAlBlkSiz);

	return (drXTClpSiz);
}

/*
 *	get_vol_size: get the size of the volume including the extent/catalog
 */
static int
get_vol_size(vblen)
	int	vblen;
{
	int	drXTClpSiz;
	int	drAlBlkSiz;
	int	new_vblen;

	/*
	 * try to estimate a "volume size" based on the code in hfs_format
	 * - we need the size of the catalog/extents and Desktop files included
	 * in the volume, as we add this to the end of the ISO volume
	 */
	drXTClpSiz = XClpSiz(vblen);
	drAlBlkSiz = AlcSiz(vblen);

	/*
	 * catalog file is set at CTC times (default twice) the extents
	 * file size - hence the (ctc_size + 1) below. The Desktop starts of
	 * the same size as the "clump size" == 4 x drAlBlkSiz,
	 * plus a spare drAlBlkSiz for the alternative MDB
	 */
	new_vblen = vblen +
	    ((hce->ctc_size + 1) * drXTClpSiz + 5 * drAlBlkSiz) / HFS_BLOCKSZ;

	return (new_vblen);
}

/*
 *	write_fork: "write" file data to the volume
 *
 *	This is used to update the HFS file internal structures
 *	but no data is actually written (it's trapped deep down in
 *	libhfs).
 */
int
write_fork(hfp, tot)
	hfsfile	*hfp;
	long	tot;
{
	char		blk[HFS_BLOCKSZ];
	unsigned short	start;
	long		len;

	len = tot;
	/* we need to know where this fork starts */
	start = hfs_get_drAllocPtr(hfp);

	/* loop through the data a block at a time */
	while (len >= HFS_BLOCKSZ) {
		if (hfs_write(hfp, blk, HFS_BLOCKSZ) < 0)
			return (-1);
		len -= HFS_BLOCKSZ;
	}
	/* write out anything left */
	if (len)
		if (hfs_write(hfp, blk, len) < 0)
			return (-1);

	/*
	 * set the start of the allocation search to be immediately after
	 * this fork
	 */
	hfs_set_drAllocPtr(hfp, start, tot);

	return (0);
}

/*
 *	make_mac_volume: "create" an HFS volume using the ISO data
 *
 *	The HFS volume structures are set up (but no data is written yet).
 *
 *	ISO volumes have a allocation size of 2048 bytes - regardless
 *	of the size of the volume. HFS allocation size is depends on volume
 *	size, so we may have to update the ISO structures to add in any
 *	padding.
 */
int
make_mac_volume(dpnt, start_extent)
	struct directory	*dpnt;
	int			start_extent;
{
	char	vol_name[HFS_MAX_VLEN + 1];	/* Mac volume name */
	hfsvol	*vol;			/* Mac volume */
	int	vblen;			/* vol length (HFS blocks) */
	int	Csize,
		lastCsize;		/* allocation sizes */
	int	ret = 0;		/* return value */
	int	loop = 1;

	/* umount volume if we have had a previous attempt */
	if (vol_save)
		if (hfs_umount(vol_save, 0, hfs_lock) < 0)
			return (-1);

	/* set the default clump size to the ISO block size */
	Csize = lastCsize = SECTOR_SIZE;

	if (verbose > 1)
		fprintf(stderr, "Creating HFS Volume info\n");

	/* name or copy ISO volume name to Mac Volume name */
	strncpy(vol_name, hfs_volume_id ? hfs_volume_id : volume_id,
								HFS_MAX_VLEN);
	vol_name[HFS_MAX_VLEN] = '\0';

	/* get initial size of HFS volume (size of current ISO volume) */
	vblen = (last_extent - session_start) * HFS_BLK_CONV;

	/* make sure volume is at least 800k */
	if (vblen < HFS_MIN_SIZE)
		vblen += insert_padding_file(HFS_MIN_SIZE - vblen);

	/*
	 * add on size of extents/catalog file, but this may mean the
	 * allocation size will change, so loop round until the
	 * allocation size doesn't change
	 */
	while (loop) {
		hce->XTCsize = XClpSiz(vblen);
		vblen = get_vol_size(vblen);
		Csize = AlcSiz(vblen);

		if (Csize == lastCsize) {
			/* allocation size hasn't changed, so carry on */
			loop = 0;
		} else {
			/*
			 * allocation size has changed, so update
			 * ISO volume size
			 */
			if ((vblen = get_adj_size(Csize)) < 0) {
				sprintf(hce->error,
					"too many files for HFS volume");
				return (-1);
			}
			vblen +=
				ROUND_UP((start_extent - session_start) *
						HFS_BLK_CONV, Csize);
			lastCsize = Csize;
		}
	}

	/* take off the label/map size */
	vblen -= hce->hfs_map_size;

	hce->hfs_vol_size = vblen;

	/* set the default allocation size for libhfs */
	hce->Csize = Csize;

	/* format and mount the "volume" */
	if (hfs_format(hce, 0, vol_name) < 0) {
		sprintf(hce->error, "can't HFS format %s", vol_name);
		return (-1);
	}
	/*
	 * update the ISO structures with new start extents and any
	 * padding required
	 */
	if (Csize != SECTOR_SIZE) {
		last_extent = adj_size(Csize, start_extent,
					hce->hfs_hdr_size + hce->hfs_map_size);
		adj_size_other(dpnt);
	}
	if ((vol = hfs_mount(hce, 0, 0)) == 0) {
		sprintf(hce->error, "can't HFS mount %s", vol_name);
		return (-1);
	}
	/* save the volume for possible later use */
	vol_save = vol;

	/*
	 * Recursively "copy" the files to the volume
	 * - we need to know the first allocation block in the volume as
	 * starting blocks of files are relative to this.
	 */
	ret = copy_to_mac_vol(vol, dpnt);
	if (ret < 0)
		return (ret);

	/*
	 * make the Desktop files - I *think* this stops the Mac rebuilding the
	 * desktop when the CD is mounted on a Mac These will be ignored if they
	 * already exist
	 */
	if (create_dt)
		ret = make_desktop(vol,
				(last_extent - session_start) * HFS_BLK_CONV);
	if (ret < 0)
		return (ret);

	/* close the volume */
	if (hfs_flush(vol) < 0)
		return (-1);

	/* unmount and set the start blocks for the catalog/extents files */
	if (hfs_umount(vol, (last_extent - session_start) * HFS_BLK_CONV, hfs_lock) < 0)
		return (-1);

	return (Csize);
}

#define	TEN 10	/* well, it is! */
#define	LCHAR "_"

/*
 *	copy_to_mac_vol: copy all files in a directory to corresponding
 *			 Mac folder.
 *
 *	Files are copied recursively to corresponding folders on the Mac
 *	volume. The caller routine needs to do a hfs_chdir before calling this
 *	routine.
 */
static int
copy_to_mac_vol(vol, node)
	hfsvol		*vol;
	struct directory *node;
{
	struct directory_entry	*s_entry;	/* ISO directory entry */
	struct directory_entry	*s_entry1;	/* tmp ISO directory entry */
	struct directory	*dpnt;		/* ISO directory */

	hfsfile			*hfp;		/* HFS file */
	hfsdirent		*ent;		/* HFS file entities */
	long			id;		/* current HFS folder */
	long			dext,
				rext;		/* real data/rsrc start blk */
	int			ret;		/* result code */
	int			new_name;	/* HFS file has modified name */

	int			tens;
	int			digits;
	int			i;

	/* store the current HFS directory ID */
	if ((id = hfs_getcwd(vol)) == 0)
		return (-1);

	if (verbose > 1)
		fprintf(stderr, "HFS scanning %s\n", node->whole_name);

	/* loop through the ISO directory entries and process files */
	for (s_entry = node->contents; s_entry; s_entry = s_entry->next) {
		/* ignore directory and associated (rsrc) files */
		if (s_entry->isorec.flags[0] & (ISO_DIRECTORY|ISO_ASSOCIATED))
			continue;

		/* ignore any non-Mac type file */
		if (!s_entry->hfs_ent)
			continue;

		/*
		 * ignore if from a previous session
		 * - should be trapped above
		 */
		if (s_entry->starting_block < session_start)
			continue;

#ifdef DEBUG
		fprintf(stderr, " Name = %s", s_entry->whole_name);
		fprintf(stderr, "   Startb =  %d\n", s_entry->starting_block);
#endif	/* DEBUG */

		ent = s_entry->hfs_ent;

		/* create file */
		i = HFS_MAX_FLEN - strlen(ent->name);
		new_name = 0;
		tens = TEN;
		digits = 1;

		while (1) {
			/*
			 * try to open file - if it exists,
			 * then append '_' to the name and try again
			 */
			errno = 0;
			if ((hfs_create(vol, ent->name, ent->u.file.type,
						ent->u.file.creator)) < 0) {
				if (errno != EEXIST) {
					/*
					 * not an "exist" error, or we can't
					 * append as the filename is already
					 * HFS_MAX_FLEN chars
					 */
					sprintf(hce->error,
						"can't HFS create file %s",
						s_entry->whole_name);
					return (-1);
				} else if (i == 0) {
					/*
					 * File name at max HFS length
					 * - make unique name
					 */
					if (!new_name)
						new_name++;

					sprintf(ent->name +
						HFS_MAX_FLEN - digits - 1,
						"%s%d", LCHAR, new_name);
					new_name++;
					if (new_name == tens) {
						tens *= TEN;
						digits++;
					}
				} else {
					/* append '_' to get new name */
					strcat(ent->name, LCHAR);
					i--;
					new_name = 1;
				}
			} else
				break;
		}

		/* warn that we have a new name */
		if (new_name && verbose > 0) {
			fprintf(stderr, "Using HFS name: %s for %s\n",
				ent->name,
				s_entry->whole_name);
		}
		/* open file */
		if ((hfp = hfs_open(vol, ent->name)) == 0) {
			sprintf(hce->error, "can't HFS open %s",
				s_entry->whole_name);
			return (-1);
		}
		/* if it has a data fork, then "write" it out */
		if (ent->u.file.dsize)
			write_fork(hfp, ent->u.file.dsize);

		/* if it has a resource fork, set the fork and "write" it out */
		if (ent->u.file.rsize) {
			if ((hfs_setfork(hfp, 1)) < 0)
				return (-1);
			write_fork(hfp, ent->u.file.rsize);
		}

		/* make file invisible if ISO9660 hidden */
		if (s_entry->de_flags & HIDDEN_FILE)
			ent->fdflags |= HFS_FNDR_ISINVISIBLE;

		/* update any HFS file attributes */
		if ((hfs_fsetattr(hfp, ent)) < 0) {
			sprintf(hce->error, "can't HFS set attributes %s",
				s_entry->whole_name);
			return (-1);
		}
		/*
		 * get the ISO starting block of data fork (may be zero)
		 * and convert to the equivalent HFS block
		 */
		if (ent->u.file.dsize) {
			dext = (s_entry->starting_block - session_start) *
								HFS_BLK_CONV;
		} else {
			dext = 0;
		}

		/*
		 * if the file has a resource fork (associated file),
		 * get it's ISO starting block and convert as above
		 */
		if (s_entry->assoc && ent->u.file.rsize) {
			rext =
			    (s_entry->assoc->starting_block - session_start) *
								HFS_BLK_CONV;
		} else {
			rext = 0;
		}

		/* close the file and update the starting blocks */
		if (hfs_close(hfp, dext, rext) < 0) {
			sprintf(hce->error, "can't HFS close file %s",
				s_entry->whole_name);
			return (-1);
		}
	}

	/* set folder info and custom icon (if it exists) */
	set_dir_info(vol, node);

	/*
	 * process sub-directories  - have a slight problem here,
	 * if the directory had been relocated, then we need to find the
	 * real directory - we do this by first finding the
	 * real directory_entry, and then finding it's directory info
	 */

	/* following code taken from joliet.c */
	for (s_entry = node->contents; s_entry; s_entry = s_entry->next) {
		if ((s_entry->de_flags & RELOCATED_DIRECTORY) != 0) {
			/*
			 * if the directory has been reloacted, then search the
			 * relocated directory for the real entry
			 */
			for (s_entry1 = reloc_dir->contents; s_entry1;
						s_entry1 = s_entry1->next) {
				if (s_entry1->parent_rec == s_entry)
					break;
			}

			/* have a problem - can't find the real directory */
			if (s_entry1 == NULL) {
				sprintf(hce->error,
					"can't locate relocated directory %s",
					s_entry->whole_name);
				return (-1);
			}
		} else
			s_entry1 = s_entry;

		/* now have the correct entry - now find the actual directory */
		if ((s_entry1->isorec.flags[0] & ISO_DIRECTORY) &&
				strcmp(s_entry1->name, ".") &&
				strcmp(s_entry1->name, "..")) {
			if ((s_entry->de_flags & RELOCATED_DIRECTORY) != 0)
				dpnt = reloc_dir->subdir;
			else
				dpnt = node->subdir;

			while (1) {
				if (dpnt->self == s_entry1)
					break;
				dpnt = dpnt->next;
				if (!dpnt) {
					sprintf(hce->error,
					    "can't find directory location %s",
							s_entry1->whole_name);
					return (-1);
				}
			}
			/*
			 * now have the correct directory
			 * - so do the HFS stuff
			 */
			ent = dpnt->hfs_ent;

			/*
			 * if we don't have hfs entries, then this is a "deep"
			 * directory - this will be processed later
			 */
			if (!ent)
				continue;

			/* make sub-folder */
			i = HFS_MAX_FLEN - strlen(ent->name);
			new_name = 0;
			tens = TEN;
			digits = 1;

			while (1) {
				/*
				 * try to create new directory
				 * - if it exists, then append '_' to the name
				 * and try again
				 */
				errno = 0;
				if (hfs_mkdir(vol, ent->name) < 0) {
					if (errno != EEXIST) {
						/*
						 * not an "exist" error,
						 * or we can't append as the
						 * filename is already
						 * HFS_MAX_FLEN chars
						 */
						sprintf(hce->error,
						    "can't HFS create folder %s",
							s_entry->whole_name);
						return (-1);
					} else if (i == 0) {
						/*
						 * File name at max HFS length
						 * - make unique name
						 */
						if (!new_name)
							new_name++;

						sprintf(ent->name +
						    HFS_MAX_FLEN - digits - 1,
						    "%s%d", LCHAR, new_name);
						new_name++;
						if (new_name == tens) {
							tens *= TEN;
							digits++;
						}
					} else {
						/* append '_' to get new name */
						strcat(ent->name, LCHAR);
						i--;
						new_name = 1;
					}
				} else
					break;
			}

			/* warn that we have a new name */
			if (new_name && verbose > 0) {
				fprintf(stderr, "Using HFS name: %s for %s\n",
							ent->name,
							s_entry->whole_name);
			}
			/* see if we need to "bless" this folder */
			if (hfs_bless && strcmp(s_entry->whole_name, hfs_bless)
					== 0) {
				hfs_stat(vol, ent->name, ent);
				hfs_vsetbless(vol, ent->cnid);
				if (verbose > 0) {
					fprintf(stderr, "Blessing %s (%s)\n",
							ent->name,
							s_entry->whole_name);
				}
				/* stop any further checks */
				hfs_bless = NULL;
			}
			/* change to sub-folder */
			if (hfs_chdir(vol, ent->name) < 0)
				return (-1);

			/* recursively copy files ... */
			ret = copy_to_mac_vol(vol, dpnt);
			if (ret < 0)
				return (ret);

			/* change back to this folder */
			if (hfs_setcwd(vol, id) < 0)
				return (-1);
		}
	}

	return (0);
}

/*
 *	set_dir_info:	Set directory info for a file - also use a custom
 *			Icon - if it exists.
 *
 *	Sets folder' layout (window layout, view, scroll bars etc)
 *
 *	Set the 'HFS_FNDR_HASCUSTOMICON' bit of the folder flags
 *	if a file called 'Icon\r' exists in the folder
 *
 *	Also makes sure the Icon file is invisible
 *	Don't worry if any of this fails ...
 *
 *	Thanks to Rob Leslie <rob@mars.org> for how to do this.
 */

#define	ICON	"Icon"

static void
set_dir_info(vol, de)
	hfsvol			*vol;
	struct directory	*de;
{
	hfsdirent	*ent = de->hfs_ent;
	hfsdirent	ent1;
	char		name[HFS_MAX_FLEN + 1];
	unsigned short	flags = 0;

	memset(&ent1, 0, sizeof (hfsdirent));

	sprintf(name, "%s\r", ICON);

	/* get the attributes for the Icon file */
	if (hfs_stat(vol, name, &ent1) == 0) {

		/* make sure it is invisible */
		ent1.fdflags |= HFS_FNDR_ISINVISIBLE;

		/* set the new attributes for the Icon file */
		hfs_setattr(vol, name, &ent1);

		/* flag the folder as having a custom icon */
		flags |= HFS_FNDR_HASCUSTOMICON;
	}

	/* make the current folder invisible if ISO9660 hidden */
	if (de->self->de_flags & HIDDEN_FILE) {
		flags |= HFS_FNDR_ISINVISIBLE;
	}

	/* may not have an hfs_ent for this directory */
	if (ent == NULL) {
		ent = &ent1;
		memset(ent, 0, sizeof (hfsdirent));

		/* get the attributes for the folder */
		if (hfs_stat(vol, ":", ent) < 0)
			return;
	}

	/* set HFS_FNDR_HASCUSTOMICON/HFS_FNDR_ISINVISIBLE if needed */
	ent->fdflags |= flags;

	/* set the new attributes for the folder */
	if (hfs_setattr(vol, ":", ent) < 0) {
		/*
		 * Only needed if we add things after this if statement.
		 */
/*		return;*/
	}
}

#endif	/* APPLE_HYB */
