/*-
 * Copyright (c) 2009-2012 Michihiro NAKAJIMA
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "archive_platform.h"

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_UTSNAME_H
#include <sys/utsname.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif
#include <stdio.h>
#include <stdarg.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <time.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_ZLIB_H
#include <zlib.h>
#endif

#include "archive.h"
#include "archive_endian.h"
#include "archive_entry.h"
#include "archive_entry_locale.h"
#include "archive_private.h"
#include "archive_rb.h"
#include "archive_write_private.h"

#if defined(_WIN32) && !defined(__CYGWIN__)
#define getuid()			0
#define getgid()			0
#endif

/*#define DEBUG 1*/
#ifdef DEBUG
/* To compare to the ISO image file made by mkisofs. */
#define COMPAT_MKISOFS		1
#endif

#define LOGICAL_BLOCK_BITS			11
#define LOGICAL_BLOCK_SIZE			2048
#define PATH_TABLE_BLOCK_SIZE			4096

#define SYSTEM_AREA_BLOCK			16
#define PRIMARY_VOLUME_DESCRIPTOR_BLOCK 	1
#define SUPPLEMENTARY_VOLUME_DESCRIPTOR_BLOCK 	1
#define BOOT_RECORD_DESCRIPTOR_BLOCK	 	1
#define VOLUME_DESCRIPTOR_SET_TERMINATOR_BLOCK	1
#define NON_ISO_FILE_SYSTEM_INFORMATION_BLOCK	1
#define RRIP_ER_BLOCK				1
#define PADDING_BLOCK				150

#define FD_1_2M_SIZE		(1024 * 1200)
#define FD_1_44M_SIZE		(1024 * 1440)
#define FD_2_88M_SIZE		(1024 * 2880)
#define MULTI_EXTENT_SIZE	(ARCHIVE_LITERAL_LL(1) << 32)	/* 4Gi bytes. */
#define MAX_DEPTH		8
#define RR_CE_SIZE		28		/* SUSP "CE" extension size */

#define FILE_FLAG_EXISTENCE	0x01
#define FILE_FLAG_DIRECTORY	0x02
#define FILE_FLAG_ASSOCIATED	0x04
#define FILE_FLAG_RECORD	0x08
#define FILE_FLAG_PROTECTION	0x10
#define FILE_FLAG_MULTI_EXTENT	0x80

static const char rrip_identifier[] =
	"RRIP_1991A";
static const char rrip_descriptor[] =
	"THE ROCK RIDGE INTERCHANGE PROTOCOL PROVIDES SUPPORT FOR "
	"POSIX FILE SYSTEM SEMANTICS";
static const char rrip_source[] =
	"PLEASE CONTACT DISC PUBLISHER FOR SPECIFICATION SOURCE.  "
	"SEE PUBLISHER IDENTIFIER IN PRIMARY VOLUME DESCRIPTOR FOR "
	"CONTACT INFORMATION.";
#define RRIP_ER_ID_SIZE		(sizeof(rrip_identifier)-1)
#define RRIP_ER_DSC_SIZE	(sizeof(rrip_descriptor)-1)
#define RRIP_ER_SRC_SIZE	(sizeof(rrip_source)-1)
#define RRIP_ER_SIZE		(8 + RRIP_ER_ID_SIZE + \
				RRIP_ER_DSC_SIZE + RRIP_ER_SRC_SIZE)

static const unsigned char zisofs_magic[8] = {
	0x37, 0xE4, 0x53, 0x96, 0xC9, 0xDB, 0xD6, 0x07
};

#define ZF_HEADER_SIZE	16	/* zisofs header size. */
#define ZF_LOG2_BS	15	/* log2 block size; 32K bytes. */
#define ZF_BLOCK_SIZE	(1UL << ZF_LOG2_BS)

/*
 * Manage extra records.
 */
struct extr_rec {
	int		 location;
	int		 offset;
	unsigned char	 buf[LOGICAL_BLOCK_SIZE];
	struct extr_rec	*next;
};

struct ctl_extr_rec {
	int		 use_extr;
	unsigned char	*bp;
	struct isoent	*isoent;
	unsigned char	*ce_ptr;
	int		 cur_len;
	int		 dr_len;
	int		 limit;
	int		 extr_off;
	int		 extr_loc;
};
#define DR_SAFETY	RR_CE_SIZE
#define DR_LIMIT	(254 - DR_SAFETY)

/*
 * The relation of struct isofile and isoent and archive_entry.
 *
 * Primary volume tree  --> struct isoent
 *                                |
 *                                v
 *                          struct isofile --> archive_entry
 *                                ^
 *                                |
 * Joliet volume tree   --> struct isoent
 *
 * struct isoent has specific information for volume.
 */

struct isofile {
	/* Used for managing struct isofile list. */
	struct isofile		*allnext;
	struct isofile		*datanext;
	/* Used for managing a hardlinked struct isofile list. */
	struct isofile		*hlnext;
	struct isofile		*hardlink_target;

	struct archive_entry	*entry;

	/*
	 * Used for making a directory tree.
	 */
	struct archive_string	 parentdir;
	struct archive_string	 basename;
	struct archive_string	 basename_utf16;
	struct archive_string	 symlink;
	int			 dircnt;	/* The number of elements of
						 * its parent directory */

	/*
	 * Used for a Directory Record.
	 */
	struct content {
		int64_t		 offset_of_temp;
		int64_t		 size;
		int		 blocks;
		uint32_t 	 location;
		/*
		 * One extent equals one content.
		 * If this entry has multi extent, `next' variable points
		 * next content data.
		 */
		struct content	*next;		/* next content	*/
	} content, *cur_content;
	int			 write_content;

	enum {
		NO = 0,
		BOOT_CATALOG,
		BOOT_IMAGE
	} boot;

	/*
	 * Used for a zisofs.
	 */
	struct {
		unsigned char	 header_size;
		unsigned char	 log2_bs;
		uint32_t	 uncompressed_size;
	} zisofs;
};

struct isoent {
	/* Keep `rbnode' at the first member of struct isoent. */
	struct archive_rb_node	 rbnode;

	struct isofile		*file;

	struct isoent		*parent;
	/* A list of children.(use chnext) */
	struct {
		struct isoent	*first;
		struct isoent	**last;
		int		 cnt;
	}			 children;
	struct archive_rb_tree	 rbtree;

	/* A list of sub directories.(use drnext) */
	struct {
		struct isoent	*first;
		struct isoent	**last;
		int		 cnt;
	}			 subdirs;
	/* A sorted list of sub directories. */
	struct isoent		**children_sorted;
	/* Used for managing struct isoent list. */
	struct isoent		*chnext;
	struct isoent		*drnext;
	struct isoent		*ptnext;

	/*
	 * Used for making a Directory Record.
	 */
	int			 dir_number;
	struct {
		int		 vd;
		int		 self;
		int		 parent;
		int		 normal;
	}			 dr_len;
	uint32_t 		 dir_location;
	int			 dir_block;

	/*
	 * Identifier:
	 *   on primary, ISO9660 file/directory name.
	 *   on joliet, UCS2 file/directory name.
	 * ext_off   : offset of identifier extension.
	 * ext_len   : length of identifier extension.
	 * id_len    : byte size of identifier.
	 *   on primary, this is ext_off + ext_len + version length.
	 *   on joliet, this is ext_off + ext_len.
	 * mb_len    : length of multibyte-character of identifier.
	 *   on primary, mb_len and id_len are always the same.
	 *   on joliet, mb_len and id_len are different.
	 */
	char			*identifier;
	int			 ext_off;
	int			 ext_len;
	int			 id_len;
	int			 mb_len;

	/*
	 * Used for making a Rockridge extension.
	 * This is a part of Directory Records.
	 */
	struct isoent		*rr_parent;
	struct isoent		*rr_child;

	/* Extra Record.(which we call in this source file)
	 * A maximum size of the Directory Record is 254.
	 * so, if generated RRIP data of a file cannot into a Directory
	 * Record because of its size, that surplus data relocate this
	 * Extra Record.
	 */
	struct {
		struct extr_rec	*first;
		struct extr_rec	**last;
		struct extr_rec	*current;
	}			 extr_rec_list;

	unsigned int		 virtual:1;
	/* If set to one, this file type is a directory.
	 * A convenience flag to be used as
	 * "archive_entry_filetype(isoent->file->entry) == AE_IFDIR".
	 */
	unsigned int		 dir:1;
};

struct hardlink {
	struct archive_rb_node	 rbnode;
	int			 nlink;
	struct {
		struct isofile	*first;
		struct isofile	**last;
	}			 file_list;
};

/*
 * ISO writer options
 */
struct iso_option {
	/*
	 * Usage  : abstract-file=<value>
	 * Type   : string, max 37 bytes
	 * Default: Not specified
	 * COMPAT : mkisofs -abstract <value>
	 *
	 * Specifies Abstract Filename.
	 * This file shall be described in the Root Directory
	 * and containing a abstract statement.
	 */
	unsigned int	 abstract_file:1;
#define OPT_ABSTRACT_FILE_DEFAULT	0	/* Not specified */
#define ABSTRACT_FILE_SIZE		37

	/*
	 * Usage  : application-id=<value>
	 * Type   : string, max 128 bytes
	 * Default: Not specified
	 * COMPAT : mkisofs -A/-appid <value>.
	 *
	 * Specifies Application Identifier.
	 * If the first byte is set to '_'(5F), the remaining
	 * bytes of this option shall specify an identifier
	 * for a file containing the identification of the
	 * application.
	 * This file shall be described in the Root Directory.
	 */
	unsigned int	 application_id:1;
#define OPT_APPLICATION_ID_DEFAULT	0	/* Use default identifier */
#define APPLICATION_IDENTIFIER_SIZE	128

	/*
	 * Usage : !allow-vernum
	 * Type  : boolean
	 * Default: Enabled
	 *	  : Violates the ISO9660 standard if disable.
	 * COMPAT: mkisofs -N
	 *
	 * Allow filenames to use version numbers.
	 */
	unsigned int	 allow_vernum:1;
#define OPT_ALLOW_VERNUM_DEFAULT	1	/* Enabled */

	/*
	 * Usage  : biblio-file=<value>
	 * Type   : string, max 37 bytes
	 * Default: Not specified
	 * COMPAT : mkisofs -biblio <value>
	 *
	 * Specifies Bibliographic Filename.
	 * This file shall be described in the Root Directory
	 * and containing bibliographic records.
	 */
	unsigned int	 biblio_file:1;
#define OPT_BIBLIO_FILE_DEFAULT		0	/* Not specified */
#define BIBLIO_FILE_SIZE		37

	/*
	 * Usage  : boot=<value>
	 * Type   : string
	 * Default: Not specified
	 * COMPAT : mkisofs -b/-eltorito-boot <value>
	 *
	 * Specifies "El Torito" boot image file to make
	 * a bootable CD.
	 */
	unsigned int	 boot:1;
#define OPT_BOOT_DEFAULT		0	/* Not specified */

	/*
	 * Usage  : boot-catalog=<value>
	 * Type   : string
	 * Default: "boot.catalog"
	 * COMPAT : mkisofs -c/-eltorito-catalog <value>
	 *
	 * Specifies a fullpath of El Torito boot catalog.
	 */
	unsigned int	 boot_catalog:1;
#define OPT_BOOT_CATALOG_DEFAULT	0	/* Not specified */

	/*
	 * Usage  : boot-info-table
	 * Type   : boolean
	 * Default: Disabled
	 * COMPAT : mkisofs -boot-info-table
	 *
	 * Modify the boot image file specified by `boot'
	 * option; ISO writer stores boot file information
	 * into the boot file in ISO image at offset 8
	 * through offset 64.
	 */
	unsigned int	 boot_info_table:1;
#define OPT_BOOT_INFO_TABLE_DEFAULT	0	/* Disabled */

	/*
	 * Usage  : boot-load-seg=<value>
	 * Type   : hexadecimal
	 * Default: Not specified
	 * COMPAT : mkisofs -boot-load-seg <value>
	 *
	 * Specifies a load segment for boot image.
	 * This is used with no-emulation mode.
	 */
	unsigned int	 boot_load_seg:1;
#define OPT_BOOT_LOAD_SEG_DEFAULT	0	/* Not specified */

	/*
	 * Usage  : boot-load-size=<value>
	 * Type   : decimal
	 * Default: Not specified
	 * COMPAT : mkisofs -boot-load-size <value>
	 *
	 * Specifies a sector count for boot image.
	 * This is used with no-emulation mode.
	 */
	unsigned int	 boot_load_size:1;
#define OPT_BOOT_LOAD_SIZE_DEFAULT	0	/* Not specified */

	/*
	 * Usage  : boot-type=<boot-media-type>
	 *        : 'no-emulation' : 'no emulation' image
	 *        :           'fd' : floppy disk image
	 *        :    'hard-disk' : hard disk image
	 * Type   : string
	 * Default: Auto detect
	 *        : We check a size of boot image;
	 *        : If the size is just 1.22M/1.44M/2.88M,
	 *        : we assume boot_type is 'fd';
	 *        : otherwise boot_type is 'no-emulation'.
	 * COMPAT :
	 *    boot=no-emulation
	 *	mkisofs -no-emul-boot
	 *    boot=fd
	 *	This is a default on the mkisofs.
	 *    boot=hard-disk
	 *	mkisofs -hard-disk-boot
	 *
	 * Specifies a type of "El Torito" boot image.
	 */
	unsigned int	 boot_type:2;
#define OPT_BOOT_TYPE_AUTO		0	/* auto detect		  */
#define OPT_BOOT_TYPE_NO_EMU		1	/* ``no emulation'' image */
#define OPT_BOOT_TYPE_FD		2	/* floppy disk image	  */
#define OPT_BOOT_TYPE_HARD_DISK		3	/* hard disk image	  */
#define OPT_BOOT_TYPE_DEFAULT		OPT_BOOT_TYPE_AUTO

	/*
	 * Usage  : compression-level=<value>
	 * Type   : decimal
	 * Default: Not specified
	 * COMPAT : NONE
	 *
	 * Specifies compression level for option zisofs=direct.
	 */
	unsigned int	 compression_level:1;
#define OPT_COMPRESSION_LEVEL_DEFAULT	0	/* Not specified */

	/*
	 * Usage  : copyright-file=<value>
	 * Type   : string, max 37 bytes
	 * Default: Not specified
	 * COMPAT : mkisofs -copyright <value>
	 *
	 * Specifies Copyright Filename.
	 * This file shall be described in the Root Directory
	 * and containing a copyright statement.
	 */
	unsigned int	 copyright_file:1;
#define OPT_COPYRIGHT_FILE_DEFAULT	0	/* Not specified */
#define COPYRIGHT_FILE_SIZE		37

	/*
	 * Usage  : gid=<value>
	 * Type   : decimal
	 * Default: Not specified
	 * COMPAT : mkisofs -gid <value>
	 *
	 * Specifies a group id to rewrite the group id of all files.
	 */
	unsigned int	 gid:1;
#define OPT_GID_DEFAULT			0	/* Not specified */

	/*
	 * Usage  : iso-level=[1234]
	 * Type   : decimal
	 * Default: 1
	 * COMPAT : mkisofs -iso-level <value>
	 *
	 * Specifies ISO9600 Level.
	 * Level 1: [DEFAULT]
	 *   - limits each file size less than 4Gi bytes;
	 *   - a File Name shall not contain more than eight
	 *     d-characters or eight d1-characters;
	 *   - a File Name Extension shall not contain more than
	 *     three d-characters or three d1-characters;
	 *   - a Directory Identifier shall not contain more
	 *     than eight d-characters or eight d1-characters.
	 * Level 2:
	 *   - limits each file size less than 4Giga bytes;
	 *   - a File Name shall not contain more than thirty
	 *     d-characters or thirty d1-characters;
	 *   - a File Name Extension shall not contain more than
	 *     thirty d-characters or thirty d1-characters;
	 *   - a Directory Identifier shall not contain more
	 *     than thirty-one d-characters or thirty-one
	 *     d1-characters.
	 * Level 3:
	 *   - no limit of file size; use multi extent.
	 * Level 4:
	 *   - this level 4 simulates mkisofs option
	 *     '-iso-level 4';
	 *   - crate a enhanced volume as mkisofs doing;
	 *   - allow a File Name to have leading dot;
	 *   - allow a File Name to have all ASCII letters;
	 *   - allow a File Name to have multiple dots;
	 *   - allow more then 8 depths of directory trees;
	 *   - disable a version number to a File Name;
	 *   - disable a forced period to the tail of a File Name;
	 *   - the maximum length of files and directories is raised to 193.
	 *     if rockridge option is disabled, raised to 207.
	 */
	unsigned int	 iso_level:3;
#define OPT_ISO_LEVEL_DEFAULT		1	/* ISO Level 1 */

	/*
	 * Usage  : joliet[=long]
	 *        : !joliet
	 *        :   Do not generate Joliet Volume and Records.
	 *        : joliet [DEFAULT]
	 *        :   Generates Joliet Volume and Directory Records.
	 *        :   [COMPAT: mkisofs -J/-joliet]
	 *        : joliet=long
	 *        :   The joliet filenames are up to 103 Unicode
	 *        :   characters.
	 *        :   This option breaks the Joliet specification.
	 *        :   [COMPAT: mkisofs -J -joliet-long]
	 * Type   : boolean/string
	 * Default: Enabled
	 * COMPAT : mkisofs -J / -joliet-long
	 *
	 * Generates Joliet Volume and Directory Records.
	 */
	unsigned int	 joliet:2;
#define OPT_JOLIET_DISABLE		0	/* Not generate Joliet Records. */
#define OPT_JOLIET_ENABLE		1	/* Generate Joliet Records.  */
#define OPT_JOLIET_LONGNAME		2	/* Use long joliet filenames.*/
#define OPT_JOLIET_DEFAULT		OPT_JOLIET_ENABLE

	/*
	 * Usage  : !limit-depth
	 * Type   : boolean
	 * Default: Enabled
	 *	  : Violates the ISO9660 standard if disable.
	 * COMPAT : mkisofs -D/-disable-deep-relocation
	 *
	 * The number of levels in hierarchy cannot exceed eight.
	 */
	unsigned int	 limit_depth:1;
#define OPT_LIMIT_DEPTH_DEFAULT		1	/* Enabled */

	/*
	 * Usage  : !limit-dirs
	 * Type   : boolean
	 * Default: Enabled
	 *	  : Violates the ISO9660 standard if disable.
	 * COMPAT : mkisofs -no-limit-pathtables
	 *
	 * Limits the number of directories less than 65536 due
	 * to the size of the Parent Directory Number of Path
	 * Table.
	 */
	unsigned int	 limit_dirs:1;
#define OPT_LIMIT_DIRS_DEFAULT		1	/* Enabled */

	/*
	 * Usage  : !pad
	 * Type   : boolean
	 * Default: Enabled
	 * COMPAT : -pad/-no-pad
	 *
	 * Pads the end of the ISO image by null of 300Ki bytes.
	 */
	unsigned int	 pad:1;
#define OPT_PAD_DEFAULT			1	/* Enabled */

	/*
	 * Usage  : publisher=<value>
	 * Type   : string, max 128 bytes
	 * Default: Not specified
	 * COMPAT : mkisofs -publisher <value>
	 *
	 * Specifies Publisher Identifier.
	 * If the first byte is set to '_'(5F), the remaining
	 * bytes of this option shall specify an identifier
	 * for a file containing the identification of the user.
	 * This file shall be described in the Root Directory.
	 */
	unsigned int	 publisher:1;
#define OPT_PUBLISHER_DEFAULT		0	/* Not specified */
#define PUBLISHER_IDENTIFIER_SIZE	128

	/*
	 * Usage  : rockridge
	 *        : !rockridge
	 *        :    disable to generate SUSP and RR records.
	 *        : rockridge
	 *        :    the same as 'rockridge=useful'.
	 *        : rockridge=strict
	 *        :    generate SUSP and RR records.
	 *        :    [COMPAT: mkisofs -R]
	 *        : rockridge=useful [DEFAULT]
	 *        :    generate SUSP and RR records.
	 *        :    [COMPAT: mkisofs -r]
	 *        :    NOTE  Our rockridge=useful option does not set a zero
	 *        :          to uid and gid, you should use application
	 *        :          option such as --gid,--gname,--uid and --uname
	 *        :          bsdtar options instead.
	 * Type   : boolean/string
	 * Default: Enabled as rockridge=useful
	 * COMPAT : mkisofs -r / -R
	 *
	 * Generates SUSP and RR records.
	 */
	unsigned int	 rr:2;
#define OPT_RR_DISABLED			0
#define OPT_RR_STRICT			1
#define OPT_RR_USEFUL			2
#define OPT_RR_DEFAULT			OPT_RR_USEFUL

	/*
	 * Usage  : volume-id=<value>
	 * Type   : string, max 32 bytes
	 * Default: Not specified
	 * COMPAT : mkisofs -V <value>
	 *
	 * Specifies Volume Identifier.
	 */
	unsigned int	 volume_id:1;
#define OPT_VOLUME_ID_DEFAULT		0	/* Use default identifier */
#define VOLUME_IDENTIFIER_SIZE		32

	/*
	 * Usage  : !zisofs [DEFAULT]
	 *        :    Disable to generate RRIP 'ZF' extension.
	 *        : zisofs
	 *        :    Make files zisofs file and generate RRIP 'ZF'
 	 *        :    extension. So you do not need mkzftree utility
	 *        :    for making zisofs.
	 *        :    When the file size is less than one Logical Block
	 *        :    size, that file will not zisofs'ed since it does
	 *        :    reduce an ISO-image size.
	 *        :
	 *        :    When you specify option 'boot=<boot-image>', that
	 *        :    'boot-image' file won't be converted to zisofs file.
	 * Type   : boolean
	 * Default: Disabled
	 *
	 * Generates RRIP 'ZF' System Use Entry.
	 */
	unsigned int	 zisofs:1;
#define OPT_ZISOFS_DISABLED		0
#define OPT_ZISOFS_DIRECT		1
#define OPT_ZISOFS_DEFAULT		OPT_ZISOFS_DISABLED

};

struct iso9660 {
	/* The creation time of ISO image. */
	time_t			 birth_time;
	/* A file stream of a temporary file, which file contents
	 * save to until ISO image can be created. */
	int			 temp_fd;

	struct isofile		*cur_file;
	struct isoent		*cur_dirent;
	struct archive_string	 cur_dirstr;
	uint64_t		 bytes_remaining;
	int			 need_multi_extent;

	/* Temporary string buffer for Joliet extension. */
	struct archive_string	 utf16be;
	struct archive_string	 mbs;

	struct archive_string_conv *sconv_to_utf16be;
	struct archive_string_conv *sconv_from_utf16be;

	/* A list of all of struct isofile entries. */
	struct {
		struct isofile	*first;
		struct isofile	**last;
	}			 all_file_list;

	/* A list of struct isofile entries which have its
	 * contents and are not a directory, a hardlinked file
	 * and a symlink file. */
	struct {
		struct isofile	*first;
		struct isofile	**last;
	}			 data_file_list;

	/* Used for managing to find hardlinking files. */
	struct archive_rb_tree	 hardlink_rbtree;

	/* Used for making the Path Table Record. */
	struct vdd {
		/* the root of entry tree. */
		struct isoent	*rootent;
		enum vdd_type {
			VDD_PRIMARY,
			VDD_JOLIET,
			VDD_ENHANCED
		} vdd_type;

		struct path_table {
			struct isoent		*first;
			struct isoent		**last;
			struct isoent		**sorted;
			int			 cnt;
		} *pathtbl;
		int				 max_depth;

		int		 path_table_block;
		int		 path_table_size;
		int		 location_type_L_path_table;
		int		 location_type_M_path_table;
		int		 total_dir_block;
	} primary, joliet;

	/* Used for making a Volume Descriptor. */
	int			 volume_space_size;
	int			 volume_sequence_number;
	int			 total_file_block;
	struct archive_string	 volume_identifier;
	struct archive_string	 publisher_identifier;
	struct archive_string	 data_preparer_identifier;
	struct archive_string	 application_identifier;
	struct archive_string	 copyright_file_identifier;
	struct archive_string	 abstract_file_identifier;
	struct archive_string	 bibliographic_file_identifier;

	/* Used for making rockridge extensions. */
	int			 location_rrip_er;

	/* Used for making zisofs. */
	struct {
		unsigned int	 detect_magic:1;
		unsigned int	 making:1;
		unsigned int	 allzero:1;
		unsigned char	 magic_buffer[64];
		int		 magic_cnt;

#ifdef HAVE_ZLIB_H
		/*
		 * Copy a compressed file to iso9660.zisofs.temp_fd
		 * and also copy a uncompressed file(original file) to
		 * iso9660.temp_fd . If the number of logical block
		 * of the compressed file is less than the number of
		 * logical block of the uncompressed file, use it and
		 * remove the copy of the uncompressed file.
		 * but if not, we use uncompressed file and remove
		 * the copy of the compressed file.
		 */
		uint32_t	*block_pointers;
		size_t		 block_pointers_allocated;
		int		 block_pointers_cnt;
		int		 block_pointers_idx;
		int64_t		 total_size;
		int64_t		 block_offset;

		z_stream	 stream;
		int		 stream_valid;
		int64_t		 remaining;
		int		 compression_level;
#endif
	} zisofs;

	struct isoent		*directories_too_deep;
	int			 dircnt_max;

	/* Write buffer. */
#define wb_buffmax()	(LOGICAL_BLOCK_SIZE * 32)
#define wb_remaining(a)	(((struct iso9660 *)(a)->format_data)->wbuff_remaining)
#define wb_offset(a)	(((struct iso9660 *)(a)->format_data)->wbuff_offset \
		+ wb_buffmax() - wb_remaining(a))
	unsigned char		 wbuff[LOGICAL_BLOCK_SIZE * 32];
	size_t			 wbuff_remaining;
	enum {
		WB_TO_STREAM,
		WB_TO_TEMP
	} 			 wbuff_type;
	int64_t			 wbuff_offset;
	int64_t			 wbuff_written;
	int64_t			 wbuff_tail;

	/* 'El Torito' boot data. */
	struct {
		/* boot catalog file */
		struct archive_string	 catalog_filename;
		struct isoent		*catalog;
		/* boot image file */
		struct archive_string	 boot_filename;
		struct isoent		*boot;

		unsigned char		 platform_id;
#define BOOT_PLATFORM_X86	0
#define BOOT_PLATFORM_PPC	1
#define BOOT_PLATFORM_MAC	2
		struct archive_string	 id;
		unsigned char		 media_type;
#define BOOT_MEDIA_NO_EMULATION		0
#define BOOT_MEDIA_1_2M_DISKETTE	1
#define BOOT_MEDIA_1_44M_DISKETTE	2
#define BOOT_MEDIA_2_88M_DISKETTE	3
#define BOOT_MEDIA_HARD_DISK		4
		unsigned char		 system_type;
		uint16_t		 boot_load_seg;
		uint16_t		 boot_load_size;
#define BOOT_LOAD_SIZE		4
	} el_torito;

	struct iso_option	 opt;
};

/*
 * Types of Volume Descriptor
 */
enum VD_type {
	VDT_BOOT_RECORD=0,	/* Boot Record Volume Descriptor 	*/
	VDT_PRIMARY=1,		/* Primary Volume Descriptor		*/
	VDT_SUPPLEMENTARY=2,	/* Supplementary Volume Descriptor	*/
	VDT_TERMINATOR=255	/* Volume Descriptor Set Terminator	*/
};

/*
 * Types of Directory Record
 */
enum dir_rec_type {
	DIR_REC_VD,		/* Stored in Volume Descriptor.	*/
	DIR_REC_SELF,		/* Stored as Current Directory.	*/
	DIR_REC_PARENT,		/* Stored as Parent Directory.	*/
	DIR_REC_NORMAL 		/* Stored as Child.		*/
};

/*
 * Kinds of Volume Descriptor Character
 */
enum vdc {
	VDC_STD,
	VDC_LOWERCASE,
	VDC_UCS2,
	VDC_UCS2_DIRECT
};

/*
 * IDentifier Resolver.
 * Used for resolving duplicated filenames.
 */
struct idr {
	struct idrent {
		struct archive_rb_node	rbnode;
		/* Used in wait_list. */
		struct idrent		*wnext;
		struct idrent		*avail;

		struct isoent		*isoent;
		int			 weight;
		int			 noff;
		int			 rename_num;
	} *idrent_pool;

	struct archive_rb_tree		 rbtree;

	struct {
		struct idrent		*first;
		struct idrent		**last;
	} wait_list;

	int				 pool_size;
	int				 pool_idx;
	int				 num_size;
	int				 null_size;

	char				 char_map[0x80];
};

enum char_type {
	A_CHAR,
	D_CHAR
};


static int	iso9660_options(struct archive_write *,
		    const char *, const char *);
static int	iso9660_write_header(struct archive_write *,
		    struct archive_entry *);
static ssize_t	iso9660_write_data(struct archive_write *,
		    const void *, size_t);
static int	iso9660_finish_entry(struct archive_write *);
static int	iso9660_close(struct archive_write *);
static int	iso9660_free(struct archive_write *);

static void	get_system_identifier(char *, size_t);
static void	set_str(unsigned char *, const char *, size_t, char,
		    const char *);
static inline int joliet_allowed_char(unsigned char, unsigned char);
static int	set_str_utf16be(struct archive_write *, unsigned char *,
			const char *, size_t, uint16_t, enum vdc);
static int	set_str_a_characters_bp(struct archive_write *,
			unsigned char *, int, int, const char *, enum vdc);
static int	set_str_d_characters_bp(struct archive_write *,
			unsigned char *, int, int, const char *, enum  vdc);
static void	set_VD_bp(unsigned char *, enum VD_type, unsigned char);
static inline void set_unused_field_bp(unsigned char *, int, int);

static unsigned char *extra_open_record(unsigned char *, int,
		    struct isoent *, struct ctl_extr_rec *);
static void	extra_close_record(struct ctl_extr_rec *, int);
static unsigned char * extra_next_record(struct ctl_extr_rec *, int);
static unsigned char *extra_get_record(struct isoent *, int *, int *, int *);
static void	extra_tell_used_size(struct ctl_extr_rec *, int);
static int	extra_setup_location(struct isoent *, int);
static int	set_directory_record_rr(unsigned char *, int,
		    struct isoent *, struct iso9660 *, enum dir_rec_type);
static int	set_directory_record(unsigned char *, size_t,
		    struct isoent *, struct iso9660 *, enum dir_rec_type,
		    enum vdd_type);
static inline int get_dir_rec_size(struct iso9660 *, struct isoent *,
		    enum dir_rec_type, enum vdd_type);
static inline unsigned char *wb_buffptr(struct archive_write *);
static int	wb_write_out(struct archive_write *);
static int	wb_consume(struct archive_write *, size_t);
#ifdef HAVE_ZLIB_H
static int	wb_set_offset(struct archive_write *, int64_t);
#endif
static int	write_null(struct archive_write *, size_t);
static int	write_VD_terminator(struct archive_write *);
static int	set_file_identifier(unsigned char *, int, int, enum vdc,
		    struct archive_write *, struct vdd *,
		    struct archive_string *, const char *, int,
		    enum char_type);
static int	write_VD(struct archive_write *, struct vdd *);
static int	write_VD_boot_record(struct archive_write *);
static int	write_information_block(struct archive_write *);
static int	write_path_table(struct archive_write *, int,
		    struct vdd *);
static int	write_directory_descriptors(struct archive_write *,
		    struct vdd *);
static int	write_file_descriptors(struct archive_write *);
static int	write_rr_ER(struct archive_write *);
static void	calculate_path_table_size(struct vdd *);

static void	isofile_init_entry_list(struct iso9660 *);
static void	isofile_add_entry(struct iso9660 *, struct isofile *);
static void	isofile_free_all_entries(struct iso9660 *);
static void	isofile_init_entry_data_file_list(struct iso9660 *);
static void	isofile_add_data_file(struct iso9660 *, struct isofile *);
static struct isofile * isofile_new(struct archive_write *,
		    struct archive_entry *);
static void	isofile_free(struct isofile *);
static int	isofile_gen_utility_names(struct archive_write *,
		    struct isofile *);
static int	isofile_register_hardlink(struct archive_write *,
		    struct isofile *);
static void	isofile_connect_hardlink_files(struct iso9660 *);
static void	isofile_init_hardlinks(struct iso9660 *);
static void	isofile_free_hardlinks(struct iso9660 *);

static struct isoent *isoent_new(struct isofile *);
static int	isoent_clone_tree(struct archive_write *,
		    struct isoent **, struct isoent *);
static void	_isoent_free(struct isoent *isoent);
static void	isoent_free_all(struct isoent *);
static struct isoent * isoent_create_virtual_dir(struct archive_write *,
		    struct iso9660 *, const char *);
static int	isoent_cmp_node(const struct archive_rb_node *,
		    const struct archive_rb_node *);
static int	isoent_cmp_key(const struct archive_rb_node *,
		    const void *);
static int	isoent_add_child_head(struct isoent *, struct isoent *);
static int	isoent_add_child_tail(struct isoent *, struct isoent *);
static void	isoent_remove_child(struct isoent *, struct isoent *);
static void	isoent_setup_directory_location(struct iso9660 *,
		    int, struct vdd *);
static void	isoent_setup_file_location(struct iso9660 *, int);
static int	get_path_component(char *, size_t, const char *);
static int	isoent_tree(struct archive_write *, struct isoent **);
static struct isoent *isoent_find_child(struct isoent *, const char *);
static struct isoent *isoent_find_entry(struct isoent *, const char *);
static void	idr_relaxed_filenames(char *);
static void	idr_init(struct iso9660 *, struct vdd *, struct idr *);
static void	idr_cleanup(struct idr *);
static int	idr_ensure_poolsize(struct archive_write *, struct idr *,
		    int);
static int	idr_start(struct archive_write *, struct idr *,
		    int, int, int, int, const struct archive_rb_tree_ops *);
static void	idr_register(struct idr *, struct isoent *, int,
		    int);
static void	idr_extend_identifier(struct idrent *, int, int);
static void	idr_resolve(struct idr *, void (*)(unsigned char *, int));
static void	idr_set_num(unsigned char *, int);
static void	idr_set_num_beutf16(unsigned char *, int);
static int	isoent_gen_iso9660_identifier(struct archive_write *,
		    struct isoent *, struct idr *);
static int	isoent_gen_joliet_identifier(struct archive_write *,
		    struct isoent *, struct idr *);
static int	isoent_cmp_iso9660_identifier(const struct isoent *,
		    const struct isoent *);
static int	isoent_cmp_node_iso9660(const struct archive_rb_node *,
		    const struct archive_rb_node *);
static int	isoent_cmp_key_iso9660(const struct archive_rb_node *,
		    const void *);
static int	isoent_cmp_joliet_identifier(const struct isoent *,
		    const struct isoent *);
static int	isoent_cmp_node_joliet(const struct archive_rb_node *,
		    const struct archive_rb_node *);
static int	isoent_cmp_key_joliet(const struct archive_rb_node *,
		    const void *);
static inline void path_table_add_entry(struct path_table *, struct isoent *);
static inline struct isoent * path_table_last_entry(struct path_table *);
static int	isoent_make_path_table(struct archive_write *);
static int	isoent_find_out_boot_file(struct archive_write *,
		    struct isoent *);
static int	isoent_create_boot_catalog(struct archive_write *,
		    struct isoent *);
static size_t	fd_boot_image_size(int);
static int	make_boot_catalog(struct archive_write *);
static int	setup_boot_information(struct archive_write *);

static int	zisofs_init(struct archive_write *, struct isofile *);
static void	zisofs_detect_magic(struct archive_write *,
		    const void *, size_t);
static int	zisofs_write_to_temp(struct archive_write *,
		    const void *, size_t);
static int	zisofs_finish_entry(struct archive_write *);
static int	zisofs_rewind_boot_file(struct archive_write *);
static int	zisofs_free(struct archive_write *);

int
archive_write_set_format_iso9660(struct archive *_a)
{
	struct archive_write *a = (struct archive_write *)_a;
	struct iso9660 *iso9660;

	archive_check_magic(_a, ARCHIVE_WRITE_MAGIC,
	    ARCHIVE_STATE_NEW, "archive_write_set_format_iso9660");

	/* If another format was already registered, unregister it. */
	if (a->format_free != NULL)
		(a->format_free)(a);

	iso9660 = calloc(1, sizeof(*iso9660));
	if (iso9660 == NULL) {
		archive_set_error(&a->archive, ENOMEM,
		    "Can't allocate iso9660 data");
		return (ARCHIVE_FATAL);
	}
	iso9660->birth_time = 0;
	iso9660->temp_fd = -1;
	iso9660->cur_file = NULL;
	iso9660->primary.max_depth = 0;
	iso9660->primary.vdd_type = VDD_PRIMARY;
	iso9660->primary.pathtbl = NULL;
	iso9660->joliet.rootent = NULL;
	iso9660->joliet.max_depth = 0;
	iso9660->joliet.vdd_type = VDD_JOLIET;
	iso9660->joliet.pathtbl = NULL;
	isofile_init_entry_list(iso9660);
	isofile_init_entry_data_file_list(iso9660);
	isofile_init_hardlinks(iso9660);
	iso9660->directories_too_deep = NULL;
	iso9660->dircnt_max = 1;
	iso9660->wbuff_remaining = wb_buffmax();
	iso9660->wbuff_type = WB_TO_TEMP;
	iso9660->wbuff_offset = 0;
	iso9660->wbuff_written = 0;
	iso9660->wbuff_tail = 0;
	archive_string_init(&(iso9660->utf16be));
	archive_string_init(&(iso9660->mbs));

	/*
	 * Init Identifiers used for PVD and SVD.
	 */
	archive_string_init(&(iso9660->volume_identifier));
	archive_strcpy(&(iso9660->volume_identifier), "CDROM");
	archive_string_init(&(iso9660->publisher_identifier));
	archive_string_init(&(iso9660->data_preparer_identifier));
	archive_string_init(&(iso9660->application_identifier));
	archive_strcpy(&(iso9660->application_identifier),
	    archive_version_string());
	archive_string_init(&(iso9660->copyright_file_identifier));
	archive_string_init(&(iso9660->abstract_file_identifier));
	archive_string_init(&(iso9660->bibliographic_file_identifier));

	/*
	 * Init El Torito bootable CD variables.
	 */
	archive_string_init(&(iso9660->el_torito.catalog_filename));
	iso9660->el_torito.catalog = NULL;
	/* Set default file name of boot catalog  */
	archive_strcpy(&(iso9660->el_torito.catalog_filename),
	    "boot.catalog");
	archive_string_init(&(iso9660->el_torito.boot_filename));
	iso9660->el_torito.boot = NULL;
	iso9660->el_torito.platform_id = BOOT_PLATFORM_X86;
	archive_string_init(&(iso9660->el_torito.id));
	iso9660->el_torito.boot_load_seg = 0;
	iso9660->el_torito.boot_load_size = BOOT_LOAD_SIZE;

	/*
	 * Init zisofs variables.
	 */
#ifdef HAVE_ZLIB_H
	iso9660->zisofs.block_pointers = NULL;
	iso9660->zisofs.block_pointers_allocated = 0;
	iso9660->zisofs.stream_valid = 0;
	iso9660->zisofs.compression_level = 9;
	memset(&(iso9660->zisofs.stream), 0,
	    sizeof(iso9660->zisofs.stream));
#endif

	/*
	 * Set default value of iso9660 options.
	 */
	iso9660->opt.abstract_file = OPT_ABSTRACT_FILE_DEFAULT;
	iso9660->opt.application_id = OPT_APPLICATION_ID_DEFAULT;
	iso9660->opt.allow_vernum = OPT_ALLOW_VERNUM_DEFAULT;
	iso9660->opt.biblio_file = OPT_BIBLIO_FILE_DEFAULT;
	iso9660->opt.boot = OPT_BOOT_DEFAULT;
	iso9660->opt.boot_catalog = OPT_BOOT_CATALOG_DEFAULT;
	iso9660->opt.boot_info_table = OPT_BOOT_INFO_TABLE_DEFAULT;
	iso9660->opt.boot_load_seg = OPT_BOOT_LOAD_SEG_DEFAULT;
	iso9660->opt.boot_load_size = OPT_BOOT_LOAD_SIZE_DEFAULT;
	iso9660->opt.boot_type = OPT_BOOT_TYPE_DEFAULT;
	iso9660->opt.compression_level = OPT_COMPRESSION_LEVEL_DEFAULT;
	iso9660->opt.copyright_file = OPT_COPYRIGHT_FILE_DEFAULT;
	iso9660->opt.iso_level = OPT_ISO_LEVEL_DEFAULT;
	iso9660->opt.joliet = OPT_JOLIET_DEFAULT;
	iso9660->opt.limit_depth = OPT_LIMIT_DEPTH_DEFAULT;
	iso9660->opt.limit_dirs = OPT_LIMIT_DIRS_DEFAULT;
	iso9660->opt.pad = OPT_PAD_DEFAULT;
	iso9660->opt.publisher = OPT_PUBLISHER_DEFAULT;
	iso9660->opt.rr = OPT_RR_DEFAULT;
	iso9660->opt.volume_id = OPT_VOLUME_ID_DEFAULT;
	iso9660->opt.zisofs = OPT_ZISOFS_DEFAULT;

	/* Create the root directory. */
	iso9660->primary.rootent =
	    isoent_create_virtual_dir(a, iso9660, "");
	if (iso9660->primary.rootent == NULL) {
		free(iso9660);
		archive_set_error(&a->archive, ENOMEM,
		    "Can't allocate memory");
		return (ARCHIVE_FATAL);
	}
	iso9660->primary.rootent->parent = iso9660->primary.rootent;
	iso9660->cur_dirent = iso9660->primary.rootent;
	archive_string_init(&(iso9660->cur_dirstr));
	archive_string_ensure(&(iso9660->cur_dirstr), 1);
	iso9660->cur_dirstr.s[0] = 0;
	iso9660->sconv_to_utf16be = NULL;
	iso9660->sconv_from_utf16be = NULL;

	a->format_data = iso9660;
	a->format_name = "iso9660";
	a->format_options = iso9660_options;
	a->format_write_header = iso9660_write_header;
	a->format_write_data = iso9660_write_data;
	a->format_finish_entry = iso9660_finish_entry;
	a->format_close = iso9660_close;
	a->format_free = iso9660_free;
	a->archive.archive_format = ARCHIVE_FORMAT_ISO9660;
	a->archive.archive_format_name = "ISO9660";

	return (ARCHIVE_OK);
}

static int
get_str_opt(struct archive_write *a, struct archive_string *s,
    size_t maxsize, const char *key, const char *value)
{

	if (strlen(value) > maxsize) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
		    "Value is longer than %zu characters "
		    "for option ``%s''", maxsize, key);
		return (ARCHIVE_FATAL);
	}
	archive_strcpy(s, value);
	return (ARCHIVE_OK);
}

static int
get_num_opt(struct archive_write *a, int *num, int high, int low,
    const char *key, const char *value)
{
	const char *p = value;
	int data = 0;
	int neg = 0;

	if (p == NULL) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
		    "Invalid value(empty) for option ``%s''", key);
		return (ARCHIVE_FATAL);
	}
	if (*p == '-') {
		neg = 1;
		p++;
	}
	while (*p) {
		if (*p >= '0' && *p <= '9')
			data = data * 10 + *p - '0';
		else {
			archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
			    "Invalid value for option ``%s''", key);
			return (ARCHIVE_FATAL);
		}
		if (data > high) {
			archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
			    "Invalid value(over %d) for "
			    "option ``%s''", high, key);
			return (ARCHIVE_FATAL);
		}
		if (data < low) {
			archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
			    "Invalid value(under %d) for "
			    "option ``%s''", low, key);
			return (ARCHIVE_FATAL);
		}
		p++;
	}
	if (neg)
		data *= -1;
	*num = data;

	return (ARCHIVE_OK);
}

static int
iso9660_options(struct archive_write *a, const char *key, const char *value)
{
	struct iso9660 *iso9660 = a->format_data;
	const char *p;
	int r;

	switch (key[0]) {
	case 'a':
		if (strcmp(key, "abstract-file") == 0) {
			r = get_str_opt(a,
			    &(iso9660->abstract_file_identifier),
			    ABSTRACT_FILE_SIZE, key, value);
			iso9660->opt.abstract_file = r == ARCHIVE_OK;
			return (r);
		}
		if (strcmp(key, "application-id") == 0) {
			r = get_str_opt(a,
			    &(iso9660->application_identifier),
			    APPLICATION_IDENTIFIER_SIZE, key, value);
			iso9660->opt.application_id = r == ARCHIVE_OK;
			return (r);
		}
		if (strcmp(key, "allow-vernum") == 0) {
			iso9660->opt.allow_vernum = value != NULL;
			return (ARCHIVE_OK);
		}
		break;
	case 'b':
		if (strcmp(key, "biblio-file") == 0) {
			r = get_str_opt(a,
			    &(iso9660->bibliographic_file_identifier),
			    BIBLIO_FILE_SIZE, key, value);
			iso9660->opt.biblio_file = r == ARCHIVE_OK;
			return (r);
		}
		if (strcmp(key, "boot") == 0) {
			if (value == NULL)
				iso9660->opt.boot = 0;
			else {
				iso9660->opt.boot = 1;
				archive_strcpy(
				    &(iso9660->el_torito.boot_filename),
				    value);
			}
			return (ARCHIVE_OK);
		}
		if (strcmp(key, "boot-catalog") == 0) {
			r = get_str_opt(a,
			    &(iso9660->el_torito.catalog_filename),
			    1024, key, value);
			iso9660->opt.boot_catalog = r == ARCHIVE_OK;
			return (r);
		}
		if (strcmp(key, "boot-info-table") == 0) {
			iso9660->opt.boot_info_table = value != NULL;
			return (ARCHIVE_OK);
		}
		if (strcmp(key, "boot-load-seg") == 0) {
			uint32_t seg;

			iso9660->opt.boot_load_seg = 0;
			if (value == NULL)
				goto invalid_value;
			seg = 0;
			p = value;
			if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X'))
				p += 2;
			while (*p) {
				if (seg)
					seg <<= 4;
				if (*p >= 'A' && *p <= 'F')
					seg += *p - 'A' + 0x0a;
				else if (*p >= 'a' && *p <= 'f')
					seg += *p - 'a' + 0x0a;
				else if (*p >= '0' && *p <= '9')
					seg += *p - '0';
				else
					goto invalid_value;
				if (seg > 0xffff) {
					archive_set_error(&a->archive,
					    ARCHIVE_ERRNO_MISC,
					    "Invalid value(over 0xffff) for "
					    "option ``%s''", key);
					return (ARCHIVE_FATAL);
				}
				p++;
			}
			iso9660->el_torito.boot_load_seg = (uint16_t)seg;
			iso9660->opt.boot_load_seg = 1;
			return (ARCHIVE_OK);
		}
		if (strcmp(key, "boot-load-size") == 0) {
			int num = 0;
			r = get_num_opt(a, &num, 0xffff, 1, key, value);
			iso9660->opt.boot_load_size = r == ARCHIVE_OK;
			if (r != ARCHIVE_OK)
				return (ARCHIVE_FATAL);
			iso9660->el_torito.boot_load_size = (uint16_t)num;
			return (ARCHIVE_OK);
		}
		if (strcmp(key, "boot-type") == 0) {
			if (value == NULL)
				goto invalid_value;
			if (strcmp(value, "no-emulation") == 0)
				iso9660->opt.boot_type = OPT_BOOT_TYPE_NO_EMU;
			else if (strcmp(value, "fd") == 0)
				iso9660->opt.boot_type = OPT_BOOT_TYPE_FD;
			else if (strcmp(value, "hard-disk") == 0)
				iso9660->opt.boot_type = OPT_BOOT_TYPE_HARD_DISK;
			else
				goto invalid_value;
			return (ARCHIVE_OK);
		}
		break;
	case 'c':
		if (strcmp(key, "compression-level") == 0) {
#ifdef HAVE_ZLIB_H
			if (value == NULL ||
			    !(value[0] >= '0' && value[0] <= '9') ||
			    value[1] != '\0')
				goto invalid_value;
                	iso9660->zisofs.compression_level = value[0] - '0';
			iso9660->opt.compression_level = 1;
                	return (ARCHIVE_OK);
#else
			archive_set_error(&a->archive,
			    ARCHIVE_ERRNO_MISC,
			    "Option ``%s'' "
			    "is not supported on this platform.", key);
			return (ARCHIVE_FATAL);
#endif
		}
		if (strcmp(key, "copyright-file") == 0) {
			r = get_str_opt(a,
			    &(iso9660->copyright_file_identifier),
			    COPYRIGHT_FILE_SIZE, key, value);
			iso9660->opt.copyright_file = r == ARCHIVE_OK;
			return (r);
		}
#ifdef DEBUG
		/* Specifies Volume creation date and time;
		 * year(4),month(2),day(2),hour(2),minute(2),second(2).
		 * e.g. "20090929033757"
		 */
		if (strcmp(key, "creation") == 0) {
			struct tm tm;
			char buf[5];

			p = value;
			if (p == NULL || strlen(p) < 14)
				goto invalid_value;
			memset(&tm, 0, sizeof(tm));
			memcpy(buf, p, 4); buf[4] = '\0'; p += 4;
			tm.tm_year = strtol(buf, NULL, 10) - 1900;
			memcpy(buf, p, 2); buf[2] = '\0'; p += 2;
			tm.tm_mon = strtol(buf, NULL, 10) - 1;
			memcpy(buf, p, 2); buf[2] = '\0'; p += 2;
			tm.tm_mday = strtol(buf, NULL, 10);
			memcpy(buf, p, 2); buf[2] = '\0'; p += 2;
			tm.tm_hour = strtol(buf, NULL, 10);
			memcpy(buf, p, 2); buf[2] = '\0'; p += 2;
			tm.tm_min = strtol(buf, NULL, 10);
			memcpy(buf, p, 2); buf[2] = '\0';
			tm.tm_sec = strtol(buf, NULL, 10);
			iso9660->birth_time = mktime(&tm);
			return (ARCHIVE_OK);
		}
#endif
		break;
	case 'i':
		if (strcmp(key, "iso-level") == 0) {
			if (value != NULL && value[1] == '\0' &&
			    (value[0] >= '1' && value[0] <= '4')) {
				iso9660->opt.iso_level = value[0]-'0';
				return (ARCHIVE_OK);
			}
			goto invalid_value;
		}
		break;
	case 'j':
		if (strcmp(key, "joliet") == 0) {
			if (value == NULL)
				iso9660->opt.joliet = OPT_JOLIET_DISABLE;
			else if (strcmp(value, "1") == 0)
				iso9660->opt.joliet = OPT_JOLIET_ENABLE;
			else if (strcmp(value, "long") == 0)
				iso9660->opt.joliet = OPT_JOLIET_LONGNAME;
			else
				goto invalid_value;
			return (ARCHIVE_OK);
		}
		break;
	case 'l':
		if (strcmp(key, "limit-depth") == 0) {
			iso9660->opt.limit_depth = value != NULL;
			return (ARCHIVE_OK);
		}
		if (strcmp(key, "limit-dirs") == 0) {
			iso9660->opt.limit_dirs = value != NULL;
			return (ARCHIVE_OK);
		}
		break;
	case 'p':
		if (strcmp(key, "pad") == 0) {
			iso9660->opt.pad = value != NULL;
			return (ARCHIVE_OK);
		}
		if (strcmp(key, "publisher") == 0) {
			r = get_str_opt(a,
			    &(iso9660->publisher_identifier),
			    PUBLISHER_IDENTIFIER_SIZE, key, value);
			iso9660->opt.publisher = r == ARCHIVE_OK;
			return (r);
		}
		break;
	case 'r':
		if (strcmp(key, "rockridge") == 0 ||
		    strcmp(key, "Rockridge") == 0) {
			if (value == NULL)
				iso9660->opt.rr = OPT_RR_DISABLED;
			else if (strcmp(value, "1") == 0)
				iso9660->opt.rr = OPT_RR_USEFUL;
			else if (strcmp(value, "strict") == 0)
				iso9660->opt.rr = OPT_RR_STRICT;
			else if (strcmp(value, "useful") == 0)
				iso9660->opt.rr = OPT_RR_USEFUL;
			else
				goto invalid_value;
			return (ARCHIVE_OK);
		}
		break;
	case 'v':
		if (strcmp(key, "volume-id") == 0) {
			r = get_str_opt(a, &(iso9660->volume_identifier),
			    VOLUME_IDENTIFIER_SIZE, key, value);
			iso9660->opt.volume_id = r == ARCHIVE_OK;
			return (r);
		}
		break;
	case 'z':
		if (strcmp(key, "zisofs") == 0) {
			if (value == NULL)
				iso9660->opt.zisofs = OPT_ZISOFS_DISABLED;
			else {
#ifdef HAVE_ZLIB_H
				iso9660->opt.zisofs = OPT_ZISOFS_DIRECT;
#else
				archive_set_error(&a->archive,
				    ARCHIVE_ERRNO_MISC,
				    "``zisofs'' "
				    "is not supported on this platform.");
				return (ARCHIVE_FATAL);
#endif
			}
			return (ARCHIVE_OK);
		}
		break;
	}

	/* Note: The "warn" return is just to inform the options
	 * supervisor that we didn't handle it.  It will generate
	 * a suitable error if no one used this option. */
	return (ARCHIVE_WARN);

invalid_value:
	archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
	    "Invalid value for option ``%s''", key);
	return (ARCHIVE_FAILED);
}

static int
iso9660_write_header(struct archive_write *a, struct archive_entry *entry)
{
	struct iso9660 *iso9660;
	struct isofile *file;
	struct isoent *isoent;
	int r, ret = ARCHIVE_OK;

	iso9660 = a->format_data;

	iso9660->cur_file = NULL;
	iso9660->bytes_remaining = 0;
	iso9660->need_multi_extent = 0;
	if (archive_entry_filetype(entry) == AE_IFLNK
	    && iso9660->opt.rr == OPT_RR_DISABLED) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
		    "Ignore symlink file.");
		iso9660->cur_file = NULL;
		return (ARCHIVE_WARN);
	}
	if (archive_entry_filetype(entry) == AE_IFREG &&
	    archive_entry_size(entry) >= MULTI_EXTENT_SIZE) {
		if (iso9660->opt.iso_level < 3) {
			archive_set_error(&a->archive,
			    ARCHIVE_ERRNO_MISC,
			    "Ignore over %lld bytes file. "
			    "This file too large.",
			    MULTI_EXTENT_SIZE);
				iso9660->cur_file = NULL;
			return (ARCHIVE_WARN);
		}
		iso9660->need_multi_extent = 1;
	}

	file = isofile_new(a, entry);
	if (file == NULL) {
		archive_set_error(&a->archive, ENOMEM,
		    "Can't allocate data");
		return (ARCHIVE_FATAL);
	}
	r = isofile_gen_utility_names(a, file);
	if (r < ARCHIVE_WARN) {
		isofile_free(file);
		return (r);
	}
	else if (r < ret)
		ret = r;

	/*
	 * Ignore a path which looks like the top of directory name
	 * since we have already made the root directory of an ISO image.
	 */
	if (archive_strlen(&(file->parentdir)) == 0 &&
	    archive_strlen(&(file->basename)) == 0) {
		isofile_free(file);
		return (r);
	}

	isofile_add_entry(iso9660, file);
	isoent = isoent_new(file);
	if (isoent == NULL) {
		archive_set_error(&a->archive, ENOMEM,
		    "Can't allocate data");
		return (ARCHIVE_FATAL);
	}
	if (isoent->file->dircnt > iso9660->dircnt_max)
		iso9660->dircnt_max = isoent->file->dircnt;

	/* Add the current file into tree */
	r = isoent_tree(a, &isoent);
	if (r != ARCHIVE_OK)
		return (r);

	/* If there is the same file in tree and
	 * the current file is older than the file in tree.
	 * So we don't need the current file data anymore. */
	if (isoent->file != file)
		return (ARCHIVE_OK);

	/* Non regular files contents are unneeded to be saved to
	 * temporary files. */
	if (archive_entry_filetype(file->entry) != AE_IFREG)
		return (ret);

	/*
	 * Set the current file to cur_file to read its contents.
	 */
	iso9660->cur_file = file;

	if (archive_entry_nlink(file->entry) > 1) {
		r = isofile_register_hardlink(a, file);
		if (r != ARCHIVE_OK)
			return (ARCHIVE_FATAL);
	}

	/*
	 * Prepare to save the contents of the file.
	 */
	if (iso9660->temp_fd < 0) {
		iso9660->temp_fd = __archive_mktemp(NULL);
		if (iso9660->temp_fd < 0) {
			archive_set_error(&a->archive, errno,
			    "Couldn't create temporary file");
			return (ARCHIVE_FATAL);
		}
	}

	/* Save an offset of current file in temporary file. */
	file->content.offset_of_temp = wb_offset(a);
	file->cur_content = &(file->content);
	r = zisofs_init(a, file);
	if (r < ret)
		ret = r;
	iso9660->bytes_remaining =  archive_entry_size(file->entry);

	return (ret);
}

static int
write_to_temp(struct archive_write *a, const void *buff, size_t s)
{
	struct iso9660 *iso9660 = a->format_data;
	ssize_t written;
	const unsigned char *b;

	b = (const unsigned char *)buff;
	while (s) {
		written = write(iso9660->temp_fd, b, s);
		if (written < 0) {
			archive_set_error(&a->archive, errno,
			    "Can't write to temporary file");
			return (ARCHIVE_FATAL);
		}
		s -= written;
		b += written;
	}
	return (ARCHIVE_OK);
}

static int
wb_write_to_temp(struct archive_write *a, const void *buff, size_t s)
{
	const char *xp = buff;
	size_t xs = s;

	/*
	 * If a written data size is big enough to use system-call
	 * and there is no waiting data, this calls write_to_temp() in
	 * order to reduce a extra memory copy.
	 */
	if (wb_remaining(a) == wb_buffmax() && s > (1024 * 16)) {
		struct iso9660 *iso9660 = (struct iso9660 *)a->format_data;
		xs = s % LOGICAL_BLOCK_SIZE;
		iso9660->wbuff_offset += s - xs;
		if (write_to_temp(a, buff, s - xs) != ARCHIVE_OK)
			return (ARCHIVE_FATAL);
		if (xs == 0)
			return (ARCHIVE_OK);
		xp += s - xs;
	}

	while (xs) {
		size_t size = xs;
		if (size > wb_remaining(a))
			size = wb_remaining(a);
		memcpy(wb_buffptr(a), xp, size);
		if (wb_consume(a, size) != ARCHIVE_OK)
			return (ARCHIVE_FATAL);
		xs -= size;
		xp += size;
	}
	return (ARCHIVE_OK);
}

static int
wb_write_padding_to_temp(struct archive_write *a, int64_t csize)
{
	size_t ns;
	int ret;

	ns = (size_t)(csize % LOGICAL_BLOCK_SIZE);
	if (ns != 0)
		ret = write_null(a, LOGICAL_BLOCK_SIZE - ns);
	else
		ret = ARCHIVE_OK;
	return (ret);
}

static ssize_t
write_iso9660_data(struct archive_write *a, const void *buff, size_t s)
{
	struct iso9660 *iso9660 = a->format_data;
	size_t ws;

	if (iso9660->temp_fd < 0) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
		    "Couldn't create temporary file");
		return (ARCHIVE_FATAL);
	}

	ws = s;
	if (iso9660->need_multi_extent &&
	    (iso9660->cur_file->cur_content->size + ws) >=
	      (MULTI_EXTENT_SIZE - LOGICAL_BLOCK_SIZE)) {
		struct content *con;
		size_t ts;

		ts = (size_t)(MULTI_EXTENT_SIZE - LOGICAL_BLOCK_SIZE -
		    iso9660->cur_file->cur_content->size);

		if (iso9660->zisofs.detect_magic)
			zisofs_detect_magic(a, buff, ts);

		if (iso9660->zisofs.making) {
			if (zisofs_write_to_temp(a, buff, ts) != ARCHIVE_OK)
				return (ARCHIVE_FATAL);
		} else {
			if (wb_write_to_temp(a, buff, ts) != ARCHIVE_OK)
				return (ARCHIVE_FATAL);
			iso9660->cur_file->cur_content->size += ts;
		}

		/* Write padding. */
		if (wb_write_padding_to_temp(a,
		    iso9660->cur_file->cur_content->size) != ARCHIVE_OK)
			return (ARCHIVE_FATAL);

		/* Compute the logical block number. */
		iso9660->cur_file->cur_content->blocks = (int)
		    ((iso9660->cur_file->cur_content->size
		     + LOGICAL_BLOCK_SIZE -1) >> LOGICAL_BLOCK_BITS);

		/*
		 * Make next extent.
		 */
		ws -= ts;
		buff = (const void *)(((const unsigned char *)buff) + ts);
		/* Make a content for next extent. */
		con = calloc(1, sizeof(*con));
		if (con == NULL) {
			archive_set_error(&a->archive, ENOMEM,
			    "Can't allocate content data");
			return (ARCHIVE_FATAL);
		}
		con->offset_of_temp = wb_offset(a);
		iso9660->cur_file->cur_content->next = con;
		iso9660->cur_file->cur_content = con;
#ifdef HAVE_ZLIB_H
		iso9660->zisofs.block_offset = 0;
#endif
	}

	if (iso9660->zisofs.detect_magic)
		zisofs_detect_magic(a, buff, ws);

	if (iso9660->zisofs.making) {
		if (zisofs_write_to_temp(a, buff, ws) != ARCHIVE_OK)
			return (ARCHIVE_FATAL);
	} else {
		if (wb_write_to_temp(a, buff, ws) != ARCHIVE_OK)
			return (ARCHIVE_FATAL);
		iso9660->cur_file->cur_content->size += ws;
	}

	return (s);
}

static ssize_t
iso9660_write_data(struct archive_write *a, const void *buff, size_t s)
{
	struct iso9660 *iso9660 = a->format_data;
	ssize_t r;

	if (iso9660->cur_file == NULL)
		return (0);
	if (archive_entry_filetype(iso9660->cur_file->entry) != AE_IFREG)
		return (0);
	if (s > iso9660->bytes_remaining)
		s = (size_t)iso9660->bytes_remaining;
	if (s == 0)
		return (0);

	r = write_iso9660_data(a, buff, s);
	if (r > 0)
		iso9660->bytes_remaining -= r;
	return (r);
}

static int
iso9660_finish_entry(struct archive_write *a)
{
	struct iso9660 *iso9660 = a->format_data;

	if (iso9660->cur_file == NULL)
		return (ARCHIVE_OK);
	if (archive_entry_filetype(iso9660->cur_file->entry) != AE_IFREG)
		return (ARCHIVE_OK);
	if (iso9660->cur_file->content.size == 0)
		return (ARCHIVE_OK);

	/* If there are unwritten data, write null data instead. */
	while (iso9660->bytes_remaining > 0) {
		size_t s;

		s = (iso9660->bytes_remaining > a->null_length)?
		    a->null_length: (size_t)iso9660->bytes_remaining;
		if (write_iso9660_data(a, a->nulls, s) < 0)
			return (ARCHIVE_FATAL);
		iso9660->bytes_remaining -= s;
	}

	if (iso9660->zisofs.making && zisofs_finish_entry(a) != ARCHIVE_OK)
		return (ARCHIVE_FATAL);

	/* Write padding. */
	if (wb_write_padding_to_temp(a, iso9660->cur_file->cur_content->size)
	    != ARCHIVE_OK)
		return (ARCHIVE_FATAL);

	/* Compute the logical block number. */
	iso9660->cur_file->cur_content->blocks = (int)
	    ((iso9660->cur_file->cur_content->size
	     + LOGICAL_BLOCK_SIZE -1) >> LOGICAL_BLOCK_BITS);

	/* Add the current file to data file list. */
	isofile_add_data_file(iso9660, iso9660->cur_file);

	return (ARCHIVE_OK);
}

static int
iso9660_close(struct archive_write *a)
{
	struct iso9660 *iso9660;
	int ret, blocks;

	iso9660 = a->format_data;

	/*
	 * Write remaining data out to the temporary file.
	 */
	if (wb_remaining(a) > 0) {
		ret = wb_write_out(a);
		if (ret < 0)
			return (ret);
	}

	/*
	 * Preparations...
	 */
#ifdef DEBUG
	if (iso9660->birth_time == 0)
#endif
		time(&(iso9660->birth_time));

	/*
	 * Prepare a bootable ISO image.
	 */
	if (iso9660->opt.boot) {
		/* Find out the boot file entry. */
		ret = isoent_find_out_boot_file(a, iso9660->primary.rootent);
		if (ret < 0)
			return (ret);
		/* Reconvert the boot file from zisofs'ed form to
		 * plain form. */
		ret = zisofs_rewind_boot_file(a);
		if (ret < 0)
			return (ret);
		/* Write remaining data out to the temporary file. */
		if (wb_remaining(a) > 0) {
			ret = wb_write_out(a);
			if (ret < 0)
				return (ret);
		}
		/* Create the boot catalog. */
		ret = isoent_create_boot_catalog(a, iso9660->primary.rootent);
		if (ret < 0)
			return (ret);
	}

	/*
	 * Prepare joliet extensions.
	 */
	if (iso9660->opt.joliet) {
		/* Make a new tree for joliet. */
		ret = isoent_clone_tree(a, &(iso9660->joliet.rootent),
		    iso9660->primary.rootent);
		if (ret < 0)
			return (ret);
		/* Make sure we have UTF-16BE converters.
		 * if there is no file entry, converters are still
		 * uninitialized. */
		if (iso9660->sconv_to_utf16be == NULL) {
			iso9660->sconv_to_utf16be =
			    archive_string_conversion_to_charset(
				&(a->archive), "UTF-16BE", 1);
			if (iso9660->sconv_to_utf16be == NULL)
				/* Couldn't allocate memory */
				return (ARCHIVE_FATAL);
			iso9660->sconv_from_utf16be =
			    archive_string_conversion_from_charset(
				&(a->archive), "UTF-16BE", 1);
			if (iso9660->sconv_from_utf16be == NULL)
				/* Couldn't allocate memory */
				return (ARCHIVE_FATAL);
		}
	}

	/*
	 * Make Path Tables.
	 */
	ret = isoent_make_path_table(a);
	if (ret < 0)
		return (ret);

	/*
	 * Calculate a total volume size and setup all locations of
	 * contents of an iso9660 image.
	 */
	blocks = SYSTEM_AREA_BLOCK
		+ PRIMARY_VOLUME_DESCRIPTOR_BLOCK
		+ VOLUME_DESCRIPTOR_SET_TERMINATOR_BLOCK
		+ NON_ISO_FILE_SYSTEM_INFORMATION_BLOCK;
	if (iso9660->opt.boot)
		blocks += BOOT_RECORD_DESCRIPTOR_BLOCK;
	if (iso9660->opt.joliet)
		blocks += SUPPLEMENTARY_VOLUME_DESCRIPTOR_BLOCK;
	if (iso9660->opt.iso_level == 4)
		blocks += SUPPLEMENTARY_VOLUME_DESCRIPTOR_BLOCK;

	/* Setup the locations of Path Table. */
	iso9660->primary.location_type_L_path_table = blocks;
	blocks += iso9660->primary.path_table_block;
	iso9660->primary.location_type_M_path_table = blocks;
	blocks += iso9660->primary.path_table_block;
	if (iso9660->opt.joliet) {
		iso9660->joliet.location_type_L_path_table = blocks;
		blocks += iso9660->joliet.path_table_block;
		iso9660->joliet.location_type_M_path_table = blocks;
		blocks += iso9660->joliet.path_table_block;
	}

	/* Setup the locations of directories. */
	isoent_setup_directory_location(iso9660, blocks,
	    &(iso9660->primary));
	blocks += iso9660->primary.total_dir_block;
	if (iso9660->opt.joliet) {
		isoent_setup_directory_location(iso9660, blocks,
		    &(iso9660->joliet));
		blocks += iso9660->joliet.total_dir_block;
	}

	if (iso9660->opt.rr) {
		iso9660->location_rrip_er = blocks;
		blocks += RRIP_ER_BLOCK;
	}

	/* Setup the locations of all file contents. */
 	isoent_setup_file_location(iso9660, blocks);
	blocks += iso9660->total_file_block;
	if (iso9660->opt.boot && iso9660->opt.boot_info_table) {
		ret = setup_boot_information(a);
		if (ret < 0)
			return (ret);
	}

	/* Now we have a total volume size. */
	iso9660->volume_space_size = blocks;
	if (iso9660->opt.pad)
		iso9660->volume_space_size += PADDING_BLOCK;
	iso9660->volume_sequence_number = 1;


	/*
	 * Write an ISO 9660 image.
	 */

	/* Switch to start using wbuff as file buffer. */
	iso9660->wbuff_remaining = wb_buffmax();
	iso9660->wbuff_type = WB_TO_STREAM;
	iso9660->wbuff_offset = 0;
	iso9660->wbuff_written = 0;
	iso9660->wbuff_tail = 0;

	/* Write The System Area */
	ret = write_null(a, SYSTEM_AREA_BLOCK * LOGICAL_BLOCK_SIZE);
	if (ret != ARCHIVE_OK)
		return (ARCHIVE_FATAL);

	/* Write Primary Volume Descriptor */
	ret = write_VD(a, &(iso9660->primary));
	if (ret != ARCHIVE_OK)
		return (ARCHIVE_FATAL);

	if (iso9660->opt.boot) {
		/* Write Boot Record Volume Descriptor */
		ret = write_VD_boot_record(a);
		if (ret != ARCHIVE_OK)
			return (ARCHIVE_FATAL);
	}

	if (iso9660->opt.iso_level == 4) {
		/* Write Enhanced Volume Descriptor */
		iso9660->primary.vdd_type = VDD_ENHANCED;
		ret = write_VD(a, &(iso9660->primary));
		iso9660->primary.vdd_type = VDD_PRIMARY;
		if (ret != ARCHIVE_OK)
			return (ARCHIVE_FATAL);
	}

	if (iso9660->opt.joliet) {
		ret = write_VD(a, &(iso9660->joliet));
		if (ret != ARCHIVE_OK)
			return (ARCHIVE_FATAL);
	}

	/* Write Volume Descriptor Set Terminator */
	ret = write_VD_terminator(a);
	if (ret != ARCHIVE_OK)
		return (ARCHIVE_FATAL);

	/* Write Non-ISO File System Information */
	ret = write_information_block(a);
	if (ret != ARCHIVE_OK)
		return (ARCHIVE_FATAL);

	/* Write Type L Path Table */
	ret = write_path_table(a, 0, &(iso9660->primary));
	if (ret != ARCHIVE_OK)
		return (ARCHIVE_FATAL);

	/* Write Type M Path Table */
	ret = write_path_table(a, 1, &(iso9660->primary));
	if (ret != ARCHIVE_OK)
		return (ARCHIVE_FATAL);

	if (iso9660->opt.joliet) {
		/* Write Type L Path Table */
		ret = write_path_table(a, 0, &(iso9660->joliet));
		if (ret != ARCHIVE_OK)
			return (ARCHIVE_FATAL);

		/* Write Type M Path Table */
		ret = write_path_table(a, 1, &(iso9660->joliet));
		if (ret != ARCHIVE_OK)
			return (ARCHIVE_FATAL);
	}

	/* Write Directory Descriptors */
	ret = write_directory_descriptors(a, &(iso9660->primary));
	if (ret != ARCHIVE_OK)
		return (ARCHIVE_FATAL);

	if (iso9660->opt.joliet) {
		ret = write_directory_descriptors(a, &(iso9660->joliet));
		if (ret != ARCHIVE_OK)
			return (ARCHIVE_FATAL);
	}

	if (iso9660->opt.rr) {
		/* Write Rockridge ER(Extensions Reference) */
		ret = write_rr_ER(a);
		if (ret != ARCHIVE_OK)
			return (ARCHIVE_FATAL);
	}

	/* Write File Descriptors */
	ret = write_file_descriptors(a);
	if (ret != ARCHIVE_OK)
		return (ARCHIVE_FATAL);

	/* Write Padding  */
	if (iso9660->opt.pad) {
		ret = write_null(a, PADDING_BLOCK * LOGICAL_BLOCK_SIZE);
		if (ret != ARCHIVE_OK)
			return (ARCHIVE_FATAL);
	}

	if (iso9660->directories_too_deep != NULL) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
		    "%s: Directories too deep.",
		    archive_entry_pathname(
			iso9660->directories_too_deep->file->entry));
		return (ARCHIVE_WARN);
	}

	/* Write remaining data out. */
	ret = wb_write_out(a);

	return (ret);
}

static int
iso9660_free(struct archive_write *a)
{
	struct iso9660 *iso9660;
	int i, ret;

	iso9660 = a->format_data;

	/* Close the temporary file. */
	if (iso9660->temp_fd >= 0)
		close(iso9660->temp_fd);

	/* Free some stuff for zisofs operations. */
	ret = zisofs_free(a);

	/* Remove directory entries in tree which includes file entries. */
	isoent_free_all(iso9660->primary.rootent);
	for (i = 0; i < iso9660->primary.max_depth; i++)
		free(iso9660->primary.pathtbl[i].sorted);
	free(iso9660->primary.pathtbl);

	if (iso9660->opt.joliet) {
		isoent_free_all(iso9660->joliet.rootent);
		for (i = 0; i < iso9660->joliet.max_depth; i++)
			free(iso9660->joliet.pathtbl[i].sorted);
		free(iso9660->joliet.pathtbl);
	}

	/* Remove isofile entries. */
	isofile_free_all_entries(iso9660);
	isofile_free_hardlinks(iso9660);

	archive_string_free(&(iso9660->cur_dirstr));
	archive_string_free(&(iso9660->volume_identifier));
	archive_string_free(&(iso9660->publisher_identifier));
	archive_string_free(&(iso9660->data_preparer_identifier));
	archive_string_free(&(iso9660->application_identifier));
	archive_string_free(&(iso9660->copyright_file_identifier));
	archive_string_free(&(iso9660->abstract_file_identifier));
	archive_string_free(&(iso9660->bibliographic_file_identifier));
	archive_string_free(&(iso9660->el_torito.catalog_filename));
	archive_string_free(&(iso9660->el_torito.boot_filename));
	archive_string_free(&(iso9660->el_torito.id));
	archive_string_free(&(iso9660->utf16be));
	archive_string_free(&(iso9660->mbs));

	free(iso9660);
	a->format_data = NULL;

	return (ret);
}

/*
 * Get the System Identifier
 */
static void
get_system_identifier(char *system_id, size_t size)
{
#if defined(HAVE_SYS_UTSNAME_H)
	struct utsname u;

	uname(&u);
	strncpy(system_id, u.sysname, size-1);
	system_id[size-1] = '\0';
#elif defined(_WIN32) && !defined(__CYGWIN__)
	strncpy(system_id, "Windows", size-1);
	system_id[size-1] = '\0';
#else
	strncpy(system_id, "Unknown", size-1);
	system_id[size-1] = '\0';
#endif
}

static void
set_str(unsigned char *p, const char *s, size_t l, char f, const char *map)
{
	unsigned char c;

	if (s == NULL)
		s = "";
	while ((c = *s++) != 0 && l > 0) {
		if (c >= 0x80 || map[c] == 0)
		 {
			/* illegal character */
			if (c >= 'a' && c <= 'z') {
				/* convert c from a-z to A-Z */
				c -= 0x20;
			} else
				c = 0x5f;
		}
		*p++ = c;
		l--;
	}
	/* If l isn't zero, fill p buffer by the character
	 * which indicated by f. */
	if (l > 0)
		memset(p , f, l);
}

static inline int
joliet_allowed_char(unsigned char high, unsigned char low)
{
	int utf16 = (high << 8) | low;

	if (utf16 <= 0x001F)
		return (0);

	switch (utf16) {
	case 0x002A: /* '*' */
	case 0x002F: /* '/' */
	case 0x003A: /* ':' */
	case 0x003B: /* ';' */
	case 0x003F: /* '?' */
	case 0x005C: /* '\' */
		return (0);/* Not allowed. */
	}
	return (1);
}

static int
set_str_utf16be(struct archive_write *a, unsigned char *p, const char *s,
    size_t l, uint16_t uf, enum vdc vdc)
{
	size_t size, i;
	int onepad;

	if (s == NULL)
		s = "\0\0";
	if (l & 0x01) {
		onepad = 1;
		l &= ~1;
	} else
		onepad = 0;
	if (vdc == VDC_UCS2) {
		struct iso9660 *iso9660 = a->format_data;
		if (archive_strncpy_l(&iso9660->utf16be, s, strlen(s),
		    iso9660->sconv_to_utf16be) != 0 && errno == ENOMEM) {
			archive_set_error(&a->archive, ENOMEM,
			    "Can't allocate memory for UTF-16BE");
			return (ARCHIVE_FATAL);
		}
		size = iso9660->utf16be.length;
		if (size > l)
			size = l;
		memcpy(p, iso9660->utf16be.s, size);
	} else {
		const uint16_t *u16 = (const uint16_t *)s;

		size = 0;
		while (*u16++)
			size += 2;
		if (size > l)
			size = l;
		memcpy(p, s, size);
	}
	for (i = 0; i < size; i += 2, p += 2) {
		if (!joliet_allowed_char(p[0], p[1]))
			archive_be16enc(p, 0x005F);/* '_' */
	}
	l -= size;
	while (l > 0) {
		archive_be16enc(p, uf);
		p += 2;
		l -= 2;
	}
	if (onepad)
		*p = 0;
	return (ARCHIVE_OK);
}

static const char a_characters_map[0x80] = {
/*  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F          */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 00-0F */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 10-1F */
    1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 20-2F */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 30-3F */
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 40-4F */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,/* 50-5F */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 60-6F */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 70-7F */
};

static const char a1_characters_map[0x80] = {
/*  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F          */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 00-0F */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 10-1F */
    1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 20-2F */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 30-3F */
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 40-4F */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,/* 50-5F */
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 60-6F */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,/* 70-7F */
};

static const char d_characters_map[0x80] = {
/*  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F          */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 00-0F */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 10-1F */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 20-2F */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,/* 30-3F */
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 40-4F */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,/* 50-5F */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 60-6F */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 70-7F */
};

static const char d1_characters_map[0x80] = {
/*  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F          */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 00-0F */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 10-1F */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 20-2F */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,/* 30-3F */
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 40-4F */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,/* 50-5F */
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 60-6F */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,/* 70-7F */
};

static int
set_str_a_characters_bp(struct archive_write *a, unsigned char *bp,
    int from, int to, const char *s, enum vdc vdc)
{
	int r;

	switch (vdc) {
	case VDC_STD:
		set_str(bp+from, s, to - from + 1, 0x20,
		    a_characters_map);
		r = ARCHIVE_OK;
		break;
	case VDC_LOWERCASE:
		set_str(bp+from, s, to - from + 1, 0x20,
		    a1_characters_map);
		r = ARCHIVE_OK;
		break;
	case VDC_UCS2:
	case VDC_UCS2_DIRECT:
		r = set_str_utf16be(a, bp+from, s, to - from + 1,
		    0x0020, vdc);
		break;
	default:
		r = ARCHIVE_FATAL;
	}
	return (r);
}

static int
set_str_d_characters_bp(struct archive_write *a, unsigned char *bp,
    int from, int to, const char *s, enum  vdc vdc)
{
	int r;

	switch (vdc) {
	case VDC_STD:
		set_str(bp+from, s, to - from + 1, 0x20,
		    d_characters_map);
		r = ARCHIVE_OK;
		break;
	case VDC_LOWERCASE:
		set_str(bp+from, s, to - from + 1, 0x20,
		    d1_characters_map);
		r = ARCHIVE_OK;
		break;
	case VDC_UCS2:
	case VDC_UCS2_DIRECT:
		r = set_str_utf16be(a, bp+from, s, to - from + 1,
		    0x0020, vdc);
		break;
	default:
		r = ARCHIVE_FATAL;
	}
	return (r);
}

static void
set_VD_bp(unsigned char *bp, enum VD_type type, unsigned char ver)
{

	/* Volume Descriptor Type */
	bp[1] = (unsigned char)type;
	/* Standard Identifier */
	memcpy(bp + 2, "CD001", 5);
	/* Volume Descriptor Version */
	bp[7] = ver;
}

static inline void
set_unused_field_bp(unsigned char *bp, int from, int to)
{
	memset(bp + from, 0, to - from + 1);
}

/*
 * 8-bit unsigned numerical values.
 * ISO9660 Standard 7.1.1
 */
static inline void
set_num_711(unsigned char *p, unsigned char value)
{
	*p = value;
}

/*
 * 8-bit signed numerical values.
 * ISO9660 Standard 7.1.2
 */
static inline void
set_num_712(unsigned char *p, char value)
{
	*((char *)p) = value;
}

/*
 * Least significant byte first.
 * ISO9660 Standard 7.2.1
 */
static inline void
set_num_721(unsigned char *p, uint16_t value)
{
	archive_le16enc(p, value);
}

/*
 * Most significant byte first.
 * ISO9660 Standard 7.2.2
 */
static inline void
set_num_722(unsigned char *p, uint16_t value)
{
	archive_be16enc(p, value);
}

/*
 * Both-byte orders.
 * ISO9660 Standard 7.2.3
 */
static void
set_num_723(unsigned char *p, uint16_t value)
{
	archive_le16enc(p, value);
	archive_be16enc(p+2, value);
}

/*
 * Least significant byte first.
 * ISO9660 Standard 7.3.1
 */
static inline void
set_num_731(unsigned char *p, uint32_t value)
{
	archive_le32enc(p, value);
}

/*
 * Most significant byte first.
 * ISO9660 Standard 7.3.2
 */
static inline void
set_num_732(unsigned char *p, uint32_t value)
{
	archive_be32enc(p, value);
}

/*
 * Both-byte orders.
 * ISO9660 Standard 7.3.3
 */
static inline void
set_num_733(unsigned char *p, uint32_t value)
{
	archive_le32enc(p, value);
	archive_be32enc(p+4, value);
}

static void
set_digit(unsigned char *p, size_t s, int value)
{

	while (s--) {
		p[s] = '0' + (value % 10);
		value /= 10;
	}
}

#if defined(HAVE_STRUCT_TM_TM_GMTOFF)
#define get_gmoffset(tm)	((tm)->tm_gmtoff)
#elif defined(HAVE_STRUCT_TM___TM_GMTOFF)
#define get_gmoffset(tm)	((tm)->__tm_gmtoff)
#else
static long
get_gmoffset(struct tm *tm)
{
	long offset;

#if defined(HAVE__GET_TIMEZONE)
	_get_timezone(&offset);
#elif defined(__CYGWIN__) || defined(__MINGW32__) || defined(__BORLANDC__) || defined(ESP_PLATFORM)
	offset = _timezone;
#else
	offset = timezone;
#endif
	offset *= -1;
	if (tm->tm_isdst)
		offset += 3600;
	return (offset);
}
#endif

static void
get_tmfromtime(struct tm *tm, time_t *t)
{
#if HAVE_LOCALTIME_S
	localtime_s(tm, t);
#elif HAVE_LOCALTIME_R
	tzset();
	localtime_r(t, tm);
#else
	memcpy(tm, localtime(t), sizeof(*tm));
#endif
}

/*
 * Date and Time Format.
 * ISO9660 Standard 8.4.26.1
 */
static void
set_date_time(unsigned char *p, time_t t)
{
	struct tm tm;

	get_tmfromtime(&tm, &t);
	set_digit(p, 4, tm.tm_year + 1900);
	set_digit(p+4, 2, tm.tm_mon + 1);
	set_digit(p+6, 2, tm.tm_mday);
	set_digit(p+8, 2, tm.tm_hour);
	set_digit(p+10, 2, tm.tm_min);
	set_digit(p+12, 2, tm.tm_sec);
	set_digit(p+14, 2, 0);
	set_num_712(p+16, (char)(get_gmoffset(&tm)/(60*15)));
}

static void
set_date_time_null(unsigned char *p)
{
	memset(p, (int)'0', 16);
	p[16] = 0;
}

static void
set_time_915(unsigned char *p, time_t t)
{
	struct tm tm;

	get_tmfromtime(&tm, &t);
	set_num_711(p+0, tm.tm_year);
	set_num_711(p+1, tm.tm_mon+1);
	set_num_711(p+2, tm.tm_mday);
	set_num_711(p+3, tm.tm_hour);
	set_num_711(p+4, tm.tm_min);
	set_num_711(p+5, tm.tm_sec);
	set_num_712(p+6, (char)(get_gmoffset(&tm)/(60*15)));
}


/*
 * Write SUSP "CE" System Use Entry.
 */
static int
set_SUSP_CE(unsigned char *p, int location, int offset, int size)
{
	unsigned char *bp = p -1;
	/*  Extend the System Use Area
	 *   "CE" Format:
	 *               len  ver
	 *    +----+----+----+----+-----------+-----------+
	 *    | 'C'| 'E'| 1C | 01 | LOCATION1 | LOCATION2 |
	 *    +----+----+----+----+-----------+-----------+
	 *    0    1    2    3    4          12          20
	 *    +-----------+
	 *    | LOCATION3 |
	 *    +-----------+
	 *   20          28
	 *   LOCATION1 : Location of Continuation of System Use Area.
	 *   LOCATION2 : Offset to Start of Continuation.
	 *   LOCATION3 : Length of the Continuation.
	 */

	bp[1] = 'C';
	bp[2] = 'E';
	bp[3] = RR_CE_SIZE;	/* length	*/
	bp[4] = 1;		/* version	*/
	set_num_733(bp+5, location);
	set_num_733(bp+13, offset);
	set_num_733(bp+21, size);
	return (RR_CE_SIZE);
}

/*
 * The functions, which names are beginning with extra_, are used to
 * control extra records.
 * The maximum size of a Directory Record is 254. When a filename is
 * very long, all of RRIP data of a file won't stored to the Directory
 * Record and so remaining RRIP data store to an extra record instead.
 */
static unsigned char *
extra_open_record(unsigned char *bp, int dr_len, struct isoent *isoent,
    struct ctl_extr_rec *ctl)
{
	ctl->bp = bp;
	if (bp != NULL)
		bp += dr_len;
	ctl->use_extr = 0;
	ctl->isoent = isoent;
	ctl->ce_ptr = NULL;
	ctl->cur_len = ctl->dr_len = dr_len;
	ctl->limit = DR_LIMIT;

	return (bp);
}

static void
extra_close_record(struct ctl_extr_rec *ctl, int ce_size)
{
	int padding = 0;

	if (ce_size > 0)
		extra_tell_used_size(ctl, ce_size);
	/* Padding. */
	if (ctl->cur_len & 0x01) {
		ctl->cur_len++;
		if (ctl->bp != NULL)
			ctl->bp[ctl->cur_len] = 0;
		padding = 1;
	}
	if (ctl->use_extr) {
		if (ctl->ce_ptr != NULL)
			set_SUSP_CE(ctl->ce_ptr, ctl->extr_loc,
			    ctl->extr_off, ctl->cur_len - padding);
	} else
		ctl->dr_len = ctl->cur_len;
}

#define extra_space(ctl)	((ctl)->limit - (ctl)->cur_len)

static unsigned char *
extra_next_record(struct ctl_extr_rec *ctl, int length)
{
	int cur_len = ctl->cur_len;/* save cur_len */

	/* Close the current extra record or Directory Record. */
	extra_close_record(ctl, RR_CE_SIZE);

	/* Get a next extra record. */
	ctl->use_extr = 1;
	if (ctl->bp != NULL) {
		/* Storing data into an extra record. */
		unsigned char *p;

		/* Save the pointer where a CE extension will be
		 * stored to. */
		ctl->ce_ptr = &ctl->bp[cur_len+1];
		p = extra_get_record(ctl->isoent,
		    &ctl->limit, &ctl->extr_off, &ctl->extr_loc);
		ctl->bp = p - 1;/* the base of bp offset is 1. */
	} else
		/* Calculating the size of an extra record. */
		(void)extra_get_record(ctl->isoent,
		    &ctl->limit, NULL, NULL);
	ctl->cur_len = 0;
	/* Check if an extra record is almost full.
	 * If so, get a next one. */
	if (extra_space(ctl) < length)
		(void)extra_next_record(ctl, length);

	return (ctl->bp);
}

static inline struct extr_rec *
extra_last_record(struct isoent *isoent)
{
	if (isoent->extr_rec_list.first == NULL)
		return (NULL);
	return ((struct extr_rec *)(void *)
		((char *)(isoent->extr_rec_list.last)
		    - offsetof(struct extr_rec, next)));
}

static unsigned char *
extra_get_record(struct isoent *isoent, int *space, int *off, int *loc)
{
	struct extr_rec *rec;

	isoent = isoent->parent;
	if (off != NULL) {
		/* Storing data into an extra record. */
		rec = isoent->extr_rec_list.current;
		if (DR_SAFETY > LOGICAL_BLOCK_SIZE - rec->offset)
			rec = rec->next;
	} else {
		/* Calculating the size of an extra record. */
		rec = extra_last_record(isoent);
		if (rec == NULL ||
		    DR_SAFETY > LOGICAL_BLOCK_SIZE - rec->offset) {
			rec = malloc(sizeof(*rec));
			if (rec == NULL)
				return (NULL);
			rec->location = 0;
			rec->offset = 0;
			/* Insert `rec` into the tail of isoent->extr_rec_list */
			rec->next = NULL;
			/*
			 * Note: testing isoent->extr_rec_list.last == NULL
			 * here is really unneeded since it has been already
			 * initialized at isoent_new function but Clang Static
			 * Analyzer claims that it is dereference of null
			 * pointer.
			 */
			if (isoent->extr_rec_list.last == NULL)
				isoent->extr_rec_list.last =
					&(isoent->extr_rec_list.first);
			*isoent->extr_rec_list.last = rec;
			isoent->extr_rec_list.last = &(rec->next);
		}
	}
	*space = LOGICAL_BLOCK_SIZE - rec->offset - DR_SAFETY;
	if (*space & 0x01)
		*space -= 1;/* Keep padding space. */
	if (off != NULL)
		*off = rec->offset;
	if (loc != NULL)
		*loc = rec->location;
	isoent->extr_rec_list.current = rec;

	return (&rec->buf[rec->offset]);
}

static void
extra_tell_used_size(struct ctl_extr_rec *ctl, int size)
{
	struct isoent *isoent;
	struct extr_rec *rec;

	if (ctl->use_extr) {
		isoent = ctl->isoent->parent;
		rec = isoent->extr_rec_list.current;
		if (rec != NULL)
			rec->offset += size;
	}
	ctl->cur_len += size;
}

static int
extra_setup_location(struct isoent *isoent, int location)
{
	struct extr_rec *rec;
	int cnt;

	cnt = 0;
	rec = isoent->extr_rec_list.first;
	isoent->extr_rec_list.current = rec;
	while (rec) {
		cnt++;
		rec->location = location++;
		rec->offset = 0;
		rec = rec->next;
	}
	return (cnt);
}

/*
 * Create the RRIP entries.
 */
static int
set_directory_record_rr(unsigned char *bp, int dr_len,
    struct isoent *isoent, struct iso9660 *iso9660, enum dir_rec_type t)
{
	/* Flags(BP 5) of the Rockridge "RR" System Use Field */
	unsigned char rr_flag;
#define RR_USE_PX	0x01
#define RR_USE_PN	0x02
#define RR_USE_SL	0x04
#define RR_USE_NM	0x08
#define RR_USE_CL	0x10
#define RR_USE_PL	0x20
#define RR_USE_RE	0x40
#define RR_USE_TF	0x80
	int length;
	struct ctl_extr_rec ctl;
	struct isoent *rr_parent, *pxent;
	struct isofile *file;

	bp = extra_open_record(bp, dr_len, isoent, &ctl);

	if (t == DIR_REC_PARENT) {
		rr_parent = isoent->rr_parent;
		pxent = isoent->parent;
		if (rr_parent != NULL)
			isoent = rr_parent;
		else
			isoent = isoent->parent;
	} else {
		rr_parent = NULL;
		pxent = isoent;
	}
	file = isoent->file;

	if (t != DIR_REC_NORMAL) {
		rr_flag = RR_USE_PX | RR_USE_TF;
		if (rr_parent != NULL)
			rr_flag |= RR_USE_PL;
	} else {
		rr_flag = RR_USE_PX | RR_USE_NM | RR_USE_TF;
		if (archive_entry_filetype(file->entry) == AE_IFLNK)
			rr_flag |= RR_USE_SL;
		if (isoent->rr_parent != NULL)
			rr_flag |= RR_USE_RE;
		if (isoent->rr_child != NULL)
			rr_flag |= RR_USE_CL;
		if (archive_entry_filetype(file->entry) == AE_IFCHR ||
		    archive_entry_filetype(file->entry) == AE_IFBLK)
			rr_flag |= RR_USE_PN;
#ifdef COMPAT_MKISOFS
		/*
		 * mkisofs 2.01.01a63 records "RE" extension to
		 * the entry of "rr_moved" directory.
		 * I don't understand this behavior.
		 */
		if (isoent->virtual &&
		    isoent->parent == iso9660->primary.rootent &&
		    strcmp(isoent->file->basename.s, "rr_moved") == 0)
			rr_flag |= RR_USE_RE;
#endif
	}

	/* Write "SP" System Use Entry. */
	if (t == DIR_REC_SELF && isoent == isoent->parent) {
		length = 7;
		if (bp != NULL) {
			bp[1] = 'S';
			bp[2] = 'P';
			bp[3] = length;
			bp[4] = 1;	/* version	*/
			bp[5] = 0xBE;  /* Check Byte	*/
			bp[6] = 0xEF;  /* Check Byte	*/
			bp[7] = 0;
			bp += length;
		}
		extra_tell_used_size(&ctl, length);
	}

	/* Write "RR" System Use Entry. */
	length = 5;
	if (extra_space(&ctl) < length)
		bp = extra_next_record(&ctl, length);
	if (bp != NULL) {
		bp[1] = 'R';
		bp[2] = 'R';
		bp[3] = length;
		bp[4] = 1;	/* version */
		bp[5] = rr_flag;
		bp += length;
	}
	extra_tell_used_size(&ctl, length);

	/* Write "NM" System Use Entry. */
	if (rr_flag & RR_USE_NM) {
		/*
		 *   "NM" Format:
		 *     e.g. a basename is 'foo'
		 *               len  ver  flg
		 *    +----+----+----+----+----+----+----+----+
		 *    | 'N'| 'M'| 08 | 01 | 00 | 'f'| 'o'| 'o'|
		 *    +----+----+----+----+----+----+----+----+
		 *    <----------------- len ----------------->
		 */
		size_t nmlen = file->basename.length;
		const char *nm = file->basename.s;
		size_t nmmax;

		if (extra_space(&ctl) < 6)
			bp = extra_next_record(&ctl, 6);
		if (bp != NULL) {
			bp[1] = 'N';
			bp[2] = 'M';
			bp[4] = 1;	    /* version	*/
		}
		nmmax = extra_space(&ctl);
		if (nmmax > 0xff)
			nmmax = 0xff;
		while (nmlen + 5 > nmmax) {
			length = (int)nmmax;
			if (bp != NULL) {
				bp[3] = length;
				bp[5] = 0x01;/* Alternate Name continues
					       * in next "NM" field */
				memcpy(bp+6, nm, length - 5);
				bp += length;
			}
			nmlen -= length - 5;
			nm += length - 5;
			extra_tell_used_size(&ctl, length);
			if (extra_space(&ctl) < 6) {
				bp = extra_next_record(&ctl, 6);
				nmmax = extra_space(&ctl);
				if (nmmax > 0xff)
					nmmax = 0xff;
			}
			if (bp != NULL) {
				bp[1] = 'N';
				bp[2] = 'M';
				bp[4] = 1;    /* version */
			}
		}
		length = 5 + (int)nmlen;
		if (bp != NULL) {
			bp[3] = length;
			bp[5] = 0;
			memcpy(bp+6, nm, nmlen);
			bp += length;
		}
		extra_tell_used_size(&ctl, length);
	}

	/* Write "PX" System Use Entry. */
	if (rr_flag & RR_USE_PX) {
		/*
		 *   "PX" Format:
		 *               len  ver
		 *    +----+----+----+----+-----------+-----------+
		 *    | 'P'| 'X'| 2C | 01 | FILE MODE |   LINKS   |
		 *    +----+----+----+----+-----------+-----------+
		 *    0    1    2    3    4          12          20
		 *    +-----------+-----------+------------------+
		 *    |  USER ID  | GROUP ID  |FILE SERIAL NUMBER|
		 *    +-----------+-----------+------------------+
		 *   20          28          36                 44
		 */
		length = 44;
		if (extra_space(&ctl) < length)
			bp = extra_next_record(&ctl, length);
		if (bp != NULL) {
			mode_t mode;
			int64_t uid;
			int64_t gid;

			mode = archive_entry_mode(file->entry);
			uid = archive_entry_uid(file->entry);
			gid = archive_entry_gid(file->entry);
			if (iso9660->opt.rr == OPT_RR_USEFUL) {
				/*
				 * This action is similar to mkisofs -r option
				 * but our rockridge=useful option does not
				 * set a zero to uid and gid.
				 */
				/* set all read bit ON */
				mode |= 0444;
#if !defined(_WIN32) && !defined(__CYGWIN__)
				if (mode & 0111)
#endif
					/* set all exec bit ON */
					mode |= 0111;
				/* clear all write bits. */
				mode &= ~0222;
				/* clear setuid,setgid,sticky bits. */
				mode &= ~07000;
			}

			bp[1] = 'P';
			bp[2] = 'X';
			bp[3] = length;
			bp[4] = 1;	/* version	*/
			/* file mode */
			set_num_733(bp+5, mode);
			/* file links (stat.st_nlink) */
			set_num_733(bp+13,
			    archive_entry_nlink(file->entry));
			set_num_733(bp+21, (uint32_t)uid);
			set_num_733(bp+29, (uint32_t)gid);
			/* File Serial Number */
			if (pxent->dir)
				set_num_733(bp+37, pxent->dir_location);
			else if (file->hardlink_target != NULL)
				set_num_733(bp+37,
				    file->hardlink_target->cur_content->location);
			else
				set_num_733(bp+37,
				    file->cur_content->location);
			bp += length;
		}
		extra_tell_used_size(&ctl, length);
	}

	/* Write "SL" System Use Entry. */
	if (rr_flag & RR_USE_SL) {
		/*
		 *   "SL" Format:
		 *     e.g. a symbolic name is 'foo/bar'
		 *               len  ver  flg
		 *    +----+----+----+----+----+------------+
		 *    | 'S'| 'L'| 0F | 01 | 00 | components |
		 *    +----+----+----+----+----+-----+------+
		 *    0    1    2    3    4    5  ...|...  15
		 *    <----------------- len --------+------>
		 *    components :                   |
		 *     cflg clen                     |
		 *    +----+----+----+----+----+     |
		 *    | 00 | 03 | 'f'| 'o'| 'o'| <---+
		 *    +----+----+----+----+----+     |
		 *    5    6    7    8    9   10     |
		 *     cflg clen                     |
		 *    +----+----+----+----+----+     |
		 *    | 00 | 03 | 'b'| 'a'| 'r'| <---+
		 *    +----+----+----+----+----+
		 *   10   11   12   13   14   15
		 *
		 *    - cflg : flag of component
		 *    - clen : length of component
		 */
		const char *sl;
		char sl_last;

		if (extra_space(&ctl) < 7)
			bp = extra_next_record(&ctl, 7);
		sl = file->symlink.s;
		sl_last = '\0';
		if (bp != NULL) {
			bp[1] = 'S';
			bp[2] = 'L';
			bp[4] = 1;	/* version	*/
		}
		for (;;) {
			unsigned char *nc, *cf,  *cl, cldmy = 0;
			int sllen, slmax;

			slmax = extra_space(&ctl);
			if (slmax > 0xff)
				slmax = 0xff;
			if (bp != NULL)
				nc = &bp[6];
			else
				nc = NULL;
			cf = cl = NULL;
			sllen = 0;
			while (*sl && sllen + 11 < slmax) {
				if (sl_last == '\0' && sl[0] == '/') {
					/*
					 *     flg  len
					 *    +----+----+
					 *    | 08 | 00 | ROOT component.
					 *    +----+----+ ("/")
					 *
				 	 * Root component has to appear
				 	 * at the first component only.
					 */
					if (nc != NULL) {
						cf = nc++;
						*cf = 0x08; /* ROOT */
						*nc++ = 0;
					}
					sllen += 2;
					sl++;
					sl_last = '/';
					cl = NULL;
					continue;
				}
				if (((sl_last == '\0' || sl_last == '/') &&
				      sl[0] == '.' && sl[1] == '.' &&
				     (sl[2] == '/' || sl[2] == '\0')) ||
				    (sl[0] == '/' &&
				      sl[1] == '.' && sl[2] == '.' &&
				     (sl[3] == '/' || sl[3] == '\0'))) {
					/*
					 *     flg  len
					 *    +----+----+
					 *    | 04 | 00 | PARENT component.
					 *    +----+----+ ("..")
					 */
					if (nc != NULL) {
						cf = nc++;
						*cf = 0x04; /* PARENT */
						*nc++ = 0;
					}
					sllen += 2;
					if (sl[0] == '/')
						sl += 3;/* skip "/.." */
					else
						sl += 2;/* skip ".." */
					sl_last = '.';
					cl = NULL;
					continue;
				}
				if (((sl_last == '\0' || sl_last == '/') &&
				      sl[0] == '.' &&
				     (sl[1] == '/' || sl[1] == '\0')) ||
				    (sl[0] == '/' && sl[1] == '.' &&
				     (sl[2] == '/' || sl[2] == '\0'))) {
					/*
					 *     flg  len
					 *    +----+----+
					 *    | 02 | 00 | CURRENT component.
					 *    +----+----+ (".")
					 */
					if (nc != NULL) {
						cf = nc++;
						*cf = 0x02; /* CURRENT */
						*nc++ = 0;
					}
					sllen += 2;
					if (sl[0] == '/')
						sl += 2;/* skip "/." */
					else
						sl ++;  /* skip "." */
					sl_last = '.';
					cl = NULL;
					continue;
				}
				if (sl[0] == '/' || cl == NULL) {
					if (nc != NULL) {
						cf = nc++;
						*cf = 0;
						cl = nc++;
						*cl = 0;
					} else
						cl = &cldmy;
					sllen += 2;
					if (sl[0] == '/') {
						sl_last = *sl++;
						continue;
					}
				}
				sl_last = *sl++;
				if (nc != NULL) {
					*nc++ = sl_last;
					(*cl) ++;
				}
				sllen++;
			}
			if (*sl) {
				length = 5 + sllen;
				if (bp != NULL) {
					/*
					 * Mark flg as CONTINUE component.
					 */
					*cf |= 0x01;
					/*
					 *               len  ver  flg
					 *    +----+----+----+----+----+-
					 *    | 'S'| 'L'| XX | 01 | 01 |
					 *    +----+----+----+----+----+-
					 *                           ^
					 *           continues in next "SL"
					 */
					bp[3] = length;
					bp[5] = 0x01;/* This Symbolic Link
						      * continues in next
						      * "SL" field */
					bp += length;
				}
				extra_tell_used_size(&ctl, length);
				if (extra_space(&ctl) < 11)
					bp = extra_next_record(&ctl, 11);
				if (bp != NULL) {
					/* Next 'SL' */
					bp[1] = 'S';
					bp[2] = 'L';
					bp[4] = 1;    /* version */
				}
			} else {
				length = 5 + sllen;
				if (bp != NULL) {
					bp[3] = length;
					bp[5] = 0;
					bp += length;
				}
				extra_tell_used_size(&ctl, length);
				break;
			}
		}
	}

	/* Write "TF" System Use Entry. */
	if (rr_flag & RR_USE_TF) {
		/*
		 *   "TF" Format:
		 *               len  ver
		 *    +----+----+----+----+-----+-------------+
		 *    | 'T'| 'F'| XX | 01 |FLAGS| TIME STAMPS |
		 *    +----+----+----+----+-----+-------------+
		 *    0    1    2    3    4     5            XX
		 *    TIME STAMPS : ISO 9660 Standard 9.1.5.
		 *                  If TF_LONG_FORM FLAGS is set,
		 *                  use ISO9660 Standard 8.4.26.1.
		 */
#define TF_CREATION	0x01	/* Creation time recorded		*/
#define TF_MODIFY	0x02	/* Modification time recorded		*/
#define TF_ACCESS	0x04	/* Last Access time recorded		*/
#define TF_ATTRIBUTES	0x08	/* Last Attribute Change time recorded  */
#define TF_BACKUP	0x10	/* Last Backup time recorded		*/
#define TF_EXPIRATION	0x20	/* Expiration time recorded		*/
#define TF_EFFECTIVE	0x40	/* Effective time recorded		*/
#define TF_LONG_FORM	0x80	/* ISO 9660 17-byte time format used	*/
		unsigned char tf_flags;

		length = 5;
		tf_flags = 0;
#ifndef COMPAT_MKISOFS
		if (archive_entry_birthtime_is_set(file->entry) &&
		    archive_entry_birthtime(file->entry) <=
		    archive_entry_mtime(file->entry)) {
			length += 7;
			tf_flags |= TF_CREATION;
		}
#endif
		if (archive_entry_mtime_is_set(file->entry)) {
			length += 7;
			tf_flags |= TF_MODIFY;
		}
		if (archive_entry_atime_is_set(file->entry)) {
			length += 7;
			tf_flags |= TF_ACCESS;
		}
		if (archive_entry_ctime_is_set(file->entry)) {
			length += 7;
			tf_flags |= TF_ATTRIBUTES;
		}
		if (extra_space(&ctl) < length)
			bp = extra_next_record(&ctl, length);
		if (bp != NULL) {
			bp[1] = 'T';
			bp[2] = 'F';
			bp[3] = length;
			bp[4] = 1;	/* version	*/
			bp[5] = tf_flags;
			bp += 5;
			/* Creation time */
			if (tf_flags & TF_CREATION) {
				set_time_915(bp+1,
				    archive_entry_birthtime(file->entry));
				bp += 7;
			}
			/* Modification time */
			if (tf_flags & TF_MODIFY) {
				set_time_915(bp+1,
				    archive_entry_mtime(file->entry));
				bp += 7;
			}
			/* Last Access time */
			if (tf_flags & TF_ACCESS) {
				set_time_915(bp+1,
				    archive_entry_atime(file->entry));
				bp += 7;
			}
			/* Last Attribute Change time */
			if (tf_flags & TF_ATTRIBUTES) {
				set_time_915(bp+1,
				    archive_entry_ctime(file->entry));
				bp += 7;
			}
		}
		extra_tell_used_size(&ctl, length);
	}

	/* Write "RE" System Use Entry. */
	if (rr_flag & RR_USE_RE) {
		/*
		 *   "RE" Format:
		 *               len  ver
		 *    +----+----+----+----+
		 *    | 'R'| 'E'| 04 | 01 |
		 *    +----+----+----+----+
		 *    0    1    2    3    4
		 */
		length = 4;
		if (extra_space(&ctl) < length)
			bp = extra_next_record(&ctl, length);
		if (bp != NULL) {
			bp[1] = 'R';
			bp[2] = 'E';
			bp[3] = length;
			bp[4] = 1;	/* version	*/
			bp += length;
		}
		extra_tell_used_size(&ctl, length);
	}

	/* Write "PL" System Use Entry. */
	if (rr_flag & RR_USE_PL) {
		/*
		 *   "PL" Format:
		 *               len  ver
		 *    +----+----+----+----+------------+
		 *    | 'P'| 'L'| 0C | 01 | *LOCATION  |
		 *    +----+----+----+----+------------+
		 *    0    1    2    3    4           12
		 *    *LOCATION: location of parent directory
		 */
		length = 12;
		if (extra_space(&ctl) < length)
			bp = extra_next_record(&ctl, length);
		if (bp != NULL) {
			bp[1] = 'P';
			bp[2] = 'L';
			bp[3] = length;
			bp[4] = 1;	/* version	*/
			set_num_733(bp + 5,
			    rr_parent->dir_location);
			bp += length;
		}
		extra_tell_used_size(&ctl, length);
	}

	/* Write "CL" System Use Entry. */
	if (rr_flag & RR_USE_CL) {
		/*
		 *   "CL" Format:
		 *               len  ver
		 *    +----+----+----+----+------------+
		 *    | 'C'| 'L'| 0C | 01 | *LOCATION  |
		 *    +----+----+----+----+------------+
		 *    0    1    2    3    4           12
		 *    *LOCATION: location of child directory
		 */
		length = 12;
		if (extra_space(&ctl) < length)
			bp = extra_next_record(&ctl, length);
		if (bp != NULL) {
			bp[1] = 'C';
			bp[2] = 'L';
			bp[3] = length;
			bp[4] = 1;	/* version	*/
			set_num_733(bp + 5,
			    isoent->rr_child->dir_location);
			bp += length;
		}
		extra_tell_used_size(&ctl, length);
	}

	/* Write "PN" System Use Entry. */
	if (rr_flag & RR_USE_PN) {
		/*
		 *   "PN" Format:
		 *               len  ver
		 *    +----+----+----+----+------------+------------+
		 *    | 'P'| 'N'| 14 | 01 | dev_t high | dev_t low  |
		 *    +----+----+----+----+------------+------------+
		 *    0    1    2    3    4           12           20
		 */
		length = 20;
		if (extra_space(&ctl) < length)
			bp = extra_next_record(&ctl, length);
		if (bp != NULL) {
			uint64_t dev;

			bp[1] = 'P';
			bp[2] = 'N';
			bp[3] = length;
			bp[4] = 1;	/* version	*/
			dev = (uint64_t)archive_entry_rdev(file->entry);
			set_num_733(bp + 5, (uint32_t)(dev >> 32));
			set_num_733(bp + 13, (uint32_t)(dev & 0xFFFFFFFF));
			bp += length;
		}
		extra_tell_used_size(&ctl, length);
	}

	/* Write "ZF" System Use Entry. */
	if (file->zisofs.header_size) {
		/*
		 *   "ZF" Format:
		 *               len  ver
		 *    +----+----+----+----+----+----+-------------+
		 *    | 'Z'| 'F'| 10 | 01 | 'p'| 'z'| Header Size |
		 *    +----+----+----+----+----+----+-------------+
		 *    0    1    2    3    4    5    6             7
		 *    +--------------------+-------------------+
		 *    | Log2 of block Size | Uncompressed Size |
		 *    +--------------------+-------------------+
		 *    7                    8                   16
		 */
		length = 16;
		if (extra_space(&ctl) < length)
			bp = extra_next_record(&ctl, length);
		if (bp != NULL) {
			bp[1] = 'Z';
			bp[2] = 'F';
			bp[3] = length;
			bp[4] = 1;	/* version	*/
			bp[5] = 'p';
			bp[6] = 'z';
			bp[7] = file->zisofs.header_size;
			bp[8] = file->zisofs.log2_bs;
			set_num_733(bp + 9, file->zisofs.uncompressed_size);
			bp += length;
		}
		extra_tell_used_size(&ctl, length);
	}

	/* Write "CE" System Use Entry. */
	if (t == DIR_REC_SELF && isoent == isoent->parent) {
		length = RR_CE_SIZE;
		if (bp != NULL)
			set_SUSP_CE(bp+1, iso9660->location_rrip_er,
			    0, RRIP_ER_SIZE);
		extra_tell_used_size(&ctl, length);
	}

	extra_close_record(&ctl, 0);

	return (ctl.dr_len);
}

/*
 * Write data of a Directory Record or calculate writing bytes itself.
 * If parameter `p' is NULL, calculates the size of writing data, which
 * a Directory Record needs to write, then it saved and return
 * the calculated size.
 * Parameter `n' is a remaining size of buffer. when parameter `p' is
 * not NULL, check whether that `n' is not less than the saved size.
 * if that `n' is small, return zero.
 *
 * This format of the Directory Record is according to
 * ISO9660 Standard 9.1
 */
static int
set_directory_record(unsigned char *p, size_t n, struct isoent *isoent,
    struct iso9660 *iso9660, enum dir_rec_type t,
    enum vdd_type vdd_type)
{
	unsigned char *bp;
	size_t dr_len;
	size_t fi_len;

	if (p != NULL) {
		/*
		 * Check whether a write buffer size is less than the
		 * saved size which is needed to write this Directory
		 * Record.
		 */
		switch (t) {
		case DIR_REC_VD:
			dr_len = isoent->dr_len.vd; break;
		case DIR_REC_SELF:
			dr_len = isoent->dr_len.self; break;
		case DIR_REC_PARENT:
			dr_len = isoent->dr_len.parent; break;
		case DIR_REC_NORMAL:
		default:
			dr_len = isoent->dr_len.normal; break;
		}
		if (dr_len > n)
			return (0);/* Needs more buffer size. */
	}

	if (t == DIR_REC_NORMAL && isoent->identifier != NULL)
		fi_len = isoent->id_len;
	else
		fi_len = 1;

	if (p != NULL) {
		struct isoent *xisoent;
		struct isofile *file;
		unsigned char flag;

		if (t == DIR_REC_PARENT)
			xisoent = isoent->parent;
		else
			xisoent = isoent;
		file = isoent->file;
		if (file->hardlink_target != NULL)
			file = file->hardlink_target;
		/* Make a file flag. */
		if (xisoent->dir)
			flag = FILE_FLAG_DIRECTORY;
		else {
			if (file->cur_content->next != NULL)
				flag = FILE_FLAG_MULTI_EXTENT;
			else
				flag = 0;
		}

		bp = p -1;
		/* Extended Attribute Record Length */
		set_num_711(bp+2, 0);
		/* Location of Extent */
		if (xisoent->dir)
			set_num_733(bp+3, xisoent->dir_location);
		else
			set_num_733(bp+3, file->cur_content->location);
		/* Data Length */
		if (xisoent->dir)
			set_num_733(bp+11,
			    xisoent->dir_block * LOGICAL_BLOCK_SIZE);
		else
			set_num_733(bp+11, (uint32_t)file->cur_content->size);
		/* Recording Date and Time */
		/* NOTE:
		 *  If a file type is symbolic link, you are seeing this
		 *  field value is different from a value mkisofs makes.
		 *  libarchive uses lstat to get this one, but it
		 *  seems mkisofs uses stat to get.
		 */
		set_time_915(bp+19,
		    archive_entry_mtime(xisoent->file->entry));
		/* File Flags */
		bp[26] = flag;
		/* File Unit Size */
		set_num_711(bp+27, 0);
		/* Interleave Gap Size */
		set_num_711(bp+28, 0);
		/* Volume Sequence Number */
		set_num_723(bp+29, iso9660->volume_sequence_number);
		/* Length of File Identifier */
		set_num_711(bp+33, (unsigned char)fi_len);
		/* File Identifier */
		switch (t) {
		case DIR_REC_VD:
		case DIR_REC_SELF:
			set_num_711(bp+34, 0);
			break;
		case DIR_REC_PARENT:
			set_num_711(bp+34, 1);
			break;
		case DIR_REC_NORMAL:
			if (isoent->identifier != NULL)
				memcpy(bp+34, isoent->identifier, fi_len);
			else
				set_num_711(bp+34, 0);
			break;
		}
	} else
		bp = NULL;
	dr_len = 33 + fi_len;
	/* Padding Field */
	if (dr_len & 0x01) {
		dr_len ++;
		if (p != NULL)
			bp[dr_len] = 0;
	}

	/* Volume Descriptor does not record extension. */
	if (t == DIR_REC_VD) {
		if (p != NULL)
			/* Length of Directory Record */
			set_num_711(p, (unsigned char)dr_len);
		else
			isoent->dr_len.vd = (int)dr_len;
		return ((int)dr_len);
	}

	/* Rockridge */
	if (iso9660->opt.rr && vdd_type != VDD_JOLIET)
		dr_len = set_directory_record_rr(bp, (int)dr_len,
		    isoent, iso9660, t);

	if (p != NULL)
		/* Length of Directory Record */
		set_num_711(p, (unsigned char)dr_len);
	else {
		/*
		 * Save the size which is needed to write this
		 * Directory Record.
		 */
		switch (t) {
		case DIR_REC_VD:
			/* This case does not come, but compiler
			 * complains that DIR_REC_VD not handled
			 *  in switch ....  */
			break;
		case DIR_REC_SELF:
			isoent->dr_len.self = (int)dr_len; break;
		case DIR_REC_PARENT:
			isoent->dr_len.parent = (int)dr_len; break;
		case DIR_REC_NORMAL:
			isoent->dr_len.normal = (int)dr_len; break;
		}
	}

	return ((int)dr_len);
}

/*
 * Calculate the size of a directory record.
 */
static inline int
get_dir_rec_size(struct iso9660 *iso9660, struct isoent *isoent,
    enum dir_rec_type t, enum vdd_type vdd_type)
{

	return (set_directory_record(NULL, SIZE_MAX,
	    isoent, iso9660, t, vdd_type));
}

/*
 * Manage to write ISO-image data with wbuff to reduce calling
 * __archive_write_output() for performance.
 */


static inline unsigned char *
wb_buffptr(struct archive_write *a)
{
	struct iso9660 *iso9660 = (struct iso9660 *)a->format_data;

	return (&(iso9660->wbuff[sizeof(iso9660->wbuff)
		- iso9660->wbuff_remaining]));
}

static int
wb_write_out(struct archive_write *a)
{
	struct iso9660 *iso9660 = (struct iso9660 *)a->format_data;
	size_t wsize, nw;
	int r;

	wsize = sizeof(iso9660->wbuff) - iso9660->wbuff_remaining;
	nw = wsize % LOGICAL_BLOCK_SIZE;
	if (iso9660->wbuff_type == WB_TO_STREAM)
		r = __archive_write_output(a, iso9660->wbuff, wsize - nw);
	else
		r = write_to_temp(a, iso9660->wbuff, wsize - nw);
	/* Increase the offset. */
	iso9660->wbuff_offset += wsize - nw;
	if (iso9660->wbuff_offset > iso9660->wbuff_written)
		iso9660->wbuff_written = iso9660->wbuff_offset;
	iso9660->wbuff_remaining = sizeof(iso9660->wbuff);
	if (nw) {
		iso9660->wbuff_remaining -= nw;
		memmove(iso9660->wbuff, iso9660->wbuff + wsize - nw, nw);
	}
	return (r);
}

static int
wb_consume(struct archive_write *a, size_t size)
{
	struct iso9660 *iso9660 = (struct iso9660 *)a->format_data;

	if (size > iso9660->wbuff_remaining ||
	    iso9660->wbuff_remaining == 0) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
		    "Internal Programming error: iso9660:wb_consume()"
		    " size=%jd, wbuff_remaining=%jd",
		    (intmax_t)size, (intmax_t)iso9660->wbuff_remaining);
		return (ARCHIVE_FATAL);
	}
	iso9660->wbuff_remaining -= size;
	if (iso9660->wbuff_remaining < LOGICAL_BLOCK_SIZE)
		return (wb_write_out(a));
	return (ARCHIVE_OK);
}

#ifdef HAVE_ZLIB_H

static int
wb_set_offset(struct archive_write *a, int64_t off)
{
	struct iso9660 *iso9660 = (struct iso9660 *)a->format_data;
	int64_t used, ext_bytes;

	if (iso9660->wbuff_type != WB_TO_TEMP) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
		    "Internal Programming error: iso9660:wb_set_offset()");
		return (ARCHIVE_FATAL);
	}

	used = sizeof(iso9660->wbuff) - iso9660->wbuff_remaining;
	if (iso9660->wbuff_offset + used > iso9660->wbuff_tail)
		iso9660->wbuff_tail = iso9660->wbuff_offset + used;
	if (iso9660->wbuff_offset < iso9660->wbuff_written) {
		if (used > 0 &&
		    write_to_temp(a, iso9660->wbuff, (size_t)used) != ARCHIVE_OK)
			return (ARCHIVE_FATAL);
		iso9660->wbuff_offset = iso9660->wbuff_written;
		lseek(iso9660->temp_fd, iso9660->wbuff_offset, SEEK_SET);
		iso9660->wbuff_remaining = sizeof(iso9660->wbuff);
		used = 0;
	}
	if (off < iso9660->wbuff_offset) {
		/*
		 * Write out waiting data.
		 */
		if (used > 0) {
			if (wb_write_out(a) != ARCHIVE_OK)
				return (ARCHIVE_FATAL);
		}
		lseek(iso9660->temp_fd, off, SEEK_SET);
		iso9660->wbuff_offset = off;
		iso9660->wbuff_remaining = sizeof(iso9660->wbuff);
	} else if (off <= iso9660->wbuff_tail) {
		iso9660->wbuff_remaining = (size_t)
		    (sizeof(iso9660->wbuff) - (off - iso9660->wbuff_offset));
	} else {
		ext_bytes = off - iso9660->wbuff_tail;
		iso9660->wbuff_remaining = (size_t)(sizeof(iso9660->wbuff)
		   - (iso9660->wbuff_tail - iso9660->wbuff_offset));
		while (ext_bytes >= (int64_t)iso9660->wbuff_remaining) {
			if (write_null(a, (size_t)iso9660->wbuff_remaining)
			    != ARCHIVE_OK)
				return (ARCHIVE_FATAL);
			ext_bytes -= iso9660->wbuff_remaining;
		}
		if (ext_bytes > 0) {
			if (write_null(a, (size_t)ext_bytes) != ARCHIVE_OK)
				return (ARCHIVE_FATAL);
		}
	}
	return (ARCHIVE_OK);
}

#endif /* HAVE_ZLIB_H */

static int
write_null(struct archive_write *a, size_t size)
{
	size_t remaining;
	unsigned char *p, *old;
	int r;

	remaining = wb_remaining(a);
	p = wb_buffptr(a);
	if (size <= remaining) {
		memset(p, 0, size);
		return (wb_consume(a, size));
	}
	memset(p, 0, remaining);
	r = wb_consume(a, remaining);
	if (r != ARCHIVE_OK)
		return (r);
	size -= remaining;
	old = p;
	p = wb_buffptr(a);
	memset(p, 0, old - p);
	remaining = wb_remaining(a);
	while (size) {
		size_t wsize = size;

		if (wsize > remaining)
			wsize = remaining;
		r = wb_consume(a, wsize);
		if (r != ARCHIVE_OK)
			return (r);
		size -= wsize;
	}
	return (ARCHIVE_OK);
}

/*
 * Write Volume Descriptor Set Terminator
 */
static int
write_VD_terminator(struct archive_write *a)
{
	unsigned char *bp;

	bp = wb_buffptr(a) -1;
	set_VD_bp(bp, VDT_TERMINATOR, 1);
	set_unused_field_bp(bp, 8, LOGICAL_BLOCK_SIZE);

	return (wb_consume(a, LOGICAL_BLOCK_SIZE));
}

static int
set_file_identifier(unsigned char *bp, int from, int to, enum vdc vdc,
    struct archive_write *a, struct vdd *vdd, struct archive_string *id,
    const char *label, int leading_under, enum char_type char_type)
{
	char identifier[256];
	struct isoent *isoent;
	const char *ids;
	size_t len;
	int r;

	if (id->length > 0 && leading_under && id->s[0] != '_') {
		if (char_type == A_CHAR)
			r = set_str_a_characters_bp(a, bp, from, to, id->s, vdc);
		else
			r = set_str_d_characters_bp(a, bp, from, to, id->s, vdc);
	} else if (id->length > 0) {
		ids = id->s;
		if (leading_under)
			ids++;
		isoent = isoent_find_entry(vdd->rootent, ids);
		if (isoent == NULL) {
			archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
			    "Not Found %s `%s'.",
			    label, ids);
			return (ARCHIVE_FATAL);
		}
		len = isoent->ext_off + isoent->ext_len;
		if (vdd->vdd_type == VDD_JOLIET) {
			if (len > sizeof(identifier)-2)
				len = sizeof(identifier)-2;
		} else {
			if (len > sizeof(identifier)-1)
				len = sizeof(identifier)-1;
		}
		memcpy(identifier, isoent->identifier, len);
		identifier[len] = '\0';
		if (vdd->vdd_type == VDD_JOLIET) {
			identifier[len+1] = 0;
			vdc = VDC_UCS2_DIRECT;
		}
		if (char_type == A_CHAR)
			r = set_str_a_characters_bp(a, bp, from, to,
			    identifier, vdc);
		else
			r = set_str_d_characters_bp(a, bp, from, to,
			    identifier, vdc);
	} else {
		if (char_type == A_CHAR)
			r = set_str_a_characters_bp(a, bp, from, to, NULL, vdc);
		else
			r = set_str_d_characters_bp(a, bp, from, to, NULL, vdc);
	}
	return (r);
}

/*
 * Write Primary/Supplementary Volume Descriptor
 */
static int
write_VD(struct archive_write *a, struct vdd *vdd)
{
	struct iso9660 *iso9660;
	unsigned char *bp;
	uint16_t volume_set_size = 1;
	char identifier[256];
	enum VD_type vdt;
	enum vdc vdc;
	unsigned char vd_ver, fst_ver;
	int r;

	iso9660 = a->format_data;
	switch (vdd->vdd_type) {
	case VDD_JOLIET:
		vdt = VDT_SUPPLEMENTARY;
		vd_ver = fst_ver = 1;
		vdc = VDC_UCS2;
		break;
	case VDD_ENHANCED:
		vdt = VDT_SUPPLEMENTARY;
		vd_ver = fst_ver = 2;
		vdc = VDC_LOWERCASE;
		break;
	case VDD_PRIMARY:
	default:
		vdt = VDT_PRIMARY;
		vd_ver = fst_ver = 1;
#ifdef COMPAT_MKISOFS
		vdc = VDC_LOWERCASE;
#else
		vdc = VDC_STD;
#endif
		break;
	}

	bp = wb_buffptr(a) -1;
	/* Volume Descriptor Type */
	set_VD_bp(bp, vdt, vd_ver);
	/* Unused Field */
	set_unused_field_bp(bp, 8, 8);
	/* System Identifier */
	get_system_identifier(identifier, sizeof(identifier));
	r = set_str_a_characters_bp(a, bp, 9, 40, identifier, vdc);
	if (r != ARCHIVE_OK)
		return (r);
	/* Volume Identifier */
	r = set_str_d_characters_bp(a, bp, 41, 72,
	    iso9660->volume_identifier.s, vdc);
	if (r != ARCHIVE_OK)
		return (r);
	/* Unused Field */
	set_unused_field_bp(bp, 73, 80);
	/* Volume Space Size */
	set_num_733(bp+81, iso9660->volume_space_size);
	if (vdd->vdd_type == VDD_JOLIET) {
		/* Escape Sequences */
		bp[89] = 0x25;/* UCS-2 Level 3 */
		bp[90] = 0x2F;
		bp[91] = 0x45;
		memset(bp + 92, 0, 120 - 92 + 1);
	} else {
		/* Unused Field */
		set_unused_field_bp(bp, 89, 120);
	}
	/* Volume Set Size */
	set_num_723(bp+121, volume_set_size);
	/* Volume Sequence Number */
	set_num_723(bp+125, iso9660->volume_sequence_number);
	/* Logical Block Size */
	set_num_723(bp+129, LOGICAL_BLOCK_SIZE);
	/* Path Table Size */
	set_num_733(bp+133, vdd->path_table_size);
	/* Location of Occurrence of Type L Path Table */
	set_num_731(bp+141, vdd->location_type_L_path_table);
	/* Location of Optional Occurrence of Type L Path Table */
	set_num_731(bp+145, 0);
	/* Location of Occurrence of Type M Path Table */
	set_num_732(bp+149, vdd->location_type_M_path_table);
	/* Location of Optional Occurrence of Type M Path Table */
	set_num_732(bp+153, 0);
	/* Directory Record for Root Directory(BP 157 to 190) */
	set_directory_record(bp+157, 190-157+1, vdd->rootent,
	    iso9660, DIR_REC_VD, vdd->vdd_type);
	/* Volume Set Identifier */
	r = set_str_d_characters_bp(a, bp, 191, 318, "", vdc);
	if (r != ARCHIVE_OK)
		return (r);
	/* Publisher Identifier */
	r = set_file_identifier(bp, 319, 446, vdc, a, vdd,
	    &(iso9660->publisher_identifier),
	    "Publisher File", 1, A_CHAR);
	if (r != ARCHIVE_OK)
		return (r);
	/* Data Preparer Identifier */
	r = set_file_identifier(bp, 447, 574, vdc, a, vdd,
	    &(iso9660->data_preparer_identifier),
	    "Data Preparer File", 1, A_CHAR);
	if (r != ARCHIVE_OK)
		return (r);
	/* Application Identifier */
	r = set_file_identifier(bp, 575, 702, vdc, a, vdd,
	    &(iso9660->application_identifier),
	    "Application File", 1, A_CHAR);
	if (r != ARCHIVE_OK)
		return (r);
	/* Copyright File Identifier */
	r = set_file_identifier(bp, 703, 739, vdc, a, vdd,
	    &(iso9660->copyright_file_identifier),
	    "Copyright File", 0, D_CHAR);
	if (r != ARCHIVE_OK)
		return (r);
	/* Abstract File Identifier */
	r = set_file_identifier(bp, 740, 776, vdc, a, vdd,
	    &(iso9660->abstract_file_identifier),
	    "Abstract File", 0, D_CHAR);
	if (r != ARCHIVE_OK)
		return (r);
	/* Bibliographic File Identifier */
	r = set_file_identifier(bp, 777, 813, vdc, a, vdd,
	    &(iso9660->bibliographic_file_identifier),
	    "Bibliongraphic File", 0, D_CHAR);
	if (r != ARCHIVE_OK)
		return (r);
	/* Volume Creation Date and Time */
	set_date_time(bp+814, iso9660->birth_time);
	/* Volume Modification Date and Time */
	set_date_time(bp+831, iso9660->birth_time);
	/* Volume Expiration Date and Time(obsolete) */
	set_date_time_null(bp+848);
	/* Volume Effective Date and Time */
	set_date_time(bp+865, iso9660->birth_time);
	/* File Structure Version */
	bp[882] = fst_ver;
	/* Reserved */
	bp[883] = 0;
	/* Application Use */
	memset(bp + 884, 0x20, 1395 - 884 + 1);
	/* Reserved */
	set_unused_field_bp(bp, 1396, LOGICAL_BLOCK_SIZE);

	return (wb_consume(a, LOGICAL_BLOCK_SIZE));
}

/*
 * Write Boot Record Volume Descriptor
 */
static int
write_VD_boot_record(struct archive_write *a)
{
	struct iso9660 *iso9660;
	unsigned char *bp;

	iso9660 = a->format_data;
	bp = wb_buffptr(a) -1;
	/* Volume Descriptor Type */
	set_VD_bp(bp, VDT_BOOT_RECORD, 1);
	/* Boot System Identifier */
	memcpy(bp+8, "EL TORITO SPECIFICATION", 23);
	set_unused_field_bp(bp, 8+23, 39);
	/* Unused */
	set_unused_field_bp(bp, 40, 71);
	/* Absolute pointer to first sector of Boot Catalog */
	set_num_731(bp+72,
	    iso9660->el_torito.catalog->file->content.location);
	/* Unused */
	set_unused_field_bp(bp, 76, LOGICAL_BLOCK_SIZE);

	return (wb_consume(a, LOGICAL_BLOCK_SIZE));
}

enum keytype {
	KEY_FLG,
	KEY_STR,
	KEY_INT,
	KEY_HEX
};
static void
set_option_info(struct archive_string *info, int *opt, const char *key,
    enum keytype type,  ...)
{
	va_list ap;
	char prefix;
	const char *s;
	int d;

	prefix = (*opt==0)? ' ':',';
	va_start(ap, type);
	switch (type) {
	case KEY_FLG:
		d = va_arg(ap, int);
		archive_string_sprintf(info, "%c%s%s",
		    prefix, (d == 0)?"!":"", key);
		break;
	case KEY_STR:
		s = va_arg(ap, const char *);
		archive_string_sprintf(info, "%c%s=%s",
		    prefix, key, s);
		break;
	case KEY_INT:
		d = va_arg(ap, int);
		archive_string_sprintf(info, "%c%s=%d",
		    prefix, key, d);
		break;
	case KEY_HEX:
		d = va_arg(ap, int);
		archive_string_sprintf(info, "%c%s=%x",
		    prefix, key, (unsigned int)d);
		break;
	}
	va_end(ap);

	*opt = 1;
}

/*
 * Make Non-ISO File System Information
 */
static int
write_information_block(struct archive_write *a)
{
	struct iso9660 *iso9660;
	char buf[128];
	const char *v;
	int opt, r;
	struct archive_string info;
	size_t info_size = LOGICAL_BLOCK_SIZE *
			       NON_ISO_FILE_SYSTEM_INFORMATION_BLOCK;

	iso9660 = (struct iso9660 *)a->format_data;
	if (info_size > wb_remaining(a)) {
		r = wb_write_out(a);
		if (r != ARCHIVE_OK)
			return (r);
	}
	archive_string_init(&info);
	if (archive_string_ensure(&info, info_size) == NULL) {
		archive_set_error(&a->archive, ENOMEM,
		    "Can't allocate memory");
		return (ARCHIVE_FATAL);
	}
	memset(info.s, 0, info_size);
	opt = 0;
#if defined(HAVE_CTIME_S)
	ctime_s(buf, sizeof(buf), &(iso9660->birth_time));
#elif defined(HAVE_CTIME_R)
	ctime_r(&(iso9660->birth_time), buf);
#else
	strncpy(buf, ctime(&(iso9660->birth_time)), sizeof(buf)-1);
	buf[sizeof(buf)-1] = '\0';
#endif
	archive_string_sprintf(&info,
	    "INFO %s%s", buf, archive_version_string());
	if (iso9660->opt.abstract_file != OPT_ABSTRACT_FILE_DEFAULT)
		set_option_info(&info, &opt, "abstract-file",
		    KEY_STR, iso9660->abstract_file_identifier.s);
	if (iso9660->opt.application_id != OPT_APPLICATION_ID_DEFAULT)
		set_option_info(&info, &opt, "application-id",
		    KEY_STR, iso9660->application_identifier.s);
	if (iso9660->opt.allow_vernum != OPT_ALLOW_VERNUM_DEFAULT)
		set_option_info(&info, &opt, "allow-vernum",
		    KEY_FLG, iso9660->opt.allow_vernum);
	if (iso9660->opt.biblio_file != OPT_BIBLIO_FILE_DEFAULT)
		set_option_info(&info, &opt, "biblio-file",
		    KEY_STR, iso9660->bibliographic_file_identifier.s);
	if (iso9660->opt.boot != OPT_BOOT_DEFAULT)
		set_option_info(&info, &opt, "boot",
		    KEY_STR, iso9660->el_torito.boot_filename.s);
	if (iso9660->opt.boot_catalog != OPT_BOOT_CATALOG_DEFAULT)
		set_option_info(&info, &opt, "boot-catalog",
		    KEY_STR, iso9660->el_torito.catalog_filename.s);
	if (iso9660->opt.boot_info_table != OPT_BOOT_INFO_TABLE_DEFAULT)
		set_option_info(&info, &opt, "boot-info-table",
		    KEY_FLG, iso9660->opt.boot_info_table);
	if (iso9660->opt.boot_load_seg != OPT_BOOT_LOAD_SEG_DEFAULT)
		set_option_info(&info, &opt, "boot-load-seg",
		    KEY_HEX, iso9660->el_torito.boot_load_seg);
	if (iso9660->opt.boot_load_size != OPT_BOOT_LOAD_SIZE_DEFAULT)
		set_option_info(&info, &opt, "boot-load-size",
		    KEY_INT, iso9660->el_torito.boot_load_size);
	if (iso9660->opt.boot_type != OPT_BOOT_TYPE_DEFAULT) {
		v = "no-emulation";
		if (iso9660->opt.boot_type == OPT_BOOT_TYPE_FD)
			v = "fd";
		if (iso9660->opt.boot_type == OPT_BOOT_TYPE_HARD_DISK)
			v = "hard-disk";
		set_option_info(&info, &opt, "boot-type",
		    KEY_STR, v);
	}
#ifdef HAVE_ZLIB_H
	if (iso9660->opt.compression_level != OPT_COMPRESSION_LEVEL_DEFAULT)
		set_option_info(&info, &opt, "compression-level",
		    KEY_INT, iso9660->zisofs.compression_level);
#endif
	if (iso9660->opt.copyright_file != OPT_COPYRIGHT_FILE_DEFAULT)
		set_option_info(&info, &opt, "copyright-file",
		    KEY_STR, iso9660->copyright_file_identifier.s);
	if (iso9660->opt.iso_level != OPT_ISO_LEVEL_DEFAULT)
		set_option_info(&info, &opt, "iso-level",
		    KEY_INT, iso9660->opt.iso_level);
	if (iso9660->opt.joliet != OPT_JOLIET_DEFAULT) {
		if (iso9660->opt.joliet == OPT_JOLIET_LONGNAME)
			set_option_info(&info, &opt, "joliet",
			    KEY_STR, "long");
		else
			set_option_info(&info, &opt, "joliet",
			    KEY_FLG, iso9660->opt.joliet);
	}
	if (iso9660->opt.limit_depth != OPT_LIMIT_DEPTH_DEFAULT)
		set_option_info(&info, &opt, "limit-depth",
		    KEY_FLG, iso9660->opt.limit_depth);
	if (iso9660->opt.limit_dirs != OPT_LIMIT_DIRS_DEFAULT)
		set_option_info(&info, &opt, "limit-dirs",
		    KEY_FLG, iso9660->opt.limit_dirs);
	if (iso9660->opt.pad != OPT_PAD_DEFAULT)
		set_option_info(&info, &opt, "pad",
		    KEY_FLG, iso9660->opt.pad);
	if (iso9660->opt.publisher != OPT_PUBLISHER_DEFAULT)
		set_option_info(&info, &opt, "publisher",
		    KEY_STR, iso9660->publisher_identifier.s);
	if (iso9660->opt.rr != OPT_RR_DEFAULT) {
		if (iso9660->opt.rr == OPT_RR_DISABLED)
			set_option_info(&info, &opt, "rockridge",
			    KEY_FLG, iso9660->opt.rr);
		else if (iso9660->opt.rr == OPT_RR_STRICT)
			set_option_info(&info, &opt, "rockridge",
			    KEY_STR, "strict");
		else if (iso9660->opt.rr == OPT_RR_USEFUL)
			set_option_info(&info, &opt, "rockridge",
			    KEY_STR, "useful");
	}
	if (iso9660->opt.volume_id != OPT_VOLUME_ID_DEFAULT)
		set_option_info(&info, &opt, "volume-id",
		    KEY_STR, iso9660->volume_identifier.s);
	if (iso9660->opt.zisofs != OPT_ZISOFS_DEFAULT)
		set_option_info(&info, &opt, "zisofs",
		    KEY_FLG, iso9660->opt.zisofs);

	memcpy(wb_buffptr(a), info.s, info_size);
	archive_string_free(&info);
	return (wb_consume(a, info_size));
}

static int
write_rr_ER(struct archive_write *a)
{
	unsigned char *p;

	p = wb_buffptr(a);

	memset(p, 0, LOGICAL_BLOCK_SIZE);
	p[0] = 'E';
	p[1] = 'R';
	p[3] = 0x01;
	p[2] = RRIP_ER_SIZE;
	p[4] = RRIP_ER_ID_SIZE;
	p[5] = RRIP_ER_DSC_SIZE;
	p[6] = RRIP_ER_SRC_SIZE;
	p[7] = 0x01;
	memcpy(&p[8], rrip_identifier, p[4]);
	memcpy(&p[8+p[4]], rrip_descriptor, p[5]);
	memcpy(&p[8+p[4]+p[5]], rrip_source, p[6]);

	return (wb_consume(a, LOGICAL_BLOCK_SIZE));
}

static void
calculate_path_table_size(struct vdd *vdd)
{
	int depth, size;
	struct path_table *pt;

	pt = vdd->pathtbl;
	size = 0;
	for (depth = 0; depth < vdd->max_depth; depth++) {
		struct isoent **ptbl;
		int i, cnt;

		if ((cnt = pt[depth].cnt) == 0)
			break;

		ptbl = pt[depth].sorted;
		for (i = 0; i < cnt; i++) {
			int len;

			if (ptbl[i]->identifier == NULL)
				len = 1; /* root directory */
			else
				len = ptbl[i]->id_len;
			if (len & 0x01)
				len++; /* Padding Field */
			size += 8 + len;
		}
	}
	vdd->path_table_size = size;
	vdd->path_table_block =
	    ((size + PATH_TABLE_BLOCK_SIZE -1) /
	    PATH_TABLE_BLOCK_SIZE) *
	    (PATH_TABLE_BLOCK_SIZE / LOGICAL_BLOCK_SIZE);
}

static int
_write_path_table(struct archive_write *a, int type_m, int depth,
    struct vdd *vdd)
{
	unsigned char *bp, *wb;
	struct isoent **ptbl;
	size_t wbremaining;
	int i, r, wsize;

	if (vdd->pathtbl[depth].cnt == 0)
		return (0);

	wsize = 0;
	wb = wb_buffptr(a);
	wbremaining = wb_remaining(a);
	bp = wb - 1;
	ptbl = vdd->pathtbl[depth].sorted;
	for (i = 0; i < vdd->pathtbl[depth].cnt; i++) {
		struct isoent *np;
		size_t len;

		np = ptbl[i];
		if (np->identifier == NULL)
			len = 1; /* root directory */
		else
			len = np->id_len;
		if (wbremaining - ((bp+1) - wb) < (len + 1 + 8)) {
			r = wb_consume(a, (bp+1) - wb);
			if (r < 0)
				return (r);
			wb = wb_buffptr(a);
			wbremaining = wb_remaining(a);
			bp = wb -1;
		}
		/* Length of Directory Identifier */
		set_num_711(bp+1, (unsigned char)len);
		/* Extended Attribute Record Length */
		set_num_711(bp+2, 0);
		/* Location of Extent */
		if (type_m)
			set_num_732(bp+3, np->dir_location);
		else
			set_num_731(bp+3, np->dir_location);
		/* Parent Directory Number */
		if (type_m)
			set_num_722(bp+7, np->parent->dir_number);
		else
			set_num_721(bp+7, np->parent->dir_number);
		/* Directory Identifier */
		if (np->identifier == NULL)
			bp[9] = 0;
		else
			memcpy(&bp[9], np->identifier, len);
		if (len & 0x01) {
			/* Padding Field */
			bp[9+len] = 0;
			len++;
		}
		wsize += 8 + (int)len;
		bp += 8 + len;
	}
	if ((bp + 1) > wb) {
		r = wb_consume(a, (bp+1)-wb);
		if (r < 0)
			return (r);
	}
	return (wsize);
}

static int
write_path_table(struct archive_write *a, int type_m, struct vdd *vdd)
{
	int depth, r;
	size_t path_table_size;

	r = ARCHIVE_OK;
	path_table_size = 0;
	for (depth = 0; depth < vdd->max_depth; depth++) {
		r = _write_path_table(a, type_m, depth, vdd);
		if (r < 0)
			return (r);
		path_table_size += r;
	}

	/* Write padding data. */
	path_table_size = path_table_size % PATH_TABLE_BLOCK_SIZE;
	if (path_table_size > 0)
		r = write_null(a, PATH_TABLE_BLOCK_SIZE - path_table_size);
	return (r);
}

static int
calculate_directory_descriptors(struct iso9660 *iso9660, struct vdd *vdd,
    struct isoent *isoent, int depth)
{
	struct isoent **enttbl;
	int bs, block, i;

	block = 1;
	bs = get_dir_rec_size(iso9660, isoent, DIR_REC_SELF, vdd->vdd_type);
	bs += get_dir_rec_size(iso9660, isoent, DIR_REC_PARENT, vdd->vdd_type);

	if (isoent->children.cnt <= 0 || (vdd->vdd_type != VDD_JOLIET &&
	    !iso9660->opt.rr && depth + 1 >= vdd->max_depth))
		return (block);

	enttbl = isoent->children_sorted;
	for (i = 0; i < isoent->children.cnt; i++) {
		struct isoent *np = enttbl[i];
		struct isofile *file;

		file = np->file;
		if (file->hardlink_target != NULL)
			file = file->hardlink_target;
		file->cur_content = &(file->content);
		do {
			int dr_l;

			dr_l = get_dir_rec_size(iso9660, np, DIR_REC_NORMAL,
			    vdd->vdd_type);
			if ((bs + dr_l) > LOGICAL_BLOCK_SIZE) {
				block ++;
				bs = dr_l;
			} else
				bs += dr_l;
			file->cur_content = file->cur_content->next;
		} while (file->cur_content != NULL);
	}
	return (block);
}

static int
_write_directory_descriptors(struct archive_write *a, struct vdd *vdd,
    struct isoent *isoent, int depth)
{
	struct iso9660 *iso9660 = a->format_data;
	struct isoent **enttbl;
	unsigned char *p, *wb;
	int i, r;
	int dr_l;

	p = wb = wb_buffptr(a);
#define WD_REMAINING	(LOGICAL_BLOCK_SIZE - (p - wb))
	p += set_directory_record(p, WD_REMAINING, isoent,
	    iso9660, DIR_REC_SELF, vdd->vdd_type);
	p += set_directory_record(p, WD_REMAINING, isoent,
	    iso9660, DIR_REC_PARENT, vdd->vdd_type);

	if (isoent->children.cnt <= 0 || (vdd->vdd_type != VDD_JOLIET &&
	    !iso9660->opt.rr && depth + 1 >= vdd->max_depth)) {
		memset(p, 0, WD_REMAINING);
		return (wb_consume(a, LOGICAL_BLOCK_SIZE));
	}

	enttbl = isoent->children_sorted;
	for (i = 0; i < isoent->children.cnt; i++) {
		struct isoent *np = enttbl[i];
		struct isofile *file = np->file;

		if (file->hardlink_target != NULL)
			file = file->hardlink_target;
		file->cur_content = &(file->content);
		do {
			dr_l = set_directory_record(p, WD_REMAINING,
			    np, iso9660, DIR_REC_NORMAL,
			    vdd->vdd_type);
			if (dr_l == 0) {
				memset(p, 0, WD_REMAINING);
				r = wb_consume(a, LOGICAL_BLOCK_SIZE);
				if (r < 0)
					return (r);
				p = wb = wb_buffptr(a);
				dr_l = set_directory_record(p,
				    WD_REMAINING, np, iso9660,
				    DIR_REC_NORMAL, vdd->vdd_type);
			}
			p += dr_l;
			file->cur_content = file->cur_content->next;
		} while (file->cur_content != NULL);
	}
	memset(p, 0, WD_REMAINING);
	return (wb_consume(a, LOGICAL_BLOCK_SIZE));
}

static int
write_directory_descriptors(struct archive_write *a, struct vdd *vdd)
{
	struct isoent *np;
	int depth, r;

	depth = 0;
	np = vdd->rootent;
	do {
		struct extr_rec *extr;

		r = _write_directory_descriptors(a, vdd, np, depth);
		if (r < 0)
			return (r);
		if (vdd->vdd_type != VDD_JOLIET) {
			/*
			 * This extract record is used by SUSP,RRIP.
			 * Not for joliet.
			 */
			for (extr = np->extr_rec_list.first;
			    extr != NULL;
			    extr = extr->next) {
				unsigned char *wb;

				wb = wb_buffptr(a);
				memcpy(wb, extr->buf, extr->offset);
				memset(wb + extr->offset, 0,
				    LOGICAL_BLOCK_SIZE - extr->offset);
				r = wb_consume(a, LOGICAL_BLOCK_SIZE);
				if (r < 0)
					return (r);
			}
		}

		if (np->subdirs.first != NULL && depth + 1 < vdd->max_depth) {
			/* Enter to sub directories. */
			np = np->subdirs.first;
			depth++;
			continue;
		}
		while (np != np->parent) {
			if (np->drnext == NULL) {
				/* Return to the parent directory. */
				np = np->parent;
				depth--;
			} else {
				np = np->drnext;
				break;
			}
		}
	} while (np != np->parent);

	return (ARCHIVE_OK);
}

/*
 * Read file contents from the temporary file, and write it.
 */
static int
write_file_contents(struct archive_write *a, int64_t offset, int64_t size)
{
	struct iso9660 *iso9660 = a->format_data;
	int r;

	lseek(iso9660->temp_fd, offset, SEEK_SET);

	while (size) {
		size_t rsize;
		ssize_t rs;
		unsigned char *wb;

		wb = wb_buffptr(a);
		rsize = wb_remaining(a);
		if (rsize > (size_t)size)
			rsize = (size_t)size;
		rs = read(iso9660->temp_fd, wb, rsize);
		if (rs <= 0) {
			archive_set_error(&a->archive, errno,
			    "Can't read temporary file(%jd)", (intmax_t)rs);
			return (ARCHIVE_FATAL);
		}
		size -= rs;
		r = wb_consume(a, rs);
		if (r < 0)
			return (r);
	}
	return (ARCHIVE_OK);
}

static int
write_file_descriptors(struct archive_write *a)
{
	struct iso9660 *iso9660 = a->format_data;
	struct isofile *file;
	int64_t blocks, offset;
	int r;

	blocks = 0;
	offset = 0;

	/* Make the boot catalog contents, and write it. */
	if (iso9660->el_torito.catalog != NULL) {
		r = make_boot_catalog(a);
		if (r < 0)
			return (r);
	}

	/* Write the boot file contents. */
	if (iso9660->el_torito.boot != NULL) {
		file = iso9660->el_torito.boot->file;
		blocks = file->content.blocks;
		offset = file->content.offset_of_temp;
		if (offset != 0) {
			r = write_file_contents(a, offset,
			    blocks << LOGICAL_BLOCK_BITS);
			if (r < 0)
				return (r);
			blocks = 0;
			offset = 0;
		}
	}

	/* Write out all file contents. */
	for (file = iso9660->data_file_list.first;
	    file != NULL; file = file->datanext) {

		if (!file->write_content)
			continue;

		if ((offset + (blocks << LOGICAL_BLOCK_BITS)) <
		     file->content.offset_of_temp) {
			if (blocks > 0) {
				r = write_file_contents(a, offset,
				    blocks << LOGICAL_BLOCK_BITS);
				if (r < 0)
					return (r);
			}
			blocks = 0;
			offset = file->content.offset_of_temp;
		}

		file->cur_content = &(file->content);
		do {
			blocks += file->cur_content->blocks;
			/* Next fragment */
			file->cur_content = file->cur_content->next;
		} while (file->cur_content != NULL);
	}

	/* Flush out remaining blocks. */
	if (blocks > 0) {
		r = write_file_contents(a, offset,
		    blocks << LOGICAL_BLOCK_BITS);
		if (r < 0)
			return (r);
	}

	return (ARCHIVE_OK);
}

static void
isofile_init_entry_list(struct iso9660 *iso9660)
{
	iso9660->all_file_list.first = NULL;
	iso9660->all_file_list.last = &(iso9660->all_file_list.first);
}

static void
isofile_add_entry(struct iso9660 *iso9660, struct isofile *file)
{
	file->allnext = NULL;
	*iso9660->all_file_list.last = file;
	iso9660->all_file_list.last = &(file->allnext);
}

static void
isofile_free_all_entries(struct iso9660 *iso9660)
{
	struct isofile *file, *file_next;

	file = iso9660->all_file_list.first;
	while (file != NULL) {
		file_next = file->allnext;
		isofile_free(file);
		file = file_next;
	}
}

static void
isofile_init_entry_data_file_list(struct iso9660 *iso9660)
{
	iso9660->data_file_list.first = NULL;
	iso9660->data_file_list.last = &(iso9660->data_file_list.first);
}

static void
isofile_add_data_file(struct iso9660 *iso9660, struct isofile *file)
{
	file->datanext = NULL;
	*iso9660->data_file_list.last = file;
	iso9660->data_file_list.last = &(file->datanext);
}


static struct isofile *
isofile_new(struct archive_write *a, struct archive_entry *entry)
{
	struct isofile *file;

	file = calloc(1, sizeof(*file));
	if (file == NULL)
		return (NULL);

	if (entry != NULL)
		file->entry = archive_entry_clone(entry);
	else
		file->entry = archive_entry_new2(&a->archive);
	if (file->entry == NULL) {
		free(file);
		return (NULL);
	}
	archive_string_init(&(file->parentdir));
	archive_string_init(&(file->basename));
	archive_string_init(&(file->basename_utf16));
	archive_string_init(&(file->symlink));
	file->cur_content = &(file->content);

	return (file);
}

static void
isofile_free(struct isofile *file)
{
	struct content *con, *tmp;

	con = file->content.next;
	while (con != NULL) {
		tmp = con;
		con = con->next;
		free(tmp);
	}
	archive_entry_free(file->entry);
	archive_string_free(&(file->parentdir));
	archive_string_free(&(file->basename));
	archive_string_free(&(file->basename_utf16));
	archive_string_free(&(file->symlink));
	free(file);
}

#if defined(_WIN32) || defined(__CYGWIN__)
static int
cleanup_backslash_1(char *p)
{
	int mb, dos;

	mb = dos = 0;
	while (*p) {
		if (*(unsigned char *)p > 127)
			mb = 1;
		if (*p == '\\') {
			/* If we have not met any multi-byte characters,
			 * we can replace '\' with '/'. */
			if (!mb)
				*p = '/';
			dos = 1;
		}
		p++;
	}
	if (!mb || !dos)
		return (0);
	return (-1);
}

static void
cleanup_backslash_2(wchar_t *p)
{

	/* Convert a path-separator from '\' to  '/' */
	while (*p != L'\0') {
		if (*p == L'\\')
			*p = L'/';
		p++;
	}
}
#endif

/*
 * Generate a parent directory name and a base name from a pathname.
 */
static int
isofile_gen_utility_names(struct archive_write *a, struct isofile *file)
{
	struct iso9660 *iso9660;
	const char *pathname;
	char *p, *dirname, *slash;
	size_t len;
	int ret = ARCHIVE_OK;

	iso9660 = a->format_data;

	archive_string_empty(&(file->parentdir));
	archive_string_empty(&(file->basename));
	archive_string_empty(&(file->basename_utf16));
	archive_string_empty(&(file->symlink));

	pathname =  archive_entry_pathname(file->entry);
	if (pathname == NULL || pathname[0] == '\0') {/* virtual root */
		file->dircnt = 0;
		return (ret);
	}

	/*
	 * Make a UTF-16BE basename if Joliet extension enabled.
	 */
	if (iso9660->opt.joliet) {
		const char *u16, *ulast;
		size_t u16len, ulen_last;

		if (iso9660->sconv_to_utf16be == NULL) {
			iso9660->sconv_to_utf16be =
			    archive_string_conversion_to_charset(
				&(a->archive), "UTF-16BE", 1);
			if (iso9660->sconv_to_utf16be == NULL)
				/* Couldn't allocate memory */
				return (ARCHIVE_FATAL);
			iso9660->sconv_from_utf16be =
			    archive_string_conversion_from_charset(
				&(a->archive), "UTF-16BE", 1);
			if (iso9660->sconv_from_utf16be == NULL)
				/* Couldn't allocate memory */
				return (ARCHIVE_FATAL);
		}

		/*
		 * Convert a filename to UTF-16BE.
		 */
		if (0 > archive_entry_pathname_l(file->entry, &u16, &u16len,
		    iso9660->sconv_to_utf16be)) {
			if (errno == ENOMEM) {
				archive_set_error(&a->archive, ENOMEM,
				    "Can't allocate memory for UTF-16BE");
				return (ARCHIVE_FATAL);
			}
			archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
			    "A filename cannot be converted to UTF-16BE;"
			    "You should disable making Joliet extension");
			ret = ARCHIVE_WARN;
		}

		/*
		 * Make sure a path separator is not in the last;
		 * Remove trailing '/'.
		 */
		while (u16len >= 2) {
#if defined(_WIN32) || defined(__CYGWIN__)
			if (u16[u16len-2] == 0 &&
			    (u16[u16len-1] == '/' || u16[u16len-1] == '\\'))
#else
			if (u16[u16len-2] == 0 && u16[u16len-1] == '/')
#endif
			{
				u16len -= 2;
			} else
				break;
		}

		/*
		 * Find a basename in UTF-16BE.
		 */
		ulast = u16;
		u16len >>= 1;
		ulen_last = u16len;
		while (u16len > 0) {
#if defined(_WIN32) || defined(__CYGWIN__)
			if (u16[0] == 0 && (u16[1] == '/' || u16[1] == '\\'))
#else
			if (u16[0] == 0 && u16[1] == '/')
#endif
			{
				ulast = u16 + 2;
				ulen_last = u16len -1;
			}
			u16 += 2;
			u16len --;
		}
		ulen_last <<= 1;
		if (archive_string_ensure(&(file->basename_utf16),
		    ulen_last) == NULL) {
			archive_set_error(&a->archive, ENOMEM,
			    "Can't allocate memory for UTF-16BE");
			return (ARCHIVE_FATAL);
		}

		/*
		 * Set UTF-16BE basename.
		 */
		memcpy(file->basename_utf16.s, ulast, ulen_last);
		file->basename_utf16.length = ulen_last;
	}

	archive_strcpy(&(file->parentdir), pathname);
#if defined(_WIN32) || defined(__CYGWIN__)
	/*
	 * Convert a path-separator from '\' to  '/'
	 */
	if (cleanup_backslash_1(file->parentdir.s) != 0) {
		const wchar_t *wp = archive_entry_pathname_w(file->entry);
		struct archive_wstring ws;

		if (wp != NULL) {
			int r;
			archive_string_init(&ws);
			archive_wstrcpy(&ws, wp);
			cleanup_backslash_2(ws.s);
			archive_string_empty(&(file->parentdir));
			r = archive_string_append_from_wcs(&(file->parentdir),
			    ws.s, ws.length);
			archive_wstring_free(&ws);
			if (r < 0 && errno == ENOMEM) {
				archive_set_error(&a->archive, ENOMEM,
				    "Can't allocate memory");
				return (ARCHIVE_FATAL);
			}
		}
	}
#endif

	len = file->parentdir.length;
	p = dirname = file->parentdir.s;

	/*
	 * Remove leading '/', '../' and './' elements
	 */
	while (*p) {
		if (p[0] == '/') {
			p++;
			len--;
		} else if (p[0] != '.')
			break;
		else if (p[1] == '.' && p[2] == '/') {
			p += 3;
			len -= 3;
		} else if (p[1] == '/' || (p[1] == '.' && p[2] == '\0')) {
			p += 2;
			len -= 2;
		} else if (p[1] == '\0') {
			p++;
			len--;
		} else
			break;
	}
	if (p != dirname) {
		memmove(dirname, p, len+1);
		p = dirname;
	}
	/*
	 * Remove "/","/." and "/.." elements from tail.
	 */
	while (len > 0) {
		size_t ll = len;

		if (len > 0 && p[len-1] == '/') {
			p[len-1] = '\0';
			len--;
		}
		if (len > 1 && p[len-2] == '/' && p[len-1] == '.') {
			p[len-2] = '\0';
			len -= 2;
		}
		if (len > 2 && p[len-3] == '/' && p[len-2] == '.' &&
		    p[len-1] == '.') {
			p[len-3] = '\0';
			len -= 3;
		}
		if (ll == len)
			break;
	}
	while (*p) {
		if (p[0] == '/') {
			if (p[1] == '/')
				/* Convert '//' --> '/' */
				memmove(p, p+1, strlen(p+1) + 1);
			else if (p[1] == '.' && p[2] == '/')
				/* Convert '/./' --> '/' */
				memmove(p, p+2, strlen(p+2) + 1);
			else if (p[1] == '.' && p[2] == '.' && p[3] == '/') {
				/* Convert 'dir/dir1/../dir2/'
				 *     --> 'dir/dir2/'
				 */
				char *rp = p -1;
				while (rp >= dirname) {
					if (*rp == '/')
						break;
					--rp;
				}
				if (rp > dirname) {
					strcpy(rp, p+3);
					p = rp;
				} else {
					strcpy(dirname, p+4);
					p = dirname;
				}
			} else
				p++;
		} else
			p++;
	}
	p = dirname;
	len = strlen(p);

	if (archive_entry_filetype(file->entry) == AE_IFLNK) {
		/* Convert symlink name too. */
		pathname = archive_entry_symlink(file->entry);
		archive_strcpy(&(file->symlink),  pathname);
#if defined(_WIN32) || defined(__CYGWIN__)
		/*
		 * Convert a path-separator from '\' to  '/'
		 */
		if (archive_strlen(&(file->symlink)) > 0 &&
		    cleanup_backslash_1(file->symlink.s) != 0) {
			const wchar_t *wp =
			    archive_entry_symlink_w(file->entry);
			struct archive_wstring ws;

			if (wp != NULL) {
				int r;
				archive_string_init(&ws);
				archive_wstrcpy(&ws, wp);
				cleanup_backslash_2(ws.s);
				archive_string_empty(&(file->symlink));
				r = archive_string_append_from_wcs(
				    &(file->symlink),
				    ws.s, ws.length);
				archive_wstring_free(&ws);
				if (r < 0 && errno == ENOMEM) {
					archive_set_error(&a->archive, ENOMEM,
					    "Can't allocate memory");
					return (ARCHIVE_FATAL);
				}
			}
		}
#endif
	}
	/*
	 * - Count up directory elements.
	 * - Find out the position which points the last position of
	 *   path separator('/').
	 */
	slash = NULL;
	file->dircnt = 0;
	for (; *p != '\0'; p++)
		if (*p == '/') {
			slash = p;
			file->dircnt++;
		}
	if (slash == NULL) {
		/* The pathname doesn't have a parent directory. */
		file->parentdir.length = len;
		archive_string_copy(&(file->basename), &(file->parentdir));
		archive_string_empty(&(file->parentdir));
		*file->parentdir.s = '\0';
		return (ret);
	}

	/* Make a basename from dirname and slash */
	*slash  = '\0';
	file->parentdir.length = slash - dirname;
	archive_strcpy(&(file->basename),  slash + 1);
	if (archive_entry_filetype(file->entry) == AE_IFDIR)
		file->dircnt ++;
	return (ret);
}

/*
 * Register a entry to get a hardlink target.
 */
static int
isofile_register_hardlink(struct archive_write *a, struct isofile *file)
{
	struct iso9660 *iso9660 = a->format_data;
	struct hardlink *hl;
	const char *pathname;

	archive_entry_set_nlink(file->entry, 1);
	pathname = archive_entry_hardlink(file->entry);
	if (pathname == NULL) {
		/* This `file` is a hardlink target. */
		hl = malloc(sizeof(*hl));
		if (hl == NULL) {
			archive_set_error(&a->archive, ENOMEM,
			    "Can't allocate memory");
			return (ARCHIVE_FATAL);
		}
		hl->nlink = 1;
		/* A hardlink target must be the first position. */
		file->hlnext = NULL;
		hl->file_list.first = file;
		hl->file_list.last = &(file->hlnext);
		__archive_rb_tree_insert_node(&(iso9660->hardlink_rbtree),
		    (struct archive_rb_node *)hl);
	} else {
		hl = (struct hardlink *)__archive_rb_tree_find_node(
		    &(iso9660->hardlink_rbtree), pathname);
		if (hl != NULL) {
			/* Insert `file` entry into the tail. */
			file->hlnext = NULL;
			*hl->file_list.last = file;
			hl->file_list.last = &(file->hlnext);
			hl->nlink++;
		}
		archive_entry_unset_size(file->entry);
	}

	return (ARCHIVE_OK);
}

/*
 * Hardlinked files have to have the same location of extent.
 * We have to find out hardlink target entries for the entries
 * which have a hardlink target name.
 */
static void
isofile_connect_hardlink_files(struct iso9660 *iso9660)
{
	struct archive_rb_node *n;
	struct hardlink *hl;
	struct isofile *target, *nf;

	ARCHIVE_RB_TREE_FOREACH(n, &(iso9660->hardlink_rbtree)) {
		hl = (struct hardlink *)n;

		/* The first entry must be a hardlink target. */
		target = hl->file_list.first;
		archive_entry_set_nlink(target->entry, hl->nlink);
		/* Set a hardlink target to reference entries. */
		for (nf = target->hlnext;
		    nf != NULL; nf = nf->hlnext) {
			nf->hardlink_target = target;
			archive_entry_set_nlink(nf->entry, hl->nlink);
		}
	}
}

static int
isofile_hd_cmp_node(const struct archive_rb_node *n1,
    const struct archive_rb_node *n2)
{
	const struct hardlink *h1 = (const struct hardlink *)n1;
	const struct hardlink *h2 = (const struct hardlink *)n2;

	return (strcmp(archive_entry_pathname(h1->file_list.first->entry),
		       archive_entry_pathname(h2->file_list.first->entry)));
}

static int
isofile_hd_cmp_key(const struct archive_rb_node *n, const void *key)
{
	const struct hardlink *h = (const struct hardlink *)n;

	return (strcmp(archive_entry_pathname(h->file_list.first->entry),
		       (const char *)key));
}

static void
isofile_init_hardlinks(struct iso9660 *iso9660)
{
	static const struct archive_rb_tree_ops rb_ops = {
		isofile_hd_cmp_node, isofile_hd_cmp_key,
	};

	__archive_rb_tree_init(&(iso9660->hardlink_rbtree), &rb_ops);
}

static void
isofile_free_hardlinks(struct iso9660 *iso9660)
{
	struct archive_rb_node *n, *tmp;

	ARCHIVE_RB_TREE_FOREACH_SAFE(n, &(iso9660->hardlink_rbtree), tmp) {
		__archive_rb_tree_remove_node(&(iso9660->hardlink_rbtree), n);
		free(n);
	}
}

static struct isoent *
isoent_new(struct isofile *file)
{
	struct isoent *isoent;
	static const struct archive_rb_tree_ops rb_ops = {
		isoent_cmp_node, isoent_cmp_key,
	};

	isoent = calloc(1, sizeof(*isoent));
	if (isoent == NULL)
		return (NULL);
	isoent->file = file;
	isoent->children.first = NULL;
	isoent->children.last = &(isoent->children.first);
	__archive_rb_tree_init(&(isoent->rbtree), &rb_ops);
	isoent->subdirs.first = NULL;
	isoent->subdirs.last = &(isoent->subdirs.first);
	isoent->extr_rec_list.first = NULL;
	isoent->extr_rec_list.last = &(isoent->extr_rec_list.first);
	isoent->extr_rec_list.current = NULL;
	if (archive_entry_filetype(file->entry) == AE_IFDIR)
		isoent->dir = 1;

	return (isoent);
}

static inline struct isoent *
isoent_clone(struct isoent *src)
{
	return (isoent_new(src->file));
}

static void
_isoent_free(struct isoent *isoent)
{
	struct extr_rec *er, *er_next;

	free(isoent->children_sorted);
	free(isoent->identifier);
	er = isoent->extr_rec_list.first;
	while (er != NULL) {
		er_next = er->next;
		free(er);
		er = er_next;
	}
	free(isoent);
}

static void
isoent_free_all(struct isoent *isoent)
{
	struct isoent *np, *np_temp;

	if (isoent == NULL)
		return;
	np = isoent;
	for (;;) {
		if (np->dir) {
			if (np->children.first != NULL) {
				/* Enter to sub directories. */
				np = np->children.first;
				continue;
			}
		}
		for (;;) {
			np_temp = np;
			if (np->chnext == NULL) {
				/* Return to the parent directory. */
				np = np->parent;
				_isoent_free(np_temp);
				if (np == np_temp)
					return;
			} else {
				np = np->chnext;
				_isoent_free(np_temp);
				break;
			}
		}
	}
}

static struct isoent *
isoent_create_virtual_dir(struct archive_write *a, struct iso9660 *iso9660, const char *pathname)
{
	struct isofile *file;
	struct isoent *isoent;

	file = isofile_new(a, NULL);
	if (file == NULL)
		return (NULL);
	archive_entry_set_pathname(file->entry, pathname);
	archive_entry_unset_mtime(file->entry);
	archive_entry_unset_atime(file->entry);
	archive_entry_unset_ctime(file->entry);
	archive_entry_set_uid(file->entry, getuid());
	archive_entry_set_gid(file->entry, getgid());
	archive_entry_set_mode(file->entry, 0555 | AE_IFDIR);
	archive_entry_set_nlink(file->entry, 2);
	if (isofile_gen_utility_names(a, file) < ARCHIVE_WARN) {
		isofile_free(file);
		return (NULL);
	}
	isofile_add_entry(iso9660, file);

	isoent = isoent_new(file);
	if (isoent == NULL)
		return (NULL);
	isoent->dir = 1;
	isoent->virtual = 1;

	return (isoent);
}

static int
isoent_cmp_node(const struct archive_rb_node *n1,
    const struct archive_rb_node *n2)
{
	const struct isoent *e1 = (const struct isoent *)n1;
	const struct isoent *e2 = (const struct isoent *)n2;

	return (strcmp(e1->file->basename.s, e2->file->basename.s));
}

static int
isoent_cmp_key(const struct archive_rb_node *n, const void *key)
{
	const struct isoent *e = (const struct isoent *)n;

	return (strcmp(e->file->basename.s, (const char *)key));
}

static int
isoent_add_child_head(struct isoent *parent, struct isoent *child)
{

	if (!__archive_rb_tree_insert_node(
	    &(parent->rbtree), (struct archive_rb_node *)child))
		return (0);
	if ((child->chnext = parent->children.first) == NULL)
		parent->children.last = &(child->chnext);
	parent->children.first = child;
	parent->children.cnt++;
	child->parent = parent;

	/* Add a child to a sub-directory chain */
	if (child->dir) {
		if ((child->drnext = parent->subdirs.first) == NULL)
			parent->subdirs.last = &(child->drnext);
		parent->subdirs.first = child;
		parent->subdirs.cnt++;
		child->parent = parent;
	} else
		child->drnext = NULL;
	return (1);
}

static int
isoent_add_child_tail(struct isoent *parent, struct isoent *child)
{

	if (!__archive_rb_tree_insert_node(
	    &(parent->rbtree), (struct archive_rb_node *)child))
		return (0);
	child->chnext = NULL;
	*parent->children.last = child;
	parent->children.last = &(child->chnext);
	parent->children.cnt++;
	child->parent = parent;

	/* Add a child to a sub-directory chain */
	child->drnext = NULL;
	if (child->dir) {
		*parent->subdirs.last = child;
		parent->subdirs.last = &(child->drnext);
		parent->subdirs.cnt++;
		child->parent = parent;
	}
	return (1);
}

static void
isoent_remove_child(struct isoent *parent, struct isoent *child)
{
	struct isoent *ent;

	/* Remove a child entry from children chain. */
	ent = parent->children.first;
	while (ent->chnext != child)
		ent = ent->chnext;
	if ((ent->chnext = ent->chnext->chnext) == NULL)
		parent->children.last = &(ent->chnext);
	parent->children.cnt--;

	if (child->dir) {
		/* Remove a child entry from sub-directory chain. */
		ent = parent->subdirs.first;
		while (ent->drnext != child)
			ent = ent->drnext;
		if ((ent->drnext = ent->drnext->drnext) == NULL)
			parent->subdirs.last = &(ent->drnext);
		parent->subdirs.cnt--;
	}

	__archive_rb_tree_remove_node(&(parent->rbtree),
	    (struct archive_rb_node *)child);
}

static int
isoent_clone_tree(struct archive_write *a, struct isoent **nroot,
    struct isoent *root)
{
	struct isoent *np, *xroot, *newent;

	np = root;
	xroot = NULL;
	do {
		newent = isoent_clone(np);
		if (newent == NULL) {
			archive_set_error(&a->archive, ENOMEM,
			    "Can't allocate memory");
			return (ARCHIVE_FATAL);
		}
		if (xroot == NULL) {
			*nroot = xroot = newent;
			newent->parent = xroot;
		} else
			isoent_add_child_tail(xroot, newent);
		if (np->dir && np->children.first != NULL) {
			/* Enter to sub directories. */
			np = np->children.first;
			xroot = newent;
			continue;
		}
		while (np != np->parent) {
			if (np->chnext == NULL) {
				/* Return to the parent directory. */
				np = np->parent;
				xroot = xroot->parent;
			} else {
				np = np->chnext;
				break;
			}
		}
	} while (np != np->parent);

	return (ARCHIVE_OK);
}

/*
 * Setup directory locations.
 */
static void
isoent_setup_directory_location(struct iso9660 *iso9660, int location,
    struct vdd *vdd)
{
	struct isoent *np;
	int depth;

	vdd->total_dir_block = 0;
	depth = 0;
	np = vdd->rootent;
	do {
		int block;

		np->dir_block = calculate_directory_descriptors(
		    iso9660, vdd, np, depth);
		vdd->total_dir_block += np->dir_block;
		np->dir_location = location;
		location += np->dir_block;
		block = extra_setup_location(np, location);
		vdd->total_dir_block += block;
		location += block;

		if (np->subdirs.first != NULL && depth + 1 < vdd->max_depth) {
			/* Enter to sub directories. */
			np = np->subdirs.first;
			depth++;
			continue;
		}
		while (np != np->parent) {
			if (np->drnext == NULL) {
				/* Return to the parent directory. */
				np = np->parent;
				depth--;
			} else {
				np = np->drnext;
				break;
			}
		}
	} while (np != np->parent);
}

static void
_isoent_file_location(struct iso9660 *iso9660, struct isoent *isoent,
    int *symlocation)
{
	struct isoent **children;
	int n;

	if (isoent->children.cnt == 0)
		return;

	children = isoent->children_sorted;
	for (n = 0; n < isoent->children.cnt; n++) {
		struct isoent *np;
		struct isofile *file;

		np = children[n];
		if (np->dir)
			continue;
		if (np == iso9660->el_torito.boot)
			continue;
		file = np->file;
		if (file->boot || file->hardlink_target != NULL)
			continue;
		if (archive_entry_filetype(file->entry) == AE_IFLNK ||
		    file->content.size == 0) {
			/*
			 * Do not point a valid location.
			 * Make sure entry is not hardlink file.
			 */
			file->content.location = (*symlocation)--;
			continue;
		}

		file->write_content = 1;
	}
}

/*
 * Setup file locations.
 */
static void
isoent_setup_file_location(struct iso9660 *iso9660, int location)
{
	struct isoent *isoent;
	struct isoent *np;
	struct isofile *file;
	size_t size;
	int block;
	int depth;
	int joliet;
	int symlocation;
	int total_block;

	iso9660->total_file_block = 0;
	if ((isoent = iso9660->el_torito.catalog) != NULL) {
		isoent->file->content.location = location;
		block = (int)((archive_entry_size(isoent->file->entry) +
		    LOGICAL_BLOCK_SIZE -1) >> LOGICAL_BLOCK_BITS);
		location += block;
		iso9660->total_file_block += block;
	}
	if ((isoent = iso9660->el_torito.boot) != NULL) {
		isoent->file->content.location = location;
		size = fd_boot_image_size(iso9660->el_torito.media_type);
		if (size == 0)
			size = (size_t)archive_entry_size(isoent->file->entry);
		block = ((int)size + LOGICAL_BLOCK_SIZE -1)
		    >> LOGICAL_BLOCK_BITS;
		location += block;
		iso9660->total_file_block += block;
		isoent->file->content.blocks = block;
	}

	depth = 0;
	symlocation = -16;
	if (!iso9660->opt.rr && iso9660->opt.joliet) {
		joliet = 1;
		np = iso9660->joliet.rootent;
	} else {
		joliet = 0;
		np = iso9660->primary.rootent;
	}
	do {
		_isoent_file_location(iso9660, np, &symlocation);

		if (np->subdirs.first != NULL &&
		    (joliet ||
		    ((iso9660->opt.rr == OPT_RR_DISABLED &&
		      depth + 2 < iso9660->primary.max_depth) ||
		     (iso9660->opt.rr &&
		      depth + 1 < iso9660->primary.max_depth)))) {
			/* Enter to sub directories. */
			np = np->subdirs.first;
			depth++;
			continue;
		}
		while (np != np->parent) {
			if (np->drnext == NULL) {
				/* Return to the parent directory. */
				np = np->parent;
				depth--;
			} else {
				np = np->drnext;
				break;
			}
		}
	} while (np != np->parent);

	total_block = 0;
	for (file = iso9660->data_file_list.first;
	    file != NULL; file = file->datanext) {

		if (!file->write_content)
			continue;

		file->cur_content = &(file->content);
		do {
			file->cur_content->location = location;
			location += file->cur_content->blocks;
			total_block += file->cur_content->blocks;
			/* Next fragment */
			file->cur_content = file->cur_content->next;
		} while (file->cur_content != NULL);
	}
	iso9660->total_file_block += total_block;
}

static int
get_path_component(char *name, size_t n, const char *fn)
{
	char *p;
	size_t l;

	p = strchr(fn, '/');
	if (p == NULL) {
		if ((l = strlen(fn)) == 0)
			return (0);
	} else
		l = p - fn;
	if (l > n -1)
		return (-1);
	memcpy(name, fn, l);
	name[l] = '\0';

	return ((int)l);
}

/*
 * Add a new entry into the tree.
 */
static int
isoent_tree(struct archive_write *a, struct isoent **isoentpp)
{
#if defined(_WIN32) && !defined(__CYGWIN__)
	char name[_MAX_FNAME];/* Included null terminator size. */
#elif defined(NAME_MAX) && NAME_MAX >= 255
	char name[NAME_MAX+1];
#else
	char name[256];
#endif
	struct iso9660 *iso9660 = a->format_data;
	struct isoent *dent, *isoent, *np;
	struct isofile *f1, *f2;
	const char *fn, *p;
	int l;

	isoent = *isoentpp;
	dent = iso9660->primary.rootent;
	if (isoent->file->parentdir.length > 0)
		fn = p = isoent->file->parentdir.s;
	else
		fn = p = "";

	/*
	 * If the path of the parent directory of `isoent' entry is
	 * the same as the path of `cur_dirent', add isoent to
	 * `cur_dirent'.
	 */
	if (archive_strlen(&(iso9660->cur_dirstr))
	      == archive_strlen(&(isoent->file->parentdir)) &&
	    strcmp(iso9660->cur_dirstr.s, fn) == 0) {
		if (!isoent_add_child_tail(iso9660->cur_dirent, isoent)) {
			np = (struct isoent *)__archive_rb_tree_find_node(
			    &(iso9660->cur_dirent->rbtree),
			    isoent->file->basename.s);
			goto same_entry;
		}
		return (ARCHIVE_OK);
	}

	for (;;) {
		l = get_path_component(name, sizeof(name), fn);
		if (l == 0) {
			np = NULL;
			break;
		}
		if (l < 0) {
			archive_set_error(&a->archive,
			    ARCHIVE_ERRNO_MISC,
			    "A name buffer is too small");
			_isoent_free(isoent);
			return (ARCHIVE_FATAL);
		}

		np = isoent_find_child(dent, name);
		if (np == NULL || fn[0] == '\0')
			break;

		/* Find next subdirectory. */
		if (!np->dir) {
			/* NOT Directory! */
			archive_set_error(&a->archive,
			    ARCHIVE_ERRNO_MISC,
			    "`%s' is not directory, we cannot insert `%s' ",
			    archive_entry_pathname(np->file->entry),
			    archive_entry_pathname(isoent->file->entry));
			_isoent_free(isoent);
			*isoentpp = NULL;
			return (ARCHIVE_FAILED);
		}
		fn += l;
		if (fn[0] == '/')
			fn++;
		dent = np;
	}
	if (np == NULL) {
		/*
		 * Create virtual parent directories.
		 */
		while (fn[0] != '\0') {
			struct isoent *vp;
			struct archive_string as;

			archive_string_init(&as);
			archive_strncat(&as, p, fn - p + l);
			if (as.s[as.length-1] == '/') {
				as.s[as.length-1] = '\0';
				as.length--;
			}
			vp = isoent_create_virtual_dir(a, iso9660, as.s);
			if (vp == NULL) {
				archive_string_free(&as);
				archive_set_error(&a->archive, ENOMEM,
				    "Can't allocate memory");
				_isoent_free(isoent);
				*isoentpp = NULL;
				return (ARCHIVE_FATAL);
			}
			archive_string_free(&as);

			if (vp->file->dircnt > iso9660->dircnt_max)
				iso9660->dircnt_max = vp->file->dircnt;
			isoent_add_child_tail(dent, vp);
			np = vp;

			fn += l;
			if (fn[0] == '/')
				fn++;
			l = get_path_component(name, sizeof(name), fn);
			if (l < 0) {
				archive_string_free(&as);
				archive_set_error(&a->archive,
				    ARCHIVE_ERRNO_MISC,
				    "A name buffer is too small");
				_isoent_free(isoent);
				*isoentpp = NULL;
				return (ARCHIVE_FATAL);
			}
			dent = np;
		}

		/* Found out the parent directory where isoent can be
		 * inserted. */
		iso9660->cur_dirent = dent;
		archive_string_empty(&(iso9660->cur_dirstr));
		archive_string_ensure(&(iso9660->cur_dirstr),
		    archive_strlen(&(dent->file->parentdir)) +
		    archive_strlen(&(dent->file->basename)) + 2);
		if (archive_strlen(&(dent->file->parentdir)) +
		    archive_strlen(&(dent->file->basename)) == 0)
			iso9660->cur_dirstr.s[0] = 0;
		else {
			if (archive_strlen(&(dent->file->parentdir)) > 0) {
				archive_string_copy(&(iso9660->cur_dirstr),
				    &(dent->file->parentdir));
				archive_strappend_char(&(iso9660->cur_dirstr), '/');
			}
			archive_string_concat(&(iso9660->cur_dirstr),
			    &(dent->file->basename));
		}

		if (!isoent_add_child_tail(dent, isoent)) {
			np = (struct isoent *)__archive_rb_tree_find_node(
			    &(dent->rbtree), isoent->file->basename.s);
			goto same_entry;
		}
		return (ARCHIVE_OK);
	}

same_entry:
	/*
	 * We have already has the entry the filename of which is
	 * the same.
	 */
	f1 = np->file;
	f2 = isoent->file;

	/* If the file type of entries is different,
	 * we cannot handle it. */
	if (archive_entry_filetype(f1->entry) !=
	    archive_entry_filetype(f2->entry)) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
		    "Found duplicate entries `%s' and its file type is "
		    "different",
		    archive_entry_pathname(f1->entry));
		_isoent_free(isoent);
		*isoentpp = NULL;
		return (ARCHIVE_FAILED);
	}

	/* Swap file entries. */
	np->file = f2;
	isoent->file = f1;
	np->virtual = 0;

	_isoent_free(isoent);
	*isoentpp = np;
	return (ARCHIVE_OK);
}

/*
 * Find a entry from `isoent'
 */
static struct isoent *
isoent_find_child(struct isoent *isoent, const char *child_name)
{
	struct isoent *np;

	np = (struct isoent *)__archive_rb_tree_find_node(
	    &(isoent->rbtree), child_name);
	return (np);
}

/*
 * Find a entry full-path of which is specified by `fn' parameter,
 * in the tree.
 */
static struct isoent *
isoent_find_entry(struct isoent *rootent, const char *fn)
{
#if defined(_WIN32) && !defined(__CYGWIN__)
	char name[_MAX_FNAME];/* Included null terminator size. */
#elif defined(NAME_MAX) && NAME_MAX >= 255
	char name[NAME_MAX+1];
#else
	char name[256];
#endif
	struct isoent *isoent, *np;
	int l;

	isoent = rootent;
	np = NULL;
	for (;;) {
		l = get_path_component(name, sizeof(name), fn);
		if (l == 0)
			break;
		fn += l;
		if (fn[0] == '/')
			fn++;

		np = isoent_find_child(isoent, name);
		if (np == NULL)
			break;
		if (fn[0] == '\0')
			break;/* We found out the entry */

		/* Try sub directory. */
		isoent = np;
		np = NULL;
		if (!isoent->dir)
			break;/* Not directory */
	}

	return (np);
}

/*
 * Following idr_* functions are used for resolving duplicated filenames
 * and unreceivable filenames to generate ISO9660/Joliet Identifiers.
 */

static void
idr_relaxed_filenames(char *map)
{
	int i;

	for (i = 0x21; i <= 0x2F; i++)
		map[i] = 1;
	for (i = 0x3A; i <= 0x41; i++)
		map[i] = 1;
	for (i = 0x5B; i <= 0x5E; i++)
		map[i] = 1;
	map[0x60] = 1;
	for (i = 0x7B; i <= 0x7E; i++)
		map[i] = 1;
}

static void
idr_init(struct iso9660 *iso9660, struct vdd *vdd, struct idr *idr)
{

	idr->idrent_pool = NULL;
	idr->pool_size = 0;
	if (vdd->vdd_type != VDD_JOLIET) {
		if (iso9660->opt.iso_level <= 3) {
			memcpy(idr->char_map, d_characters_map,
			    sizeof(idr->char_map));
		} else {
			memcpy(idr->char_map, d1_characters_map,
			    sizeof(idr->char_map));
			idr_relaxed_filenames(idr->char_map);
		}
	}
}

static void
idr_cleanup(struct idr *idr)
{
	free(idr->idrent_pool);
}

static int
idr_ensure_poolsize(struct archive_write *a, struct idr *idr,
    int cnt)
{

	if (idr->pool_size < cnt) {
		void *p;
		const int bk = (1 << 7) - 1;
		int psize;

		psize = (cnt + bk) & ~bk;
		p = realloc(idr->idrent_pool, sizeof(struct idrent) * psize);
		if (p == NULL) {
			archive_set_error(&a->archive, ENOMEM,
			    "Can't allocate memory");
			return (ARCHIVE_FATAL);
		}
		idr->idrent_pool = (struct idrent *)p;
		idr->pool_size = psize;
	}
	return (ARCHIVE_OK);
}

static int
idr_start(struct archive_write *a, struct idr *idr, int cnt, int ffmax,
    int num_size, int null_size, const struct archive_rb_tree_ops *rbt_ops)
{
	int r;

	(void)ffmax; /* UNUSED */

	r = idr_ensure_poolsize(a, idr, cnt);
	if (r != ARCHIVE_OK)
		return (r);
	__archive_rb_tree_init(&(idr->rbtree), rbt_ops);
	idr->wait_list.first = NULL;
	idr->wait_list.last = &(idr->wait_list.first);
	idr->pool_idx = 0;
	idr->num_size = num_size;
	idr->null_size = null_size;
	return (ARCHIVE_OK);
}

static void
idr_register(struct idr *idr, struct isoent *isoent, int weight, int noff)
{
	struct idrent *idrent, *n;

	idrent = &(idr->idrent_pool[idr->pool_idx++]);
	idrent->wnext = idrent->avail = NULL;
	idrent->isoent = isoent;
	idrent->weight = weight;
	idrent->noff = noff;
	idrent->rename_num = 0;

	if (!__archive_rb_tree_insert_node(&(idr->rbtree), &(idrent->rbnode))) {
		n = (struct idrent *)__archive_rb_tree_find_node(
		    &(idr->rbtree), idrent->isoent);
		if (n != NULL) {
			/* this `idrent' needs to rename. */
			idrent->avail = n;
			*idr->wait_list.last = idrent;
			idr->wait_list.last = &(idrent->wnext);
		}
	}
}

static void
idr_extend_identifier(struct idrent *wnp, int numsize, int nullsize)
{
	unsigned char *p;
	int wnp_ext_off;

	wnp_ext_off = wnp->isoent->ext_off;
	if (wnp->noff + numsize != wnp_ext_off) {
		p = (unsigned char *)wnp->isoent->identifier;
		/* Extend the filename; foo.c --> foo___.c */
		memmove(p + wnp->noff + numsize, p + wnp_ext_off,
		    wnp->isoent->ext_len + nullsize);
		wnp->isoent->ext_off = wnp_ext_off = wnp->noff + numsize;
		wnp->isoent->id_len = wnp_ext_off + wnp->isoent->ext_len;
	}
}

static void
idr_resolve(struct idr *idr, void (*fsetnum)(unsigned char *p, int num))
{
	struct idrent *n;
	unsigned char *p;

	for (n = idr->wait_list.first; n != NULL; n = n->wnext) {
		idr_extend_identifier(n, idr->num_size, idr->null_size);
		p = (unsigned char *)n->isoent->identifier + n->noff;
		do {
			fsetnum(p, n->avail->rename_num++);
		} while (!__archive_rb_tree_insert_node(
		    &(idr->rbtree), &(n->rbnode)));
	}
}

static void
idr_set_num(unsigned char *p, int num)
{
	static const char xdig[] = {
		'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
		'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
		'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
		'U', 'V', 'W', 'X', 'Y', 'Z'
	};

	num %= sizeof(xdig) * sizeof(xdig) * sizeof(xdig);
	p[0] = xdig[(num / (sizeof(xdig) * sizeof(xdig)))];
	num %= sizeof(xdig) * sizeof(xdig);
	p[1] = xdig[ (num / sizeof(xdig))];
	num %= sizeof(xdig);
	p[2] = xdig[num];
}

static void
idr_set_num_beutf16(unsigned char *p, int num)
{
	static const uint16_t xdig[] = {
		0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035,
		0x0036, 0x0037, 0x0038, 0x0039,
		0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046,
		0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C,
		0x004D, 0x004E, 0x004F, 0x0050, 0x0051, 0x0052,
		0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
		0x0059, 0x005A
	};
#define XDIG_CNT	(sizeof(xdig)/sizeof(xdig[0]))

	num %= XDIG_CNT * XDIG_CNT * XDIG_CNT;
	archive_be16enc(p, xdig[(num / (XDIG_CNT * XDIG_CNT))]);
	num %= XDIG_CNT * XDIG_CNT;
	archive_be16enc(p+2, xdig[ (num / XDIG_CNT)]);
	num %= XDIG_CNT;
	archive_be16enc(p+4, xdig[num]);
}

/*
 * Generate ISO9660 Identifier.
 */
static int
isoent_gen_iso9660_identifier(struct archive_write *a, struct isoent *isoent,
    struct idr *idr)
{
	struct iso9660 *iso9660;
	struct isoent *np;
	char *p;
	int l, r;
	const char *char_map;
	char allow_ldots, allow_multidot, allow_period, allow_vernum;
	int fnmax, ffmax, dnmax;
	static const struct archive_rb_tree_ops rb_ops = {
		isoent_cmp_node_iso9660, isoent_cmp_key_iso9660
	};

	if (isoent->children.cnt == 0)
		return (0);

	iso9660 = a->format_data;
	char_map = idr->char_map;
	if (iso9660->opt.iso_level <= 3) {
		allow_ldots = 0;
		allow_multidot = 0;
		allow_period = 1;
		allow_vernum = iso9660->opt.allow_vernum;
		if (iso9660->opt.iso_level == 1) {
			fnmax = 8;
			ffmax = 12;/* fnmax + '.' + 3 */
			dnmax = 8;
		} else {
			fnmax = 30;
			ffmax = 31;
			dnmax = 31;
		}
	} else {
		allow_ldots = allow_multidot = 1;
		allow_period = allow_vernum = 0;
		if (iso9660->opt.rr)
			/*
			 * MDR : The maximum size of Directory Record(254).
			 * DRL : A Directory Record Length(33).
			 * CE  : A size of SUSP CE System Use Entry(28).
			 * MDR - DRL - CE = 254 - 33 - 28 = 193.
			 */
			fnmax = ffmax = dnmax = 193;
		else
			/*
			 * XA  : CD-ROM XA System Use Extension
			 *       Information(14).
			 * MDR - DRL - XA = 254 - 33 -14 = 207.
			 */
			fnmax = ffmax = dnmax = 207;
	}

	r = idr_start(a, idr, isoent->children.cnt, ffmax, 3, 1, &rb_ops);
	if (r < 0)
		return (r);

	for (np = isoent->children.first; np != NULL; np = np->chnext) {
		char *dot, *xdot;
		int ext_off, noff, weight;

		l = (int)np->file->basename.length;
		p = malloc(l+31+2+1);
		if (p == NULL) {
			archive_set_error(&a->archive, ENOMEM,
			    "Can't allocate memory");
			return (ARCHIVE_FATAL);
		}
		memcpy(p, np->file->basename.s, l);
		p[l] = '\0';
		np->identifier = p;

		dot = xdot = NULL;
		if (!allow_ldots) {
			/*
			 * If there is a '.' character at the first byte,
			 * it has to be replaced by '_' character.
			 */
			if (*p == '.')
				*p++ = '_';
		}
		for (;*p; p++) {
			if (*p & 0x80) {
				*p = '_';
				continue;
			}
			if (char_map[(unsigned char)*p]) {
				/* if iso-level is '4', a character '.' is
				 * allowed by char_map. */
				if (*p == '.') {
					xdot = dot;
					dot = p;
				}
				continue;
			}
			if (*p >= 'a' && *p <= 'z') {
				*p -= 'a' - 'A';
				continue;
			}
			if (*p == '.') {
				xdot = dot;
				dot = p;
				if (allow_multidot)
					continue;
			}
			*p = '_';
		}
		p = np->identifier;
		weight = -1;
		if (dot == NULL) {
			int nammax;

			if (np->dir)
				nammax = dnmax;
			else
				nammax = fnmax;

			if (l > nammax) {
				p[nammax] = '\0';
				weight = nammax;
				ext_off = nammax;
			} else
				ext_off = l;
		} else {
			*dot = '.';
			ext_off = (int)(dot - p);

			if (iso9660->opt.iso_level == 1) {
				if (dot - p <= 8) {
					if (strlen(dot) > 4) {
						/* A length of a file extension
						 * must be less than 4 */
						dot[4] = '\0';
						weight = 0;
					}
				} else {
					p[8] = dot[0];
					p[9] = dot[1];
					p[10] = dot[2];
					p[11] = dot[3];
					p[12] = '\0';
					weight = 8;
					ext_off = 8;
				}
			} else if (np->dir) {
				if (l > dnmax) {
					p[dnmax] = '\0';
					weight = dnmax;
					if (ext_off > dnmax)
						ext_off = dnmax;
				}
			} else if (l > ffmax) {
				int extlen = (int)strlen(dot);
				int xdoff;

				if (xdot != NULL)
					xdoff = (int)(xdot - p);
				else
					xdoff = 0;

				if (extlen > 1 && xdoff < fnmax-1) {
					int off;

					if (extlen > ffmax)
						extlen = ffmax;
					off = ffmax - extlen;
					if (off == 0) {
						/* A dot('.')  character
						 * doesn't place to the first
						 * byte of identifier. */
						off ++;
						extlen --;
					}
					memmove(p+off, dot, extlen);
					p[ffmax] = '\0';
					ext_off = off;
					weight = off;
#ifdef COMPAT_MKISOFS
				} else if (xdoff >= fnmax-1) {
					/* Simulate a bug(?) of mkisofs. */
					p[fnmax-1] = '\0';
					ext_off = fnmax-1;
					weight = fnmax-1;
#endif
				} else {
					p[fnmax] = '\0';
					ext_off = fnmax;
					weight = fnmax;
				}
			}
		}
		/* Save an offset of a file name extension to sort files. */
		np->ext_off = ext_off;
		np->ext_len = (int)strlen(&p[ext_off]);
		np->id_len = l = ext_off + np->ext_len;

		/* Make an offset of the number which is used to be set
		 * hexadecimal number to avoid duplicate identifier. */
		if (iso9660->opt.iso_level == 1) {
			if (ext_off >= 5)
				noff = 5;
			else
				noff = ext_off;
		} else {
			if (l == ffmax)
				noff = ext_off - 3;
			else if (l == ffmax-1)
				noff = ext_off - 2;
			else if (l == ffmax-2)
				noff = ext_off - 1;
			else
				noff = ext_off;
		}
		/* Register entry to the identifier resolver. */
		idr_register(idr, np, weight, noff);
	}

	/* Resolve duplicate identifier. */
	idr_resolve(idr, idr_set_num);

	/* Add a period and a version number to identifiers. */
	for (np = isoent->children.first; np != NULL; np = np->chnext) {
		if (!np->dir && np->rr_child == NULL) {
			p = np->identifier + np->ext_off + np->ext_len;
			if (np->ext_len == 0 && allow_period) {
				*p++ = '.';
				np->ext_len = 1;
			}
			if (np->ext_len == 1 && !allow_period) {
				*--p = '\0';
				np->ext_len = 0;
			}
			np->id_len = np->ext_off + np->ext_len;
			if (allow_vernum) {
				*p++ = ';';
				*p++ = '1';
				np->id_len += 2;
			}
			*p = '\0';
		} else
			np->id_len = np->ext_off + np->ext_len;
		np->mb_len = np->id_len;
	}
	return (ARCHIVE_OK);
}

/*
 * Generate Joliet Identifier.
 */
static int
isoent_gen_joliet_identifier(struct archive_write *a, struct isoent *isoent,
    struct idr *idr)
{
	struct iso9660 *iso9660;
	struct isoent *np;
	unsigned char *p;
	size_t l;
	int r;
	size_t ffmax, parent_len;
	static const struct archive_rb_tree_ops rb_ops = {
		isoent_cmp_node_joliet, isoent_cmp_key_joliet
	};

	if (isoent->children.cnt == 0)
		return (0);

	iso9660 = a->format_data;
	if (iso9660->opt.joliet == OPT_JOLIET_LONGNAME)
		ffmax = 206;
	else
		ffmax = 128;

	r = idr_start(a, idr, isoent->children.cnt, (int)ffmax, 6, 2, &rb_ops);
	if (r < 0)
		return (r);

	parent_len = 1;
	for (np = isoent; np->parent != np; np = np->parent)
		parent_len += np->mb_len + 1;

	for (np = isoent->children.first; np != NULL; np = np->chnext) {
		unsigned char *dot;
		int ext_off, noff, weight;
		size_t lt;

		if ((l = np->file->basename_utf16.length) > ffmax)
			l = ffmax;

		p = malloc((l+1)*2);
		if (p == NULL) {
			archive_set_error(&a->archive, ENOMEM,
			    "Can't allocate memory");
			return (ARCHIVE_FATAL);
		}
		memcpy(p, np->file->basename_utf16.s, l);
		p[l] = 0;
		p[l+1] = 0;

		np->identifier = (char *)p;
		lt = l;
		dot = p + l;
		weight = 0;
		while (lt > 0) {
			if (!joliet_allowed_char(p[0], p[1]))
				archive_be16enc(p, 0x005F); /* '_' */
			else if (p[0] == 0 && p[1] == 0x2E) /* '.' */
				dot = p;
			p += 2;
			lt -= 2;
		}
		ext_off = (int)(dot - (unsigned char *)np->identifier);
		np->ext_off = ext_off;
		np->ext_len = (int)l - ext_off;
		np->id_len = (int)l;

		/*
		 * Get a length of MBS of a full-pathname.
		 */
		if (np->file->basename_utf16.length > ffmax) {
			if (archive_strncpy_l(&iso9660->mbs,
			    (const char *)np->identifier, l,
				iso9660->sconv_from_utf16be) != 0 &&
			    errno == ENOMEM) {
				archive_set_error(&a->archive, errno,
				    "No memory");
				return (ARCHIVE_FATAL);
			}
			np->mb_len = (int)iso9660->mbs.length;
			if (np->mb_len != (int)np->file->basename.length)
				weight = np->mb_len;
		} else
			np->mb_len = (int)np->file->basename.length;

		/* If a length of full-pathname is longer than 240 bytes,
		 * it violates Joliet extensions regulation. */
		if (parent_len > 240
		    || np->mb_len > 240
		    || parent_len + np->mb_len > 240) {
			archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
			    "The regulation of Joliet extensions;"
			    " A length of a full-pathname of `%s' is "
			    "longer than 240 bytes, (p=%d, b=%d)",
			    archive_entry_pathname(np->file->entry),
			    (int)parent_len, (int)np->mb_len);
			return (ARCHIVE_FATAL);
		}

		/* Make an offset of the number which is used to be set
		 * hexadecimal number to avoid duplicate identifier. */
		if (l == ffmax)
			noff = ext_off - 6;
		else if (l == ffmax-2)
			noff = ext_off - 4;
		else if (l == ffmax-4)
			noff = ext_off - 2;
		else
			noff = ext_off;
		/* Register entry to the identifier resolver. */
		idr_register(idr, np, weight, noff);
	}

	/* Resolve duplicate identifier with Joliet Volume. */
	idr_resolve(idr, idr_set_num_beutf16);

	return (ARCHIVE_OK);
}

/*
 * This comparing rule is according to ISO9660 Standard 9.3
 */
static int
isoent_cmp_iso9660_identifier(const struct isoent *p1, const struct isoent *p2)
{
	const char *s1, *s2;
	int cmp;
	int l;

	s1 = p1->identifier;
	s2 = p2->identifier;

	/* Compare File Name */
	l = p1->ext_off;
	if (l > p2->ext_off)
		l = p2->ext_off;
	cmp = memcmp(s1, s2, l);
	if (cmp != 0)
		return (cmp);
	if (p1->ext_off < p2->ext_off) {
		s2 += l;
		l = p2->ext_off - p1->ext_off;
		while (l--)
			if (0x20 != *s2++)
				return (0x20
				    - *(const unsigned char *)(s2 - 1));
	} else if (p1->ext_off > p2->ext_off) {
		s1 += l;
		l = p1->ext_off - p2->ext_off;
		while (l--)
			if (0x20 != *s1++)
				return (*(const unsigned char *)(s1 - 1)
				    - 0x20);
	}
	/* Compare File Name Extension */
	if (p1->ext_len == 0 && p2->ext_len == 0)
		return (0);
	if (p1->ext_len == 1 && p2->ext_len == 1)
		return (0);
	if (p1->ext_len <= 1)
		return (-1);
	if (p2->ext_len <= 1)
		return (1);
	l = p1->ext_len;
	if (l > p2->ext_len)
		l = p2->ext_len;
	s1 = p1->identifier + p1->ext_off;
	s2 = p2->identifier + p2->ext_off;
	if (l > 1) {
		cmp = memcmp(s1, s2, l);
		if (cmp != 0)
			return (cmp);
	}
	if (p1->ext_len < p2->ext_len) {
		s2 += l;
		l = p2->ext_len - p1->ext_len;
		while (l--)
			if (0x20 != *s2++)
				return (0x20
				    - *(const unsigned char *)(s2 - 1));
	} else if (p1->ext_len > p2->ext_len) {
		s1 += l;
		l = p1->ext_len - p2->ext_len;
		while (l--)
			if (0x20 != *s1++)
				return (*(const unsigned char *)(s1 - 1)
				    - 0x20);
	}
	/* Compare File Version Number */
	/* No operation. The File Version Number is always one. */

	return (cmp);
}

static int
isoent_cmp_node_iso9660(const struct archive_rb_node *n1,
    const struct archive_rb_node *n2)
{
	const struct idrent *e1 = (const struct idrent *)n1;
	const struct idrent *e2 = (const struct idrent *)n2;

	return (isoent_cmp_iso9660_identifier(e2->isoent, e1->isoent));
}

static int
isoent_cmp_key_iso9660(const struct archive_rb_node *node, const void *key)
{
	const struct isoent *isoent = (const struct isoent *)key;
	const struct idrent *idrent = (const struct idrent *)node;

	return (isoent_cmp_iso9660_identifier(isoent, idrent->isoent));
}

static int
isoent_cmp_joliet_identifier(const struct isoent *p1, const struct isoent *p2)
{
	const unsigned char *s1, *s2;
	int cmp;
	int l;

	s1 = (const unsigned char *)p1->identifier;
	s2 = (const unsigned char *)p2->identifier;

	/* Compare File Name */
	l = p1->ext_off;
	if (l > p2->ext_off)
		l = p2->ext_off;
	cmp = memcmp(s1, s2, l);
	if (cmp != 0)
		return (cmp);
	if (p1->ext_off < p2->ext_off) {
		s2 += l;
		l = p2->ext_off - p1->ext_off;
		while (l--)
			if (0 != *s2++)
				return (- *(const unsigned char *)(s2 - 1));
	} else if (p1->ext_off > p2->ext_off) {
		s1 += l;
		l = p1->ext_off - p2->ext_off;
		while (l--)
			if (0 != *s1++)
				return (*(const unsigned char *)(s1 - 1));
	}
	/* Compare File Name Extension */
	if (p1->ext_len == 0 && p2->ext_len == 0)
		return (0);
	if (p1->ext_len == 2 && p2->ext_len == 2)
		return (0);
	if (p1->ext_len <= 2)
		return (-1);
	if (p2->ext_len <= 2)
		return (1);
	l = p1->ext_len;
	if (l > p2->ext_len)
		l = p2->ext_len;
	s1 = (unsigned char *)(p1->identifier + p1->ext_off);
	s2 = (unsigned char *)(p2->identifier + p2->ext_off);
	if (l > 1) {
		cmp = memcmp(s1, s2, l);
		if (cmp != 0)
			return (cmp);
	}
	if (p1->ext_len < p2->ext_len) {
		s2 += l;
		l = p2->ext_len - p1->ext_len;
		while (l--)
			if (0 != *s2++)
				return (- *(const unsigned char *)(s2 - 1));
	} else if (p1->ext_len > p2->ext_len) {
		s1 += l;
		l = p1->ext_len - p2->ext_len;
		while (l--)
			if (0 != *s1++)
				return (*(const unsigned char *)(s1 - 1));
	}
	/* Compare File Version Number */
	/* No operation. The File Version Number is always one. */

	return (cmp);
}

static int
isoent_cmp_node_joliet(const struct archive_rb_node *n1,
    const struct archive_rb_node *n2)
{
	const struct idrent *e1 = (const struct idrent *)n1;
	const struct idrent *e2 = (const struct idrent *)n2;

	return (isoent_cmp_joliet_identifier(e2->isoent, e1->isoent));
}

static int
isoent_cmp_key_joliet(const struct archive_rb_node *node, const void *key)
{
	const struct isoent *isoent = (const struct isoent *)key;
	const struct idrent *idrent = (const struct idrent *)node;

	return (isoent_cmp_joliet_identifier(isoent, idrent->isoent));
}

static int
isoent_make_sorted_files(struct archive_write *a, struct isoent *isoent,
    struct idr *idr)
{
	struct archive_rb_node *rn;
	struct isoent **children;

	children = malloc(isoent->children.cnt * sizeof(struct isoent *));
	if (children == NULL) {
		archive_set_error(&a->archive, ENOMEM,
		    "Can't allocate memory");
		return (ARCHIVE_FATAL);
	}
	isoent->children_sorted = children;

	ARCHIVE_RB_TREE_FOREACH(rn, &(idr->rbtree)) {
		struct idrent *idrent = (struct idrent *)rn;
		*children ++ = idrent->isoent;
	}
	return (ARCHIVE_OK);
}

/*
 * - Generate ISO9660 and Joliet identifiers from basenames.
 * - Sort files by each directory.
 */
static int
isoent_traverse_tree(struct archive_write *a, struct vdd* vdd)
{
	struct iso9660 *iso9660 = a->format_data;
	struct isoent *np;
	struct idr idr;
	int depth;
	int r;
	int (*genid)(struct archive_write *, struct isoent *, struct idr *);

	idr_init(iso9660, vdd, &idr);
	np = vdd->rootent;
	depth = 0;
	if (vdd->vdd_type == VDD_JOLIET)
		genid = isoent_gen_joliet_identifier;
	else
		genid = isoent_gen_iso9660_identifier;
	do {
		if (np->virtual &&
		    !archive_entry_mtime_is_set(np->file->entry)) {
			/* Set properly times to virtual directory */
			archive_entry_set_mtime(np->file->entry,
			    iso9660->birth_time, 0);
			archive_entry_set_atime(np->file->entry,
			    iso9660->birth_time, 0);
			archive_entry_set_ctime(np->file->entry,
			    iso9660->birth_time, 0);
		}
		if (np->children.first != NULL) {
			if (vdd->vdd_type != VDD_JOLIET &&
			    !iso9660->opt.rr && depth + 1 >= vdd->max_depth) {
				if (np->children.cnt > 0)
					iso9660->directories_too_deep = np;
			} else {
				/* Generate Identifier */
				r = genid(a, np, &idr);
				if (r < 0)
					goto exit_traverse_tree;
				r = isoent_make_sorted_files(a, np, &idr);
				if (r < 0)
					goto exit_traverse_tree;

				if (np->subdirs.first != NULL &&
				    depth + 1 < vdd->max_depth) {
					/* Enter to sub directories. */
					np = np->subdirs.first;
					depth++;
					continue;
				}
			}
		}
		while (np != np->parent) {
			if (np->drnext == NULL) {
				/* Return to the parent directory. */
				np = np->parent;
				depth--;
			} else {
				np = np->drnext;
				break;
			}
		}
	} while (np != np->parent);

	r = ARCHIVE_OK;
exit_traverse_tree:
	idr_cleanup(&idr);

	return (r);
}

/*
 * Collect directory entries into path_table by a directory depth.
 */
static int
isoent_collect_dirs(struct vdd *vdd, struct isoent *rootent, int depth)
{
	struct isoent *np;

	if (rootent == NULL)
		rootent = vdd->rootent;
	np = rootent;
	do {
		/* Register current directory to pathtable. */
		path_table_add_entry(&(vdd->pathtbl[depth]), np);

		if (np->subdirs.first != NULL && depth + 1 < vdd->max_depth) {
			/* Enter to sub directories. */
			np = np->subdirs.first;
			depth++;
			continue;
		}
		while (np != rootent) {
			if (np->drnext == NULL) {
				/* Return to the parent directory. */
				np = np->parent;
				depth--;
			} else {
				np = np->drnext;
				break;
			}
		}
	} while (np != rootent);

	return (ARCHIVE_OK);
}

/*
 * The entry whose number of levels in a directory hierarchy is
 * large than eight relocate to rr_move directory.
 */
static int
isoent_rr_move_dir(struct archive_write *a, struct isoent **rr_moved,
    struct isoent *curent, struct isoent **newent)
{
	struct iso9660 *iso9660 = a->format_data;
	struct isoent *rrmoved, *mvent, *np;

	if ((rrmoved = *rr_moved) == NULL) {
		struct isoent *rootent = iso9660->primary.rootent;
		/* There isn't rr_move entry.
		 * Create rr_move entry and insert it into the root entry.
		 */
		rrmoved = isoent_create_virtual_dir(a, iso9660, "rr_moved");
		if (rrmoved == NULL) {
			archive_set_error(&a->archive, ENOMEM,
			    "Can't allocate memory");
			return (ARCHIVE_FATAL);
		}
		/* Add "rr_moved" entry to the root entry. */
		isoent_add_child_head(rootent, rrmoved);
		archive_entry_set_nlink(rootent->file->entry,
		    archive_entry_nlink(rootent->file->entry) + 1);
		/* Register "rr_moved" entry to second level pathtable. */
		path_table_add_entry(&(iso9660->primary.pathtbl[1]), rrmoved);
		/* Save rr_moved. */
		*rr_moved = rrmoved;
	}
	/*
	 * Make a clone of curent which is going to be relocated
	 * to rr_moved.
	 */
	mvent = isoent_clone(curent);
	if (mvent == NULL) {
		archive_set_error(&a->archive, ENOMEM,
		    "Can't allocate memory");
		return (ARCHIVE_FATAL);
	}
	/* linking..  and use for creating "CL", "PL" and "RE" */
	mvent->rr_parent = curent->parent;
	curent->rr_child = mvent;
	/*
	 * Move subdirectories from the curent to mvent
	 */
	if (curent->children.first != NULL) {
		*mvent->children.last = curent->children.first;
		mvent->children.last = curent->children.last;
	}
	for (np = mvent->children.first; np != NULL; np = np->chnext)
		np->parent = mvent;
	mvent->children.cnt = curent->children.cnt;
	curent->children.cnt = 0;
	curent->children.first = NULL;
	curent->children.last = &curent->children.first;

	if (curent->subdirs.first != NULL) {
		*mvent->subdirs.last = curent->subdirs.first;
		mvent->subdirs.last = curent->subdirs.last;
	}
	mvent->subdirs.cnt = curent->subdirs.cnt;
	curent->subdirs.cnt = 0;
	curent->subdirs.first = NULL;
	curent->subdirs.last = &curent->subdirs.first;

	/*
	 * The mvent becomes a child of the rr_moved entry.
	 */
	isoent_add_child_tail(rrmoved, mvent);
	archive_entry_set_nlink(rrmoved->file->entry,
	    archive_entry_nlink(rrmoved->file->entry) + 1);
	/*
	 * This entry which relocated to the rr_moved directory
	 * has to set the flag as a file.
	 * See also RRIP 4.1.5.1 Description of the "CL" System Use Entry.
	 */
	curent->dir = 0;

	*newent = mvent;

	return (ARCHIVE_OK);
}

static int
isoent_rr_move(struct archive_write *a)
{
	struct iso9660 *iso9660 = a->format_data;
	struct path_table *pt;
	struct isoent *rootent, *rr_moved;
	struct isoent *np, *last;
	int r;

	pt = &(iso9660->primary.pathtbl[MAX_DEPTH-1]);
	/* There aren't level 8 directories reaching a deeper level. */
	if (pt->cnt == 0)
		return (ARCHIVE_OK);

	rootent = iso9660->primary.rootent;
	/* If "rr_moved" directory is already existing,
	 * we have to use it. */
	rr_moved = isoent_find_child(rootent, "rr_moved");
	if (rr_moved != NULL &&
	    rr_moved != rootent->children.first) {
		/*
		 * It's necessary that rr_move is the first entry
		 * of the root.
		 */
		/* Remove "rr_moved" entry from children chain. */
		isoent_remove_child(rootent, rr_moved);

		/* Add "rr_moved" entry into the head of children chain. */
		isoent_add_child_head(rootent, rr_moved);
	}

	/*
	 * Check level 8 path_table.
	 * If find out sub directory entries, that entries move to rr_move.
	 */
	np = pt->first;
	while (np != NULL) {
		last = path_table_last_entry(pt);
		for (; np != NULL; np = np->ptnext) {
			struct isoent *mvent;
			struct isoent *newent;

			if (!np->dir)
				continue;
			for (mvent = np->subdirs.first;
			    mvent != NULL; mvent = mvent->drnext) {
				r = isoent_rr_move_dir(a, &rr_moved,
				    mvent, &newent);
				if (r < 0)
					return (r);
				isoent_collect_dirs(&(iso9660->primary),
				    newent, 2);
			}
		}
		/* If new entries are added to level 8 path_talbe,
		 * its sub directory entries move to rr_move too.
		 */
		np = last->ptnext;
	}

	return (ARCHIVE_OK);
}

/*
 * This comparing rule is according to ISO9660 Standard 6.9.1
 */
static int
__LA_LIBC_CC
_compare_path_table(const void *v1, const void *v2)
{
	const struct isoent *p1, *p2;
	const char *s1, *s2;
	int cmp, l;

	p1 = *((const struct isoent **)(uintptr_t)v1);
	p2 = *((const struct isoent **)(uintptr_t)v2);

	/* Compare parent directory number */
	cmp = p1->parent->dir_number - p2->parent->dir_number;
	if (cmp != 0)
		return (cmp);

	/* Compare identifier */
	s1 = p1->identifier;
	s2 = p2->identifier;
	l = p1->ext_off;
	if (l > p2->ext_off)
		l = p2->ext_off;
	cmp = strncmp(s1, s2, l);
	if (cmp != 0)
		return (cmp);
	if (p1->ext_off < p2->ext_off) {
		s2 += l;
		l = p2->ext_off - p1->ext_off;
		while (l--)
			if (0x20 != *s2++)
				return (0x20
				    - *(const unsigned char *)(s2 - 1));
	} else if (p1->ext_off > p2->ext_off) {
		s1 += l;
		l = p1->ext_off - p2->ext_off;
		while (l--)
			if (0x20 != *s1++)
				return (*(const unsigned char *)(s1 - 1)
				    - 0x20);
	}
	return (0);
}

static int
__LA_LIBC_CC
_compare_path_table_joliet(const void *v1, const void *v2)
{
	const struct isoent *p1, *p2;
	const unsigned char *s1, *s2;
	int cmp, l;

	p1 = *((const struct isoent **)(uintptr_t)v1);
	p2 = *((const struct isoent **)(uintptr_t)v2);

	/* Compare parent directory number */
	cmp = p1->parent->dir_number - p2->parent->dir_number;
	if (cmp != 0)
		return (cmp);

	/* Compare identifier */
	s1 = (const unsigned char *)p1->identifier;
	s2 = (const unsigned char *)p2->identifier;
	l = p1->ext_off;
	if (l > p2->ext_off)
		l = p2->ext_off;
	cmp = memcmp(s1, s2, l);
	if (cmp != 0)
		return (cmp);
	if (p1->ext_off < p2->ext_off) {
		s2 += l;
		l = p2->ext_off - p1->ext_off;
		while (l--)
			if (0 != *s2++)
				return (- *(const unsigned char *)(s2 - 1));
	} else if (p1->ext_off > p2->ext_off) {
		s1 += l;
		l = p1->ext_off - p2->ext_off;
		while (l--)
			if (0 != *s1++)
				return (*(const unsigned char *)(s1 - 1));
	}
	return (0);
}

static inline void
path_table_add_entry(struct path_table *pathtbl, struct isoent *ent)
{
	ent->ptnext = NULL;
	*pathtbl->last = ent;
	pathtbl->last = &(ent->ptnext);
	pathtbl->cnt ++;
}

static inline struct isoent *
path_table_last_entry(struct path_table *pathtbl)
{
	if (pathtbl->first == NULL)
		return (NULL);
	return (((struct isoent *)(void *)
		((char *)(pathtbl->last) - offsetof(struct isoent, ptnext))));
}

/*
 * Sort directory entries in path_table
 * and assign directory number to each entries.
 */
static int
isoent_make_path_table_2(struct archive_write *a, struct vdd *vdd,
    int depth, int *dir_number)
{
	struct isoent *np;
	struct isoent **enttbl;
	struct path_table *pt;
	int i;

	pt = &vdd->pathtbl[depth];
	if (pt->cnt == 0) {
		pt->sorted = NULL;
		return (ARCHIVE_OK);
	}
	enttbl = malloc(pt->cnt * sizeof(struct isoent *));
	if (enttbl == NULL) {
		archive_set_error(&a->archive, ENOMEM,
		    "Can't allocate memory");
		return (ARCHIVE_FATAL);
	}
	pt->sorted = enttbl;
	for (np = pt->first; np != NULL; np = np->ptnext)
		*enttbl ++ = np;
	enttbl = pt->sorted;

	switch (vdd->vdd_type) {
	case VDD_PRIMARY:
	case VDD_ENHANCED:
#ifdef __COMPAR_FN_T
		qsort(enttbl, pt->cnt, sizeof(struct isoent *),
		    (__compar_fn_t)_compare_path_table);
#else
		qsort(enttbl, pt->cnt, sizeof(struct isoent *),
		    _compare_path_table);
#endif
		break;
	case VDD_JOLIET:
#ifdef __COMPAR_FN_T
		qsort(enttbl, pt->cnt, sizeof(struct isoent *),
		    (__compar_fn_t)_compare_path_table_joliet);
#else
		qsort(enttbl, pt->cnt, sizeof(struct isoent *),
		    _compare_path_table_joliet);
#endif
		break;
	}
	for (i = 0; i < pt->cnt; i++)
		enttbl[i]->dir_number = (*dir_number)++;

	return (ARCHIVE_OK);
}

static int
isoent_alloc_path_table(struct archive_write *a, struct vdd *vdd,
    int max_depth)
{
	int i;

	vdd->max_depth = max_depth;
	vdd->pathtbl = malloc(sizeof(*vdd->pathtbl) * vdd->max_depth);
	if (vdd->pathtbl == NULL) {
		archive_set_error(&a->archive, ENOMEM,
		    "Can't allocate memory");
		return (ARCHIVE_FATAL);
	}
	for (i = 0; i < vdd->max_depth; i++) {
		vdd->pathtbl[i].first = NULL;
		vdd->pathtbl[i].last = &(vdd->pathtbl[i].first);
		vdd->pathtbl[i].sorted = NULL;
		vdd->pathtbl[i].cnt = 0;
	}
	return (ARCHIVE_OK);
}

/*
 * Make Path Tables
 */
static int
isoent_make_path_table(struct archive_write *a)
{
	struct iso9660 *iso9660 = a->format_data;
	int depth, r;
	int dir_number;

	/*
	 * Init Path Table.
	 */
	if (iso9660->dircnt_max >= MAX_DEPTH &&
	    (!iso9660->opt.limit_depth || iso9660->opt.iso_level == 4))
		r = isoent_alloc_path_table(a, &(iso9660->primary),
		    iso9660->dircnt_max + 1);
	else
		/* The number of levels in the hierarchy cannot exceed
		 * eight. */
		r = isoent_alloc_path_table(a, &(iso9660->primary),
		    MAX_DEPTH);
	if (r < 0)
		return (r);
	if (iso9660->opt.joliet) {
		r = isoent_alloc_path_table(a, &(iso9660->joliet),
		    iso9660->dircnt_max + 1);
		if (r < 0)
			return (r);
	}

	/* Step 0.
	 * - Collect directories for primary and joliet.
	 */
	isoent_collect_dirs(&(iso9660->primary), NULL, 0);
	if (iso9660->opt.joliet)
		isoent_collect_dirs(&(iso9660->joliet), NULL, 0);
	/*
	 * Rockridge; move deeper depth directories to rr_moved.
	 */
	if (iso9660->opt.rr) {
		r = isoent_rr_move(a);
		if (r < 0)
			return (r);
	}

 	/* Update nlink. */
	isofile_connect_hardlink_files(iso9660);

	/* Step 1.
	 * - Renew a value of the depth of that directories.
	 * - Resolve hardlinks.
 	 * - Convert pathnames to ISO9660 name or UCS2(joliet).
	 * - Sort files by each directory.
	 */
	r = isoent_traverse_tree(a, &(iso9660->primary));
	if (r < 0)
		return (r);
	if (iso9660->opt.joliet) {
		r = isoent_traverse_tree(a, &(iso9660->joliet));
		if (r < 0)
			return (r);
	}

	/* Step 2.
	 * - Sort directories.
	 * - Assign all directory number.
	 */
	dir_number = 1;
	for (depth = 0; depth < iso9660->primary.max_depth; depth++) {
		r = isoent_make_path_table_2(a, &(iso9660->primary),
		    depth, &dir_number);
		if (r < 0)
			return (r);
	}
	if (iso9660->opt.joliet) {
		dir_number = 1;
		for (depth = 0; depth < iso9660->joliet.max_depth; depth++) {
			r = isoent_make_path_table_2(a, &(iso9660->joliet),
			    depth, &dir_number);
			if (r < 0)
				return (r);
		}
	}
	if (iso9660->opt.limit_dirs && dir_number > 0xffff) {
		/*
		 * Maximum number of directories is 65535(0xffff)
		 * doe to size(16bit) of Parent Directory Number of
		 * the Path Table.
		 * See also ISO9660 Standard 9.4.
		 */
		archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
		    "Too many directories(%d) over 65535.", dir_number);
		return (ARCHIVE_FATAL);
	}

	/* Get the size of the Path Table. */
	calculate_path_table_size(&(iso9660->primary));
	if (iso9660->opt.joliet)
		calculate_path_table_size(&(iso9660->joliet));

	return (ARCHIVE_OK);
}

static int
isoent_find_out_boot_file(struct archive_write *a, struct isoent *rootent)
{
	struct iso9660 *iso9660 = a->format_data;

	/* Find a isoent of the boot file. */
	iso9660->el_torito.boot = isoent_find_entry(rootent,
	    iso9660->el_torito.boot_filename.s);
	if (iso9660->el_torito.boot == NULL) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
		    "Can't find the boot image file ``%s''",
		    iso9660->el_torito.boot_filename.s);
		return (ARCHIVE_FATAL);
	}
	iso9660->el_torito.boot->file->boot = BOOT_IMAGE;
	return (ARCHIVE_OK);
}

static int
isoent_create_boot_catalog(struct archive_write *a, struct isoent *rootent)
{
	struct iso9660 *iso9660 = a->format_data;
	struct isofile *file;
	struct isoent *isoent;
	struct archive_entry *entry;

	(void)rootent; /* UNUSED */
	/*
	 * Create the entry which is the "boot.catalog" file.
	 */
	file = isofile_new(a, NULL);
	if (file == NULL) {
		archive_set_error(&a->archive, ENOMEM,
		    "Can't allocate memory");
		return (ARCHIVE_FATAL);
	}
	archive_entry_set_pathname(file->entry,
	    iso9660->el_torito.catalog_filename.s);
	archive_entry_set_size(file->entry, LOGICAL_BLOCK_SIZE);
	archive_entry_set_mtime(file->entry, iso9660->birth_time, 0);
	archive_entry_set_atime(file->entry, iso9660->birth_time, 0);
	archive_entry_set_ctime(file->entry, iso9660->birth_time, 0);
	archive_entry_set_uid(file->entry, getuid());
	archive_entry_set_gid(file->entry, getgid());
	archive_entry_set_mode(file->entry, AE_IFREG | 0444);
	archive_entry_set_nlink(file->entry, 1);

	if (isofile_gen_utility_names(a, file) < ARCHIVE_WARN) {
		isofile_free(file);
		return (ARCHIVE_FATAL);
	}
	file->boot = BOOT_CATALOG;
	file->content.size = LOGICAL_BLOCK_SIZE;
	isofile_add_entry(iso9660, file);

	isoent = isoent_new(file);
	if (isoent == NULL) {
		archive_set_error(&a->archive, ENOMEM,
		    "Can't allocate memory");
		return (ARCHIVE_FATAL);
	}
	isoent->virtual = 1;

	/* Add the "boot.catalog" entry into tree */
	if (isoent_tree(a, &isoent) != ARCHIVE_OK)
		return (ARCHIVE_FATAL);

	iso9660->el_torito.catalog = isoent;
	/*
	 * Get a boot media type.
	 */
	switch (iso9660->opt.boot_type) {
	default:
	case OPT_BOOT_TYPE_AUTO:
		/* Try detecting a media type of the boot image. */
		entry = iso9660->el_torito.boot->file->entry;
		if (archive_entry_size(entry) == FD_1_2M_SIZE)
			iso9660->el_torito.media_type =
			    BOOT_MEDIA_1_2M_DISKETTE;
		else if (archive_entry_size(entry) == FD_1_44M_SIZE)
			iso9660->el_torito.media_type =
			    BOOT_MEDIA_1_44M_DISKETTE;
		else if (archive_entry_size(entry) == FD_2_88M_SIZE)
			iso9660->el_torito.media_type =
			    BOOT_MEDIA_2_88M_DISKETTE;
		else
			/* We cannot decide whether the boot image is
			 * hard-disk. */
			iso9660->el_torito.media_type =
			    BOOT_MEDIA_NO_EMULATION;
		break;
	case OPT_BOOT_TYPE_NO_EMU:
		iso9660->el_torito.media_type = BOOT_MEDIA_NO_EMULATION;
		break;
	case OPT_BOOT_TYPE_HARD_DISK:
		iso9660->el_torito.media_type = BOOT_MEDIA_HARD_DISK;
		break;
	case OPT_BOOT_TYPE_FD:
		entry = iso9660->el_torito.boot->file->entry;
		if (archive_entry_size(entry) <= FD_1_2M_SIZE)
			iso9660->el_torito.media_type =
			    BOOT_MEDIA_1_2M_DISKETTE;
		else if (archive_entry_size(entry) <= FD_1_44M_SIZE)
			iso9660->el_torito.media_type =
			    BOOT_MEDIA_1_44M_DISKETTE;
		else if (archive_entry_size(entry) <= FD_2_88M_SIZE)
			iso9660->el_torito.media_type =
			    BOOT_MEDIA_2_88M_DISKETTE;
		else {
			archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
			    "Boot image file(``%s'') size is too big "
			    "for fd type.",
			    iso9660->el_torito.boot_filename.s);
			return (ARCHIVE_FATAL);
		}
		break;
	}

	/*
	 * Get a system type.
	 * TODO: `El Torito' specification says "A copy of byte 5 from the
	 *       Partition Table found in the boot image".
	 */
	iso9660->el_torito.system_type = 0;

	/*
	 * Get an ID.
	 */
	if (iso9660->opt.publisher)
		archive_string_copy(&(iso9660->el_torito.id),
		    &(iso9660->publisher_identifier));


	return (ARCHIVE_OK);
}

/*
 * If a media type is floppy, return its image size.
 * otherwise return 0.
 */
static size_t
fd_boot_image_size(int media_type)
{
	switch (media_type) {
	case BOOT_MEDIA_1_2M_DISKETTE:
		return (FD_1_2M_SIZE);
	case BOOT_MEDIA_1_44M_DISKETTE:
		return (FD_1_44M_SIZE);
	case BOOT_MEDIA_2_88M_DISKETTE:
		return (FD_2_88M_SIZE);
	default:
		return (0);
	}
}

/*
 * Make a boot catalog image data.
 */
static int
make_boot_catalog(struct archive_write *a)
{
	struct iso9660 *iso9660 = a->format_data;
	unsigned char *block;
	unsigned char *p;
	uint16_t sum, *wp;

	block = wb_buffptr(a);
	memset(block, 0, LOGICAL_BLOCK_SIZE);
	p = block;
	/*
	 * Validation Entry
	 */
	/* Header ID */
	p[0] = 1;
	/* Platform ID */
	p[1] = iso9660->el_torito.platform_id;
	/* Reserved */
	p[2] = p[3] = 0;
	/* ID */
	if (archive_strlen(&(iso9660->el_torito.id)) > 0)
		strncpy((char *)p+4, iso9660->el_torito.id.s, 23);
	p[27] = 0;
	/* Checksum */
	p[28] = p[29] = 0;
	/* Key */
	p[30] = 0x55;
	p[31] = 0xAA;

	sum = 0;
	wp = (uint16_t *)block;
	while (wp < (uint16_t *)&block[32])
		sum += archive_le16dec(wp++);
	set_num_721(&block[28], (~sum) + 1);

	/*
	 * Initial/Default Entry
	 */
	p = &block[32];
	/* Boot Indicator */
	p[0] = 0x88;
	/* Boot media type */
	p[1] = iso9660->el_torito.media_type;
	/* Load Segment */
	if (iso9660->el_torito.media_type == BOOT_MEDIA_NO_EMULATION)
		set_num_721(&p[2], iso9660->el_torito.boot_load_seg);
	else
		set_num_721(&p[2], 0);
	/* System Type */
	p[4] = iso9660->el_torito.system_type;
	/* Unused */
	p[5] = 0;
	/* Sector Count */
	if (iso9660->el_torito.media_type == BOOT_MEDIA_NO_EMULATION)
		set_num_721(&p[6], iso9660->el_torito.boot_load_size);
	else
		set_num_721(&p[6], 1);
	/* Load RBA */
	set_num_731(&p[8],
	    iso9660->el_torito.boot->file->content.location);
	/* Unused */
	memset(&p[12], 0, 20);

	return (wb_consume(a, LOGICAL_BLOCK_SIZE));
}

static int
setup_boot_information(struct archive_write *a)
{
	struct iso9660 *iso9660 = a->format_data;
	struct isoent *np;
	int64_t size;
	uint32_t sum;
	unsigned char buff[4096];

	np = iso9660->el_torito.boot;
	lseek(iso9660->temp_fd,
	    np->file->content.offset_of_temp + 64, SEEK_SET);
	size = archive_entry_size(np->file->entry) - 64;
	if (size <= 0) {
		archive_set_error(&a->archive, errno,
		    "Boot file(%jd) is too small", (intmax_t)size + 64);
		return (ARCHIVE_FATAL);
	}
	sum = 0;
	while (size > 0) {
		size_t rsize;
		ssize_t i, rs;

		if (size > (int64_t)sizeof(buff))
			rsize = sizeof(buff);
		else
			rsize = (size_t)size;

		rs = read(iso9660->temp_fd, buff, rsize);
		if (rs <= 0) {
			archive_set_error(&a->archive, errno,
			    "Can't read temporary file(%jd)",
			    (intmax_t)rs);
			return (ARCHIVE_FATAL);
		}
		for (i = 0; i < rs; i += 4)
			sum += archive_le32dec(buff + i);
		size -= rs;
	}
	/* Set the location of Primary Volume Descriptor. */
	set_num_731(buff, SYSTEM_AREA_BLOCK);
	/* Set the location of the boot file. */
	set_num_731(buff+4, np->file->content.location);
	/* Set the size of the boot file. */
	size = fd_boot_image_size(iso9660->el_torito.media_type);
	if (size == 0)
		size = archive_entry_size(np->file->entry);
	set_num_731(buff+8, (uint32_t)size);
	/* Set the sum of the boot file. */
	set_num_731(buff+12, sum);
	/* Clear reserved bytes. */
	memset(buff+16, 0, 40);

	/* Overwrite the boot file. */
	lseek(iso9660->temp_fd,
	    np->file->content.offset_of_temp + 8, SEEK_SET);
	return (write_to_temp(a, buff, 56));
}

#ifdef HAVE_ZLIB_H

static int
zisofs_init_zstream(struct archive_write *a)
{
	struct iso9660 *iso9660 = a->format_data;
	int r;

	iso9660->zisofs.stream.next_in = NULL;
	iso9660->zisofs.stream.avail_in = 0;
	iso9660->zisofs.stream.total_in = 0;
	iso9660->zisofs.stream.total_out = 0;
	if (iso9660->zisofs.stream_valid)
		r = deflateReset(&(iso9660->zisofs.stream));
	else {
		r = deflateInit(&(iso9660->zisofs.stream),
		    iso9660->zisofs.compression_level);
		iso9660->zisofs.stream_valid = 1;
	}
	switch (r) {
	case Z_OK:
		break;
	default:
	case Z_STREAM_ERROR:
		archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
		    "Internal error initializing "
		    "compression library: invalid setup parameter");
		return (ARCHIVE_FATAL);
	case Z_MEM_ERROR:
		archive_set_error(&a->archive, ENOMEM,
		    "Internal error initializing "
		    "compression library");
		return (ARCHIVE_FATAL);
	case Z_VERSION_ERROR:
		archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
		    "Internal error initializing "
		    "compression library: invalid library version");
		return (ARCHIVE_FATAL);
	}
	return (ARCHIVE_OK);
}

#endif /* HAVE_ZLIB_H */

static int
zisofs_init(struct archive_write *a,  struct isofile *file)
{
	struct iso9660 *iso9660 = a->format_data;
#ifdef HAVE_ZLIB_H
	uint64_t tsize;
	size_t _ceil, bpsize;
	int r;
#endif

	iso9660->zisofs.detect_magic = 0;
	iso9660->zisofs.making = 0;

	if (!iso9660->opt.rr || !iso9660->opt.zisofs)
		return (ARCHIVE_OK);

	if (archive_entry_size(file->entry) >= 24 &&
	    archive_entry_size(file->entry) < MULTI_EXTENT_SIZE) {
		/* Acceptable file size for zisofs. */
		iso9660->zisofs.detect_magic = 1;
		iso9660->zisofs.magic_cnt = 0;
	}
	if (!iso9660->zisofs.detect_magic)
		return (ARCHIVE_OK);

#ifdef HAVE_ZLIB_H
	/* The number of Logical Blocks which uncompressed data
	 * will use in iso-image file is the same as the number of
	 * Logical Blocks which zisofs(compressed) data will use
	 * in ISO-image file. It won't reduce iso-image file size. */
	if (archive_entry_size(file->entry) <= LOGICAL_BLOCK_SIZE)
		return (ARCHIVE_OK);

	/* Initialize compression library */
	r = zisofs_init_zstream(a);
	if (r != ARCHIVE_OK)
		return (ARCHIVE_FATAL);

	/* Mark file->zisofs to create RRIP 'ZF' Use Entry. */
	file->zisofs.header_size = ZF_HEADER_SIZE >> 2;
	file->zisofs.log2_bs = ZF_LOG2_BS;
	file->zisofs.uncompressed_size =
		(uint32_t)archive_entry_size(file->entry);

	/* Calculate a size of Block Pointers of zisofs. */
	_ceil = (file->zisofs.uncompressed_size + ZF_BLOCK_SIZE -1)
		>> file->zisofs.log2_bs;
	iso9660->zisofs.block_pointers_cnt = (int)_ceil + 1;
	iso9660->zisofs.block_pointers_idx = 0;

	/* Ensure a buffer size used for Block Pointers */
	bpsize = iso9660->zisofs.block_pointers_cnt *
	    sizeof(iso9660->zisofs.block_pointers[0]);
	if (iso9660->zisofs.block_pointers_allocated < bpsize) {
		free(iso9660->zisofs.block_pointers);
		iso9660->zisofs.block_pointers = malloc(bpsize);
		if (iso9660->zisofs.block_pointers == NULL) {
			archive_set_error(&a->archive, ENOMEM,
			    "Can't allocate data");
			return (ARCHIVE_FATAL);
		}
		iso9660->zisofs.block_pointers_allocated = bpsize;
	}

	/*
	 * Skip zisofs header and Block Pointers, which we will write
	 * after all compressed data of a file written to the temporary
	 * file.
	 */
	tsize = ZF_HEADER_SIZE + bpsize;
	if (write_null(a, (size_t)tsize) != ARCHIVE_OK)
		return (ARCHIVE_FATAL);

	/*
	 * Initialize some variables to make zisofs.
	 */
	archive_le32enc(&(iso9660->zisofs.block_pointers[0]),
		(uint32_t)tsize);
	iso9660->zisofs.remaining = file->zisofs.uncompressed_size;
	iso9660->zisofs.making = 1;
	iso9660->zisofs.allzero = 1;
	iso9660->zisofs.block_offset = tsize;
	iso9660->zisofs.total_size = tsize;
	iso9660->cur_file->cur_content->size = tsize;
#endif

	return (ARCHIVE_OK);
}

static void
zisofs_detect_magic(struct archive_write *a, const void *buff, size_t s)
{
	struct iso9660 *iso9660 = a->format_data;
	struct isofile *file = iso9660->cur_file;
	const unsigned char *p, *endp;
	const unsigned char *magic_buff;
	uint32_t uncompressed_size;
	unsigned char header_size;
	unsigned char log2_bs;
	size_t _ceil, doff;
	uint32_t bst, bed;
	int magic_max;
	int64_t entry_size;

	entry_size = archive_entry_size(file->entry);
	if ((int64_t)sizeof(iso9660->zisofs.magic_buffer) > entry_size)
		magic_max = (int)entry_size;
	else
		magic_max = sizeof(iso9660->zisofs.magic_buffer);

	if (iso9660->zisofs.magic_cnt == 0 && s >= (size_t)magic_max)
		/* It's unnecessary we copy buffer. */
		magic_buff = buff;
	else {
		if (iso9660->zisofs.magic_cnt < magic_max) {
			size_t l;

			l = sizeof(iso9660->zisofs.magic_buffer)
			    - iso9660->zisofs.magic_cnt;
			if (l > s)
				l = s;
			memcpy(iso9660->zisofs.magic_buffer
			    + iso9660->zisofs.magic_cnt, buff, l);
			iso9660->zisofs.magic_cnt += (int)l;
			if (iso9660->zisofs.magic_cnt < magic_max)
				return;
		}
		magic_buff = iso9660->zisofs.magic_buffer;
	}
	iso9660->zisofs.detect_magic = 0;
	p = magic_buff;

	/* Check the magic code of zisofs. */
	if (memcmp(p, zisofs_magic, sizeof(zisofs_magic)) != 0)
		/* This is not zisofs file which made by mkzftree. */
		return;
	p += sizeof(zisofs_magic);

	/* Read a zisofs header. */
	uncompressed_size = archive_le32dec(p);
	header_size = p[4];
	log2_bs = p[5];
	if (uncompressed_size < 24 || header_size != 4 ||
	    log2_bs > 30 || log2_bs < 7)
		return;/* Invalid or not supported header. */

	/* Calculate a size of Block Pointers of zisofs. */
	_ceil = (uncompressed_size +
	        (ARCHIVE_LITERAL_LL(1) << log2_bs) -1) >> log2_bs;
	doff = (_ceil + 1) * 4 + 16;
	if (entry_size < (int64_t)doff)
		return;/* Invalid data. */

	/* Check every Block Pointer has valid value. */
	p = magic_buff + 16;
	endp = magic_buff + magic_max;
	while (_ceil && p + 8 <= endp) {
		bst = archive_le32dec(p);
		if (bst != doff)
			return;/* Invalid data. */
		p += 4;
		bed = archive_le32dec(p);
		if (bed < bst || bed > entry_size)
			return;/* Invalid data. */
		doff += bed - bst;
		_ceil--;
	}

	file->zisofs.uncompressed_size = uncompressed_size;
	file->zisofs.header_size = header_size;
	file->zisofs.log2_bs = log2_bs;

	/* Disable making a zisofs image. */
	iso9660->zisofs.making = 0;
}

#ifdef HAVE_ZLIB_H

/*
 * Compress data and write it to a temporary file.
 */
static int
zisofs_write_to_temp(struct archive_write *a, const void *buff, size_t s)
{
	struct iso9660 *iso9660 = a->format_data;
	struct isofile *file = iso9660->cur_file;
	const unsigned char *b;
	z_stream *zstrm;
	size_t avail, csize;
	int flush, r;

	zstrm = &(iso9660->zisofs.stream);
	zstrm->next_out = wb_buffptr(a);
	zstrm->avail_out = (uInt)wb_remaining(a);
	b = (const unsigned char *)buff;
	do {
		avail = ZF_BLOCK_SIZE - zstrm->total_in;
		if (s < avail) {
			avail = s;
			flush = Z_NO_FLUSH;
		} else
			flush = Z_FINISH;
		iso9660->zisofs.remaining -= avail;
		if (iso9660->zisofs.remaining <= 0)
			flush = Z_FINISH;

		zstrm->next_in = (Bytef *)(uintptr_t)(const void *)b;
		zstrm->avail_in = (uInt)avail;

		/*
		 * Check if current data block are all zero.
		 */
		if (iso9660->zisofs.allzero) {
			const unsigned char *nonzero = b;
			const unsigned char *nonzeroend = b + avail;

			while (nonzero < nonzeroend)
				if (*nonzero++) {
					iso9660->zisofs.allzero = 0;
					break;
				}
		}
		b += avail;
		s -= avail;

		/*
		 * If current data block are all zero, we do not use
		 * compressed data.
		 */
		if (flush == Z_FINISH && iso9660->zisofs.allzero &&
		    avail + zstrm->total_in == ZF_BLOCK_SIZE) {
			if (iso9660->zisofs.block_offset !=
			    file->cur_content->size) {
				int64_t diff;

				r = wb_set_offset(a,
				    file->cur_content->offset_of_temp +
				        iso9660->zisofs.block_offset);
				if (r != ARCHIVE_OK)
					return (r);
				diff = file->cur_content->size -
				    iso9660->zisofs.block_offset;
				file->cur_content->size -= diff;
				iso9660->zisofs.total_size -= diff;
			}
			zstrm->avail_in = 0;
		}

		/*
		 * Compress file data.
		 */
		while (zstrm->avail_in > 0) {
			csize = zstrm->total_out;
			r = deflate(zstrm, flush);
			switch (r) {
			case Z_OK:
			case Z_STREAM_END:
				csize = zstrm->total_out - csize;
				if (wb_consume(a, csize) != ARCHIVE_OK)
					return (ARCHIVE_FATAL);
				iso9660->zisofs.total_size += csize;
				iso9660->cur_file->cur_content->size += csize;
				zstrm->next_out = wb_buffptr(a);
				zstrm->avail_out = (uInt)wb_remaining(a);
				break;
			default:
				archive_set_error(&a->archive,
				    ARCHIVE_ERRNO_MISC,
				    "Compression failed:"
				    " deflate() call returned status %d",
				    r);
				return (ARCHIVE_FATAL);
			}
		}

		if (flush == Z_FINISH) {
			/*
			 * Save the information of one zisofs block.
			 */
			iso9660->zisofs.block_pointers_idx ++;
			archive_le32enc(&(iso9660->zisofs.block_pointers[
			    iso9660->zisofs.block_pointers_idx]),
				(uint32_t)iso9660->zisofs.total_size);
			r = zisofs_init_zstream(a);
			if (r != ARCHIVE_OK)
				return (ARCHIVE_FATAL);
			iso9660->zisofs.allzero = 1;
			iso9660->zisofs.block_offset = file->cur_content->size;
		}
	} while (s);

	return (ARCHIVE_OK);
}

static int
zisofs_finish_entry(struct archive_write *a)
{
	struct iso9660 *iso9660 = a->format_data;
	struct isofile *file = iso9660->cur_file;
	unsigned char buff[16];
	size_t s;
	int64_t tail;

	/* Direct temp file stream to zisofs temp file stream. */
	archive_entry_set_size(file->entry, iso9660->zisofs.total_size);

	/*
	 * Save a file pointer which points the end of current zisofs data.
	 */
	tail = wb_offset(a);

	/*
	 * Make a header.
	 *
	 * +-----------------+----------------+-----------------+
	 * | Header 16 bytes | Block Pointers | Compressed data |
	 * +-----------------+----------------+-----------------+
	 * 0                16               +X
	 * Block Pointers :
	 *   4 * (((Uncompressed file size + block_size -1) / block_size) + 1)
	 *
	 * Write zisofs header.
	 *    Magic number
	 * +----+----+----+----+----+----+----+----+
	 * | 37 | E4 | 53 | 96 | C9 | DB | D6 | 07 |
	 * +----+----+----+----+----+----+----+----+
	 * 0    1    2    3    4    5    6    7    8
	 *
	 * +------------------------+------------------+
	 * | Uncompressed file size | header_size >> 2 |
	 * +------------------------+------------------+
	 * 8                       12                 13
	 *
	 * +-----------------+----------------+
	 * | log2 block_size | Reserved(0000) |
	 * +-----------------+----------------+
	 * 13               14               16
	 */
	memcpy(buff, zisofs_magic, 8);
	set_num_731(buff+8, file->zisofs.uncompressed_size);
	buff[12] = file->zisofs.header_size;
	buff[13] = file->zisofs.log2_bs;
	buff[14] = buff[15] = 0;/* Reserved */

	/* Move to the right position to write the header. */
	wb_set_offset(a, file->content.offset_of_temp);

	/* Write the header. */
	if (wb_write_to_temp(a, buff, 16) != ARCHIVE_OK)
		return (ARCHIVE_FATAL);

	/*
	 * Write zisofs Block Pointers.
	 */
	s = iso9660->zisofs.block_pointers_cnt *
	    sizeof(iso9660->zisofs.block_pointers[0]);
	if (wb_write_to_temp(a, iso9660->zisofs.block_pointers, s)
	    != ARCHIVE_OK)
		return (ARCHIVE_FATAL);

	/* Set a file pointer back to the end of the temporary file. */
	wb_set_offset(a, tail);

	return (ARCHIVE_OK);
}

static int
zisofs_free(struct archive_write *a)
{
	struct iso9660 *iso9660 = a->format_data;
	int ret = ARCHIVE_OK;

	free(iso9660->zisofs.block_pointers);
	if (iso9660->zisofs.stream_valid &&
	    deflateEnd(&(iso9660->zisofs.stream)) != Z_OK) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
		    "Failed to clean up compressor");
		ret = ARCHIVE_FATAL;
	}
	iso9660->zisofs.block_pointers = NULL;
	iso9660->zisofs.stream_valid = 0;
	return (ret);
}

struct zisofs_extract {
	int		 pz_log2_bs; /* Log2 of block size */
	uint64_t	 pz_uncompressed_size;
	size_t		 uncompressed_buffer_size;

	unsigned int	 initialized:1;
	unsigned int	 header_passed:1;

	uint32_t	 pz_offset;
	unsigned char	*block_pointers;
	size_t		 block_pointers_size;
	size_t		 block_pointers_avail;
	size_t		 block_off;
	uint32_t	 block_avail;

	z_stream	 stream;
	int		 stream_valid;
};

static ssize_t
zisofs_extract_init(struct archive_write *a, struct zisofs_extract *zisofs,
    const unsigned char *p, size_t bytes)
{
	size_t avail = bytes;
	size_t _ceil, xsize;

	/* Allocate block pointers buffer. */
	_ceil = (size_t)((zisofs->pz_uncompressed_size +
		(((int64_t)1) << zisofs->pz_log2_bs) - 1)
		>> zisofs->pz_log2_bs);
	xsize = (_ceil + 1) * 4;
	if (zisofs->block_pointers == NULL) {
		size_t alloc = ((xsize >> 10) + 1) << 10;
		zisofs->block_pointers = malloc(alloc);
		if (zisofs->block_pointers == NULL) {
			archive_set_error(&a->archive, ENOMEM,
			    "No memory for zisofs decompression");
			return (ARCHIVE_FATAL);
		}
	}
	zisofs->block_pointers_size = xsize;

	/* Allocate uncompressed data buffer. */
	zisofs->uncompressed_buffer_size = (size_t)1UL << zisofs->pz_log2_bs;

	/*
	 * Read the file header, and check the magic code of zisofs.
	 */
	if (!zisofs->header_passed) {
		int err = 0;
		if (avail < 16) {
			archive_set_error(&a->archive,
			    ARCHIVE_ERRNO_FILE_FORMAT,
			    "Illegal zisofs file body");
			return (ARCHIVE_FATAL);
		}

		if (memcmp(p, zisofs_magic, sizeof(zisofs_magic)) != 0)
			err = 1;
		else if (archive_le32dec(p + 8) != zisofs->pz_uncompressed_size)
			err = 1;
		else if (p[12] != 4 || p[13] != zisofs->pz_log2_bs)
			err = 1;
		if (err) {
			archive_set_error(&a->archive,
			    ARCHIVE_ERRNO_FILE_FORMAT,
			    "Illegal zisofs file body");
			return (ARCHIVE_FATAL);
		}
		avail -= 16;
		p += 16;
		zisofs->header_passed = 1;
	}

	/*
	 * Read block pointers.
	 */
	if (zisofs->header_passed &&
	    zisofs->block_pointers_avail < zisofs->block_pointers_size) {
		xsize = zisofs->block_pointers_size
		    - zisofs->block_pointers_avail;
		if (avail < xsize)
			xsize = avail;
		memcpy(zisofs->block_pointers
		    + zisofs->block_pointers_avail, p, xsize);
		zisofs->block_pointers_avail += xsize;
		avail -= xsize;
	    	if (zisofs->block_pointers_avail
		    == zisofs->block_pointers_size) {
			/* We've got all block pointers and initialize
			 * related variables.	*/
			zisofs->block_off = 0;
			zisofs->block_avail = 0;
			/* Complete a initialization */
			zisofs->initialized = 1;
		}
	}
	return ((ssize_t)avail);
}

static ssize_t
zisofs_extract(struct archive_write *a, struct zisofs_extract *zisofs,
    const unsigned char *p, size_t bytes)
{
	size_t avail;
	int r;

	if (!zisofs->initialized) {
		ssize_t rs = zisofs_extract_init(a, zisofs, p, bytes);
		if (rs < 0)
			return (rs);
		if (!zisofs->initialized) {
			/* We need more data. */
			zisofs->pz_offset += (uint32_t)bytes;
			return (bytes);
		}
		avail = rs;
		p += bytes - avail;
	} else
		avail = bytes;

	/*
	 * Get block offsets from block pointers.
	 */
	if (zisofs->block_avail == 0) {
		uint32_t bst, bed;

		if (zisofs->block_off + 4 >= zisofs->block_pointers_size) {
			/* There isn't a pair of offsets. */
			archive_set_error(&a->archive,
			    ARCHIVE_ERRNO_FILE_FORMAT,
			    "Illegal zisofs block pointers");
			return (ARCHIVE_FATAL);
		}
		bst = archive_le32dec(
		    zisofs->block_pointers + zisofs->block_off);
		if (bst != zisofs->pz_offset + (bytes - avail)) {
			archive_set_error(&a->archive,
			    ARCHIVE_ERRNO_FILE_FORMAT,
			    "Illegal zisofs block pointers(cannot seek)");
			return (ARCHIVE_FATAL);
		}
		bed = archive_le32dec(
		    zisofs->block_pointers + zisofs->block_off + 4);
		if (bed < bst) {
			archive_set_error(&a->archive,
			    ARCHIVE_ERRNO_FILE_FORMAT,
			    "Illegal zisofs block pointers");
			return (ARCHIVE_FATAL);
		}
		zisofs->block_avail = bed - bst;
		zisofs->block_off += 4;

		/* Initialize compression library for new block. */
		if (zisofs->stream_valid)
			r = inflateReset(&zisofs->stream);
		else
			r = inflateInit(&zisofs->stream);
		if (r != Z_OK) {
			archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
			    "Can't initialize zisofs decompression.");
			return (ARCHIVE_FATAL);
		}
		zisofs->stream_valid = 1;
		zisofs->stream.total_in = 0;
		zisofs->stream.total_out = 0;
	}

	/*
	 * Make uncompressed data.
	 */
	if (zisofs->block_avail == 0) {
		/*
		 * It's basically 32K bytes NUL data.
		 */
		unsigned char *wb;
		size_t size, wsize;

		size = zisofs->uncompressed_buffer_size;
		while (size) {
			wb = wb_buffptr(a);
			if (size > wb_remaining(a))
				wsize = wb_remaining(a);
			else
				wsize = size;
			memset(wb, 0, wsize);
			r = wb_consume(a, wsize);
			if (r < 0)
				return (r);
			size -= wsize;
		}
	} else {
		zisofs->stream.next_in = (Bytef *)(uintptr_t)(const void *)p;
		if (avail > zisofs->block_avail)
			zisofs->stream.avail_in = zisofs->block_avail;
		else
			zisofs->stream.avail_in = (uInt)avail;
		zisofs->stream.next_out = wb_buffptr(a);
		zisofs->stream.avail_out = (uInt)wb_remaining(a);

		r = inflate(&zisofs->stream, 0);
		switch (r) {
		case Z_OK: /* Decompressor made some progress.*/
		case Z_STREAM_END: /* Found end of stream. */
			break;
		default:
			archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
			    "zisofs decompression failed (%d)", r);
			return (ARCHIVE_FATAL);
		}
		avail -= zisofs->stream.next_in - p;
		zisofs->block_avail -= (uint32_t)(zisofs->stream.next_in - p);
		r = wb_consume(a, wb_remaining(a) - zisofs->stream.avail_out);
		if (r < 0)
			return (r);
	}
	zisofs->pz_offset += (uint32_t)bytes;
	return (bytes - avail);
}

static int
zisofs_rewind_boot_file(struct archive_write *a)
{
	struct iso9660 *iso9660 = a->format_data;
	struct isofile *file;
	unsigned char *rbuff;
	ssize_t r;
	size_t remaining, rbuff_size;
	struct zisofs_extract zext;
	int64_t read_offset, write_offset, new_offset;
	int fd, ret = ARCHIVE_OK;

	file = iso9660->el_torito.boot->file;
	/*
	 * There is nothing to do if this boot file does not have
	 * zisofs header.
	 */
	if (file->zisofs.header_size == 0)
		return (ARCHIVE_OK);

	/*
	 * Uncompress the zisofs'ed file contents.
	 */
	memset(&zext, 0, sizeof(zext));
	zext.pz_uncompressed_size = file->zisofs.uncompressed_size;
	zext.pz_log2_bs = file->zisofs.log2_bs;

	fd = iso9660->temp_fd;
	new_offset = wb_offset(a);
	read_offset = file->content.offset_of_temp;
	remaining = (size_t)file->content.size;
	if (remaining > 1024 * 32)
		rbuff_size = 1024 * 32;
	else
		rbuff_size = remaining;

	rbuff = malloc(rbuff_size);
	if (rbuff == NULL) {
		archive_set_error(&a->archive, ENOMEM, "Can't allocate memory");
		return (ARCHIVE_FATAL);
	}
	while (remaining) {
		size_t rsize;
		ssize_t rs;

		/* Get the current file pointer. */
		write_offset = lseek(fd, 0, SEEK_CUR);

		/* Change the file pointer to read. */
		lseek(fd, read_offset, SEEK_SET);

		rsize = rbuff_size;
		if (rsize > remaining)
			rsize = remaining;
		rs = read(iso9660->temp_fd, rbuff, rsize);
		if (rs <= 0) {
			archive_set_error(&a->archive, errno,
			    "Can't read temporary file(%jd)", (intmax_t)rs);
			ret = ARCHIVE_FATAL;
			break;
		}
		remaining -= rs;
		read_offset += rs;

		/* Put the file pointer back to write. */
		lseek(fd, write_offset, SEEK_SET);

		r = zisofs_extract(a, &zext, rbuff, rs);
		if (r < 0) {
			ret = (int)r;
			break;
		}
	}

	if (ret == ARCHIVE_OK) {
		/*
		 * Change the boot file content from zisofs'ed data
		 * to plain data.
		 */
		file->content.offset_of_temp = new_offset;
		file->content.size = file->zisofs.uncompressed_size;
		archive_entry_set_size(file->entry, file->content.size);
		/* Set to be no zisofs. */
		file->zisofs.header_size = 0;
		file->zisofs.log2_bs = 0;
		file->zisofs.uncompressed_size = 0;
		r = wb_write_padding_to_temp(a, file->content.size);
		if (r < 0)
			ret = ARCHIVE_FATAL;
	}

	/*
	 * Free the resource we used in this function only.
	 */
	free(rbuff);
	free(zext.block_pointers);
	if (zext.stream_valid && inflateEnd(&(zext.stream)) != Z_OK) {
        	archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
		    "Failed to clean up compressor");
		ret = ARCHIVE_FATAL;
	}

	return (ret);
}

#else

static int
zisofs_write_to_temp(struct archive_write *a, const void *buff, size_t s)
{
	(void)buff; /* UNUSED */
	(void)s; /* UNUSED */
	archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Programming error");
	return (ARCHIVE_FATAL);
}

static int
zisofs_rewind_boot_file(struct archive_write *a)
{
	struct iso9660 *iso9660 = a->format_data;

	if (iso9660->el_torito.boot->file->zisofs.header_size != 0) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
		    "We cannot extract the zisofs imaged boot file;"
		    " this may not boot in being zisofs imaged");
		return (ARCHIVE_FAILED);
	}
	return (ARCHIVE_OK);
}

static int
zisofs_finish_entry(struct archive_write *a)
{
	(void)a; /* UNUSED */
	return (ARCHIVE_OK);
}

static int
zisofs_free(struct archive_write *a)
{
	(void)a; /* UNUSED */
	return (ARCHIVE_OK);
}

#endif /* HAVE_ZLIB_H */

