From 3ccb4ab471632e74170b4957ded719752fedabc5 Mon Sep 17 00:00:00 2001
From: Sascha Wildner <saw@online.de>
Date: Sun, 27 Jan 2019 22:42:13 +0100
Subject: [PATCH 1/3] Bring in most of makefs(8) from FreeBSD unchanged.

---
 usr.sbin/makefs/Makefile                   |   38 +
 usr.sbin/makefs/cd9660.c                   | 2133 ++++++++++++++++++++
 usr.sbin/makefs/cd9660.h                   |  358 ++++
 usr.sbin/makefs/cd9660/Makefile.inc        |    9 +
 usr.sbin/makefs/cd9660/cd9660_archimedes.c |  126 ++
 usr.sbin/makefs/cd9660/cd9660_archimedes.h |   52 +
 usr.sbin/makefs/cd9660/cd9660_conversion.c |  202 ++
 usr.sbin/makefs/cd9660/cd9660_debug.c      |  489 +++++
 usr.sbin/makefs/cd9660/cd9660_eltorito.c   |  742 +++++++
 usr.sbin/makefs/cd9660/cd9660_eltorito.h   |  168 ++
 usr.sbin/makefs/cd9660/cd9660_strings.c    |  122 ++
 usr.sbin/makefs/cd9660/cd9660_write.c      |  514 +++++
 usr.sbin/makefs/cd9660/iso9660_rrip.c      |  841 ++++++++
 usr.sbin/makefs/cd9660/iso9660_rrip.h      |  294 +++
 usr.sbin/makefs/ffs.c                      | 1185 +++++++++++
 usr.sbin/makefs/ffs.h                      |   73 +
 usr.sbin/makefs/ffs/Makefile.inc           |    9 +
 usr.sbin/makefs/ffs/buf.c                  |  216 ++
 usr.sbin/makefs/ffs/buf.h                  |   77 +
 usr.sbin/makefs/ffs/ffs_alloc.c            |  683 +++++++
 usr.sbin/makefs/ffs/ffs_balloc.c           |  580 ++++++
 usr.sbin/makefs/ffs/ffs_bswap.c            |  261 +++
 usr.sbin/makefs/ffs/ffs_extern.h           |   79 +
 usr.sbin/makefs/ffs/ffs_subr.c             |  184 ++
 usr.sbin/makefs/ffs/mkfs.c                 |  837 ++++++++
 usr.sbin/makefs/ffs/newfs_extern.h         |   38 +
 usr.sbin/makefs/ffs/ufs_bmap.c             |  142 ++
 usr.sbin/makefs/ffs/ufs_bswap.h            |   83 +
 usr.sbin/makefs/ffs/ufs_inode.h            |   99 +
 usr.sbin/makefs/makefs.8                   |  428 ++++
 usr.sbin/makefs/makefs.c                   |  506 +++++
 usr.sbin/makefs/makefs.h                   |  304 +++
 usr.sbin/makefs/mtree.c                    | 1112 ++++++++++
 usr.sbin/makefs/walk.c                     |  698 +++++++
 34 files changed, 13682 insertions(+)
 create mode 100644 usr.sbin/makefs/Makefile
 create mode 100644 usr.sbin/makefs/cd9660.c
 create mode 100644 usr.sbin/makefs/cd9660.h
 create mode 100644 usr.sbin/makefs/cd9660/Makefile.inc
 create mode 100644 usr.sbin/makefs/cd9660/cd9660_archimedes.c
 create mode 100644 usr.sbin/makefs/cd9660/cd9660_archimedes.h
 create mode 100644 usr.sbin/makefs/cd9660/cd9660_conversion.c
 create mode 100644 usr.sbin/makefs/cd9660/cd9660_debug.c
 create mode 100644 usr.sbin/makefs/cd9660/cd9660_eltorito.c
 create mode 100644 usr.sbin/makefs/cd9660/cd9660_eltorito.h
 create mode 100644 usr.sbin/makefs/cd9660/cd9660_strings.c
 create mode 100644 usr.sbin/makefs/cd9660/cd9660_write.c
 create mode 100644 usr.sbin/makefs/cd9660/iso9660_rrip.c
 create mode 100644 usr.sbin/makefs/cd9660/iso9660_rrip.h
 create mode 100644 usr.sbin/makefs/ffs.c
 create mode 100644 usr.sbin/makefs/ffs.h
 create mode 100644 usr.sbin/makefs/ffs/Makefile.inc
 create mode 100644 usr.sbin/makefs/ffs/buf.c
 create mode 100644 usr.sbin/makefs/ffs/buf.h
 create mode 100644 usr.sbin/makefs/ffs/ffs_alloc.c
 create mode 100644 usr.sbin/makefs/ffs/ffs_balloc.c
 create mode 100644 usr.sbin/makefs/ffs/ffs_bswap.c
 create mode 100644 usr.sbin/makefs/ffs/ffs_extern.h
 create mode 100644 usr.sbin/makefs/ffs/ffs_subr.c
 create mode 100644 usr.sbin/makefs/ffs/mkfs.c
 create mode 100644 usr.sbin/makefs/ffs/newfs_extern.h
 create mode 100644 usr.sbin/makefs/ffs/ufs_bmap.c
 create mode 100644 usr.sbin/makefs/ffs/ufs_bswap.h
 create mode 100644 usr.sbin/makefs/ffs/ufs_inode.h
 create mode 100644 usr.sbin/makefs/makefs.8
 create mode 100644 usr.sbin/makefs/makefs.c
 create mode 100644 usr.sbin/makefs/makefs.h
 create mode 100644 usr.sbin/makefs/mtree.c
 create mode 100644 usr.sbin/makefs/walk.c

diff --git a/usr.sbin/makefs/Makefile b/usr.sbin/makefs/Makefile
new file mode 100644
index 0000000000..7987440cc3
--- /dev/null
+++ b/usr.sbin/makefs/Makefile
@@ -0,0 +1,38 @@
+#	$FreeBSD: head/usr.sbin/makefs/Makefile 332844 2018-04-20 22:23:38Z emaste $
+
+SRCDIR:=${.PARSEDIR:tA}
+
+.include <src.opts.mk>
+
+PROG=	makefs
+
+CFLAGS+=-I${SRCDIR}
+
+SRCS=	cd9660.c ffs.c \
+	makefs.c \
+	mtree.c \
+	walk.c
+MAN=	makefs.8
+
+WARNS?=	2
+
+.include "${SRCDIR}/cd9660/Makefile.inc"
+.include "${SRCDIR}/ffs/Makefile.inc"
+
+CFLAGS+=-DHAVE_STRUCT_STAT_ST_FLAGS=1
+
+.PATH: ${SRCTOP}/contrib/mtree
+CFLAGS+=-I${SRCTOP}/contrib/mtree
+SRCS+=	getid.c misc.c spec.c
+
+.PATH: ${SRCTOP}/contrib/mknod
+CFLAGS+=-I${SRCTOP}/contrib/mknod
+SRCS+=	pack_dev.c
+
+CFLAGS+=	-I${SRCTOP}/lib/libnetbsd
+LIBADD=		netbsd util sbuf
+
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/makefs/cd9660.c b/usr.sbin/makefs/cd9660.c
new file mode 100644
index 0000000000..67c0f5b45f
--- /dev/null
+++ b/usr.sbin/makefs/cd9660.c
@@ -0,0 +1,2133 @@
+/*	$NetBSD: cd9660.c,v 1.32 2011/08/23 17:09:11 christos Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-NetBSD AND BSD-4-Clause
+ *
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam.  All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM 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.
+ */
+/*
+ * Copyright (c) 2001 Wasabi Systems, Inc.
+ * All rights reserved.
+ *
+ * Written by Luke Mewburn for Wasabi Systems, Inc.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software developed for the NetBSD Project by
+ *      Wasabi Systems, Inc.
+ * 4. The name of Wasabi Systems, Inc. may not be used to endorse
+ *    or promote products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``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 WASABI SYSTEMS, INC
+ * 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.
+ */
+/*
+ * Copyright (c) 1982, 1986, 1989, 1993
+ *	The Regents of the University of California.  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.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/cd9660.c 331843 2018-03-31 15:04:41Z benno $");
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <util.h>
+
+#include "makefs.h"
+#include "cd9660.h"
+#include "cd9660/iso9660_rrip.h"
+#include "cd9660/cd9660_archimedes.h"
+
+static void cd9660_finalize_PVD(iso9660_disk *);
+static cd9660node *cd9660_allocate_cd9660node(void);
+static void cd9660_set_defaults(iso9660_disk *);
+static int cd9660_arguments_set_string(const char *, const char *, int,
+    char, char *);
+static void cd9660_populate_iso_dir_record(
+    struct _iso_directory_record_cd9660 *, u_char, u_char, u_char,
+    const char *);
+static void cd9660_setup_root_node(iso9660_disk *);
+static int cd9660_setup_volume_descriptors(iso9660_disk *);
+#if 0
+static int cd9660_fill_extended_attribute_record(cd9660node *);
+#endif
+static void cd9660_sort_nodes(cd9660node *);
+static int cd9660_translate_node_common(iso9660_disk *, cd9660node *);
+static int cd9660_translate_node(iso9660_disk *, fsnode *, cd9660node *);
+static int cd9660_compare_filename(const char *, const char *);
+static void cd9660_sorted_child_insert(cd9660node *, cd9660node *);
+static int cd9660_handle_collisions(iso9660_disk *, cd9660node *, int);
+static cd9660node *cd9660_rename_filename(iso9660_disk *, cd9660node *, int,
+    int);
+static void cd9660_copy_filenames(iso9660_disk *, cd9660node *);
+static void cd9660_sorting_nodes(cd9660node *);
+static int cd9660_count_collisions(cd9660node *);
+static cd9660node *cd9660_rrip_move_directory(iso9660_disk *, cd9660node *);
+static int cd9660_add_dot_records(iso9660_disk *, cd9660node *);
+
+static void cd9660_convert_structure(iso9660_disk *, fsnode *, cd9660node *, int,
+    int *, int *);
+static void cd9660_free_structure(cd9660node *);
+static int cd9660_generate_path_table(iso9660_disk *);
+static int cd9660_level1_convert_filename(iso9660_disk *, const char *, char *,
+    int);
+static int cd9660_level2_convert_filename(iso9660_disk *, const char *, char *,
+    int);
+#if 0
+static int cd9660_joliet_convert_filename(iso9660_disk *, const char *, char *,
+    int);
+#endif
+static int cd9660_convert_filename(iso9660_disk *, const char *, char *, int);
+static void cd9660_populate_dot_records(iso9660_disk *, cd9660node *);
+static int64_t cd9660_compute_offsets(iso9660_disk *, cd9660node *, int64_t);
+#if 0
+static int cd9660_copy_stat_info(cd9660node *, cd9660node *, int);
+#endif
+static cd9660node *cd9660_create_virtual_entry(iso9660_disk *, const char *,
+    cd9660node *, int, int);
+static cd9660node *cd9660_create_file(iso9660_disk *, const char *,
+    cd9660node *, cd9660node *);
+static cd9660node *cd9660_create_directory(iso9660_disk *, const char *,
+    cd9660node *, cd9660node *);
+static cd9660node *cd9660_create_special_directory(iso9660_disk *, u_char,
+    cd9660node *);
+static int  cd9660_add_generic_bootimage(iso9660_disk *, const char *);
+
+
+/*
+ * Allocate and initialize a cd9660node
+ * @returns struct cd9660node * Pointer to new node, or NULL on error
+ */
+static cd9660node *
+cd9660_allocate_cd9660node(void)
+{
+	cd9660node *temp = ecalloc(1, sizeof(*temp));
+
+	TAILQ_INIT(&temp->cn_children);
+	temp->parent = temp->dot_record = temp->dot_dot_record = NULL;
+	temp->ptnext = temp->ptprev = temp->ptlast = NULL;
+	temp->node = NULL;
+	temp->isoDirRecord = NULL;
+	temp->isoExtAttributes = NULL;
+	temp->rr_real_parent = temp->rr_relocated = NULL;
+	temp->su_tail_data = NULL;
+	return temp;
+}
+
+/**
+* Set default values for cd9660 extension to makefs
+*/
+static void
+cd9660_set_defaults(iso9660_disk *diskStructure)
+{
+	/*Fix the sector size for now, though the spec allows for other sizes*/
+	diskStructure->sectorSize = 2048;
+
+	/* Set up defaults in our own structure */
+	diskStructure->verbose_level = 0;
+	diskStructure->keep_bad_images = 0;
+	diskStructure->follow_sym_links = 0;
+	diskStructure->isoLevel = 2;
+
+	diskStructure->rock_ridge_enabled = 0;
+	diskStructure->rock_ridge_renamed_dir_name = 0;
+	diskStructure->rock_ridge_move_count = 0;
+	diskStructure->rr_moved_dir = 0;
+
+	diskStructure->archimedes_enabled = 0;
+	diskStructure->chrp_boot = 0;
+
+	diskStructure->include_padding_areas = 1;
+
+	/* Spec breaking functionality */
+	diskStructure->allow_deep_trees =
+	    diskStructure->allow_start_dot =
+	    diskStructure->allow_max_name =
+	    diskStructure->allow_illegal_chars =
+	    diskStructure->allow_lowercase =
+	    diskStructure->allow_multidot =
+	    diskStructure->omit_trailing_period = 0;
+
+	/* Make sure the PVD is clear */
+	memset(&diskStructure->primaryDescriptor, 0, 2048);
+
+	memset(diskStructure->primaryDescriptor.publisher_id,	0x20,128);
+	memset(diskStructure->primaryDescriptor.preparer_id,	0x20,128);
+	memset(diskStructure->primaryDescriptor.application_id,	0x20,128);
+	memset(diskStructure->primaryDescriptor.copyright_file_id, 0x20,37);
+	memset(diskStructure->primaryDescriptor.abstract_file_id, 0x20,37);
+	memset(diskStructure->primaryDescriptor.bibliographic_file_id, 0x20,37);
+
+	strcpy(diskStructure->primaryDescriptor.system_id, "FreeBSD");
+
+	/* Boot support: Initially disabled */
+	diskStructure->has_generic_bootimage = 0;
+	diskStructure->generic_bootimage = NULL;
+
+	diskStructure->boot_image_directory = 0;
+	/*memset(diskStructure->boot_descriptor, 0, 2048);*/
+
+	diskStructure->is_bootable = 0;
+	TAILQ_INIT(&diskStructure->boot_images);
+	LIST_INIT(&diskStructure->boot_entries);
+}
+
+void
+cd9660_prep_opts(fsinfo_t *fsopts)
+{
+	iso9660_disk *diskStructure = ecalloc(1, sizeof(*diskStructure));
+
+#define OPT_STR(letter, name, desc) \
+	{ letter, name, NULL, OPT_STRBUF, 0, 0, desc }
+
+#define OPT_NUM(letter, name, field, min, max, desc) \
+	{ letter, name, &diskStructure->field, \
+	  sizeof(diskStructure->field) == 8 ? OPT_INT64 : \
+	  (sizeof(diskStructure->field) == 4 ? OPT_INT32 : \
+	  (sizeof(diskStructure->field) == 2 ? OPT_INT16 : OPT_INT8)), \
+	  min, max, desc }
+
+#define OPT_BOOL(letter, name, field, desc) \
+	OPT_NUM(letter, name, field, 0, 1, desc)
+
+	const option_t cd9660_options[] = {
+		OPT_NUM('l', "isolevel", isoLevel,
+		    1, 2, "ISO Level"),
+		OPT_NUM('v', "verbose", verbose_level,
+		    0, 2, "Turns on verbose output"),
+
+		OPT_BOOL('h', "help", displayHelp,
+		    "Show help message"),
+		OPT_BOOL('S', "follow-symlinks", follow_sym_links,
+		    "Resolve symlinks in pathnames"),
+		OPT_BOOL('R', "rockridge", rock_ridge_enabled,
+		    "Enable Rock-Ridge extensions"),
+		OPT_BOOL('C', "chrp-boot", chrp_boot,
+		    "Enable CHRP boot"),
+		OPT_BOOL('K', "keep-bad-images", keep_bad_images,
+		    "Keep bad images"),
+		OPT_BOOL('D', "allow-deep-trees", allow_deep_trees,
+		    "Allow trees more than 8 levels"),
+		OPT_BOOL('a', "allow-max-name", allow_max_name,
+		    "Allow 37 char filenames (unimplemented)"),
+		OPT_BOOL('i', "allow-illegal-chars", allow_illegal_chars,
+		    "Allow illegal characters in filenames"),
+		OPT_BOOL('m', "allow-multidot", allow_multidot,
+		    "Allow multiple periods in filenames"),
+		OPT_BOOL('o', "omit-trailing-period", omit_trailing_period,
+		    "Omit trailing periods in filenames"),
+		OPT_BOOL('\0', "allow-lowercase", allow_lowercase,
+		    "Allow lowercase characters in filenames"),
+		OPT_BOOL('\0', "archimedes", archimedes_enabled,
+		    "Enable Archimedes structure"),
+		OPT_BOOL('\0', "no-trailing-padding", include_padding_areas,
+		    "Include padding areas"),
+
+		OPT_STR('A', "applicationid", "Application Identifier"),
+		OPT_STR('P', "publisher", "Publisher Identifier"),
+		OPT_STR('p', "preparer", "Preparer Identifier"),
+		OPT_STR('L', "label", "Disk Label"),
+		OPT_STR('V', "volumeid", "Volume Set Identifier"),
+		OPT_STR('B', "bootimage", "Boot image parameter"),
+		OPT_STR('G', "generic-bootimage", "Generic boot image param"),
+		OPT_STR('\0', "bootimagedir", "Boot image directory"),
+		OPT_STR('\0', "no-emul-boot", "No boot emulation"),
+		OPT_STR('\0', "no-boot", "No boot support"),
+		OPT_STR('\0', "hard-disk-boot", "Boot from hard disk"),
+		OPT_STR('\0', "boot-load-segment", "Boot load segment"),
+		OPT_STR('\0', "platformid", "Section Header Platform ID"),
+
+		{ .name = NULL }
+	};
+
+	fsopts->fs_specific = diskStructure;
+	fsopts->fs_options = copy_opts(cd9660_options);
+
+	cd9660_set_defaults(diskStructure);
+}
+
+void
+cd9660_cleanup_opts(fsinfo_t *fsopts)
+{
+	free(fsopts->fs_specific);
+	free(fsopts->fs_options);
+}
+
+static int
+cd9660_arguments_set_string(const char *val, const char *fieldtitle, int length,
+			    char testmode, char * dest)
+{
+	int len, test;
+
+	if (val == NULL)
+		warnx("error: The %s requires a string argument", fieldtitle);
+	else if ((len = strlen(val)) <= length) {
+		if (testmode == 'd')
+			test = cd9660_valid_d_chars(val);
+		else
+			test = cd9660_valid_a_chars(val);
+		if (test) {
+			memcpy(dest, val, len);
+			if (test == 2)
+				cd9660_uppercase_characters(dest, len);
+			return 1;
+		} else
+			warnx("error: The %s must be composed of "
+			      "%c-characters", fieldtitle, testmode);
+	} else
+		warnx("error: The %s must be at most 32 characters long",
+		    fieldtitle);
+	return 0;
+}
+
+/*
+ * Command-line parsing function
+ */
+
+int
+cd9660_parse_opts(const char *option, fsinfo_t *fsopts)
+{
+	int	rv, i;
+	iso9660_disk *diskStructure = fsopts->fs_specific;
+	option_t *cd9660_options = fsopts->fs_options;
+	char buf[1024];
+	const char *name, *desc;
+
+	assert(option != NULL);
+
+	if (debug & DEBUG_FS_PARSE_OPTS)
+		printf("%s: got `%s'\n", __func__, option);
+
+	i = set_option(cd9660_options, option, buf, sizeof(buf));
+	if (i == -1)
+		return 0;
+
+	if (cd9660_options[i].name == NULL)
+		abort();
+
+	name = cd9660_options[i].name;
+	desc = cd9660_options[i].desc;
+	switch (cd9660_options[i].letter) {
+	case 'h':
+	case 'S':
+		rv = 0; /* this is not handled yet */
+		break;
+	case 'L':
+		rv = cd9660_arguments_set_string(buf, desc, 32, 'd',
+		    diskStructure->primaryDescriptor.volume_id);
+		break;
+	case 'A':
+		rv = cd9660_arguments_set_string(buf, desc, 128, 'a',
+		    diskStructure->primaryDescriptor.application_id);
+		break;
+	case 'P':
+		rv = cd9660_arguments_set_string(buf, desc, 128, 'a',
+		    diskStructure->primaryDescriptor.publisher_id);
+		break;
+	case 'p':
+		rv = cd9660_arguments_set_string(buf, desc, 128, 'a',
+		    diskStructure->primaryDescriptor.preparer_id);
+		break;
+	case 'V':
+		rv = cd9660_arguments_set_string(buf, desc, 128, 'a',
+		    diskStructure->primaryDescriptor.volume_set_id);
+		break;
+	/* Boot options */
+	case 'B':
+		if (buf[0] == '\0') {
+			warnx("The Boot Image parameter requires a valid boot"
+			    "information string");
+			rv = 0;
+		} else
+			rv = cd9660_add_boot_disk(diskStructure, buf);
+		break;
+	case 'G':
+		if (buf[0] == '\0') {
+			warnx("The Generic Boot Image parameter requires a"
+			    " valid boot information string");
+			rv = 0;
+		} else
+			rv = cd9660_add_generic_bootimage(diskStructure, buf);
+		break;
+	default:
+		if (strcmp(name, "bootimagedir") == 0) {
+			/*
+			 * XXXfvdl this is unused.
+			 */
+			if (buf[0] == '\0') {
+				warnx("The Boot Image Directory parameter"
+				    " requires a directory name");
+				rv = 0;
+			} else {
+				diskStructure->boot_image_directory =
+				    emalloc(strlen(buf) + 1);
+				/* BIG TODO: Add the max length function here */
+				rv = cd9660_arguments_set_string(buf, desc, 12,
+				    'd', diskStructure->boot_image_directory);
+			}
+		} else if (strcmp(name, "no-emul-boot") == 0 ||
+		    strcmp(name, "no-boot") == 0 ||
+		    strcmp(name, "hard-disk-boot") == 0) {
+			/* RRIP */
+			cd9660_eltorito_add_boot_option(diskStructure, name, 0);
+			rv = 1;
+		} else if (strcmp(name, "boot-load-segment") == 0 ||
+		    strcmp(name, "platformid") == 0) {
+			if (buf[0] == '\0') {
+				warnx("Option `%s' doesn't contain a value",
+				    name);
+				rv = 0;
+			} else {
+				cd9660_eltorito_add_boot_option(diskStructure,
+				    name, buf);
+				rv = 1;
+			}
+		} else
+			rv = 1;
+	}
+	return rv;
+}
+
+/*
+ * Main function for cd9660_makefs
+ * Builds the ISO image file
+ * @param const char *image The image filename to create
+ * @param const char *dir The directory that is being read
+ * @param struct fsnode *root The root node of the filesystem tree
+ * @param struct fsinfo_t *fsopts Any options
+ */
+void
+cd9660_makefs(const char *image, const char *dir, fsnode *root,
+    fsinfo_t *fsopts)
+{
+	int64_t startoffset;
+	int numDirectories;
+	uint64_t pathTableSectors;
+	int64_t firstAvailableSector;
+	int64_t totalSpace;
+	int error;
+	cd9660node *real_root;
+	iso9660_disk *diskStructure = fsopts->fs_specific;
+
+	if (diskStructure->verbose_level > 0)
+		printf("%s: ISO level is %i\n", __func__,
+		    diskStructure->isoLevel);
+	if (diskStructure->isoLevel < 2 &&
+	    diskStructure->allow_multidot)
+		errx(EXIT_FAILURE, "allow-multidot requires iso level of 2");
+
+	assert(image != NULL);
+	assert(dir != NULL);
+	assert(root != NULL);
+
+	if (diskStructure->displayHelp) {
+		/*
+		 * Display help here - probably want to put it in
+		 * a separate function
+		 */
+		return;
+	}
+
+	if (diskStructure->verbose_level > 0)
+		printf("%s: image %s directory %s root %p\n", __func__,
+		    image, dir, root);
+
+	/* Set up some constants. Later, these will be defined with options */
+
+	/* Counter needed for path tables */
+	numDirectories = 0;
+
+	/* Convert tree to our own format */
+	/* Actually, we now need to add the REAL root node, at level 0 */
+
+	real_root = cd9660_allocate_cd9660node();
+	real_root->isoDirRecord = emalloc(sizeof(*real_root->isoDirRecord));
+	/* Leave filename blank for root */
+	memset(real_root->isoDirRecord->name, 0,
+	    ISO_FILENAME_MAXLENGTH_WITH_PADDING);
+
+	real_root->level = 0;
+	diskStructure->rootNode = real_root;
+	real_root->type = CD9660_TYPE_DIR;
+	error = 0;
+	real_root->node = root;
+	cd9660_convert_structure(diskStructure, root, real_root, 1,
+	    &numDirectories, &error);
+
+	if (TAILQ_EMPTY(&real_root->cn_children)) {
+		errx(EXIT_FAILURE, "%s: converted directory is empty. "
+		    "Tree conversion failed", __func__);
+	} else if (error != 0) {
+		errx(EXIT_FAILURE, "%s: tree conversion failed", __func__);
+	} else {
+		if (diskStructure->verbose_level > 0)
+			printf("%s: tree converted\n", __func__);
+	}
+
+	/* Add the dot and dot dot records */
+	cd9660_add_dot_records(diskStructure, real_root);
+
+	cd9660_setup_root_node(diskStructure);
+
+	if (diskStructure->verbose_level > 0)
+		printf("%s: done converting tree\n", __func__);
+
+	/* non-SUSP extensions */
+	if (diskStructure->archimedes_enabled)
+		archimedes_convert_tree(diskStructure->rootNode);
+
+	/* Rock ridge / SUSP init pass */
+	if (diskStructure->rock_ridge_enabled) {
+		cd9660_susp_initialize(diskStructure, diskStructure->rootNode,
+		    diskStructure->rootNode, NULL);
+	}
+
+	/* Build path table structure */
+	diskStructure->pathTableLength = cd9660_generate_path_table(
+	    diskStructure);
+
+	pathTableSectors = CD9660_BLOCKS(diskStructure->sectorSize,
+		diskStructure->pathTableLength);
+
+	firstAvailableSector = cd9660_setup_volume_descriptors(diskStructure);
+	if (diskStructure->is_bootable) {
+		firstAvailableSector = cd9660_setup_boot(diskStructure,
+		    firstAvailableSector);
+		if (firstAvailableSector < 0)
+			errx(EXIT_FAILURE, "setup_boot failed");
+	}
+	/* LE first, then BE */
+	diskStructure->primaryLittleEndianTableSector = firstAvailableSector;
+	diskStructure->primaryBigEndianTableSector =
+		diskStructure->primaryLittleEndianTableSector + pathTableSectors;
+
+	/* Set the secondary ones to -1, not going to use them for now */
+	diskStructure->secondaryBigEndianTableSector = -1;
+	diskStructure->secondaryLittleEndianTableSector = -1;
+
+	diskStructure->dataFirstSector =
+	    diskStructure->primaryBigEndianTableSector + pathTableSectors;
+	if (diskStructure->verbose_level > 0)
+		printf("%s: Path table conversion complete. "
+		    "Each table is %i bytes, or %" PRIu64 " sectors.\n",
+		    __func__,
+		    diskStructure->pathTableLength, pathTableSectors);
+
+	startoffset = diskStructure->sectorSize*diskStructure->dataFirstSector;
+
+	totalSpace = cd9660_compute_offsets(diskStructure, real_root, startoffset);
+
+	diskStructure->totalSectors = diskStructure->dataFirstSector +
+		CD9660_BLOCKS(diskStructure->sectorSize, totalSpace);
+
+	/* Disabled until pass 1 is done */
+	if (diskStructure->rock_ridge_enabled) {
+		diskStructure->susp_continuation_area_start_sector =
+		    diskStructure->totalSectors;
+		diskStructure->totalSectors +=
+		    CD9660_BLOCKS(diskStructure->sectorSize,
+			diskStructure->susp_continuation_area_size);
+		cd9660_susp_finalize(diskStructure, diskStructure->rootNode);
+	}
+
+
+	cd9660_finalize_PVD(diskStructure);
+
+	/* Add padding sectors, just for testing purposes right now */
+	/* diskStructure->totalSectors+=150; */
+
+	/* Debugging output */
+	if (diskStructure->verbose_level > 0) {
+		printf("%s: Sectors 0-15 reserved\n", __func__);
+		printf("%s: Primary path tables starts in sector %"
+		    PRId64 "\n", __func__,
+		    diskStructure->primaryLittleEndianTableSector);
+		printf("%s: File data starts in sector %"
+		    PRId64 "\n", __func__, diskStructure->dataFirstSector);
+		printf("%s: Total sectors: %"
+		    PRId64 "\n", __func__, diskStructure->totalSectors);
+	}
+
+	/*
+	 * Add padding sectors at the end
+	 * TODO: Clean this up and separate padding
+	 */
+	if (diskStructure->include_padding_areas)
+		diskStructure->totalSectors += 150;
+
+	cd9660_write_image(diskStructure, image);
+
+	if (diskStructure->verbose_level > 1) {
+		debug_print_volume_descriptor_information(diskStructure);
+		debug_print_tree(diskStructure, real_root, 0);
+		debug_print_path_tree(real_root);
+	}
+
+	/* Clean up data structures */
+	cd9660_free_structure(real_root);
+
+	if (diskStructure->verbose_level > 0)
+		printf("%s: done\n", __func__);
+}
+
+/* Generic function pointer - implement later */
+typedef int (*cd9660node_func)(cd9660node *);
+
+static void
+cd9660_finalize_PVD(iso9660_disk *diskStructure)
+{
+	time_t tstamp = stampst.st_ino ? stampst.st_mtime : time(NULL);
+
+	/* root should be a fixed size of 34 bytes since it has no name */
+	memcpy(diskStructure->primaryDescriptor.root_directory_record,
+		diskStructure->rootNode->dot_record->isoDirRecord, 34);
+
+	/* In RRIP, this might be longer than 34 */
+	diskStructure->primaryDescriptor.root_directory_record[0] = 34;
+
+	/* Set up all the important numbers in the PVD */
+	cd9660_bothendian_dword(diskStructure->totalSectors,
+	    (unsigned char *)diskStructure->primaryDescriptor.volume_space_size);
+	cd9660_bothendian_word(1,
+	    (unsigned char *)diskStructure->primaryDescriptor.volume_set_size);
+	cd9660_bothendian_word(1,
+	    (unsigned char *)
+		diskStructure->primaryDescriptor.volume_sequence_number);
+	cd9660_bothendian_word(diskStructure->sectorSize,
+	    (unsigned char *)
+		diskStructure->primaryDescriptor.logical_block_size);
+	cd9660_bothendian_dword(diskStructure->pathTableLength,
+	    (unsigned char *)diskStructure->primaryDescriptor.path_table_size);
+
+	cd9660_731(diskStructure->primaryLittleEndianTableSector,
+		(u_char *)diskStructure->primaryDescriptor.type_l_path_table);
+	cd9660_732(diskStructure->primaryBigEndianTableSector,
+		(u_char *)diskStructure->primaryDescriptor.type_m_path_table);
+
+	diskStructure->primaryDescriptor.file_structure_version[0] = 1;
+
+	/* Pad all strings with spaces instead of nulls */
+	cd9660_pad_string_spaces(diskStructure->primaryDescriptor.volume_id, 32);
+	cd9660_pad_string_spaces(diskStructure->primaryDescriptor.system_id, 32);
+	cd9660_pad_string_spaces(diskStructure->primaryDescriptor.volume_set_id,
+	    128);
+	cd9660_pad_string_spaces(diskStructure->primaryDescriptor.publisher_id,
+	    128);
+	cd9660_pad_string_spaces(diskStructure->primaryDescriptor.preparer_id,
+	    128);
+	cd9660_pad_string_spaces(diskStructure->primaryDescriptor.application_id,
+	    128);
+	cd9660_pad_string_spaces(
+	    diskStructure->primaryDescriptor.copyright_file_id, 37);
+	cd9660_pad_string_spaces(
+		diskStructure->primaryDescriptor.abstract_file_id, 37);
+	cd9660_pad_string_spaces(
+		diskStructure->primaryDescriptor.bibliographic_file_id, 37);
+
+	/* Setup dates */
+	cd9660_time_8426(
+	    (unsigned char *)diskStructure->primaryDescriptor.creation_date,
+	    tstamp);
+	cd9660_time_8426(
+	    (unsigned char *)diskStructure->primaryDescriptor.modification_date,
+	    tstamp);
+
+#if 0
+	cd9660_set_date(diskStructure->primaryDescriptor.expiration_date,
+	    tstamp);
+#endif
+
+	memset(diskStructure->primaryDescriptor.expiration_date, '0', 16);
+	diskStructure->primaryDescriptor.expiration_date[16] = 0;
+
+	cd9660_time_8426(
+	    (unsigned char *)diskStructure->primaryDescriptor.effective_date,
+	    tstamp);
+	/* make this sane */
+	cd9660_time_915(diskStructure->rootNode->dot_record->isoDirRecord->date,
+	    tstamp);
+}
+
+static void
+cd9660_populate_iso_dir_record(struct _iso_directory_record_cd9660 *record,
+			       u_char ext_attr_length, u_char flags,
+			       u_char name_len, const char * name)
+{
+	record->ext_attr_length[0] = ext_attr_length;
+	record->flags[0] = ISO_FLAG_CLEAR | flags;
+	record->file_unit_size[0] = 0;
+	record->interleave[0] = 0;
+	cd9660_bothendian_word(1, record->volume_sequence_number);
+	record->name_len[0] = name_len;
+	memset(record->name, '\0', sizeof (record->name));
+	memcpy(record->name, name, name_len);
+	record->length[0] = 33 + name_len;
+
+	/* Todo : better rounding */
+	record->length[0] += (record->length[0] & 1) ? 1 : 0;
+}
+
+static void
+cd9660_setup_root_node(iso9660_disk *diskStructure)
+{
+	cd9660_populate_iso_dir_record(diskStructure->rootNode->isoDirRecord,
+	    0, ISO_FLAG_DIRECTORY, 1, "\0");
+
+}
+
+/*********** SUPPORT FUNCTIONS ***********/
+static int
+cd9660_setup_volume_descriptors(iso9660_disk *diskStructure)
+{
+	/* Boot volume descriptor should come second */
+	int sector = 16;
+	/* For now, a fixed 2 : PVD and terminator */
+	volume_descriptor *temp, *t;
+
+	/* Set up the PVD */
+	temp = emalloc(sizeof(*temp));
+	temp->volumeDescriptorData =
+	   (unsigned char *)&diskStructure->primaryDescriptor;
+	temp->volumeDescriptorData[0] = ISO_VOLUME_DESCRIPTOR_PVD;
+	temp->volumeDescriptorData[6] = 1;
+	temp->sector = sector;
+	memcpy(temp->volumeDescriptorData + 1,
+	    ISO_VOLUME_DESCRIPTOR_STANDARD_ID, 5);
+	diskStructure->firstVolumeDescriptor = temp;
+
+	sector++;
+	/* Set up boot support if enabled. BVD must reside in sector 17 */
+	if (diskStructure->is_bootable) {
+		t = emalloc(sizeof(*t));
+		t->volumeDescriptorData = ecalloc(1, 2048);
+		temp->next = t;
+		temp = t;
+		t->sector = 17;
+		if (diskStructure->verbose_level > 0)
+			printf("Setting up boot volume descriptor\n");
+		cd9660_setup_boot_volume_descriptor(diskStructure, t);
+		sector++;
+	}
+
+	/* Set up the terminator */
+	t = emalloc(sizeof(*t));
+	t->volumeDescriptorData = ecalloc(1, 2048);
+	temp->next = t;
+	t->volumeDescriptorData[0] = ISO_VOLUME_DESCRIPTOR_TERMINATOR;
+	t->next = NULL;
+	t->volumeDescriptorData[6] = 1;
+	t->sector = sector;
+	memcpy(t->volumeDescriptorData + 1,
+	    ISO_VOLUME_DESCRIPTOR_STANDARD_ID, 5);
+
+	sector++;
+	return sector;
+}
+
+#if 0
+/*
+ * Populate EAR at some point. Not required, but is used by NetBSD's
+ * cd9660 support
+ */
+static int
+cd9660_fill_extended_attribute_record(cd9660node *node)
+{
+	node->isoExtAttributes = emalloc(sizeof(*node->isoExtAttributes));
+	return 1;
+}
+#endif
+
+static int
+cd9660_translate_node_common(iso9660_disk *diskStructure, cd9660node *newnode)
+{
+	time_t tstamp = stampst.st_ino ? stampst.st_mtime : time(NULL);
+	u_char flag;
+	char temp[ISO_FILENAME_MAXLENGTH_WITH_PADDING];
+
+	/* Now populate the isoDirRecord structure */
+	memset(temp, 0, ISO_FILENAME_MAXLENGTH_WITH_PADDING);
+
+	(void)cd9660_convert_filename(diskStructure, newnode->node->name,
+	    temp, !(S_ISDIR(newnode->node->type)));
+
+	flag = ISO_FLAG_CLEAR;
+	if (S_ISDIR(newnode->node->type))
+		flag |= ISO_FLAG_DIRECTORY;
+
+	cd9660_populate_iso_dir_record(newnode->isoDirRecord, 0,
+	    flag, strlen(temp), temp);
+
+	/* Set the various dates */
+
+	/* If we want to use the current date and time */
+
+	cd9660_time_915(newnode->isoDirRecord->date, tstamp);
+
+	cd9660_bothendian_dword(newnode->fileDataLength,
+	    newnode->isoDirRecord->size);
+	/* If the file is a link, we want to set the size to 0 */
+	if (S_ISLNK(newnode->node->type))
+		newnode->fileDataLength = 0;
+
+	return 1;
+}
+
+/*
+ * Translate fsnode to cd9660node
+ * Translate filenames and other metadata, including dates, sizes,
+ * permissions, etc
+ * @param struct fsnode * The node generated by makefs
+ * @param struct cd9660node * The intermediate node to be written to
+ * @returns int 0 on failure, 1 on success
+ */
+static int
+cd9660_translate_node(iso9660_disk *diskStructure, fsnode *node,
+    cd9660node *newnode)
+{
+	if (node == NULL) {
+		if (diskStructure->verbose_level > 0)
+			printf("%s: NULL node passed, returning\n", __func__);
+		return 0;
+	}
+	newnode->isoDirRecord = emalloc(sizeof(*newnode->isoDirRecord));
+	/* Set the node pointer */
+	newnode->node = node;
+
+	/* Set the size */
+	if (!(S_ISDIR(node->type)))
+		newnode->fileDataLength = node->inode->st.st_size;
+
+	if (cd9660_translate_node_common(diskStructure, newnode) == 0)
+		return 0;
+
+	/* Finally, overwrite some of the values that are set by default */
+	cd9660_time_915(newnode->isoDirRecord->date,
+	    stampst.st_ino ? stampst.st_mtime : node->inode->st.st_mtime);
+
+	return 1;
+}
+
+/*
+ * Compares two ISO filenames
+ * @param const char * The first file name
+ * @param const char * The second file name
+ * @returns : -1 if first is less than second, 0 if they are the same, 1 if
+ * 	the second is greater than the first
+ */
+static int
+cd9660_compare_filename(const char *first, const char *second)
+{
+	/*
+	 * This can be made more optimal once it has been tested
+	 * (the extra character, for example, is for testing)
+	 */
+
+	int p1 = 0;
+	int p2 = 0;
+	char c1, c2;
+	/* First, on the filename */
+
+	while (p1 < ISO_FILENAME_MAXLENGTH_BEFORE_VERSION-1
+		&& p2 < ISO_FILENAME_MAXLENGTH_BEFORE_VERSION-1) {
+		c1 = first[p1];
+		c2 = second[p2];
+		if (c1 == '.' && c2 =='.')
+			break;
+		else if (c1 == '.') {
+			p2++;
+			c1 = ' ';
+		} else if (c2 == '.') {
+			p1++;
+			c2 = ' ';
+		} else {
+			p1++;
+			p2++;
+		}
+
+		if (c1 < c2)
+			return -1;
+		else if (c1 > c2) {
+			return 1;
+		}
+	}
+
+	if (first[p1] == '.' && second[p2] == '.') {
+		p1++;
+		p2++;
+		while (p1 < ISO_FILENAME_MAXLENGTH_BEFORE_VERSION - 1
+			&& p2 < ISO_FILENAME_MAXLENGTH_BEFORE_VERSION - 1) {
+			c1 = first[p1];
+			c2 = second[p2];
+			if (c1 == ';' && c2 == ';')
+				break;
+			else if (c1 == ';') {
+				p2++;
+				c1 = ' ';
+			} else if (c2 == ';') {
+				p1++;
+				c2 = ' ';
+			} else {
+				p1++;
+				p2++;
+			}
+
+			if (c1 < c2)
+				return -1;
+			else if (c1 > c2)
+				return 1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Insert a node into list with ISO sorting rules
+ * @param cd9660node * The head node of the list
+ * @param cd9660node * The node to be inserted
+ */
+static void
+cd9660_sorted_child_insert(cd9660node *parent, cd9660node *cn_new)
+{
+	int compare;
+	cd9660node *cn;
+	struct cd9660_children_head *head = &parent->cn_children;
+
+	/* TODO: Optimize? */
+	cn_new->parent = parent;
+
+	/*
+	 * first will either be 0, the . or the ..
+	 * if . or .., this means no other entry may be written before first
+	 * if 0, the new node may be inserted at the head
+	 */
+
+	TAILQ_FOREACH(cn, head, cn_next_child) {
+		/*
+		 * Dont insert a node twice -
+		 * that would cause an infinite loop
+		 */
+		if (cn_new == cn)
+			return;
+
+		compare = cd9660_compare_filename(cn_new->isoDirRecord->name,
+			cn->isoDirRecord->name);
+
+		if (compare == 0)
+			compare = cd9660_compare_filename(cn_new->node->name,
+				cn->node->name);
+
+		if (compare < 0)
+			break;
+	}
+	if (cn == NULL)
+		TAILQ_INSERT_TAIL(head, cn_new, cn_next_child);
+	else
+		TAILQ_INSERT_BEFORE(cn, cn_new, cn_next_child);
+}
+
+/*
+ * Called After cd9660_sorted_child_insert
+ * handles file collisions by suffixing each filname with ~n
+ * where n represents the files respective place in the ordering
+ */
+static int
+cd9660_handle_collisions(iso9660_disk *diskStructure, cd9660node *colliding,
+    int past)
+{
+	cd9660node *iter, *next, *prev;
+	int skip;
+	int delete_chars = 0;
+	int temp_past = past;
+	int temp_skip;
+	int flag = 0;
+	cd9660node *end_of_range;
+
+	for (iter = TAILQ_FIRST(&colliding->cn_children);
+	     iter != NULL && (next = TAILQ_NEXT(iter, cn_next_child)) != NULL;) {
+		if (strcmp(iter->isoDirRecord->name,
+		           next->isoDirRecord->name) != 0) {
+			iter = TAILQ_NEXT(iter, cn_next_child);
+			continue;
+		}
+		flag = 1;
+		temp_skip = skip = cd9660_count_collisions(iter);
+		end_of_range = iter;
+		while (temp_skip > 0) {
+			temp_skip--;
+			end_of_range = TAILQ_NEXT(end_of_range, cn_next_child);
+		}
+		temp_past = past;
+		while (temp_past > 0) {
+			if ((next = TAILQ_NEXT(end_of_range, cn_next_child)) != NULL)
+				end_of_range = next;
+			else if ((prev = TAILQ_PREV(iter, cd9660_children_head, cn_next_child)) != NULL)
+				iter = prev;
+			else
+				delete_chars++;
+			temp_past--;
+		}
+		skip += past;
+		iter = cd9660_rename_filename(diskStructure, iter, skip,
+		    delete_chars);
+	}
+	return flag;
+}
+
+
+static cd9660node *
+cd9660_rename_filename(iso9660_disk *diskStructure, cd9660node *iter, int num,
+    int delete_chars)
+{
+	int i = 0;
+	int numbts, digit, digits, temp, powers, count;
+	char *naming;
+	int maxlength;
+        char *tmp;
+
+	if (diskStructure->verbose_level > 0)
+		printf("Rename_filename called\n");
+
+	assert(1 <= diskStructure->isoLevel && diskStructure->isoLevel <= 2);
+	/* TODO : A LOT of chanes regarding 8.3 filenames */
+	if (diskStructure->isoLevel == 1)
+		maxlength = 8;
+	else if (diskStructure->isoLevel == 2)
+		maxlength = 31;
+	else
+		maxlength = ISO_FILENAME_MAXLENGTH_BEFORE_VERSION;
+
+	tmp = emalloc(ISO_FILENAME_MAXLENGTH_WITH_PADDING);
+
+	while (i < num && iter) {
+		powers = 1;
+		count = 0;
+		digits = 1;
+		while (((int)(i / powers) ) >= 10) {
+			digits++;
+			powers = powers * 10;
+		}
+
+		naming = iter->o_name;
+
+		/*
+		while ((*naming != '.') && (*naming != ';')) {
+			naming++;
+			count++;
+		}
+		*/
+
+		while (count < maxlength) {
+			if (*naming == ';')
+				break;
+			naming++;
+			count++;
+		}
+
+		if ((count + digits) < maxlength)
+			numbts = count;
+		else
+			numbts = maxlength - (digits);
+		numbts -= delete_chars;
+
+		/* 8.3 rules - keep the extension, add before the dot */
+
+		/*
+		 * This code makes a bunch of assumptions.
+		 * See if you can spot them all :)
+		 */
+
+#if 0
+		if (diskStructure->isoLevel == 1) {
+			numbts = 8 - digits - delete_chars;
+			if (dot < 0) {
+
+			} else {
+				if (dot < 8) {
+					memmove(&tmp[numbts],&tmp[dot],4);
+				}
+			}
+		}
+#endif
+
+		/* (copying just the filename before the '.' */
+		memcpy(tmp, (iter->o_name), numbts);
+
+		/* adding the appropriate number following the name */
+		temp = i;
+		while (digits > 0) {
+			digit = (int)(temp / powers);
+			temp = temp - digit * powers;
+			sprintf(&tmp[numbts] , "%d", digit);
+			digits--;
+			numbts++;
+			powers = powers / 10;
+		}
+
+		while ((*naming != ';')  && (numbts < maxlength)) {
+			tmp[numbts] = (*naming);
+			naming++;
+			numbts++;
+		}
+
+		tmp[numbts] = ';';
+		tmp[numbts+1] = '1';
+		tmp[numbts+2] = '\0';
+
+		/*
+		 * now tmp has exactly the identifier
+		 * we want so we'll copy it back to record
+		 */
+		memcpy((iter->isoDirRecord->name), tmp, numbts + 3);
+
+		iter = TAILQ_NEXT(iter, cn_next_child);
+		i++;
+	}
+
+	free(tmp);
+	return iter;
+}
+
+/* Todo: Figure out why these functions are nec. */
+static void
+cd9660_copy_filenames(iso9660_disk *diskStructure, cd9660node *node)
+{
+	cd9660node *cn;
+
+	if (TAILQ_EMPTY(&node->cn_children))
+		return;
+
+	if (TAILQ_FIRST(&node->cn_children)->isoDirRecord == NULL) {
+		debug_print_tree(diskStructure, diskStructure->rootNode, 0);
+		exit(1);
+	}
+
+	TAILQ_FOREACH(cn, &node->cn_children, cn_next_child) {
+		cd9660_copy_filenames(diskStructure, cn);
+		memcpy(cn->o_name, cn->isoDirRecord->name,
+		    ISO_FILENAME_MAXLENGTH_WITH_PADDING);
+	}
+}
+
+static void
+cd9660_sorting_nodes(cd9660node *node)
+{
+	cd9660node *cn;
+
+	TAILQ_FOREACH(cn, &node->cn_children, cn_next_child)
+		cd9660_sorting_nodes(cn);
+	cd9660_sort_nodes(node);
+}
+
+/* XXX Bubble sort. */
+static void
+cd9660_sort_nodes(cd9660node *node)
+{
+	cd9660node *cn, *next;
+
+	do {
+		TAILQ_FOREACH(cn, &node->cn_children, cn_next_child) {
+			if ((next = TAILQ_NEXT(cn, cn_next_child)) == NULL)
+				return;
+			else if (strcmp(next->isoDirRecord->name,
+				        cn->isoDirRecord->name) >= 0)
+				continue;
+			TAILQ_REMOVE(&node->cn_children, next, cn_next_child);
+			TAILQ_INSERT_BEFORE(cn, next, cn_next_child);
+			break;
+		}
+	} while (cn != NULL);
+}
+
+static int
+cd9660_count_collisions(cd9660node *copy)
+{
+	int count = 0;
+	cd9660node *iter, *next;
+
+	for (iter = copy;
+	     (next = TAILQ_NEXT(iter, cn_next_child)) != NULL;
+	     iter = next) {
+		if (cd9660_compare_filename(iter->isoDirRecord->name,
+			next->isoDirRecord->name) == 0)
+			count++;
+		else
+			return count;
+	}
+#if 0
+	if ((next = TAILQ_NEXT(iter, cn_next_child)) != NULL) {
+		printf("%s: count is %i\n", __func__, count);
+		compare = cd9660_compare_filename(iter->isoDirRecord->name,
+			next->isoDirRecord->name);
+		if (compare == 0) {
+			count++;
+			return cd9660_recurse_on_collision(next, count);
+		} else
+			return count;
+	}
+#endif
+	return count;
+}
+
+static cd9660node *
+cd9660_rrip_move_directory(iso9660_disk *diskStructure, cd9660node *dir)
+{
+	char newname[9];
+	cd9660node *tfile;
+
+	/*
+	 * This function needs to:
+	 * 1) Create an empty virtual file in place of the old directory
+	 * 2) Point the virtual file to the new directory
+	 * 3) Point the relocated directory to its old parent
+	 * 4) Move the directory specified by dir into rr_moved_dir,
+	 * and rename it to "diskStructure->rock_ridge_move_count" (as a string)
+	 */
+
+	/* First see if the moved directory even exists */
+	if (diskStructure->rr_moved_dir == NULL) {
+		diskStructure->rr_moved_dir = cd9660_create_directory(
+		    diskStructure, ISO_RRIP_DEFAULT_MOVE_DIR_NAME,
+		    diskStructure->rootNode, dir);
+		if (diskStructure->rr_moved_dir == NULL)
+			return 0;
+		cd9660_time_915(diskStructure->rr_moved_dir->isoDirRecord->date,
+		    stampst.st_ino ? stampst.st_mtime : start_time.tv_sec);
+	}
+
+	/* Create a file with the same ORIGINAL name */
+	tfile = cd9660_create_file(diskStructure, dir->node->name, dir->parent,
+	    dir);
+	if (tfile == NULL)
+		return NULL;
+
+	diskStructure->rock_ridge_move_count++;
+	snprintf(newname, sizeof(newname), "%08i",
+	    diskStructure->rock_ridge_move_count);
+
+	/* Point to old parent */
+	dir->rr_real_parent = dir->parent;
+
+	/* Place the placeholder file */
+	if (TAILQ_EMPTY(&dir->rr_real_parent->cn_children)) {
+		TAILQ_INSERT_HEAD(&dir->rr_real_parent->cn_children, tfile,
+		    cn_next_child);
+	} else {
+		cd9660_sorted_child_insert(dir->rr_real_parent, tfile);
+	}
+
+	/* Point to new parent */
+	dir->parent = diskStructure->rr_moved_dir;
+
+	/* Point the file to the moved directory */
+	tfile->rr_relocated = dir;
+
+	/* Actually move the directory */
+	cd9660_sorted_child_insert(diskStructure->rr_moved_dir, dir);
+
+	/* TODO: Inherit permissions / ownership (basically the entire inode) */
+
+	/* Set the new name */
+	memset(dir->isoDirRecord->name, 0, ISO_FILENAME_MAXLENGTH_WITH_PADDING);
+	strncpy(dir->isoDirRecord->name, newname, 8);
+	dir->isoDirRecord->length[0] = 34 + 8;
+	dir->isoDirRecord->name_len[0] = 8;
+
+	return dir;
+}
+
+static int
+cd9660_add_dot_records(iso9660_disk *diskStructure, cd9660node *root)
+{
+	struct cd9660_children_head *head = &root->cn_children;
+	cd9660node *cn;
+
+	TAILQ_FOREACH(cn, head, cn_next_child) {
+		if ((cn->type & CD9660_TYPE_DIR) == 0)
+			continue;
+		/* Recursion first */
+		cd9660_add_dot_records(diskStructure, cn);
+	}
+	cd9660_create_special_directory(diskStructure, CD9660_TYPE_DOT, root);
+	cd9660_create_special_directory(diskStructure, CD9660_TYPE_DOTDOT,
+	    root);
+	return 1;
+}
+
+/*
+ * Convert node to cd9660 structure
+ * This function is designed to be called recursively on the root node of
+ * the filesystem
+ * Lots of recursion going on here, want to make sure it is efficient
+ * @param struct fsnode * The root node to be converted
+ * @param struct cd9660* The parent node (should not be NULL)
+ * @param int Current directory depth
+ * @param int* Running count of the number of directories that are being created
+ */
+static void
+cd9660_convert_structure(iso9660_disk *diskStructure, fsnode *root,
+    cd9660node *parent_node, int level, int *numDirectories, int *error)
+{
+	fsnode *iterator = root;
+	cd9660node *this_node;
+	int working_level;
+	int add;
+	int flag = 0;
+	int counter = 0;
+
+	/*
+	 * Newer, more efficient method, reduces recursion depth
+	 */
+	if (root == NULL) {
+		warnx("%s: root is null", __func__);
+		return;
+	}
+
+	/* Test for an empty directory - makefs still gives us the . record */
+	if ((S_ISDIR(root->type)) && (root->name[0] == '.')
+		&& (root->name[1] == '\0')) {
+		root = root->next;
+		if (root == NULL)
+			return;
+	}
+	if ((this_node = cd9660_allocate_cd9660node()) == NULL) {
+		CD9660_MEM_ALLOC_ERROR(__func__);
+	}
+
+	/*
+	 * To reduce the number of recursive calls, we will iterate over
+	 * the next pointers to the right.
+	 */
+	while (iterator != NULL) {
+		add = 1;
+		/*
+		 * Increment the directory count if this is a directory
+		 * Ignore "." entries. We will generate them later
+		 */
+		if (!S_ISDIR(iterator->type) ||
+		    strcmp(iterator->name, ".") != 0) {
+
+			/* Translate the node, including its filename */
+			this_node->parent = parent_node;
+			cd9660_translate_node(diskStructure, iterator,
+			    this_node);
+			this_node->level = level;
+
+			if (S_ISDIR(iterator->type)) {
+				(*numDirectories)++;
+				this_node->type = CD9660_TYPE_DIR;
+				working_level = level + 1;
+
+				/*
+				 * If at level 8, directory would be at 8
+				 * and have children at 9 which is not
+				 * allowed as per ISO spec
+				 */
+				if (level == 8) {
+					if ((!diskStructure->allow_deep_trees) &&
+					  (!diskStructure->rock_ridge_enabled)) {
+						warnx("error: found entry "
+						     "with depth greater "
+						     "than 8.");
+						(*error) = 1;
+						return;
+					} else if (diskStructure->
+						   rock_ridge_enabled) {
+						working_level = 3;
+						/*
+						 * Moved directory is actually
+						 * at level 2.
+						 */
+						this_node->level =
+						    working_level - 1;
+						if (cd9660_rrip_move_directory(
+							diskStructure,
+							this_node) == NULL) {
+							warnx("Failure in "
+							      "cd9660_rrip_"
+							      "move_directory"
+							);
+							(*error) = 1;
+							return;
+						}
+						add = 0;
+					}
+				}
+
+				/* Do the recursive call on the children */
+				if (iterator->child != NULL) {
+					cd9660_convert_structure(diskStructure,
+						iterator->child, this_node,
+						working_level,
+						numDirectories, error);
+
+					if ((*error) == 1) {
+						warnx("%s: Error on recursive "
+						    "call", __func__);
+						return;
+					}
+				}
+
+			} else {
+				/* Only directories should have children */
+				assert(iterator->child == NULL);
+
+				this_node->type = CD9660_TYPE_FILE;
+			}
+
+			/*
+			 * Finally, do a sorted insert
+			 */
+			if (add) {
+				cd9660_sorted_child_insert(
+				    parent_node, this_node);
+			}
+
+			/*Allocate new temp_node */
+			if (iterator->next != NULL) {
+				this_node = cd9660_allocate_cd9660node();
+				if (this_node == NULL)
+					CD9660_MEM_ALLOC_ERROR(__func__);
+			}
+		}
+		iterator = iterator->next;
+	}
+
+	/* cd9660_handle_collisions(first_node); */
+
+	/* TODO: need cleanup */
+	cd9660_copy_filenames(diskStructure, parent_node);
+
+	do {
+		flag = cd9660_handle_collisions(diskStructure, parent_node,
+		    counter);
+		counter++;
+		cd9660_sorting_nodes(parent_node);
+	} while ((flag == 1) && (counter < 100));
+}
+
+/*
+ * Clean up the cd9660node tree
+ * This is designed to be called recursively on the root node
+ * @param struct cd9660node *root The node to free
+ * @returns void
+ */
+static void
+cd9660_free_structure(cd9660node *root)
+{
+	cd9660node *cn;
+
+	while ((cn = TAILQ_FIRST(&root->cn_children)) != NULL) {
+		TAILQ_REMOVE(&root->cn_children, cn, cn_next_child);
+		cd9660_free_structure(cn);
+	}
+	free(root);
+}
+
+/*
+ * Be a little more memory conservative:
+ * instead of having the TAILQ_ENTRY as part of the cd9660node,
+ * just create a temporary structure
+ */
+static struct ptq_entry
+{
+	TAILQ_ENTRY(ptq_entry) ptq;
+	cd9660node *node;
+} *n;
+
+#define PTQUEUE_NEW(n,s,r,t){\
+	n = emalloc(sizeof(struct s));	\
+	n->node = t;\
+}
+
+/*
+ * Generate the path tables
+ * The specific implementation of this function is left as an exercise to the
+ * programmer. It could be done recursively. Make sure you read how the path
+ * table has to be laid out, it has levels.
+ * @param struct iso9660_disk *disk The disk image
+ * @returns int The number of built path tables (between 1 and 4), 0 on failure
+ */
+static int
+cd9660_generate_path_table(iso9660_disk *diskStructure)
+{
+	cd9660node *cn, *dirNode = diskStructure->rootNode;
+	cd9660node *last = dirNode;
+	int pathTableSize = 0;	/* computed as we go */
+	int counter = 1;	/* root gets a count of 0 */
+
+	TAILQ_HEAD(cd9660_pt_head, ptq_entry) pt_head;
+	TAILQ_INIT(&pt_head);
+
+	PTQUEUE_NEW(n, ptq_entry, -1, diskStructure->rootNode);
+
+	/* Push the root node */
+	TAILQ_INSERT_HEAD(&pt_head, n, ptq);
+
+	/* Breadth-first traversal of file structure */
+	while (pt_head.tqh_first != 0) {
+		n = pt_head.tqh_first;
+		dirNode = n->node;
+		TAILQ_REMOVE(&pt_head, pt_head.tqh_first, ptq);
+		free(n);
+
+		/* Update the size */
+		pathTableSize += ISO_PATHTABLE_ENTRY_BASESIZE
+		    + dirNode->isoDirRecord->name_len[0]+
+			(dirNode->isoDirRecord->name_len[0] % 2 == 0 ? 0 : 1);
+			/* includes the padding bit */
+
+		dirNode->ptnumber=counter;
+		if (dirNode != last) {
+			last->ptnext = dirNode;
+			dirNode->ptprev = last;
+		}
+		last = dirNode;
+
+		/* Push children onto queue */
+		TAILQ_FOREACH(cn, &dirNode->cn_children, cn_next_child) {
+			/*
+			 * Dont add the DOT and DOTDOT types to the path
+			 * table.
+			 */
+			if ((cn->type != CD9660_TYPE_DOT)
+				&& (cn->type != CD9660_TYPE_DOTDOT)) {
+
+				if (S_ISDIR(cn->node->type)) {
+					PTQUEUE_NEW(n, ptq_entry, -1, cn);
+					TAILQ_INSERT_TAIL(&pt_head, n, ptq);
+				}
+			}
+		}
+		counter++;
+	}
+	return pathTableSize;
+}
+
+void
+cd9660_compute_full_filename(cd9660node *node, char *buf)
+{
+	int len;
+
+	len = CD9660MAXPATH + 1;
+	len = snprintf(buf, len, "%s/%s/%s", node->node->root,
+	    node->node->path, node->node->name);
+	if (len > CD9660MAXPATH)
+		errx(EXIT_FAILURE, "Pathname too long.");
+}
+
+/* NEW filename conversion method */
+typedef int(*cd9660_filename_conversion_functor)(iso9660_disk *, const char *,
+    char *, int);
+
+
+/*
+ * TODO: These two functions are almost identical.
+ * Some code cleanup is possible here
+ *
+ * XXX bounds checking!
+ */
+static int
+cd9660_level1_convert_filename(iso9660_disk *diskStructure, const char *oldname,
+    char *newname, int is_file)
+{
+	/*
+	 * ISO 9660 : 10.1
+	 * File Name shall not contain more than 8 d or d1 characters
+	 * File Name Extension shall not contain more than 3 d or d1 characters
+	 * Directory Identifier shall not contain more than 8 d or d1 characters
+	 */
+	int namelen = 0;
+	int extlen = 0;
+	int found_ext = 0;
+
+	while (*oldname != '\0' && extlen < 3) {
+		/* Handle period first, as it is special */
+		if (*oldname == '.') {
+			if (found_ext) {
+				*newname++ = '_';
+				extlen ++;
+			}
+			else {
+				*newname++ = '.';
+				found_ext = 1;
+			}
+		} else {
+			/* cut RISC OS file type off ISO name */
+			if (diskStructure->archimedes_enabled &&
+			    *oldname == ',' && strlen(oldname) == 4)
+				break;
+
+			/* Enforce 12.3 / 8 */
+			if (namelen == 8 && !found_ext)
+				break;
+
+			if (islower((unsigned char)*oldname))
+				*newname++ = toupper((unsigned char)*oldname);
+			else if (isupper((unsigned char)*oldname)
+			    || isdigit((unsigned char)*oldname))
+				*newname++ = *oldname;
+			else
+				*newname++ = '_';
+
+			if (found_ext)
+				extlen++;
+			else
+				namelen++;
+		}
+		oldname++;
+	}
+	if (is_file) {
+		if (!found_ext && !diskStructure->omit_trailing_period)
+			*newname++ = '.';
+		/* Add version */
+		sprintf(newname, ";%i", 1);
+	}
+	return namelen + extlen + found_ext;
+}
+
+/* XXX bounds checking! */
+static int
+cd9660_level2_convert_filename(iso9660_disk *diskStructure, const char *oldname,
+    char *newname, int is_file)
+{
+	/*
+	 * ISO 9660 : 7.5.1
+	 * File name : 0+ d or d1 characters
+	 * separator 1 (.)
+	 * File name extension : 0+ d or d1 characters
+	 * separator 2 (;)
+	 * File version number (5 characters, 1-32767)
+	 * 1 <= Sum of File name and File name extension <= 30
+	 */
+	int namelen = 0;
+	int extlen = 0;
+	int found_ext = 0;
+
+	while (*oldname != '\0' && namelen + extlen < 30) {
+		/* Handle period first, as it is special */
+		if (*oldname == '.') {
+			if (found_ext) {
+				if (diskStructure->allow_multidot) {
+					*newname++ = '.';
+				} else {
+					*newname++ = '_';
+				}
+				extlen ++;
+			}
+			else {
+				*newname++ = '.';
+				found_ext = 1;
+			}
+		} else {
+			/* cut RISC OS file type off ISO name */
+			if (diskStructure->archimedes_enabled &&
+			    *oldname == ',' && strlen(oldname) == 4)
+				break;
+
+			 if (islower((unsigned char)*oldname))
+				*newname++ = toupper((unsigned char)*oldname);
+			else if (isupper((unsigned char)*oldname) ||
+			    isdigit((unsigned char)*oldname))
+				*newname++ = *oldname;
+			else if (diskStructure->allow_multidot &&
+			    *oldname == '.') {
+			    	*newname++ = '.';
+			} else {
+				*newname++ = '_';
+			}
+
+			if (found_ext)
+				extlen++;
+			else
+				namelen++;
+		}
+		oldname ++;
+	}
+	if (is_file) {
+		if (!found_ext && !diskStructure->omit_trailing_period)
+			*newname++ = '.';
+		/* Add version */
+		sprintf(newname, ";%i", 1);
+	}
+	return namelen + extlen + found_ext;
+}
+
+#if 0
+static int
+cd9660_joliet_convert_filename(iso9660_disk *diskStructure, const char *oldname,
+    char *newname, int is_file)
+{
+	/* TODO: implement later, move to cd9660_joliet.c ?? */
+}
+#endif
+
+
+/*
+ * Convert a file name to ISO compliant file name
+ * @param char * oldname The original filename
+ * @param char ** newname The new file name, in the appropriate character
+ *                        set and of appropriate length
+ * @param int 1 if file, 0 if directory
+ * @returns int The length of the new string
+ */
+static int
+cd9660_convert_filename(iso9660_disk *diskStructure, const char *oldname,
+    char *newname, int is_file)
+{
+	assert(1 <= diskStructure->isoLevel && diskStructure->isoLevel <= 2);
+	/* NEW */
+	cd9660_filename_conversion_functor conversion_function = NULL;
+	if (diskStructure->isoLevel == 1)
+		conversion_function = &cd9660_level1_convert_filename;
+	else if (diskStructure->isoLevel == 2)
+		conversion_function = &cd9660_level2_convert_filename;
+	return (*conversion_function)(diskStructure, oldname, newname, is_file);
+}
+
+int
+cd9660_compute_record_size(iso9660_disk *diskStructure, cd9660node *node)
+{
+	int size = node->isoDirRecord->length[0];
+
+	if (diskStructure->rock_ridge_enabled)
+		size += node->susp_entry_size;
+	size += node->su_tail_size;
+	size += size & 1; /* Ensure length of record is even. */
+	assert(size <= 254);
+	return size;
+}
+
+static void
+cd9660_populate_dot_records(iso9660_disk *diskStructure, cd9660node *node)
+{
+	node->dot_record->fileDataSector = node->fileDataSector;
+	memcpy(node->dot_record->isoDirRecord,node->isoDirRecord, 34);
+	node->dot_record->isoDirRecord->name_len[0] = 1;
+	node->dot_record->isoDirRecord->name[0] = 0;
+	node->dot_record->isoDirRecord->name[1] = 0;
+	node->dot_record->isoDirRecord->length[0] = 34;
+	node->dot_record->fileRecordSize =
+	    cd9660_compute_record_size(diskStructure, node->dot_record);
+
+	if (node == diskStructure->rootNode) {
+		node->dot_dot_record->fileDataSector = node->fileDataSector;
+		memcpy(node->dot_dot_record->isoDirRecord,node->isoDirRecord,
+		    34);
+	} else {
+		node->dot_dot_record->fileDataSector =
+		    node->parent->fileDataSector;
+		memcpy(node->dot_dot_record->isoDirRecord,
+		    node->parent->isoDirRecord,34);
+	}
+	node->dot_dot_record->isoDirRecord->name_len[0] = 1;
+	node->dot_dot_record->isoDirRecord->name[0] = 1;
+	node->dot_dot_record->isoDirRecord->name[1] = 0;
+	node->dot_dot_record->isoDirRecord->length[0] = 34;
+	node->dot_dot_record->fileRecordSize =
+	    cd9660_compute_record_size(diskStructure, node->dot_dot_record);
+}
+
+/*
+ * @param struct cd9660node *node The node
+ * @param int The offset (in bytes) - SHOULD align to the beginning of a sector
+ * @returns int The total size of files and directory entries (should be
+ *              a multiple of sector size)
+*/
+static int64_t
+cd9660_compute_offsets(iso9660_disk *diskStructure, cd9660node *node,
+    int64_t startOffset)
+{
+	/*
+	 * This function needs to compute the size of directory records and
+	 * runs, file lengths, and set the appropriate variables both in
+	 * cd9660node and isoDirEntry
+	 */
+	int64_t used_bytes = 0;
+	int64_t current_sector_usage = 0;
+	cd9660node *child;
+	fsinode *inode;
+	int64_t r;
+
+	assert(node != NULL);
+
+
+	/*
+	 * NOTE : There needs to be some special case detection for
+	 * the "real root" node, since for it, node->node is undefined
+	 */
+
+	node->fileDataSector = -1;
+
+	if (node->type & CD9660_TYPE_DIR) {
+		node->fileRecordSize = cd9660_compute_record_size(
+		    diskStructure, node);
+		/*Set what sector this directory starts in*/
+		node->fileDataSector =
+		    CD9660_BLOCKS(diskStructure->sectorSize,startOffset);
+
+		cd9660_bothendian_dword(node->fileDataSector,
+		    node->isoDirRecord->extent);
+
+		/*
+		 * First loop over children, need to know the size of
+		 * their directory records
+		 */
+		node->fileSectorsUsed = 1;
+		TAILQ_FOREACH(child, &node->cn_children, cn_next_child) {
+			node->fileDataLength +=
+			    cd9660_compute_record_size(diskStructure, child);
+			if ((cd9660_compute_record_size(diskStructure, child) +
+			    current_sector_usage) >=
+		 	    diskStructure->sectorSize) {
+				current_sector_usage = 0;
+				node->fileSectorsUsed++;
+			}
+
+			current_sector_usage +=
+			    cd9660_compute_record_size(diskStructure, child);
+		}
+
+		cd9660_bothendian_dword(node->fileSectorsUsed *
+			diskStructure->sectorSize,node->isoDirRecord->size);
+
+		/*
+		 * This should point to the sector after the directory
+		 * record (or, the first byte in that sector)
+		 */
+		used_bytes += node->fileSectorsUsed * diskStructure->sectorSize;
+
+		for (child = TAILQ_NEXT(node->dot_dot_record, cn_next_child);
+		     child != NULL; child = TAILQ_NEXT(child, cn_next_child)) {
+			/* Directories need recursive call */
+			if (S_ISDIR(child->node->type)) {
+				r = cd9660_compute_offsets(diskStructure, child,
+				    used_bytes + startOffset);
+
+				if (r != -1)
+					used_bytes += r;
+				else
+					return -1;
+			}
+		}
+
+		/* Explicitly set the . and .. records */
+		cd9660_populate_dot_records(diskStructure, node);
+
+		/* Finally, do another iteration to write the file data*/
+		for (child = TAILQ_NEXT(node->dot_dot_record, cn_next_child);
+		     child != NULL;
+		     child = TAILQ_NEXT(child, cn_next_child)) {
+			/* Files need extent set */
+			if (S_ISDIR(child->node->type))
+				continue;
+			child->fileRecordSize =
+			    cd9660_compute_record_size(diskStructure, child);
+
+			child->fileSectorsUsed =
+			    CD9660_BLOCKS(diskStructure->sectorSize,
+				child->fileDataLength);
+
+			inode = child->node->inode;
+			if ((inode->flags & FI_ALLOCATED) == 0) {
+				inode->ino =
+				    CD9660_BLOCKS(diskStructure->sectorSize,
+				        used_bytes + startOffset);
+				inode->flags |= FI_ALLOCATED;
+				used_bytes += child->fileSectorsUsed *
+				    diskStructure->sectorSize;
+			} else {
+				INODE_WARNX(("%s: already allocated inode %d "
+				      "data sectors at %" PRIu32, __func__,
+				      (int)inode->st.st_ino, inode->ino));
+			}
+			child->fileDataSector = inode->ino;
+			cd9660_bothendian_dword(child->fileDataSector,
+				child->isoDirRecord->extent);
+		}
+	}
+
+	return used_bytes;
+}
+
+#if 0
+/* Might get rid of this func */
+static int
+cd9660_copy_stat_info(cd9660node *from, cd9660node *to, int file)
+{
+	to->node->inode->st.st_dev = 0;
+	to->node->inode->st.st_ino = 0;
+	to->node->inode->st.st_size = 0;
+	to->node->inode->st.st_blksize = from->node->inode->st.st_blksize;
+	to->node->inode->st.st_atime = from->node->inode->st.st_atime;
+	to->node->inode->st.st_mtime = from->node->inode->st.st_mtime;
+	to->node->inode->st.st_ctime = from->node->inode->st.st_ctime;
+	to->node->inode->st.st_uid = from->node->inode->st.st_uid;
+	to->node->inode->st.st_gid = from->node->inode->st.st_gid;
+	to->node->inode->st.st_mode = from->node->inode->st.st_mode;
+	/* Clear out type */
+	to->node->inode->st.st_mode = to->node->inode->st.st_mode & ~(S_IFMT);
+	if (file)
+		to->node->inode->st.st_mode |= S_IFREG;
+	else
+		to->node->inode->st.st_mode |= S_IFDIR;
+	return 1;
+}
+#endif
+
+static cd9660node *
+cd9660_create_virtual_entry(iso9660_disk *diskStructure, const char *name,
+    cd9660node *parent, int file, int insert)
+{
+	cd9660node *temp;
+	fsnode * tfsnode;
+
+	assert(parent != NULL);
+
+	temp = cd9660_allocate_cd9660node();
+	if (temp == NULL)
+		return NULL;
+
+	tfsnode = emalloc(sizeof(*tfsnode));
+	tfsnode->name = estrdup(name);
+	temp->isoDirRecord = emalloc(sizeof(*temp->isoDirRecord));
+
+	cd9660_convert_filename(diskStructure, tfsnode->name,
+	    temp->isoDirRecord->name, file);
+
+	temp->node = tfsnode;
+	temp->parent = parent;
+
+	if (insert) {
+		if (temp->parent != NULL) {
+			temp->level = temp->parent->level + 1;
+			if (!TAILQ_EMPTY(&temp->parent->cn_children))
+				cd9660_sorted_child_insert(temp->parent, temp);
+			else
+				TAILQ_INSERT_HEAD(&temp->parent->cn_children,
+				    temp, cn_next_child);
+		}
+	}
+
+	if (parent->node != NULL) {
+		tfsnode->type = parent->node->type;
+	}
+
+	/* Clear out file type bits */
+	tfsnode->type &= ~(S_IFMT);
+	if (file)
+		tfsnode->type |= S_IFREG;
+	else
+		tfsnode->type |= S_IFDIR;
+
+	/* Indicate that there is no spec entry (inode) */
+	tfsnode->flags &= ~(FSNODE_F_HASSPEC);
+#if 0
+	cd9660_copy_stat_info(parent, temp, file);
+#endif
+	return temp;
+}
+
+static cd9660node *
+cd9660_create_file(iso9660_disk *diskStructure, const char *name,
+    cd9660node *parent, cd9660node *me)
+{
+	cd9660node *temp;
+
+	temp = cd9660_create_virtual_entry(diskStructure, name, parent, 1, 1);
+	if (temp == NULL)
+		return NULL;
+
+	temp->fileDataLength = 0;
+
+	temp->type = CD9660_TYPE_FILE | CD9660_TYPE_VIRTUAL;
+
+	temp->node->inode = ecalloc(1, sizeof(*temp->node->inode));
+	*temp->node->inode = *me->node->inode;
+
+	if (cd9660_translate_node_common(diskStructure, temp) == 0)
+		return NULL;
+	return temp;
+}
+
+/*
+ * Create a new directory which does not exist on disk
+ * @param const char * name The name to assign to the directory
+ * @param const char * parent Pointer to the parent directory
+ * @returns cd9660node * Pointer to the new directory
+ */
+static cd9660node *
+cd9660_create_directory(iso9660_disk *diskStructure, const char *name,
+    cd9660node *parent, cd9660node *me)
+{
+	cd9660node *temp;
+
+	temp = cd9660_create_virtual_entry(diskStructure, name, parent, 0, 1);
+	if (temp == NULL)
+		return NULL;
+	temp->node->type |= S_IFDIR;
+
+	temp->type = CD9660_TYPE_DIR | CD9660_TYPE_VIRTUAL;
+
+	temp->node->inode = ecalloc(1, sizeof(*temp->node->inode));
+	*temp->node->inode = *me->node->inode;
+
+	if (cd9660_translate_node_common(diskStructure, temp) == 0)
+		return NULL;
+	return temp;
+}
+
+static cd9660node *
+cd9660_create_special_directory(iso9660_disk *diskStructure, u_char type,
+    cd9660node *parent)
+{
+	cd9660node *temp, *first;
+	char na[2];
+
+	assert(parent != NULL);
+
+	if (type == CD9660_TYPE_DOT)
+		na[0] = 0;
+	else if (type == CD9660_TYPE_DOTDOT)
+		na[0] = 1;
+	else
+		return 0;
+
+	na[1] = 0;
+	if ((temp = cd9660_create_virtual_entry(diskStructure, na, parent,
+	    0, 0)) == NULL)
+		return NULL;
+
+	temp->parent = parent;
+	temp->type = type;
+	temp->isoDirRecord->length[0] = 34;
+	/* Dot record is always first */
+	if (type == CD9660_TYPE_DOT) {
+		parent->dot_record = temp;
+		TAILQ_INSERT_HEAD(&parent->cn_children, temp, cn_next_child);
+	/* DotDot should be second */
+	} else if (type == CD9660_TYPE_DOTDOT) {
+		parent->dot_dot_record = temp;
+		/*
+                 * If the first child is the dot record, insert
+                 * this second.  Otherwise, insert it at the head.
+		 */
+		if ((first = TAILQ_FIRST(&parent->cn_children)) == NULL ||
+		    (first->type & CD9660_TYPE_DOT) == 0) {
+			TAILQ_INSERT_HEAD(&parent->cn_children, temp,
+			    cn_next_child);
+		} else {
+			TAILQ_INSERT_AFTER(&parent->cn_children, first, temp,
+			    cn_next_child);
+		}
+	}
+
+	return temp;
+}
+
+static int
+cd9660_add_generic_bootimage(iso9660_disk *diskStructure, const char *bootimage)
+{
+	struct stat stbuf;
+
+	assert(bootimage != NULL);
+
+	if (*bootimage == '\0') {
+		warnx("Error: Boot image must be a filename");
+		return 0;
+	}
+
+	diskStructure->generic_bootimage = estrdup(bootimage);
+
+	/* Get information about the file */
+	if (lstat(diskStructure->generic_bootimage, &stbuf) == -1)
+		err(EXIT_FAILURE, "%s: lstat(\"%s\")", __func__,
+		    diskStructure->generic_bootimage);
+
+	if (stbuf.st_size > 32768) {
+		warnx("Error: Boot image must be no greater than 32768 bytes");
+		return 0;
+	}
+
+	if (diskStructure->verbose_level > 0) {
+		printf("Generic boot image has size %lld\n",
+		    (long long)stbuf.st_size);
+	}
+
+	diskStructure->has_generic_bootimage = 1;
+
+	return 1;
+}
diff --git a/usr.sbin/makefs/cd9660.h b/usr.sbin/makefs/cd9660.h
new file mode 100644
index 0000000000..59edb7e9a7
--- /dev/null
+++ b/usr.sbin/makefs/cd9660.h
@@ -0,0 +1,358 @@
+/*	$NetBSD: cd9660.h,v 1.17 2011/06/23 02:35:56 enami Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-NetBSD
+ *
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam.  All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM 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.
+ *
+ * $FreeBSD: head/usr.sbin/makefs/cd9660.h 326276 2017-11-27 15:37:16Z pfg $
+ */
+
+#ifndef _MAKEFS_CD9660_H
+#define _MAKEFS_CD9660_H
+
+#include <inttypes.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <limits.h>
+#include <sys/queue.h>
+#include <sys/param.h>
+#include <sys/endian.h>
+
+#include "makefs.h"
+#include "iso.h"
+#include "iso_rrip.h"
+#include "cd9660/cd9660_eltorito.h"
+
+#ifdef DEBUG
+#define	INODE_WARNX(__x)	warnx __x
+#else /* DEBUG */
+#define	INODE_WARNX(__x)
+#endif /* DEBUG */
+
+#define CD9660MAXPATH 4096
+
+#define ISO_STRING_FILTER_NONE = 0x00
+#define ISO_STRING_FILTER_DCHARS = 0x01
+#define ISO_STRING_FILTER_ACHARS = 0x02
+
+/*
+Extended preferences type, in the spirit of what makefs gives us (only ints)
+*/
+typedef struct {
+	const char  *shortName;		/* Short option */
+	const char	*name;		/* option name */
+	char		*value;		/* where to stuff the value */
+	int		minLength;	/* minimum for value */
+	int		maxLength;	/* maximum for value */
+	const char	*desc;		/* option description */
+	int		filterFlags;
+} string_option_t;
+
+/******** STRUCTURES **********/
+
+/*Defaults*/
+#define ISO_DEFAULT_VOLUMEID "MAKEFS_CD9660_IMAGE"
+#define ISO_DEFAULT_APPID "MAKEFS"
+#define ISO_DEFAULT_PUBLISHER "MAKEFS"
+#define ISO_DEFAULT_PREPARER "MAKEFS"
+
+#define ISO_VOLUME_DESCRIPTOR_STANDARD_ID "CD001"
+#define ISO_VOLUME_DESCRIPTOR_BOOT 0
+#define ISO_VOLUME_DESCRIPTOR_PVD 1
+#define ISO_VOLUME_DESCRIPTOR_TERMINATOR 255
+
+/*30 for name and extension, as well as version number and padding bit*/
+#define ISO_FILENAME_MAXLENGTH_BEFORE_VERSION 30
+#define ISO_FILENAME_MAXLENGTH	36
+#define ISO_FILENAME_MAXLENGTH_WITH_PADDING 37
+
+#define ISO_FLAG_CLEAR 0x00
+#define ISO_FLAG_HIDDEN 0x01
+#define ISO_FLAG_DIRECTORY 0x02
+#define ISO_FLAG_ASSOCIATED 0x04
+#define ISO_FLAG_PERMISSIONS 0x08
+#define ISO_FLAG_RESERVED5 0x10
+#define ISO_FLAG_RESERVED6 0x20
+#define ISO_FLAG_FINAL_RECORD 0x40
+
+#define ISO_PATHTABLE_ENTRY_BASESIZE 8
+
+#define ISO_RRIP_DEFAULT_MOVE_DIR_NAME "RR_MOVED"
+#define RRIP_DEFAULT_MOVE_DIR_NAME ".rr_moved"
+
+#define	CD9660_BLOCKS(__sector_size, __bytes)	\
+	howmany((__bytes), (__sector_size))
+
+#define CD9660_MEM_ALLOC_ERROR(_F)	\
+    err(EXIT_FAILURE, "%s, %s l. %d", _F, __FILE__, __LINE__)
+
+#define CD9660_TYPE_FILE	0x01
+#define CD9660_TYPE_DIR		0x02
+#define CD9660_TYPE_DOT		0x04
+#define CD9660_TYPE_DOTDOT	0x08
+#define CD9660_TYPE_VIRTUAL	0x80
+
+#define CD9660_INODE_HASH_SIZE	1024
+#define CD9660_SECTOR_SIZE	2048
+
+#define CD9660_END_PADDING	150
+
+/* Slight modification of the ISO structure in iso.h */
+typedef struct _iso_directory_record_cd9660 {
+	u_char length			[ISODCL (1, 1)];	/* 711 */
+	u_char ext_attr_length		[ISODCL (2, 2)];	/* 711 */
+	u_char extent			[ISODCL (3, 10)];	/* 733 */
+	u_char size			[ISODCL (11, 18)];	/* 733 */
+	u_char date			[ISODCL (19, 25)];	/* 7 by 711 */
+	u_char flags			[ISODCL (26, 26)];
+	u_char file_unit_size		[ISODCL (27, 27)];	/* 711 */
+	u_char interleave		[ISODCL (28, 28)];	/* 711 */
+	u_char volume_sequence_number	[ISODCL (29, 32)];	/* 723 */
+	u_char name_len			[ISODCL (33, 33)];	/* 711 */
+	char name			[ISO_FILENAME_MAXLENGTH_WITH_PADDING];
+} iso_directory_record_cd9660;
+
+/* TODO: Lots of optimization of this structure */
+typedef struct _cd9660node {
+	u_char	type;/* Used internally */
+	/* Tree structure */
+	struct _cd9660node	*parent;	/* parent (NULL if root) */
+	TAILQ_HEAD(cd9660_children_head, _cd9660node)	cn_children;
+	TAILQ_ENTRY(_cd9660node)		cn_next_child;
+
+	struct _cd9660node *dot_record; /* For directories, used mainly in RRIP */
+	struct _cd9660node *dot_dot_record;
+
+	fsnode		*node;		/* pointer to fsnode */
+	struct _iso_directory_record_cd9660	*isoDirRecord;
+	struct iso_extended_attributes	*isoExtAttributes;
+
+	/***** SIZE CALCULATION *****/
+	/*already stored in isoDirRecord, but this is an int version, and will be
+		copied to isoDirRecord on writing*/
+	uint32_t fileDataSector;
+
+	/*
+	 * same thing, though some notes:
+	 * If a file, this is the file size
+	 * If a directory, this is the size of all its children's
+	 *	directory records
+	 * plus necessary padding
+	 */
+	int64_t fileDataLength;
+
+	int64_t fileSectorsUsed;
+	int fileRecordSize;/*copy of a variable, int for quicker calculations*/
+
+	/* Old name, used for renaming - needs to be optimized but low priority */
+	char o_name [ISO_FILENAME_MAXLENGTH_WITH_PADDING];
+
+	/***** SPACE RESERVED FOR EXTENSIONS *****/
+	/* For memory efficiency's sake - we should move this to a separate struct
+		and point to null if not needed */
+	/* For Rock Ridge */
+	struct _cd9660node *rr_real_parent, *rr_relocated;
+
+	int64_t susp_entry_size;
+	int64_t susp_dot_entry_size;
+	int64_t susp_dot_dot_entry_size;
+
+	/* Continuation area stuff */
+	int64_t susp_entry_ce_start;
+	int64_t susp_dot_ce_start;
+	int64_t susp_dot_dot_ce_start;
+
+	int64_t susp_entry_ce_length;
+	int64_t susp_dot_ce_length;
+	int64_t susp_dot_dot_ce_length;
+
+	/* Data to put at the end of the System Use field */
+	int64_t su_tail_size;
+	char *su_tail_data;
+
+	/*** PATH TABLE STUFF ***/
+	int level;			/*depth*/
+	int ptnumber;
+	struct _cd9660node *ptnext, *ptprev, *ptlast;
+
+	/* SUSP entries */
+	TAILQ_HEAD(susp_linked_list, ISO_SUSP_ATTRIBUTES) head;
+} cd9660node;
+
+typedef struct _path_table_entry
+{
+	u_char length[ISODCL (1, 1)];
+	u_char extended_attribute_length[ISODCL (2, 2)];
+	u_char first_sector[ISODCL (3, 6)];
+	u_char parent_number[ISODCL (7, 8)];
+	u_char name[ISO_FILENAME_MAXLENGTH_WITH_PADDING];
+} path_table_entry;
+
+typedef struct _volume_descriptor
+{
+	u_char *volumeDescriptorData; /*ALWAYS 2048 bytes long*/
+	int64_t sector;
+	struct _volume_descriptor *next;
+} volume_descriptor;
+
+typedef struct _iso9660_disk {
+	int sectorSize;
+	struct iso_primary_descriptor		primaryDescriptor;
+	struct iso_supplementary_descriptor	supplementaryDescriptor;
+
+	volume_descriptor *firstVolumeDescriptor;
+
+	cd9660node *rootNode;
+
+	/* Important sector numbers here */
+	/* primaryDescriptor.type_l_path_table*/
+	int64_t primaryBigEndianTableSector;
+
+	/* primaryDescriptor.type_m_path_table*/
+	int64_t primaryLittleEndianTableSector;
+
+	/* primaryDescriptor.opt_type_l_path_table*/
+	int64_t secondaryBigEndianTableSector;
+
+	/* primaryDescriptor.opt_type_m_path_table*/
+	int64_t secondaryLittleEndianTableSector;
+
+	/* primaryDescriptor.path_table_size*/
+	int pathTableLength;
+	int64_t dataFirstSector;
+
+	int64_t totalSectors;
+	/* OPTIONS GO HERE */
+	int	isoLevel;
+
+	int include_padding_areas;
+
+	int follow_sym_links;
+	int verbose_level;
+	int displayHelp;
+	int keep_bad_images;
+
+	/* SUSP options and variables */
+	int64_t susp_continuation_area_start_sector;
+	int64_t susp_continuation_area_size;
+	int64_t susp_continuation_area_current_free;
+
+	int rock_ridge_enabled;
+	/* Other Rock Ridge Variables */
+	char *rock_ridge_renamed_dir_name;
+	int rock_ridge_move_count;
+	cd9660node *rr_moved_dir;
+
+	int archimedes_enabled;
+	int chrp_boot;
+
+	/* Spec breaking options */
+	u_char allow_deep_trees;
+	u_char allow_start_dot;
+	u_char allow_max_name; /* Allow 37 char filenames*/
+	u_char allow_illegal_chars; /* ~, !, # */
+	u_char allow_lowercase;
+	u_char allow_multidot;
+	u_char omit_trailing_period;
+
+	/* BOOT INFORMATION HERE */
+	int has_generic_bootimage; /* Default to 0 */
+	char *generic_bootimage;
+
+	int is_bootable;/* Default to 0 */
+	int64_t boot_catalog_sector;
+	boot_volume_descriptor *boot_descriptor;
+	char * boot_image_directory;
+
+	TAILQ_HEAD(boot_image_list,cd9660_boot_image) boot_images;
+	int image_serialno;
+	LIST_HEAD(boot_catalog_entries,boot_catalog_entry) boot_entries;
+
+} iso9660_disk;
+
+/************ FUNCTIONS **************/
+int			cd9660_valid_a_chars(const char *);
+int			cd9660_valid_d_chars(const char *);
+void			cd9660_uppercase_characters(char *, int);
+
+/* ISO Data Types */
+void			cd9660_721(uint16_t, unsigned char *);
+void			cd9660_731(uint32_t, unsigned char *);
+void			cd9660_722(uint16_t, unsigned char *);
+void			cd9660_732(uint32_t, unsigned char *);
+void 			cd9660_bothendian_dword(uint32_t dw, unsigned char *);
+void 			cd9660_bothendian_word(uint16_t dw, unsigned char *);
+void			cd9660_set_date(char *, time_t);
+void			cd9660_time_8426(unsigned char *, time_t);
+void			cd9660_time_915(unsigned char *, time_t);
+
+/*** Boot Functions ***/
+int	cd9660_write_generic_bootimage(FILE *);
+int	cd9660_write_boot(iso9660_disk *, FILE *);
+int	cd9660_add_boot_disk(iso9660_disk *, const char *);
+int	cd9660_eltorito_add_boot_option(iso9660_disk *, const char *,
+    const char *);
+int	cd9660_setup_boot(iso9660_disk *, int);
+int	cd9660_setup_boot_volume_descriptor(iso9660_disk *,
+    volume_descriptor *);
+
+
+/*** Write Functions ***/
+int	cd9660_write_image(iso9660_disk *, const char *image);
+int	cd9660_copy_file(iso9660_disk *, FILE *, off_t, const char *);
+
+void	cd9660_compute_full_filename(cd9660node *, char *);
+int	cd9660_compute_record_size(iso9660_disk *, cd9660node *);
+
+/* Debugging functions */
+void	debug_print_tree(iso9660_disk *, cd9660node *,int);
+void	debug_print_path_tree(cd9660node *);
+void	debug_print_volume_descriptor_information(iso9660_disk *);
+void	debug_dump_to_xml_ptentry(path_table_entry *,int, int);
+void	debug_dump_to_xml_path_table(FILE *, off_t, int, int);
+void	debug_dump_to_xml(FILE *);
+int	debug_get_encoded_number(unsigned char *, int);
+void	debug_dump_integer(const char *, char *,int);
+void	debug_dump_string(const char *,unsigned char *,int);
+void	debug_dump_directory_record_9_1(unsigned char *);
+void	debug_dump_to_xml_volume_descriptor(unsigned char *,int);
+
+void	cd9660_pad_string_spaces(char *, int);
+
+#endif
diff --git a/usr.sbin/makefs/cd9660/Makefile.inc b/usr.sbin/makefs/cd9660/Makefile.inc
new file mode 100644
index 0000000000..2d3e934fa1
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/Makefile.inc
@@ -0,0 +1,9 @@
+#	$FreeBSD: head/usr.sbin/makefs/cd9660/Makefile.inc 332844 2018-04-20 22:23:38Z emaste $
+#
+
+.PATH:	${SRCDIR}/cd9660
+
+CFLAGS+=-I${SRCTOP}/sys/fs/cd9660/
+
+SRCS+=	cd9660_strings.c cd9660_debug.c cd9660_eltorito.c \
+	cd9660_write.c cd9660_conversion.c iso9660_rrip.c cd9660_archimedes.c
diff --git a/usr.sbin/makefs/cd9660/cd9660_archimedes.c b/usr.sbin/makefs/cd9660/cd9660_archimedes.c
new file mode 100644
index 0000000000..a6e2d86f11
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/cd9660_archimedes.c
@@ -0,0 +1,126 @@
+/* $NetBSD: cd9660_archimedes.c,v 1.1 2009/01/10 22:06:29 bjh21 Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1998, 2009 Ben Harris
+ * 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.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
+ */
+/*
+ * cd9660_archimedes.c - support for RISC OS "ARCHIMEDES" extension
+ *
+ * RISC OS CDFS looks for a special block at the end of the System Use
+ * Field for each file.  If present, this contains the RISC OS load
+ * and exec address (used to hold the file timestamp and type), the
+ * file attributes, and a flag indicating whether the first character
+ * of the filename should be replaced with '!' (since many special
+ * RISC OS filenames do).
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/cd9660/cd9660_archimedes.c 326276 2017-11-27 15:37:16Z pfg $");
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <util.h>
+
+#include "makefs.h"
+#include "cd9660.h"
+#include "cd9660_archimedes.h"
+
+/*
+ * Convert a Unix time_t (non-leap seconds since 1970-01-01) to a RISC
+ * OS time (non-leap(?) centiseconds since 1900-01-01(?)).
+ */
+
+static u_int64_t
+riscos_date(time_t unixtime)
+{
+	u_int64_t base;
+
+	base = 31536000ULL * 70 + 86400 * 17;
+	return (((u_int64_t)unixtime) + base)*100;
+}
+
+/*
+ * Add "ARCHIMEDES" metadata to a node if that seems appropriate.
+ *
+ * We touch regular files with names matching /,[0-9a-f]{3}$/ and
+ * directories matching /^!/.
+ */
+static void
+archimedes_convert_node(cd9660node *node)
+{
+	struct ISO_ARCHIMEDES *arc;
+	size_t len;
+	int type = -1;
+	uint64_t stamp;
+
+	if (node->su_tail_data != NULL)
+		/* Something else already has the tail. */
+		return;
+
+	len = strlen(node->node->name);
+	if (len < 1) return;
+
+	if (len >= 4 && node->node->name[len-4] == ',')
+		/* XXX should support ,xxx and ,lxa */
+		type = strtoul(node->node->name + len - 3, NULL, 16);
+	if (type == -1 && node->node->name[0] != '!')
+		return;
+	if (type == -1) type = 0;
+
+	assert(sizeof(*arc) == 32);
+	arc = ecalloc(1, sizeof(*arc));
+
+	stamp = riscos_date(node->node->inode->st.st_mtime);
+
+	memcpy(arc->magic, "ARCHIMEDES", 10);
+	cd9660_731(0xfff00000 | (type << 8) | (stamp >> 32), arc->loadaddr);
+	cd9660_731(stamp & 0x00ffffffffULL, arc->execaddr);
+	arc->ro_attr = RO_ACCESS_UR | RO_ACCESS_OR;
+	arc->cdfs_attr = node->node->name[0] == '!' ? CDFS_PLING : 0;
+	node->su_tail_data = (void *)arc;
+	node->su_tail_size = sizeof(*arc);
+}
+
+/*
+ * Add "ARCHIMEDES" metadata to an entire tree recursively.
+ */
+void
+archimedes_convert_tree(cd9660node *node)
+{
+	cd9660node *cn;
+
+	assert(node != NULL);
+
+	archimedes_convert_node(node);
+
+		/* Recurse on children. */
+	TAILQ_FOREACH(cn, &node->cn_children, cn_next_child)
+		archimedes_convert_tree(cn);
+}
diff --git a/usr.sbin/makefs/cd9660/cd9660_archimedes.h b/usr.sbin/makefs/cd9660/cd9660_archimedes.h
new file mode 100644
index 0000000000..07d29cba90
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/cd9660_archimedes.h
@@ -0,0 +1,52 @@
+/* $NetBSD: cd9660_archimedes.h,v 1.1 2009/01/10 22:06:29 bjh21 Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1998, 2009 Ben Harris
+ * 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.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
+ */
+/*
+ * cd9660_archimedes.c - support for RISC OS "ARCHIMEDES" extension
+ *
+ * $FreeBSD: head/usr.sbin/makefs/cd9660/cd9660_archimedes.h 326276 2017-11-27 15:37:16Z pfg $
+ */
+
+struct ISO_ARCHIMEDES {
+	char		magic[10];	/* "ARCHIMEDES" */
+	unsigned char	loadaddr[4];	/* Load address, little-endian */
+	unsigned char	execaddr[4];	/* Exec address, little-endian */
+	unsigned char	ro_attr;	/* RISC OS attributes */
+#define RO_ACCESS_UR	0x01 /* Owner read */
+#define RO_ACCESS_UW	0x02 /* Owner write */
+#define RO_ACCESS_L	0x04 /* Locked */
+#define RO_ACCESS_OR	0x10 /* Public read */
+#define RO_ACCESS_OW	0x20 /* Public write */
+	unsigned char	cdfs_attr;	/* Extra attributes for CDFS */
+#define CDFS_PLING	0x01	/* Filename begins with '!' */
+	char		reserved[12];
+};
+
+extern void archimedes_convert_tree(cd9660node *);
diff --git a/usr.sbin/makefs/cd9660/cd9660_conversion.c b/usr.sbin/makefs/cd9660/cd9660_conversion.c
new file mode 100644
index 0000000000..59e3545637
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/cd9660_conversion.c
@@ -0,0 +1,202 @@
+/*	$NetBSD: cd9660_conversion.c,v 1.4 2007/03/14 14:11:17 christos Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-NetBSD
+ *
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam.  All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM 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 "cd9660.h"
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/cd9660/cd9660_conversion.c 326276 2017-11-27 15:37:16Z pfg $");
+
+static char cd9660_compute_gm_offset(time_t);
+
+#if 0
+static inline int
+cd9660_pad_even(length)
+int length;
+{
+	return length + (length & 0x01);
+}
+#endif
+
+/*
+* These can probably be implemented using a macro
+*/
+
+/* Little endian */
+void
+cd9660_721(uint16_t w, unsigned char *twochar)
+{
+#if BYTE_ORDER == BIG_ENDIAN
+	w = bswap16(w);
+#endif
+	memcpy(twochar,&w,2);
+}
+
+void
+cd9660_731(uint32_t w, unsigned char *fourchar)
+{
+#if BYTE_ORDER == BIG_ENDIAN
+	w = bswap32(w);
+#endif
+	memcpy(fourchar,&w,4);
+}
+
+/* Big endian */
+void
+cd9660_722(uint16_t w, unsigned char *twochar)
+{
+#if BYTE_ORDER == LITTLE_ENDIAN
+	w = bswap16(w);
+#endif
+	memcpy(twochar,&w,2);
+}
+
+void
+cd9660_732(uint32_t w, unsigned char *fourchar)
+{
+#if BYTE_ORDER == LITTLE_ENDIAN
+	w = bswap32(w);
+#endif
+	memcpy(fourchar,&w,4);
+}
+
+/**
+* Convert a dword into a double endian string of eight characters
+* @param int The double word to convert
+* @param char* The string to write the both endian double word to - It is assumed this is allocated and at least
+*		eight characters long
+*/
+void
+cd9660_bothendian_dword(uint32_t dw, unsigned char *eightchar)
+{
+	uint32_t le, be;
+#if BYTE_ORDER == LITTLE_ENDIAN
+	le = dw;
+	be = bswap32(dw);
+#endif
+#if BYTE_ORDER == BIG_ENDIAN
+	be = dw;
+	le = bswap32(dw);
+#endif
+	memcpy(eightchar, &le, 4);
+	memcpy((eightchar+4), &be, 4);
+}
+
+/**
+* Convert a word into a double endian string of four characters
+* @param int The word to convert
+* @param char* The string to write the both endian word to - It is assumed this is allocated and at least
+*		four characters long
+*/
+void
+cd9660_bothendian_word(uint16_t dw, unsigned char *fourchar)
+{
+	uint16_t le, be;
+#if BYTE_ORDER == LITTLE_ENDIAN
+	le = dw;
+	be = bswap16(dw);
+#endif
+#if BYTE_ORDER == BIG_ENDIAN
+	be = dw;
+	le = bswap16(dw);
+#endif
+	memcpy(fourchar, &le, 2);
+	memcpy((fourchar+2), &be, 2);
+}
+
+void
+cd9660_pad_string_spaces(char *str, int len)
+{
+	int i;
+
+	for (i = 0; i < len; i ++) {
+		if (str[i] == '\0')
+			str[i] = 0x20;
+	}
+}
+
+static char
+cd9660_compute_gm_offset(time_t tim)
+{
+	struct tm t, gm;
+
+	(void)localtime_r(&tim, &t);
+	(void)gmtime_r(&tim, &gm);
+	gm.tm_year -= t.tm_year;
+	gm.tm_yday -= t.tm_yday;
+	gm.tm_hour -= t.tm_hour;
+	gm.tm_min  -= t.tm_min;
+	if (gm.tm_year < 0)
+		gm.tm_yday = -1;
+	else if (gm.tm_year > 0)
+		gm.tm_yday = 1;
+
+	return (char)(-(gm.tm_min + 60* (24 * gm.tm_yday + gm.tm_hour)) / 15);
+}
+
+/* Long dates: 17 characters */
+void
+cd9660_time_8426(unsigned char *buf, time_t tim)
+{
+	struct tm t;
+	char temp[18];
+
+	(void)localtime_r(&tim, &t);
+	(void)snprintf(temp, sizeof(temp), "%04i%02i%02i%02i%02i%02i%02i",
+		1900+(int)t.tm_year,
+		(int)t.tm_mon+1,
+		(int)t.tm_mday,
+		(int)t.tm_hour,
+		(int)t.tm_min,
+		(int)t.tm_sec,
+		0);
+	(void)memcpy(buf, temp, 16);
+	buf[16] = cd9660_compute_gm_offset(tim);
+}
+
+/* Short dates: 7 characters */
+void
+cd9660_time_915(unsigned char *buf, time_t tim)
+{
+	struct tm t;
+
+	(void)localtime_r(&tim, &t);
+	buf[0] = t.tm_year;
+	buf[1] = t.tm_mon+1;
+	buf[2] = t.tm_mday;
+	buf[3] = t.tm_hour;
+	buf[4] = t.tm_min;
+	buf[5] = t.tm_sec;
+	buf[6] = cd9660_compute_gm_offset(tim);
+}
diff --git a/usr.sbin/makefs/cd9660/cd9660_debug.c b/usr.sbin/makefs/cd9660/cd9660_debug.c
new file mode 100644
index 0000000000..ee7c07a10f
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/cd9660_debug.c
@@ -0,0 +1,489 @@
+/*	$NetBSD: cd9660_debug.c,v 1.11 2010/10/27 18:51:35 christos Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-NetBSD
+ *
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam.  All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/cd9660/cd9660_debug.c 333664 2018-05-16 02:58:05Z emaste $");
+#include <sys/param.h>
+
+#include <sys/mount.h>
+
+#include "makefs.h"
+#include "cd9660.h"
+#include "iso9660_rrip.h"
+
+static void debug_print_susp_attrs(cd9660node *, int);
+static void debug_dump_to_xml_padded_hex_output(const char *, unsigned char *,
+						int);
+
+static inline void
+print_n_tabs(int n)
+{
+	int i;
+
+	for (i = 1; i <= n; i ++)
+		printf("\t");
+}
+
+#if 0
+void
+debug_print_rrip_info(cd9660node *n)
+{
+	struct ISO_SUSP_ATTRIBUTES *t;
+	TAILQ_FOREACH(t, &node->head, rr_ll) {
+
+	}
+}
+#endif
+
+static void
+debug_print_susp_attrs(cd9660node *n, int indent)
+{
+	struct ISO_SUSP_ATTRIBUTES *t;
+
+	TAILQ_FOREACH(t, &n->head, rr_ll) {
+		print_n_tabs(indent);
+		printf("-");
+		printf("%c%c: L:%i",t->attr.su_entry.SP.h.type[0],
+		    t->attr.su_entry.SP.h.type[1],
+		    (int)t->attr.su_entry.SP.h.length[0]);
+		printf("\n");
+	}
+}
+
+void
+debug_print_tree(iso9660_disk *diskStructure, cd9660node *node, int level)
+{
+#if !HAVE_NBTOOL_CONFIG_H
+	cd9660node *cn;
+
+	print_n_tabs(level);
+	if (node->type & CD9660_TYPE_DOT) {
+		printf(". (%i)\n",
+		    isonum_733(node->isoDirRecord->extent));
+	} else if (node->type & CD9660_TYPE_DOTDOT) {
+		printf("..(%i)\n",
+		    isonum_733(node->isoDirRecord->extent));
+	} else if (node->isoDirRecord->name[0]=='\0') {
+		printf("(ROOT) (%" PRIu32 " to %" PRId64 ")\n",
+		    node->fileDataSector,
+		    node->fileDataSector +
+			node->fileSectorsUsed - 1);
+	} else {
+		printf("%s (%s) (%" PRIu32 " to %" PRId64 ")\n",
+		    node->isoDirRecord->name,
+		    (node->isoDirRecord->flags[0]
+			& ISO_FLAG_DIRECTORY) ?  "DIR" : "FILE",
+		    node->fileDataSector,
+		    (node->fileSectorsUsed == 0) ?
+			node->fileDataSector :
+			node->fileDataSector
+			    + node->fileSectorsUsed - 1);
+	}
+	if (diskStructure->rock_ridge_enabled)
+		debug_print_susp_attrs(node, level + 1);
+	TAILQ_FOREACH(cn, &node->cn_children, cn_next_child)
+		debug_print_tree(diskStructure, cn, level + 1);
+#else
+	printf("Sorry, debugging is not supported in host-tools mode.\n");
+#endif
+}
+
+void
+debug_print_path_tree(cd9660node *n)
+{
+	cd9660node *iterator = n;
+
+	/* Only display this message when called with the root node */
+	if (n->parent == NULL)
+		printf("debug_print_path_table: Dumping path table contents\n");
+
+	while (iterator != NULL) {
+		if (iterator->isoDirRecord->name[0] == '\0')
+			printf("0) (ROOT)\n");
+		else
+			printf("%i) %s\n", iterator->level,
+			    iterator->isoDirRecord->name);
+
+		iterator = iterator->ptnext;
+	}
+}
+
+void
+debug_print_volume_descriptor_information(iso9660_disk *diskStructure)
+{
+	volume_descriptor *tmp = diskStructure->firstVolumeDescriptor;
+	char temp[CD9660_SECTOR_SIZE];
+
+	printf("==Listing Volume Descriptors==\n");
+
+	while (tmp != NULL) {
+		memset(temp, 0, CD9660_SECTOR_SIZE);
+		memcpy(temp, tmp->volumeDescriptorData + 1, 5);
+		printf("Volume descriptor in sector %" PRId64
+		    ": type %i, ID %s\n",
+		    tmp->sector, tmp->volumeDescriptorData[0], temp);
+		switch(tmp->volumeDescriptorData[0]) {
+		case 0:/*boot record*/
+			break;
+
+		case 1:		/* PVD */
+			break;
+
+		case 2:		/* SVD */
+			break;
+
+		case 3:		/* Volume Partition Descriptor */
+			break;
+
+		case 255:	/* terminator */
+			break;
+		}
+		tmp = tmp->next;
+	}
+
+	printf("==Done Listing Volume Descriptors==\n");
+}
+
+void
+debug_dump_to_xml_ptentry(path_table_entry *pttemp, int num, int mode)
+{
+	printf("<ptentry num=\"%i\">\n" ,num);
+	printf("<length>%i</length>\n", pttemp->length[0]);
+	printf("<extended_attribute_length>%i</extended_attribute_length>\n",
+	    pttemp->extended_attribute_length[0]);
+	printf("<parent_number>%i</parent_number>\n",
+	    debug_get_encoded_number(pttemp->parent_number,mode));
+	debug_dump_to_xml_padded_hex_output("name",
+	    pttemp->name, pttemp->length[0]);
+	printf("</ptentry>\n");
+}
+
+void
+debug_dump_to_xml_path_table(FILE *fd, off_t sector, int size, int mode)
+{
+	path_table_entry pttemp;
+	int t = 0;
+	int n = 0;
+
+	if (fseeko(fd, CD9660_SECTOR_SIZE * sector, SEEK_SET) == -1)
+		err(1, "fseeko");
+
+	while (t < size) {
+		/* Read fixed data first */
+		fread(&pttemp, 1, 8, fd);
+		t += 8;
+		/* Read variable */
+		fread(((unsigned char*)&pttemp) + 8, 1, pttemp.length[0], fd);
+		t += pttemp.length[0];
+		debug_dump_to_xml_ptentry(&pttemp, n, mode);
+		n++;
+	}
+
+}
+
+/*
+ * XML Debug output functions
+ * Dump hierarchy of CD, as well as volume info, to XML
+ * Can be used later to diff against a standard,
+ * or just provide easy to read detailed debug output
+ */
+void
+debug_dump_to_xml(FILE *fd)
+{
+	unsigned char buf[CD9660_SECTOR_SIZE];
+	off_t sector;
+	int t, t2;
+	struct iso_primary_descriptor primaryVD;
+	struct _boot_volume_descriptor bootVD;
+
+	printf("<cd9660dump>\n");
+
+	/* Display Volume Descriptors */
+	sector = 16;
+	do {
+		if (fseeko(fd, CD9660_SECTOR_SIZE * sector, SEEK_SET) == -1)
+			err(1, "fseeko");
+		fread(buf, 1, CD9660_SECTOR_SIZE, fd);
+		t = (int)((unsigned char)buf[0]);
+		switch (t) {
+		case 0:
+			memcpy(&bootVD, buf, CD9660_SECTOR_SIZE);
+			break;
+		case 1:
+			memcpy(&primaryVD, buf, CD9660_SECTOR_SIZE);
+			break;
+		}
+		debug_dump_to_xml_volume_descriptor(buf, sector);
+		sector++;
+	} while (t != 255);
+
+	t = debug_get_encoded_number((u_char *)primaryVD.type_l_path_table,
+	    731);
+	t2 = debug_get_encoded_number((u_char *)primaryVD.path_table_size, 733);
+	printf("Path table 1 located at sector %i and is %i bytes long\n",
+	    t,t2);
+	debug_dump_to_xml_path_table(fd, t, t2, 721);
+
+	t = debug_get_encoded_number((u_char *)primaryVD.type_m_path_table,
+	    731);
+	debug_dump_to_xml_path_table(fd, t, t2, 722);
+
+	printf("</cd9660dump>\n");
+}
+
+static void
+debug_dump_to_xml_padded_hex_output(const char *element, unsigned char *buf,
+				    int len)
+{
+	int i;
+	int t;
+
+	printf("<%s>",element);
+	for (i = 0; i < len; i++) {
+		t = (unsigned char)buf[i];
+		if (t >= 32 && t < 127)
+			printf("%c",t);
+	}
+	printf("</%s>\n",element);
+
+	printf("<%s:hex>",element);
+	for (i = 0; i < len; i++) {
+		t = (unsigned char)buf[i];
+		printf(" %x",t);
+	}
+	printf("</%s:hex>\n",element);
+}
+
+int
+debug_get_encoded_number(unsigned char* buf, int mode)
+{
+#if !HAVE_NBTOOL_CONFIG_H
+	switch (mode) {
+	/* 711: Single bite */
+	case 711:
+		return isonum_711(buf);
+
+	/* 712: Single signed byte */
+	case 712:
+		return isonum_712((signed char *)buf);
+
+	/* 721: 16 bit LE */
+	case 721:
+		return isonum_721(buf);
+
+	/* 731: 32 bit LE */
+	case 731:
+		return isonum_731(buf);
+
+	/* 722: 16 bit BE */
+	case 722:
+		return isonum_722(buf);
+
+	/* 732: 32 bit BE */
+	case 732:
+		return isonum_732(buf);
+
+	/* 723: 16 bit bothE */
+	case 723:
+		return isonum_723(buf);
+
+	/* 733: 32 bit bothE */
+	case 733:
+		return isonum_733(buf);
+	}
+#endif
+	return 0;
+}
+
+void
+debug_dump_integer(const char *element, char* buf, int mode)
+{
+	printf("<%s>%i</%s>\n", element,
+	    debug_get_encoded_number((unsigned char *)buf, mode), element);
+}
+
+void
+debug_dump_string(const char *element __unused, unsigned char *buf __unused, int len __unused)
+{
+
+}
+
+void
+debug_dump_directory_record_9_1(unsigned char* buf)
+{
+	printf("<directoryrecord>\n");
+	debug_dump_integer("length",
+	    ((struct iso_directory_record*) buf)->length, 711);
+	debug_dump_integer("ext_attr_length",
+	    ((struct iso_directory_record*) buf)->ext_attr_length,711);
+	debug_dump_integer("extent",
+	    (char *)((struct iso_directory_record*) buf)->extent, 733);
+	debug_dump_integer("size",
+	    (char *)((struct iso_directory_record*) buf)->size, 733);
+	debug_dump_integer("flags",
+	    ((struct iso_directory_record*) buf)->flags, 711);
+	debug_dump_integer("file_unit_size",
+	    ((struct iso_directory_record*) buf)->file_unit_size,711);
+	debug_dump_integer("interleave",
+	    ((struct iso_directory_record*) buf)->interleave, 711);
+	debug_dump_integer("volume_sequence_number",
+	    ((struct iso_directory_record*) buf)->volume_sequence_number,
+	    723);
+	debug_dump_integer("name_len",
+	    ((struct iso_directory_record*) buf)->name_len, 711);
+	debug_dump_to_xml_padded_hex_output("name",
+	    (u_char *)((struct iso_directory_record*) buf)->name,
+		debug_get_encoded_number((u_char *)
+		    ((struct iso_directory_record*) buf)->length, 711));
+	printf("</directoryrecord>\n");
+}
+
+
+void
+debug_dump_to_xml_volume_descriptor(unsigned char* buf, int sector)
+{
+	printf("<volumedescriptor sector=\"%i\">\n", sector);
+	printf("<vdtype>");
+	switch(buf[0]) {
+	case 0:
+		printf("boot");
+		break;
+
+	case 1:
+		printf("primary");
+		break;
+
+	case 2:
+		printf("supplementary");
+		break;
+
+	case 3:
+		printf("volume partition descriptor");
+		break;
+
+	case 255:
+		printf("terminator");
+		break;
+	}
+
+	printf("</vdtype>\n");
+	switch(buf[0]) {
+	case 1:
+		debug_dump_integer("type",
+		    ((struct iso_primary_descriptor*)buf)->type, 711);
+		debug_dump_to_xml_padded_hex_output("id",
+		    (u_char *)((struct iso_primary_descriptor*) buf)->id,
+		    ISODCL (  2,   6));
+		debug_dump_integer("version",
+		    ((struct iso_primary_descriptor*)buf)->version,
+		     711);
+		debug_dump_to_xml_padded_hex_output("system_id",
+		    (u_char *)((struct iso_primary_descriptor*)buf)->system_id,
+		    ISODCL(9,40));
+		debug_dump_to_xml_padded_hex_output("volume_id",
+		    (u_char *)((struct iso_primary_descriptor*)buf)->volume_id,
+		    ISODCL(41,72));
+		debug_dump_integer("volume_space_size",
+		    ((struct iso_primary_descriptor*)buf)->volume_space_size,
+		    733);
+		debug_dump_integer("volume_set_size",
+		    ((struct iso_primary_descriptor*)buf)->volume_set_size,
+			    733);
+		debug_dump_integer("volume_sequence_number",
+		    ((struct iso_primary_descriptor*)buf)->volume_sequence_number,
+		    723);
+		debug_dump_integer("logical_block_size",
+		    ((struct iso_primary_descriptor*)buf)->logical_block_size,
+			    723);
+		debug_dump_integer("path_table_size",
+		    ((struct iso_primary_descriptor*)buf)->path_table_size,
+			    733);
+		debug_dump_integer("type_l_path_table",
+		    ((struct iso_primary_descriptor*)buf)->type_l_path_table,
+		    731);
+		debug_dump_integer("opt_type_l_path_table",
+		    ((struct iso_primary_descriptor*)buf)->opt_type_l_path_table,
+		    731);
+		debug_dump_integer("type_m_path_table",
+		    ((struct iso_primary_descriptor*)buf)->type_m_path_table,
+		    732);
+		debug_dump_integer("opt_type_m_path_table",
+			((struct iso_primary_descriptor*)buf)->opt_type_m_path_table,732);
+		debug_dump_directory_record_9_1(
+		    (u_char *)((struct iso_primary_descriptor*)buf)->root_directory_record);
+		debug_dump_to_xml_padded_hex_output("volume_set_id",
+		    (u_char *)((struct iso_primary_descriptor*) buf)->volume_set_id,
+		    ISODCL (191, 318));
+		debug_dump_to_xml_padded_hex_output("publisher_id",
+		    (u_char *)((struct iso_primary_descriptor*) buf)->publisher_id,
+		    ISODCL (319, 446));
+		debug_dump_to_xml_padded_hex_output("preparer_id",
+		    (u_char *)((struct iso_primary_descriptor*) buf)->preparer_id,
+		    ISODCL (447, 574));
+		debug_dump_to_xml_padded_hex_output("application_id",
+		    (u_char *)((struct iso_primary_descriptor*) buf)->application_id,
+		    ISODCL (575, 702));
+		debug_dump_to_xml_padded_hex_output("copyright_file_id",
+		    (u_char *)((struct iso_primary_descriptor*) buf)->copyright_file_id,
+		    ISODCL (703, 739));
+		debug_dump_to_xml_padded_hex_output("abstract_file_id",
+		    (u_char *)((struct iso_primary_descriptor*) buf)->abstract_file_id,
+		    ISODCL (740, 776));
+		debug_dump_to_xml_padded_hex_output("bibliographic_file_id",
+		    (u_char *)((struct iso_primary_descriptor*) buf)->bibliographic_file_id,
+		    ISODCL (777, 813));
+
+		debug_dump_to_xml_padded_hex_output("creation_date",
+		    (u_char *)((struct iso_primary_descriptor*) buf)->creation_date,
+		    ISODCL (814, 830));
+		debug_dump_to_xml_padded_hex_output("modification_date",
+		    (u_char *)((struct iso_primary_descriptor*) buf)->modification_date,
+		    ISODCL (831, 847));
+		debug_dump_to_xml_padded_hex_output("expiration_date",
+		    (u_char *)((struct iso_primary_descriptor*) buf)->expiration_date,
+		    ISODCL (848, 864));
+		debug_dump_to_xml_padded_hex_output("effective_date",
+		    (u_char *)((struct iso_primary_descriptor*) buf)->effective_date,
+		    ISODCL (865, 881));
+
+		debug_dump_to_xml_padded_hex_output("file_structure_version",
+		    (u_char *)((struct iso_primary_descriptor*) buf)->file_structure_version,
+		    ISODCL(882,882));
+		break;
+	}
+	printf("</volumedescriptor>\n");
+}
+
diff --git a/usr.sbin/makefs/cd9660/cd9660_eltorito.c b/usr.sbin/makefs/cd9660/cd9660_eltorito.c
new file mode 100644
index 0000000000..3d81265fc8
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/cd9660_eltorito.c
@@ -0,0 +1,742 @@
+/*	$NetBSD: cd9660_eltorito.c,v 1.23 2018/03/28 06:48:55 nonaka Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-NetBSD
+ *
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam.  All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM 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 "cd9660.h"
+#include "cd9660_eltorito.h"
+#include <util.h>
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/cd9660/cd9660_eltorito.c 331843 2018-03-31 15:04:41Z benno $");
+
+#ifdef DEBUG
+#define	ELTORITO_DPRINTF(__x)	printf __x
+#else
+#define	ELTORITO_DPRINTF(__x)
+#endif
+
+#include <util.h>
+
+static struct boot_catalog_entry *cd9660_init_boot_catalog_entry(void);
+static struct boot_catalog_entry *cd9660_boot_setup_validation_entry(char);
+static struct boot_catalog_entry *cd9660_boot_setup_default_entry(
+    struct cd9660_boot_image *);
+static struct boot_catalog_entry *cd9660_boot_setup_section_head(char);
+#if 0
+static u_char cd9660_boot_get_system_type(struct cd9660_boot_image *);
+#endif
+
+static struct cd9660_boot_image *default_boot_image;
+
+int
+cd9660_add_boot_disk(iso9660_disk *diskStructure, const char *boot_info)
+{
+	struct stat stbuf;
+	const char *mode_msg;
+	char *temp;
+	char *sysname;
+	char *filename;
+	struct cd9660_boot_image *new_image, *tmp_image;
+
+	assert(boot_info != NULL);
+
+	if (*boot_info == '\0') {
+		warnx("Error: Boot disk information must be in the "
+		      "format 'system;filename'");
+		return 0;
+	}
+
+	/* First decode the boot information */
+	temp = estrdup(boot_info);
+
+	sysname = temp;
+	filename = strchr(sysname, ';');
+	if (filename == NULL) {
+		warnx("supply boot disk information in the format "
+		    "'system;filename'");
+		free(temp);
+		return 0;
+	}
+
+	*filename++ = '\0';
+
+	if (diskStructure->verbose_level > 0) {
+		printf("Found bootdisk with system %s, and filename %s\n",
+		    sysname, filename);
+	}
+	new_image = ecalloc(1, sizeof(*new_image));
+	new_image->loadSegment = 0;	/* default for now */
+
+	/* Decode System */
+	if (strcmp(sysname, "i386") == 0)
+		new_image->system = ET_SYS_X86;
+	else if (strcmp(sysname, "powerpc") == 0)
+		new_image->system = ET_SYS_PPC;
+	else if (strcmp(sysname, "macppc") == 0 ||
+	         strcmp(sysname, "mac68k") == 0)
+		new_image->system = ET_SYS_MAC;
+	else {
+		warnx("boot disk system must be "
+		      "i386, powerpc, macppc, or mac68k");
+		free(temp);
+		free(new_image);
+		return 0;
+	}
+
+
+	new_image->filename = estrdup(filename);
+
+	free(temp);
+
+	/* Get information about the file */
+	if (lstat(new_image->filename, &stbuf) == -1)
+		err(EXIT_FAILURE, "%s: lstat(\"%s\")", __func__,
+		    new_image->filename);
+
+	switch (stbuf.st_size) {
+	case 1440 * 1024:
+		new_image->targetMode = ET_MEDIA_144FDD;
+		mode_msg = "Assigned boot image to 1.44 emulation mode";
+		break;
+	case 1200 * 1024:
+		new_image->targetMode = ET_MEDIA_12FDD;
+		mode_msg = "Assigned boot image to 1.2 emulation mode";
+		break;
+	case 2880 * 1024:
+		new_image->targetMode = ET_MEDIA_288FDD;
+		mode_msg = "Assigned boot image to 2.88 emulation mode";
+		break;
+	default:
+		new_image->targetMode = ET_MEDIA_NOEM;
+		mode_msg = "Assigned boot image to no emulation mode";
+		break;
+	}
+
+	if (diskStructure->verbose_level > 0)
+		printf("%s\n", mode_msg);
+
+	new_image->size = stbuf.st_size;
+	new_image->num_sectors =
+	    howmany(new_image->size, diskStructure->sectorSize) *
+	    howmany(diskStructure->sectorSize, 512);
+	if (diskStructure->verbose_level > 0) {
+		printf("New image has size %d, uses %d 512-byte sectors\n",
+		    new_image->size, new_image->num_sectors);
+	}
+	new_image->sector = -1;
+	/* Bootable by default */
+	new_image->bootable = ET_BOOTABLE;
+	/* Add boot disk */
+
+	/* Group images for the same platform together. */
+	TAILQ_FOREACH(tmp_image, &diskStructure->boot_images, image_list) {
+		if (tmp_image->system != new_image->system)
+			break;
+	}
+
+	if (tmp_image == NULL) {
+		TAILQ_INSERT_HEAD(&diskStructure->boot_images, new_image,
+		    image_list);
+	} else
+		TAILQ_INSERT_BEFORE(tmp_image, new_image, image_list);
+
+	new_image->serialno = diskStructure->image_serialno++;
+
+	new_image->platform_id = new_image->system;
+
+	/* TODO : Need to do anything about the boot image in the tree? */
+	diskStructure->is_bootable = 1;
+
+	/* First boot image is initial/default entry. */
+	if (default_boot_image == NULL)
+		default_boot_image = new_image;
+
+	return 1;
+}
+
+int
+cd9660_eltorito_add_boot_option(iso9660_disk *diskStructure,
+    const char *option_string, const char *value)
+{
+	char *eptr;
+	struct cd9660_boot_image *image;
+
+	assert(option_string != NULL);
+
+	/* Find the last image added */
+	TAILQ_FOREACH(image, &diskStructure->boot_images, image_list) {
+		if (image->serialno + 1 == diskStructure->image_serialno)
+			break;
+	}
+	if (image == NULL)
+		errx(EXIT_FAILURE, "Attempted to add boot option, "
+		    "but no boot images have been specified");
+
+	if (strcmp(option_string, "no-emul-boot") == 0) {
+		image->targetMode = ET_MEDIA_NOEM;
+	} else if (strcmp(option_string, "no-boot") == 0) {
+		image->bootable = ET_NOT_BOOTABLE;
+	} else if (strcmp(option_string, "hard-disk-boot") == 0) {
+		image->targetMode = ET_MEDIA_HDD;
+	} else if (strcmp(option_string, "boot-load-segment") == 0) {
+		image->loadSegment = strtoul(value, &eptr, 16);
+		if (eptr == value || *eptr != '\0' || errno != ERANGE) {
+			warn("%s: strtoul", __func__);
+			return 0;
+		}
+	} else if (strcmp(option_string, "platformid") == 0) {
+		if (strcmp(value, "efi") == 0)
+			image->platform_id = ET_SYS_EFI;
+		else {
+			warn("%s: unknown platform: %s", __func__, value);
+			return 0;
+		}
+	} else {
+		return 0;
+	}
+	return 1;
+}
+
+static struct boot_catalog_entry *
+cd9660_init_boot_catalog_entry(void)
+{
+	return ecalloc(1, sizeof(struct boot_catalog_entry));
+}
+
+static struct boot_catalog_entry *
+cd9660_boot_setup_validation_entry(char sys)
+{
+	struct boot_catalog_entry *entry;
+	boot_catalog_validation_entry *ve;
+	int16_t checksum;
+	unsigned char *csptr;
+	size_t i;
+	entry = cd9660_init_boot_catalog_entry();
+
+	entry->entry_type = ET_ENTRY_VE;
+	ve = &entry->entry_data.VE;
+
+	ve->header_id[0] = 1;
+	ve->platform_id[0] = sys;
+	ve->key[0] = 0x55;
+	ve->key[1] = 0xAA;
+
+	/* Calculate checksum */
+	checksum = 0;
+	cd9660_721(0, ve->checksum);
+	csptr = (unsigned char*)ve;
+	for (i = 0; i < sizeof(*ve); i += 2) {
+		checksum += (int16_t)csptr[i];
+		checksum += 256 * (int16_t)csptr[i + 1];
+	}
+	checksum = -checksum;
+	cd9660_721(checksum, ve->checksum);
+
+        ELTORITO_DPRINTF(("%s: header_id %d, platform_id %d, key[0] %d, key[1] %d, "
+	    "checksum %04x\n", __func__, ve->header_id[0], ve->platform_id[0],
+	    ve->key[0], ve->key[1], checksum));
+	return entry;
+}
+
+static struct boot_catalog_entry *
+cd9660_boot_setup_default_entry(struct cd9660_boot_image *disk)
+{
+	struct boot_catalog_entry *default_entry;
+	boot_catalog_initial_entry *ie;
+
+	default_entry = cd9660_init_boot_catalog_entry();
+	if (default_entry == NULL)
+		return NULL;
+
+	default_entry->entry_type = ET_ENTRY_IE;
+	ie = &default_entry->entry_data.IE;
+
+	ie->boot_indicator[0] = disk->bootable;
+	ie->media_type[0] = disk->targetMode;
+	cd9660_721(disk->loadSegment, ie->load_segment);
+	ie->system_type[0] = disk->system;
+	cd9660_721(disk->num_sectors, ie->sector_count);
+	cd9660_731(disk->sector, ie->load_rba);
+
+	ELTORITO_DPRINTF(("%s: boot indicator %d, media type %d, "
+	    "load segment %04x, system type %d, sector count %d, "
+	    "load rba %d\n", __func__, ie->boot_indicator[0],
+	    ie->media_type[0], disk->loadSegment, ie->system_type[0],
+	    disk->num_sectors, disk->sector));
+	return default_entry;
+}
+
+static struct boot_catalog_entry *
+cd9660_boot_setup_section_head(char platform)
+{
+	struct boot_catalog_entry *entry;
+	boot_catalog_section_header *sh;
+
+	entry = cd9660_init_boot_catalog_entry();
+	if (entry == NULL)
+		return NULL;
+
+	entry->entry_type = ET_ENTRY_SH;
+	sh = &entry->entry_data.SH;
+	/*
+	 * More by default.
+	 * The last one will manually be set to ET_SECTION_HEADER_LAST
+	 */
+	sh->header_indicator[0] = ET_SECTION_HEADER_MORE;
+	sh->platform_id[0] = platform;
+	sh->num_section_entries[0] = 0;
+	return entry;
+}
+
+static struct boot_catalog_entry *
+cd9660_boot_setup_section_entry(struct cd9660_boot_image *disk)
+{
+	struct boot_catalog_entry *entry;
+	boot_catalog_section_entry *se;
+	if ((entry = cd9660_init_boot_catalog_entry()) == NULL)
+		return NULL;
+
+	entry->entry_type = ET_ENTRY_SE;
+	se = &entry->entry_data.SE;
+
+	se->boot_indicator[0] = ET_BOOTABLE;
+	se->media_type[0] = disk->targetMode;
+	cd9660_721(disk->loadSegment, se->load_segment);
+	cd9660_721(disk->num_sectors, se->sector_count);
+	cd9660_731(disk->sector, se->load_rba);
+	return entry;
+}
+
+#if 0
+static u_char
+cd9660_boot_get_system_type(struct cd9660_boot_image *disk)
+{
+	/*
+		For hard drive booting, we need to examine the MBR to figure
+		out what the partition type is
+	*/
+	return 0;
+}
+#endif
+
+/*
+ * Set up the BVD, Boot catalog, and the boot entries, but do no writing
+ */
+int
+cd9660_setup_boot(iso9660_disk *diskStructure, int first_sector)
+{
+	int sector;
+	int used_sectors;
+	int num_entries = 0;
+	int catalog_sectors;
+	struct boot_catalog_entry *x86_head, *mac_head, *ppc_head, *efi_head,
+		*valid_entry, *default_entry, *temp, *head, **headp, *next;
+	struct cd9660_boot_image *tmp_disk;
+
+	headp = NULL;
+	x86_head = mac_head = ppc_head = efi_head = NULL;
+
+	/* If there are no boot disks, don't bother building boot information */
+	if (TAILQ_EMPTY(&diskStructure->boot_images))
+		return 0;
+
+	/* Point to catalog: For now assume it consumes one sector */
+	ELTORITO_DPRINTF(("Boot catalog will go in sector %d\n", first_sector));
+	diskStructure->boot_catalog_sector = first_sector;
+	cd9660_bothendian_dword(first_sector,
+		diskStructure->boot_descriptor->boot_catalog_pointer);
+
+	/* Step 1: Generate boot catalog */
+	/* Step 1a: Validation entry */
+	valid_entry = cd9660_boot_setup_validation_entry(ET_SYS_X86);
+	if (valid_entry == NULL)
+		return -1;
+
+	/*
+	 * Count how many boot images there are,
+	 * and how many sectors they consume.
+	 */
+	num_entries = 1;
+	used_sectors = 0;
+
+	TAILQ_FOREACH(tmp_disk, &diskStructure->boot_images, image_list) {
+		used_sectors += tmp_disk->num_sectors;
+
+		/* One default entry per image */
+		num_entries++;
+	}
+	catalog_sectors = howmany(num_entries * 0x20, diskStructure->sectorSize);
+	used_sectors += catalog_sectors;
+
+	if (diskStructure->verbose_level > 0) {
+		printf("%s: there will be %i entries consuming %i sectors. "
+		       "Catalog is %i sectors\n", __func__, num_entries,
+		       used_sectors, catalog_sectors);
+	}
+
+	/* Populate sector numbers */
+	sector = first_sector + catalog_sectors;
+	TAILQ_FOREACH(tmp_disk, &diskStructure->boot_images, image_list) {
+		tmp_disk->sector = sector;
+		sector += tmp_disk->num_sectors /
+		    (diskStructure->sectorSize / 512);
+	}
+
+	LIST_INSERT_HEAD(&diskStructure->boot_entries, valid_entry, ll_struct);
+
+	/* Step 1b: Initial/default entry */
+	/* TODO : PARAM */
+	if (default_boot_image != NULL) {
+		struct cd9660_boot_image *tcbi;
+		TAILQ_FOREACH(tcbi, &diskStructure->boot_images, image_list) {
+			if (tcbi == default_boot_image) {
+				tmp_disk = tcbi;
+				break;
+			}
+		}
+	}
+	if (tmp_disk == NULL)
+		tmp_disk = TAILQ_FIRST(&diskStructure->boot_images);
+	default_entry = cd9660_boot_setup_default_entry(tmp_disk);
+	if (default_entry == NULL) {
+		warnx("Error: memory allocation failed in cd9660_setup_boot");
+		return -1;
+	}
+
+	LIST_INSERT_AFTER(valid_entry, default_entry, ll_struct);
+
+	/* Todo: multiple default entries? */
+
+	tmp_disk = TAILQ_FIRST(&diskStructure->boot_images);
+
+	head = NULL;
+	temp = default_entry;
+
+	/* If multiple boot images are given : */
+	for (; tmp_disk != NULL; tmp_disk = TAILQ_NEXT(tmp_disk, image_list)) {
+		if (tmp_disk == default_boot_image)
+			continue;
+
+		/* Step 2: Section header */
+		switch (tmp_disk->platform_id) {
+		case ET_SYS_X86:
+			headp = &x86_head;
+			break;
+		case ET_SYS_PPC:
+			headp = &ppc_head;
+			break;
+		case ET_SYS_MAC:
+			headp = &mac_head;
+			break;
+		case ET_SYS_EFI:
+			headp = &efi_head;
+			break;
+		default:
+			warnx("%s: internal error: unknown system type",
+			    __func__);
+			return -1;
+		}
+
+		if (*headp == NULL) {
+			head =
+			  cd9660_boot_setup_section_head(tmp_disk->platform_id);
+			if (head == NULL) {
+				warnx("Error: memory allocation failed in "
+				      "cd9660_setup_boot");
+				return -1;
+			}
+			LIST_INSERT_AFTER(default_entry, head, ll_struct);
+			*headp = head;
+		} else
+			head = *headp;
+
+		head->entry_data.SH.num_section_entries[0]++;
+
+		/* Step 2a: Section entry and extensions */
+		temp = cd9660_boot_setup_section_entry(tmp_disk);
+		if (temp == NULL) {
+			warn("%s: cd9660_boot_setup_section_entry", __func__);
+			return -1;
+		}
+
+		while ((next = LIST_NEXT(head, ll_struct)) != NULL &&
+		       next->entry_type == ET_ENTRY_SE)
+			head = next;
+
+		LIST_INSERT_AFTER(head, temp, ll_struct);
+	}
+
+	/* Find the last Section Header entry and mark it as the last. */
+	head = NULL;
+	LIST_FOREACH(next, &diskStructure->boot_entries, ll_struct) {
+		if (next->entry_type == ET_ENTRY_SH)
+			head = next;
+	}
+	if (head != NULL)
+		head->entry_data.SH.header_indicator[0] = ET_SECTION_HEADER_LAST;
+
+	/* TODO: Remaining boot disks when implemented */
+
+	return first_sector + used_sectors;
+}
+
+int
+cd9660_setup_boot_volume_descriptor(iso9660_disk *diskStructure,
+    volume_descriptor *bvd)
+{
+	boot_volume_descriptor *bvdData =
+	    (boot_volume_descriptor*)bvd->volumeDescriptorData;
+
+	bvdData->boot_record_indicator[0] = ISO_VOLUME_DESCRIPTOR_BOOT;
+	memcpy(bvdData->identifier, ISO_VOLUME_DESCRIPTOR_STANDARD_ID, 5);
+	bvdData->version[0] = 1;
+	memcpy(bvdData->boot_system_identifier, ET_ID, 23);
+	memcpy(bvdData->identifier, ISO_VOLUME_DESCRIPTOR_STANDARD_ID, 5);
+	diskStructure->boot_descriptor =
+	    (boot_volume_descriptor*) bvd->volumeDescriptorData;
+	return 1;
+}
+
+static int
+cd9660_write_mbr_partition_entry(FILE *fd, int idx, off_t sector_start,
+    off_t nsectors, int type)
+{
+	uint8_t val;
+	uint32_t lba;
+
+	if (fseeko(fd, (off_t)(idx) * 16 + 0x1be, SEEK_SET) == -1)
+		err(1, "fseeko");
+	
+	val = 0x80; /* Bootable */
+	fwrite(&val, sizeof(val), 1, fd);
+
+	val = 0xff; /* CHS begin */
+	fwrite(&val, sizeof(val), 1, fd);
+	fwrite(&val, sizeof(val), 1, fd);
+	fwrite(&val, sizeof(val), 1, fd);
+
+	val = type; /* Part type */
+	fwrite(&val, sizeof(val), 1, fd);
+
+	val = 0xff; /* CHS end */
+	fwrite(&val, sizeof(val), 1, fd);
+	fwrite(&val, sizeof(val), 1, fd);
+	fwrite(&val, sizeof(val), 1, fd);
+
+	/* LBA extent */
+	lba = htole32(sector_start);
+	fwrite(&lba, sizeof(lba), 1, fd);
+	lba = htole32(nsectors);
+	fwrite(&lba, sizeof(lba), 1, fd);
+
+	return 0;
+}
+
+static int
+cd9660_write_apm_partition_entry(FILE *fd, int idx, int total_partitions,
+    off_t sector_start, off_t nsectors, off_t sector_size,
+    const char *part_name, const char *part_type)
+{
+	uint32_t apm32, part_status;
+	uint16_t apm16;
+
+	/* See Apple Tech Note 1189 for the details about the pmPartStatus
+	 * flags.
+	 * Below the flags which are default:
+	 * - IsValid     0x01
+	 * - IsAllocated 0x02
+	 * - IsReadable  0x10
+	 * - IsWritable  0x20
+	 */
+	part_status = 0x01 | 0x02 | 0x10 | 0x20;
+
+	if (fseeko(fd, (off_t)(idx + 1) * sector_size, SEEK_SET) == -1)
+		err(1, "fseeko");
+
+	/* Signature */
+	apm16 = htobe16(0x504d);
+	fwrite(&apm16, sizeof(apm16), 1, fd);
+	apm16 = 0;
+	fwrite(&apm16, sizeof(apm16), 1, fd);
+
+	/* Total number of partitions */
+	apm32 = htobe32(total_partitions);
+	fwrite(&apm32, sizeof(apm32), 1, fd);
+	/* Bounds */
+	apm32 = htobe32(sector_start);
+	fwrite(&apm32, sizeof(apm32), 1, fd);
+	apm32 = htobe32(nsectors);
+	fwrite(&apm32, sizeof(apm32), 1, fd);
+
+	fwrite(part_name, strlen(part_name) + 1, 1, fd);
+	fseek(fd, 32 - strlen(part_name) - 1, SEEK_CUR);
+	fwrite(part_type, strlen(part_type) + 1, 1, fd);
+	fseek(fd, 32 - strlen(part_type) - 1, SEEK_CUR);
+
+	apm32 = 0;
+	/* pmLgDataStart */
+	fwrite(&apm32, sizeof(apm32), 1, fd);
+	/* pmDataCnt */ 
+	apm32 = htobe32(nsectors);
+	fwrite(&apm32, sizeof(apm32), 1, fd);
+	/* pmPartStatus */
+	apm32 = htobe32(part_status);
+	fwrite(&apm32, sizeof(apm32), 1, fd);
+
+	return 0;
+}
+
+int
+cd9660_write_boot(iso9660_disk *diskStructure, FILE *fd)
+{
+	struct boot_catalog_entry *e;
+	struct cd9660_boot_image *t;
+	int apm_partitions = 0;
+	int mbr_partitions = 0;
+
+	/* write boot catalog */
+	if (fseeko(fd, (off_t)diskStructure->boot_catalog_sector *
+	    diskStructure->sectorSize, SEEK_SET) == -1)
+		err(1, "fseeko");
+
+	if (diskStructure->verbose_level > 0) {
+		printf("Writing boot catalog to sector %" PRId64 "\n",
+		    diskStructure->boot_catalog_sector);
+	}
+	LIST_FOREACH(e, &diskStructure->boot_entries, ll_struct) {
+		if (diskStructure->verbose_level > 0) {
+			printf("Writing catalog entry of type %d\n",
+			    e->entry_type);
+		}
+		/*
+		 * It doesn't matter which one gets written
+		 * since they are the same size
+		 */
+		fwrite(&(e->entry_data.VE), 1, 32, fd);
+	}
+	if (diskStructure->verbose_level > 0)
+		printf("Finished writing boot catalog\n");
+
+	/* copy boot images */
+	TAILQ_FOREACH(t, &diskStructure->boot_images, image_list) {
+		if (diskStructure->verbose_level > 0) {
+			printf("Writing boot image from %s to sectors %d\n",
+			    t->filename, t->sector);
+		}
+		cd9660_copy_file(diskStructure, fd, t->sector, t->filename);
+
+		if (t->system == ET_SYS_MAC) 
+			apm_partitions++;
+		if (t->system == ET_SYS_PPC) 
+			mbr_partitions++;
+	}
+
+	/* some systems need partition tables as well */
+	if (mbr_partitions > 0 || diskStructure->chrp_boot) {
+		uint16_t sig;
+
+		fseek(fd, 0x1fe, SEEK_SET);
+		sig = htole16(0xaa55);
+		fwrite(&sig, sizeof(sig), 1, fd);
+
+		mbr_partitions = 0;
+
+		/* Write ISO9660 descriptor, enclosing the whole disk */
+		if (diskStructure->chrp_boot)
+			cd9660_write_mbr_partition_entry(fd, mbr_partitions++,
+			    0, diskStructure->totalSectors *
+			    (diskStructure->sectorSize / 512), 0x96);
+
+		/* Write all partition entries */
+		TAILQ_FOREACH(t, &diskStructure->boot_images, image_list) {
+			if (t->system != ET_SYS_PPC)
+				continue;
+			cd9660_write_mbr_partition_entry(fd, mbr_partitions++,
+			    t->sector * (diskStructure->sectorSize / 512),
+			    t->num_sectors * (diskStructure->sectorSize / 512),
+			    0x41 /* PReP Boot */);
+		}
+	}
+
+	if (apm_partitions > 0) {
+		/* Write DDR and global APM info */
+		uint32_t apm32;
+		uint16_t apm16;
+		int total_parts;
+
+		fseek(fd, 0, SEEK_SET);
+		apm16 = htobe16(0x4552);
+		fwrite(&apm16, sizeof(apm16), 1, fd);
+		/* Device block size */
+		apm16 = htobe16(512);
+		fwrite(&apm16, sizeof(apm16), 1, fd);
+		/* Device block count */
+		apm32 = htobe32(diskStructure->totalSectors *
+		    (diskStructure->sectorSize / 512));
+		fwrite(&apm32, sizeof(apm32), 1, fd);
+		/* Device type/id */
+		apm16 = htobe16(1);
+		fwrite(&apm16, sizeof(apm16), 1, fd);
+		fwrite(&apm16, sizeof(apm16), 1, fd);
+
+		/* Count total needed entries */
+		total_parts = 2 + apm_partitions; /* Self + ISO9660 */
+
+		/* Write self-descriptor */
+		cd9660_write_apm_partition_entry(fd, 0, total_parts, 1,
+		    total_parts, 512, "Apple", "Apple_partition_map");
+
+		/* Write all partition entries */
+		apm_partitions = 0;
+		TAILQ_FOREACH(t, &diskStructure->boot_images, image_list) {
+			if (t->system != ET_SYS_MAC)
+				continue;
+
+			cd9660_write_apm_partition_entry(fd,
+			    1 + apm_partitions++, total_parts,
+			    t->sector * (diskStructure->sectorSize / 512),
+			    t->num_sectors * (diskStructure->sectorSize / 512),
+			    512, "CD Boot", "Apple_Bootstrap");
+		}
+
+		/* Write ISO9660 descriptor, enclosing the whole disk */
+		cd9660_write_apm_partition_entry(fd, 2 + apm_partitions,
+		    total_parts, 0, diskStructure->totalSectors *
+		    (diskStructure->sectorSize / 512), 512, "ISO9660",
+		    "CD_ROM_Mode_1");
+	}
+
+	return 0;
+}
diff --git a/usr.sbin/makefs/cd9660/cd9660_eltorito.h b/usr.sbin/makefs/cd9660/cd9660_eltorito.h
new file mode 100644
index 0000000000..1e7dc73827
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/cd9660_eltorito.h
@@ -0,0 +1,168 @@
+/*	$NetBSD: cd9660_eltorito.h,v 1.6 2017/01/24 11:22:43 nonaka Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-NetBSD
+ *
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam.  All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM 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.
+ *
+ * $FreeBSD: head/usr.sbin/makefs/cd9660/cd9660_eltorito.h 331843 2018-03-31 15:04:41Z benno $
+ */
+
+#ifndef _CD9660_ELTORITO_H_
+#define _CD9660_ELTORITO_H_
+
+/* Boot defines */
+#define	ET_ID		"EL TORITO SPECIFICATION"
+#define	ET_SYS_X86	0
+#define	ET_SYS_PPC	1
+#define	ET_SYS_MAC	2
+#define	ET_SYS_EFI	0xef	/* Platform ID at section header entry */
+
+#define ET_BOOT_ENTRY_SIZE 0x20
+
+#define	ET_BOOTABLE		0x88
+#define	ET_NOT_BOOTABLE	0
+
+#define	ET_MEDIA_NOEM	0
+#define	ET_MEDIA_12FDD			1
+#define	ET_MEDIA_144FDD			2
+#define	ET_MEDIA_288FDD			3
+#define	ET_MEDIA_HDD			4
+
+#define ET_INDICATOR_HEADERMORE	0x90
+#define ET_INDICATOR_HEADERLAST	0x91
+#define ET_INDICATOR_EXTENSION	0x44
+
+/*** Boot Structures ***/
+
+typedef struct _boot_volume_descriptor {
+	u_char boot_record_indicator	[ISODCL(0x00,0x00)];
+	u_char identifier		[ISODCL(0x01,0x05)];
+	u_char version			[ISODCL(0x06,0x06)];
+	u_char boot_system_identifier	[ISODCL(0x07,0x26)];
+	u_char unused1			[ISODCL(0x27,0x46)];
+	u_char boot_catalog_pointer	[ISODCL(0x47,0x4A)];
+	u_char unused2			[ISODCL(0x4B,0x7FF)];
+} boot_volume_descriptor;
+
+typedef struct _boot_catalog_validation_entry {
+	u_char header_id		[ISODCL(0x00,0x00)];
+	u_char platform_id		[ISODCL(0x01,0x01)];
+	u_char reserved1		[ISODCL(0x02,0x03)];
+	u_char manufacturer		[ISODCL(0x04,0x1B)];
+	u_char checksum			[ISODCL(0x1C,0x1D)];
+	u_char key			[ISODCL(0x1E,0x1F)];
+} boot_catalog_validation_entry;
+
+typedef struct _boot_catalog_initial_entry {
+	u_char boot_indicator		[ISODCL(0x00,0x00)];
+	u_char media_type		[ISODCL(0x01,0x01)];
+	u_char load_segment		[ISODCL(0x02,0x03)];
+	u_char system_type		[ISODCL(0x04,0x04)];
+	u_char unused_1			[ISODCL(0x05,0x05)];
+	u_char sector_count		[ISODCL(0x06,0x07)];
+	u_char load_rba			[ISODCL(0x08,0x0B)];
+	u_char unused_2			[ISODCL(0x0C,0x1F)];
+} boot_catalog_initial_entry;
+
+#define ET_SECTION_HEADER_MORE		0x90
+#define ET_SECTION_HEADER_LAST		0x91
+
+typedef struct _boot_catalog_section_header {
+	u_char header_indicator		[ISODCL(0x00,0x00)];
+	u_char platform_id		[ISODCL(0x01,0x01)];
+	u_char num_section_entries	[ISODCL(0x02,0x03)];
+	u_char id_string		[ISODCL(0x04,0x1F)];
+} boot_catalog_section_header;
+
+typedef struct _boot_catalog_section_entry {
+	u_char boot_indicator		[ISODCL(0x00,0x00)];
+	u_char media_type		[ISODCL(0x01,0x01)];
+	u_char load_segment		[ISODCL(0x02,0x03)];
+	u_char system_type		[ISODCL(0x04,0x04)];
+	u_char unused_1			[ISODCL(0x05,0x05)];
+	u_char sector_count		[ISODCL(0x06,0x07)];
+	u_char load_rba			[ISODCL(0x08,0x0B)];
+	u_char selection_criteria	[ISODCL(0x0C,0x0C)];
+	u_char vendor_criteria		[ISODCL(0x0D,0x1F)];
+} boot_catalog_section_entry;
+
+typedef struct _boot_catalog_section_entry_extension {
+	u_char extension_indicator	[ISODCL(0x00,0x00)];
+	u_char flags			[ISODCL(0x01,0x01)];
+	u_char vendor_criteria		[ISODCL(0x02,0x1F)];
+} boot_catalog_section_entry_extension;
+
+#define ET_ENTRY_VE 1
+#define ET_ENTRY_IE 2
+#define ET_ENTRY_SH 3
+#define ET_ENTRY_SE 4
+#define ET_ENTRY_EX 5
+
+struct boot_catalog_entry {
+	char entry_type;
+	union {
+		boot_catalog_validation_entry		VE;
+		boot_catalog_initial_entry 		IE;
+		boot_catalog_section_header		SH;
+		boot_catalog_section_entry		SE;
+		boot_catalog_section_entry_extension	EX;
+	} entry_data;
+
+	LIST_ENTRY(boot_catalog_entry) ll_struct;
+};
+
+/* Temporary structure */
+struct cd9660_boot_image {
+	char *filename;
+	int size;
+	int sector; 			/* copied to LoadRBA */
+	int num_sectors;
+	unsigned int loadSegment;
+	u_char targetMode;
+	u_char system;
+	u_char bootable;
+	u_char platform_id;		/* for section header entry */
+	/*
+	 * If the boot image exists in the filesystem
+	 * already, this is a pointer to that node. For the sake
+	 * of simplicity in future versions, this pointer is only
+	 * to the node in the primary volume. This SHOULD be done
+	 * via a hashtable lookup.
+	 */
+	struct _cd9660node *boot_image_node;
+	TAILQ_ENTRY(cd9660_boot_image) image_list;
+	int serialno;
+};
+
+
+#endif /* _CD9660_ELTORITO_H_ */
+
diff --git a/usr.sbin/makefs/cd9660/cd9660_strings.c b/usr.sbin/makefs/cd9660/cd9660_strings.c
new file mode 100644
index 0000000000..9e7741dee9
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/cd9660_strings.c
@@ -0,0 +1,122 @@
+/*	$NetBSD: cd9660_strings.c,v 1.4 2007/01/16 17:32:05 hubertf Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-NetBSD
+ *
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam.  All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM 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 <sys/mount.h>
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/cd9660/cd9660_strings.c 326276 2017-11-27 15:37:16Z pfg $");
+#include <sys/param.h>
+#include <ctype.h>
+
+#include "makefs.h"
+#include "cd9660.h"
+
+
+void
+cd9660_uppercase_characters(char *str, int len)
+{
+	int p;
+
+	for (p = 0; p < len; p++) {
+		if (islower((unsigned char)str[p]) )
+			str[p] -= 32;
+	}
+}
+
+static inline int
+cd9660_is_d_char(char c)
+{
+	return (isupper((unsigned char)c)
+		|| c == '_'
+		|| (c >= '0' && c <= '9'));
+}
+
+static inline int
+cd9660_is_a_char(char c)
+{
+	return (isupper((unsigned char)c)
+			|| c == '_'
+			|| (c >= '%' && c <= '?')
+			|| (c >= ' ' && c <= '\"'));
+}
+
+/*
+ * Test a string to see if it is composed of valid a characters
+ * @param const char* The string to test
+ * @returns int 1 if valid, 2 if valid if characters are converted to
+ *              upper case, 0 otherwise
+ */
+int
+cd9660_valid_a_chars(const char *str)
+{
+	const char *c = str;
+	int upperFound = 0;
+
+	while ((*c) != '\0') {
+		if (!(cd9660_is_a_char(*c))) {
+			if (islower((unsigned char)*c) )
+				upperFound = 1;
+			else
+				return 0;
+		}
+		c++;
+	}
+	return upperFound + 1;
+}
+
+/*
+ * Test a string to see if it is composed of valid d characters
+ * @param const char* The string to test
+ * @returns int 1 if valid, 2 if valid if characters are converted to
+ *                upper case, 0 otherwise
+ */
+int
+cd9660_valid_d_chars(const char *str)
+{
+	const char *c=str;
+	int upperFound = 0;
+
+	while ((*c) != '\0') {
+		if (!(cd9660_is_d_char(*c))) {
+			if (islower((unsigned char)*c) )
+				upperFound = 1;
+			else
+				return 0;
+		}
+		c++;
+	}
+	return upperFound + 1;
+}
diff --git a/usr.sbin/makefs/cd9660/cd9660_write.c b/usr.sbin/makefs/cd9660/cd9660_write.c
new file mode 100644
index 0000000000..23a2174cc8
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/cd9660_write.c
@@ -0,0 +1,514 @@
+/*	$NetBSD: cd9660_write.c,v 1.14 2011/01/04 09:48:21 wiz Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-NetBSD
+ *
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam.  All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM 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 "cd9660.h"
+#include "iso9660_rrip.h"
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/cd9660/cd9660_write.c 326276 2017-11-27 15:37:16Z pfg $");
+
+#include <util.h>
+
+static int cd9660_write_volume_descriptors(iso9660_disk *, FILE *);
+static int cd9660_write_path_table(iso9660_disk *, FILE *, off_t, int);
+static int cd9660_write_path_tables(iso9660_disk *, FILE *);
+static int cd9660_write_file(iso9660_disk *, FILE *, cd9660node *);
+static int cd9660_write_filedata(iso9660_disk *, FILE *, off_t,
+    const unsigned char *, int);
+#if 0
+static int cd9660_write_buffered(FILE *, off_t, int, const unsigned char *);
+#endif
+static void cd9660_write_rr(iso9660_disk *, FILE *, cd9660node *, off_t, off_t);
+
+/*
+ * Write the image
+ * Writes the entire image
+ * @param const char* The filename for the image
+ * @returns int 1 on success, 0 on failure
+ */
+int
+cd9660_write_image(iso9660_disk *diskStructure, const char* image)
+{
+	FILE *fd;
+	int status;
+	char buf[CD9660_SECTOR_SIZE];
+
+	if ((fd = fopen(image, "w+")) == NULL) {
+		err(EXIT_FAILURE, "%s: Can't open `%s' for writing", __func__,
+		    image);
+	}
+
+	if (diskStructure->verbose_level > 0)
+		printf("Writing image\n");
+
+	if (diskStructure->has_generic_bootimage) {
+		status = cd9660_copy_file(diskStructure, fd, 0,
+		    diskStructure->generic_bootimage);
+		if (status == 0) {
+			warnx("%s: Error writing generic boot image",
+			    __func__);
+			goto cleanup_bad_image;
+		}
+	}
+
+	/* Write the volume descriptors */
+	status = cd9660_write_volume_descriptors(diskStructure, fd);
+	if (status == 0) {
+		warnx("%s: Error writing volume descriptors to image",
+		    __func__);
+		goto cleanup_bad_image;
+	}
+
+	if (diskStructure->verbose_level > 0)
+		printf("Volume descriptors written\n");
+
+	/*
+	 * Write the path tables: there are actually four, but right
+	 * now we are only concearned with two.
+	 */
+	status = cd9660_write_path_tables(diskStructure, fd);
+	if (status == 0) {
+		warnx("%s: Error writing path tables to image", __func__);
+		goto cleanup_bad_image;
+	}
+
+	if (diskStructure->verbose_level > 0)
+		printf("Path tables written\n");
+
+	/* Write the directories and files */
+	status = cd9660_write_file(diskStructure, fd, diskStructure->rootNode);
+	if (status == 0) {
+		warnx("%s: Error writing files to image", __func__);
+		goto cleanup_bad_image;
+	}
+
+	if (diskStructure->is_bootable) {
+		cd9660_write_boot(diskStructure, fd);
+	}
+
+	/* Write padding bits. This is temporary */
+	memset(buf, 0, CD9660_SECTOR_SIZE);
+	cd9660_write_filedata(diskStructure, fd,
+	    diskStructure->totalSectors - 1, buf, 1);
+
+	if (diskStructure->verbose_level > 0)
+		printf("Files written\n");
+	fclose(fd);
+
+	if (diskStructure->verbose_level > 0)
+		printf("Image closed\n");
+	return 1;
+
+cleanup_bad_image:
+	fclose(fd);
+	if (!diskStructure->keep_bad_images)
+		unlink(image);
+	if (diskStructure->verbose_level > 0)
+		printf("Bad image cleaned up\n");
+	return 0;
+}
+
+static int
+cd9660_write_volume_descriptors(iso9660_disk *diskStructure, FILE *fd)
+{
+	volume_descriptor *vd_temp = diskStructure->firstVolumeDescriptor;
+
+	while (vd_temp != NULL) {
+		cd9660_write_filedata(diskStructure, fd, vd_temp->sector,
+		    vd_temp->volumeDescriptorData, 1);
+		vd_temp = vd_temp->next;
+	}
+	return 1;
+}
+
+/*
+ * Write out an individual path table
+ * Used just to keep redundant code to a minimum
+ * @param FILE *fd Valid file pointer
+ * @param int Sector to start writing path table to
+ * @param int Endian mode : BIG_ENDIAN or LITTLE_ENDIAN
+ * @returns int 1 on success, 0 on failure
+ */
+static int
+cd9660_write_path_table(iso9660_disk *diskStructure, FILE *fd, off_t sector,
+    int mode)
+{
+	int path_table_sectors = CD9660_BLOCKS(diskStructure->sectorSize,
+	    diskStructure->pathTableLength);
+	unsigned char *buffer;
+	unsigned char *buffer_head;
+	int len, ret;
+	path_table_entry temp_entry;
+	cd9660node *ptcur;
+
+	buffer = ecalloc(path_table_sectors, diskStructure->sectorSize);
+	buffer_head = buffer;
+
+	ptcur = diskStructure->rootNode;
+
+	while (ptcur != NULL) {
+		memset(&temp_entry, 0, sizeof(path_table_entry));
+		temp_entry.length[0] = ptcur->isoDirRecord->name_len[0];
+		temp_entry.extended_attribute_length[0] =
+		    ptcur->isoDirRecord->ext_attr_length[0];
+		memcpy(temp_entry.name, ptcur->isoDirRecord->name,
+		    temp_entry.length[0] + 1);
+
+		/* round up */
+		len = temp_entry.length[0] + 8 + (temp_entry.length[0] & 0x01);
+
+                /* todo: function pointers instead */
+		if (mode == LITTLE_ENDIAN) {
+			cd9660_731(ptcur->fileDataSector,
+			    temp_entry.first_sector);
+			cd9660_721((ptcur->parent == NULL ?
+				1 : ptcur->parent->ptnumber),
+			    temp_entry.parent_number);
+		} else {
+			cd9660_732(ptcur->fileDataSector,
+			    temp_entry.first_sector);
+			cd9660_722((ptcur->parent == NULL ?
+				1 : ptcur->parent->ptnumber),
+			    temp_entry.parent_number);
+		}
+
+
+		memcpy(buffer, &temp_entry, len);
+		buffer += len;
+
+		ptcur = ptcur->ptnext;
+	}
+
+	ret = cd9660_write_filedata(diskStructure, fd, sector, buffer_head,
+	    path_table_sectors);
+	free(buffer_head);
+	return ret;
+}
+
+
+/*
+ * Write out the path tables to disk
+ * Each file descriptor should be pointed to by the PVD, so we know which
+ * sector to copy them to. One thing to watch out for: the only path tables
+ * stored are in the endian mode that the application is compiled for. So,
+ * the first thing to do is write out that path table, then to write the one
+ * in the other endian mode requires to convert the endianness of each entry
+ * in the table. The best way to do this would be to create a temporary
+ * path_table_entry structure, then for each path table entry, copy it to
+ * the temporary entry, translate, then copy that to disk.
+ *
+ * @param FILE* Valid file descriptor
+ * @returns int 0 on failure, 1 on success
+ */
+static int
+cd9660_write_path_tables(iso9660_disk *diskStructure, FILE *fd)
+{
+	if (cd9660_write_path_table(diskStructure, fd,
+	    diskStructure->primaryLittleEndianTableSector, LITTLE_ENDIAN) == 0)
+		return 0;
+
+	if (cd9660_write_path_table(diskStructure, fd,
+	    diskStructure->primaryBigEndianTableSector, BIG_ENDIAN) == 0)
+		return 0;
+
+	/* @TODO: handle remaining two path tables */
+	return 1;
+}
+
+/*
+ * Write a file to disk
+ * Writes a file, its directory record, and its data to disk
+ * This file is designed to be called RECURSIVELY, so initially call it
+ * with the root node. All of the records should store what sector the
+ * file goes in, so no computation should be  necessary.
+ *
+ * @param int fd Valid file descriptor
+ * @param struct cd9660node* writenode Pointer to the file to be written
+ * @returns int 0 on failure, 1 on success
+ */
+static int
+cd9660_write_file(iso9660_disk *diskStructure, FILE *fd, cd9660node *writenode)
+{
+	char *buf;
+	char *temp_file_name;
+	int ret;
+	off_t working_sector;
+	int cur_sector_offset;
+	iso_directory_record_cd9660 temp_record;
+	cd9660node *temp;
+	int rv = 0;
+
+	/* Todo : clean up variables */
+
+	temp_file_name = ecalloc(CD9660MAXPATH + 1, 1);
+	buf = emalloc(diskStructure->sectorSize);
+	if ((writenode->level != 0) &&
+	    !(writenode->node->type & S_IFDIR)) {
+		fsinode *inode = writenode->node->inode;
+		/* Only attempt to write unwritten files that have length. */
+		if ((inode->flags & FI_WRITTEN) != 0) {
+			INODE_WARNX(("%s: skipping written inode %d", __func__,
+			    (int)inode->st.st_ino));
+		} else if (writenode->fileDataLength > 0) {
+			INODE_WARNX(("%s: writing inode %d blocks at %" PRIu32,
+			    __func__, (int)inode->st.st_ino, inode->ino));
+			inode->flags |= FI_WRITTEN;
+			if (writenode->node->contents == NULL)
+				cd9660_compute_full_filename(writenode,
+				    temp_file_name);
+			ret = cd9660_copy_file(diskStructure, fd,
+			    writenode->fileDataSector,
+			    (writenode->node->contents != NULL) ?
+			    writenode->node->contents : temp_file_name);
+			if (ret == 0)
+				goto out;
+		}
+	} else {
+		/*
+		 * Here is a new revelation that ECMA didn't explain
+		 * (at least not well).
+		 * ALL . and .. records store the name "\0" and "\1"
+		 * respectively. So, for each directory, we have to
+		 * make a new node.
+		 *
+		 * This is where it gets kinda messy, since we have to
+		 * be careful of sector boundaries
+		 */
+		cur_sector_offset = 0;
+		working_sector = writenode->fileDataSector;
+		if (fseeko(fd, working_sector * diskStructure->sectorSize,
+		    SEEK_SET) == -1)
+			err(1, "fseeko");
+
+		/*
+		 * Now loop over children, writing out their directory
+		 * records - beware of sector boundaries
+	 	 */
+		TAILQ_FOREACH(temp, &writenode->cn_children, cn_next_child) {
+			/*
+			 * Copy the temporary record and adjust its size
+			 * if necessary
+			 */
+			memcpy(&temp_record, temp->isoDirRecord,
+			    sizeof(iso_directory_record_cd9660));
+
+			temp_record.length[0] =
+			    cd9660_compute_record_size(diskStructure, temp);
+
+			if (temp_record.length[0] + cur_sector_offset >=
+			    diskStructure->sectorSize) {
+				cur_sector_offset = 0;
+				working_sector++;
+
+				/* Seek to the next sector. */
+				if (fseeko(fd, working_sector *
+				    diskStructure->sectorSize, SEEK_SET) == -1)
+					err(1, "fseeko");
+			}
+			/* Write out the basic ISO directory record */
+			(void)fwrite(&temp_record, 1,
+			    temp->isoDirRecord->length[0], fd);
+			if (diskStructure->rock_ridge_enabled) {
+				cd9660_write_rr(diskStructure, fd, temp,
+				    cur_sector_offset, working_sector);
+			}
+			if (fseeko(fd, working_sector *
+			    diskStructure->sectorSize + cur_sector_offset +
+			    temp_record.length[0] - temp->su_tail_size,
+			    SEEK_SET) == -1)
+				err(1, "fseeko");
+			if (temp->su_tail_size > 0)
+				fwrite(temp->su_tail_data, 1,
+				    temp->su_tail_size, fd);
+			if (ferror(fd)) {
+				warnx("%s: write error", __func__);
+				goto out;
+			}
+			cur_sector_offset += temp_record.length[0];
+
+		}
+
+		/*
+		 * Recurse on children.
+		 */
+		TAILQ_FOREACH(temp, &writenode->cn_children, cn_next_child) {
+			if ((ret = cd9660_write_file(diskStructure, fd, temp)) == 0)
+				goto out;
+		}
+	}
+	rv = 1;
+out:
+	free(temp_file_name);
+	free(buf);
+	return rv;
+}
+
+/*
+ * Wrapper function to write a buffer (one sector) to disk.
+ * Seeks and writes the buffer.
+ * NOTE: You dont NEED to use this function, but it might make your
+ * life easier if you have to write things that align to a sector
+ * (such as volume descriptors).
+ *
+ * @param int fd Valid file descriptor
+ * @param int sector Sector number to write to
+ * @param const unsigned char* Buffer to write. This should be the
+ *                             size of a sector, and if only a portion
+ *                             is written, the rest should be set to 0.
+ */
+static int
+cd9660_write_filedata(iso9660_disk *diskStructure, FILE *fd, off_t sector,
+    const unsigned char *buf, int numsecs)
+{
+	off_t curpos;
+	size_t success;
+
+	curpos = ftello(fd);
+
+	if (fseeko(fd, sector * diskStructure->sectorSize, SEEK_SET) == -1)
+		err(1, "fseeko");
+
+	success = fwrite(buf, diskStructure->sectorSize * numsecs, 1, fd);
+
+	if (fseeko(fd, curpos, SEEK_SET) == -1)
+		err(1, "fseeko");
+
+	if (success == 1)
+		success = diskStructure->sectorSize * numsecs;
+	return success;
+}
+
+#if 0
+static int
+cd9660_write_buffered(FILE *fd, off_t offset, int buff_len,
+		      const unsigned char* buffer)
+{
+	static int working_sector = -1;
+	static char buf[CD9660_SECTOR_SIZE];
+
+	return 0;
+}
+#endif
+
+int
+cd9660_copy_file(iso9660_disk *diskStructure, FILE *fd, off_t start_sector,
+    const char *filename)
+{
+	FILE *rf;
+	int bytes_read;
+	off_t sector = start_sector;
+	int buf_size = diskStructure->sectorSize;
+	char *buf;
+
+	buf = emalloc(buf_size);
+	if ((rf = fopen(filename, "rb")) == NULL) {
+		warn("%s: cannot open %s", __func__, filename);
+		free(buf);
+		return 0;
+	}
+
+	if (diskStructure->verbose_level > 1)
+		printf("Writing file: %s\n",filename);
+
+	if (fseeko(fd, start_sector * diskStructure->sectorSize, SEEK_SET) == -1)
+		err(1, "fseeko");
+
+	while (!feof(rf)) {
+		bytes_read = fread(buf,1,buf_size,rf);
+		if (ferror(rf)) {
+			warn("%s: fread", __func__);
+			free(buf);
+			(void)fclose(rf);
+			return 0;
+		}
+
+		fwrite(buf,1,bytes_read,fd);
+		if (ferror(fd)) {
+			warn("%s: fwrite", __func__);
+			free(buf);
+			(void)fclose(rf);
+			return 0;
+		}
+		sector++;
+	}
+
+	fclose(rf);
+	free(buf);
+	return 1;
+}
+
+static void
+cd9660_write_rr(iso9660_disk *diskStructure, FILE *fd, cd9660node *writenode,
+    off_t offset, off_t sector)
+{
+	int in_ca = 0;
+	struct ISO_SUSP_ATTRIBUTES *myattr;
+
+	offset += writenode->isoDirRecord->length[0];
+	if (fseeko(fd, sector * diskStructure->sectorSize + offset, SEEK_SET) ==
+	    -1)
+		err(1, "fseeko");
+	/* Offset now points at the end of the record */
+	TAILQ_FOREACH(myattr, &writenode->head, rr_ll) {
+		fwrite(&(myattr->attr), CD9660_SUSP_ENTRY_SIZE(myattr), 1, fd);
+
+		if (!in_ca) {
+			offset += CD9660_SUSP_ENTRY_SIZE(myattr);
+			if (myattr->last_in_suf) {
+				/*
+				 * Point the offset to the start of this
+				 * record's CE area
+				 */
+				if (fseeko(fd, ((off_t)diskStructure->
+				    susp_continuation_area_start_sector *
+				    diskStructure->sectorSize)
+				    + writenode->susp_entry_ce_start,
+				    SEEK_SET) == -1)
+					err(1, "fseeko");
+				in_ca = 1;
+			}
+		}
+	}
+
+	/*
+	 * If we had to go to the continuation area, head back to
+	 * where we should be.
+	 */
+	if (in_ca)
+		if (fseeko(fd, sector * diskStructure->sectorSize + offset,
+		    SEEK_SET) == -1)
+			err(1, "fseeko");
+}
diff --git a/usr.sbin/makefs/cd9660/iso9660_rrip.c b/usr.sbin/makefs/cd9660/iso9660_rrip.c
new file mode 100644
index 0000000000..672d799c2e
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/iso9660_rrip.c
@@ -0,0 +1,841 @@
+/*	$NetBSD: iso9660_rrip.c,v 1.14 2014/05/30 13:14:47 martin Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-NetBSD
+ *
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam.  All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM 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.
+ */
+/* This will hold all the function definitions
+ * defined in iso9660_rrip.h
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/cd9660/iso9660_rrip.c 326276 2017-11-27 15:37:16Z pfg $");
+
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <stdio.h>
+
+#include "makefs.h"
+#include "cd9660.h"
+#include "iso9660_rrip.h"
+#include <util.h>
+
+static void cd9660_rrip_initialize_inode(cd9660node *);
+static int cd9660_susp_handle_continuation(iso9660_disk *, cd9660node *);
+static int cd9660_susp_handle_continuation_common(iso9660_disk *, cd9660node *,
+    int);
+
+int
+cd9660_susp_initialize(iso9660_disk *diskStructure, cd9660node *node,
+    cd9660node *parent, cd9660node *grandparent)
+{
+	cd9660node *cn;
+	int r;
+
+	/* Make sure the node is not NULL. If it is, there are major problems */
+	assert(node != NULL);
+
+	if (!(node->type & CD9660_TYPE_DOT) &&
+	    !(node->type & CD9660_TYPE_DOTDOT))
+		TAILQ_INIT(&(node->head));
+	if (node->dot_record != 0)
+		TAILQ_INIT(&(node->dot_record->head));
+	if (node->dot_dot_record != 0)
+		TAILQ_INIT(&(node->dot_dot_record->head));
+
+	 /* SUSP specific entries here */
+	if ((r = cd9660_susp_initialize_node(diskStructure, node)) < 0)
+		return r;
+
+	/* currently called cd9660node_rrip_init_links */
+	r = cd9660_rrip_initialize_node(diskStructure, node, parent, grandparent);
+	if (r < 0)
+		return r;
+
+	/*
+	 * See if we need a CE record, and set all of the
+	 * associated counters.
+	 *
+	 * This should be called after all extensions. After
+	 * this is called, no new records should be added.
+	 */
+	if ((r = cd9660_susp_handle_continuation(diskStructure, node)) < 0)
+		return r;
+
+	/* Recurse on children. */
+	TAILQ_FOREACH(cn, &node->cn_children, cn_next_child) {
+		if ((r = cd9660_susp_initialize(diskStructure, cn, node, parent)) < 0)
+			return 0;
+	}
+	return 1;
+}
+
+int
+cd9660_susp_finalize(iso9660_disk *diskStructure, cd9660node *node)
+{
+	cd9660node *temp;
+	int r;
+
+	assert(node != NULL);
+
+	if (node == diskStructure->rootNode)
+		diskStructure->susp_continuation_area_current_free = 0;
+
+	if ((r = cd9660_susp_finalize_node(diskStructure, node)) < 0)
+		return r;
+	if ((r = cd9660_rrip_finalize_node(node)) < 0)
+		return r;
+
+	TAILQ_FOREACH(temp, &node->cn_children, cn_next_child) {
+		if ((r = cd9660_susp_finalize(diskStructure, temp)) < 0)
+			return r;
+	}
+	return 1;
+}
+
+/*
+ * If we really wanted to speed things up, we could have some sort of
+ * lookup table on the SUSP entry type that calls a functor. Or, we could
+ * combine the functions. These functions are kept separate to allow
+ * easier addition of other extensions.
+
+ * For the sake of simplicity and clarity, we won't be doing that for now.
+ */
+
+/*
+ * SUSP needs to update the following types:
+ * CE (continuation area)
+ */
+int
+cd9660_susp_finalize_node(iso9660_disk *diskStructure, cd9660node *node)
+{
+	struct ISO_SUSP_ATTRIBUTES *t;
+
+	/* Handle CE counters */
+	if (node->susp_entry_ce_length > 0) {
+		node->susp_entry_ce_start =
+		    diskStructure->susp_continuation_area_current_free;
+		diskStructure->susp_continuation_area_current_free +=
+		    node->susp_entry_ce_length;
+	}
+
+	TAILQ_FOREACH(t, &node->head, rr_ll) {
+		if (t->susp_type != SUSP_TYPE_SUSP ||
+		    t->entry_type != SUSP_ENTRY_SUSP_CE)
+			continue;
+		cd9660_bothendian_dword(
+			diskStructure->
+			  susp_continuation_area_start_sector,
+			t->attr.su_entry.CE.ca_sector);
+
+		cd9660_bothendian_dword(
+			diskStructure->
+			  susp_continuation_area_start_sector,
+			t->attr.su_entry.CE.ca_sector);
+		cd9660_bothendian_dword(node->susp_entry_ce_start,
+			t->attr.su_entry.CE.offset);
+		cd9660_bothendian_dword(node->susp_entry_ce_length,
+			t->attr.su_entry.CE.length);
+	}
+	return 0;
+}
+
+int
+cd9660_rrip_finalize_node(cd9660node *node)
+{
+	struct ISO_SUSP_ATTRIBUTES *t;
+
+	TAILQ_FOREACH(t, &node->head, rr_ll) {
+		if (t->susp_type != SUSP_TYPE_RRIP)
+			continue;
+		switch (t->entry_type) {
+		case SUSP_ENTRY_RRIP_CL:
+			/* Look at rr_relocated*/
+			if (node->rr_relocated == NULL)
+				return -1;
+			cd9660_bothendian_dword(
+				node->rr_relocated->fileDataSector,
+				(unsigned char *)
+				    t->attr.rr_entry.CL.dir_loc);
+			break;
+		case SUSP_ENTRY_RRIP_PL:
+			/* Look at rr_real_parent */
+			if (node->parent == NULL ||
+			    node->parent->rr_real_parent == NULL)
+				return -1;
+			cd9660_bothendian_dword(
+				node->parent->rr_real_parent->fileDataSector,
+				(unsigned char *)
+				    t->attr.rr_entry.PL.dir_loc);
+			break;
+		}
+	}
+	return 0;
+}
+
+static int
+cd9660_susp_handle_continuation_common(iso9660_disk *diskStructure,
+    cd9660node *node, int space)
+{
+	int ca_used, susp_used, susp_used_pre_ce, working;
+	struct ISO_SUSP_ATTRIBUTES *temp, *pre_ce, *last, *CE, *ST;
+
+	pre_ce = last = NULL;
+	working = 254 - space;
+	if (node->su_tail_size > 0)
+		/* Allow 4 bytes for "ST" record. */
+		working -= node->su_tail_size + 4;
+	/* printf("There are %i bytes to work with\n",working); */
+
+	susp_used_pre_ce = susp_used = 0;
+	ca_used = 0;
+	TAILQ_FOREACH(temp, &node->head, rr_ll) {
+		if (working < 0)
+			break;
+		/*
+		 * printf("SUSP Entry found, length is %i\n",
+		 * CD9660_SUSP_ENTRY_SIZE(temp));
+		 */
+		working -= CD9660_SUSP_ENTRY_SIZE(temp);
+		if (working >= 0) {
+			last = temp;
+			susp_used += CD9660_SUSP_ENTRY_SIZE(temp);
+		}
+		if (working >= 28) {
+			/*
+			 * Remember the last entry after which we
+			 * could insert a "CE" entry.
+			 */
+			pre_ce = last;
+			susp_used_pre_ce = susp_used;
+		}
+	}
+
+	/* A CE entry is needed */
+	if (working <= 0) {
+		CE = cd9660node_susp_create_node(SUSP_TYPE_SUSP,
+			SUSP_ENTRY_SUSP_CE, "CE", SUSP_LOC_ENTRY);
+		cd9660_susp_ce(CE, node);
+		/* This will automatically insert at the appropriate location */
+		if (pre_ce != NULL)
+			TAILQ_INSERT_AFTER(&node->head, pre_ce, CE, rr_ll);
+		else
+			TAILQ_INSERT_HEAD(&node->head, CE, rr_ll);
+		last = CE;
+		susp_used = susp_used_pre_ce + 28;
+		/* Count how much CA data is necessary */
+		for (temp = TAILQ_NEXT(last, rr_ll); temp != NULL;
+		     temp = TAILQ_NEXT(temp, rr_ll)) {
+			ca_used += CD9660_SUSP_ENTRY_SIZE(temp);
+		}
+	}
+
+	/* An ST entry is needed */
+	if (node->su_tail_size > 0) {
+		ST = cd9660node_susp_create_node(SUSP_TYPE_SUSP,
+		    SUSP_ENTRY_SUSP_ST, "ST", SUSP_LOC_ENTRY);
+		cd9660_susp_st(ST, node);
+		if (last != NULL)
+			TAILQ_INSERT_AFTER(&node->head, last, ST, rr_ll);
+		else
+			TAILQ_INSERT_HEAD(&node->head, ST, rr_ll);
+		last = ST;
+		susp_used += 4;
+	}
+	if (last != NULL)
+		last->last_in_suf = 1;
+
+	node->susp_entry_size = susp_used;
+	node->susp_entry_ce_length = ca_used;
+
+	diskStructure->susp_continuation_area_size += ca_used;
+	return 1;
+}
+
+/* See if a continuation entry is needed for each of the different types */
+static int
+cd9660_susp_handle_continuation(iso9660_disk *diskStructure, cd9660node *node)
+{
+	assert (node != NULL);
+
+	/* Entry */
+	if (cd9660_susp_handle_continuation_common(diskStructure,
+		node,(int)(node->isoDirRecord->length[0])) < 0)
+		return 0;
+
+	return 1;
+}
+
+int
+cd9660_susp_initialize_node(iso9660_disk *diskStructure, cd9660node *node)
+{
+	struct ISO_SUSP_ATTRIBUTES *temp;
+
+	/*
+	 * Requirements/notes:
+	 * CE: is added for us where needed
+	 * ST: not sure if it is even required, but if so, should be
+	 *     handled by the CE code
+	 * PD: isn't needed (though might be added for testing)
+	 * SP: is stored ONLY on the . record of the root directory
+	 * ES: not sure
+	 */
+
+	/* Check for root directory, add SP and ER if needed. */
+	if (node->type & CD9660_TYPE_DOT) {
+		if (node->parent == diskStructure->rootNode) {
+			temp = cd9660node_susp_create_node(SUSP_TYPE_SUSP,
+				SUSP_ENTRY_SUSP_SP, "SP", SUSP_LOC_DOT);
+			cd9660_susp_sp(temp, node);
+
+			/* Should be first entry. */
+			TAILQ_INSERT_HEAD(&node->head, temp, rr_ll);
+		}
+	}
+	return 1;
+}
+
+static void
+cd9660_rrip_initialize_inode(cd9660node *node)
+{
+	struct ISO_SUSP_ATTRIBUTES *attr;
+
+	/*
+	 * Inode dependent values - this may change,
+	 * but for now virtual files and directories do
+	 * not have an inode structure
+	 */
+
+	if ((node->node != NULL) && (node->node->inode != NULL)) {
+		/* PX - POSIX attributes */
+		attr = cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+			SUSP_ENTRY_RRIP_PX, "PX", SUSP_LOC_ENTRY);
+		cd9660node_rrip_px(attr, node->node);
+
+		TAILQ_INSERT_TAIL(&node->head, attr, rr_ll);
+
+		/* TF - timestamp */
+		attr = cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+			SUSP_ENTRY_RRIP_TF, "TF", SUSP_LOC_ENTRY);
+		cd9660node_rrip_tf(attr, node->node);
+		TAILQ_INSERT_TAIL(&node->head, attr, rr_ll);
+
+		/* SL - Symbolic link */
+		/* ?????????? Dan - why is this here? */
+		if (TAILQ_EMPTY(&node->cn_children) &&
+		    node->node->inode != NULL &&
+		    S_ISLNK(node->node->inode->st.st_mode))
+			cd9660_createSL(node);
+
+		/* PN - device number */
+		if (node->node->inode != NULL &&
+		    ((S_ISCHR(node->node->inode->st.st_mode) ||
+		     S_ISBLK(node->node->inode->st.st_mode)))) {
+			attr =
+			    cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+				SUSP_ENTRY_RRIP_PN, "PN",
+				SUSP_LOC_ENTRY);
+			cd9660node_rrip_pn(attr, node->node);
+			TAILQ_INSERT_TAIL(&node->head, attr, rr_ll);
+		}
+	}
+}
+
+int
+cd9660_rrip_initialize_node(iso9660_disk *diskStructure, cd9660node *node,
+    cd9660node *parent, cd9660node *grandparent)
+{
+	struct ISO_SUSP_ATTRIBUTES *current = NULL;
+
+	assert(node != NULL);
+
+	if (node->type & CD9660_TYPE_DOT) {
+		/*
+		 * Handle ER - should be the only entry to appear on
+		 * a "." record
+		 */
+		if (node->parent == diskStructure->rootNode) {
+			cd9660_susp_ER(node, 1, SUSP_RRIP_ER_EXT_ID,
+				SUSP_RRIP_ER_EXT_DES, SUSP_RRIP_ER_EXT_SRC);
+		}
+		if (parent != NULL && parent->node != NULL &&
+		    parent->node->inode != NULL) {
+			/* PX - POSIX attributes */
+			current = cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+				SUSP_ENTRY_RRIP_PX, "PX", SUSP_LOC_ENTRY);
+			cd9660node_rrip_px(current, parent->node);
+			TAILQ_INSERT_TAIL(&node->head, current, rr_ll);
+		}
+	} else if (node->type & CD9660_TYPE_DOTDOT) {
+		if (grandparent != NULL && grandparent->node != NULL &&
+		    grandparent->node->inode != NULL) {
+			/* PX - POSIX attributes */
+			current = cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+				SUSP_ENTRY_RRIP_PX, "PX", SUSP_LOC_ENTRY);
+			cd9660node_rrip_px(current, grandparent->node);
+			TAILQ_INSERT_TAIL(&node->head, current, rr_ll);
+		}
+		/* Handle PL */
+		if (parent != NULL && parent->rr_real_parent != NULL) {
+			current = cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+			    SUSP_ENTRY_RRIP_PL, "PL", SUSP_LOC_DOTDOT);
+			cd9660_rrip_PL(current,node);
+			TAILQ_INSERT_TAIL(&node->head, current, rr_ll);
+		}
+	} else {
+		cd9660_rrip_initialize_inode(node);
+
+		/*
+		 * Not every node needs a NM set - only if the name is
+		 * actually different. IE: If a file is TEST -> TEST,
+		 * no NM. test -> TEST, need a NM
+		 *
+		 * The rr_moved_dir needs to be assigned a NM record as well.
+		 */
+		if (node == diskStructure->rr_moved_dir) {
+			cd9660_rrip_add_NM(node, RRIP_DEFAULT_MOVE_DIR_NAME);
+		}
+		else if ((node->node != NULL) &&
+			((strlen(node->node->name) !=
+			    (uint8_t)node->isoDirRecord->name_len[0]) ||
+			(memcmp(node->node->name,node->isoDirRecord->name,
+				(uint8_t)node->isoDirRecord->name_len[0]) != 0))) {
+			cd9660_rrip_NM(node);
+		}
+
+
+
+		/* Rock ridge directory relocation code here. */
+
+		/* First handle the CL for the placeholder file. */
+		if (node->rr_relocated != NULL) {
+			current = cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+				SUSP_ENTRY_RRIP_CL, "CL", SUSP_LOC_ENTRY);
+			cd9660_rrip_CL(current, node);
+			TAILQ_INSERT_TAIL(&node->head, current, rr_ll);
+		}
+
+		/* Handle RE*/
+		if (node->rr_real_parent != NULL) {
+			current = cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+				SUSP_ENTRY_RRIP_RE, "RE", SUSP_LOC_ENTRY);
+			cd9660_rrip_RE(current,node);
+			TAILQ_INSERT_TAIL(&node->head, current, rr_ll);
+		}
+	}
+	return 1;
+}
+
+struct ISO_SUSP_ATTRIBUTES*
+cd9660node_susp_create_node(int susp_type, int entry_type, const char *type_id,
+			    int write_loc)
+{
+	struct ISO_SUSP_ATTRIBUTES* temp;
+
+	temp = emalloc(sizeof(*temp));
+	temp->susp_type = susp_type;
+	temp->entry_type = entry_type;
+	temp->last_in_suf = 0;
+	/* Phase this out */
+	temp->type_of[0] = type_id[0];
+	temp->type_of[1] = type_id[1];
+	temp->write_location = write_loc;
+
+	/*
+	 * Since the first four bytes is common, lets go ahead and
+	 * set the type identifier, since we are passing that to this
+	 * function anyhow.
+	 */
+	temp->attr.su_entry.SP.h.type[0] = type_id[0];
+	temp->attr.su_entry.SP.h.type[1] = type_id[1];
+	return temp;
+}
+
+int
+cd9660_rrip_PL(struct ISO_SUSP_ATTRIBUTES* p, cd9660node *node __unused)
+{
+	p->attr.rr_entry.PL.h.length[0] = 12;
+	p->attr.rr_entry.PL.h.version[0] = 1;
+	return 1;
+}
+
+int
+cd9660_rrip_CL(struct ISO_SUSP_ATTRIBUTES *p, cd9660node *node __unused)
+{
+	p->attr.rr_entry.CL.h.length[0] = 12;
+	p->attr.rr_entry.CL.h.version[0] = 1;
+	return 1;
+}
+
+int
+cd9660_rrip_RE(struct ISO_SUSP_ATTRIBUTES *p, cd9660node *node __unused)
+{
+	p->attr.rr_entry.RE.h.length[0] = 4;
+	p->attr.rr_entry.RE.h.version[0] = 1;
+	return 1;
+}
+
+void
+cd9660_createSL(cd9660node *node)
+{
+	struct ISO_SUSP_ATTRIBUTES* current;
+	int path_count, dir_count, done, i, j, dir_copied;
+	char temp_cr[255];
+	char temp_sl[255]; /* used in copying continuation entry*/
+	char* sl_ptr;
+
+	sl_ptr = node->node->symlink;
+
+	done = 0;
+	path_count = 0;
+	dir_count = 0;
+	dir_copied = 0;
+	current = cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+	    SUSP_ENTRY_RRIP_SL, "SL", SUSP_LOC_ENTRY);
+
+	current->attr.rr_entry.SL.h.version[0] = 1;
+	current->attr.rr_entry.SL.flags[0] = SL_FLAGS_NONE;
+
+	if (*sl_ptr == '/') {
+		temp_cr[0] = SL_FLAGS_ROOT;
+		temp_cr[1] = 0;
+		memcpy(current->attr.rr_entry.SL.component + path_count,
+		    temp_cr, 2);
+		path_count += 2;
+		sl_ptr++;
+	}
+
+	for (i = 0; i < (dir_count + 2); i++)
+		temp_cr[i] = '\0';
+
+	while (!done) {
+		while ((*sl_ptr != '/') && (*sl_ptr != '\0')) {
+			dir_copied = 1;
+			if (*sl_ptr == '.') {
+				if ((*(sl_ptr + 1) == '/') || (*(sl_ptr + 1)
+				     == '\0')) {
+					temp_cr[0] = SL_FLAGS_CURRENT;
+					sl_ptr++;
+				} else if(*(sl_ptr + 1) == '.') {
+					if ((*(sl_ptr + 2) == '/') ||
+					    (*(sl_ptr + 2) == '\0')) {
+						temp_cr[0] = SL_FLAGS_PARENT;
+						sl_ptr += 2;
+					}
+				} else {
+					temp_cr[dir_count+2] = *sl_ptr;
+					sl_ptr++;
+					dir_count++;
+				}
+			} else {
+				temp_cr[dir_count + 2] = *sl_ptr;
+				sl_ptr++;
+				dir_count++;
+			}
+		}
+
+		if ((path_count + dir_count) >= 249) {
+			current->attr.rr_entry.SL.flags[0] |= SL_FLAGS_CONTINUE;
+
+			j = 0;
+
+			if (path_count <= 249) {
+				while(j != (249 - path_count)) {
+					temp_sl[j] = temp_cr[j];
+					j++;
+				}
+				temp_sl[0] = SL_FLAGS_CONTINUE;
+				temp_sl[1] = j - 2;
+				memcpy(
+				    current->attr.rr_entry.SL.component +
+					path_count,
+				    temp_sl, j);
+			}
+
+			path_count += j;
+			current->attr.rr_entry.SL.h.length[0] = path_count + 5;
+			TAILQ_INSERT_TAIL(&node->head, current, rr_ll);
+			current= cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+			       SUSP_ENTRY_RRIP_SL, "SL", SUSP_LOC_ENTRY);
+			current->attr.rr_entry.SL.h.version[0] = 1;
+			current->attr.rr_entry.SL.flags[0] = SL_FLAGS_NONE;
+
+			path_count = 0;
+
+			if (dir_count > 2) {
+				while (j != dir_count + 2) {
+					current->attr.rr_entry.SL.component[
+					    path_count + 2] = temp_cr[j];
+					j++;
+					path_count++;
+				}
+				current->attr.rr_entry.SL.component[1]
+				    = path_count;
+				path_count+= 2;
+			} else {
+				while(j != dir_count) {
+					current->attr.rr_entry.SL.component[
+					    path_count+2] = temp_cr[j];
+					j++;
+					path_count++;
+				}
+			}
+		} else {
+			if (dir_copied == 1) {
+				temp_cr[1] = dir_count;
+				memcpy(current->attr.rr_entry.SL.component +
+					path_count,
+				    temp_cr, dir_count + 2);
+				path_count += dir_count + 2;
+			}
+		}
+
+		if (*sl_ptr == '\0') {
+			done = 1;
+			current->attr.rr_entry.SL.h.length[0] = path_count + 5;
+			TAILQ_INSERT_TAIL(&node->head, current, rr_ll);
+		} else {
+			sl_ptr++;
+			dir_count = 0;
+			dir_copied = 0;
+			for(i = 0; i < 255; i++) {
+				temp_cr[i] = '\0';
+			}
+		}
+	}
+}
+
+int
+cd9660node_rrip_px(struct ISO_SUSP_ATTRIBUTES *v, fsnode *pxinfo)
+{
+	v->attr.rr_entry.PX.h.length[0] = 44;
+	v->attr.rr_entry.PX.h.version[0] = 1;
+	cd9660_bothendian_dword(pxinfo->inode->st.st_mode,
+	    v->attr.rr_entry.PX.mode);
+	cd9660_bothendian_dword(pxinfo->inode->st.st_nlink,
+	    v->attr.rr_entry.PX.links);
+	cd9660_bothendian_dword(pxinfo->inode->st.st_uid,
+	    v->attr.rr_entry.PX.uid);
+	cd9660_bothendian_dword(pxinfo->inode->st.st_gid,
+	    v->attr.rr_entry.PX.gid);
+	cd9660_bothendian_dword(pxinfo->inode->st.st_ino,
+	    v->attr.rr_entry.PX.serial);
+
+	return 1;
+}
+
+int
+cd9660node_rrip_pn(struct ISO_SUSP_ATTRIBUTES *pn_field, fsnode *fnode)
+{
+	pn_field->attr.rr_entry.PN.h.length[0] = 20;
+	pn_field->attr.rr_entry.PN.h.version[0] = 1;
+
+	if (sizeof (fnode->inode->st.st_rdev) > 4)
+		cd9660_bothendian_dword(
+		    (uint64_t)fnode->inode->st.st_rdev >> 32,
+		    pn_field->attr.rr_entry.PN.high);
+	else
+		cd9660_bothendian_dword(0, pn_field->attr.rr_entry.PN.high);
+
+	cd9660_bothendian_dword(fnode->inode->st.st_rdev & 0xffffffff,
+		pn_field->attr.rr_entry.PN.low);
+	return 1;
+}
+
+#if 0
+int
+cd9660node_rrip_nm(struct ISO_SUSP_ATTRIBUTES *p, cd9660node *file_node)
+{
+	int nm_length = strlen(file_node->isoDirRecord->name) + 5;
+        p->attr.rr_entry.NM.h.type[0] = 'N';
+	p->attr.rr_entry.NM.h.type[1] = 'M';
+	sprintf(p->attr.rr_entry.NM.altname, "%s", file_node->isoDirRecord->name);
+	p->attr.rr_entry.NM.h.length[0] = (unsigned char)nm_length;
+	p->attr.rr_entry.NM.h.version[0] = (unsigned char)1;
+	p->attr.rr_entry.NM.flags[0] = (unsigned char) NM_PARENT;
+	return 1;
+}
+#endif
+
+int
+cd9660node_rrip_tf(struct ISO_SUSP_ATTRIBUTES *p, fsnode *_node)
+{
+	p->attr.rr_entry.TF.flags[0] = TF_MODIFY | TF_ACCESS | TF_ATTRIBUTES;
+	p->attr.rr_entry.TF.h.length[0] = 5;
+	p->attr.rr_entry.TF.h.version[0] = 1;
+
+	/*
+	 * Need to add creation time, backup time,
+	 * expiration time, and effective time.
+	 */
+
+	cd9660_time_915(p->attr.rr_entry.TF.timestamp,
+		_node->inode->st.st_atime);
+	p->attr.rr_entry.TF.h.length[0] += 7;
+
+	cd9660_time_915(p->attr.rr_entry.TF.timestamp + 7,
+		_node->inode->st.st_mtime);
+	p->attr.rr_entry.TF.h.length[0] += 7;
+
+	cd9660_time_915(p->attr.rr_entry.TF.timestamp + 14,
+		_node->inode->st.st_ctime);
+	p->attr.rr_entry.TF.h.length[0] += 7;
+	return 1;
+}
+
+int
+cd9660_susp_sp(struct ISO_SUSP_ATTRIBUTES *p, cd9660node *spinfo __unused)
+{
+	p->attr.su_entry.SP.h.length[0] = 7;
+	p->attr.su_entry.SP.h.version[0] = 1;
+	p->attr.su_entry.SP.check[0] = 0xBE;
+	p->attr.su_entry.SP.check[1] = 0xEF;
+	p->attr.su_entry.SP.len_skp[0] = 0;
+	return 1;
+}
+
+int
+cd9660_susp_st(struct ISO_SUSP_ATTRIBUTES *p, cd9660node *stinfo __unused)
+{
+	p->attr.su_entry.ST.h.type[0] = 'S';
+	p->attr.su_entry.ST.h.type[1] = 'T';
+	p->attr.su_entry.ST.h.length[0] = 4;
+	p->attr.su_entry.ST.h.version[0] = 1;
+	return 1;
+}
+
+int
+cd9660_susp_ce(struct ISO_SUSP_ATTRIBUTES *p, cd9660node *spinfo __unused)
+{
+	p->attr.su_entry.CE.h.length[0] = 28;
+	p->attr.su_entry.CE.h.version[0] = 1;
+	/* Other attributes dont matter right now, will be updated later */
+	return 1;
+}
+
+int
+cd9660_susp_pd(struct ISO_SUSP_ATTRIBUTES *p __unused, int length __unused)
+{
+	return 1;
+}
+
+void
+cd9660_rrip_add_NM(cd9660node *node, const char *name)
+{
+	int working,len;
+	const char *p;
+	struct ISO_SUSP_ATTRIBUTES *r;
+
+	/*
+	 * Each NM record has 254 byes to work with. This means that
+	 * the name data itself only has 249 bytes to work with. So, a
+	 * name with 251 characters would require two nm records.
+	 */
+	p = name;
+	working = 1;
+	while (working) {
+		r = cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+		    SUSP_ENTRY_RRIP_NM, "NM", SUSP_LOC_ENTRY);
+		r->attr.rr_entry.NM.h.version[0] = 1;
+		r->attr.rr_entry.NM.flags[0] = RRIP_NM_FLAGS_NONE;
+		len = strlen(p);
+
+		if (len > 249) {
+			len = 249;
+			r->attr.rr_entry.NM.flags[0] = RRIP_NM_FLAGS_CONTINUE;
+		} else {
+			working = 0;
+		}
+		memcpy(r->attr.rr_entry.NM.altname, p, len);
+		r->attr.rr_entry.NM.h.length[0] = 5 + len;
+
+		TAILQ_INSERT_TAIL(&node->head, r, rr_ll);
+
+		p += len;
+	}
+}
+
+void
+cd9660_rrip_NM(cd9660node *node)
+{
+	cd9660_rrip_add_NM(node, node->node->name);
+}
+
+struct ISO_SUSP_ATTRIBUTES*
+cd9660_susp_ER(cd9660node *node,
+	       u_char ext_version, const char* ext_id, const char* ext_des,
+	       const char* ext_src)
+{
+	int l;
+	struct ISO_SUSP_ATTRIBUTES *r;
+
+	r = cd9660node_susp_create_node(SUSP_TYPE_SUSP,
+			SUSP_ENTRY_SUSP_ER, "ER", SUSP_LOC_DOT);
+
+	/* Fixed data is 8 bytes */
+	r->attr.su_entry.ER.h.length[0] = 8;
+	r->attr.su_entry.ER.h.version[0] = 1;
+
+	r->attr.su_entry.ER.len_id[0] = (u_char)strlen(ext_id);
+	r->attr.su_entry.ER.len_des[0] = (u_char)strlen(ext_des);
+	r->attr.su_entry.ER.len_src[0] = (u_char)strlen(ext_src);
+
+	l = r->attr.su_entry.ER.len_id[0] +
+		r->attr.su_entry.ER.len_src[0] +
+		r->attr.su_entry.ER.len_des[0];
+
+	/* Everything must fit. */
+	assert(l + r->attr.su_entry.ER.h.length[0] <= 254);
+
+	r->attr.su_entry.ER.h.length[0] += (u_char)l;
+
+
+	r->attr.su_entry.ER.ext_ver[0] = ext_version;
+	memcpy(r->attr.su_entry.ER.ext_data, ext_id,
+		(int)r->attr.su_entry.ER.len_id[0]);
+	l = (int) r->attr.su_entry.ER.len_id[0];
+	memcpy(r->attr.su_entry.ER.ext_data + l,ext_des,
+		(int)r->attr.su_entry.ER.len_des[0]);
+
+	l += (int)r->attr.su_entry.ER.len_des[0];
+	memcpy(r->attr.su_entry.ER.ext_data + l,ext_src,
+		(int)r->attr.su_entry.ER.len_src[0]);
+
+	TAILQ_INSERT_TAIL(&node->head, r, rr_ll);
+	return r;
+}
+
+struct ISO_SUSP_ATTRIBUTES*
+cd9660_susp_ES(struct ISO_SUSP_ATTRIBUTES *last __unused, cd9660node *node __unused)
+{
+	return NULL;
+}
diff --git a/usr.sbin/makefs/cd9660/iso9660_rrip.h b/usr.sbin/makefs/cd9660/iso9660_rrip.h
new file mode 100644
index 0000000000..ae98f18733
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/iso9660_rrip.h
@@ -0,0 +1,294 @@
+/*	$NetBSD: iso9660_rrip.h,v 1.5 2009/01/10 22:06:29 bjh21 Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-NetBSD
+ *
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam.  All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM 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.
+ *
+ * $FreeBSD: head/usr.sbin/makefs/cd9660/iso9660_rrip.h 326276 2017-11-27 15:37:16Z pfg $
+ */
+#ifndef __ISO9660_RRIP_H__
+#define __ISO9660_RRIP_H__
+
+/*
+ * This will hold all the functions needed to
+ * write an ISO 9660 image with Rock Ridge Extensions
+ */
+
+/* For writing must use ISO_RRIP_EXTREF structure */
+
+#include "makefs.h"
+#include <cd9660_rrip.h>
+#include "cd9660.h"
+#include <sys/queue.h>
+
+#define	 PX_LENGTH	   0x2C
+#define	 PN_LENGTH	   0x14
+#define	 TF_CREATION	   0x00
+#define	 TF_MODIFY	   0x01
+#define	 TF_ACCESS	   0x02
+#define	 TF_ATTRIBUTES	   0x04
+#define	 TF_BACKUP	   0x08
+#define	 TF_EXPIRATION	   0x10
+#define	 TF_EFFECTIVE	   0x20
+#define	 TF_LONGFORM	   0x40
+#define  NM_CONTINUE	   0x80
+#define	 NM_CURRENT	   0x100
+#define	 NM_PARENT	   0x200
+
+
+#define	 SUSP_LOC_ENTRY	   0x01
+#define	 SUSP_LOC_DOT	   0x02
+#define	 SUSP_LOC_DOTDOT   0x04
+
+#define SUSP_TYPE_SUSP		1
+#define SUSP_TYPE_RRIP		2
+
+#define SUSP_ENTRY_SUSP_CE	1
+#define SUSP_ENTRY_SUSP_PD	2
+#define SUSP_ENTRY_SUSP_SP	3
+#define SUSP_ENTRY_SUSP_ST	4
+#define SUSP_ENTRY_SUSP_ER	5
+#define SUSP_ENTRY_SUSP_ES	6
+
+#define SUSP_ENTRY_RRIP_PX	1
+#define SUSP_ENTRY_RRIP_PN	2
+#define SUSP_ENTRY_RRIP_SL	3
+#define SUSP_ENTRY_RRIP_NM	4
+#define SUSP_ENTRY_RRIP_CL	5
+#define SUSP_ENTRY_RRIP_PL	6
+#define SUSP_ENTRY_RRIP_RE	7
+#define SUSP_ENTRY_RRIP_TF	8
+#define SUSP_ENTRY_RRIP_SF	9
+
+#define SUSP_RRIP_ER_EXT_ID "IEEE_P1282"
+#define SUSP_RRIP_ER_EXT_DES "THE IEEE P1282 PROTOCOL PROVIDES SUPPORT FOR POSIX FILE SYSTEM SEMANTICS."
+#define SUSP_RRIP_ER_EXT_SRC "PLEASE CONTACT THE IEEE STANDARDS DEPARTMENT, PISCATAWAY, NJ, USA FOR THE P1282 SPECIFICATION."
+
+#define SL_FLAGS_NONE	        0
+#define SL_FLAGS_CONTINUE       1
+#define SL_FLAGS_CURRENT        2
+#define SL_FLAGS_PARENT	        4
+#define SL_FLAGS_ROOT	        8
+
+typedef struct {
+	ISO_SUSP_HEADER		 h;
+	u_char mode		[ISODCL(5,12)];
+	u_char links		[ISODCL(13,20)];
+	u_char uid		[ISODCL(21,28)];
+	u_char gid		[ISODCL(29,36)];
+	u_char serial		[ISODCL(37,44)];
+} ISO_RRIP_PX;
+
+typedef struct {
+	ISO_SUSP_HEADER		 h;
+	u_char high		[ISODCL(5,12)];
+	u_char low		[ISODCL(13,20)];
+} ISO_RRIP_PN;
+
+typedef struct {
+	ISO_SUSP_HEADER		 h;
+	u_char flags		 [ISODCL ( 4, 4)];
+	u_char component	 [ISODCL ( 4, 256)];
+	u_int  nBytes;
+} ISO_RRIP_SL;
+
+typedef struct {
+	ISO_SUSP_HEADER		 h;
+	u_char flags		 [ISODCL ( 4, 4)];
+	u_char timestamp	 [ISODCL ( 5, 256)];
+} ISO_RRIP_TF;
+
+#define RRIP_NM_FLAGS_NONE 0x00
+#define RRIP_NM_FLAGS_CONTINUE 0x01
+#define RRIP_NM_FLAGS_CURRENT 0x02
+#define RRIP_NM_FLAGS_PARENT 0x04
+
+typedef struct {
+	ISO_SUSP_HEADER		 h;
+	u_char flags		 [ISODCL ( 4, 4)];
+	u_char altname		 [ISODCL ( 4, 256)];
+} ISO_RRIP_NM;
+
+/* Note that this is the same structure as cd9660_rrip.h : ISO_RRIP_CONT */
+typedef struct {
+	ISO_SUSP_HEADER		 h;
+	u_char ca_sector	 [ISODCL ( 5, 12)];
+	u_char offset		 [ISODCL ( 13, 20)];
+	u_char length		 [ISODCL ( 21, 28)];
+} ISO_SUSP_CE;
+
+typedef struct {
+	ISO_SUSP_HEADER		 h;
+	u_char padding_area	 [ISODCL ( 4, 256)];
+} ISO_SUSP_PD;
+
+typedef struct {
+	ISO_SUSP_HEADER		 h;
+	u_char check		 [ISODCL ( 4, 5)];
+	u_char len_skp		 [ISODCL ( 6, 6)];
+} ISO_SUSP_SP;
+
+typedef struct {
+	ISO_SUSP_HEADER		 h;
+} ISO_SUSP_ST;
+
+typedef struct {
+	ISO_SUSP_HEADER		 h;
+	u_char len_id		 [ISODCL ( 4, 4)];
+	u_char len_des		 [ISODCL ( 5, 5)];
+	u_char len_src		 [ISODCL ( 6, 6)];
+	u_char ext_ver		 [ISODCL ( 7, 7)];
+	u_char ext_data		 [ISODCL (8,256)];
+/*	u_char ext_id		 [ISODCL ( 8, 256)];
+	u_char ext_des		 [ISODCL ( 257, 513)];
+	u_char ext_src		 [ISODCL ( 514, 770)];*/
+} ISO_SUSP_ER;
+
+typedef struct {
+	ISO_SUSP_HEADER		 h;
+	u_char ext_seq		 [ISODCL ( 4, 4)];
+} ISO_SUSP_ES;
+
+typedef union {
+	ISO_RRIP_PX			PX;
+	ISO_RRIP_PN			PN;
+	ISO_RRIP_SL			SL;
+	ISO_RRIP_NM			NM;
+	ISO_RRIP_CLINK			CL;
+	ISO_RRIP_PLINK			PL;
+	ISO_RRIP_RELDIR			RE;
+	ISO_RRIP_TF			TF;
+} rrip_entry;
+
+typedef union {
+	ISO_SUSP_CE			CE;
+	ISO_SUSP_PD			PD;
+	ISO_SUSP_SP			SP;
+	ISO_SUSP_ST			ST;
+	ISO_SUSP_ER			ER;
+	ISO_SUSP_ES			ES;
+} susp_entry;
+
+typedef union {
+	susp_entry		  su_entry;
+	rrip_entry		  rr_entry;
+} SUSP_ENTRIES;
+
+struct ISO_SUSP_ATTRIBUTES {
+	SUSP_ENTRIES attr;
+	int type;
+	char type_of[2];
+	char last_in_suf;	/* last entry in the System Use Field? */
+	/* Dan's addons - will merge later. This allows use of a switch */
+	char susp_type; 	/* SUSP or RRIP */
+	char entry_type;	/* Record type */
+	char write_location;
+	TAILQ_ENTRY(ISO_SUSP_ATTRIBUTES) rr_ll;
+};
+
+#define CD9660_SUSP_ENTRY_SIZE(entry)\
+	((int) ((entry)->attr.su_entry.SP.h.length[0]))
+
+/* Recursive function - move later to func pointer code*/
+int cd9660_susp_finalize(iso9660_disk *, cd9660node *);
+
+/* These two operate on single nodes */
+int cd9660_susp_finalize_node(iso9660_disk *, cd9660node *);
+int cd9660_rrip_finalize_node(cd9660node *);
+
+/* POSIX File attribute */
+int cd9660node_rrip_px(struct ISO_SUSP_ATTRIBUTES *, fsnode *);
+
+/* Device number */
+int cd9660node_rrip_pn(struct ISO_SUSP_ATTRIBUTES *, fsnode *);
+
+/* Symbolic link */
+int cd9660node_rrip_SL(struct ISO_SUSP_ATTRIBUTES *, fsnode *);
+
+/* Alternate Name function */
+void cd9660_rrip_NM(cd9660node *);
+void cd9660_rrip_add_NM(cd9660node *,const char *);
+
+/* Parent and child link function */
+int cd9660_rrip_PL(struct ISO_SUSP_ATTRIBUTES *, cd9660node *);
+int cd9660_rrip_CL(struct ISO_SUSP_ATTRIBUTES *, cd9660node *);
+int cd9660_rrip_RE(struct ISO_SUSP_ATTRIBUTES *, cd9660node *);
+
+int cd9660node_rrip_tf(struct ISO_SUSP_ATTRIBUTES *, fsnode *);
+
+
+
+/*
+ * Relocation directory function. I'm not quite sure what
+ * sort of parameters are needed, but personally I don't think
+ * any parameters are needed except for the memory address where
+ * the information needs to be put in
+ */
+int cd9660node_rrip_re(void *, fsnode *);
+
+/*
+ * Don't know if this function is needed because it apparently is an
+ * optional feature that does not really need to be implemented but I
+ * thought I should add it anyway.
+ */
+int cd9660_susp_ce (struct ISO_SUSP_ATTRIBUTES *, cd9660node *);
+int cd9660_susp_pd (struct ISO_SUSP_ATTRIBUTES *, int);
+int cd9660_susp_sp (struct ISO_SUSP_ATTRIBUTES *, cd9660node *);
+int cd9660_susp_st (struct ISO_SUSP_ATTRIBUTES *, cd9660node *);
+
+struct ISO_SUSP_ATTRIBUTES *cd9660_susp_ER(cd9660node *, u_char, const char *,
+    const char *, const char *);
+struct ISO_SUSP_ATTRIBUTES *cd9660_susp_ES(struct ISO_SUSP_ATTRIBUTES*,
+    cd9660node *);
+
+
+/* Helper functions */
+
+/* Common SUSP/RRIP functions */
+int cd9660_susp_initialize(iso9660_disk *, cd9660node *, cd9660node *,
+    cd9660node *);
+int cd9660_susp_initialize_node(iso9660_disk *, cd9660node *);
+struct ISO_SUSP_ATTRIBUTES *cd9660node_susp_create_node(int, int, const char *,
+    int);
+struct ISO_SUSP_ATTRIBUTES *cd9660node_susp_add_entry(cd9660node *,
+    struct ISO_SUSP_ATTRIBUTES *, struct ISO_SUSP_ATTRIBUTES *, int);
+
+/* RRIP specific functions */
+int cd9660_rrip_initialize_node(iso9660_disk *, cd9660node *, cd9660node *,
+    cd9660node *);
+void cd9660_createSL(cd9660node *);
+
+/* Functions that probably can be removed */
+/* int cd9660node_initialize_node(int, char *); */
+
+
+#endif
diff --git a/usr.sbin/makefs/ffs.c b/usr.sbin/makefs/ffs.c
new file mode 100644
index 0000000000..ff51619157
--- /dev/null
+++ b/usr.sbin/makefs/ffs.c
@@ -0,0 +1,1185 @@
+/*	$NetBSD: ffs.c,v 1.45 2011/10/09 22:49:26 christos Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * Copyright (c) 2001 Wasabi Systems, Inc.
+ * All rights reserved.
+ *
+ * Written by Luke Mewburn for Wasabi Systems, Inc.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software developed for the NetBSD Project by
+ *      Wasabi Systems, Inc.
+ * 4. The name of Wasabi Systems, Inc. may not be used to endorse
+ *    or promote products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``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 WASABI SYSTEMS, INC
+ * 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.
+ */
+/*
+ * Copyright (c) 1982, 1986, 1989, 1993
+ *	The Regents of the University of California.  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.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
+ *
+ *	@(#)ffs_alloc.c	8.19 (Berkeley) 7/13/95
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/ffs.c 336736 2018-07-26 13:33:10Z emaste $");
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/param.h>
+
+#include <sys/mount.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <util.h>
+
+#include "makefs.h"
+#include "ffs.h"
+
+#if HAVE_STRUCT_STATVFS_F_IOSIZE && HAVE_FSTATVFS
+#include <sys/statvfs.h>
+#endif
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/dir.h>
+#include <ufs/ffs/fs.h>
+
+
+#include "ffs/ufs_bswap.h"
+#include "ffs/ufs_inode.h"
+#include "ffs/newfs_extern.h"
+#include "ffs/ffs_extern.h"
+
+#undef DIP
+#define DIP(dp, field) \
+	((ffs_opts->version == 1) ? \
+	(dp)->ffs1_din.di_##field : (dp)->ffs2_din.di_##field)
+
+/*
+ * Various file system defaults (cribbed from newfs(8)).
+ */
+#define	DFL_FRAGSIZE		4096		/* fragment size */
+#define	DFL_BLKSIZE		32768		/* block size */
+#define	DFL_SECSIZE		512		/* sector size */
+#define	DFL_CYLSPERGROUP	65536		/* cylinders per group */
+#define	DFL_FRAGSPERINODE	4		/* fragments per inode */
+#define	DFL_ROTDELAY		0		/* rotational delay */
+#define	DFL_NRPOS		1		/* rotational positions */
+#define	DFL_RPM			3600		/* rpm of disk */
+#define	DFL_NSECTORS		64		/* # of sectors */
+#define	DFL_NTRACKS		16		/* # of tracks */
+
+
+typedef struct {
+	u_char		*buf;		/* buf for directory */
+	doff_t		size;		/* full size of buf */
+	doff_t		cur;		/* offset of current entry */
+} dirbuf_t;
+
+
+static	int	ffs_create_image(const char *, fsinfo_t *);
+static	void	ffs_dump_fsinfo(fsinfo_t *);
+static	void	ffs_dump_dirbuf(dirbuf_t *, const char *, int);
+static	void	ffs_make_dirbuf(dirbuf_t *, const char *, fsnode *, int);
+static	int	ffs_populate_dir(const char *, fsnode *, fsinfo_t *);
+static	void	ffs_size_dir(fsnode *, fsinfo_t *);
+static	void	ffs_validate(const char *, fsnode *, fsinfo_t *);
+static	void	ffs_write_file(union dinode *, uint32_t, void *, fsinfo_t *);
+static	void	ffs_write_inode(union dinode *, uint32_t, const fsinfo_t *);
+static  void	*ffs_build_dinode1(struct ufs1_dinode *, dirbuf_t *, fsnode *,
+				 fsnode *, fsinfo_t *);
+static  void	*ffs_build_dinode2(struct ufs2_dinode *, dirbuf_t *, fsnode *,
+				 fsnode *, fsinfo_t *);
+
+
+	/* publicly visible functions */
+
+void
+ffs_prep_opts(fsinfo_t *fsopts)
+{
+	ffs_opt_t *ffs_opts = ecalloc(1, sizeof(*ffs_opts));
+
+	const option_t ffs_options[] = {
+	    { 'b', "bsize", &ffs_opts->bsize, OPT_INT32,
+	      1, INT_MAX, "block size" },
+	    { 'f', "fsize", &ffs_opts->fsize, OPT_INT32,
+	      1, INT_MAX, "fragment size" },
+	    { 'd', "density", &ffs_opts->density, OPT_INT32,
+	      1, INT_MAX, "bytes per inode" },
+	    { 'm', "minfree", &ffs_opts->minfree, OPT_INT32,
+	      0, 99, "minfree" },
+	    { 'M', "maxbpg", &ffs_opts->maxbpg, OPT_INT32,
+	      1, INT_MAX, "max blocks per file in a cg" },
+	    { 'a', "avgfilesize", &ffs_opts->avgfilesize, OPT_INT32,
+	      1, INT_MAX, "expected average file size" },
+	    { 'n', "avgfpdir", &ffs_opts->avgfpdir, OPT_INT32,
+	      1, INT_MAX, "expected # of files per directory" },
+	    { 'x', "extent", &ffs_opts->maxbsize, OPT_INT32,
+	      1, INT_MAX, "maximum # extent size" },
+	    { 'g', "maxbpcg", &ffs_opts->maxblkspercg, OPT_INT32,
+	      1, INT_MAX, "max # of blocks per group" },
+	    { 'v', "version", &ffs_opts->version, OPT_INT32,
+	      1, 2, "UFS version" },
+	    { 'o', "optimization", NULL, OPT_STRBUF,
+	      0, 0, "Optimization (time|space)" },
+	    { 'l', "label", ffs_opts->label, OPT_STRARRAY,
+	      1, sizeof(ffs_opts->label), "UFS label" },
+	    { 's', "softupdates", &ffs_opts->softupdates, OPT_INT32,
+	      0, 1, "enable softupdates" },
+	    { .name = NULL }
+	};
+
+	ffs_opts->bsize= -1;
+	ffs_opts->fsize= -1;
+	ffs_opts->cpg= -1;
+	ffs_opts->density= -1;
+	ffs_opts->minfree= -1;
+	ffs_opts->optimization= -1;
+	ffs_opts->maxcontig= -1;
+	ffs_opts->maxbpg= -1;
+	ffs_opts->avgfilesize= -1;
+	ffs_opts->avgfpdir= -1;
+	ffs_opts->version = 1;
+	ffs_opts->softupdates = 0;
+
+	fsopts->fs_specific = ffs_opts;
+	fsopts->fs_options = copy_opts(ffs_options);
+}
+
+void
+ffs_cleanup_opts(fsinfo_t *fsopts)
+{
+	free(fsopts->fs_specific);
+	free(fsopts->fs_options);
+}
+
+int
+ffs_parse_opts(const char *option, fsinfo_t *fsopts)
+{
+	ffs_opt_t	*ffs_opts = fsopts->fs_specific;
+	option_t *ffs_options = fsopts->fs_options;
+	char buf[1024];
+
+	int	rv;
+
+	assert(option != NULL);
+	assert(fsopts != NULL);
+	assert(ffs_opts != NULL);
+
+	if (debug & DEBUG_FS_PARSE_OPTS)
+		printf("ffs_parse_opts: got `%s'\n", option);
+
+	rv = set_option(ffs_options, option, buf, sizeof(buf));
+	if (rv == -1)
+		return 0;
+
+	if (ffs_options[rv].name == NULL)
+		abort();
+
+	switch (ffs_options[rv].letter) {
+	case 'o':
+		if (strcmp(buf, "time") == 0) {
+			ffs_opts->optimization = FS_OPTTIME;
+		} else if (strcmp(buf, "space") == 0) {
+			ffs_opts->optimization = FS_OPTSPACE;
+		} else {
+			warnx("Invalid optimization `%s'", buf);
+			return 0;
+		}
+		break;
+	default:
+		break;
+	}
+	return 1;
+}
+
+
+void
+ffs_makefs(const char *image, const char *dir, fsnode *root, fsinfo_t *fsopts)
+{
+	struct fs	*superblock;
+	struct timeval	start;
+
+	assert(image != NULL);
+	assert(dir != NULL);
+	assert(root != NULL);
+	assert(fsopts != NULL);
+
+	if (debug & DEBUG_FS_MAKEFS)
+		printf("ffs_makefs: image %s directory %s root %p\n",
+		    image, dir, root);
+
+		/* validate tree and options */
+	TIMER_START(start);
+	ffs_validate(dir, root, fsopts);
+	TIMER_RESULTS(start, "ffs_validate");
+
+	printf("Calculated size of `%s': %lld bytes, %lld inodes\n",
+	    image, (long long)fsopts->size, (long long)fsopts->inodes);
+
+		/* create image */
+	TIMER_START(start);
+	if (ffs_create_image(image, fsopts) == -1)
+		errx(1, "Image file `%s' not created.", image);
+	TIMER_RESULTS(start, "ffs_create_image");
+
+	fsopts->curinode = UFS_ROOTINO;
+
+	if (debug & DEBUG_FS_MAKEFS)
+		putchar('\n');
+
+		/* populate image */
+	printf("Populating `%s'\n", image);
+	TIMER_START(start);
+	if (! ffs_populate_dir(dir, root, fsopts))
+		errx(1, "Image file `%s' not populated.", image);
+	TIMER_RESULTS(start, "ffs_populate_dir");
+
+		/* ensure no outstanding buffers remain */
+	if (debug & DEBUG_FS_MAKEFS)
+		bcleanup();
+
+		/* update various superblock parameters */
+	superblock = fsopts->superblock;
+	superblock->fs_fmod = 0;
+	superblock->fs_old_cstotal.cs_ndir   = superblock->fs_cstotal.cs_ndir;
+	superblock->fs_old_cstotal.cs_nbfree = superblock->fs_cstotal.cs_nbfree;
+	superblock->fs_old_cstotal.cs_nifree = superblock->fs_cstotal.cs_nifree;
+	superblock->fs_old_cstotal.cs_nffree = superblock->fs_cstotal.cs_nffree;
+
+		/* write out superblock; image is now complete */
+	ffs_write_superblock(fsopts->superblock, fsopts);
+	if (close(fsopts->fd) == -1)
+		err(1, "Closing `%s'", image);
+	fsopts->fd = -1;
+	printf("Image `%s' complete\n", image);
+}
+
+	/* end of public functions */
+
+
+static void
+ffs_validate(const char *dir, fsnode *root, fsinfo_t *fsopts)
+{
+	int32_t	ncg = 1;
+#ifdef notyet
+	int32_t	spc, nspf, ncyl, fssize;
+#endif
+	ffs_opt_t	*ffs_opts = fsopts->fs_specific;
+
+	assert(dir != NULL);
+	assert(root != NULL);
+	assert(fsopts != NULL);
+	assert(ffs_opts != NULL);
+
+	if (debug & DEBUG_FS_VALIDATE) {
+		printf("ffs_validate: before defaults set:\n");
+		ffs_dump_fsinfo(fsopts);
+	}
+
+		/* set FFS defaults */
+	if (fsopts->sectorsize == -1)
+		fsopts->sectorsize = DFL_SECSIZE;
+	if (ffs_opts->fsize == -1)
+		ffs_opts->fsize = MAX(DFL_FRAGSIZE, fsopts->sectorsize);
+	if (ffs_opts->bsize == -1)
+		ffs_opts->bsize = MIN(DFL_BLKSIZE, 8 * ffs_opts->fsize);
+	if (ffs_opts->cpg == -1)
+		ffs_opts->cpg = DFL_CYLSPERGROUP;
+	else
+		ffs_opts->cpgflg = 1;
+				/* fsopts->density is set below */
+	if (ffs_opts->nsectors == -1)
+		ffs_opts->nsectors = DFL_NSECTORS;
+	if (ffs_opts->minfree == -1)
+		ffs_opts->minfree = MINFREE;
+	if (ffs_opts->optimization == -1)
+		ffs_opts->optimization = DEFAULTOPT;
+	if (ffs_opts->maxcontig == -1)
+		ffs_opts->maxcontig =
+		    MAX(1, MIN(MAXPHYS, FFS_MAXBSIZE) / ffs_opts->bsize);
+	/* XXX ondisk32 */
+	if (ffs_opts->maxbpg == -1)
+		ffs_opts->maxbpg = ffs_opts->bsize / sizeof(int32_t);
+	if (ffs_opts->avgfilesize == -1)
+		ffs_opts->avgfilesize = AVFILESIZ;
+	if (ffs_opts->avgfpdir == -1)
+		ffs_opts->avgfpdir = AFPDIR;
+
+	if (fsopts->maxsize > 0 &&
+	    roundup(fsopts->minsize, ffs_opts->bsize) > fsopts->maxsize)
+		errx(1, "`%s' minsize of %lld rounded up to ffs bsize of %d "
+		    "exceeds maxsize %lld.  Lower bsize, or round the minimum "
+		    "and maximum sizes to bsize.", dir,
+		    (long long)fsopts->minsize, ffs_opts->bsize,
+		    (long long)fsopts->maxsize);
+
+		/* calculate size of tree */
+	ffs_size_dir(root, fsopts);
+	fsopts->inodes += UFS_ROOTINO;		/* include first two inodes */
+
+	if (debug & DEBUG_FS_VALIDATE)
+		printf("ffs_validate: size of tree: %lld bytes, %lld inodes\n",
+		    (long long)fsopts->size, (long long)fsopts->inodes);
+
+		/* add requested slop */
+	fsopts->size += fsopts->freeblocks;
+	fsopts->inodes += fsopts->freefiles;
+	if (fsopts->freefilepc > 0)
+		fsopts->inodes =
+		    fsopts->inodes * (100 + fsopts->freefilepc) / 100;
+	if (fsopts->freeblockpc > 0)
+		fsopts->size =
+		    fsopts->size * (100 + fsopts->freeblockpc) / 100;
+
+		/* add space needed for superblocks */
+	/*
+	 * The old SBOFF (SBLOCK_UFS1) is used here because makefs is
+	 * typically used for small filesystems where space matters.
+	 * XXX make this an option.
+	 */
+	fsopts->size += (SBLOCK_UFS1 + SBLOCKSIZE) * ncg;
+		/* add space needed to store inodes, x3 for blockmaps, etc */
+	if (ffs_opts->version == 1)
+		fsopts->size += ncg * DINODE1_SIZE *
+		    roundup(fsopts->inodes / ncg, 
+			ffs_opts->bsize / DINODE1_SIZE);
+	else
+		fsopts->size += ncg * DINODE2_SIZE *
+		    roundup(fsopts->inodes / ncg, 
+			ffs_opts->bsize / DINODE2_SIZE);
+
+		/* add minfree */
+	if (ffs_opts->minfree > 0)
+		fsopts->size =
+		    fsopts->size * (100 + ffs_opts->minfree) / 100;
+	/*
+	 * XXX	any other fs slop to add, such as csum's, bitmaps, etc ??
+	 */
+
+	if (fsopts->size < fsopts->minsize)	/* ensure meets minimum size */
+		fsopts->size = fsopts->minsize;
+
+		/* round up to the next block */
+	fsopts->size = roundup(fsopts->size, ffs_opts->bsize);
+
+		/* round up to requested block size, if any */
+	if (fsopts->roundup > 0)
+		fsopts->size = roundup(fsopts->size, fsopts->roundup);
+
+		/* calculate density if necessary */
+	if (ffs_opts->density == -1)
+		ffs_opts->density = fsopts->size / fsopts->inodes + 1;
+
+	if (debug & DEBUG_FS_VALIDATE) {
+		printf("ffs_validate: after defaults set:\n");
+		ffs_dump_fsinfo(fsopts);
+		printf("ffs_validate: dir %s; %lld bytes, %lld inodes\n",
+		    dir, (long long)fsopts->size, (long long)fsopts->inodes);
+	}
+		/* now check calculated sizes vs requested sizes */
+	if (fsopts->maxsize > 0 && fsopts->size > fsopts->maxsize) {
+		errx(1, "`%s' size of %lld is larger than the maxsize of %lld.",
+		    dir, (long long)fsopts->size, (long long)fsopts->maxsize);
+	}
+}
+
+
+static void
+ffs_dump_fsinfo(fsinfo_t *f)
+{
+
+	ffs_opt_t	*fs = f->fs_specific;
+
+	printf("fsopts at %p\n", f);
+
+	printf("\tsize %lld, inodes %lld, curinode %u\n",
+	    (long long)f->size, (long long)f->inodes, f->curinode);
+
+	printf("\tminsize %lld, maxsize %lld\n",
+	    (long long)f->minsize, (long long)f->maxsize);
+	printf("\tfree files %lld, freefile %% %d\n",
+	    (long long)f->freefiles, f->freefilepc);
+	printf("\tfree blocks %lld, freeblock %% %d\n",
+	    (long long)f->freeblocks, f->freeblockpc);
+	printf("\tneedswap %d, sectorsize %d\n", f->needswap, f->sectorsize);
+
+	printf("\tbsize %d, fsize %d, cpg %d, density %d\n",
+	    fs->bsize, fs->fsize, fs->cpg, fs->density);
+	printf("\tnsectors %d, rpm %d, minfree %d\n",
+	    fs->nsectors, fs->rpm, fs->minfree);
+	printf("\tmaxcontig %d, maxbpg %d\n",
+	    fs->maxcontig, fs->maxbpg);
+	printf("\toptimization %s\n",
+	    fs->optimization == FS_OPTSPACE ? "space" : "time");
+}
+
+
+static int
+ffs_create_image(const char *image, fsinfo_t *fsopts)
+{
+#if HAVE_STRUCT_STATVFS_F_IOSIZE && HAVE_FSTATVFS
+	struct statvfs	sfs;
+#endif
+	struct fs	*fs;
+	char	*buf;
+	int	i, bufsize;
+	off_t	bufrem;
+	int	oflags = O_RDWR | O_CREAT;
+	time_t	tstamp;
+
+	assert (image != NULL);
+	assert (fsopts != NULL);
+
+		/* create image */
+	if (fsopts->offset == 0)
+		oflags |= O_TRUNC;
+	if ((fsopts->fd = open(image, oflags, 0666)) == -1) {
+		warn("Can't open `%s' for writing", image);
+		return (-1);
+	}
+
+		/* zero image */
+#if HAVE_STRUCT_STATVFS_F_IOSIZE && HAVE_FSTATVFS
+	if (fstatvfs(fsopts->fd, &sfs) == -1) {
+#endif
+		bufsize = 8192;
+#if HAVE_STRUCT_STATVFS_F_IOSIZE && HAVE_FSTATVFS
+		warn("can't fstatvfs `%s', using default %d byte chunk",
+		    image, bufsize);
+	} else
+		bufsize = sfs.f_iosize;
+#endif
+	bufrem = fsopts->size;
+	if (fsopts->sparse) {
+		if (ftruncate(fsopts->fd, bufrem) == -1) {
+			warn("sparse option disabled.");
+			fsopts->sparse = 0;
+		}
+	}
+	if (fsopts->sparse) {
+		/* File truncated at bufrem. Remaining is 0 */
+		bufrem = 0;
+		buf = NULL;
+	} else {
+		if (debug & DEBUG_FS_CREATE_IMAGE)
+			printf("zero-ing image `%s', %lld sectors, "
+			    "using %d byte chunks\n", image, (long long)bufrem,
+			    bufsize);
+		buf = ecalloc(1, bufsize);
+	}
+
+	if (fsopts->offset != 0)
+		if (lseek(fsopts->fd, fsopts->offset, SEEK_SET) == -1) {
+			warn("can't seek");
+			free(buf);
+			return -1;
+		}
+
+	while (bufrem > 0) {
+		i = write(fsopts->fd, buf, MIN(bufsize, bufrem));
+		if (i == -1) {
+			warn("zeroing image, %lld bytes to go",
+			    (long long)bufrem);
+			free(buf);
+			return (-1);
+		}
+		bufrem -= i;
+	}
+	if (buf)
+		free(buf);
+
+		/* make the file system */
+	if (debug & DEBUG_FS_CREATE_IMAGE)
+		printf("calling mkfs(\"%s\", ...)\n", image);
+
+	if (stampst.st_ino != 0)
+		tstamp = stampst.st_ctime;
+	else
+		tstamp = start_time.tv_sec;
+
+	srandom(tstamp);
+
+	fs = ffs_mkfs(image, fsopts, tstamp);
+	fsopts->superblock = (void *)fs;
+	if (debug & DEBUG_FS_CREATE_IMAGE) {
+		time_t t;
+
+		t = (time_t)((struct fs *)fsopts->superblock)->fs_time;
+		printf("mkfs returned %p; fs_time %s",
+		    fsopts->superblock, ctime(&t));
+		printf("fs totals: nbfree %lld, nffree %lld, nifree %lld, ndir %lld\n",
+		    (long long)fs->fs_cstotal.cs_nbfree,
+		    (long long)fs->fs_cstotal.cs_nffree,
+		    (long long)fs->fs_cstotal.cs_nifree,
+		    (long long)fs->fs_cstotal.cs_ndir);
+	}
+
+	if (fs->fs_cstotal.cs_nifree + UFS_ROOTINO < fsopts->inodes) {
+		warnx(
+		"Image file `%s' has %lld free inodes; %lld are required.",
+		    image,
+		    (long long)(fs->fs_cstotal.cs_nifree + UFS_ROOTINO),
+		    (long long)fsopts->inodes);
+		return (-1);
+	}
+	return (fsopts->fd);
+}
+
+
+static void
+ffs_size_dir(fsnode *root, fsinfo_t *fsopts)
+{
+	struct direct	tmpdir;
+	fsnode *	node;
+	int		curdirsize, this;
+	ffs_opt_t	*ffs_opts = fsopts->fs_specific;
+
+	/* node may be NULL (empty directory) */
+	assert(fsopts != NULL);
+	assert(ffs_opts != NULL);
+
+	if (debug & DEBUG_FS_SIZE_DIR)
+		printf("ffs_size_dir: entry: bytes %lld inodes %lld\n",
+		    (long long)fsopts->size, (long long)fsopts->inodes);
+
+#define	ADDDIRENT(e) do {						\
+	tmpdir.d_namlen = strlen((e));					\
+	this = DIRSIZ_SWAP(0, &tmpdir, 0);					\
+	if (debug & DEBUG_FS_SIZE_DIR_ADD_DIRENT)			\
+		printf("ADDDIRENT: was: %s (%d) this %d cur %d\n",	\
+		    e, tmpdir.d_namlen, this, curdirsize);		\
+	if (this + curdirsize > roundup(curdirsize, DIRBLKSIZ))		\
+		curdirsize = roundup(curdirsize, DIRBLKSIZ);		\
+	curdirsize += this;						\
+	if (debug & DEBUG_FS_SIZE_DIR_ADD_DIRENT)			\
+		printf("ADDDIRENT: now: %s (%d) this %d cur %d\n",	\
+		    e, tmpdir.d_namlen, this, curdirsize);		\
+} while (0);
+
+	/*
+	 * XXX	this needs to take into account extra space consumed
+	 *	by indirect blocks, etc.
+	 */
+#define	ADDSIZE(x) do {							\
+	fsopts->size += roundup((x), ffs_opts->fsize);			\
+} while (0);
+
+	curdirsize = 0;
+	for (node = root; node != NULL; node = node->next) {
+		ADDDIRENT(node->name);
+		if (node == root) {			/* we're at "." */
+			assert(strcmp(node->name, ".") == 0);
+			ADDDIRENT("..");
+		} else if ((node->inode->flags & FI_SIZED) == 0) {
+				/* don't count duplicate names */
+			node->inode->flags |= FI_SIZED;
+			if (debug & DEBUG_FS_SIZE_DIR_NODE)
+				printf("ffs_size_dir: `%s' size %lld\n",
+				    node->name,
+				    (long long)node->inode->st.st_size);
+			fsopts->inodes++;
+			if (node->type == S_IFREG)
+				ADDSIZE(node->inode->st.st_size);
+			if (node->type == S_IFLNK) {
+				size_t slen;
+
+				slen = strlen(node->symlink) + 1;
+				if (slen >= (ffs_opts->version == 1 ?
+						UFS1_MAXSYMLINKLEN :
+						UFS2_MAXSYMLINKLEN))
+					ADDSIZE(slen);
+			}
+		}
+		if (node->type == S_IFDIR)
+			ffs_size_dir(node->child, fsopts);
+	}
+	ADDSIZE(curdirsize);
+
+	if (debug & DEBUG_FS_SIZE_DIR)
+		printf("ffs_size_dir: exit: size %lld inodes %lld\n",
+		    (long long)fsopts->size, (long long)fsopts->inodes);
+}
+
+static void *
+ffs_build_dinode1(struct ufs1_dinode *dinp, dirbuf_t *dbufp, fsnode *cur,
+		 fsnode *root, fsinfo_t *fsopts)
+{
+	size_t slen;
+	void *membuf;
+	struct stat *st = stampst.st_ino != 0 ? &stampst : &cur->inode->st;
+
+	memset(dinp, 0, sizeof(*dinp));
+	dinp->di_mode = cur->inode->st.st_mode;
+	dinp->di_nlink = cur->inode->nlink;
+	dinp->di_size = cur->inode->st.st_size;
+#if HAVE_STRUCT_STAT_ST_FLAGS
+	dinp->di_flags = cur->inode->st.st_flags;
+#endif
+	dinp->di_gen = random();
+	dinp->di_uid = cur->inode->st.st_uid;
+	dinp->di_gid = cur->inode->st.st_gid;
+
+	dinp->di_atime = st->st_atime;
+	dinp->di_mtime = st->st_mtime;
+	dinp->di_ctime = st->st_ctime;
+#if HAVE_STRUCT_STAT_ST_MTIMENSEC
+	dinp->di_atimensec = st->st_atimensec;
+	dinp->di_mtimensec = st->st_mtimensec;
+	dinp->di_ctimensec = st->st_ctimensec;
+#endif
+		/* not set: di_db, di_ib, di_blocks, di_spare */
+
+	membuf = NULL;
+	if (cur == root) {			/* "."; write dirbuf */
+		membuf = dbufp->buf;
+		dinp->di_size = dbufp->size;
+	} else if (S_ISBLK(cur->type) || S_ISCHR(cur->type)) {
+		dinp->di_size = 0;	/* a device */
+		dinp->di_rdev =
+		    ufs_rw32(cur->inode->st.st_rdev, fsopts->needswap);
+	} else if (S_ISLNK(cur->type)) {	/* symlink */
+		slen = strlen(cur->symlink);
+		if (slen < UFS1_MAXSYMLINKLEN) {	/* short link */
+			memcpy(dinp->di_db, cur->symlink, slen);
+		} else
+			membuf = cur->symlink;
+		dinp->di_size = slen;
+	}
+	return membuf;
+}
+
+static void *
+ffs_build_dinode2(struct ufs2_dinode *dinp, dirbuf_t *dbufp, fsnode *cur,
+		 fsnode *root, fsinfo_t *fsopts)
+{
+	size_t slen;
+	void *membuf;
+	struct stat *st = stampst.st_ino != 0 ? &stampst : &cur->inode->st;
+
+	memset(dinp, 0, sizeof(*dinp));
+	dinp->di_mode = cur->inode->st.st_mode;
+	dinp->di_nlink = cur->inode->nlink;
+	dinp->di_size = cur->inode->st.st_size;
+#if HAVE_STRUCT_STAT_ST_FLAGS
+	dinp->di_flags = cur->inode->st.st_flags;
+#endif
+	dinp->di_gen = random();
+	dinp->di_uid = cur->inode->st.st_uid;
+	dinp->di_gid = cur->inode->st.st_gid;
+
+	dinp->di_atime = st->st_atime;
+	dinp->di_mtime = st->st_mtime;
+	dinp->di_ctime = st->st_ctime;
+#if HAVE_STRUCT_STAT_ST_MTIMENSEC
+	dinp->di_atimensec = st->st_atimensec;
+	dinp->di_mtimensec = st->st_mtimensec;
+	dinp->di_ctimensec = st->st_ctimensec;
+#endif
+#if HAVE_STRUCT_STAT_BIRTHTIME
+	dinp->di_birthtime = st->st_birthtime;
+	dinp->di_birthnsec = st->st_birthtimensec;
+#endif
+		/* not set: di_db, di_ib, di_blocks, di_spare */
+
+	membuf = NULL;
+	if (cur == root) {			/* "."; write dirbuf */
+		membuf = dbufp->buf;
+		dinp->di_size = dbufp->size;
+	} else if (S_ISBLK(cur->type) || S_ISCHR(cur->type)) {
+		dinp->di_size = 0;	/* a device */
+		dinp->di_rdev =
+		    ufs_rw64(cur->inode->st.st_rdev, fsopts->needswap);
+	} else if (S_ISLNK(cur->type)) {	/* symlink */
+		slen = strlen(cur->symlink);
+		if (slen < UFS2_MAXSYMLINKLEN) {	/* short link */
+			memcpy(dinp->di_db, cur->symlink, slen);
+		} else
+			membuf = cur->symlink;
+		dinp->di_size = slen;
+	}
+	return membuf;
+}
+
+static int
+ffs_populate_dir(const char *dir, fsnode *root, fsinfo_t *fsopts)
+{
+	fsnode		*cur;
+	dirbuf_t	dirbuf;
+	union dinode	din;
+	void		*membuf;
+	char		path[MAXPATHLEN + 1];
+	ffs_opt_t	*ffs_opts = fsopts->fs_specific;
+
+	assert(dir != NULL);
+	assert(root != NULL);
+	assert(fsopts != NULL);
+	assert(ffs_opts != NULL);
+
+	(void)memset(&dirbuf, 0, sizeof(dirbuf));
+
+	if (debug & DEBUG_FS_POPULATE)
+		printf("ffs_populate_dir: PASS 1  dir %s node %p\n", dir, root);
+
+		/*
+		 * pass 1: allocate inode numbers, build directory `file'
+		 */
+	for (cur = root; cur != NULL; cur = cur->next) {
+		if ((cur->inode->flags & FI_ALLOCATED) == 0) {
+			cur->inode->flags |= FI_ALLOCATED;
+			if (cur == root && cur->parent != NULL)
+				cur->inode->ino = cur->parent->inode->ino;
+			else {
+				cur->inode->ino = fsopts->curinode;
+				fsopts->curinode++;
+			}
+		}
+		ffs_make_dirbuf(&dirbuf, cur->name, cur, fsopts->needswap);
+		if (cur == root) {		/* we're at "."; add ".." */
+			ffs_make_dirbuf(&dirbuf, "..",
+			    cur->parent == NULL ? cur : cur->parent->first,
+			    fsopts->needswap);
+			root->inode->nlink++;	/* count my parent's link */
+		} else if (cur->child != NULL)
+			root->inode->nlink++;	/* count my child's link */
+
+		/*
+		 * XXX	possibly write file and long symlinks here,
+		 *	ensuring that blocks get written before inodes?
+		 *	otoh, this isn't a real filesystem, so who
+		 *	cares about ordering? :-)
+		 */
+	}
+	if (debug & DEBUG_FS_POPULATE_DIRBUF)
+		ffs_dump_dirbuf(&dirbuf, dir, fsopts->needswap);
+
+		/*
+		 * pass 2: write out dirbuf, then non-directories at this level
+		 */
+	if (debug & DEBUG_FS_POPULATE)
+		printf("ffs_populate_dir: PASS 2  dir %s\n", dir);
+	for (cur = root; cur != NULL; cur = cur->next) {
+		if (cur->inode->flags & FI_WRITTEN)
+			continue;		/* skip hard-linked entries */
+		cur->inode->flags |= FI_WRITTEN;
+
+		if (cur->contents == NULL) {
+			if (snprintf(path, sizeof(path), "%s/%s/%s", cur->root,
+			    cur->path, cur->name) >= (int)sizeof(path))
+				errx(1, "Pathname too long.");
+		}
+
+		if (cur->child != NULL)
+			continue;		/* child creates own inode */
+
+				/* build on-disk inode */
+		if (ffs_opts->version == 1)
+			membuf = ffs_build_dinode1(&din.ffs1_din, &dirbuf, cur,
+			    root, fsopts);
+		else
+			membuf = ffs_build_dinode2(&din.ffs2_din, &dirbuf, cur,
+			    root, fsopts);
+
+		if (debug & DEBUG_FS_POPULATE_NODE) {
+			printf("ffs_populate_dir: writing ino %d, %s",
+			    cur->inode->ino, inode_type(cur->type));
+			if (cur->inode->nlink > 1)
+				printf(", nlink %d", cur->inode->nlink);
+			putchar('\n');
+		}
+
+		if (membuf != NULL) {
+			ffs_write_file(&din, cur->inode->ino, membuf, fsopts);
+		} else if (S_ISREG(cur->type)) {
+			ffs_write_file(&din, cur->inode->ino,
+			    (cur->contents) ?  cur->contents : path, fsopts);
+		} else {
+			assert (! S_ISDIR(cur->type));
+			ffs_write_inode(&din, cur->inode->ino, fsopts);
+		}
+	}
+
+		/*
+		 * pass 3: write out sub-directories
+		 */
+	if (debug & DEBUG_FS_POPULATE)
+		printf("ffs_populate_dir: PASS 3  dir %s\n", dir);
+	for (cur = root; cur != NULL; cur = cur->next) {
+		if (cur->child == NULL)
+			continue;
+		if ((size_t)snprintf(path, sizeof(path), "%s/%s", dir,
+		    cur->name) >= sizeof(path))
+			errx(1, "Pathname too long.");
+		if (! ffs_populate_dir(path, cur->child, fsopts))
+			return (0);
+	}
+
+	if (debug & DEBUG_FS_POPULATE)
+		printf("ffs_populate_dir: DONE dir %s\n", dir);
+
+		/* cleanup */
+	if (dirbuf.buf != NULL)
+		free(dirbuf.buf);
+	return (1);
+}
+
+
+static void
+ffs_write_file(union dinode *din, uint32_t ino, void *buf, fsinfo_t *fsopts)
+{
+	int 	isfile, ffd;
+	char	*fbuf, *p;
+	off_t	bufleft, chunk, offset;
+	ssize_t nread;
+	struct inode	in;
+	struct buf *	bp;
+	ffs_opt_t	*ffs_opts = fsopts->fs_specific;
+	struct vnode vp = { fsopts, NULL };
+
+	assert (din != NULL);
+	assert (buf != NULL);
+	assert (fsopts != NULL);
+	assert (ffs_opts != NULL);
+
+	isfile = S_ISREG(DIP(din, mode));
+	fbuf = NULL;
+	ffd = -1;
+	p = NULL;
+
+	in.i_fs = (struct fs *)fsopts->superblock;
+	in.i_devvp = &vp;
+
+	if (debug & DEBUG_FS_WRITE_FILE) {
+		printf(
+		    "ffs_write_file: ino %u, din %p, isfile %d, %s, size %lld",
+		    ino, din, isfile, inode_type(DIP(din, mode) & S_IFMT),
+		    (long long)DIP(din, size));
+		if (isfile)
+			printf(", file '%s'\n", (char *)buf);
+		else
+			printf(", buffer %p\n", buf);
+	}
+
+	in.i_number = ino;
+	in.i_size = DIP(din, size);
+	if (ffs_opts->version == 1)
+		memcpy(&in.i_din.ffs1_din, &din->ffs1_din,
+		    sizeof(in.i_din.ffs1_din));
+	else
+		memcpy(&in.i_din.ffs2_din, &din->ffs2_din,
+		    sizeof(in.i_din.ffs2_din));
+
+	if (DIP(din, size) == 0)
+		goto write_inode_and_leave;		/* mmm, cheating */
+
+	if (isfile) {
+		fbuf = emalloc(ffs_opts->bsize);
+		if ((ffd = open((char *)buf, O_RDONLY, 0444)) == -1) {
+			warn("Can't open `%s' for reading", (char *)buf);
+			goto leave_ffs_write_file;
+		}
+	} else {
+		p = buf;
+	}
+
+	chunk = 0;
+	for (bufleft = DIP(din, size); bufleft > 0; bufleft -= chunk) {
+		chunk = MIN(bufleft, ffs_opts->bsize);
+		if (!isfile)
+			;
+		else if ((nread = read(ffd, fbuf, chunk)) == -1)
+			err(EXIT_FAILURE, "Reading `%s', %lld bytes to go",
+			    (char *)buf, (long long)bufleft);
+		else if (nread != chunk)
+			errx(EXIT_FAILURE, "Reading `%s', %lld bytes to go, "
+			    "read %zd bytes, expected %ju bytes, does "
+			    "metalog size= attribute mismatch source size?",
+			    (char *)buf, (long long)bufleft, nread,
+			    (uintmax_t)chunk);
+		else
+			p = fbuf;
+		offset = DIP(din, size) - bufleft;
+		if (debug & DEBUG_FS_WRITE_FILE_BLOCK)
+			printf(
+		"ffs_write_file: write %p offset %lld size %lld left %lld\n",
+			    p, (long long)offset,
+			    (long long)chunk, (long long)bufleft);
+	/*
+	 * XXX	if holey support is desired, do the check here
+	 *
+	 * XXX	might need to write out last bit in fragroundup
+	 *	sized chunk. however, ffs_balloc() handles this for us
+	 */
+		errno = ffs_balloc(&in, offset, chunk, &bp);
+ bad_ffs_write_file:
+		if (errno != 0)
+			err(1,
+			    "Writing inode %d (%s), bytes %lld + %lld",
+			    ino,
+			    isfile ? (char *)buf :
+			      inode_type(DIP(din, mode) & S_IFMT),
+			    (long long)offset, (long long)chunk);
+		memcpy(bp->b_data, p, chunk);
+		errno = bwrite(bp);
+		if (errno != 0)
+			goto bad_ffs_write_file;
+		brelse(bp);
+		if (!isfile)
+			p += chunk;
+	}
+  
+ write_inode_and_leave:
+	ffs_write_inode(&in.i_din, in.i_number, fsopts);
+
+ leave_ffs_write_file:
+	if (fbuf)
+		free(fbuf);
+	if (ffd != -1)
+		close(ffd);
+}
+
+
+static void
+ffs_dump_dirbuf(dirbuf_t *dbuf, const char *dir, int needswap)
+{
+	doff_t		i;
+	struct direct	*de;
+	uint16_t	reclen;
+
+	assert (dbuf != NULL);
+	assert (dir != NULL);
+	printf("ffs_dump_dirbuf: dir %s size %d cur %d\n",
+	    dir, dbuf->size, dbuf->cur);
+
+	for (i = 0; i < dbuf->size; ) {
+		de = (struct direct *)(dbuf->buf + i);
+		reclen = ufs_rw16(de->d_reclen, needswap);
+		printf(
+	    " inode %4d %7s offset %4d reclen %3d namlen %3d name %s\n",
+		    ufs_rw32(de->d_ino, needswap),
+		    inode_type(DTTOIF(de->d_type)), i, reclen,
+		    de->d_namlen, de->d_name);
+		i += reclen;
+		assert(reclen > 0);
+	}
+}
+
+static void
+ffs_make_dirbuf(dirbuf_t *dbuf, const char *name, fsnode *node, int needswap)
+{
+	struct direct	de, *dp;
+	uint16_t	llen, reclen;
+	u_char		*newbuf;
+
+	assert (dbuf != NULL);
+	assert (name != NULL);
+	assert (node != NULL);
+					/* create direct entry */
+	(void)memset(&de, 0, sizeof(de));
+	de.d_ino = ufs_rw32(node->inode->ino, needswap);
+	de.d_type = IFTODT(node->type);
+	de.d_namlen = (uint8_t)strlen(name);
+	strcpy(de.d_name, name);
+	reclen = DIRSIZ_SWAP(0, &de, needswap);
+	de.d_reclen = ufs_rw16(reclen, needswap);
+
+	dp = (struct direct *)(dbuf->buf + dbuf->cur);
+	llen = 0;
+	if (dp != NULL)
+		llen = DIRSIZ_SWAP(0, dp, needswap);
+
+	if (debug & DEBUG_FS_MAKE_DIRBUF)
+		printf(
+		    "ffs_make_dirbuf: dbuf siz %d cur %d lastlen %d\n"
+		    "  ino %d type %d reclen %d namlen %d name %.30s\n",
+		    dbuf->size, dbuf->cur, llen,
+		    ufs_rw32(de.d_ino, needswap), de.d_type, reclen,
+		    de.d_namlen, de.d_name);
+
+	if (reclen + dbuf->cur + llen > roundup(dbuf->size, DIRBLKSIZ)) {
+		if (debug & DEBUG_FS_MAKE_DIRBUF)
+			printf("ffs_make_dirbuf: growing buf to %d\n",
+			    dbuf->size + DIRBLKSIZ);
+		newbuf = erealloc(dbuf->buf, dbuf->size + DIRBLKSIZ);
+		dbuf->buf = newbuf;
+		dbuf->size += DIRBLKSIZ;
+		memset(dbuf->buf + dbuf->size - DIRBLKSIZ, 0, DIRBLKSIZ);
+		dbuf->cur = dbuf->size - DIRBLKSIZ;
+	} else if (dp) {			/* shrink end of previous */
+		dp->d_reclen = ufs_rw16(llen,needswap);
+		dbuf->cur += llen;
+	}
+	dp = (struct direct *)(dbuf->buf + dbuf->cur);
+	memcpy(dp, &de, reclen);
+	dp->d_reclen = ufs_rw16(dbuf->size - dbuf->cur, needswap);
+}
+
+/*
+ * cribbed from sys/ufs/ffs/ffs_alloc.c
+ */
+static void
+ffs_write_inode(union dinode *dp, uint32_t ino, const fsinfo_t *fsopts)
+{
+	char 		*buf;
+	struct ufs1_dinode *dp1;
+	struct ufs2_dinode *dp2, *dip;
+	struct cg	*cgp;
+	struct fs	*fs;
+	int		cg, cgino;
+	uint32_t	i;
+	daddr_t		d;
+	char		sbbuf[FFS_MAXBSIZE];
+	uint32_t	initediblk;
+	ffs_opt_t	*ffs_opts = fsopts->fs_specific;
+
+	assert (dp != NULL);
+	assert (ino > 0);
+	assert (fsopts != NULL);
+	assert (ffs_opts != NULL);
+
+	fs = (struct fs *)fsopts->superblock;
+	cg = ino_to_cg(fs, ino);
+	cgino = ino % fs->fs_ipg;
+	if (debug & DEBUG_FS_WRITE_INODE)
+		printf("ffs_write_inode: din %p ino %u cg %d cgino %d\n",
+		    dp, ino, cg, cgino);
+
+	ffs_rdfs(fsbtodb(fs, cgtod(fs, cg)), (int)fs->fs_cgsize, &sbbuf,
+	    fsopts);
+	cgp = (struct cg *)sbbuf;
+	if (!cg_chkmagic_swap(cgp, fsopts->needswap))
+		errx(1, "ffs_write_inode: cg %d: bad magic number", cg);
+
+	assert (isclr(cg_inosused_swap(cgp, fsopts->needswap), cgino));
+
+	buf = emalloc(fs->fs_bsize);
+	dp1 = (struct ufs1_dinode *)buf;
+	dp2 = (struct ufs2_dinode *)buf;
+
+	if (fs->fs_cstotal.cs_nifree == 0)
+		errx(1, "ffs_write_inode: fs out of inodes for ino %u",
+		    ino);
+	if (fs->fs_cs(fs, cg).cs_nifree == 0)
+		errx(1,
+		    "ffs_write_inode: cg %d out of inodes for ino %u",
+		    cg, ino);
+	setbit(cg_inosused_swap(cgp, fsopts->needswap), cgino);
+	ufs_add32(cgp->cg_cs.cs_nifree, -1, fsopts->needswap);
+	fs->fs_cstotal.cs_nifree--;
+	fs->fs_cs(fs, cg).cs_nifree--;
+	if (S_ISDIR(DIP(dp, mode))) {
+		ufs_add32(cgp->cg_cs.cs_ndir, 1, fsopts->needswap);
+		fs->fs_cstotal.cs_ndir++;
+		fs->fs_cs(fs, cg).cs_ndir++; 
+	}
+
+	/*
+	 * Initialize inode blocks on the fly for UFS2.
+	 */
+	initediblk = ufs_rw32(cgp->cg_initediblk, fsopts->needswap);
+	while (ffs_opts->version == 2 && cgino + INOPB(fs) > initediblk &&
+	    initediblk < ufs_rw32(cgp->cg_niblk, fsopts->needswap)) {
+		memset(buf, 0, fs->fs_bsize);
+		dip = (struct ufs2_dinode *)buf;
+		for (i = 0; i < INOPB(fs); i++) {
+			dip->di_gen = random();
+			dip++;
+		}
+		ffs_wtfs(fsbtodb(fs, ino_to_fsba(fs,
+				  cg * fs->fs_ipg + initediblk)),
+		    fs->fs_bsize, buf, fsopts);
+		initediblk += INOPB(fs);
+		cgp->cg_initediblk = ufs_rw32(initediblk, fsopts->needswap);
+	}
+
+
+	ffs_wtfs(fsbtodb(fs, cgtod(fs, cg)), (int)fs->fs_cgsize, &sbbuf,
+	    fsopts);
+
+					/* now write inode */
+	d = fsbtodb(fs, ino_to_fsba(fs, ino));
+	ffs_rdfs(d, fs->fs_bsize, buf, fsopts);
+	if (fsopts->needswap) {
+		if (ffs_opts->version == 1)
+			ffs_dinode1_swap(&dp->ffs1_din,
+			    &dp1[ino_to_fsbo(fs, ino)]);
+		else
+			ffs_dinode2_swap(&dp->ffs2_din,
+			    &dp2[ino_to_fsbo(fs, ino)]);
+	} else {
+		if (ffs_opts->version == 1)
+			dp1[ino_to_fsbo(fs, ino)] = dp->ffs1_din;
+		else
+			dp2[ino_to_fsbo(fs, ino)] = dp->ffs2_din;
+	}
+	ffs_wtfs(d, fs->fs_bsize, buf, fsopts);
+	free(buf);
+}
+
+void
+panic(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	vwarnx(fmt, ap);
+	va_end(ap);
+	exit(1);
+}
diff --git a/usr.sbin/makefs/ffs.h b/usr.sbin/makefs/ffs.h
new file mode 100644
index 0000000000..49edc3d2bf
--- /dev/null
+++ b/usr.sbin/makefs/ffs.h
@@ -0,0 +1,73 @@
+/*	$NetBSD: ffs.h,v 1.2 2004/12/20 20:51:42 jmc Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * Copyright (c) 2001-2003 Wasabi Systems, Inc.
+ * All rights reserved.
+ *
+ * Written by Luke Mewburn for Wasabi Systems, Inc.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software developed for the NetBSD Project by
+ *      Wasabi Systems, Inc.
+ * 4. The name of Wasabi Systems, Inc. may not be used to endorse
+ *    or promote products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``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 WASABI SYSTEMS, INC
+ * 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.
+ *
+ * $FreeBSD: head/usr.sbin/makefs/ffs.h 326276 2017-11-27 15:37:16Z pfg $
+ */
+
+#ifndef _FFS_H
+#define _FFS_H
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+typedef struct {
+	char	label[MAXVOLLEN];	/* volume name/label */
+	int	bsize;		/* block size */
+	int	fsize;		/* fragment size */
+	int	cpg;		/* cylinders per group */
+	int	cpgflg;		/* cpg was specified by user */
+	int	density;	/* bytes per inode */
+	int	ntracks;	/* number of tracks */
+	int	nsectors;	/* number of sectors */
+	int	rpm;		/* rpm */
+	int	minfree;	/* free space threshold */
+	int	optimization;	/* optimization (space or time) */
+	int	maxcontig;	/* max contiguous blocks to allocate */
+	int	rotdelay;	/* rotational delay between blocks */
+	int	maxbpg;		/* maximum blocks per file in a cyl group */
+	int	nrpos;		/* # of distinguished rotational positions */
+	int	avgfilesize;	/* expected average file size */
+	int	avgfpdir;	/* expected # of files per directory */
+	int	version;	/* filesystem version (1 = FFS, 2 = UFS2) */
+	int	maxbsize;	/* maximum extent size */
+	int	maxblkspercg;	/* max # of blocks per cylinder group */
+	int	softupdates;	/* soft updates */
+		/* XXX: support `old' file systems ? */
+} ffs_opt_t;
+
+#endif /* _FFS_H */
diff --git a/usr.sbin/makefs/ffs/Makefile.inc b/usr.sbin/makefs/ffs/Makefile.inc
new file mode 100644
index 0000000000..6a9792a92e
--- /dev/null
+++ b/usr.sbin/makefs/ffs/Makefile.inc
@@ -0,0 +1,9 @@
+#	$FreeBSD: head/usr.sbin/makefs/ffs/Makefile.inc 332844 2018-04-20 22:23:38Z emaste $
+#
+
+.PATH:	${SRCDIR}/ffs ${SRCTOP}/sys/ufs/ffs
+SRCS+=	ffs_alloc.c ffs_balloc.c ffs_bswap.c ffs_subr.c ufs_bmap.c
+SRCS+=	buf.c mkfs.c
+
+# Reach-over source from sys/ufs/ffs
+SRCS+=	ffs_tables.c
diff --git a/usr.sbin/makefs/ffs/buf.c b/usr.sbin/makefs/ffs/buf.c
new file mode 100644
index 0000000000..16329f70b5
--- /dev/null
+++ b/usr.sbin/makefs/ffs/buf.c
@@ -0,0 +1,216 @@
+/*	$NetBSD: buf.c,v 1.13 2004/06/20 22:20:18 jmc Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * Copyright (c) 2001 Wasabi Systems, Inc.
+ * All rights reserved.
+ *
+ * Written by Luke Mewburn for Wasabi Systems, Inc.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software developed for the NetBSD Project by
+ *      Wasabi Systems, Inc.
+ * 4. The name of Wasabi Systems, Inc. may not be used to endorse
+ *    or promote products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``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 WASABI SYSTEMS, INC
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/ffs/buf.c 336736 2018-07-26 13:33:10Z emaste $");
+
+#include <sys/param.h>
+#include <sys/time.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+
+#include "makefs.h"
+#include "buf.h"
+
+static TAILQ_HEAD(buftailhead,buf) buftail;
+
+int
+bread(struct vnode *vp, daddr_t blkno, int size, struct ucred *u1 __unused,
+    struct buf **bpp)
+{
+	off_t	offset;
+	ssize_t	rv;
+	fsinfo_t *fs = vp->fs;
+
+	assert (bpp != NULL);
+
+	if (debug & DEBUG_BUF_BREAD)
+		printf("%s: blkno %lld size %d\n", __func__, (long long)blkno,
+		    size);
+	*bpp = getblk(vp, blkno, size, 0, 0, 0);
+	offset = (*bpp)->b_blkno * fs->sectorsize + fs->offset;
+	if (debug & DEBUG_BUF_BREAD)
+		printf("%s: blkno %lld offset %lld bcount %ld\n", __func__,
+		    (long long)(*bpp)->b_blkno, (long long) offset,
+		    (*bpp)->b_bcount);
+	if (lseek((*bpp)->b_fs->fd, offset, SEEK_SET) == -1)
+		err(1, "%s: lseek %lld (%lld)", __func__,
+		    (long long)(*bpp)->b_blkno, (long long)offset);
+	rv = read((*bpp)->b_fs->fd, (*bpp)->b_data, (*bpp)->b_bcount);
+	if (debug & DEBUG_BUF_BREAD)
+		printf("%s: read %ld (%lld) returned %d\n", __func__,
+		    (*bpp)->b_bcount, (long long)offset, (int)rv);
+	if (rv == -1)				/* read error */
+		err(1, "%s: read %ld (%lld) returned %d", __func__,
+		    (*bpp)->b_bcount, (long long)offset, (int)rv);
+	else if (rv != (*bpp)->b_bcount)	/* short read */
+		err(1, "%s: read %ld (%lld) returned %d", __func__,
+		    (*bpp)->b_bcount, (long long)offset, (int)rv);
+	else
+		return (0);
+}
+
+void
+brelse(struct buf *bp)
+{
+
+	assert (bp != NULL);
+	assert (bp->b_data != NULL);
+
+	if (bp->b_lblkno < 0) {
+		/*
+		 * XXX	don't remove any buffers with negative logical block
+		 *	numbers (lblkno), so that we retain the mapping
+		 *	of negative lblkno -> real blkno that ffs_balloc()
+		 *	sets up.
+		 *
+		 *	if we instead released these buffers, and implemented
+		 *	ufs_strategy() (and ufs_bmaparray()) and called those
+		 *	from bread() and bwrite() to convert the lblkno to
+		 *	a real blkno, we'd add a lot more code & complexity
+		 *	and reading off disk, for little gain, because this
+		 *	simple hack works for our purpose.
+		 */
+		bp->b_bcount = 0;
+		return;
+	}
+
+	TAILQ_REMOVE(&buftail, bp, b_tailq);
+	free(bp->b_data);
+	free(bp);
+}
+
+int
+bwrite(struct buf *bp)
+{
+	off_t	offset;
+	ssize_t	rv;
+	fsinfo_t *fs = bp->b_fs;
+
+	assert (bp != NULL);
+	offset = bp->b_blkno * fs->sectorsize + fs->offset;
+	if (debug & DEBUG_BUF_BWRITE)
+		printf("bwrite: blkno %lld offset %lld bcount %ld\n",
+		    (long long)bp->b_blkno, (long long) offset,
+		    bp->b_bcount);
+	if (lseek(bp->b_fs->fd, offset, SEEK_SET) == -1)
+		return (errno);
+	rv = write(bp->b_fs->fd, bp->b_data, bp->b_bcount);
+	if (debug & DEBUG_BUF_BWRITE)
+		printf("bwrite: write %ld (offset %lld) returned %lld\n",
+		    bp->b_bcount, (long long)offset, (long long)rv);
+	if (rv == bp->b_bcount)
+		return (0);
+	else if (rv == -1)		/* write error */
+		return (errno);
+	else				/* short write ? */
+		return (EAGAIN);
+}
+
+void
+bcleanup(void)
+{
+	struct buf *bp;
+
+	/*
+	 * XXX	this really shouldn't be necessary, but i'm curious to
+	 *	know why there's still some buffers lying around that
+	 *	aren't brelse()d
+	 */
+
+	if (TAILQ_EMPTY(&buftail))
+		return;
+
+	printf("bcleanup: unflushed buffers:\n");
+	TAILQ_FOREACH(bp, &buftail, b_tailq) {
+		printf("\tlblkno %10lld  blkno %10lld  count %6ld  bufsize %6ld\n",
+		    (long long)bp->b_lblkno, (long long)bp->b_blkno,
+		    bp->b_bcount, bp->b_bufsize);
+	}
+	printf("bcleanup: done\n");
+}
+
+struct buf *
+getblk(struct vnode *vp, daddr_t blkno, int size, int u1 __unused,
+    int u2 __unused, int u3 __unused)
+{
+	static int buftailinitted;
+	struct buf *bp;
+	void *n;
+
+	if (debug & DEBUG_BUF_GETBLK)
+		printf("getblk: blkno %lld size %d\n", (long long)blkno, size);
+
+	bp = NULL;
+	if (!buftailinitted) {
+		if (debug & DEBUG_BUF_GETBLK)
+			printf("getblk: initialising tailq\n");
+		TAILQ_INIT(&buftail);
+		buftailinitted = 1;
+	} else {
+		TAILQ_FOREACH(bp, &buftail, b_tailq) {
+			if (bp->b_lblkno != blkno)
+				continue;
+			break;
+		}
+	}
+	if (bp == NULL) {
+		bp = ecalloc(1, sizeof(*bp));
+		bp->b_bufsize = 0;
+		bp->b_blkno = bp->b_lblkno = blkno;
+		bp->b_fs = vp->fs;
+		bp->b_data = NULL;
+		TAILQ_INSERT_HEAD(&buftail, bp, b_tailq);
+	}
+	bp->b_bcount = size;
+	if (bp->b_data == NULL || bp->b_bcount > bp->b_bufsize) {
+		n = erealloc(bp->b_data, size);
+		memset(n, 0, size);
+		bp->b_data = n;
+		bp->b_bufsize = size;
+	}
+
+	return (bp);
+}
diff --git a/usr.sbin/makefs/ffs/buf.h b/usr.sbin/makefs/ffs/buf.h
new file mode 100644
index 0000000000..8df6be5b61
--- /dev/null
+++ b/usr.sbin/makefs/ffs/buf.h
@@ -0,0 +1,77 @@
+/*	$NetBSD: buf.h,v 1.3 2001/11/02 03:12:49 lukem Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * Copyright (c) 2001 Wasabi Systems, Inc.
+ * All rights reserved.
+ *
+ * Written by Luke Mewburn for Wasabi Systems, Inc.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software developed for the NetBSD Project by
+ *      Wasabi Systems, Inc.
+ * 4. The name of Wasabi Systems, Inc. may not be used to endorse
+ *    or promote products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``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 WASABI SYSTEMS, INC
+ * 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.
+ *
+ * $FreeBSD: head/usr.sbin/makefs/ffs/buf.h 336736 2018-07-26 13:33:10Z emaste $
+ */
+
+#ifndef _FFS_BUF_H
+#define	_FFS_BUF_H
+
+#include <sys/param.h>
+#include <sys/queue.h>
+
+struct makefs_fsinfo;
+struct ucred;
+
+struct vnode {
+	struct makefs_fsinfo *fs;
+	void *v_data;
+};
+
+struct buf {
+	void *		b_data;
+	long		b_bufsize;
+	long		b_bcount;
+	daddr_t		b_blkno;
+	daddr_t		b_lblkno;
+	struct makefs_fsinfo *b_fs;
+
+	TAILQ_ENTRY(buf)	b_tailq;
+};
+
+void		bcleanup(void);
+int		bread(struct vnode *, daddr_t, int, struct ucred *,
+    struct buf **);
+void		brelse(struct buf *);
+int		bwrite(struct buf *);
+struct buf *	getblk(struct vnode *, daddr_t, int, int, int, int);
+
+#define	bdwrite(bp)	bwrite(bp)
+#define	clrbuf(bp)	memset((bp)->b_data, 0, (u_int)(bp)->b_bcount)
+
+#endif	/* _FFS_BUF_H */
diff --git a/usr.sbin/makefs/ffs/ffs_alloc.c b/usr.sbin/makefs/ffs/ffs_alloc.c
new file mode 100644
index 0000000000..ee114d1075
--- /dev/null
+++ b/usr.sbin/makefs/ffs/ffs_alloc.c
@@ -0,0 +1,683 @@
+/*	$NetBSD: ffs_alloc.c,v 1.14 2004/06/20 22:20:18 jmc Exp $	*/
+/* From: NetBSD: ffs_alloc.c,v 1.50 2001/09/06 02:16:01 lukem Exp */
+
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * This software was developed for the FreeBSD Project by Marshall
+ * Kirk McKusick and Network Associates Laboratories, the Security
+ * Research Division of Network Associates, Inc. under DARPA/SPAWAR
+ * contract N66001-01-C-8035 ("CBOSS"), as part of the DARPA CHATS
+ * research program
+ *
+ * Copyright (c) 1982, 1986, 1989, 1993
+ *	The Regents of the University of California.  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.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
+ *
+ *	@(#)ffs_alloc.c	8.19 (Berkeley) 7/13/95
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/ffs/ffs_alloc.c 336736 2018-07-26 13:33:10Z emaste $");
+
+#include <sys/param.h>
+#include <sys/time.h>
+
+#include <errno.h>
+#include <stdint.h>
+
+#include "makefs.h"
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include "ffs/ufs_bswap.h"
+#include "ffs/buf.h"
+#include "ffs/ufs_inode.h"
+#include "ffs/ffs_extern.h"
+
+static int scanc(u_int, const u_char *, const u_char *, int);
+
+static daddr_t ffs_alloccg(struct inode *, int, daddr_t, int);
+static daddr_t ffs_alloccgblk(struct inode *, struct buf *, daddr_t);
+static daddr_t ffs_hashalloc(struct inode *, u_int, daddr_t, int,
+		     daddr_t (*)(struct inode *, int, daddr_t, int));
+static int32_t ffs_mapsearch(struct fs *, struct cg *, daddr_t, int);
+
+/*
+ * Allocate a block in the file system.
+ * 
+ * The size of the requested block is given, which must be some
+ * multiple of fs_fsize and <= fs_bsize.
+ * A preference may be optionally specified. If a preference is given
+ * the following hierarchy is used to allocate a block:
+ *   1) allocate the requested block.
+ *   2) allocate a rotationally optimal block in the same cylinder.
+ *   3) allocate a block in the same cylinder group.
+ *   4) quadradically rehash into other cylinder groups, until an
+ *      available block is located.
+ * If no block preference is given the following hierarchy is used
+ * to allocate a block:
+ *   1) allocate a block in the cylinder group that contains the
+ *      inode for the file.
+ *   2) quadradically rehash into other cylinder groups, until an
+ *      available block is located.
+ */
+int
+ffs_alloc(struct inode *ip, daddr_t lbn __unused, daddr_t bpref, int size,
+    daddr_t *bnp)
+{
+	struct fs *fs = ip->i_fs;
+	daddr_t bno;
+	int cg;
+	
+	*bnp = 0;
+	if (size > fs->fs_bsize || fragoff(fs, size) != 0) {
+		errx(1, "ffs_alloc: bad size: bsize %d size %d",
+		    fs->fs_bsize, size);
+	}
+	if (size == fs->fs_bsize && fs->fs_cstotal.cs_nbfree == 0)
+		goto nospace;
+	if (bpref >= fs->fs_size)
+		bpref = 0;
+	if (bpref == 0)
+		cg = ino_to_cg(fs, ip->i_number);
+	else
+		cg = dtog(fs, bpref);
+	bno = ffs_hashalloc(ip, cg, bpref, size, ffs_alloccg);
+	if (bno > 0) {
+		if (ip->i_fs->fs_magic == FS_UFS1_MAGIC)
+			ip->i_ffs1_blocks += size / DEV_BSIZE;
+		else
+			ip->i_ffs2_blocks += size / DEV_BSIZE;
+		*bnp = bno;
+		return (0);
+	}
+nospace:
+	return (ENOSPC);
+}
+
+/*
+ * Select the desired position for the next block in a file.  The file is
+ * logically divided into sections. The first section is composed of the
+ * direct blocks. Each additional section contains fs_maxbpg blocks.
+ * 
+ * If no blocks have been allocated in the first section, the policy is to
+ * request a block in the same cylinder group as the inode that describes
+ * the file. If no blocks have been allocated in any other section, the
+ * policy is to place the section in a cylinder group with a greater than
+ * average number of free blocks.  An appropriate cylinder group is found
+ * by using a rotor that sweeps the cylinder groups. When a new group of
+ * blocks is needed, the sweep begins in the cylinder group following the
+ * cylinder group from which the previous allocation was made. The sweep
+ * continues until a cylinder group with greater than the average number
+ * of free blocks is found. If the allocation is for the first block in an
+ * indirect block, the information on the previous allocation is unavailable;
+ * here a best guess is made based upon the logical block number being
+ * allocated.
+ * 
+ * If a section is already partially allocated, the policy is to
+ * contiguously allocate fs_maxcontig blocks.  The end of one of these
+ * contiguous blocks and the beginning of the next is physically separated
+ * so that the disk head will be in transit between them for at least
+ * fs_rotdelay milliseconds.  This is to allow time for the processor to
+ * schedule another I/O transfer.
+ */
+/* XXX ondisk32 */
+daddr_t
+ffs_blkpref_ufs1(struct inode *ip, daddr_t lbn, int indx, int32_t *bap)
+{
+	struct fs *fs;
+	u_int cg, startcg;
+	int avgbfree;
+
+	fs = ip->i_fs;
+	if (indx % fs->fs_maxbpg == 0 || bap[indx - 1] == 0) {
+		if (lbn < UFS_NDADDR + NINDIR(fs)) {
+			cg = ino_to_cg(fs, ip->i_number);
+			return (fs->fs_fpg * cg + fs->fs_frag);
+		}
+		/*
+		 * Find a cylinder with greater than average number of
+		 * unused data blocks.
+		 */
+		if (indx == 0 || bap[indx - 1] == 0)
+			startcg =
+			    ino_to_cg(fs, ip->i_number) + lbn / fs->fs_maxbpg;
+		else
+			startcg = dtog(fs,
+				ufs_rw32(bap[indx - 1], UFS_FSNEEDSWAP(fs)) + 1);
+		startcg %= fs->fs_ncg;
+		avgbfree = fs->fs_cstotal.cs_nbfree / fs->fs_ncg;
+		for (cg = startcg; cg < fs->fs_ncg; cg++)
+			if (fs->fs_cs(fs, cg).cs_nbfree >= avgbfree)
+				return (fs->fs_fpg * cg + fs->fs_frag);
+		for (cg = 0; cg <= startcg; cg++)
+			if (fs->fs_cs(fs, cg).cs_nbfree >= avgbfree)
+				return (fs->fs_fpg * cg + fs->fs_frag);
+		return (0);
+	}
+	/*
+	 * We just always try to lay things out contiguously.
+	 */
+	return ufs_rw32(bap[indx - 1], UFS_FSNEEDSWAP(fs)) + fs->fs_frag;
+}
+
+daddr_t
+ffs_blkpref_ufs2(struct inode *ip, daddr_t lbn, int indx, int64_t *bap)
+{
+	struct fs *fs;
+	u_int cg, startcg;
+	int avgbfree;
+
+	fs = ip->i_fs;
+	if (indx % fs->fs_maxbpg == 0 || bap[indx - 1] == 0) {
+		if (lbn < UFS_NDADDR + NINDIR(fs)) {
+			cg = ino_to_cg(fs, ip->i_number);
+			return (fs->fs_fpg * cg + fs->fs_frag);
+		}
+		/*
+		 * Find a cylinder with greater than average number of
+		 * unused data blocks.
+		 */
+		if (indx == 0 || bap[indx - 1] == 0)
+			startcg =
+			    ino_to_cg(fs, ip->i_number) + lbn / fs->fs_maxbpg;
+		else
+			startcg = dtog(fs,
+				ufs_rw64(bap[indx - 1], UFS_FSNEEDSWAP(fs)) + 1);
+		startcg %= fs->fs_ncg;
+		avgbfree = fs->fs_cstotal.cs_nbfree / fs->fs_ncg;
+		for (cg = startcg; cg < fs->fs_ncg; cg++)
+			if (fs->fs_cs(fs, cg).cs_nbfree >= avgbfree) {
+				return (fs->fs_fpg * cg + fs->fs_frag);
+			}
+		for (cg = 0; cg < startcg; cg++)
+			if (fs->fs_cs(fs, cg).cs_nbfree >= avgbfree) {
+				return (fs->fs_fpg * cg + fs->fs_frag);
+			}
+		return (0);
+	}
+	/*
+	 * We just always try to lay things out contiguously.
+	 */
+	return ufs_rw64(bap[indx - 1], UFS_FSNEEDSWAP(fs)) + fs->fs_frag;
+}
+
+/*
+ * Implement the cylinder overflow algorithm.
+ *
+ * The policy implemented by this algorithm is:
+ *   1) allocate the block in its requested cylinder group.
+ *   2) quadradically rehash on the cylinder group number.
+ *   3) brute force search for a free block.
+ *
+ * `size':	size for data blocks, mode for inodes
+ */
+/*VARARGS5*/
+static daddr_t
+ffs_hashalloc(struct inode *ip, u_int cg, daddr_t pref, int size,
+    daddr_t (*allocator)(struct inode *, int, daddr_t, int))
+{
+	struct fs *fs;
+	daddr_t result;
+	u_int i, icg = cg;
+
+	fs = ip->i_fs;
+	/*
+	 * 1: preferred cylinder group
+	 */
+	result = (*allocator)(ip, cg, pref, size);
+	if (result)
+		return (result);
+	/*
+	 * 2: quadratic rehash
+	 */
+	for (i = 1; i < fs->fs_ncg; i *= 2) {
+		cg += i;
+		if (cg >= fs->fs_ncg)
+			cg -= fs->fs_ncg;
+		result = (*allocator)(ip, cg, 0, size);
+		if (result)
+			return (result);
+	}
+	/*
+	 * 3: brute force search
+	 * Note that we start at i == 2, since 0 was checked initially,
+	 * and 1 is always checked in the quadratic rehash.
+	 */
+	cg = (icg + 2) % fs->fs_ncg;
+	for (i = 2; i < fs->fs_ncg; i++) {
+		result = (*allocator)(ip, cg, 0, size);
+		if (result)
+			return (result);
+		cg++;
+		if (cg == fs->fs_ncg)
+			cg = 0;
+	}
+	return (0);
+}
+
+/*
+ * Determine whether a block can be allocated.
+ *
+ * Check to see if a block of the appropriate size is available,
+ * and if it is, allocate it.
+ */
+static daddr_t
+ffs_alloccg(struct inode *ip, int cg, daddr_t bpref, int size)
+{
+	struct cg *cgp;
+	struct buf *bp;
+	daddr_t bno, blkno;
+	int error, frags, allocsiz, i;
+	struct fs *fs = ip->i_fs;
+	const int needswap = UFS_FSNEEDSWAP(fs);
+
+	if (fs->fs_cs(fs, cg).cs_nbfree == 0 && size == fs->fs_bsize)
+		return (0);
+	error = bread(ip->i_devvp, fsbtodb(fs, cgtod(fs, cg)), (int)fs->fs_cgsize,
+	    NULL, &bp);
+	if (error) {
+		brelse(bp);
+		return (0);
+	}
+	cgp = (struct cg *)bp->b_data;
+	if (!cg_chkmagic_swap(cgp, needswap) ||
+	    (cgp->cg_cs.cs_nbfree == 0 && size == fs->fs_bsize)) {
+		brelse(bp);
+		return (0);
+	}
+	if (size == fs->fs_bsize) {
+		bno = ffs_alloccgblk(ip, bp, bpref);
+		bdwrite(bp);
+		return (bno);
+	}
+	/*
+	 * check to see if any fragments are already available
+	 * allocsiz is the size which will be allocated, hacking
+	 * it down to a smaller size if necessary
+	 */
+	frags = numfrags(fs, size);
+	for (allocsiz = frags; allocsiz < fs->fs_frag; allocsiz++)
+		if (cgp->cg_frsum[allocsiz] != 0)
+			break;
+	if (allocsiz == fs->fs_frag) {
+		/*
+		 * no fragments were available, so a block will be 
+		 * allocated, and hacked up
+		 */
+		if (cgp->cg_cs.cs_nbfree == 0) {
+			brelse(bp);
+			return (0);
+		}
+		bno = ffs_alloccgblk(ip, bp, bpref);
+		bpref = dtogd(fs, bno);
+		for (i = frags; i < fs->fs_frag; i++)
+			setbit(cg_blksfree_swap(cgp, needswap), bpref + i);
+		i = fs->fs_frag - frags;
+		ufs_add32(cgp->cg_cs.cs_nffree, i, needswap);
+		fs->fs_cstotal.cs_nffree += i;
+		fs->fs_cs(fs, cg).cs_nffree += i;
+		fs->fs_fmod = 1;
+		ufs_add32(cgp->cg_frsum[i], 1, needswap);
+		bdwrite(bp);
+		return (bno);
+	}
+	bno = ffs_mapsearch(fs, cgp, bpref, allocsiz);
+	for (i = 0; i < frags; i++)
+		clrbit(cg_blksfree_swap(cgp, needswap), bno + i);
+	ufs_add32(cgp->cg_cs.cs_nffree, -frags, needswap);
+	fs->fs_cstotal.cs_nffree -= frags;
+	fs->fs_cs(fs, cg).cs_nffree -= frags;
+	fs->fs_fmod = 1;
+	ufs_add32(cgp->cg_frsum[allocsiz], -1, needswap);
+	if (frags != allocsiz)
+		ufs_add32(cgp->cg_frsum[allocsiz - frags], 1, needswap);
+	blkno = cg * fs->fs_fpg + bno;
+	bdwrite(bp);
+	return blkno;
+}
+
+/*
+ * Allocate a block in a cylinder group.
+ *
+ * This algorithm implements the following policy:
+ *   1) allocate the requested block.
+ *   2) allocate a rotationally optimal block in the same cylinder.
+ *   3) allocate the next available block on the block rotor for the
+ *      specified cylinder group.
+ * Note that this routine only allocates fs_bsize blocks; these
+ * blocks may be fragmented by the routine that allocates them.
+ */
+static daddr_t
+ffs_alloccgblk(struct inode *ip, struct buf *bp, daddr_t bpref)
+{
+	struct cg *cgp;
+	daddr_t blkno;
+	int32_t bno;
+	struct fs *fs = ip->i_fs;
+	const int needswap = UFS_FSNEEDSWAP(fs);
+	u_int8_t *blksfree_swap;
+
+	cgp = (struct cg *)bp->b_data;
+	blksfree_swap = cg_blksfree_swap(cgp, needswap);
+	if (bpref == 0 || (uint32_t)dtog(fs, bpref) != ufs_rw32(cgp->cg_cgx, needswap)) {
+		bpref = ufs_rw32(cgp->cg_rotor, needswap);
+	} else {
+		bpref = blknum(fs, bpref);
+		bno = dtogd(fs, bpref);
+		/*
+		 * if the requested block is available, use it
+		 */
+		if (ffs_isblock(fs, blksfree_swap, fragstoblks(fs, bno)))
+			goto gotit;
+	}
+	/*
+	 * Take the next available one in this cylinder group.
+	 */
+	bno = ffs_mapsearch(fs, cgp, bpref, (int)fs->fs_frag);
+	if (bno < 0)
+		return (0);
+	cgp->cg_rotor = ufs_rw32(bno, needswap);
+gotit:
+	blkno = fragstoblks(fs, bno);
+	ffs_clrblock(fs, blksfree_swap, (long)blkno);
+	ffs_clusteracct(fs, cgp, blkno, -1);
+	ufs_add32(cgp->cg_cs.cs_nbfree, -1, needswap);
+	fs->fs_cstotal.cs_nbfree--;
+	fs->fs_cs(fs, ufs_rw32(cgp->cg_cgx, needswap)).cs_nbfree--;
+	fs->fs_fmod = 1;
+	blkno = ufs_rw32(cgp->cg_cgx, needswap) * fs->fs_fpg + bno;
+	return (blkno);
+}
+
+/*
+ * Free a block or fragment.
+ *
+ * The specified block or fragment is placed back in the
+ * free map. If a fragment is deallocated, a possible 
+ * block reassembly is checked.
+ */
+void
+ffs_blkfree(struct inode *ip, daddr_t bno, long size)
+{
+	struct cg *cgp;
+	struct buf *bp;
+	int32_t fragno, cgbno;
+	int i, error, cg, blk, frags, bbase;
+	struct fs *fs = ip->i_fs;
+	const int needswap = UFS_FSNEEDSWAP(fs);
+
+	if (size > fs->fs_bsize || fragoff(fs, size) != 0 ||
+	    fragnum(fs, bno) + numfrags(fs, size) > fs->fs_frag) {
+		errx(1, "blkfree: bad size: bno %lld bsize %d size %ld",
+		    (long long)bno, fs->fs_bsize, size);
+	}
+	cg = dtog(fs, bno);
+	if (bno >= fs->fs_size) {
+		warnx("bad block %lld, ino %ju", (long long)bno,
+		    (uintmax_t)ip->i_number);
+		return;
+	}
+	error = bread(ip->i_devvp, fsbtodb(fs, cgtod(fs, cg)), (int)fs->fs_cgsize,
+	    NULL, &bp);
+	if (error) {
+		brelse(bp);
+		return;
+	}
+	cgp = (struct cg *)bp->b_data;
+	if (!cg_chkmagic_swap(cgp, needswap)) {
+		brelse(bp);
+		return;
+	}
+	cgbno = dtogd(fs, bno);
+	if (size == fs->fs_bsize) {
+		fragno = fragstoblks(fs, cgbno);
+		if (!ffs_isfreeblock(fs, cg_blksfree_swap(cgp, needswap), fragno)) {
+			errx(1, "blkfree: freeing free block %lld",
+			    (long long)bno);
+		}
+		ffs_setblock(fs, cg_blksfree_swap(cgp, needswap), fragno);
+		ffs_clusteracct(fs, cgp, fragno, 1);
+		ufs_add32(cgp->cg_cs.cs_nbfree, 1, needswap);
+		fs->fs_cstotal.cs_nbfree++;
+		fs->fs_cs(fs, cg).cs_nbfree++;
+	} else {
+		bbase = cgbno - fragnum(fs, cgbno);
+		/*
+		 * decrement the counts associated with the old frags
+		 */
+		blk = blkmap(fs, cg_blksfree_swap(cgp, needswap), bbase);
+		ffs_fragacct_swap(fs, blk, cgp->cg_frsum, -1, needswap);
+		/*
+		 * deallocate the fragment
+		 */
+		frags = numfrags(fs, size);
+		for (i = 0; i < frags; i++) {
+			if (isset(cg_blksfree_swap(cgp, needswap), cgbno + i)) {
+				errx(1, "blkfree: freeing free frag: block %lld",
+				    (long long)(cgbno + i));
+			}
+			setbit(cg_blksfree_swap(cgp, needswap), cgbno + i);
+		}
+		ufs_add32(cgp->cg_cs.cs_nffree, i, needswap);
+		fs->fs_cstotal.cs_nffree += i;
+		fs->fs_cs(fs, cg).cs_nffree += i;
+		/*
+		 * add back in counts associated with the new frags
+		 */
+		blk = blkmap(fs, cg_blksfree_swap(cgp, needswap), bbase);
+		ffs_fragacct_swap(fs, blk, cgp->cg_frsum, 1, needswap);
+		/*
+		 * if a complete block has been reassembled, account for it
+		 */
+		fragno = fragstoblks(fs, bbase);
+		if (ffs_isblock(fs, cg_blksfree_swap(cgp, needswap), fragno)) {
+			ufs_add32(cgp->cg_cs.cs_nffree, -fs->fs_frag, needswap);
+			fs->fs_cstotal.cs_nffree -= fs->fs_frag;
+			fs->fs_cs(fs, cg).cs_nffree -= fs->fs_frag;
+			ffs_clusteracct(fs, cgp, fragno, 1);
+			ufs_add32(cgp->cg_cs.cs_nbfree, 1, needswap);
+			fs->fs_cstotal.cs_nbfree++;
+			fs->fs_cs(fs, cg).cs_nbfree++;
+		}
+	}
+	fs->fs_fmod = 1;
+	bdwrite(bp);
+}
+
+
+static int
+scanc(u_int size, const u_char *cp, const u_char table[], int mask)
+{
+	const u_char *end = &cp[size];
+
+	while (cp < end && (table[*cp] & mask) == 0)
+		cp++;
+	return (end - cp);
+}
+
+/*
+ * Find a block of the specified size in the specified cylinder group.
+ *
+ * It is a panic if a request is made to find a block if none are
+ * available.
+ */
+static int32_t
+ffs_mapsearch(struct fs *fs, struct cg *cgp, daddr_t bpref, int allocsiz)
+{
+	int32_t bno;
+	int start, len, loc, i;
+	int blk, field, subfield, pos;
+	int ostart, olen;
+	const int needswap = UFS_FSNEEDSWAP(fs);
+
+	/*
+	 * find the fragment by searching through the free block
+	 * map for an appropriate bit pattern
+	 */
+	if (bpref)
+		start = dtogd(fs, bpref) / NBBY;
+	else
+		start = ufs_rw32(cgp->cg_frotor, needswap) / NBBY;
+	len = howmany(fs->fs_fpg, NBBY) - start;
+	ostart = start;
+	olen = len;
+	loc = scanc((u_int)len,
+		(const u_char *)&cg_blksfree_swap(cgp, needswap)[start],
+		(const u_char *)fragtbl[fs->fs_frag],
+		(1 << (allocsiz - 1 + (fs->fs_frag % NBBY))));
+	if (loc == 0) {
+		len = start + 1;
+		start = 0;
+		loc = scanc((u_int)len,
+			(const u_char *)&cg_blksfree_swap(cgp, needswap)[0],
+			(const u_char *)fragtbl[fs->fs_frag],
+			(1 << (allocsiz - 1 + (fs->fs_frag % NBBY))));
+		if (loc == 0) {
+			errx(1,
+    "ffs_alloccg: map corrupted: start %d len %d offset %d %ld",
+				ostart, olen,
+				ufs_rw32(cgp->cg_freeoff, needswap),
+				(long)cg_blksfree_swap(cgp, needswap) - (long)cgp);
+			/* NOTREACHED */
+		}
+	}
+	bno = (start + len - loc) * NBBY;
+	cgp->cg_frotor = ufs_rw32(bno, needswap);
+	/*
+	 * found the byte in the map
+	 * sift through the bits to find the selected frag
+	 */
+	for (i = bno + NBBY; bno < i; bno += fs->fs_frag) {
+		blk = blkmap(fs, cg_blksfree_swap(cgp, needswap), bno);
+		blk <<= 1;
+		field = around[allocsiz];
+		subfield = inside[allocsiz];
+		for (pos = 0; pos <= fs->fs_frag - allocsiz; pos++) {
+			if ((blk & field) == subfield)
+				return (bno + pos);
+			field <<= 1;
+			subfield <<= 1;
+		}
+	}
+	errx(1, "ffs_alloccg: block not in map: bno %lld", (long long)bno);
+	return (-1);
+}
+
+/*
+ * Update the cluster map because of an allocation or free.
+ *
+ * Cnt == 1 means free; cnt == -1 means allocating.
+ */
+void
+ffs_clusteracct(struct fs *fs, struct cg *cgp, int32_t blkno, int cnt)
+{
+	int32_t *sump;
+	int32_t *lp;
+	u_char *freemapp, *mapp;
+	int i, start, end, forw, back, map, bit;
+	const int needswap = UFS_FSNEEDSWAP(fs);
+
+	if (fs->fs_contigsumsize <= 0)
+		return;
+	freemapp = cg_clustersfree_swap(cgp, needswap);
+	sump = cg_clustersum_swap(cgp, needswap);
+	/*
+	 * Allocate or clear the actual block.
+	 */
+	if (cnt > 0)
+		setbit(freemapp, blkno);
+	else
+		clrbit(freemapp, blkno);
+	/*
+	 * Find the size of the cluster going forward.
+	 */
+	start = blkno + 1;
+	end = start + fs->fs_contigsumsize;
+	if ((unsigned)end >= ufs_rw32(cgp->cg_nclusterblks, needswap))
+		end = ufs_rw32(cgp->cg_nclusterblks, needswap);
+	mapp = &freemapp[start / NBBY];
+	map = *mapp++;
+	bit = 1 << (start % NBBY);
+	for (i = start; i < end; i++) {
+		if ((map & bit) == 0)
+			break;
+		if ((i & (NBBY - 1)) != (NBBY - 1)) {
+			bit <<= 1;
+		} else {
+			map = *mapp++;
+			bit = 1;
+		}
+	}
+	forw = i - start;
+	/*
+	 * Find the size of the cluster going backward.
+	 */
+	start = blkno - 1;
+	end = start - fs->fs_contigsumsize;
+	if (end < 0)
+		end = -1;
+	mapp = &freemapp[start / NBBY];
+	map = *mapp--;
+	bit = 1 << (start % NBBY);
+	for (i = start; i > end; i--) {
+		if ((map & bit) == 0)
+			break;
+		if ((i & (NBBY - 1)) != 0) {
+			bit >>= 1;
+		} else {
+			map = *mapp--;
+			bit = 1 << (NBBY - 1);
+		}
+	}
+	back = start - i;
+	/*
+	 * Account for old cluster and the possibly new forward and
+	 * back clusters.
+	 */
+	i = back + forw + 1;
+	if (i > fs->fs_contigsumsize)
+		i = fs->fs_contigsumsize;
+	ufs_add32(sump[i], cnt, needswap);
+	if (back > 0)
+		ufs_add32(sump[back], -cnt, needswap);
+	if (forw > 0)
+		ufs_add32(sump[forw], -cnt, needswap);
+
+	/*
+	 * Update cluster summary information.
+	 */
+	lp = &sump[fs->fs_contigsumsize];
+	for (i = fs->fs_contigsumsize; i > 0; i--)
+		if (ufs_rw32(*lp--, needswap) > 0)
+			break;
+	fs->fs_maxcluster[ufs_rw32(cgp->cg_cgx, needswap)] = i;
+}
diff --git a/usr.sbin/makefs/ffs/ffs_balloc.c b/usr.sbin/makefs/ffs/ffs_balloc.c
new file mode 100644
index 0000000000..c390de9efd
--- /dev/null
+++ b/usr.sbin/makefs/ffs/ffs_balloc.c
@@ -0,0 +1,580 @@
+/*	$NetBSD: ffs_balloc.c,v 1.13 2004/06/20 22:20:18 jmc Exp $	*/
+/* From NetBSD: ffs_balloc.c,v 1.25 2001/08/08 08:36:36 lukem Exp */
+
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1982, 1986, 1989, 1993
+ *	The Regents of the University of California.  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.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
+ *
+ *	@(#)ffs_balloc.c	8.8 (Berkeley) 6/16/95
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/ffs/ffs_balloc.c 336736 2018-07-26 13:33:10Z emaste $");
+
+#include <sys/param.h>
+#include <sys/time.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "makefs.h"
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include "ffs/ufs_bswap.h"
+#include "ffs/buf.h"
+#include "ffs/ufs_inode.h"
+#include "ffs/ffs_extern.h"
+
+static int ffs_balloc_ufs1(struct inode *, off_t, int, struct buf **);
+static int ffs_balloc_ufs2(struct inode *, off_t, int, struct buf **);
+
+/*
+ * Balloc defines the structure of file system storage
+ * by allocating the physical blocks on a device given
+ * the inode and the logical block number in a file.
+ *
+ * Assume: flags == B_SYNC | B_CLRBUF
+ */
+
+int
+ffs_balloc(struct inode *ip, off_t offset, int bufsize, struct buf **bpp)
+{
+	if (ip->i_fs->fs_magic == FS_UFS2_MAGIC)
+		return ffs_balloc_ufs2(ip, offset, bufsize, bpp);
+	else
+		return ffs_balloc_ufs1(ip, offset, bufsize, bpp);
+}
+
+static int
+ffs_balloc_ufs1(struct inode *ip, off_t offset, int bufsize, struct buf **bpp)
+{
+	daddr_t lbn, lastlbn;
+	int size;
+	int32_t nb;
+	struct buf *bp, *nbp;
+	struct fs *fs = ip->i_fs;
+	struct indir indirs[UFS_NIADDR + 2];
+	daddr_t newb, pref;
+	int32_t *bap;
+	int osize, nsize, num, i, error;
+	int32_t *allocblk, allociblk[UFS_NIADDR + 1];
+	int32_t *allocib;
+	const int needswap = UFS_FSNEEDSWAP(fs);
+
+	lbn = lblkno(fs, offset);
+	size = blkoff(fs, offset) + bufsize;
+	if (bpp != NULL) {
+		*bpp = NULL;
+	}
+
+	assert(size <= fs->fs_bsize);
+	if (lbn < 0)
+		return (EFBIG);
+
+	/*
+	 * If the next write will extend the file into a new block,
+	 * and the file is currently composed of a fragment
+	 * this fragment has to be extended to be a full block.
+	 */
+
+	lastlbn = lblkno(fs, ip->i_ffs1_size);
+	if (lastlbn < UFS_NDADDR && lastlbn < lbn) {
+		nb = lastlbn;
+		osize = blksize(fs, ip, nb);
+		if (osize < fs->fs_bsize && osize > 0) {
+			warnx("need to ffs_realloccg; not supported!");
+			abort();
+		}
+	}
+
+	/*
+	 * The first UFS_NDADDR blocks are direct blocks
+	 */
+
+	if (lbn < UFS_NDADDR) {
+		nb = ufs_rw32(ip->i_ffs1_db[lbn], needswap);
+		if (nb != 0 && ip->i_ffs1_size >=
+		    (uint64_t)lblktosize(fs, lbn + 1)) {
+
+			/*
+			 * The block is an already-allocated direct block
+			 * and the file already extends past this block,
+			 * thus this must be a whole block.
+			 * Just read the block (if requested).
+			 */
+
+			if (bpp != NULL) {
+				error = bread(ip->i_devvp, lbn, fs->fs_bsize,
+				    NULL, bpp);
+				if (error) {
+					brelse(*bpp);
+					return (error);
+				}
+			}
+			return (0);
+		}
+		if (nb != 0) {
+
+			/*
+			 * Consider need to reallocate a fragment.
+			 */
+
+			osize = fragroundup(fs, blkoff(fs, ip->i_ffs1_size));
+			nsize = fragroundup(fs, size);
+			if (nsize <= osize) {
+
+				/*
+				 * The existing block is already
+				 * at least as big as we want.
+				 * Just read the block (if requested).
+				 */
+
+				if (bpp != NULL) {
+					error = bread(ip->i_devvp, lbn, osize,
+					    NULL, bpp);
+					if (error) {
+						brelse(*bpp);
+						return (error);
+					}
+				}
+				return 0;
+			} else {
+				warnx("need to ffs_realloccg; not supported!");
+				abort();
+			}
+		} else {
+
+			/*
+			 * the block was not previously allocated,
+			 * allocate a new block or fragment.
+			 */
+
+			if (ip->i_ffs1_size < (uint64_t)lblktosize(fs, lbn + 1))
+				nsize = fragroundup(fs, size);
+			else
+				nsize = fs->fs_bsize;
+			error = ffs_alloc(ip, lbn,
+			    ffs_blkpref_ufs1(ip, lbn, (int)lbn,
+				&ip->i_ffs1_db[0]),
+				nsize, &newb);
+			if (error)
+				return (error);
+			if (bpp != NULL) {
+				bp = getblk(ip->i_devvp, lbn, nsize, 0, 0, 0);
+				bp->b_blkno = fsbtodb(fs, newb);
+				clrbuf(bp);
+				*bpp = bp;
+			}
+		}
+		ip->i_ffs1_db[lbn] = ufs_rw32((int32_t)newb, needswap);
+		return (0);
+	}
+
+	/*
+	 * Determine the number of levels of indirection.
+	 */
+
+	pref = 0;
+	if ((error = ufs_getlbns(ip, lbn, indirs, &num)) != 0)
+		return (error);
+
+	if (num < 1) {
+		warnx("ffs_balloc: ufs_getlbns returned indirect block");
+		abort();
+	}
+
+	/*
+	 * Fetch the first indirect block allocating if necessary.
+	 */
+
+	--num;
+	nb = ufs_rw32(ip->i_ffs1_ib[indirs[0].in_off], needswap);
+	allocib = NULL;
+	allocblk = allociblk;
+	if (nb == 0) {
+		pref = ffs_blkpref_ufs1(ip, lbn, 0, (int32_t *)0);
+		error = ffs_alloc(ip, lbn, pref, (int)fs->fs_bsize, &newb);
+		if (error)
+			return error;
+		nb = newb;
+		*allocblk++ = nb;
+		bp = getblk(ip->i_devvp, indirs[1].in_lbn, fs->fs_bsize, 0, 0, 0);
+		bp->b_blkno = fsbtodb(fs, nb);
+		clrbuf(bp);
+		/*
+		 * Write synchronously so that indirect blocks
+		 * never point at garbage.
+		 */
+		if ((error = bwrite(bp)) != 0)
+			return error;
+		allocib = &ip->i_ffs1_ib[indirs[0].in_off];
+		*allocib = ufs_rw32((int32_t)nb, needswap);
+	}
+
+	/*
+	 * Fetch through the indirect blocks, allocating as necessary.
+	 */
+
+	for (i = 1;;) {
+		error = bread(ip->i_devvp, indirs[i].in_lbn, fs->fs_bsize,
+		    NULL, &bp);
+		if (error) {
+			brelse(bp);
+			return error;
+		}
+		bap = (int32_t *)bp->b_data;
+		nb = ufs_rw32(bap[indirs[i].in_off], needswap);
+		if (i == num)
+			break;
+		i++;
+		if (nb != 0) {
+			brelse(bp);
+			continue;
+		}
+		if (pref == 0)
+			pref = ffs_blkpref_ufs1(ip, lbn, 0, (int32_t *)0);
+		error = ffs_alloc(ip, lbn, pref, (int)fs->fs_bsize, &newb);
+		if (error) {
+			brelse(bp);
+			return error;
+		}
+		nb = newb;
+		*allocblk++ = nb;
+		nbp = getblk(ip->i_devvp, indirs[i].in_lbn, fs->fs_bsize, 0, 0, 0);
+		nbp->b_blkno = fsbtodb(fs, nb);
+		clrbuf(nbp);
+		/*
+		 * Write synchronously so that indirect blocks
+		 * never point at garbage.
+		 */
+
+		if ((error = bwrite(nbp)) != 0) {
+			brelse(bp);
+			return error;
+		}
+		bap[indirs[i - 1].in_off] = ufs_rw32(nb, needswap);
+
+		bwrite(bp);
+	}
+
+	/*
+	 * Get the data block, allocating if necessary.
+	 */
+
+	if (nb == 0) {
+		pref = ffs_blkpref_ufs1(ip, lbn, indirs[num].in_off, &bap[0]);
+		error = ffs_alloc(ip, lbn, pref, (int)fs->fs_bsize, &newb);
+		if (error) {
+			brelse(bp);
+			return error;
+		}
+		nb = newb;
+		*allocblk++ = nb;
+		if (bpp != NULL) {
+			nbp = getblk(ip->i_devvp, lbn, fs->fs_bsize, 0, 0, 0);
+			nbp->b_blkno = fsbtodb(fs, nb);
+			clrbuf(nbp);
+			*bpp = nbp;
+		}
+		bap[indirs[num].in_off] = ufs_rw32(nb, needswap);
+
+		/*
+		 * If required, write synchronously, otherwise use
+		 * delayed write.
+		 */
+		bwrite(bp);
+		return (0);
+	}
+	brelse(bp);
+	if (bpp != NULL) {
+		error = bread(ip->i_devvp, lbn, (int)fs->fs_bsize, NULL, &nbp);
+		if (error) {
+			brelse(nbp);
+			return error;
+		}
+		*bpp = nbp;
+	}
+	return (0);
+}
+
+static int
+ffs_balloc_ufs2(struct inode *ip, off_t offset, int bufsize, struct buf **bpp)
+{
+	daddr_t lbn, lastlbn;
+	int size;
+	struct buf *bp, *nbp;
+	struct fs *fs = ip->i_fs;
+	struct indir indirs[UFS_NIADDR + 2];
+	daddr_t newb, pref, nb;
+	int64_t *bap;
+	int osize, nsize, num, i, error;
+	int64_t *allocblk, allociblk[UFS_NIADDR + 1];
+	int64_t *allocib;
+	const int needswap = UFS_FSNEEDSWAP(fs);
+
+	lbn = lblkno(fs, offset);
+	size = blkoff(fs, offset) + bufsize;
+	if (bpp != NULL) {
+		*bpp = NULL;
+	}
+
+	assert(size <= fs->fs_bsize);
+	if (lbn < 0)
+		return (EFBIG);
+
+	/*
+	 * If the next write will extend the file into a new block,
+	 * and the file is currently composed of a fragment
+	 * this fragment has to be extended to be a full block.
+	 */
+
+	lastlbn = lblkno(fs, ip->i_ffs2_size);
+	if (lastlbn < UFS_NDADDR && lastlbn < lbn) {
+		nb = lastlbn;
+		osize = blksize(fs, ip, nb);
+		if (osize < fs->fs_bsize && osize > 0) {
+			warnx("need to ffs_realloccg; not supported!");
+			abort();
+		}
+	}
+
+	/*
+	 * The first UFS_NDADDR blocks are direct blocks
+	 */
+
+	if (lbn < UFS_NDADDR) {
+		nb = ufs_rw64(ip->i_ffs2_db[lbn], needswap);
+		if (nb != 0 && ip->i_ffs2_size >=
+		    (uint64_t)lblktosize(fs, lbn + 1)) {
+
+			/*
+			 * The block is an already-allocated direct block
+			 * and the file already extends past this block,
+			 * thus this must be a whole block.
+			 * Just read the block (if requested).
+			 */
+
+			if (bpp != NULL) {
+				error = bread(ip->i_devvp, lbn, fs->fs_bsize,
+				    NULL, bpp);
+				if (error) {
+					brelse(*bpp);
+					return (error);
+				}
+			}
+			return (0);
+		}
+		if (nb != 0) {
+
+			/*
+			 * Consider need to reallocate a fragment.
+			 */
+
+			osize = fragroundup(fs, blkoff(fs, ip->i_ffs2_size));
+			nsize = fragroundup(fs, size);
+			if (nsize <= osize) {
+
+				/*
+				 * The existing block is already
+				 * at least as big as we want.
+				 * Just read the block (if requested).
+				 */
+
+				if (bpp != NULL) {
+					error = bread(ip->i_devvp, lbn, osize,
+					    NULL, bpp);
+					if (error) {
+						brelse(*bpp);
+						return (error);
+					}
+				}
+				return 0;
+			} else {
+				warnx("need to ffs_realloccg; not supported!");
+				abort();
+			}
+		} else {
+
+			/*
+			 * the block was not previously allocated,
+			 * allocate a new block or fragment.
+			 */
+
+			if (ip->i_ffs2_size < (uint64_t)lblktosize(fs, lbn + 1))
+				nsize = fragroundup(fs, size);
+			else
+				nsize = fs->fs_bsize;
+			error = ffs_alloc(ip, lbn,
+			    ffs_blkpref_ufs2(ip, lbn, (int)lbn,
+				&ip->i_ffs2_db[0]),
+				nsize, &newb);
+			if (error)
+				return (error);
+			if (bpp != NULL) {
+				bp = getblk(ip->i_devvp, lbn, nsize, 0, 0, 0);
+				bp->b_blkno = fsbtodb(fs, newb);
+				clrbuf(bp);
+				*bpp = bp;
+			}
+		}
+		ip->i_ffs2_db[lbn] = ufs_rw64(newb, needswap);
+		return (0);
+	}
+
+	/*
+	 * Determine the number of levels of indirection.
+	 */
+
+	pref = 0;
+	if ((error = ufs_getlbns(ip, lbn, indirs, &num)) != 0)
+		return (error);
+
+	if (num < 1) {
+		warnx("ffs_balloc: ufs_getlbns returned indirect block");
+		abort();
+	}
+
+	/*
+	 * Fetch the first indirect block allocating if necessary.
+	 */
+
+	--num;
+	nb = ufs_rw64(ip->i_ffs2_ib[indirs[0].in_off], needswap);
+	allocib = NULL;
+	allocblk = allociblk;
+	if (nb == 0) {
+		pref = ffs_blkpref_ufs2(ip, lbn, 0, (int64_t *)0);
+		error = ffs_alloc(ip, lbn, pref, (int)fs->fs_bsize, &newb);
+		if (error)
+			return error;
+		nb = newb;
+		*allocblk++ = nb;
+		bp = getblk(ip->i_devvp, indirs[1].in_lbn, fs->fs_bsize, 0, 0, 0);
+		bp->b_blkno = fsbtodb(fs, nb);
+		clrbuf(bp);
+		/*
+		 * Write synchronously so that indirect blocks
+		 * never point at garbage.
+		 */
+		if ((error = bwrite(bp)) != 0)
+			return error;
+		allocib = &ip->i_ffs2_ib[indirs[0].in_off];
+		*allocib = ufs_rw64(nb, needswap);
+	}
+
+	/*
+	 * Fetch through the indirect blocks, allocating as necessary.
+	 */
+
+	for (i = 1;;) {
+		error = bread(ip->i_devvp, indirs[i].in_lbn, fs->fs_bsize,
+		    NULL, &bp);
+		if (error) {
+			brelse(bp);
+			return error;
+		}
+		bap = (int64_t *)bp->b_data;
+		nb = ufs_rw64(bap[indirs[i].in_off], needswap);
+		if (i == num)
+			break;
+		i++;
+		if (nb != 0) {
+			brelse(bp);
+			continue;
+		}
+		if (pref == 0)
+			pref = ffs_blkpref_ufs2(ip, lbn, 0, (int64_t *)0);
+		error = ffs_alloc(ip, lbn, pref, (int)fs->fs_bsize, &newb);
+		if (error) {
+			brelse(bp);
+			return error;
+		}
+		nb = newb;
+		*allocblk++ = nb;
+		nbp = getblk(ip->i_devvp, indirs[i].in_lbn, fs->fs_bsize, 0, 0, 0);
+		nbp->b_blkno = fsbtodb(fs, nb);
+		clrbuf(nbp);
+		/*
+		 * Write synchronously so that indirect blocks
+		 * never point at garbage.
+		 */
+
+		if ((error = bwrite(nbp)) != 0) {
+			brelse(bp);
+			return error;
+		}
+		bap[indirs[i - 1].in_off] = ufs_rw64(nb, needswap);
+
+		bwrite(bp);
+	}
+
+	/*
+	 * Get the data block, allocating if necessary.
+	 */
+
+	if (nb == 0) {
+		pref = ffs_blkpref_ufs2(ip, lbn, indirs[num].in_off, &bap[0]);
+		error = ffs_alloc(ip, lbn, pref, (int)fs->fs_bsize, &newb);
+		if (error) {
+			brelse(bp);
+			return error;
+		}
+		nb = newb;
+		*allocblk++ = nb;
+		if (bpp != NULL) {
+			nbp = getblk(ip->i_devvp, lbn, fs->fs_bsize, 0, 0, 0);
+			nbp->b_blkno = fsbtodb(fs, nb);
+			clrbuf(nbp);
+			*bpp = nbp;
+		}
+		bap[indirs[num].in_off] = ufs_rw64(nb, needswap);
+
+		/*
+		 * If required, write synchronously, otherwise use
+		 * delayed write.
+		 */
+		bwrite(bp);
+		return (0);
+	}
+	brelse(bp);
+	if (bpp != NULL) {
+		error = bread(ip->i_devvp, lbn, (int)fs->fs_bsize, NULL, &nbp);
+		if (error) {
+			brelse(nbp);
+			return error;
+		}
+		*bpp = nbp;
+	}
+	return (0);
+}
diff --git a/usr.sbin/makefs/ffs/ffs_bswap.c b/usr.sbin/makefs/ffs/ffs_bswap.c
new file mode 100644
index 0000000000..6bc4f797b0
--- /dev/null
+++ b/usr.sbin/makefs/ffs/ffs_bswap.c
@@ -0,0 +1,261 @@
+/*	$NetBSD: ffs_bswap.c,v 1.28 2004/05/25 14:54:59 hannken Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-NetBSD
+ *
+ * Copyright (c) 1998 Manuel Bouyer.
+ *
+ * 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 ``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 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/ffs/ffs_bswap.c 326276 2017-11-27 15:37:16Z pfg $");
+
+#include <sys/param.h>
+#if defined(_KERNEL)
+#include <sys/systm.h>
+#endif
+
+#if !defined(_KERNEL)
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#endif
+
+#include <ufs/ufs/dinode.h>
+#include "ffs/ufs_bswap.h"
+#include <ufs/ffs/fs.h>
+#include "ffs/ffs_extern.h"
+
+#define	fs_old_postbloff	fs_spare5[0]
+#define	fs_old_rotbloff		fs_spare5[1]
+#define	fs_old_postbl_start	fs_maxbsize
+#define	fs_old_headswitch	fs_id[0]
+#define	fs_old_trkseek	fs_id[1]
+#define	fs_old_csmask	fs_spare1[0]
+#define	fs_old_csshift	fs_spare1[1]
+
+#define	FS_42POSTBLFMT		-1	/* 4.2BSD rotational table format */
+#define	FS_DYNAMICPOSTBLFMT	1	/* dynamic rotational table format */
+
+void ffs_csum_swap(struct csum *o, struct csum *n, int size);
+void ffs_csumtotal_swap(struct csum_total *o, struct csum_total *n);
+
+void
+ffs_sb_swap(struct fs *o, struct fs *n)
+{
+	size_t i;
+	u_int32_t *o32, *n32;
+
+	/*
+	 * In order to avoid a lot of lines, as the first N fields (52)
+	 * of the superblock up to fs_fmod are u_int32_t, we just loop
+	 * here to convert them.
+	 */
+	o32 = (u_int32_t *)o;
+	n32 = (u_int32_t *)n;
+	for (i = 0; i < offsetof(struct fs, fs_fmod) / sizeof(u_int32_t); i++)
+		n32[i] = bswap32(o32[i]);
+
+	n->fs_swuid = bswap64(o->fs_swuid);
+	n->fs_cgrotor = bswap32(o->fs_cgrotor); /* Unused */
+	n->fs_old_cpc = bswap32(o->fs_old_cpc);
+
+	/* These fields overlap with a possible location for the
+	 * historic FS_DYNAMICPOSTBLFMT postbl table, and with the
+	 * first half of the historic FS_42POSTBLFMT postbl table.
+	 */
+	n->fs_maxbsize = bswap32(o->fs_maxbsize);
+	n->fs_sblockloc = bswap64(o->fs_sblockloc);
+	ffs_csumtotal_swap(&o->fs_cstotal, &n->fs_cstotal);
+	n->fs_time = bswap64(o->fs_time);
+	n->fs_size = bswap64(o->fs_size);
+	n->fs_dsize = bswap64(o->fs_dsize);
+	n->fs_csaddr = bswap64(o->fs_csaddr);
+	n->fs_pendingblocks = bswap64(o->fs_pendingblocks);
+	n->fs_pendinginodes = bswap32(o->fs_pendinginodes);
+
+	/* These fields overlap with the second half of the
+	 * historic FS_42POSTBLFMT postbl table
+	 */
+	for (i = 0; i < FSMAXSNAP; i++)
+		n->fs_snapinum[i] = bswap32(o->fs_snapinum[i]);
+	n->fs_avgfilesize = bswap32(o->fs_avgfilesize);
+	n->fs_avgfpdir = bswap32(o->fs_avgfpdir);
+	/* fs_sparecon[28] - ignore for now */
+	n->fs_flags = bswap32(o->fs_flags);
+	n->fs_contigsumsize = bswap32(o->fs_contigsumsize);
+	n->fs_maxsymlinklen = bswap32(o->fs_maxsymlinklen);
+	n->fs_old_inodefmt = bswap32(o->fs_old_inodefmt);
+	n->fs_maxfilesize = bswap64(o->fs_maxfilesize);
+	n->fs_qbmask = bswap64(o->fs_qbmask);
+	n->fs_qfmask = bswap64(o->fs_qfmask);
+	n->fs_state = bswap32(o->fs_state);
+	n->fs_old_postblformat = bswap32(o->fs_old_postblformat);
+	n->fs_old_nrpos = bswap32(o->fs_old_nrpos);
+	n->fs_old_postbloff = bswap32(o->fs_old_postbloff);
+	n->fs_old_rotbloff = bswap32(o->fs_old_rotbloff);
+
+	n->fs_magic = bswap32(o->fs_magic);
+}
+
+void
+ffs_dinode1_swap(struct ufs1_dinode *o, struct ufs1_dinode *n)
+{
+
+	n->di_mode = bswap16(o->di_mode);
+	n->di_nlink = bswap16(o->di_nlink);
+	n->di_size = bswap64(o->di_size);
+	n->di_atime = bswap32(o->di_atime);
+	n->di_atimensec = bswap32(o->di_atimensec);
+	n->di_mtime = bswap32(o->di_mtime);
+	n->di_mtimensec = bswap32(o->di_mtimensec);
+	n->di_ctime = bswap32(o->di_ctime);
+	n->di_ctimensec = bswap32(o->di_ctimensec);
+	memcpy(n->di_db, o->di_db, sizeof(n->di_db));
+	memcpy(n->di_ib, o->di_ib, sizeof(n->di_ib));
+	n->di_flags = bswap32(o->di_flags);
+	n->di_blocks = bswap32(o->di_blocks);
+	n->di_gen = bswap32(o->di_gen);
+	n->di_uid = bswap32(o->di_uid);
+	n->di_gid = bswap32(o->di_gid);
+}
+
+void
+ffs_dinode2_swap(struct ufs2_dinode *o, struct ufs2_dinode *n)
+{
+	n->di_mode = bswap16(o->di_mode);
+	n->di_nlink = bswap16(o->di_nlink);
+	n->di_uid = bswap32(o->di_uid);
+	n->di_gid = bswap32(o->di_gid);
+	n->di_blksize = bswap32(o->di_blksize);
+	n->di_size = bswap64(o->di_size);
+	n->di_blocks = bswap64(o->di_blocks);
+	n->di_atime = bswap64(o->di_atime);
+	n->di_atimensec = bswap32(o->di_atimensec);
+	n->di_mtime = bswap64(o->di_mtime);
+	n->di_mtimensec = bswap32(o->di_mtimensec);
+	n->di_ctime = bswap64(o->di_ctime);
+	n->di_ctimensec = bswap32(o->di_ctimensec);
+	n->di_birthtime = bswap64(o->di_ctime);
+	n->di_birthnsec = bswap32(o->di_ctimensec);
+	n->di_gen = bswap32(o->di_gen);
+	n->di_kernflags = bswap32(o->di_kernflags);
+	n->di_flags = bswap32(o->di_flags);
+	n->di_extsize = bswap32(o->di_extsize);
+	memcpy(n->di_extb, o->di_extb, sizeof(n->di_extb));
+	memcpy(n->di_db, o->di_db, sizeof(n->di_db));
+	memcpy(n->di_ib, o->di_ib, sizeof(n->di_ib));
+}
+
+void
+ffs_csum_swap(struct csum *o, struct csum *n, int size)
+{
+	size_t i;
+	u_int32_t *oint, *nint;
+
+	oint = (u_int32_t*)o;
+	nint = (u_int32_t*)n;
+
+	for (i = 0; i < size / sizeof(u_int32_t); i++)
+		nint[i] = bswap32(oint[i]);
+}
+
+void
+ffs_csumtotal_swap(struct csum_total *o, struct csum_total *n)
+{
+	n->cs_ndir = bswap64(o->cs_ndir);
+	n->cs_nbfree = bswap64(o->cs_nbfree);
+	n->cs_nifree = bswap64(o->cs_nifree);
+	n->cs_nffree = bswap64(o->cs_nffree);
+}
+
+/*
+ * Note that ffs_cg_swap may be called with o == n.
+ */
+void
+ffs_cg_swap(struct cg *o, struct cg *n, struct fs *fs)
+{
+	int i;
+	u_int32_t *n32, *o32;
+	u_int16_t *n16, *o16;
+	int32_t btotoff, boff, clustersumoff;
+
+	n->cg_firstfield = bswap32(o->cg_firstfield);
+	n->cg_magic = bswap32(o->cg_magic);
+	n->cg_old_time = bswap32(o->cg_old_time);
+	n->cg_cgx = bswap32(o->cg_cgx);
+	n->cg_old_ncyl = bswap16(o->cg_old_ncyl);
+	n->cg_old_niblk = bswap16(o->cg_old_niblk);
+	n->cg_ndblk = bswap32(o->cg_ndblk);
+	n->cg_cs.cs_ndir = bswap32(o->cg_cs.cs_ndir);
+	n->cg_cs.cs_nbfree = bswap32(o->cg_cs.cs_nbfree);
+	n->cg_cs.cs_nifree = bswap32(o->cg_cs.cs_nifree);
+	n->cg_cs.cs_nffree = bswap32(o->cg_cs.cs_nffree);
+	n->cg_rotor = bswap32(o->cg_rotor);
+	n->cg_frotor = bswap32(o->cg_frotor);
+	n->cg_irotor = bswap32(o->cg_irotor);
+	for (i = 0; i < MAXFRAG; i++)
+		n->cg_frsum[i] = bswap32(o->cg_frsum[i]);
+	
+	n->cg_old_btotoff = bswap32(o->cg_old_btotoff);
+	n->cg_old_boff = bswap32(o->cg_old_boff);
+	n->cg_iusedoff = bswap32(o->cg_iusedoff);
+	n->cg_freeoff = bswap32(o->cg_freeoff);
+	n->cg_nextfreeoff = bswap32(o->cg_nextfreeoff);
+	n->cg_clustersumoff = bswap32(o->cg_clustersumoff);
+	n->cg_clusteroff = bswap32(o->cg_clusteroff);
+	n->cg_nclusterblks = bswap32(o->cg_nclusterblks);
+	n->cg_niblk = bswap32(o->cg_niblk);
+	n->cg_initediblk = bswap32(o->cg_initediblk);
+	n->cg_time = bswap64(o->cg_time);
+
+	if (fs->fs_magic == FS_UFS2_MAGIC)
+		return;
+
+	if (n->cg_magic == CG_MAGIC) {
+		btotoff = n->cg_old_btotoff;
+		boff = n->cg_old_boff;
+		clustersumoff = n->cg_clustersumoff;
+	} else {
+		btotoff = bswap32(n->cg_old_btotoff);
+		boff = bswap32(n->cg_old_boff);
+		clustersumoff = bswap32(n->cg_clustersumoff);
+	}
+	n32 = (u_int32_t *)((u_int8_t *)n + btotoff);
+	o32 = (u_int32_t *)((u_int8_t *)o + btotoff);
+	n16 = (u_int16_t *)((u_int8_t *)n + boff);
+	o16 = (u_int16_t *)((u_int8_t *)o + boff);
+
+	for (i = 0; i < fs->fs_old_cpg; i++)
+		n32[i] = bswap32(o32[i]);
+	
+	for (i = 0; i < fs->fs_old_cpg * fs->fs_old_nrpos; i++)
+		n16[i] = bswap16(o16[i]);
+
+	n32 = (u_int32_t *)((u_int8_t *)n + clustersumoff);
+	o32 = (u_int32_t *)((u_int8_t *)o + clustersumoff);
+	for (i = 1; i < fs->fs_contigsumsize + 1; i++)
+		n32[i] = bswap32(o32[i]);
+}
diff --git a/usr.sbin/makefs/ffs/ffs_extern.h b/usr.sbin/makefs/ffs/ffs_extern.h
new file mode 100644
index 0000000000..99a2371806
--- /dev/null
+++ b/usr.sbin/makefs/ffs/ffs_extern.h
@@ -0,0 +1,79 @@
+/*	$NetBSD: ffs_extern.h,v 1.6 2003/08/07 11:25:33 agc Exp $	*/
+/* From: NetBSD: ffs_extern.h,v 1.19 2001/08/17 02:18:48 lukem Exp */
+
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1991, 1993, 1994
+ *	The Regents of the University of California.  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.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
+ *
+ *	@(#)ffs_extern.h	8.6 (Berkeley) 3/30/95
+ * $FreeBSD: head/usr.sbin/makefs/ffs/ffs_extern.h 326025 2017-11-20 19:49:47Z pfg $
+ */
+
+#include "ffs/buf.h"
+
+struct inode;
+
+/*
+ * Structure used to pass around logical block paths generated by
+ * ufs_getlbns and used by truncate and bmap code.
+ */
+struct indir {
+	daddr_t in_lbn;		/* Logical block number. */
+	int	in_off;			/* Offset in buffer. */
+};
+
+	/* ffs.c */
+_Noreturn void panic(const char *, ...) __printflike(1, 2);
+
+	/* ffs_alloc.c */
+int ffs_alloc(struct inode *, daddr_t, daddr_t, int, daddr_t *);
+daddr_t ffs_blkpref_ufs1(struct inode *, daddr_t, int, int32_t *);
+daddr_t ffs_blkpref_ufs2(struct inode *, daddr_t, int, int64_t *);
+void ffs_blkfree(struct inode *, daddr_t, long);
+void ffs_clusteracct(struct fs *, struct cg *, int32_t, int);
+
+	/* ffs_balloc.c */
+int ffs_balloc(struct inode *, off_t, int, struct buf **);
+
+	/* ffs_bswap.c */
+void ffs_sb_swap(struct fs*, struct fs *);
+void ffs_dinode1_swap(struct ufs1_dinode *, struct ufs1_dinode *);
+void ffs_dinode2_swap(struct ufs2_dinode *, struct ufs2_dinode *);
+void ffs_csum_swap(struct csum *, struct csum *, int);
+void ffs_cg_swap(struct cg *, struct cg *, struct fs *);
+
+	/* ffs_subr.c */
+void ffs_fragacct(struct fs *, int, int32_t[], int, int);
+int ffs_isblock(struct fs *, u_char *, int32_t);
+int ffs_isfreeblock(struct fs *, u_char *, int32_t);
+void ffs_clrblock(struct fs *, u_char *, int32_t);
+void ffs_setblock(struct fs *, u_char *, int32_t);
+
+	/* ufs_bmap.c */
+int ufs_getlbns(struct inode *, daddr_t, struct indir *, int *);
diff --git a/usr.sbin/makefs/ffs/ffs_subr.c b/usr.sbin/makefs/ffs/ffs_subr.c
new file mode 100644
index 0000000000..4f8aeffd74
--- /dev/null
+++ b/usr.sbin/makefs/ffs/ffs_subr.c
@@ -0,0 +1,184 @@
+/*	$NetBSD: ffs_subr.c,v 1.32 2003/12/30 12:33:24 pk Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1982, 1986, 1989, 1993
+ *	The Regents of the University of California.  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.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
+ *
+ *	@(#)ffs_subr.c	8.5 (Berkeley) 3/21/95
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/ffs/ffs_subr.c 333664 2018-05-16 02:58:05Z emaste $");
+
+#include <sys/param.h>
+#include <sys/types.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+#include "ffs/ffs_extern.h"
+#include "ffs/ufs_bswap.h"
+
+/*
+ * Update the frsum fields to reflect addition or deletion 
+ * of some frags.
+ */
+void
+ffs_fragacct_swap(struct fs *fs, int fragmap, int32_t fraglist[], int cnt, int needswap)
+{
+	int inblk;
+	int field, subfield;
+	int siz, pos;
+
+	inblk = (int)(fragtbl[fs->fs_frag][fragmap]) << 1;
+	fragmap <<= 1;
+	for (siz = 1; siz < fs->fs_frag; siz++) {
+		if ((inblk & (1 << (siz + (fs->fs_frag & (NBBY - 1))))) == 0)
+			continue;
+		field = around[siz];
+		subfield = inside[siz];
+		for (pos = siz; pos <= fs->fs_frag; pos++) {
+			if ((fragmap & field) == subfield) {
+				fraglist[siz] = ufs_rw32(
+				    ufs_rw32(fraglist[siz], needswap) + cnt,
+				    needswap);
+				pos += siz;
+				field <<= siz;
+				subfield <<= siz;
+			}
+			field <<= 1;
+			subfield <<= 1;
+		}
+	}
+}
+
+/*
+ * block operations
+ *
+ * check if a block is available
+ *  returns true if all the corresponding bits in the free map are 1
+ *  returns false if any corresponding bit in the free map is 0
+ */
+int
+ffs_isblock(struct fs *fs, u_char *cp, int32_t h)
+{
+	u_char mask;
+
+	switch ((int)fs->fs_fragshift) {
+	case 3:
+		return (cp[h] == 0xff);
+	case 2:
+		mask = 0x0f << ((h & 0x1) << 2);
+		return ((cp[h >> 1] & mask) == mask);
+	case 1:
+		mask = 0x03 << ((h & 0x3) << 1);
+		return ((cp[h >> 2] & mask) == mask);
+	case 0:
+		mask = 0x01 << (h & 0x7);
+		return ((cp[h >> 3] & mask) == mask);
+	default:
+		panic("ffs_isblock: unknown fs_fragshift %d",
+		    (int)fs->fs_fragshift);
+	}
+}
+
+/*
+ * check if a block is completely allocated
+ *  returns true if all the corresponding bits in the free map are 0
+ *  returns false if any corresponding bit in the free map is 1
+ */
+int
+ffs_isfreeblock(struct fs *fs, u_char *cp, int32_t h)
+{
+
+	switch ((int)fs->fs_fragshift) {
+	case 3:
+		return (cp[h] == 0);
+	case 2:
+		return ((cp[h >> 1] & (0x0f << ((h & 0x1) << 2))) == 0);
+	case 1:
+		return ((cp[h >> 2] & (0x03 << ((h & 0x3) << 1))) == 0);
+	case 0:
+		return ((cp[h >> 3] & (0x01 << (h & 0x7))) == 0);
+	default:
+		panic("ffs_isfreeblock: unknown fs_fragshift %d",
+		    (int)fs->fs_fragshift);
+	}
+}
+
+/*
+ * take a block out of the map
+ */
+void
+ffs_clrblock(struct fs *fs, u_char *cp, int32_t h)
+{
+
+	switch ((int)fs->fs_fragshift) {
+	case 3:
+		cp[h] = 0;
+		return;
+	case 2:
+		cp[h >> 1] &= ~(0x0f << ((h & 0x1) << 2));
+		return;
+	case 1:
+		cp[h >> 2] &= ~(0x03 << ((h & 0x3) << 1));
+		return;
+	case 0:
+		cp[h >> 3] &= ~(0x01 << (h & 0x7));
+		return;
+	default:
+		panic("ffs_clrblock: unknown fs_fragshift %d",
+		    (int)fs->fs_fragshift);
+	}
+}
+
+/*
+ * put a block into the map
+ */
+void
+ffs_setblock(struct fs *fs, u_char *cp, int32_t h)
+{
+
+	switch ((int)fs->fs_fragshift) {
+	case 3:
+		cp[h] = 0xff;
+		return;
+	case 2:
+		cp[h >> 1] |= (0x0f << ((h & 0x1) << 2));
+		return;
+	case 1:
+		cp[h >> 2] |= (0x03 << ((h & 0x3) << 1));
+		return;
+	case 0:
+		cp[h >> 3] |= (0x01 << (h & 0x7));
+		return;
+	default:
+		panic("ffs_setblock: unknown fs_fragshift %d",
+		    (int)fs->fs_fragshift);
+	}
+}
diff --git a/usr.sbin/makefs/ffs/mkfs.c b/usr.sbin/makefs/ffs/mkfs.c
new file mode 100644
index 0000000000..55121b30c2
--- /dev/null
+++ b/usr.sbin/makefs/ffs/mkfs.c
@@ -0,0 +1,837 @@
+/*	$NetBSD: mkfs.c,v 1.22 2011/10/09 22:30:13 christos Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * This software was developed for the FreeBSD Project by Marshall
+ * Kirk McKusick and Network Associates Laboratories, the Security
+ * Research Division of Network Associates, Inc. under DARPA/SPAWAR
+ * contract N66001-01-C-8035 ("CBOSS"), as part of the DARPA CHATS
+ * research program
+ *
+ * Copyright (c) 1980, 1989, 1993
+ *	The Regents of the University of California.  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.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/ffs/mkfs.c 326025 2017-11-20 19:49:47Z pfg $");
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <util.h>
+
+#include "makefs.h"
+#include "ffs.h"
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include "ffs/ufs_bswap.h"
+#include "ffs/ufs_inode.h"
+#include "ffs/ffs_extern.h"
+#include "ffs/newfs_extern.h"
+
+#ifndef BBSIZE
+#define	BBSIZE	8192			/* size of boot area, with label */
+#endif
+
+static void initcg(uint32_t, time_t, const fsinfo_t *);
+static int ilog2(int);
+
+static int count_digits(int);
+
+/*
+ * make file system for cylinder-group style file systems
+ */
+#define	UMASK		0755
+#define	POWEROF2(num)	(((num) & ((num) - 1)) == 0)
+
+static union {
+	struct fs fs;
+	char pad[SBLOCKSIZE];
+} fsun;
+#define	sblock	fsun.fs
+
+static union {
+	struct cg cg;
+	char pad[FFS_MAXBSIZE];
+} cgun;
+#define	acg	cgun.cg
+
+static char *iobuf;
+static int iobufsize;
+
+static char writebuf[FFS_MAXBSIZE];
+
+static int     Oflag;	   /* format as an 4.3BSD file system */
+static int64_t fssize;	   /* file system size */
+static int     sectorsize;	   /* bytes/sector */
+static int     fsize;	   /* fragment size */
+static int     bsize;	   /* block size */
+static int     maxbsize;   /* maximum clustering */
+static int     maxblkspercg;
+static int     minfree;	   /* free space threshold */
+static int     opt;		   /* optimization preference (space or time) */
+static int     density;	   /* number of bytes per inode */
+static int     maxcontig;	   /* max contiguous blocks to allocate */
+static int     maxbpg;	   /* maximum blocks per file in a cyl group */
+static int     bbsize;	   /* boot block size */
+static int     sbsize;	   /* superblock size */
+static int     avgfilesize;	   /* expected average file size */
+static int     avgfpdir;	   /* expected number of files per directory */
+
+struct fs *
+ffs_mkfs(const char *fsys, const fsinfo_t *fsopts, time_t tstamp)
+{
+	int fragsperinode, optimalfpg, origdensity, minfpg, lastminfpg;
+	int32_t csfrags;
+	uint32_t i, cylno;
+	long long sizepb;
+	void *space;
+	int size;
+	int nprintcols, printcolwidth;
+	ffs_opt_t	*ffs_opts = fsopts->fs_specific;
+
+	Oflag =		ffs_opts->version;
+	fssize =        fsopts->size / fsopts->sectorsize;
+	sectorsize =    fsopts->sectorsize;
+	fsize =         ffs_opts->fsize;
+	bsize =         ffs_opts->bsize;
+	maxbsize =      ffs_opts->maxbsize;
+	maxblkspercg =  ffs_opts->maxblkspercg;
+	minfree =       ffs_opts->minfree;
+	opt =           ffs_opts->optimization;
+	density =       ffs_opts->density;
+	maxcontig =     ffs_opts->maxcontig;
+	maxbpg =        ffs_opts->maxbpg;
+	avgfilesize =   ffs_opts->avgfilesize;
+	avgfpdir =      ffs_opts->avgfpdir;
+	bbsize =        BBSIZE;
+	sbsize =        SBLOCKSIZE;
+
+	strlcpy(sblock.fs_volname, ffs_opts->label, sizeof(sblock.fs_volname));
+
+	if (Oflag == 0) {
+		sblock.fs_old_inodefmt = FS_42INODEFMT;
+		sblock.fs_maxsymlinklen = 0;
+		sblock.fs_old_flags = 0;
+	} else {
+		sblock.fs_old_inodefmt = FS_44INODEFMT;
+		sblock.fs_maxsymlinklen = (Oflag == 1 ? UFS1_MAXSYMLINKLEN :
+		    UFS2_MAXSYMLINKLEN);
+		sblock.fs_old_flags = FS_FLAGS_UPDATED;
+		sblock.fs_flags = 0;
+	}
+	/*
+	 * Validate the given file system size.
+	 * Verify that its last block can actually be accessed.
+	 * Convert to file system fragment sized units.
+	 */
+	if (fssize <= 0) {
+		printf("preposterous size %lld\n", (long long)fssize);
+		exit(13);
+	}
+	ffs_wtfs(fssize - 1, sectorsize, (char *)&sblock, fsopts);
+
+	/*
+	 * collect and verify the filesystem density info
+	 */
+	sblock.fs_avgfilesize = avgfilesize;
+	sblock.fs_avgfpdir = avgfpdir;
+	if (sblock.fs_avgfilesize <= 0)
+		printf("illegal expected average file size %d\n",
+		    sblock.fs_avgfilesize), exit(14);
+	if (sblock.fs_avgfpdir <= 0)
+		printf("illegal expected number of files per directory %d\n",
+		    sblock.fs_avgfpdir), exit(15);
+	/*
+	 * collect and verify the block and fragment sizes
+	 */
+	sblock.fs_bsize = bsize;
+	sblock.fs_fsize = fsize;
+	if (!POWEROF2(sblock.fs_bsize)) {
+		printf("block size must be a power of 2, not %d\n",
+		    sblock.fs_bsize);
+		exit(16);
+	}
+	if (!POWEROF2(sblock.fs_fsize)) {
+		printf("fragment size must be a power of 2, not %d\n",
+		    sblock.fs_fsize);
+		exit(17);
+	}
+	if (sblock.fs_fsize < sectorsize) {
+		printf("fragment size %d is too small, minimum is %d\n",
+		    sblock.fs_fsize, sectorsize);
+		exit(18);
+	}
+	if (sblock.fs_bsize < MINBSIZE) {
+		printf("block size %d is too small, minimum is %d\n",
+		    sblock.fs_bsize, MINBSIZE);
+		exit(19);
+	}
+	if (sblock.fs_bsize > FFS_MAXBSIZE) {
+		printf("block size %d is too large, maximum is %d\n",
+		    sblock.fs_bsize, FFS_MAXBSIZE);
+		exit(19);
+	}
+	if (sblock.fs_bsize < sblock.fs_fsize) {
+		printf("block size (%d) cannot be smaller than fragment size (%d)\n",
+		    sblock.fs_bsize, sblock.fs_fsize);
+		exit(20);
+	}
+
+	if (maxbsize < bsize || !POWEROF2(maxbsize)) {
+		sblock.fs_maxbsize = sblock.fs_bsize;
+		printf("Extent size set to %d\n", sblock.fs_maxbsize);
+	} else if (sblock.fs_maxbsize > FS_MAXCONTIG * sblock.fs_bsize) {
+		sblock.fs_maxbsize = FS_MAXCONTIG * sblock.fs_bsize;
+		printf("Extent size reduced to %d\n", sblock.fs_maxbsize);
+	} else {
+		sblock.fs_maxbsize = maxbsize;
+	}
+	sblock.fs_maxcontig = maxcontig;
+	if (sblock.fs_maxcontig < sblock.fs_maxbsize / sblock.fs_bsize) {
+		sblock.fs_maxcontig = sblock.fs_maxbsize / sblock.fs_bsize;
+		printf("Maxcontig raised to %d\n", sblock.fs_maxbsize);
+	}
+
+	if (sblock.fs_maxcontig > 1)
+		sblock.fs_contigsumsize = MIN(sblock.fs_maxcontig,FS_MAXCONTIG);
+
+	sblock.fs_bmask = ~(sblock.fs_bsize - 1);
+	sblock.fs_fmask = ~(sblock.fs_fsize - 1);
+	sblock.fs_qbmask = ~sblock.fs_bmask;
+	sblock.fs_qfmask = ~sblock.fs_fmask;
+	for (sblock.fs_bshift = 0, i = sblock.fs_bsize; i > 1; i >>= 1)
+		sblock.fs_bshift++;
+	for (sblock.fs_fshift = 0, i = sblock.fs_fsize; i > 1; i >>= 1)
+		sblock.fs_fshift++;
+	sblock.fs_frag = numfrags(&sblock, sblock.fs_bsize);
+	for (sblock.fs_fragshift = 0, i = sblock.fs_frag; i > 1; i >>= 1)
+		sblock.fs_fragshift++;
+	if (sblock.fs_frag > MAXFRAG) {
+		printf("fragment size %d is too small, "
+			"minimum with block size %d is %d\n",
+		    sblock.fs_fsize, sblock.fs_bsize,
+		    sblock.fs_bsize / MAXFRAG);
+		exit(21);
+	}
+	sblock.fs_fsbtodb = ilog2(sblock.fs_fsize / sectorsize);
+	sblock.fs_size = sblock.fs_providersize = fssize =
+	    dbtofsb(&sblock, fssize);
+
+	if (Oflag <= 1) {
+		sblock.fs_magic = FS_UFS1_MAGIC;
+		sblock.fs_sblockloc = SBLOCK_UFS1;
+		sblock.fs_nindir = sblock.fs_bsize / sizeof(ufs1_daddr_t);
+		sblock.fs_inopb = sblock.fs_bsize / sizeof(struct ufs1_dinode);
+		sblock.fs_maxsymlinklen = ((UFS_NDADDR + UFS_NIADDR) *
+		    sizeof (ufs1_daddr_t));
+		sblock.fs_old_inodefmt = FS_44INODEFMT;
+		sblock.fs_old_cgoffset = 0;
+		sblock.fs_old_cgmask = 0xffffffff;
+		sblock.fs_old_size = sblock.fs_size;
+		sblock.fs_old_rotdelay = 0;
+		sblock.fs_old_rps = 60;
+		sblock.fs_old_nspf = sblock.fs_fsize / sectorsize;
+		sblock.fs_old_cpg = 1;
+		sblock.fs_old_interleave = 1;
+		sblock.fs_old_trackskew = 0;
+		sblock.fs_old_cpc = 0;
+		sblock.fs_old_postblformat = 1;
+		sblock.fs_old_nrpos = 1;
+	} else {
+		sblock.fs_magic = FS_UFS2_MAGIC;
+		sblock.fs_sblockloc = SBLOCK_UFS2;
+		sblock.fs_nindir = sblock.fs_bsize / sizeof(ufs2_daddr_t);
+		sblock.fs_inopb = sblock.fs_bsize / sizeof(struct ufs2_dinode);
+		sblock.fs_maxsymlinklen = ((UFS_NDADDR + UFS_NIADDR) *
+		    sizeof (ufs2_daddr_t));
+		if (ffs_opts->softupdates == 1)
+			sblock.fs_flags |= FS_DOSOFTDEP;
+	}
+
+	sblock.fs_sblkno =
+	    roundup(howmany(sblock.fs_sblockloc + SBLOCKSIZE, sblock.fs_fsize),
+		sblock.fs_frag);
+	sblock.fs_cblkno = (daddr_t)(sblock.fs_sblkno +
+	    roundup(howmany(SBLOCKSIZE, sblock.fs_fsize), sblock.fs_frag));
+	sblock.fs_iblkno = sblock.fs_cblkno + sblock.fs_frag;
+	sblock.fs_maxfilesize = sblock.fs_bsize * UFS_NDADDR - 1;
+	for (sizepb = sblock.fs_bsize, i = 0; i < UFS_NIADDR; i++) {
+		sizepb *= NINDIR(&sblock);
+		sblock.fs_maxfilesize += sizepb;
+	}
+
+	/*
+	 * Calculate the number of blocks to put into each cylinder group.
+	 *
+	 * This algorithm selects the number of blocks per cylinder
+	 * group. The first goal is to have at least enough data blocks
+	 * in each cylinder group to meet the density requirement. Once
+	 * this goal is achieved we try to expand to have at least
+	 * 1 cylinder group. Once this goal is achieved, we pack as
+	 * many blocks into each cylinder group map as will fit.
+	 *
+	 * We start by calculating the smallest number of blocks that we
+	 * can put into each cylinder group. If this is too big, we reduce
+	 * the density until it fits.
+	 */
+	origdensity = density;
+	for (;;) {
+		fragsperinode = MAX(numfrags(&sblock, density), 1);
+		minfpg = fragsperinode * INOPB(&sblock);
+		if (minfpg > sblock.fs_size)
+			minfpg = sblock.fs_size;
+		sblock.fs_ipg = INOPB(&sblock);
+		sblock.fs_fpg = roundup(sblock.fs_iblkno +
+		    sblock.fs_ipg / INOPF(&sblock), sblock.fs_frag);
+		if (sblock.fs_fpg < minfpg)
+			sblock.fs_fpg = minfpg;
+		sblock.fs_ipg = roundup(howmany(sblock.fs_fpg, fragsperinode),
+		    INOPB(&sblock));
+		sblock.fs_fpg = roundup(sblock.fs_iblkno +
+		    sblock.fs_ipg / INOPF(&sblock), sblock.fs_frag);
+		if (sblock.fs_fpg < minfpg)
+			sblock.fs_fpg = minfpg;
+		sblock.fs_ipg = roundup(howmany(sblock.fs_fpg, fragsperinode),
+		    INOPB(&sblock));
+		if (CGSIZE(&sblock) < (unsigned long)sblock.fs_bsize)
+			break;
+		density -= sblock.fs_fsize;
+	}
+	if (density != origdensity)
+		printf("density reduced from %d to %d\n", origdensity, density);
+
+	if (maxblkspercg <= 0 || maxblkspercg >= fssize)
+		maxblkspercg = fssize - 1;
+	/*
+	 * Start packing more blocks into the cylinder group until
+	 * it cannot grow any larger, the number of cylinder groups
+	 * drops below 1, or we reach the size requested.
+	 */
+	for ( ; sblock.fs_fpg < maxblkspercg; sblock.fs_fpg += sblock.fs_frag) {
+		sblock.fs_ipg = roundup(howmany(sblock.fs_fpg, fragsperinode),
+		    INOPB(&sblock));
+		if (sblock.fs_size / sblock.fs_fpg < 1)
+			break;
+		if (CGSIZE(&sblock) < (unsigned long)sblock.fs_bsize)
+			continue;
+		if (CGSIZE(&sblock) == (unsigned long)sblock.fs_bsize)
+			break;
+		sblock.fs_fpg -= sblock.fs_frag;
+		sblock.fs_ipg = roundup(howmany(sblock.fs_fpg, fragsperinode),
+		    INOPB(&sblock));
+		break;
+	}
+	/*
+	 * Check to be sure that the last cylinder group has enough blocks
+	 * to be viable. If it is too small, reduce the number of blocks
+	 * per cylinder group which will have the effect of moving more
+	 * blocks into the last cylinder group.
+	 */
+	optimalfpg = sblock.fs_fpg;
+	for (;;) {
+		sblock.fs_ncg = howmany(sblock.fs_size, sblock.fs_fpg);
+		lastminfpg = roundup(sblock.fs_iblkno +
+		    sblock.fs_ipg / INOPF(&sblock), sblock.fs_frag);
+		if (sblock.fs_size < lastminfpg) {
+			printf("Filesystem size %lld < minimum size of %d\n",
+			    (long long)sblock.fs_size, lastminfpg);
+			exit(28);
+		}
+		if (sblock.fs_size % sblock.fs_fpg >= lastminfpg ||
+		    sblock.fs_size % sblock.fs_fpg == 0)
+			break;
+		sblock.fs_fpg -= sblock.fs_frag;
+		sblock.fs_ipg = roundup(howmany(sblock.fs_fpg, fragsperinode),
+		    INOPB(&sblock));
+	}
+	if (optimalfpg != sblock.fs_fpg)
+		printf("Reduced frags per cylinder group from %d to %d %s\n",
+		   optimalfpg, sblock.fs_fpg, "to enlarge last cyl group");
+	sblock.fs_cgsize = fragroundup(&sblock, CGSIZE(&sblock));
+	sblock.fs_dblkno = sblock.fs_iblkno + sblock.fs_ipg / INOPF(&sblock);
+	if (Oflag <= 1) {
+		sblock.fs_old_spc = sblock.fs_fpg * sblock.fs_old_nspf;
+		sblock.fs_old_nsect = sblock.fs_old_spc;
+		sblock.fs_old_npsect = sblock.fs_old_spc;
+		sblock.fs_old_ncyl = sblock.fs_ncg;
+	}
+
+	/*
+	 * fill in remaining fields of the super block
+	 */
+	sblock.fs_csaddr = cgdmin(&sblock, 0);
+	sblock.fs_cssize =
+	    fragroundup(&sblock, sblock.fs_ncg * sizeof(struct csum));
+
+	/*
+	 * Setup memory for temporary in-core cylgroup summaries.
+	 * Cribbed from ffs_mountfs().
+	 */
+	size = sblock.fs_cssize;
+	if (sblock.fs_contigsumsize > 0)
+		size += sblock.fs_ncg * sizeof(int32_t);
+	space = ecalloc(1, size);
+	sblock.fs_csp = space;
+	space = (char *)space + sblock.fs_cssize;
+	if (sblock.fs_contigsumsize > 0) {
+		int32_t *lp;
+
+		sblock.fs_maxcluster = lp = space;
+		for (i = 0; i < sblock.fs_ncg; i++)
+		*lp++ = sblock.fs_contigsumsize;
+	}
+
+	sblock.fs_sbsize = fragroundup(&sblock, sizeof(struct fs));
+	if (sblock.fs_sbsize > SBLOCKSIZE)
+		sblock.fs_sbsize = SBLOCKSIZE;
+	sblock.fs_minfree = minfree;
+	sblock.fs_maxcontig = maxcontig;
+	sblock.fs_maxbpg = maxbpg;
+	sblock.fs_optim = opt;
+	sblock.fs_cgrotor = 0;
+	sblock.fs_pendingblocks = 0;
+	sblock.fs_pendinginodes = 0;
+	sblock.fs_cstotal.cs_ndir = 0;
+	sblock.fs_cstotal.cs_nbfree = 0;
+	sblock.fs_cstotal.cs_nifree = 0;
+	sblock.fs_cstotal.cs_nffree = 0;
+	sblock.fs_fmod = 0;
+	sblock.fs_ronly = 0;
+	sblock.fs_state = 0;
+	sblock.fs_clean = FS_ISCLEAN;
+	sblock.fs_ronly = 0;
+	sblock.fs_id[0] = tstamp;
+	sblock.fs_id[1] = random();
+	sblock.fs_fsmnt[0] = '\0';
+	csfrags = howmany(sblock.fs_cssize, sblock.fs_fsize);
+	sblock.fs_dsize = sblock.fs_size - sblock.fs_sblkno -
+	    sblock.fs_ncg * (sblock.fs_dblkno - sblock.fs_sblkno);
+	sblock.fs_cstotal.cs_nbfree =
+	    fragstoblks(&sblock, sblock.fs_dsize) -
+	    howmany(csfrags, sblock.fs_frag);
+	sblock.fs_cstotal.cs_nffree =
+	    fragnum(&sblock, sblock.fs_size) +
+	    (fragnum(&sblock, csfrags) > 0 ?
+	    sblock.fs_frag - fragnum(&sblock, csfrags) : 0);
+	sblock.fs_cstotal.cs_nifree =
+	    sblock.fs_ncg * sblock.fs_ipg - UFS_ROOTINO;
+	sblock.fs_cstotal.cs_ndir = 0;
+	sblock.fs_dsize -= csfrags;
+	sblock.fs_time = tstamp;
+	if (Oflag <= 1) {
+		sblock.fs_old_time = tstamp;
+		sblock.fs_old_dsize = sblock.fs_dsize;
+		sblock.fs_old_csaddr = sblock.fs_csaddr;
+		sblock.fs_old_cstotal.cs_ndir = sblock.fs_cstotal.cs_ndir;
+		sblock.fs_old_cstotal.cs_nbfree = sblock.fs_cstotal.cs_nbfree;
+		sblock.fs_old_cstotal.cs_nifree = sblock.fs_cstotal.cs_nifree;
+		sblock.fs_old_cstotal.cs_nffree = sblock.fs_cstotal.cs_nffree;
+	}
+	/*
+	 * Dump out summary information about file system.
+	 */
+#define	B2MBFACTOR (1 / (1024.0 * 1024.0))
+	printf("%s: %.1fMB (%lld sectors) block size %d, "
+	       "fragment size %d\n",
+	    fsys, (float)sblock.fs_size * sblock.fs_fsize * B2MBFACTOR,
+	    (long long)fsbtodb(&sblock, sblock.fs_size),
+	    sblock.fs_bsize, sblock.fs_fsize);
+	printf("\tusing %d cylinder groups of %.2fMB, %d blks, "
+	       "%d inodes.\n",
+	    sblock.fs_ncg,
+	    (float)sblock.fs_fpg * sblock.fs_fsize * B2MBFACTOR,
+	    sblock.fs_fpg / sblock.fs_frag, sblock.fs_ipg);
+#undef B2MBFACTOR
+	/*
+	 * Now determine how wide each column will be, and calculate how
+	 * many columns will fit in a 76 char line. 76 is the width of the
+	 * subwindows in sysinst.
+	 */
+	printcolwidth = count_digits(
+			fsbtodb(&sblock, cgsblock(&sblock, sblock.fs_ncg -1)));
+	nprintcols = 76 / (printcolwidth + 2);
+
+	/*
+	 * allocate space for superblock, cylinder group map, and
+	 * two sets of inode blocks.
+	 */
+	if (sblock.fs_bsize < SBLOCKSIZE)
+		iobufsize = SBLOCKSIZE + 3 * sblock.fs_bsize;
+	else
+		iobufsize = 4 * sblock.fs_bsize;
+	iobuf = ecalloc(1, iobufsize);
+	/*
+	 * Make a copy of the superblock into the buffer that we will be
+	 * writing out in each cylinder group.
+	 */
+	memcpy(writebuf, &sblock, sbsize);
+	if (fsopts->needswap)
+		ffs_sb_swap(&sblock, (struct fs*)writebuf);
+	memcpy(iobuf, writebuf, SBLOCKSIZE);
+
+	printf("super-block backups (for fsck -b #) at:");
+	for (cylno = 0; cylno < sblock.fs_ncg; cylno++) {
+		initcg(cylno, tstamp, fsopts);
+		if (cylno % nprintcols == 0)
+			printf("\n");
+		printf(" %*lld,", printcolwidth,
+			(long long)fsbtodb(&sblock, cgsblock(&sblock, cylno)));
+		fflush(stdout);
+	}
+	printf("\n");
+
+	/*
+	 * Now construct the initial file system,
+	 * then write out the super-block.
+	 */
+	sblock.fs_time = tstamp;
+	if (Oflag <= 1) {
+		sblock.fs_old_cstotal.cs_ndir = sblock.fs_cstotal.cs_ndir;
+		sblock.fs_old_cstotal.cs_nbfree = sblock.fs_cstotal.cs_nbfree;
+		sblock.fs_old_cstotal.cs_nifree = sblock.fs_cstotal.cs_nifree;
+		sblock.fs_old_cstotal.cs_nffree = sblock.fs_cstotal.cs_nffree;
+	}
+	if (fsopts->needswap)
+		sblock.fs_flags |= FS_SWAPPED;
+	ffs_write_superblock(&sblock, fsopts);
+	return (&sblock);
+}
+
+/*
+ * Write out the superblock and its duplicates,
+ * and the cylinder group summaries
+ */
+void
+ffs_write_superblock(struct fs *fs, const fsinfo_t *fsopts)
+{
+	int size, blks, i, saveflag;
+	uint32_t cylno;
+	void *space;
+	char *wrbuf;
+
+	saveflag = fs->fs_flags & FS_INTERNAL;
+	fs->fs_flags &= ~FS_INTERNAL;
+
+        memcpy(writebuf, &sblock, sbsize);
+	if (fsopts->needswap)
+		ffs_sb_swap(fs, (struct fs*)writebuf);
+	ffs_wtfs(fs->fs_sblockloc / sectorsize, sbsize, writebuf, fsopts);
+
+	/* Write out the duplicate super blocks */
+	for (cylno = 0; cylno < fs->fs_ncg; cylno++)
+		ffs_wtfs(fsbtodb(fs, cgsblock(fs, cylno)),
+		    sbsize, writebuf, fsopts);
+
+	/* Write out the cylinder group summaries */
+	size = fs->fs_cssize;
+	blks = howmany(size, fs->fs_fsize);
+	space = (void *)fs->fs_csp;
+	wrbuf = emalloc(size);
+	for (i = 0; i < blks; i+= fs->fs_frag) {
+		size = fs->fs_bsize;
+		if (i + fs->fs_frag > blks)
+			size = (blks - i) * fs->fs_fsize;
+		if (fsopts->needswap)
+			ffs_csum_swap((struct csum *)space,
+			    (struct csum *)wrbuf, size);
+		else
+			memcpy(wrbuf, space, (u_int)size);
+		ffs_wtfs(fsbtodb(fs, fs->fs_csaddr + i), size, wrbuf, fsopts);
+		space = (char *)space + size;
+	}
+	free(wrbuf);
+	fs->fs_flags |= saveflag;
+}
+
+/*
+ * Initialize a cylinder group.
+ */
+static void
+initcg(uint32_t cylno, time_t utime, const fsinfo_t *fsopts)
+{
+	daddr_t cbase, dmax;
+	int32_t blkno;
+	uint32_t i, j, d, dlower, dupper;
+	struct ufs1_dinode *dp1;
+	struct ufs2_dinode *dp2;
+	int start;
+
+	/*
+	 * Determine block bounds for cylinder group.
+	 * Allow space for super block summary information in first
+	 * cylinder group.
+	 */
+	cbase = cgbase(&sblock, cylno);
+	dmax = cbase + sblock.fs_fpg;
+	if (dmax > sblock.fs_size)
+		dmax = sblock.fs_size;
+	dlower = cgsblock(&sblock, cylno) - cbase;
+	dupper = cgdmin(&sblock, cylno) - cbase;
+	if (cylno == 0)
+		dupper += howmany(sblock.fs_cssize, sblock.fs_fsize);
+	memset(&acg, 0, sblock.fs_cgsize);
+	acg.cg_time = utime;
+	acg.cg_magic = CG_MAGIC;
+	acg.cg_cgx = cylno;
+	acg.cg_niblk = sblock.fs_ipg;
+	acg.cg_initediblk = MIN(sblock.fs_ipg, 2 * INOPB(&sblock));
+	acg.cg_ndblk = dmax - cbase;
+	if (sblock.fs_contigsumsize > 0)
+		acg.cg_nclusterblks = acg.cg_ndblk >> sblock.fs_fragshift;
+	start = &acg.cg_space[0] - (u_char *)(&acg.cg_firstfield);
+	if (Oflag == 2) {
+		acg.cg_iusedoff = start;
+	} else {
+		if (cylno == sblock.fs_ncg - 1)
+			acg.cg_old_ncyl = howmany(acg.cg_ndblk,
+			    sblock.fs_fpg / sblock.fs_old_cpg);
+		else
+			acg.cg_old_ncyl = sblock.fs_old_cpg;
+		acg.cg_old_time = acg.cg_time;
+		acg.cg_time = 0;
+		acg.cg_old_niblk = acg.cg_niblk;
+		acg.cg_niblk = 0;
+		acg.cg_initediblk = 0;
+		acg.cg_old_btotoff = start;
+		acg.cg_old_boff = acg.cg_old_btotoff +
+		    sblock.fs_old_cpg * sizeof(int32_t);
+		acg.cg_iusedoff = acg.cg_old_boff +
+		    sblock.fs_old_cpg * sizeof(u_int16_t);
+	}
+	acg.cg_freeoff = acg.cg_iusedoff + howmany(sblock.fs_ipg, CHAR_BIT);
+	if (sblock.fs_contigsumsize <= 0) {
+		acg.cg_nextfreeoff = acg.cg_freeoff +
+		   howmany(sblock.fs_fpg, CHAR_BIT);
+	} else {
+		acg.cg_clustersumoff = acg.cg_freeoff +
+		    howmany(sblock.fs_fpg, CHAR_BIT) - sizeof(int32_t);
+		acg.cg_clustersumoff =
+		    roundup(acg.cg_clustersumoff, sizeof(int32_t));
+		acg.cg_clusteroff = acg.cg_clustersumoff +
+		    (sblock.fs_contigsumsize + 1) * sizeof(int32_t);
+		acg.cg_nextfreeoff = acg.cg_clusteroff +
+		    howmany(fragstoblks(&sblock, sblock.fs_fpg), CHAR_BIT);
+	}
+	if (acg.cg_nextfreeoff > (uint32_t)sblock.fs_cgsize) {
+		printf("Panic: cylinder group too big\n");
+		exit(37);
+	}
+	acg.cg_cs.cs_nifree += sblock.fs_ipg;
+	if (cylno == 0)
+		for (i = 0; i < UFS_ROOTINO; i++) {
+			setbit(cg_inosused_swap(&acg, 0), i);
+			acg.cg_cs.cs_nifree--;
+		}
+	if (cylno > 0) {
+		/*
+		 * In cylno 0, beginning space is reserved
+		 * for boot and super blocks.
+		 */
+		for (d = 0, blkno = 0; d < dlower;) {
+			ffs_setblock(&sblock, cg_blksfree_swap(&acg, 0), blkno);
+			if (sblock.fs_contigsumsize > 0)
+				setbit(cg_clustersfree_swap(&acg, 0), blkno);
+			acg.cg_cs.cs_nbfree++;
+			d += sblock.fs_frag;
+			blkno++;
+		}
+	}
+	if ((i = (dupper & (sblock.fs_frag - 1))) != 0) {
+		acg.cg_frsum[sblock.fs_frag - i]++;
+		for (d = dupper + sblock.fs_frag - i; dupper < d; dupper++) {
+			setbit(cg_blksfree_swap(&acg, 0), dupper);
+			acg.cg_cs.cs_nffree++;
+		}
+	}
+	for (d = dupper, blkno = dupper >> sblock.fs_fragshift;
+	     d + sblock.fs_frag <= acg.cg_ndblk; ) {
+		ffs_setblock(&sblock, cg_blksfree_swap(&acg, 0), blkno);
+		if (sblock.fs_contigsumsize > 0)
+			setbit(cg_clustersfree_swap(&acg, 0), blkno);
+		acg.cg_cs.cs_nbfree++;
+		d += sblock.fs_frag;
+		blkno++;
+	}
+	if (d < acg.cg_ndblk) {
+		acg.cg_frsum[acg.cg_ndblk - d]++;
+		for (; d < acg.cg_ndblk; d++) {
+			setbit(cg_blksfree_swap(&acg, 0), d);
+			acg.cg_cs.cs_nffree++;
+		}
+	}
+	if (sblock.fs_contigsumsize > 0) {
+		int32_t *sump = cg_clustersum_swap(&acg, 0);
+		u_char *mapp = cg_clustersfree_swap(&acg, 0);
+		int map = *mapp++;
+		int bit = 1;
+		int run = 0;
+
+		for (i = 0; i < acg.cg_nclusterblks; i++) {
+			if ((map & bit) != 0) {
+				run++;
+			} else if (run != 0) {
+				if (run > sblock.fs_contigsumsize)
+					run = sblock.fs_contigsumsize;
+				sump[run]++;
+				run = 0;
+			}
+			if ((i & (CHAR_BIT - 1)) != (CHAR_BIT - 1)) {
+				bit <<= 1;
+			} else {
+				map = *mapp++;
+				bit = 1;
+			}
+		}
+		if (run != 0) {
+			if (run > sblock.fs_contigsumsize)
+				run = sblock.fs_contigsumsize;
+			sump[run]++;
+		}
+	}
+	sblock.fs_cs(&sblock, cylno) = acg.cg_cs;
+	/*
+	 * Write out the duplicate super block, the cylinder group map
+	 * and two blocks worth of inodes in a single write.
+	 */
+	start = MAX(sblock.fs_bsize, SBLOCKSIZE);
+	memcpy(&iobuf[start], &acg, sblock.fs_cgsize);
+	if (fsopts->needswap)
+		ffs_cg_swap(&acg, (struct cg*)&iobuf[start], &sblock);
+	start += sblock.fs_bsize;
+	dp1 = (struct ufs1_dinode *)(&iobuf[start]);
+	dp2 = (struct ufs2_dinode *)(&iobuf[start]);
+	for (i = 0; i < acg.cg_initediblk; i++) {
+		if (sblock.fs_magic == FS_UFS1_MAGIC) {
+			/* No need to swap, it'll stay random */
+			dp1->di_gen = random();
+			dp1++;
+		} else {
+			dp2->di_gen = random();
+			dp2++;
+		}
+	}
+	ffs_wtfs(fsbtodb(&sblock, cgsblock(&sblock, cylno)), iobufsize, iobuf,
+	    fsopts);
+	/*
+	 * For the old file system, we have to initialize all the inodes.
+	 */
+	if (Oflag <= 1) {
+		for (i = 2 * sblock.fs_frag;
+		     i < sblock.fs_ipg / INOPF(&sblock);
+		     i += sblock.fs_frag) {
+			dp1 = (struct ufs1_dinode *)(&iobuf[start]);
+			for (j = 0; j < INOPB(&sblock); j++) {
+				dp1->di_gen = random();
+				dp1++;
+			}
+			ffs_wtfs(fsbtodb(&sblock, cgimin(&sblock, cylno) + i),
+			    sblock.fs_bsize, &iobuf[start], fsopts);
+		}
+	}
+}
+
+/*
+ * read a block from the file system
+ */
+void
+ffs_rdfs(daddr_t bno, int size, void *bf, const fsinfo_t *fsopts)
+{
+	int n;
+	off_t offset;
+
+	offset = bno * fsopts->sectorsize + fsopts->offset;
+	if (lseek(fsopts->fd, offset, SEEK_SET) < 0)
+		err(1, "%s: seek error for sector %lld", __func__,
+		    (long long)bno);
+	n = read(fsopts->fd, bf, size);
+	if (n == -1) {
+		abort();
+		err(1, "%s: read error bno %lld size %d", __func__,
+		    (long long)bno, size);
+	}
+	else if (n != size)
+		errx(1, "%s: read error for sector %lld", __func__,
+		    (long long)bno);
+}
+
+/*
+ * write a block to the file system
+ */
+void
+ffs_wtfs(daddr_t bno, int size, void *bf, const fsinfo_t *fsopts)
+{
+	int n;
+	off_t offset;
+
+	offset = bno * fsopts->sectorsize + fsopts->offset;
+	if (lseek(fsopts->fd, offset, SEEK_SET) < 0)
+		err(1, "%s: seek error for sector %lld", __func__,
+		    (long long)bno);
+	n = write(fsopts->fd, bf, size);
+	if (n == -1)
+		err(1, "%s: write error for sector %lld", __func__,
+		    (long long)bno);
+	else if (n != size)
+		errx(1, "%s: write error for sector %lld", __func__,
+		    (long long)bno);
+}
+
+
+/* Determine how many digits are needed to print a given integer */
+static int
+count_digits(int num)
+{
+	int ndig;
+
+	for(ndig = 1; num > 9; num /=10, ndig++);
+
+	return (ndig);
+}
+
+static int
+ilog2(int val)
+{
+	u_int n;
+
+	for (n = 0; n < sizeof(n) * CHAR_BIT; n++)
+		if (1 << n == val)
+			return (n);
+	errx(1, "%s: %d is not a power of 2", __func__, val);
+}
diff --git a/usr.sbin/makefs/ffs/newfs_extern.h b/usr.sbin/makefs/ffs/newfs_extern.h
new file mode 100644
index 0000000000..4cadf8514f
--- /dev/null
+++ b/usr.sbin/makefs/ffs/newfs_extern.h
@@ -0,0 +1,38 @@
+/*	$NetBSD: newfs_extern.h,v 1.4 2015/12/21 00:58:08 christos Exp $	*/
+/* From: NetBSD: extern.h,v 1.3 2000/12/01 12:03:27 simonb Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-NetBSD
+ *
+ * Copyright (c) 1997 Christos Zoulas.  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 ``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 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.
+ *
+ * $FreeBSD: head/usr.sbin/makefs/ffs/newfs_extern.h 326276 2017-11-27 15:37:16Z pfg $
+ */
+
+/* prototypes */
+struct fs	*ffs_mkfs(const char *, const fsinfo_t *, time_t);
+void		ffs_write_superblock(struct fs *, const fsinfo_t *);
+void		ffs_rdfs(daddr_t, int, void *, const fsinfo_t *);
+void		ffs_wtfs(daddr_t, int, void *, const fsinfo_t *);
+
+#define	FFS_MAXBSIZE	65536
diff --git a/usr.sbin/makefs/ffs/ufs_bmap.c b/usr.sbin/makefs/ffs/ufs_bmap.c
new file mode 100644
index 0000000000..e6576c6936
--- /dev/null
+++ b/usr.sbin/makefs/ffs/ufs_bmap.c
@@ -0,0 +1,142 @@
+/*	$NetBSD: ufs_bmap.c,v 1.14 2004/06/20 22:20:18 jmc Exp $	*/
+/* From: NetBSD: ufs_bmap.c,v 1.14 2001/11/08 05:00:51 chs Exp */
+
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1989, 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * (c) UNIX System Laboratories, Inc.
+ * All or some portions of this file are derived from material licensed
+ * to the University of California by American Telephone and Telegraph
+ * Co. or Unix System Laboratories, Inc. and are reproduced herein with
+ * the permission of UNIX System Laboratories, Inc.
+ *
+ * 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.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
+ *
+ *	@(#)ufs_bmap.c	8.8 (Berkeley) 8/11/95
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/ffs/ufs_bmap.c 326025 2017-11-20 19:49:47Z pfg $");
+
+#include <sys/param.h>
+#include <sys/time.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <strings.h>
+
+#include "makefs.h"
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include "ffs/ufs_bswap.h"
+#include "ffs/ufs_inode.h"
+#include "ffs/ffs_extern.h"
+
+/*
+ * Create an array of logical block number/offset pairs which represent the
+ * path of indirect blocks required to access a data block.  The first "pair"
+ * contains the logical block number of the appropriate single, double or
+ * triple indirect block and the offset into the inode indirect block array.
+ * Note, the logical block number of the inode single/double/triple indirect
+ * block appears twice in the array, once with the offset into the i_ffs_ib and
+ * once with the offset into the page itself.
+ */
+int
+ufs_getlbns(struct inode *ip, daddr_t bn, struct indir *ap, int *nump)
+{
+	daddr_t metalbn, realbn;
+	int64_t blockcnt;
+	int lbc;
+	int i, numlevels, off;
+	u_long lognindir;
+
+	lognindir = ffs(NINDIR(ip->i_fs)) - 1;
+	if (nump)
+		*nump = 0;
+	numlevels = 0;
+	realbn = bn;
+	if ((long)bn < 0)
+		bn = -(long)bn;
+
+	assert (bn >= UFS_NDADDR);
+
+	/* 
+	 * Determine the number of levels of indirection.  After this loop
+	 * is done, blockcnt indicates the number of data blocks possible
+	 * at the given level of indirection, and UFS_NIADDR - i is the number
+	 * of levels of indirection needed to locate the requested block.
+	 */
+
+	bn -= UFS_NDADDR;
+	for (lbc = 0, i = UFS_NIADDR;; i--, bn -= blockcnt) {
+		if (i == 0)
+			return (EFBIG);
+
+		lbc += lognindir;
+		blockcnt = (int64_t)1 << lbc;
+
+		if (bn < blockcnt)
+			break;
+	}
+
+	/* Calculate the address of the first meta-block. */
+	if (realbn >= 0)
+		metalbn = -(realbn - bn + UFS_NIADDR - i);
+	else
+		metalbn = -(-realbn - bn + UFS_NIADDR - i);
+
+	/* 
+	 * At each iteration, off is the offset into the bap array which is
+	 * an array of disk addresses at the current level of indirection.
+	 * The logical block number and the offset in that block are stored
+	 * into the argument array.
+	 */
+	ap->in_lbn = metalbn;
+	ap->in_off = off = UFS_NIADDR - i;
+	ap++;
+	for (++numlevels; i <= UFS_NIADDR; i++) {
+		/* If searching for a meta-data block, quit when found. */
+		if (metalbn == realbn)
+			break;
+
+		lbc -= lognindir;
+		blockcnt = (int64_t)1 << lbc;
+		off = (bn >> lbc) & (NINDIR(ip->i_fs) - 1);
+
+		++numlevels;
+		ap->in_lbn = metalbn;
+		ap->in_off = off;
+		++ap;
+
+		metalbn -= -1 + (off << lbc);
+	}
+	if (nump)
+		*nump = numlevels;
+	return (0);
+}
diff --git a/usr.sbin/makefs/ffs/ufs_bswap.h b/usr.sbin/makefs/ffs/ufs_bswap.h
new file mode 100644
index 0000000000..468cfe2540
--- /dev/null
+++ b/usr.sbin/makefs/ffs/ufs_bswap.h
@@ -0,0 +1,83 @@
+/*	$NetBSD: ufs_bswap.h,v 1.13 2003/10/05 17:48:50 bouyer Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-NetBSD
+ *
+ * Copyright (c) 1998 Manuel Bouyer.
+ *
+ * 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 ``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 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.
+ *
+ * $FreeBSD: head/usr.sbin/makefs/ffs/ufs_bswap.h 326276 2017-11-27 15:37:16Z pfg $
+ */
+
+#ifndef _UFS_UFS_BSWAP_H_
+#define _UFS_UFS_BSWAP_H_
+
+#if defined(_KERNEL_OPT)
+#include "opt_ffs.h"
+#endif
+
+#include <sys/endian.h>
+
+#include "makefs.h"
+
+/* Macros to access UFS flags */
+#ifdef FFS_EI
+#define	UFS_MPNEEDSWAP(mp)	(VFSTOUFS(mp)->um_flags & UFS_NEEDSWAP)
+#define UFS_FSNEEDSWAP(fs)	((fs)->fs_flags & FS_SWAPPED)
+#define	UFS_IPNEEDSWAP(ip)	UFS_MPNEEDSWAP(ITOV(ip)->v_mount)
+#else
+#define	UFS_MPNEEDSWAP(mp) (0)
+#define UFS_FSNEEDSWAP(fs) (0)
+#define	UFS_IPNEEDSWAP(ip) (0)
+#endif
+
+#if !defined(_KERNEL) || defined(FFS_EI)
+/* inlines for access to swapped data */
+static __inline u_int16_t
+ufs_rw16(u_int16_t a, int ns)
+{
+	return ((ns) ?  bswap16(a) : (a));
+}
+static __inline u_int32_t
+ufs_rw32(u_int32_t a, int ns)
+{
+	return ((ns) ?  bswap32(a) : (a));
+}
+static __inline u_int64_t
+ufs_rw64(u_int64_t a, int ns)
+{
+	return ((ns) ?  bswap64(a) : (a));
+}
+#else
+#define ufs_rw16(a, ns) ((uint16_t)(a))
+#define ufs_rw32(a, ns) ((uint32_t)(a))
+#define ufs_rw64(a, ns) ((uint64_t)(a))
+#endif
+
+#define ufs_add16(a, b, ns) \
+	(a) = ufs_rw16(ufs_rw16((a), (ns)) + (b), (ns))
+#define ufs_add32(a, b, ns) \
+	(a) = ufs_rw32(ufs_rw32((a), (ns)) + (b), (ns))
+#define ufs_add64(a, b, ns) \
+	(a) = ufs_rw64(ufs_rw64((a), (ns)) + (b), (ns))
+
+#endif /* !_UFS_UFS_BSWAP_H_ */
diff --git a/usr.sbin/makefs/ffs/ufs_inode.h b/usr.sbin/makefs/ffs/ufs_inode.h
new file mode 100644
index 0000000000..8f561a7c7f
--- /dev/null
+++ b/usr.sbin/makefs/ffs/ufs_inode.h
@@ -0,0 +1,99 @@
+/*	$NetBSD: ufs_inode.h,v 1.3 2003/08/07 11:25:34 agc Exp $	*/
+/* From:  NetBSD: inode.h,v 1.27 2001/12/18 10:57:23 fvdl Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1982, 1989, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * (c) UNIX System Laboratories, Inc.
+ * All or some portions of this file are derived from material licensed
+ * to the University of California by American Telephone and Telegraph
+ * Co. or Unix System Laboratories, Inc. and are reproduced herein with
+ * the permission of UNIX System Laboratories, Inc.
+ *
+ * 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.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
+ *
+ *	@(#)inode.h	8.9 (Berkeley) 5/14/95
+ * $FreeBSD: head/usr.sbin/makefs/ffs/ufs_inode.h 326025 2017-11-20 19:49:47Z pfg $
+ */
+
+union dinode {
+	struct ufs1_dinode ffs1_din;
+	struct ufs2_dinode ffs2_din;
+};
+
+struct inode {
+	ino_t	  	i_number;	/* The identity of the inode. */
+	struct vnode	*i_devvp;	/* vnode pointer (contains fsopts) */
+	struct fs	*i_fs;		/* File system */
+	union dinode	i_din;
+	uint64_t	i_size;
+};
+
+#define	i_ffs1_atime		i_din.ffs1_din.di_atime
+#define	i_ffs1_atimensec	i_din.ffs1_din.di_atimensec
+#define	i_ffs1_blocks		i_din.ffs1_din.di_blocks
+#define	i_ffs1_ctime		i_din.ffs1_din.di_ctime
+#define	i_ffs1_ctimensec	i_din.ffs1_din.di_ctimensec
+#define	i_ffs1_db		i_din.ffs1_din.di_db
+#define	i_ffs1_flags		i_din.ffs1_din.di_flags
+#define	i_ffs1_gen		i_din.ffs1_din.di_gen
+#define	i_ffs11_gid		i_din.ffs1_din.di_gid
+#define	i_ffs1_ib		i_din.ffs1_din.di_ib
+#define	i_ffs1_mode		i_din.ffs1_din.di_mode
+#define	i_ffs1_mtime		i_din.ffs1_din.di_mtime
+#define	i_ffs1_mtimensec	i_din.ffs1_din.di_mtimensec
+#define	i_ffs1_nlink		i_din.ffs1_din.di_nlink
+#define	i_ffs1_rdev		i_din.ffs1_din.di_rdev
+#define	i_ffs1_shortlink	i_din.ffs1_din.db
+#define	i_ffs1_size		i_din.ffs1_din.di_size
+#define	i_ffs1_uid		i_din.ffs1_din.di_uid
+
+#define	i_ffs2_atime		i_din.ffs2_din.di_atime
+#define	i_ffs2_atimensec	i_din.ffs2_din.di_atimensec
+#define	i_ffs2_blocks		i_din.ffs2_din.di_blocks
+#define	i_ffs2_ctime		i_din.ffs2_din.di_ctime
+#define	i_ffs2_ctimensec	i_din.ffs2_din.di_ctimensec
+#define	i_ffs2_birthtime	i_din.ffs2_din.di_birthtime
+#define	i_ffs2_birthnsec	i_din.ffs2_din.di_birthnsec
+#define	i_ffs2_db		i_din.ffs2_din.di_db
+#define	i_ffs2_flags		i_din.ffs2_din.di_flags
+#define	i_ffs2_gen		i_din.ffs2_din.di_gen
+#define	i_ffs21_gid		i_din.ffs2_din.di_gid
+#define	i_ffs2_ib		i_din.ffs2_din.di_ib
+#define	i_ffs2_mode		i_din.ffs2_din.di_mode
+#define	i_ffs2_mtime		i_din.ffs2_din.di_mtime
+#define	i_ffs2_mtimensec	i_din.ffs2_din.di_mtimensec
+#define	i_ffs2_nlink		i_din.ffs2_din.di_nlink
+#define	i_ffs2_rdev		i_din.ffs2_din.di_rdev
+#define	i_ffs2_shortlink	i_din.ffs2_din.db
+#define	i_ffs2_size		i_din.ffs2_din.di_size
+#define	i_ffs2_uid		i_din.ffs2_din.di_uid
+
+#undef DIP
+#define DIP(ip, field) \
+        (((ip)->i_fs->fs_magic == FS_UFS1_MAGIC) ? \
+        (ip)->i_ffs1_##field : (ip)->i_ffs2_##field)
diff --git a/usr.sbin/makefs/makefs.8 b/usr.sbin/makefs/makefs.8
new file mode 100644
index 0000000000..e08327ac14
--- /dev/null
+++ b/usr.sbin/makefs/makefs.8
@@ -0,0 +1,428 @@
+.\"	$NetBSD: makefs.8,v 1.33 2011/05/22 21:51:39 christos Exp $
+.\"
+.\" Copyright (c) 2001-2003 Wasabi Systems, Inc.
+.\" All rights reserved.
+.\"
+.\" Written by Luke Mewburn for Wasabi Systems, Inc.
+.\"
+.\" 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.
+.\" 3. All advertising materials mentioning features or use of this software
+.\"    must display the following acknowledgement:
+.\"      This product includes software developed for the NetBSD Project by
+.\"      Wasabi Systems, Inc.
+.\" 4. The name of Wasabi Systems, Inc. may not be used to endorse
+.\"    or promote products derived from this software without specific prior
+.\"    written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``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 WASABI SYSTEMS, INC
+.\" 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.
+.\"
+.\" $FreeBSD: head/usr.sbin/makefs/makefs.8 331843 2018-03-31 15:04:41Z benno $
+.\"
+.Dd March 30, 2018
+.Dt MAKEFS 8
+.Os
+.Sh NAME
+.Nm makefs
+.Nd create a file system image from a directory tree or a mtree manifest
+.Sh SYNOPSIS
+.Nm
+.Op Fl DxZ
+.Op Fl B Ar endian
+.Op Fl b Ar free-blocks
+.Op Fl d Ar debug-mask
+.Op Fl F Ar mtree-specfile
+.Op Fl f Ar free-files
+.Op Fl M Ar minimum-size
+.Op Fl m Ar maximum-size
+.Op Fl N Ar userdb-dir
+.Op Fl O Ar offset
+.Op Fl o Ar fs-options
+.Op Fl R Ar roundup-size
+.Op Fl S Ar sector-size
+.Op Fl s Ar image-size
+.Op Fl T Ar timestamp
+.Op Fl t Ar fs-type
+.Ar image-file
+.Ar directory | manifest
+.Op Ar extra-directory ...
+.Sh DESCRIPTION
+The utility
+.Nm
+creates a file system image into
+.Ar image-file
+from the directory tree
+.Ar directory
+or from the mtree manifest
+.Ar manifest .
+If any optional directory trees are passed in the
+.Ar extra-directory
+arguments, then the directory tree of each argument will be merged
+into the
+.Ar directory
+or
+.Ar manifest
+first before creating
+.Ar image-file .
+No special devices or privileges are required to perform this task.
+.Pp
+The options are as follows:
+.Bl -tag -width flag
+.It Fl B Ar endian
+Set the byte order of the image to
+.Ar endian .
+Valid byte orders are
+.Ql 4321 ,
+.Ql big ,
+or
+.Ql be
+for big endian, and
+.Ql 1234 ,
+.Ql little ,
+or
+.Ql le
+for little endian.
+Some file systems may have a fixed byte order; in those cases this
+argument will be ignored.
+.It Fl b Ar free-blocks
+Ensure that a minimum of
+.Ar free-blocks
+free blocks exist in the image.
+An optional
+.Ql %
+suffix may be provided to indicate that
+.Ar free-blocks
+indicates a percentage of the calculated image size.
+.It Fl D
+Treat duplicate paths in an mtree manifest as warnings not error.
+.It Fl d Ar debug-mask
+Enable various levels of debugging, depending upon which bits are
+set in
+.Ar debug-mask .
+XXX: document these
+.It Fl F Ar mtree-specfile
+.Em This is almost certainly not the option you are looking for.
+To create an image from a list of files in an mtree format manifest,
+specify it as the last argument on the commandline, not as a the
+argument to
+.Fl F .
+.Pp
+Use
+.Ar mtree-specfile
+as an
+.Xr mtree 8
+.Sq specfile
+specification.
+This option has no effect when the image is created from a mtree manifest
+rather than a directory.
+.Pp
+If a specfile entry exists in the underlying file system, its
+permissions and modification time will be used unless specifically
+overridden by the specfile.
+An error will be raised if the type of entry in the specfile
+conflicts with that of an existing entry.
+.Pp
+In the opposite case (where a specfile entry does not have an entry
+in the underlying file system) the following occurs:
+If the specfile entry is marked
+.Sy optional ,
+the specfile entry is ignored.
+Otherwise, the entry will be created in the image, and it is
+necessary to specify at least the following parameters in the
+specfile:
+.Sy type ,
+.Sy mode ,
+.Sy gname ,
+or
+.Sy gid ,
+and
+.Sy uname
+or
+.Sy uid ,
+and
+.Sy link
+(in the case of symbolic links).
+If
+.Sy time
+isn't provided, the current time will be used.
+If
+.Sy flags
+isn't provided, the current file flags will be used.
+Missing regular file entries will be created as zero-length files.
+.It Fl f Ar free-files
+Ensure that a minimum of
+.Ar free-files
+free files (inodes) exist in the image.
+An optional
+.Ql %
+suffix may be provided to indicate that
+.Ar free-files
+indicates a percentage of the calculated image size.
+.It Fl M Ar minimum-size
+Set the minimum size of the file system image to
+.Ar minimum-size .
+.It Fl m Ar maximum-size
+Set the maximum size of the file system image to
+.Ar maximum-size .
+An error will be raised if the target file system needs to be larger
+than this to accommodate the provided directory tree.
+.It Fl N Ar userdb-dir
+Use the user database text file
+.Pa master.passwd
+and group database text file
+.Pa group
+from
+.Ar userdb-dir ,
+rather than using the results from the system's
+.Xr getpwnam 3
+and
+.Xr getgrnam 3
+(and related) library calls.
+.It Fl O Ar offset
+Instead of creating the filesystem at the beginning of the file, start
+at offset.
+Valid only for
+.Sy ffs .
+.It Fl o Ar fs-options
+Set file system specific options.
+.Ar fs-options
+is a comma separated list of options.
+Valid file system specific options are detailed below.
+.It Fl p
+Deprecated.
+See the
+.Fl Z
+flag.
+.It Fl R Ar roundup-size
+Round the image up to
+.Ar roundup-size .
+.Ar roundup-size
+should be a multiple of the file system block size.
+This option only applies to the
+.Sy ffs
+file system type.
+.It Fl S Ar sector-size
+Set the file system sector size to
+.Ar sector-size .
+.\" XXX: next line also true for cd9660?
+Defaults to 512.
+.It Fl s Ar image-size
+Set the size of the file system image to
+.Ar image-size .
+.It Fl T Ar timestamp
+Specify a timestamp to be set for all filesystem files and directories
+created so that repeatable builds are possible.
+The
+.Ar timestamp
+can be a
+.Pa pathname ,
+where the timestamps are derived from that file, or an integer
+value interpreted as the number of seconds from the Epoch.
+Note that timestamps specified in an
+.Xr mtree 5
+spec file, override the default timestamp.
+.It Fl t Ar fs-type
+Create an
+.Ar fs-type
+file system image.
+The following file system types are supported:
+.Bl -tag -width cd9660 -offset indent
+.It Sy ffs
+BSD fast file system (default).
+.It Sy cd9660
+ISO 9660 file system.
+.El
+.It Fl x
+Exclude file system nodes not explicitly listed in the specfile.
+.It Fl Z
+Create a sparse file for
+.Sy ffs .
+This is useful for virtual machine images.
+.El
+.Pp
+Where sizes are specified, a decimal number of bytes is expected.
+Two or more numbers may be separated by an
+.Dq x
+to indicate a product.
+Each number may have one of the following optional suffixes:
+.Bl -tag -width 3n -offset indent -compact
+.It b
+Block; multiply by 512
+.It k
+Kibi; multiply by 1024 (1 KiB)
+.It m
+Mebi; multiply by 1048576 (1 MiB)
+.It g
+Gibi; multiply by 1073741824 (1 GiB)
+.It t
+Tebi; multiply by 1099511627776 (1 TiB)
+.It w
+Word; multiply by the number of bytes in an integer
+.El
+.\"
+.\"
+.Ss FFS-specific options
+.Sy ffs
+images have ffs-specific optional parameters that may be provided.
+Each of the options consists of a keyword, an equal sign
+.Pq Ql = ,
+and a value.
+The following keywords are supported:
+.Pp
+.Bl -tag -width optimization -offset indent -compact
+.It Sy avgfilesize
+Expected average file size.
+.It Sy avgfpdir
+Expected number of files per directory.
+.It Sy bsize
+Block size.
+.It Sy density
+Bytes per inode.
+.It Sy fsize
+Fragment size.
+.It Sy label
+Label name of the image.
+.It Sy maxbpg
+Maximum blocks per file in a cylinder group.
+.It Sy minfree
+Minimum % free.
+.It Sy optimization
+Optimization preference; one of
+.Ql space
+or
+.Ql time .
+.It Sy extent
+Maximum extent size.
+.It Sy maxbpcg
+Maximum total number of blocks in a cylinder group.
+.It Sy version
+UFS version.
+1 for FFS (default), 2 for UFS2.
+.It Sy softupdates
+0 for disable (default), 1 for enable
+.El
+.Ss CD9660-specific options
+.Sy cd9660
+images have ISO9660-specific optional parameters that may be
+provided.
+The arguments consist of a keyword and, optionally, an equal sign
+.Pq Ql = ,
+and a value.
+The following keywords are supported:
+.Pp
+.Bl -tag -width omit-trailing-period -offset indent -compact
+.It Sy allow-deep-trees
+Allow the directory structure to exceed the maximum specified in
+the spec.
+.It Sy allow-illegal-chars
+Allow illegal characters in filenames. This option is not implemented.
+.It Sy allow-lowercase
+Allow lowercase characters in filenames. This option is not implemented.
+.It Sy allow-max-name
+Allow 37 instead of 33 characters for filenames by omitting the
+version id.
+.It Sy allow-multidot
+Allow multiple dots in a filename.
+.It Sy applicationid
+Application ID of the image.
+.It Sy archimedes
+Use the
+.Ql ARCHIMEDES
+extension to encode
+.Tn RISC OS
+metadata.
+.It Sy bootimagedir
+Boot image directory. This option is not implemented.
+.It Sy chrp-boot
+Write an MBR partition table to the image to allow older CHRP hardware to
+boot.
+.It Sy boot-load-segment
+Set load segment for the boot image.
+.It Sy bootimage
+Filename of a boot image in the format
+.Dq sysid;filename ,
+where
+.Dq sysid
+is one of
+.Ql i386 ,
+.Ql mac68k ,
+.Ql macppc ,
+or
+.Ql powerpc .
+.It Sy generic-bootimage
+Load a generic boot image into the first 32K of the cd9660 image.
+.It Sy hard-disk-boot
+Boot image is a hard disk image.
+.It Sy isolevel
+An integer representing the ISO 9660 interchange level where
+.Dq level
+is either
+.Ql 1
+or
+.Ql 2 .
+.Dq level
+.Ql 3
+is not implemented.
+.It Sy keep-bad-images
+Do not discard images whose write was aborted due to an error.
+For debugging purposes.
+.It Sy label
+Label name of the image.
+.It Sy no-boot
+Boot image is not bootable.
+.It Sy no-emul-boot
+Boot image is a
+.Dq no emulation
+ElTorito image.
+.It Sy no-trailing-padding
+Do not pad the image (apparently Linux needs the padding).
+.It Sy omit-trailing-period
+Omit trailing periods in filenames.
+.It Sy platformid
+Set platform ID of section header entry of the boot image.
+.It Sy preparer
+Preparer ID of the image.
+.It Sy publisher
+Publisher ID of the image.
+.It Sy rockridge
+Use RockRidge extensions (for longer filenames, etc.).
+.It Sy verbose
+Turns on verbose output.
+.It Sy volumeid
+Volume set identifier of the image.
+.El
+.Sh SEE ALSO
+.Xr mtree 5 ,
+.Xr mtree 8 ,
+.Xr newfs 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Nx 1.6 .
+.Sh AUTHORS
+.An Luke Mewburn
+.Aq Mt lukem@NetBSD.org
+(original program),
+.An Daniel Watt ,
+.An Walter Deignan ,
+.An Ryan Gabrys ,
+.An Alan Perez-Rathke ,
+.An Ram Vedam
+(cd9660 support)
diff --git a/usr.sbin/makefs/makefs.c b/usr.sbin/makefs/makefs.c
new file mode 100644
index 0000000000..c0ca920b38
--- /dev/null
+++ b/usr.sbin/makefs/makefs.c
@@ -0,0 +1,506 @@
+/*	$NetBSD: makefs.c,v 1.26 2006/10/22 21:11:56 christos Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * Copyright (c) 2001-2003 Wasabi Systems, Inc.
+ * All rights reserved.
+ *
+ * Written by Luke Mewburn for Wasabi Systems, Inc.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software developed for the NetBSD Project by
+ *      Wasabi Systems, Inc.
+ * 4. The name of Wasabi Systems, Inc. may not be used to endorse
+ *    or promote products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``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 WASABI SYSTEMS, INC
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/makefs.c 326276 2017-11-27 15:37:16Z pfg $");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <util.h>
+
+#include "makefs.h"
+#include "mtree.h"
+
+/*
+ * list of supported file systems and dispatch functions
+ */
+typedef struct {
+	const char	*type;
+	void		(*prepare_options)(fsinfo_t *);
+	int		(*parse_options)(const char *, fsinfo_t *);
+	void		(*cleanup_options)(fsinfo_t *);
+	void		(*make_fs)(const char *, const char *, fsnode *,
+				fsinfo_t *);
+} fstype_t;
+
+static fstype_t fstypes[] = {
+#define ENTRY(name) { \
+	# name, name ## _prep_opts, name ## _parse_opts, \
+	name ## _cleanup_opts, name ## _makefs  \
+}
+	ENTRY(ffs),
+	ENTRY(cd9660),
+	{ .type = NULL	},
+};
+
+u_int		debug;
+int		dupsok;
+struct timespec	start_time;
+struct stat stampst;
+
+static	fstype_t *get_fstype(const char *);
+static int get_tstamp(const char *, struct stat *);
+static	void	usage(fstype_t *, fsinfo_t *);
+
+int
+main(int argc, char *argv[])
+{
+	struct stat	 sb;
+	struct timeval	 start;
+	fstype_t	*fstype;
+	fsinfo_t	 fsoptions;
+	fsnode		*root;
+	int	 	 ch, i, len;
+	const char	*subtree;
+	const char	*specfile;
+
+	setprogname(argv[0]);
+
+	debug = 0;
+	if ((fstype = get_fstype(DEFAULT_FSTYPE)) == NULL)
+		errx(1, "Unknown default fs type `%s'.", DEFAULT_FSTYPE);
+
+		/* set default fsoptions */
+	(void)memset(&fsoptions, 0, sizeof(fsoptions));
+	fsoptions.fd = -1;
+	fsoptions.sectorsize = -1;
+
+	if (fstype->prepare_options)
+		fstype->prepare_options(&fsoptions);
+
+	specfile = NULL;
+#ifdef CLOCK_REALTIME
+	ch = clock_gettime(CLOCK_REALTIME, &start_time);
+#else
+	ch = gettimeofday(&start, NULL);
+	start_time.tv_sec = start.tv_sec;
+	start_time.tv_nsec = start.tv_usec * 1000;
+#endif
+	if (ch == -1)
+		err(1, "Unable to get system time");
+
+
+	while ((ch = getopt(argc, argv, "B:b:Dd:f:F:M:m:N:O:o:pR:s:S:t:T:xZ")) != -1) {
+		switch (ch) {
+
+		case 'B':
+			if (strcmp(optarg, "be") == 0 ||
+			    strcmp(optarg, "4321") == 0 ||
+			    strcmp(optarg, "big") == 0) {
+#if BYTE_ORDER == LITTLE_ENDIAN
+				fsoptions.needswap = 1;
+#endif
+			} else if (strcmp(optarg, "le") == 0 ||
+			    strcmp(optarg, "1234") == 0 ||
+			    strcmp(optarg, "little") == 0) {
+#if BYTE_ORDER == BIG_ENDIAN
+				fsoptions.needswap = 1;
+#endif
+			} else {
+				warnx("Invalid endian `%s'.", optarg);
+				usage(fstype, &fsoptions);
+			}
+			break;
+
+		case 'b':
+			len = strlen(optarg) - 1;
+			if (optarg[len] == '%') {
+				optarg[len] = '\0';
+				fsoptions.freeblockpc =
+				    strsuftoll("free block percentage",
+					optarg, 0, 99);
+			} else {
+				fsoptions.freeblocks =
+				    strsuftoll("free blocks",
+					optarg, 0, LLONG_MAX);
+			}
+			break;
+
+		case 'D':
+			dupsok = 1;
+			break;
+
+		case 'd':
+			debug = strtoll(optarg, NULL, 0);
+			break;
+
+		case 'f':
+			len = strlen(optarg) - 1;
+			if (optarg[len] == '%') {
+				optarg[len] = '\0';
+				fsoptions.freefilepc =
+				    strsuftoll("free file percentage",
+					optarg, 0, 99);
+			} else {
+				fsoptions.freefiles =
+				    strsuftoll("free files",
+					optarg, 0, LLONG_MAX);
+			}
+			break;
+
+		case 'F':
+			specfile = optarg;
+			break;
+
+		case 'M':
+			fsoptions.minsize =
+			    strsuftoll("minimum size", optarg, 1LL, LLONG_MAX);
+			break;
+
+		case 'N':
+			if (! setup_getid(optarg))
+				errx(1,
+			    "Unable to use user and group databases in `%s'",
+				    optarg);
+			break;
+
+		case 'm':
+			fsoptions.maxsize =
+			    strsuftoll("maximum size", optarg, 1LL, LLONG_MAX);
+			break;
+
+		case 'O':
+			fsoptions.offset =
+			    strsuftoll("offset", optarg, 0LL, LLONG_MAX);
+			break;
+
+		case 'o':
+		{
+			char *p;
+
+			while ((p = strsep(&optarg, ",")) != NULL) {
+				if (*p == '\0')
+					errx(1, "Empty option");
+				if (! fstype->parse_options(p, &fsoptions))
+					usage(fstype, &fsoptions);
+			}
+			break;
+		}
+		case 'p':
+			/* Deprecated in favor of 'Z' */
+			fsoptions.sparse = 1;
+			break;
+
+		case 'R':
+			/* Round image size up to specified block size */
+			fsoptions.roundup =
+			    strsuftoll("roundup-size", optarg, 0, LLONG_MAX);
+			break;
+
+		case 's':
+			fsoptions.minsize = fsoptions.maxsize =
+			    strsuftoll("size", optarg, 1LL, LLONG_MAX);
+			break;
+
+		case 'S':
+			fsoptions.sectorsize =
+			    (int)strsuftoll("sector size", optarg,
+				1LL, INT_MAX);
+			break;
+
+		case 't':
+			/* Check current one and cleanup if necessary. */
+			if (fstype->cleanup_options)
+				fstype->cleanup_options(&fsoptions);
+			fsoptions.fs_specific = NULL;
+			if ((fstype = get_fstype(optarg)) == NULL)
+				errx(1, "Unknown fs type `%s'.", optarg);
+			fstype->prepare_options(&fsoptions);
+			break;
+
+		case 'T':
+			if (get_tstamp(optarg, &stampst) == -1)
+				errx(1, "Cannot get timestamp from `%s'",
+				    optarg);
+			break;
+
+		case 'x':
+			fsoptions.onlyspec = 1;
+			break;
+
+		case 'Z':
+			/* Superscedes 'p' for compatibility with NetBSD makefs(8) */
+			fsoptions.sparse = 1;
+			break;
+
+		case '?':
+		default:
+			usage(fstype, &fsoptions);
+			/* NOTREACHED */
+
+		}
+	}
+	if (debug) {
+		printf("debug mask: 0x%08x\n", debug);
+		printf("start time: %ld.%ld, %s",
+		    (long)start_time.tv_sec, (long)start_time.tv_nsec,
+		    ctime(&start_time.tv_sec));
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 2)
+		usage(fstype, &fsoptions);
+
+	/* -x must be accompanied by -F */
+	if (fsoptions.onlyspec != 0 && specfile == NULL)
+		errx(1, "-x requires -F mtree-specfile.");
+
+	/* Accept '-' as meaning "read from standard input". */
+	if (strcmp(argv[1], "-") == 0)
+		sb.st_mode = S_IFREG;
+	else {
+		if (stat(argv[1], &sb) == -1)
+			err(1, "Can't stat `%s'", argv[1]);
+	}
+
+	switch (sb.st_mode & S_IFMT) {
+	case S_IFDIR:		/* walk the tree */
+		subtree = argv[1];
+		TIMER_START(start);
+		root = walk_dir(subtree, ".", NULL, NULL);
+		TIMER_RESULTS(start, "walk_dir");
+		break;
+	case S_IFREG:		/* read the manifest file */
+		subtree = ".";
+		TIMER_START(start);
+		root = read_mtree(argv[1], NULL);
+		TIMER_RESULTS(start, "manifest");
+		break;
+	default:
+		errx(1, "%s: not a file or directory", argv[1]);
+		/* NOTREACHED */
+	}
+
+	/* append extra directory */
+	for (i = 2; i < argc; i++) {
+		if (stat(argv[i], &sb) == -1)
+			err(1, "Can't stat `%s'", argv[i]);
+		if (!S_ISDIR(sb.st_mode))
+			errx(1, "%s: not a directory", argv[i]);
+		TIMER_START(start);
+		root = walk_dir(argv[i], ".", NULL, root);
+		TIMER_RESULTS(start, "walk_dir2");
+	}
+
+	if (specfile) {		/* apply a specfile */
+		TIMER_START(start);
+		apply_specfile(specfile, subtree, root, fsoptions.onlyspec);
+		TIMER_RESULTS(start, "apply_specfile");
+	}
+
+	if (debug & DEBUG_DUMP_FSNODES) {
+		printf("\nparent: %s\n", subtree);
+		dump_fsnodes(root);
+		putchar('\n');
+	}
+
+				/* build the file system */
+	TIMER_START(start);
+	fstype->make_fs(argv[0], subtree, root, &fsoptions);
+	TIMER_RESULTS(start, "make_fs");
+
+	free_fsnodes(root);
+
+	exit(0);
+	/* NOTREACHED */
+}
+
+int
+set_option(const option_t *options, const char *option, char *buf, size_t len)
+{
+	char *var, *val;
+	int retval;
+
+	assert(option != NULL);
+
+	var = estrdup(option);
+	for (val = var; *val; val++)
+		if (*val == '=') {
+			*val++ = '\0';
+			break;
+		}
+	retval = set_option_var(options, var, val, buf, len);
+	free(var);
+	return retval;
+}
+
+int
+set_option_var(const option_t *options, const char *var, const char *val,
+    char *buf, size_t len)
+{
+	char *s;
+	size_t i;
+
+#define NUM(type) \
+	if (!*val) { \
+		*(type *)options[i].value = 1; \
+		break; \
+	} \
+	*(type *)options[i].value = (type)strsuftoll(options[i].desc, val, \
+	    options[i].minimum, options[i].maximum); break
+
+	for (i = 0; options[i].name != NULL; i++) {
+		if (var[1] == '\0') {
+			if (options[i].letter != var[0])
+				continue;
+		} else if (strcmp(options[i].name, var) != 0)
+			continue;
+		switch (options[i].type) {
+		case OPT_BOOL:
+			*(bool *)options[i].value = 1;
+			break;
+		case OPT_STRARRAY:
+			strlcpy((void *)options[i].value, val, (size_t)
+			    options[i].maximum);
+			break;
+		case OPT_STRPTR:
+			s = estrdup(val);
+			*(char **)options[i].value = s;
+			break;
+		case OPT_STRBUF:
+			if (buf == NULL)
+				abort();
+			strlcpy(buf, val, len);
+			break;
+		case OPT_INT64:
+			NUM(uint64_t);
+		case OPT_INT32:
+			NUM(uint32_t);
+		case OPT_INT16:
+			NUM(uint16_t);
+		case OPT_INT8:
+			NUM(uint8_t);
+		default:
+			warnx("Unknown type %d in option %s", options[i].type,
+			    val);
+			return 0;
+		}
+		return i;
+	}
+	warnx("Unknown option `%s'", var);
+	return -1;
+}
+
+
+static fstype_t *
+get_fstype(const char *type)
+{
+	int i;
+	
+	for (i = 0; fstypes[i].type != NULL; i++)
+		if (strcmp(fstypes[i].type, type) == 0)
+			return (&fstypes[i]);
+	return (NULL);
+}
+
+option_t *
+copy_opts(const option_t *o)
+{
+	size_t i;
+
+	for (i = 0; o[i].name; i++)
+		continue;
+	i++;
+	return memcpy(ecalloc(i, sizeof(*o)), o, i * sizeof(*o));
+}
+
+static int
+get_tstamp(const char *b, struct stat *st)
+{
+	time_t when;
+	char *eb;
+	long long l;
+
+	if (stat(b, st) != -1)
+		return 0;
+
+	{
+		errno = 0;
+		l = strtoll(b, &eb, 0);
+		if (b == eb || *eb || errno)
+			return -1;
+		when = (time_t)l;
+	}
+
+	st->st_ino = 1;
+#ifdef HAVE_STRUCT_STAT_BIRTHTIME
+	st->st_birthtime =
+#endif
+	st->st_mtime = st->st_ctime = st->st_atime = when;
+	return 0;
+}
+
+static void
+usage(fstype_t *fstype, fsinfo_t *fsoptions)
+{
+	const char *prog;
+
+	prog = getprogname();
+	fprintf(stderr,
+"Usage: %s [-xZ] [-B endian] [-b free-blocks] [-d debug-mask]\n"
+"\t[-F mtree-specfile] [-f free-files] [-M minimum-size] [-m maximum-size]\n"
+"\t[-N userdb-dir] [-O offset] [-o fs-options] [-R roundup-size]\n"
+"\t[-S sector-size] [-s image-size] [-T <timestamp/file>] [-t fs-type]\n"
+"\timage-file directory | manifest [extra-directory ...]\n",
+	    prog);
+
+	if (fstype) {
+		size_t i;
+		option_t *o = fsoptions->fs_options;
+
+		fprintf(stderr, "\n%s specific options:\n", fstype->type);
+		for (i = 0; o[i].name != NULL; i++)
+			fprintf(stderr, "\t%c%c%20.20s\t%s\n",
+			    o[i].letter ? o[i].letter : ' ',
+			    o[i].letter ? ',' : ' ',
+			    o[i].name, o[i].desc);
+	}
+	exit(1);
+}
diff --git a/usr.sbin/makefs/makefs.h b/usr.sbin/makefs/makefs.h
new file mode 100644
index 0000000000..420a0629b4
--- /dev/null
+++ b/usr.sbin/makefs/makefs.h
@@ -0,0 +1,304 @@
+/*	$NetBSD: makefs.h,v 1.20 2008/12/28 21:51:46 christos Exp $	*/
+
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * Copyright (c) 2001 Wasabi Systems, Inc.
+ * All rights reserved.
+ *
+ * Written by Luke Mewburn for Wasabi Systems, Inc.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software developed for the NetBSD Project by
+ *      Wasabi Systems, Inc.
+ * 4. The name of Wasabi Systems, Inc. may not be used to endorse
+ *    or promote products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``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 WASABI SYSTEMS, INC
+ * 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.
+ *
+ * $FreeBSD: head/usr.sbin/makefs/makefs.h 326276 2017-11-27 15:37:16Z pfg $
+ */
+
+#ifndef	_MAKEFS_H
+#define	_MAKEFS_H
+
+#include <sys/stat.h>
+#include <err.h>
+
+/*
+ * fsnode -
+ *	a component of the tree; contains a filename, a pointer to
+ *	fsinode, optional symlink name, and tree pointers
+ *
+ * fsinode - 
+ *	equivalent to an inode, containing target file system inode number,
+ *	refcount (nlink), and stat buffer
+ *
+ * A tree of fsnodes looks like this:
+ *
+ *	name	"."		"bin"		"netbsd"
+ *	type	S_IFDIR		S_IFDIR		S_IFREG
+ *	next 	  >		  >		NULL
+ *	parent	NULL		NULL		NULL
+ *	child	NULL		  v
+ *
+ *	name			"."		"ls"
+ *	type			S_IFDIR		S_IFREG
+ *	next			  >		NULL
+ *	parent			  ^		^ (to "bin")
+ *	child			NULL		NULL
+ *
+ * Notes:
+ *	-   first always points to first entry, at current level, which
+ *	    must be "." when the tree has been built; during build it may
+ *	    not be if "." hasn't yet been found by readdir(2).
+ */
+
+enum fi_flags {
+	FI_SIZED =	1<<0,		/* inode sized */
+	FI_ALLOCATED =	1<<1,		/* fsinode->ino allocated */
+	FI_WRITTEN =	1<<2,		/* inode written */
+};
+
+typedef struct {
+	uint32_t	 ino;		/* inode number used on target fs */
+	uint32_t	 nlink;		/* number of links to this entry */
+	enum fi_flags	 flags;		/* flags used by fs specific code */
+	struct stat	 st;		/* stat entry */
+} fsinode;
+
+typedef struct _fsnode {
+	struct _fsnode	*parent;	/* parent (NULL if root) */
+	struct _fsnode	*child;		/* child (if type == S_IFDIR) */
+	struct _fsnode	*next;		/* next */
+	struct _fsnode	*first;		/* first node of current level (".") */
+	uint32_t	 type;		/* type of entry */
+	fsinode		*inode;		/* actual inode data */
+	char		*symlink;	/* symlink target */
+	char		*contents;	/* file to provide contents */
+	const char	*root;		/* root path */
+	char		*path;		/* directory name */
+	char		*name;		/* file name */
+	int		flags;		/* misc flags */
+} fsnode;
+
+#define	FSNODE_F_HASSPEC	0x01	/* fsnode has a spec entry */
+#define	FSNODE_F_OPTIONAL	0x02	/* fsnode is optional */
+
+/*
+ * option_t - contains option name, description, pointer to location to store
+ * result, and range checks for the result. Used to simplify fs specific
+ * option setting
+ */
+typedef enum {
+	OPT_STRARRAY,
+	OPT_STRPTR,
+	OPT_STRBUF,
+	OPT_BOOL,
+	OPT_INT8,
+	OPT_INT16,
+	OPT_INT32,
+	OPT_INT64
+} opttype_t;
+
+typedef struct {
+	char		letter;		/* option letter NUL for none */
+	const char	*name;		/* option name */
+	void		*value;		/* where to stuff the value */
+	opttype_t	type;		/* type of entry */
+	long long	minimum;	/* minimum for value */
+	long long	maximum;	/* maximum for value */
+	const char	*desc;		/* option description */
+} option_t;
+
+/*
+ * fsinfo_t - contains various settings and parameters pertaining to
+ * the image, including current settings, global options, and fs
+ * specific options
+ */
+typedef struct makefs_fsinfo {
+		/* current settings */
+	off_t	size;		/* total size */
+	off_t	inodes;		/* number of inodes */
+	uint32_t curinode;	/* current inode */
+
+		/* image settings */
+	int	fd;		/* file descriptor of image */
+	void	*superblock;	/* superblock */
+	int	onlyspec;	/* only add entries in specfile */
+
+
+		/* global options */
+	off_t	minsize;	/* minimum size image should be */
+	off_t	maxsize;	/* maximum size image can be */
+	off_t	freefiles;	/* free file entries to leave */
+	off_t	freeblocks;	/* free blocks to leave */
+	off_t	offset;		/* offset from start of file */
+	off_t	roundup;	/* round image size up to this value */
+	int	freefilepc;	/* free file % */
+	int	freeblockpc;	/* free block % */
+	int	needswap;	/* non-zero if byte swapping needed */
+	int	sectorsize;	/* sector size */
+	int	sparse;		/* sparse image, don't fill it with zeros */
+
+	void	*fs_specific;	/* File system specific additions. */
+	option_t *fs_options;	/* File system specific options */
+} fsinfo_t;
+
+
+void		apply_specfile(const char *, const char *, fsnode *, int);
+void		dump_fsnodes(fsnode *);
+const char *	inode_type(mode_t);
+fsnode *	read_mtree(const char *, fsnode *);
+int		set_option(const option_t *, const char *, char *, size_t);
+int		set_option_var(const option_t *, const char *, const char *,
+    char *, size_t);
+fsnode *	walk_dir(const char *, const char *, fsnode *, fsnode *);
+void		free_fsnodes(fsnode *);
+option_t *	copy_opts(const option_t *);
+
+#define DECLARE_FUN(fs)							\
+void		fs ## _prep_opts(fsinfo_t *);				\
+int		fs ## _parse_opts(const char *, fsinfo_t *);		\
+void		fs ## _cleanup_opts(fsinfo_t *);			\
+void		fs ## _makefs(const char *, const char *, fsnode *, fsinfo_t *)
+
+DECLARE_FUN(ffs);
+DECLARE_FUN(cd9660);
+
+extern	u_int		debug;
+extern	int		dupsok;
+extern	struct timespec	start_time;
+extern	struct stat stampst;
+
+/*
+ * If -x is specified, we want to exclude nodes which do not appear
+ * in the spec file.
+ */
+#define	FSNODE_EXCLUDE_P(opts, fsnode)	\
+	((opts)->onlyspec != 0 && ((fsnode)->flags & FSNODE_F_HASSPEC) == 0)
+
+#define	DEBUG_TIME			0x00000001
+		/* debug bits 1..3 unused at this time */
+#define	DEBUG_WALK_DIR			0x00000010
+#define	DEBUG_WALK_DIR_NODE		0x00000020
+#define	DEBUG_WALK_DIR_LINKCHECK	0x00000040
+#define	DEBUG_DUMP_FSNODES		0x00000080
+#define	DEBUG_DUMP_FSNODES_VERBOSE	0x00000100
+#define	DEBUG_FS_PARSE_OPTS		0x00000200
+#define	DEBUG_FS_MAKEFS			0x00000400
+#define	DEBUG_FS_VALIDATE		0x00000800
+#define	DEBUG_FS_CREATE_IMAGE		0x00001000
+#define	DEBUG_FS_SIZE_DIR		0x00002000
+#define	DEBUG_FS_SIZE_DIR_NODE		0x00004000
+#define	DEBUG_FS_SIZE_DIR_ADD_DIRENT	0x00008000
+#define	DEBUG_FS_POPULATE		0x00010000
+#define	DEBUG_FS_POPULATE_DIRBUF	0x00020000
+#define	DEBUG_FS_POPULATE_NODE		0x00040000
+#define	DEBUG_FS_WRITE_FILE		0x00080000
+#define	DEBUG_FS_WRITE_FILE_BLOCK	0x00100000
+#define	DEBUG_FS_MAKE_DIRBUF		0x00200000
+#define	DEBUG_FS_WRITE_INODE		0x00400000
+#define	DEBUG_BUF_BREAD			0x00800000
+#define	DEBUG_BUF_BWRITE		0x01000000
+#define	DEBUG_BUF_GETBLK		0x02000000
+#define	DEBUG_APPLY_SPECFILE		0x04000000
+#define	DEBUG_APPLY_SPECENTRY		0x08000000
+#define	DEBUG_APPLY_SPECONLY		0x10000000
+
+
+#define	TIMER_START(x)				\
+	if (debug & DEBUG_TIME)			\
+		gettimeofday(&(x), NULL)
+
+#define	TIMER_RESULTS(x,d)				\
+	if (debug & DEBUG_TIME) {			\
+		struct timeval end, td;			\
+		gettimeofday(&end, NULL);		\
+		timersub(&end, &(x), &td);		\
+		printf("%s took %lld.%06ld seconds\n",	\
+		    (d), (long long)td.tv_sec,		\
+		    (long)td.tv_usec);			\
+	}
+
+
+#ifndef	DEFAULT_FSTYPE
+#define	DEFAULT_FSTYPE	"ffs"
+#endif
+
+
+/*
+ *	ffs specific settings
+ *	---------------------
+ */
+
+#define	FFS_EI		/* for opposite endian support in ffs headers */
+
+/*
+ * Write-arounds/compat shims for endian-agnostic support.
+ * These belong in the kernel if/when it's possible to mount
+ * filesystems w/ either byte order.
+ */
+
+/*
+ * File system internal flags, also in fs_flags.
+ * (Pick highest number to avoid conflicts with others)
+ */
+#define        FS_SWAPPED      0x80000000      /* file system is endian swapped */
+#define        FS_INTERNAL     0x80000000      /* mask for internal flags */
+
+#define        FS_ISCLEAN      1
+
+#define        DINODE1_SIZE    (sizeof(struct ufs1_dinode))
+#define        DINODE2_SIZE    (sizeof(struct ufs2_dinode))
+
+#define UFS1_MAXSYMLINKLEN   ((UFS_NDADDR + UFS_NIADDR) * sizeof(ufs1_daddr_t))
+#define UFS2_MAXSYMLINKLEN   ((UFS_NDADDR + UFS_NIADDR) * sizeof(ufs2_daddr_t))
+
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+#define DIRSIZ_SWAP(oldfmt, dp, needswap)      \
+    (((oldfmt) && !(needswap)) ?       \
+    DIRECTSIZ((dp)->d_type) : DIRECTSIZ((dp)->d_namlen))
+#else
+#define DIRSIZ_SWAP(oldfmt, dp, needswap)      \
+    (((oldfmt) && (needswap)) ?                \
+    DIRECTSIZ((dp)->d_type) : DIRECTSIZ((dp)->d_namlen))
+#endif
+
+#define        cg_chkmagic_swap(cgp, ns) \
+    (ufs_rw32((cgp)->cg_magic, (ns)) == CG_MAGIC)
+#define        cg_inosused_swap(cgp, ns) \
+    ((u_int8_t *)((u_int8_t *)(cgp) + ufs_rw32((cgp)->cg_iusedoff, (ns))))
+#define        cg_blksfree_swap(cgp, ns) \
+    ((u_int8_t *)((u_int8_t *)(cgp) + ufs_rw32((cgp)->cg_freeoff, (ns))))
+#define        cg_clustersfree_swap(cgp, ns) \
+    ((u_int8_t *)((u_int8_t *)(cgp) + ufs_rw32((cgp)->cg_clusteroff, (ns))))
+#define        cg_clustersum_swap(cgp, ns) \
+    ((int32_t *)((uintptr_t)(cgp) + ufs_rw32((cgp)->cg_clustersumoff, ns)))
+
+struct fs;
+void   ffs_fragacct_swap(struct fs *, int, int32_t [], int, int);
+
+fsinode *link_check(fsinode *);
+
+#endif	/* _MAKEFS_H */
diff --git a/usr.sbin/makefs/mtree.c b/usr.sbin/makefs/mtree.c
new file mode 100644
index 0000000000..42ea2cabbf
--- /dev/null
+++ b/usr.sbin/makefs/mtree.c
@@ -0,0 +1,1112 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2011 Marcel Moolenaar
+ * 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.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/mtree.c 332986 2018-04-25 02:43:53Z pfg $");
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/sbuf.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <inttypes.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <unistd.h>
+#include <util.h>
+#include <vis.h>
+
+#include "makefs.h"
+
+#ifndef ENOATTR
+#define	ENOATTR	ENODATA
+#endif
+
+#define	IS_DOT(nm)	((nm)[0] == '.' && (nm)[1] == '\0')
+#define	IS_DOTDOT(nm)	((nm)[0] == '.' && (nm)[1] == '.' && (nm)[2] == '\0')
+
+struct mtree_fileinfo {
+	SLIST_ENTRY(mtree_fileinfo) next;
+	FILE *fp;
+	const char *name;
+	u_int line;
+};
+
+/* Global state used while parsing. */
+static SLIST_HEAD(, mtree_fileinfo) mtree_fileinfo =
+    SLIST_HEAD_INITIALIZER(mtree_fileinfo);
+static fsnode *mtree_root;
+static fsnode *mtree_current;
+static fsnode mtree_global;
+static fsinode mtree_global_inode;
+static u_int errors, warnings;
+
+static void mtree_error(const char *, ...) __printflike(1, 2);
+static void mtree_warning(const char *, ...) __printflike(1, 2);
+
+static int
+mtree_file_push(const char *name, FILE *fp)
+{
+	struct mtree_fileinfo *fi;
+
+	fi = emalloc(sizeof(*fi));
+	if (strcmp(name, "-") == 0)
+		fi->name = estrdup("(stdin)");
+	else
+		fi->name = estrdup(name);
+	if (fi->name == NULL) {
+		free(fi);
+		return (ENOMEM);
+	}
+
+	fi->fp = fp;
+	fi->line = 0;
+
+	SLIST_INSERT_HEAD(&mtree_fileinfo, fi, next);
+	return (0);
+}
+
+static void
+mtree_print(const char *msgtype, const char *fmt, va_list ap)
+{
+	struct mtree_fileinfo *fi;
+
+	if (msgtype != NULL) {
+		fi = SLIST_FIRST(&mtree_fileinfo);
+		if (fi != NULL)
+			fprintf(stderr, "%s:%u: ", fi->name, fi->line);
+		fprintf(stderr, "%s: ", msgtype);
+	}
+	vfprintf(stderr, fmt, ap);
+}
+
+static void
+mtree_error(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	mtree_print("error", fmt, ap);
+	va_end(ap);
+
+	errors++;
+	fputc('\n', stderr);
+}
+
+static void
+mtree_warning(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	mtree_print("warning", fmt, ap);
+	va_end(ap);
+
+	warnings++;
+	fputc('\n', stderr);
+}
+
+#ifndef MAKEFS_MAX_TREE_DEPTH
+# define MAKEFS_MAX_TREE_DEPTH (MAXPATHLEN/2)
+#endif
+
+/* construct path to node->name */
+static char *
+mtree_file_path(fsnode *node)
+{
+	fsnode *pnode;
+	struct sbuf *sb;
+	char *res, *rp[MAKEFS_MAX_TREE_DEPTH];
+	int depth;
+
+	depth = 0;
+	rp[depth] = node->name;
+	for (pnode = node->parent; pnode && depth < MAKEFS_MAX_TREE_DEPTH - 1;
+	     pnode = pnode->parent) {
+		if (strcmp(pnode->name, ".") == 0)
+			break;
+		rp[++depth] = pnode->name;
+	}
+	
+	sb = sbuf_new_auto();
+	if (sb == NULL) {
+		errno = ENOMEM;
+		return (NULL);
+	}
+	while (depth > 0) {
+		sbuf_cat(sb, rp[depth--]);
+		sbuf_putc(sb, '/');
+	}
+	sbuf_cat(sb, rp[depth]);
+	sbuf_finish(sb);
+	res = estrdup(sbuf_data(sb));
+	sbuf_delete(sb);
+	if (res == NULL)
+		errno = ENOMEM;
+	return res;
+
+}
+
+/* mtree_resolve() sets errno to indicate why NULL was returned. */
+static char *
+mtree_resolve(const char *spec, int *istemp)
+{
+	struct sbuf *sb;
+	char *res, *var = NULL;
+	const char *base, *p, *v;
+	size_t len;
+	int c, error, quoted, subst;
+
+	len = strlen(spec);
+	if (len == 0) {
+		errno = EINVAL;
+		return (NULL);
+	}
+
+	c = (len > 1) ? (spec[0] == spec[len - 1]) ? spec[0] : 0 : 0;
+	*istemp = (c == '`') ? 1 : 0;
+	subst = (c == '`' || c == '"') ? 1 : 0;
+	quoted = (subst || c == '\'') ? 1 : 0;
+
+	if (!subst) {
+		res = estrdup(spec + quoted);
+		if (quoted)
+			res[len - 2] = '\0';
+		return (res);
+	}
+
+	sb = sbuf_new_auto();
+	if (sb == NULL) {
+		errno = ENOMEM;
+		return (NULL);
+	}
+
+	base = spec + 1;
+	len -= 2;
+	error = 0;
+	while (len > 0) {
+		p = strchr(base, '$');
+		if (p == NULL) {
+			sbuf_bcat(sb, base, len);
+			base += len;
+			len = 0;
+			continue;
+		}
+		/* The following is safe. spec always starts with a quote. */
+		if (p[-1] == '\\')
+			p--;
+		if (base != p) {
+			sbuf_bcat(sb, base, p - base);
+			len -= p - base;
+			base = p;
+		}
+		if (*p == '\\') {
+			sbuf_putc(sb, '$');
+			base += 2;
+			len -= 2;
+			continue;
+		}
+		/* Skip the '$'. */
+		base++;
+		len--;
+		/* Handle ${X} vs $X. */
+		v = base;
+		if (*base == '{') {
+			p = strchr(v, '}');
+			if (p == NULL)
+				p = v;
+		} else
+			p = v;
+		len -= (p + 1) - base;
+		base = p + 1;
+
+		if (v == p) {
+			sbuf_putc(sb, *v);
+			continue;
+		}
+
+		error = ENOMEM;
+		var = ecalloc(p - v, 1);
+		memcpy(var, v + 1, p - v - 1);
+		if (strcmp(var, ".CURDIR") == 0) {
+			res = getcwd(NULL, 0);
+			if (res == NULL)
+				break;
+		} else if (strcmp(var, ".PROG") == 0) {
+			res = estrdup(getprogname());
+		} else {
+			v = getenv(var);
+			if (v != NULL) {
+				res = estrdup(v);
+			} else
+				res = NULL;
+		}
+		error = 0;
+
+		if (res != NULL) {
+			sbuf_cat(sb, res);
+			free(res);
+		}
+		free(var);
+		var = NULL;
+	}
+
+	free(var);
+	sbuf_finish(sb);
+	res = (error == 0) ? strdup(sbuf_data(sb)) : NULL;
+	sbuf_delete(sb);
+	if (res == NULL)
+		errno = ENOMEM;
+	return (res);
+}
+
+static int
+skip_over(FILE *fp, const char *cs)
+{
+	int c;
+
+	c = getc(fp);
+	while (c != EOF && strchr(cs, c) != NULL)
+		c = getc(fp);
+	if (c != EOF) {
+		ungetc(c, fp);
+		return (0);
+	}
+	return (ferror(fp) ? errno : -1);
+}
+
+static int
+skip_to(FILE *fp, const char *cs)
+{
+	int c;
+
+	c = getc(fp);
+	while (c != EOF && strchr(cs, c) == NULL)
+		c = getc(fp);
+	if (c != EOF) {
+		ungetc(c, fp);
+		return (0);
+	}
+	return (ferror(fp) ? errno : -1);
+}
+
+static int
+read_word(FILE *fp, char *buf, size_t bufsz)
+{
+	struct mtree_fileinfo *fi;
+	size_t idx, qidx;
+	int c, done, error, esc, qlvl;
+
+	if (bufsz == 0)
+		return (EINVAL);
+
+	done = 0;
+	esc = 0;
+	idx = 0;
+	qidx = -1;
+	qlvl = 0;
+	do {
+		c = getc(fp);
+		switch (c) {
+		case EOF:
+			buf[idx] = '\0';
+			error = ferror(fp) ? errno : -1;
+			if (error == -1)
+				mtree_error("unexpected end of file");
+			return (error);
+		case '#':		/* comment -- skip to end of line. */
+			if (!esc) {
+				error = skip_to(fp, "\n");
+				if (!error)
+					continue;
+			}
+			break;
+		case '\\':
+			esc++;
+			break;
+		case '`':
+		case '\'':
+		case '"':
+			if (esc)
+				break;
+			if (qlvl == 0) {
+				qlvl++;
+				qidx = idx;
+			} else if (c == buf[qidx]) {
+				qlvl--;
+				if (qlvl > 0) {
+					do {
+						qidx--;
+					} while (buf[qidx] != '`' &&
+					    buf[qidx] != '\'' &&
+					    buf[qidx] != '"');
+				} else
+					qidx = -1;
+			} else {
+				qlvl++;
+				qidx = idx;
+			}
+			break;
+		case ' ':
+		case '\t':
+		case '\n':
+			if (!esc && qlvl == 0) {
+				ungetc(c, fp);
+				c = '\0';
+				done = 1;
+				break;
+			}
+			if (c == '\n') {
+				/*
+				 * We going to eat the newline ourselves.
+				 */
+				if (qlvl > 0)
+					mtree_warning("quoted word straddles "
+					    "onto next line.");
+				fi = SLIST_FIRST(&mtree_fileinfo);
+				fi->line++;
+			}
+			break;
+		default:
+			if (esc)
+				buf[idx++] = '\\';
+			break;
+		}
+		buf[idx++] = c;
+		esc = 0;
+	} while (idx < bufsz && !done);
+
+	if (idx >= bufsz) {
+		mtree_error("word too long to fit buffer (max %zu characters)",
+		    bufsz);
+		skip_to(fp, " \t\n");
+	}
+	return (0);
+}
+
+static fsnode *
+create_node(const char *name, u_int type, fsnode *parent, fsnode *global)
+{
+	fsnode *n;
+
+	n = ecalloc(1, sizeof(*n));
+	n->name = estrdup(name);
+	n->type = (type == 0) ? global->type : type;
+	n->parent = parent;
+
+	n->inode = ecalloc(1, sizeof(*n->inode));
+
+	/* Assign global options/defaults. */
+	memcpy(n->inode, global->inode, sizeof(*n->inode));
+	n->inode->st.st_mode = (n->inode->st.st_mode & ~S_IFMT) | n->type;
+
+	if (n->type == S_IFLNK)
+		n->symlink = global->symlink;
+	else if (n->type == S_IFREG)
+		n->contents = global->contents;
+
+	return (n);
+}
+
+static void
+destroy_node(fsnode *n)
+{
+
+	assert(n != NULL);
+	assert(n->name != NULL);
+	assert(n->inode != NULL);
+
+	free(n->inode);
+	free(n->name);
+	free(n);
+}
+
+static int
+read_number(const char *tok, u_int base, intmax_t *res, intmax_t min,
+    intmax_t max)
+{
+	char *end;
+	intmax_t val;
+
+	val = strtoimax(tok, &end, base);
+	if (end == tok || end[0] != '\0')
+		return (EINVAL);
+	if (val < min || val > max)
+		return (EDOM);
+	*res = val;
+	return (0);
+}
+
+static int
+read_mtree_keywords(FILE *fp, fsnode *node)
+{
+	char keyword[PATH_MAX];
+	char *name, *p, *value;
+	gid_t gid;
+	uid_t uid;
+	struct stat *st, sb;
+	intmax_t num;
+	u_long flset, flclr;
+	int error, istemp;
+	uint32_t type;
+
+	st = &node->inode->st;
+	do {
+		error = skip_over(fp, " \t");
+		if (error)
+			break;
+
+		error = read_word(fp, keyword, sizeof(keyword));
+		if (error)
+			break;
+
+		if (keyword[0] == '\0')
+			break;
+
+		value = strchr(keyword, '=');
+		if (value != NULL)
+			*value++ = '\0';
+
+		/*
+		 * We use EINVAL, ENOATTR, ENOSYS and ENXIO to signal
+		 * certain conditions:
+		 *   EINVAL -	Value provided for a keyword that does
+		 *		not take a value. The value is ignored.
+		 *   ENOATTR -	Value missing for a keyword that needs
+		 *		a value. The keyword is ignored.
+		 *   ENOSYS -	Unsupported keyword encountered. The
+		 *		keyword is ignored.
+		 *   ENXIO -	Value provided for a keyword that does
+		 *		not take a value. The value is ignored.
+		 */
+		switch (keyword[0]) {
+		case 'c':
+			if (strcmp(keyword, "contents") == 0) {
+				if (value == NULL) {
+					error = ENOATTR;
+					break;
+				}
+				node->contents = estrdup(value);
+			} else
+				error = ENOSYS;
+			break;
+		case 'f':
+			if (strcmp(keyword, "flags") == 0) {
+				if (value == NULL) {
+					error = ENOATTR;
+					break;
+				}
+				flset = flclr = 0;
+#if HAVE_STRUCT_STAT_ST_FLAGS
+				if (!strtofflags(&value, &flset, &flclr)) {
+					st->st_flags &= ~flclr;
+					st->st_flags |= flset;
+				} else
+					error = errno;
+#endif
+			} else
+				error = ENOSYS;
+			break;
+		case 'g':
+			if (strcmp(keyword, "gid") == 0) {
+				if (value == NULL) {
+					error = ENOATTR;
+					break;
+				}
+				error = read_number(value, 10, &num,
+				    0, UINT_MAX);
+				if (!error)
+					st->st_gid = num;
+			} else if (strcmp(keyword, "gname") == 0) {
+				if (value == NULL) {
+					error = ENOATTR;
+					break;
+				}
+				if (gid_from_group(value, &gid) == 0)
+					st->st_gid = gid;
+				else
+					error = EINVAL;
+			} else
+				error = ENOSYS;
+			break;
+		case 'l':
+			if (strcmp(keyword, "link") == 0) {
+				if (value == NULL) {
+					error = ENOATTR;
+					break;
+				}
+				node->symlink = emalloc(strlen(value) + 1);
+				if (node->symlink == NULL) {
+					error = errno;
+					break;
+				}
+				if (strunvis(node->symlink, value) < 0) {
+					error = errno;
+					break;
+				}
+			} else
+				error = ENOSYS;
+			break;
+		case 'm':
+			if (strcmp(keyword, "mode") == 0) {
+				if (value == NULL) {
+					error = ENOATTR;
+					break;
+				}
+				if (value[0] >= '0' && value[0] <= '9') {
+					error = read_number(value, 8, &num,
+					    0, 07777);
+					if (!error) {
+						st->st_mode &= S_IFMT;
+						st->st_mode |= num;
+					}
+				} else {
+					/* Symbolic mode not supported. */
+					error = EINVAL;
+					break;
+				}
+			} else
+				error = ENOSYS;
+			break;
+		case 'o':
+			if (strcmp(keyword, "optional") == 0) {
+				if (value != NULL)
+					error = ENXIO;
+				node->flags |= FSNODE_F_OPTIONAL;
+			} else
+				error = ENOSYS;
+			break;
+		case 's':
+			if (strcmp(keyword, "size") == 0) {
+				if (value == NULL) {
+					error = ENOATTR;
+					break;
+				}
+				error = read_number(value, 10, &num,
+				    0, INTMAX_MAX);
+				if (!error)
+					st->st_size = num;
+			} else
+				error = ENOSYS;
+			break;
+		case 't':
+			if (strcmp(keyword, "time") == 0) {
+				if (value == NULL) {
+					error = ENOATTR;
+					break;
+				}
+				p = strchr(value, '.');
+				if (p != NULL)
+					*p++ = '\0';
+				error = read_number(value, 10, &num, 0,
+				    INTMAX_MAX);
+				if (error)
+					break;
+				st->st_atime = num;
+				st->st_ctime = num;
+				st->st_mtime = num;
+				if (p == NULL)
+					break;
+				error = read_number(p, 10, &num, 0,
+				    INTMAX_MAX);
+				if (error)
+					break;
+				if (num != 0)
+					error = EINVAL;
+			} else if (strcmp(keyword, "type") == 0) {
+				if (value == NULL) {
+					error = ENOATTR;
+					break;
+				}
+				if (strcmp(value, "dir") == 0)
+					node->type = S_IFDIR;
+				else if (strcmp(value, "file") == 0)
+					node->type = S_IFREG;
+				else if (strcmp(value, "link") == 0)
+					node->type = S_IFLNK;
+				else
+					error = EINVAL;
+			} else
+				error = ENOSYS;
+			break;
+		case 'u':
+			if (strcmp(keyword, "uid") == 0) {
+				if (value == NULL) {
+					error = ENOATTR;
+					break;
+				}
+				error = read_number(value, 10, &num,
+				    0, UINT_MAX);
+				if (!error)
+					st->st_uid = num;
+			} else if (strcmp(keyword, "uname") == 0) {
+				if (value == NULL) {
+					error = ENOATTR;
+					break;
+				}
+				if (uid_from_user(value, &uid) == 0)
+					st->st_uid = uid;
+				else
+					error = EINVAL;
+			} else
+				error = ENOSYS;
+			break;
+		default:
+			error = ENOSYS;
+			break;
+		}
+
+		switch (error) {
+		case EINVAL:
+			mtree_error("%s: invalid value '%s'", keyword, value);
+			break;
+		case ENOATTR:
+			mtree_error("%s: keyword needs a value", keyword);
+			break;
+		case ENOSYS:
+			mtree_warning("%s: unsupported keyword", keyword);
+			break;
+		case ENXIO:
+			mtree_error("%s: keyword does not take a value",
+			    keyword);
+			break;
+		}
+	} while (1);
+
+	if (error)
+		return (error);
+
+	st->st_mode = (st->st_mode & ~S_IFMT) | node->type;
+
+	/* Nothing more to do for the global defaults. */
+	if (node->name == NULL)
+		return (0);
+
+	/*
+	 * Be intelligent about the file type.
+	 */
+	if (node->contents != NULL) {
+		if (node->symlink != NULL) {
+			mtree_error("%s: both link and contents keywords "
+			    "defined", node->name);
+			return (0);
+		}
+		type = S_IFREG;
+	} else if (node->type != 0) {
+		type = node->type;
+		if (type == S_IFREG) {
+			/* the named path is the default contents */
+			node->contents = mtree_file_path(node);
+		}
+	} else
+		type = (node->symlink != NULL) ? S_IFLNK : S_IFDIR;
+
+	if (node->type == 0)
+		node->type = type;
+
+	if (node->type != type) {
+		mtree_error("%s: file type and defined keywords to not match",
+		    node->name);
+		return (0);
+	}
+
+	st->st_mode = (st->st_mode & ~S_IFMT) | node->type;
+
+	if (node->contents == NULL)
+		return (0);
+
+	name = mtree_resolve(node->contents, &istemp);
+	if (name == NULL)
+		return (errno);
+
+	if (stat(name, &sb) != 0) {
+		mtree_error("%s: contents file '%s' not found", node->name,
+		    name);
+		free(name);
+		return (0);
+	}
+
+	/*
+         * Check for hardlinks. If the contents key is used, then the check
+         * will only trigger if the contents file is a link even if it is used
+         * by more than one file
+	 */
+	if (sb.st_nlink > 1) {
+		fsinode *curino;
+
+		st->st_ino = sb.st_ino;
+		st->st_dev = sb.st_dev;
+		curino = link_check(node->inode);
+		if (curino != NULL) {
+			free(node->inode);
+			node->inode = curino;
+			node->inode->nlink++;
+		}
+	}
+
+	free(node->contents);
+	node->contents = name;
+	st->st_size = sb.st_size;
+	return (0);
+}
+
+static int
+read_mtree_command(FILE *fp)
+{
+	char cmd[10];
+	int error;
+
+	error = read_word(fp, cmd, sizeof(cmd));
+	if (error)
+		goto out;
+
+	error = read_mtree_keywords(fp, &mtree_global);
+
+ out:
+	skip_to(fp, "\n");
+	(void)getc(fp);
+	return (error);
+}
+
+static int
+read_mtree_spec1(FILE *fp, bool def, const char *name)
+{
+	fsnode *last, *node, *parent;
+	u_int type;
+	int error;
+
+	assert(name[0] != '\0');
+
+	/*
+	 * Treat '..' specially, because it only changes our current
+	 * directory. We don't create a node for it. We simply ignore
+	 * any keywords that may appear on the line as well.
+	 * Going up a directory is a little non-obvious. A directory
+	 * node has a corresponding '.' child. The parent of '.' is
+	 * not the '.' node of the parent directory, but the directory
+	 * node within the parent to which the child relates. However,
+	 * going up a directory means we need to find the '.' node to
+	 * which the directoy node is linked.  This we can do via the
+	 * first * pointer, because '.' is always the first entry in a
+	 * directory.
+	 */
+	if (IS_DOTDOT(name)) {
+		/* This deals with NULL pointers as well. */
+		if (mtree_current == mtree_root) {
+			mtree_warning("ignoring .. in root directory");
+			return (0);
+		}
+
+		node = mtree_current;
+
+		assert(node != NULL);
+		assert(IS_DOT(node->name));
+		assert(node->first == node);
+
+		/* Get the corresponding directory node in the parent. */
+		node = mtree_current->parent;
+
+		assert(node != NULL);
+		assert(!IS_DOT(node->name));
+
+		node = node->first;
+
+		assert(node != NULL);
+		assert(IS_DOT(node->name));
+		assert(node->first == node);
+
+		mtree_current = node;
+		return (0);
+	}
+
+	/*
+	 * If we don't have a current directory and the first specification
+	 * (either implicit or defined) is not '.', then we need to create
+	 * a '.' node first (using a recursive call).
+	 */
+	if (!IS_DOT(name) && mtree_current == NULL) {
+		error = read_mtree_spec1(fp, false, ".");
+		if (error)
+			return (error);
+	}
+
+	/*
+	 * Lookup the name in the current directory (if we have a current
+	 * directory) to make sure we do not create multiple nodes for the
+	 * same component. For non-definitions, if we find a node with the
+	 * same name, simply change the current directory. For definitions
+	 * more happens.
+	 */
+	last = NULL;
+	node = mtree_current;
+	while (node != NULL) {
+		assert(node->first == mtree_current);
+
+		if (strcmp(name, node->name) == 0) {
+			if (def == true) {
+				if (!dupsok)
+					mtree_error(
+					    "duplicate definition of %s",
+					    name);
+				else
+					mtree_warning(
+					    "duplicate definition of %s",
+					    name);
+				return (0);
+			}
+
+			if (node->type != S_IFDIR) {
+				mtree_error("%s is not a directory", name);
+				return (0);
+			}
+
+			assert(!IS_DOT(name));
+
+			node = node->child;
+
+			assert(node != NULL);
+			assert(IS_DOT(node->name));
+
+			mtree_current = node;
+			return (0);
+		}
+
+		last = node;
+		node = last->next;
+	}
+
+	parent = (mtree_current != NULL) ? mtree_current->parent : NULL;
+	type = (def == false || IS_DOT(name)) ? S_IFDIR : 0;
+	node = create_node(name, type, parent, &mtree_global);
+	if (node == NULL)
+		return (ENOMEM);
+
+	if (def == true) {
+		error = read_mtree_keywords(fp, node);
+		if (error) {
+			destroy_node(node);
+			return (error);
+		}
+	}
+
+	node->first = (mtree_current != NULL) ? mtree_current : node;
+
+	if (last != NULL)
+		last->next = node;
+
+	if (node->type != S_IFDIR)
+		return (0);
+
+	if (!IS_DOT(node->name)) {
+		parent = node;
+		node = create_node(".", S_IFDIR, parent, parent);
+		if (node == NULL) {
+			last->next = NULL;
+			destroy_node(parent);
+			return (ENOMEM);
+		}
+		parent->child = node;
+		node->first = node;
+	}
+
+	assert(node != NULL);
+	assert(IS_DOT(node->name));
+	assert(node->first == node);
+
+	mtree_current = node;
+	if (mtree_root == NULL)
+		mtree_root = node;
+
+	return (0);
+}
+
+static int
+read_mtree_spec(FILE *fp)
+{
+	char pathspec[PATH_MAX], pathtmp[4*PATH_MAX + 1];
+	char *cp;
+	int error;
+
+	error = read_word(fp, pathtmp, sizeof(pathtmp));
+	if (error)
+		goto out;
+	if (strnunvis(pathspec, PATH_MAX, pathtmp) == -1) {
+		error = errno;
+		goto out;
+	}
+	error = 0;
+
+	cp = strchr(pathspec, '/');
+	if (cp != NULL) {
+		/* Absolute pathname */
+		mtree_current = mtree_root;
+
+		do {
+			*cp++ = '\0';
+
+			/* Disallow '..' as a component. */
+			if (IS_DOTDOT(pathspec)) {
+				mtree_error("absolute path cannot contain "
+				    ".. component");
+				goto out;
+			}
+
+			/* Ignore multiple adjacent slashes and '.'. */
+			if (pathspec[0] != '\0' && !IS_DOT(pathspec))
+				error = read_mtree_spec1(fp, false, pathspec);
+			memmove(pathspec, cp, strlen(cp) + 1);
+			cp = strchr(pathspec, '/');
+		} while (!error && cp != NULL);
+
+		/* Disallow '.' and '..' as the last component. */
+		if (!error && (IS_DOT(pathspec) || IS_DOTDOT(pathspec))) {
+			mtree_error("absolute path cannot contain . or .. "
+			    "components");
+			goto out;
+		}
+	}
+
+	/* Ignore absolute specfications that end with a slash. */
+	if (!error && pathspec[0] != '\0')
+		error = read_mtree_spec1(fp, true, pathspec);
+
+ out:
+	skip_to(fp, "\n");
+	(void)getc(fp);
+	return (error);
+}
+
+fsnode *
+read_mtree(const char *fname, fsnode *node)
+{
+	struct mtree_fileinfo *fi;
+	FILE *fp;
+	int c, error;
+
+	/* We do not yet support nesting... */
+	assert(node == NULL);
+
+	if (strcmp(fname, "-") == 0)
+		fp = stdin;
+	else {
+		fp = fopen(fname, "r");
+		if (fp == NULL)
+			err(1, "Can't open `%s'", fname);
+	}
+
+	error = mtree_file_push(fname, fp);
+	if (error)
+		goto out;
+
+	memset(&mtree_global, 0, sizeof(mtree_global));
+	memset(&mtree_global_inode, 0, sizeof(mtree_global_inode));
+	mtree_global.inode = &mtree_global_inode;
+	mtree_global_inode.nlink = 1;
+	mtree_global_inode.st.st_nlink = 1;
+	mtree_global_inode.st.st_atime = mtree_global_inode.st.st_ctime =
+	    mtree_global_inode.st.st_mtime = time(NULL);
+	errors = warnings = 0;
+
+	setgroupent(1);
+	setpassent(1);
+
+	mtree_root = node;
+	mtree_current = node;
+	do {
+		/* Start of a new line... */
+		fi = SLIST_FIRST(&mtree_fileinfo);
+		fi->line++;
+
+		error = skip_over(fp, " \t");
+		if (error)
+			break;
+
+		c = getc(fp);
+		if (c == EOF) {
+			error = ferror(fp) ? errno : -1;
+			break;
+		}
+
+		switch (c) {
+		case '\n':		/* empty line */
+			error = 0;
+			break;
+		case '#':		/* comment -- skip to end of line. */
+			error = skip_to(fp, "\n");
+			if (!error)
+				(void)getc(fp);
+			break;
+		case '/':		/* special commands */
+			error = read_mtree_command(fp);
+			break;
+		default:		/* specification */
+			ungetc(c, fp);
+			error = read_mtree_spec(fp);
+			break;
+		}
+	} while (!error);
+
+	endpwent();
+	endgrent();
+
+	if (error <= 0 && (errors || warnings)) {
+		warnx("%u error(s) and %u warning(s) in mtree manifest",
+		    errors, warnings);
+		if (errors)
+			exit(1);
+	}
+
+ out:
+	if (error > 0)
+		errc(1, error, "Error reading mtree file");
+
+	if (fp != stdin)
+		fclose(fp);
+
+	if (mtree_root != NULL)
+		return (mtree_root);
+
+	/* Handle empty specifications. */
+	node = create_node(".", S_IFDIR, NULL, &mtree_global);
+	node->first = node;
+	return (node);
+}
diff --git a/usr.sbin/makefs/walk.c b/usr.sbin/makefs/walk.c
new file mode 100644
index 0000000000..6b39fca815
--- /dev/null
+++ b/usr.sbin/makefs/walk.c
@@ -0,0 +1,698 @@
+/*	$NetBSD: walk.c,v 1.24 2008/12/28 21:51:46 christos Exp $	*/
+
+/*
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * Copyright (c) 2001 Wasabi Systems, Inc.
+ * All rights reserved.
+ *
+ * Written by Luke Mewburn for Wasabi Systems, Inc.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software developed for the NetBSD Project by
+ *      Wasabi Systems, Inc.
+ * 4. The name of Wasabi Systems, Inc. may not be used to endorse
+ *    or promote products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``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 WASABI SYSTEMS, INC
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/usr.sbin/makefs/walk.c 326276 2017-11-27 15:37:16Z pfg $");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "makefs.h"
+#include "mtree.h"
+#include "extern.h"
+
+static	void	 apply_specdir(const char *, NODE *, fsnode *, int);
+static	void	 apply_specentry(const char *, NODE *, fsnode *);
+static	fsnode	*create_fsnode(const char *, const char *, const char *,
+			       struct stat *);
+
+
+/*
+ * walk_dir --
+ *	build a tree of fsnodes from `root' and `dir', with a parent
+ *	fsnode of `parent' (which may be NULL for the root of the tree).
+ *	append the tree to a fsnode of `join' if it is not NULL.
+ *	each "level" is a directory, with the "." entry guaranteed to be
+ *	at the start of the list, and without ".." entries.
+ */
+fsnode *
+walk_dir(const char *root, const char *dir, fsnode *parent, fsnode *join)
+{
+	fsnode		*first, *cur, *prev, *last;
+	DIR		*dirp;
+	struct dirent	*dent;
+	char		path[MAXPATHLEN + 1];
+	struct stat	stbuf;
+	char		*name, *rp;
+	size_t		len;
+	int		dot;
+
+	assert(root != NULL);
+	assert(dir != NULL);
+
+	len = snprintf(path, sizeof(path), "%s/%s", root, dir);
+	if (len >= sizeof(path))
+		errx(1, "Pathname too long.");
+	if (debug & DEBUG_WALK_DIR)
+		printf("walk_dir: %s %p\n", path, parent);
+	if ((dirp = opendir(path)) == NULL)
+		err(1, "Can't opendir `%s'", path);
+	rp = path + strlen(root) + 1;
+	if (join != NULL) {
+		first = cur = join;
+		while (cur->next != NULL)
+			cur = cur->next;
+		prev = cur;
+	} else
+		first = prev = NULL;
+	last = prev;
+	while ((dent = readdir(dirp)) != NULL) {
+		name = dent->d_name;
+		dot = 0;
+		if (name[0] == '.')
+			switch (name[1]) {
+			case '\0':	/* "." */
+				if (join != NULL)
+					continue;
+				dot = 1;
+				break;
+			case '.':	/* ".." */
+				if (name[2] == '\0')
+					continue;
+				/* FALLTHROUGH */
+			default:
+				dot = 0;
+			}
+		if (debug & DEBUG_WALK_DIR_NODE)
+			printf("scanning %s/%s/%s\n", root, dir, name);
+		if ((size_t)snprintf(path + len, sizeof(path) - len, "/%s",
+		    name) >= sizeof(path) - len)
+			errx(1, "Pathname too long.");
+		if (lstat(path, &stbuf) == -1)
+			err(1, "Can't lstat `%s'", path);
+#ifdef S_ISSOCK
+		if (S_ISSOCK(stbuf.st_mode & S_IFMT)) {
+			if (debug & DEBUG_WALK_DIR_NODE)
+				printf("  skipping socket %s\n", path);
+			continue;
+		}
+#endif
+
+		if (join != NULL) {
+			cur = join->next;
+			for (;;) {
+				if (cur == NULL || strcmp(cur->name, name) == 0)
+					break;
+				if (cur == last) {
+					cur = NULL;
+					break;
+				}
+				cur = cur->next;
+			}
+			if (cur != NULL) {
+				if (S_ISDIR(cur->type) &&
+				    S_ISDIR(stbuf.st_mode)) {
+					if (debug & DEBUG_WALK_DIR_NODE)
+						printf("merging %s with %p\n",
+						    path, cur->child);
+					cur->child = walk_dir(root, rp, cur,
+					    cur->child);
+					continue;
+				}
+				errx(1, "Can't merge %s `%s' with existing %s",
+				    inode_type(stbuf.st_mode), path,
+				    inode_type(cur->type));
+			}
+		}
+
+		cur = create_fsnode(root, dir, name, &stbuf);
+		cur->parent = parent;
+		if (dot) {
+				/* ensure "." is at the start of the list */
+			cur->next = first;
+			first = cur;
+			if (! prev)
+				prev = cur;
+			cur->first = first;
+		} else {			/* not "." */
+			if (prev)
+				prev->next = cur;
+			prev = cur;
+			if (!first)
+				first = cur;
+			cur->first = first;
+			if (S_ISDIR(cur->type)) {
+				cur->child = walk_dir(root, rp, cur, NULL);
+				continue;
+			}
+		}
+		if (stbuf.st_nlink > 1) {
+			fsinode	*curino;
+
+			curino = link_check(cur->inode);
+			if (curino != NULL) {
+				free(cur->inode);
+				cur->inode = curino;
+				cur->inode->nlink++;
+				if (debug & DEBUG_WALK_DIR_LINKCHECK)
+					printf("link_check: found [%llu, %llu]\n",
+					    (unsigned long long)curino->st.st_dev,
+					    (unsigned long long)curino->st.st_ino);
+			}
+		}
+		if (S_ISLNK(cur->type)) {
+			char	slink[PATH_MAX+1];
+			int	llen;
+
+			llen = readlink(path, slink, sizeof(slink) - 1);
+			if (llen == -1)
+				err(1, "Readlink `%s'", path);
+			slink[llen] = '\0';
+			cur->symlink = estrdup(slink);
+		}
+	}
+	assert(first != NULL);
+	if (join == NULL)
+		for (cur = first->next; cur != NULL; cur = cur->next)
+			cur->first = first;
+	if (closedir(dirp) == -1)
+		err(1, "Can't closedir `%s/%s'", root, dir);
+	return (first);
+}
+
+static fsnode *
+create_fsnode(const char *root, const char *path, const char *name,
+    struct stat *stbuf)
+{
+	fsnode *cur;
+
+	cur = ecalloc(1, sizeof(*cur));
+	cur->path = estrdup(path);
+	cur->name = estrdup(name);
+	cur->inode = ecalloc(1, sizeof(*cur->inode));
+	cur->root = root;
+	cur->type = stbuf->st_mode & S_IFMT;
+	cur->inode->nlink = 1;
+	cur->inode->st = *stbuf;
+	if (stampst.st_ino) {
+		cur->inode->st.st_atime = stampst.st_atime;
+		cur->inode->st.st_mtime = stampst.st_mtime;
+		cur->inode->st.st_ctime = stampst.st_ctime;
+#if HAVE_STRUCT_STAT_ST_MTIMENSEC
+		cur->inode->st.st_atimensec = stampst.st_atimensec;
+		cur->inode->st.st_mtimensec = stampst.st_mtimensec;
+		cur->inode->st.st_ctimensec = stampst.st_ctimensec;
+#endif
+#if HAVE_STRUCT_STAT_BIRTHTIME
+		cur->inode->st.st_birthtime = stampst.st_birthtime;
+		cur->inode->st.st_birthtimensec = stampst.st_birthtimensec;
+#endif
+	}
+	return (cur);
+}
+
+/*
+ * free_fsnodes --
+ *	Removes node from tree and frees it and all of
+ *   its descendants.
+ */
+void
+free_fsnodes(fsnode *node)
+{
+	fsnode	*cur, *next;
+
+	assert(node != NULL);
+
+	/* for ".", start with actual parent node */
+	if (node->first == node) {
+		assert(node->name[0] == '.' && node->name[1] == '\0');
+		if (node->parent) {
+			assert(node->parent->child == node);
+			node = node->parent;
+		}
+	}
+
+	/* Find ourselves in our sibling list and unlink */
+	if (node->first != node) {
+		for (cur = node->first; cur->next; cur = cur->next) {
+			if (cur->next == node) {
+				cur->next = node->next;
+				node->next = NULL;
+				break;
+			}
+		}
+	}
+
+	for (cur = node; cur != NULL; cur = next) {
+		next = cur->next;
+		if (cur->child) {
+			cur->child->parent = NULL;
+			free_fsnodes(cur->child);
+		}
+		if (cur->inode->nlink-- == 1)
+			free(cur->inode);
+		if (cur->symlink)
+			free(cur->symlink);
+		free(cur->path);
+		free(cur->name);
+		free(cur);
+	}
+}
+
+/*
+ * apply_specfile --
+ *	read in the mtree(8) specfile, and apply it to the tree
+ *	at dir,parent. parameters in parent on equivalent types
+ *	will be changed to those found in specfile, and missing
+ *	entries will be added.
+ */
+void
+apply_specfile(const char *specfile, const char *dir, fsnode *parent, int speconly)
+{
+	struct timeval	 start;
+	FILE	*fp;
+	NODE	*root;
+
+	assert(specfile != NULL);
+	assert(parent != NULL);
+
+	if (debug & DEBUG_APPLY_SPECFILE)
+		printf("apply_specfile: %s, %s %p\n", specfile, dir, parent);
+
+				/* read in the specfile */
+	if ((fp = fopen(specfile, "r")) == NULL)
+		err(1, "Can't open `%s'", specfile);
+	TIMER_START(start);
+	root = spec(fp);
+	TIMER_RESULTS(start, "spec");
+	if (fclose(fp) == EOF)
+		err(1, "Can't close `%s'", specfile);
+
+				/* perform some sanity checks */
+	if (root == NULL)
+		errx(1, "Specfile `%s' did not contain a tree", specfile);
+	assert(strcmp(root->name, ".") == 0);
+	assert(root->type == F_DIR);
+
+				/* merge in the changes */
+	apply_specdir(dir, root, parent, speconly);
+
+	free_nodes(root);
+}
+
+static void
+apply_specdir(const char *dir, NODE *specnode, fsnode *dirnode, int speconly)
+{
+	char	 path[MAXPATHLEN + 1];
+	NODE	*curnode;
+	fsnode	*curfsnode;
+
+	assert(specnode != NULL);
+	assert(dirnode != NULL);
+
+	if (debug & DEBUG_APPLY_SPECFILE)
+		printf("apply_specdir: %s %p %p\n", dir, specnode, dirnode);
+
+	if (specnode->type != F_DIR)
+		errx(1, "Specfile node `%s/%s' is not a directory",
+		    dir, specnode->name);
+	if (dirnode->type != S_IFDIR)
+		errx(1, "Directory node `%s/%s' is not a directory",
+		    dir, dirnode->name);
+
+	apply_specentry(dir, specnode, dirnode);
+
+	/* Remove any filesystem nodes not found in specfile */
+	/* XXX inefficient.  This is O^2 in each dir and it would
+	 * have been better never to have walked this part of the tree
+	 * to begin with
+	 */
+	if (speconly) {
+		fsnode *next;
+		assert(dirnode->name[0] == '.' && dirnode->name[1] == '\0');
+		for (curfsnode = dirnode->next; curfsnode != NULL; curfsnode = next) {
+			next = curfsnode->next;
+			for (curnode = specnode->child; curnode != NULL;
+			     curnode = curnode->next) {
+				if (strcmp(curnode->name, curfsnode->name) == 0)
+					break;
+			}
+			if (curnode == NULL) {
+				if (debug & DEBUG_APPLY_SPECONLY) {
+					printf("apply_specdir: trimming %s/%s %p\n", dir, curfsnode->name, curfsnode);
+				}
+				free_fsnodes(curfsnode);
+			}
+		}
+	}
+
+			/* now walk specnode->child matching up with dirnode */
+	for (curnode = specnode->child; curnode != NULL;
+	    curnode = curnode->next) {
+		if (debug & DEBUG_APPLY_SPECENTRY)
+			printf("apply_specdir:  spec %s\n",
+			    curnode->name);
+		for (curfsnode = dirnode->next; curfsnode != NULL;
+		    curfsnode = curfsnode->next) {
+#if 0	/* too verbose for now */
+			if (debug & DEBUG_APPLY_SPECENTRY)
+				printf("apply_specdir:  dirent %s\n",
+				    curfsnode->name);
+#endif
+			if (strcmp(curnode->name, curfsnode->name) == 0)
+				break;
+		}
+		if ((size_t)snprintf(path, sizeof(path), "%s/%s", dir,
+		    curnode->name) >= sizeof(path))
+			errx(1, "Pathname too long.");
+		if (curfsnode == NULL) {	/* need new entry */
+			struct stat	stbuf;
+
+					    /*
+					     * don't add optional spec entries
+					     * that lack an existing fs entry
+					     */
+			if ((curnode->flags & F_OPT) &&
+			    lstat(path, &stbuf) == -1)
+					continue;
+
+					/* check that enough info is provided */
+#define NODETEST(t, m)							\
+			if (!(t))					\
+				errx(1, "`%s': %s not provided", path, m)
+			NODETEST(curnode->flags & F_TYPE, "type");
+			NODETEST(curnode->flags & F_MODE, "mode");
+				/* XXX: require F_TIME ? */
+			NODETEST(curnode->flags & F_GID ||
+			    curnode->flags & F_GNAME, "group");
+			NODETEST(curnode->flags & F_UID ||
+			    curnode->flags & F_UNAME, "user");
+/*			if (curnode->type == F_BLOCK || curnode->type == F_CHAR)
+				NODETEST(curnode->flags & F_DEV,
+				    "device number");*/
+#undef NODETEST
+
+			if (debug & DEBUG_APPLY_SPECFILE)
+				printf("apply_specdir: adding %s\n",
+				    curnode->name);
+					/* build minimal fsnode */
+			memset(&stbuf, 0, sizeof(stbuf));
+			stbuf.st_mode = nodetoino(curnode->type);
+			stbuf.st_nlink = 1;
+			stbuf.st_mtime = stbuf.st_atime =
+			    stbuf.st_ctime = start_time.tv_sec;
+#if HAVE_STRUCT_STAT_ST_MTIMENSEC
+			stbuf.st_mtimensec = stbuf.st_atimensec =
+			    stbuf.st_ctimensec = start_time.tv_nsec;
+#endif
+			curfsnode = create_fsnode(".", ".", curnode->name,
+			    &stbuf);
+			curfsnode->parent = dirnode->parent;
+			curfsnode->first = dirnode;
+			curfsnode->next = dirnode->next;
+			dirnode->next = curfsnode;
+			if (curfsnode->type == S_IFDIR) {
+					/* for dirs, make "." entry as well */
+				curfsnode->child = create_fsnode(".", ".", ".",
+				    &stbuf);
+				curfsnode->child->parent = curfsnode;
+				curfsnode->child->first = curfsnode->child;
+			}
+			if (curfsnode->type == S_IFLNK) {
+				assert(curnode->slink != NULL);
+					/* for symlinks, copy the target */
+				curfsnode->symlink = estrdup(curnode->slink);
+			}
+		}
+		apply_specentry(dir, curnode, curfsnode);
+		if (curnode->type == F_DIR) {
+			if (curfsnode->type != S_IFDIR)
+				errx(1, "`%s' is not a directory", path);
+			assert (curfsnode->child != NULL);
+			apply_specdir(path, curnode, curfsnode->child, speconly);
+		}
+	}
+}
+
+static void
+apply_specentry(const char *dir, NODE *specnode, fsnode *dirnode)
+{
+
+	assert(specnode != NULL);
+	assert(dirnode != NULL);
+
+	if (nodetoino(specnode->type) != dirnode->type)
+		errx(1, "`%s/%s' type mismatch: specfile %s, tree %s",
+		    dir, specnode->name, inode_type(nodetoino(specnode->type)),
+		    inode_type(dirnode->type));
+
+	if (debug & DEBUG_APPLY_SPECENTRY)
+		printf("apply_specentry: %s/%s\n", dir, dirnode->name);
+
+#define ASEPRINT(t, b, o, n) \
+		if (debug & DEBUG_APPLY_SPECENTRY) \
+			printf("\t\t\tchanging %s from " b " to " b "\n", \
+			    t, o, n)
+
+	if (specnode->flags & (F_GID | F_GNAME)) {
+		ASEPRINT("gid", "%d",
+		    dirnode->inode->st.st_gid, specnode->st_gid);
+		dirnode->inode->st.st_gid = specnode->st_gid;
+	}
+	if (specnode->flags & F_MODE) {
+		ASEPRINT("mode", "%#o",
+		    dirnode->inode->st.st_mode & ALLPERMS, specnode->st_mode);
+		dirnode->inode->st.st_mode &= ~ALLPERMS;
+		dirnode->inode->st.st_mode |= (specnode->st_mode & ALLPERMS);
+	}
+		/* XXX: ignoring F_NLINK for now */
+	if (specnode->flags & F_SIZE) {
+		ASEPRINT("size", "%lld",
+		    (long long)dirnode->inode->st.st_size,
+		    (long long)specnode->st_size);
+		dirnode->inode->st.st_size = specnode->st_size;
+	}
+	if (specnode->flags & F_SLINK) {
+		assert(dirnode->symlink != NULL);
+		assert(specnode->slink != NULL);
+		ASEPRINT("symlink", "%s", dirnode->symlink, specnode->slink);
+		free(dirnode->symlink);
+		dirnode->symlink = estrdup(specnode->slink);
+	}
+	if (specnode->flags & F_TIME) {
+		ASEPRINT("time", "%ld",
+		    (long)dirnode->inode->st.st_mtime,
+		    (long)specnode->st_mtimespec.tv_sec);
+		dirnode->inode->st.st_mtime =		specnode->st_mtimespec.tv_sec;
+		dirnode->inode->st.st_atime =		specnode->st_mtimespec.tv_sec;
+		dirnode->inode->st.st_ctime =		start_time.tv_sec;
+#if HAVE_STRUCT_STAT_ST_MTIMENSEC
+		dirnode->inode->st.st_mtimensec =	specnode->st_mtimespec.tv_nsec;
+		dirnode->inode->st.st_atimensec =	specnode->st_mtimespec.tv_nsec;
+		dirnode->inode->st.st_ctimensec =	start_time.tv_nsec;
+#endif
+	}
+	if (specnode->flags & (F_UID | F_UNAME)) {
+		ASEPRINT("uid", "%d",
+		    dirnode->inode->st.st_uid, specnode->st_uid);
+		dirnode->inode->st.st_uid = specnode->st_uid;
+	}
+#if HAVE_STRUCT_STAT_ST_FLAGS
+	if (specnode->flags & F_FLAGS) {
+		ASEPRINT("flags", "%#lX",
+		    (unsigned long)dirnode->inode->st.st_flags,
+		    (unsigned long)specnode->st_flags);
+		dirnode->inode->st.st_flags = specnode->st_flags;
+	}
+#endif
+/*	if (specnode->flags & F_DEV) {
+		ASEPRINT("rdev", "%#llx",
+		    (unsigned long long)dirnode->inode->st.st_rdev,
+		    (unsigned long long)specnode->st_rdev);
+		dirnode->inode->st.st_rdev = specnode->st_rdev;
+	}*/
+#undef ASEPRINT
+
+	dirnode->flags |= FSNODE_F_HASSPEC;
+}
+
+
+/*
+ * dump_fsnodes --
+ *	dump the fsnodes from `cur'
+ */
+void
+dump_fsnodes(fsnode *root)
+{
+	fsnode	*cur;
+	char	path[MAXPATHLEN + 1];
+
+	printf("dump_fsnodes: %s %p\n", root->path, root);
+	for (cur = root; cur != NULL; cur = cur->next) {
+		if (snprintf(path, sizeof(path), "%s/%s", cur->path,
+		    cur->name) >= (int)sizeof(path))
+			errx(1, "Pathname too long.");
+
+		if (debug & DEBUG_DUMP_FSNODES_VERBOSE)
+			printf("cur=%8p parent=%8p first=%8p ",
+			    cur, cur->parent, cur->first);
+		printf("%7s: %s", inode_type(cur->type), path);
+		if (S_ISLNK(cur->type)) {
+			assert(cur->symlink != NULL);
+			printf(" -> %s", cur->symlink);
+		} else {
+			assert (cur->symlink == NULL);
+		}
+		if (cur->inode->nlink > 1)
+			printf(", nlinks=%d", cur->inode->nlink);
+		putchar('\n');
+
+		if (cur->child) {
+			assert (cur->type == S_IFDIR);
+			dump_fsnodes(cur->child);
+		}
+	}
+	printf("dump_fsnodes: finished %s/%s\n", root->path, root->name);
+}
+
+
+/*
+ * inode_type --
+ *	for a given inode type `mode', return a descriptive string.
+ *	for most cases, uses inotype() from mtree/misc.c
+ */
+const char *
+inode_type(mode_t mode)
+{
+	if (S_ISREG(mode))
+		return ("file");
+	if (S_ISLNK(mode))
+		return ("symlink");
+	if (S_ISDIR(mode))
+		return ("dir");
+	if (S_ISLNK(mode))
+		return ("link");
+	if (S_ISFIFO(mode))
+		return ("fifo");
+	if (S_ISSOCK(mode))
+		return ("socket");
+	/* XXX should not happen but handle them */
+	if (S_ISCHR(mode))
+		return ("char");
+	if (S_ISBLK(mode))
+		return ("block");
+	return ("unknown");
+}
+
+
+/*
+ * link_check --
+ *	return pointer to fsinode matching `entry's st_ino & st_dev if it exists,
+ *	otherwise add `entry' to table and return NULL
+ */
+/* This was borrowed from du.c and tweaked to keep an fsnode 
+ * pointer instead. -- dbj@netbsd.org
+ */
+fsinode *
+link_check(fsinode *entry)
+{
+	static struct entry {
+		fsinode *data;
+	} *htable;
+	static int htshift;  /* log(allocated size) */
+	static int htmask;   /* allocated size - 1 */
+	static int htused;   /* 2*number of insertions */
+	int h, h2;
+	uint64_t tmp;
+	/* this constant is (1<<64)/((1+sqrt(5))/2)
+	 * aka (word size)/(golden ratio)
+	 */
+	const uint64_t HTCONST = 11400714819323198485ULL;
+	const int HTBITS = 64;
+	
+	/* Never store zero in hashtable */
+	assert(entry);
+
+	/* Extend hash table if necessary, keep load under 0.5 */
+	if (htused<<1 >= htmask) {
+		struct entry *ohtable;
+
+		if (!htable)
+			htshift = 10;   /* starting hashtable size */
+		else
+			htshift++;   /* exponential hashtable growth */
+
+		htmask  = (1 << htshift) - 1;
+		htused = 0;
+
+		ohtable = htable;
+		htable = ecalloc(htmask+1, sizeof(*htable));
+		/* populate newly allocated hashtable */
+		if (ohtable) {
+			int i;
+			for (i = 0; i <= htmask>>1; i++)
+				if (ohtable[i].data)
+					link_check(ohtable[i].data);
+			free(ohtable);
+		}
+	}
+
+	/* multiplicative hashing */
+	tmp = entry->st.st_dev;
+	tmp <<= HTBITS>>1;
+	tmp |=  entry->st.st_ino;
+	tmp *= HTCONST;
+	h  = tmp >> (HTBITS - htshift);
+	h2 = 1 | ( tmp >> (HTBITS - (htshift<<1) - 1)); /* must be odd */
+	
+	/* open address hashtable search with double hash probing */
+	while (htable[h].data) {
+		if ((htable[h].data->st.st_ino == entry->st.st_ino) &&
+		    (htable[h].data->st.st_dev == entry->st.st_dev)) {
+			return htable[h].data;
+		}
+		h = (h + h2) & htmask;
+	}
+
+	/* Insert the current entry into hashtable */
+	htable[h].data = entry;
+	htused++;
+	return NULL;
+}
-- 
2.19.2

