From c656dfd01e8cfb6322717fca3c976f565648fc56 Mon Sep 17 00:00:00 2001
From: Sascha Wildner <saw@online.de>
Date: Tue, 3 Jul 2018 23:00:15 +0200
Subject: [PATCH] WIP mtree: Sync with NetBSD 20180703

---
 usr.sbin/mtree/Makefile     |  19 +-
 usr.sbin/mtree/compare.c    | 599 ++++++++++++++++--------
 usr.sbin/mtree/crc.c        | 163 +++++++
 usr.sbin/mtree/create.c     | 525 ++++++++++++---------
 usr.sbin/mtree/excludes.c   |  66 +--
 usr.sbin/mtree/extern.h     |  72 ++-
 usr.sbin/mtree/getid.c      | 446 ++++++++++++++++++
 usr.sbin/mtree/misc.c       | 255 +++++++++--
 usr.sbin/mtree/mtree.8      | 819 +++++++++++++++++++++++++--------
 usr.sbin/mtree/mtree.c      | 244 ++++++++--
 usr.sbin/mtree/mtree.h      | 128 ++++--
 usr.sbin/mtree/only.c       | 153 +++++++
 usr.sbin/mtree/pack_dev.c   | 290 ++++++++++++
 usr.sbin/mtree/pack_dev.h   |  47 ++
 usr.sbin/mtree/spec.c       | 886 +++++++++++++++++++++++++++++-------
 usr.sbin/mtree/specspec.c   | 273 +++++++++++
 usr.sbin/mtree/stat_flags.c | 187 ++++++++
 usr.sbin/mtree/verify.c     | 214 ++++++---
 18 files changed, 4403 insertions(+), 983 deletions(-)
 create mode 100644 usr.sbin/mtree/crc.c
 create mode 100644 usr.sbin/mtree/getid.c
 create mode 100644 usr.sbin/mtree/only.c
 create mode 100644 usr.sbin/mtree/pack_dev.c
 create mode 100644 usr.sbin/mtree/pack_dev.h
 create mode 100644 usr.sbin/mtree/specspec.c
 create mode 100644 usr.sbin/mtree/stat_flags.c

diff --git a/usr.sbin/mtree/Makefile b/usr.sbin/mtree/Makefile
index 0c352936d8..3feb4a77d5 100644
--- a/usr.sbin/mtree/Makefile
+++ b/usr.sbin/mtree/Makefile
@@ -1,20 +1,25 @@
-#	From: @(#)Makefile	8.1 (Berkeley) 6/6/93
-# $FreeBSD: src/usr.sbin/mtree/Makefile,v 1.15.2.5 2001/09/24 15:44:22 ru Exp $
+#	$NetBSD: Makefile,v 1.34 2013/02/03 19:15:16 christos Exp $
+#	from: @(#)Makefile	8.2 (Berkeley) 4/27/95
 
 .PATH: ${.CURDIR}/../../usr.bin/cksum
 .PATH: ${.CURDIR}/../../sbin/md5
 
 PROG=	mtree
 MAN=	mtree.8
-SRCS=	compare.c crc.c create.c excludes.c misc.c mtree.c spec.c verify.c
+SRCS=	compare.c crc.c create.c excludes.c misc.c mtree.c spec.c specspec.c \
+	verify.c getid.c only.c
+SRCS+=	pack_dev.c stat_flags.c
 
-.if !defined(BOOTSTRAPPING)
 SRCS+=	sha1hl.c
 CFLAGS+= -DUSE_MD5 -DUSE_SHA1 -DUSE_RMD160
-DPADD=  ${LIBMD} ${LIBCRYPTO}
-LDADD=  -lmd -lprivate_crypto
+DPADD=  ${LIBMD} ${LIBCRYPTO} ${LIBUTIL}
+LDADD=  -lmd -lprivate_crypto -lutil
 LDFLAGS+=	${PRIVATELIB_LDFLAGS}
 CFLAGS+= 	-I${.CURDIR}/../../crypto/libressl/include
-.endif
+
+CFLAGS+=-I${_SHLIBDIRPREFIX}/usr/include/priv -I${.CURDIR}/../../sbin/md5
+CFLAGS+=-DNO_RMD160
+
+#CFLAGS+=-DDEBUG
 
 .include <bsd.prog.mk>
diff --git a/usr.sbin/mtree/compare.c b/usr.sbin/mtree/compare.c
index 28ac909a47..b42f04443e 100644
--- a/usr.sbin/mtree/compare.c
+++ b/usr.sbin/mtree/compare.c
@@ -1,3 +1,5 @@
+/*	$NetBSD: compare.c,v 1.58 2013/11/21 18:39:50 christos Exp $	*/
+
 /*-
  * Copyright (c) 1989, 1993
  *	The Regents of the University of California.  All rights reserved.
@@ -25,51 +27,124 @@
  * 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.
- *
- * @(#)compare.c	8.1 (Berkeley) 6/6/93
- * $FreeBSD: src/usr.sbin/mtree/compare.c,v 1.15.2.4 2003/05/07 17:55:17 tobez Exp $
  */
 
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)compare.c	8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: compare.c,v 1.58 2013/11/21 18:39:50 christos Exp $");
+#endif
+#endif /* not lint */
+
 #include <sys/param.h>
 #include <sys/stat.h>
-#include <err.h>
+
 #include <errno.h>
 #include <fcntl.h>
-#include <fts.h>
-#ifdef USE_MD5
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifndef NO_MD5
 #include <md5.h>
 #endif
-#ifdef USE_SHA1
+#ifndef NO_RMD160
+#include <rmd160.h>
+#endif
+#ifndef NO_SHA1
 #include <sha.h>
-#include "../../sbin/md5/sha1hl.h"
+#include <sha1hl.h>
 #endif
-#ifdef USE_RMD160
-#include <ripemd.h>
+#ifndef NO_SHA2
+#include <sha256.h>
+#include <sha512.h>
 #endif
-#include <stdio.h>
-#include <time.h>
-#include <unistd.h>
-#include "mtree.h"
-#include "extern.h"
 
-static const char *ftype(u_int);
+#include "extern.h"
 
 #define	INDENTNAMELEN	8
-#define	LABEL \
-	if (!label++) { \
-		len = printf("%s changed\n", RP(p)); \
-		tab = "\t"; \
+#define MARK								\
+do {									\
+	if (flavor == F_FREEBSD9) {					\
+		len = printf("%s changed\n", RP(p));			\
+		tab = "\t";						\
+	} else {							\
+		len = printf("%s: ", RP(p));				\
+		if (len > INDENTNAMELEN) {				\
+			tab = "\t";					\
+			printf("\n");					\
+		} else {						\
+			tab = "";					\
+			printf("%*s", INDENTNAMELEN - (int)len, "");	\
+		}							\
+	}								\
+} while (0)
+#define	LABEL if (!label++) MARK
+
+#if HAVE_STRUCT_STAT_ST_FLAGS
+
+
+#define CHANGEFLAGS							\
+	if (flags != p->fts_statp->st_flags) {				\
+		char *sf;						\
+		if (!label) {						\
+			MARK;						\
+			sf = flags_to_string(p->fts_statp->st_flags, "none"); \
+			printf("%sflags (\"%s\"", tab, sf);		\
+			free(sf);					\
+		}							\
+		if (lchflags(p->fts_accpath, flags)) {			\
+			label++;					\
+			printf(", not modified: %s)\n",			\
+			    strerror(errno));				\
+		} else {						\
+			sf = flags_to_string(flags, "none");		\
+			printf(", modified to \"%s\")\n", sf);		\
+			free(sf);					\
+		}							\
 	}
 
+/* SETFLAGS:
+ * given pflags, additionally set those flags specified in s->st_flags and
+ * selected by mask (the other flags are left unchanged).
+ */
+#define SETFLAGS(pflags, mask)						\
+do {									\
+	flags = (s->st_flags & (mask)) | (pflags);			\
+	CHANGEFLAGS;							\
+} while (0)
+
+/* CLEARFLAGS:
+ * given pflags, reset the flags specified in s->st_flags and selected by mask
+ * (the other flags are left unchanged).
+ */
+#define CLEARFLAGS(pflags, mask)					\
+do {									\
+	flags = (~(s->st_flags & (mask)) & CH_MASK) & (pflags);		\
+	CHANGEFLAGS;							\
+} while (0)
+#endif	/* HAVE_STRUCT_STAT_ST_FLAGS */
+
 int
 compare(NODE *s, FTSENT *p)
 {
-	uint32_t val;
-	off_t len;
+	u_int32_t len, val, flags;
 	int fd, label;
-	char *cp, *fflags;
-	const char *tab = "";
+	const char *cp, *tab;
+#if !defined(NO_MD5) || !defined(NO_RMD160) || !defined(NO_SHA1) || !defined(NO_SHA2)
+	char *digestbuf;
+#endif
 
+	tab = NULL;
 	label = 0;
 	switch(s->type) {
 	case F_BLOCK:
@@ -96,88 +171,237 @@ compare(NODE *s, FTSENT *p)
 		if (!S_ISLNK(p->fts_statp->st_mode))
 			goto typeerr;
 		break;
+#ifdef S_ISSOCK
 	case F_SOCK:
-		if (!S_ISSOCK(p->fts_statp->st_mode)) {
-typeerr:		LABEL;
-			printf("\ttype expected %s found %s\n",
-			    ftype(s->type), inotype(p->fts_statp->st_mode));
-			return (label);
-		}
+		if (!S_ISSOCK(p->fts_statp->st_mode))
+			goto typeerr;
 		break;
+#endif
+typeerr:		LABEL;
+		printf(flavor == F_FREEBSD9 ?
+		    "\ttype expected %s found %s\n" : "\ttype (%s, %s)\n",
+		    nodetype(s->type), inotype(p->fts_statp->st_mode));
+		return (label);
+	}
+	if (mtree_Wflag)
+		goto afterpermwhack;
+#if HAVE_STRUCT_STAT_ST_FLAGS
+	if (iflag && !uflag) {
+		if (s->flags & F_FLAGS)
+		    SETFLAGS(p->fts_statp->st_flags, SP_FLGS);
+		return (label);
+        }
+	if (mflag && !uflag) {
+		if (s->flags & F_FLAGS)
+		    CLEARFLAGS(p->fts_statp->st_flags, SP_FLGS);
+		return (label);
+        }
+#endif
+	if (s->flags & F_DEV &&
+	    (s->type == F_BLOCK || s->type == F_CHAR) &&
+	    s->st_rdev != p->fts_statp->st_rdev) {
+		LABEL;
+		printf(flavor == F_FREEBSD9 ?
+		    "%sdevice expected %#jx found %#jx" :
+		    "%sdevice (%#jx, %#jx",
+		    tab, (uintmax_t)s->st_rdev,
+		    (uintmax_t)p->fts_statp->st_rdev);
+		if (uflag) {
+			if ((unlink(p->fts_accpath) == -1) ||
+			    (mknod(p->fts_accpath,
+			      s->st_mode | nodetoino(s->type),
+			      s->st_rdev) == -1) ||
+			    (lchown(p->fts_accpath, p->fts_statp->st_uid,
+			      p->fts_statp->st_gid) == -1) )
+				printf(", not modified: %s%s\n",
+				    strerror(errno),
+				    flavor == F_FREEBSD9 ? "" : ")");
+			 else
+				printf(", modified%s\n",
+				    flavor == F_FREEBSD9 ? "" : ")");
+		} else
+			printf(")\n");
+		tab = "\t";
 	}
 	/* Set the uid/gid first, then set the mode. */
 	if (s->flags & (F_UID | F_UNAME) && s->st_uid != p->fts_statp->st_uid) {
 		LABEL;
-		printf("%suser expected %lu found %lu",
+		printf(flavor == F_FREEBSD9 ?
+		    "%suser expected %lu found %lu" : "%suser (%lu, %lu",
 		    tab, (u_long)s->st_uid, (u_long)p->fts_statp->st_uid);
-		if (uflag)
-			if (chown(p->fts_accpath, s->st_uid, -1))
-				printf(" not modified: %s\n",
-				    strerror(errno));
+		if (uflag) {
+			if (lchown(p->fts_accpath, s->st_uid, -1))
+				printf(", not modified: %s%s\n",
+				    strerror(errno),
+				    flavor == F_FREEBSD9 ? "" : ")");
 			else
-				printf(" modified\n");
-		else
-			printf("\n");
+				printf(", modified%s\n",
+				    flavor == F_FREEBSD9 ? "" : ")");
+		} else
+			printf(")\n");
 		tab = "\t";
 	}
 	if (s->flags & (F_GID | F_GNAME) && s->st_gid != p->fts_statp->st_gid) {
 		LABEL;
-		printf("%sgid expected %lu found %lu",
+		printf(flavor == F_FREEBSD9 ?
+		    "%sgid expected %lu found %lu" : "%sgid (%lu, %lu",
 		    tab, (u_long)s->st_gid, (u_long)p->fts_statp->st_gid);
-		if (uflag)
-			if (chown(p->fts_accpath, -1, s->st_gid))
-				printf(" not modified: %s\n",
-				    strerror(errno));
+		if (uflag) {
+			if (lchown(p->fts_accpath, -1, s->st_gid))
+				printf(", not modified: %s%s\n",
+				    strerror(errno),
+				    flavor == F_FREEBSD9 ? "" : ")");
 			else
-				printf(" modified\n");
+				printf(", modified%s\n",
+				    flavor == F_FREEBSD9 ? "" : ")");
+		}
 		else
-			printf("\n");
+			printf(")\n");
 		tab = "\t";
 	}
 	if (s->flags & F_MODE &&
-	    !S_ISLNK(p->fts_statp->st_mode) &&
 	    s->st_mode != (p->fts_statp->st_mode & MBITS)) {
+		if (lflag) {
+			mode_t tmode, mode;
+
+			tmode = s->st_mode;
+			mode = p->fts_statp->st_mode & MBITS;
+			/*
+			 * if none of the suid/sgid/etc bits are set,
+			 * then if the mode is a subset of the target,
+			 * skip.
+			 */
+			if (!((tmode & ~(S_IRWXU|S_IRWXG|S_IRWXO)) ||
+			    (mode & ~(S_IRWXU|S_IRWXG|S_IRWXO))))
+				if ((mode | tmode) == tmode)
+					goto skip;
+		}
+
 		LABEL;
-		printf("%spermissions expected %#o found %#o",
-		    tab, s->st_mode, p->fts_statp->st_mode & MBITS);
-		if (uflag)
-			if (chmod(p->fts_accpath, s->st_mode))
-				printf(" not modified: %s\n",
-				    strerror(errno));
+		printf(flavor == F_FREEBSD9 ?
+		    "%spermissions expcted %#lo found %#lo" :
+		    "%spermissions (%#lo, %#lo",
+		    tab, (u_long)s->st_mode,
+		    (u_long)p->fts_statp->st_mode & MBITS);
+		if (uflag) {
+			if (lchmod(p->fts_accpath, s->st_mode))
+				printf(", not modified: %s%s\n",
+				    strerror(errno),
+				    flavor == F_FREEBSD9 ? "" : ")");
 			else
-				printf(" modified\n");
+				printf(", modified%s\n",
+				    flavor == F_FREEBSD9 ? "" : ")");
+		}
 		else
-			printf("\n");
+			printf(")\n");
 		tab = "\t";
+	skip:	;
 	}
 	if (s->flags & F_NLINK && s->type != F_DIR &&
 	    s->st_nlink != p->fts_statp->st_nlink) {
 		LABEL;
-		printf("%slink_count expected %u found %u\n",
-		    tab, s->st_nlink, p->fts_statp->st_nlink);
+		printf(flavor == F_FREEBSD9 ?
+		    "%slink count expected %lu found %lu\n" :
+		    "%slink count (%lu, %lu)\n",
+		    tab, (u_long)s->st_nlink, (u_long)p->fts_statp->st_nlink);
 		tab = "\t";
 	}
-	if (s->flags & F_SIZE && s->st_size != p->fts_statp->st_size &&
-		!S_ISDIR(p->fts_statp->st_mode)) {
+	if (s->flags & F_SIZE && s->st_size != p->fts_statp->st_size) {
 		LABEL;
-		printf("%ssize expected %jd found %jd\n", tab,
-		    (intmax_t)s->st_size, (intmax_t)p->fts_statp->st_size);
+		printf(flavor == F_FREEBSD9 ?
+		    "%ssize expected %ju found %ju\n" : "%ssize (%ju, %ju)\n",
+		    tab, (uintmax_t)s->st_size,
+		    (uintmax_t)p->fts_statp->st_size);
 		tab = "\t";
 	}
 	/*
 	 * XXX
-	 * Catches nano-second differences, but doesn't display them.
+	 * Since utimes(2) only takes a timeval, there's no point in
+	 * comparing the low bits of the timespec nanosecond field.  This
+	 * will only result in mismatches that we can never fix.
+	 *
+	 * Doesn't display microsecond differences.
 	 */
-	if ((s->flags & F_TIME) &&
-	     ((s->st_mtimespec.tv_sec != p->fts_statp->st_mtimespec.tv_sec) ||
-	     (s->st_mtimespec.tv_nsec != p->fts_statp->st_mtimespec.tv_nsec))) {
-		LABEL;
-		printf("%smodification time expected %.24s ",
-		    tab, ctime(&s->st_mtimespec.tv_sec));
-		printf("found %.24s\n",
-		    ctime(&p->fts_statp->st_mtimespec.tv_sec));
+	if (s->flags & F_TIME) {
+		struct timeval tv[2];
+		struct stat *ps = p->fts_statp;
+		time_t smtime = s->st_mtimespec.tv_sec;
+
+#if defined(BSD4_4) && !defined(HAVE_NBTOOL_CONFIG_H)
+		time_t pmtime = ps->st_mtimespec.tv_sec;
+
+		TIMESPEC_TO_TIMEVAL(&tv[0], &s->st_mtimespec);
+		TIMESPEC_TO_TIMEVAL(&tv[1], &ps->st_mtimespec);
+#else
+		time_t pmtime = (time_t)ps->st_mtime;
+
+		tv[0].tv_sec = smtime;
+		tv[0].tv_usec = 0;
+		tv[1].tv_sec = pmtime;
+		tv[1].tv_usec = 0;
+#endif
+
+		if (tv[0].tv_sec != tv[1].tv_sec ||
+		    tv[0].tv_usec != tv[1].tv_usec) {
+			LABEL;
+			printf(flavor == F_FREEBSD9 ?
+			    "%smodification time expected %.24s found " :
+			    "%smodification time (%.24s, ",
+			    tab, ctime(&smtime));
+			printf("%.24s", ctime(&pmtime));
+			if (tflag) {
+				tv[1] = tv[0];
+				if (utimes(p->fts_accpath, tv))
+					printf(", not modified: %s%s\n",
+					    strerror(errno),
+					    flavor == F_FREEBSD9 ? "" : ")");
+				else
+					printf(", modified%s\n",
+					    flavor == F_FREEBSD9 ? "" : ")");
+			} else
+				printf("%s\n", flavor == F_FREEBSD9 ? "" : ")");
+			tab = "\t";
+		}
+	}
+#if HAVE_STRUCT_STAT_ST_FLAGS
+	/*
+	 * XXX
+	 * since lchflags(2) will reset file times, the utimes() above
+	 * may have been useless!  oh well, we'd rather have correct
+	 * flags, rather than times?
+	 */
+        if ((s->flags & F_FLAGS) && ((s->st_flags != p->fts_statp->st_flags)
+	    || mflag || iflag)) {
+		if (s->st_flags != p->fts_statp->st_flags) {
+			char *f_s;
+			LABEL;
+			f_s = flags_to_string(s->st_flags, "none");
+			printf(flavor == F_FREEBSD9 ?
+			    "%sflags expected \"%s\" found " :
+			    "%sflags (\"%s\" is not ", tab, f_s);
+			free(f_s);
+			f_s = flags_to_string(p->fts_statp->st_flags, "none");
+			printf("\"%s\"", f_s);
+			free(f_s);
+		}
+		if (uflag) {
+			if (iflag)
+				SETFLAGS(0, CH_MASK);
+			else if (mflag)
+				CLEARFLAGS(0, SP_FLGS);
+			else
+				SETFLAGS(0, (~SP_FLGS & CH_MASK));
+		} else
+			printf("%s\n", flavor == F_FREEBSD9 ? "" : ")");
 		tab = "\t";
 	}
+#endif	/* HAVE_STRUCT_STAT_ST_FLAGS */
+
+	/*
+	 * from this point, no more permission checking or whacking
+	 * occurs, only checking of stuff like checksums and symlinks.
+	 */
+ afterpermwhack:
 	if (s->flags & F_CKSUM) {
 		if ((fd = open(p->fts_accpath, O_RDONLY, 0)) < 0) {
 			LABEL;
@@ -194,158 +418,163 @@ typeerr:		LABEL;
 			close(fd);
 			if (s->cksum != val) {
 				LABEL;
-				printf("%scksum expected %lu found %u\n",
-				    tab, s->cksum, val);
-				tab = "\t";
+				printf(flavor == F_FREEBSD9 ?
+				    "%scksum expected %lu found %lu\n" :
+				    "%scksum (%lu, %lu)\n",
+				    tab, s->cksum, (unsigned long)val);
 			}
+			tab = "\t";
 		}
 	}
-	/*
-	 * XXX
-	 * since chflags(2) will reset file times, the utimes() above
-	 * may have been useless!  oh well, we'd rather have correct
-	 * flags, rather than times?
-	 */
-	if ((s->flags & F_FLAGS) && s->st_flags != p->fts_statp->st_flags) {
-		LABEL;
-		fflags = flags_to_string(s->st_flags);
-		printf("%sflags expected \"%s\"", tab, fflags);
-		free(fflags);
-
-		fflags = flags_to_string(p->fts_statp->st_flags);
-		printf(" found \"%s\"", fflags);
-		free(fflags);
-
-		if (uflag)
-			if (chflags(p->fts_accpath, s->st_flags))
-				printf(" not modified: %s\n",
-				    strerror(errno));
-			else
-				printf(" modified\n");
-		else
-			printf("\n");
-		tab = "\t";
-	}
-#ifdef USE_MD5
+#ifndef NO_MD5
 	if (s->flags & F_MD5) {
-		char *new_digest, buf[33];
-
-		new_digest = MD5File(p->fts_accpath, buf);
-		if (!new_digest) {
+		if ((digestbuf = MD5File(p->fts_accpath, NULL)) == NULL) {
 			LABEL;
-			printf("%sMD5: %s: %s\n", tab, p->fts_accpath,
-			       strerror(errno));
+			printf("%s%s: %s: %s\n",
+			    tab, MD5KEY, p->fts_accpath, strerror(errno));
+			tab = "\t";
+		} else {
+			if (strcmp(s->md5digest, digestbuf)) {
+				LABEL;
+				printf(flavor == F_FREEBSD9 ?
+				    "%s%s expected %s found %s\n" :
+				    "%s%s (0x%s, 0x%s)\n",
+				    tab, MD5KEY, s->md5digest, digestbuf);
+			}
 			tab = "\t";
-		} else if (strcmp(new_digest, s->md5digest)) {
+			free(digestbuf);
+		}
+	}
+#endif	/* ! NO_MD5 */
+#ifndef NO_RMD160
+	if (s->flags & F_RMD160) {
+		if ((digestbuf = RMD160_File(p->fts_accpath, NULL)) == NULL) {
 			LABEL;
-			printf("%sMD5 expected %s found %s\n", tab, s->md5digest,
-			       new_digest);
+			printf("%s%s: %s: %s\n",
+			    tab, RMD160KEY, p->fts_accpath, strerror(errno));
+			tab = "\t";
+		} else {
+			if (strcmp(s->rmd160digest, digestbuf)) {
+				LABEL;
+				printf(flavor == F_FREEBSD9 ?
+				    "%s%s expected %s found %s\n" :
+				    "%s%s (0x%s, 0x%s)\n",
+				    tab, RMD160KEY, s->rmd160digest, digestbuf);
+			}
 			tab = "\t";
+			free(digestbuf);
 		}
 	}
-#endif /* MD5 */
-#ifdef USE_SHA1
+#endif	/* ! NO_RMD160 */
+#ifndef NO_SHA1
 	if (s->flags & F_SHA1) {
-		char *new_digest, buf[41];
-
-		new_digest = SHA1_File(p->fts_accpath, buf);
-		if (!new_digest) {
+		if ((digestbuf = SHA1_File(p->fts_accpath, NULL)) == NULL) {
 			LABEL;
-			printf("%sSHA-1: %s: %s\n", tab, p->fts_accpath,
-			       strerror(errno));
+			printf("%s%s: %s: %s\n",
+			    tab, SHA1KEY, p->fts_accpath, strerror(errno));
+			tab = "\t";
+		} else {
+			if (strcmp(s->sha1digest, digestbuf)) {
+				LABEL;
+				printf(flavor == F_FREEBSD9 ? 
+				    "%s%s expected %s found %s\n" :
+				    "%s%s (0x%s, 0x%s)\n",
+				    tab, SHA1KEY, s->sha1digest, digestbuf);
+			}
 			tab = "\t";
-		} else if (strcmp(new_digest, s->sha1digest)) {
+			free(digestbuf);
+		}
+	}
+#endif	/* ! NO_SHA1 */
+#ifndef NO_SHA2
+	if (s->flags & F_SHA256) {
+		if ((digestbuf = SHA256_File(p->fts_accpath, NULL)) == NULL) {
 			LABEL;
-			printf("%sSHA-1 expected %s found %s\n", 
-			       tab, s->sha1digest, new_digest);
+			printf("%s%s: %s: %s\n",
+			    tab, SHA256KEY, p->fts_accpath, strerror(errno));
+			tab = "\t";
+		} else {
+			if (strcmp(s->sha256digest, digestbuf)) {
+				LABEL;
+				printf(flavor == F_FREEBSD9 ? 
+				    "%s%s expected %s found %s\n" :
+				    "%s%s (0x%s, 0x%s)\n",
+				    tab, SHA256KEY, s->sha256digest, digestbuf);
+			}
 			tab = "\t";
+			free(digestbuf);
 		}
 	}
-#endif /* SHA1 */
-#ifdef USE_RMD160
-	if (s->flags & F_RMD160) {
-		char *new_digest, buf[41];
-
-		new_digest = RIPEMD160_File(p->fts_accpath, buf);
-		if (!new_digest) {
+#if 0 /* XXX __DragonFly__ */
+#ifdef SHA384_BLOCK_LENGTH
+	if (s->flags & F_SHA384) {
+		if ((digestbuf = SHA384_File(p->fts_accpath, NULL)) == NULL) {
 			LABEL;
-			printf("%sRIPEMD160: %s: %s\n", tab,
-			       p->fts_accpath, strerror(errno));
+			printf("%s%s: %s: %s\n",
+			    tab, SHA384KEY, p->fts_accpath, strerror(errno));
+			tab = "\t";
+		} else {
+			if (strcmp(s->sha384digest, digestbuf)) {
+				LABEL;
+				printf(flavor == F_FREEBSD9 ? 
+				    "%s%s expected %s found %s\n" :
+				    "%s%s (0x%s, 0x%s)\n",
+				    tab, SHA384KEY, s->sha384digest, digestbuf);
+			}
 			tab = "\t";
-		} else if (strcmp(new_digest, s->rmd160digest)) {
+			free(digestbuf);
+		}
+	}
+#endif
+#endif
+	if (s->flags & F_SHA512) {
+		if ((digestbuf = SHA512_File(p->fts_accpath, NULL)) == NULL) {
 			LABEL;
-			printf("%sRIPEMD160 expected %s found %s\n",
-			       tab, s->rmd160digest, new_digest);
+			printf("%s%s: %s: %s\n",
+			    tab, SHA512KEY, p->fts_accpath, strerror(errno));
+			tab = "\t";
+		} else {
+			if (strcmp(s->sha512digest, digestbuf)) {
+				LABEL;
+				printf(flavor == F_FREEBSD9 ? 
+				    "%s%s expected %s found %s\n" :
+				    "%s%s (0x%s, 0x%s)\n",
+				    tab, SHA512KEY, s->sha512digest, digestbuf);
+			}
 			tab = "\t";
+			free(digestbuf);
 		}
 	}
-#endif /* RMD160 */
-
+#endif	/* ! NO_SHA2 */
 	if (s->flags & F_SLINK &&
 	    strcmp(cp = rlink(p->fts_accpath), s->slink)) {
 		LABEL;
-		printf("%slink_ref expected %s found %s\n",
-		      tab, s->slink, cp);
+		printf(flavor == F_FREEBSD9 ? 
+		    "%slink ref expected %s found %s" :
+		    "%slink ref (%s, %s", tab, cp, s->slink);
+		if (uflag) {
+			if ((unlink(p->fts_accpath) == -1) ||
+			    (symlink(s->slink, p->fts_accpath) == -1) )
+				printf(", not modified: %s%s\n",
+				    strerror(errno),
+				    flavor == F_FREEBSD9 ? "" : ")");
+			else
+				printf(", modified%s\n",
+				    flavor == F_FREEBSD9 ? "" : ")");
+		} else
+			printf("%s\n", flavor == F_FREEBSD9 ? "" : ")");
 	}
 	return (label);
 }
 
 const char *
-inotype(u_int type)
-{
-	switch(type & S_IFMT) {
-	case S_IFBLK:
-		return ("block");
-	case S_IFCHR:
-		return ("char");
-	case S_IFDIR:
-		return ("dir");
-	case S_IFIFO:
-		return ("fifo");
-	case S_IFREG:
-		return ("file");
-	case S_IFLNK:
-		return ("link");
-	case S_IFSOCK:
-		return ("socket");
-	default:
-		return ("unknown");
-	}
-	/* NOTREACHED */
-}
-
-static const char *
-ftype(u_int type)
-{
-	switch(type) {
-	case F_BLOCK:
-		return ("block");
-	case F_CHAR:
-		return ("char");
-	case F_DIR:
-		return ("dir");
-	case F_FIFO:
-		return ("fifo");
-	case F_FILE:
-		return ("file");
-	case F_LINK:
-		return ("link");
-	case F_SOCK:
-		return ("socket");
-	default:
-		return ("unknown");
-	}
-	/* NOTREACHED */
-}
-
-char *
-rlink(char *name)
+rlink(const char *name)
 {
 	static char lbuf[MAXPATHLEN];
 	int len;
 
 	if ((len = readlink(name, lbuf, sizeof(lbuf) - 1)) == -1)
-		err(1, "line %d: %s", lineno, name);
+		mtree_err("%s: %s", name, strerror(errno));
 	lbuf[len] = '\0';
 	return (lbuf);
 }
diff --git a/usr.sbin/mtree/crc.c b/usr.sbin/mtree/crc.c
new file mode 100644
index 0000000000..4eac66e5b9
--- /dev/null
+++ b/usr.sbin/mtree/crc.c
@@ -0,0 +1,163 @@
+/*	$NetBSD: crc.c,v 1.9 2012/10/05 00:40:51 christos Exp $	*/
+
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * James W. Williams of NASA Goddard Space Flight Center.
+ *
+ * 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.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)crc.c	8.1 (Berkeley) 6/17/93";
+#else
+__RCSID("$NetBSD: crc.c,v 1.9 2012/10/05 00:40:51 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+static const u_int32_t crctab[] = {
+	0x0,
+	0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
+	0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6,
+	0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
+	0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac,
+	0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f,
+	0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a,
+	0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
+	0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58,
+	0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033,
+	0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe,
+	0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
+	0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4,
+	0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
+	0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5,
+	0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
+	0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07,
+	0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c,
+	0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1,
+	0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
+	0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b,
+	0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698,
+	0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d,
+	0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
+	0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f,
+	0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
+	0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80,
+	0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
+	0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a,
+	0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629,
+	0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c,
+	0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
+	0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e,
+	0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65,
+	0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8,
+	0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
+	0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2,
+	0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
+	0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74,
+	0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
+	0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21,
+	0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a,
+	0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087,
+	0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
+	0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d,
+	0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce,
+	0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb,
+	0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
+	0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09,
+	0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
+	0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf,
+	0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
+};
+
+/*
+ * Compute a POSIX 1003.2 checksum.  This routine has been broken out so that
+ * other programs can use it.  It takes a file descriptor to read from and
+ * locations to store the crc and the number of bytes read.  It returns 0 on
+ * success and 1 on failure.  Errno is set on failure.
+ */
+u_int32_t crc_total = ~0;		/* The crc over a number of files. */
+
+int
+crc(int fd, u_int32_t *cval, u_int32_t *clen)
+{
+	u_char *p;
+	int nr;
+	u_int32_t thecrc, len;
+	u_int32_t crctot;
+	u_char buf[16 * 1024];
+
+#define	COMPUTE(var, ch)	(var) = (var) << 8 ^ crctab[(var) >> 24 ^ (ch)]
+
+	thecrc = len = crctot = 0;
+	if (sflag)
+		crctot = ~crc_total;
+	while ((nr = read(fd, buf, sizeof(buf))) > 0)
+		if (sflag) {
+			for (len += nr, p = buf; nr--; ++p) {
+				COMPUTE(thecrc, *p);
+				COMPUTE(crctot, *p);
+			}
+		} else {
+			for (len += nr, p = buf; nr--; ++p)
+				COMPUTE(thecrc, *p);
+		}
+	if (nr < 0)
+		return 1;
+
+	*clen = len;
+
+	/* Include the length of the file. */
+	if (sflag) {
+		for (; len != 0; len >>= 8) {
+			COMPUTE(thecrc, len & 0xff);
+			COMPUTE(crctot, len & 0xff);
+		}
+	} else {
+		for (; len != 0; len >>= 8)
+			COMPUTE(thecrc, len & 0xff);
+	}
+
+	*cval = ~thecrc;
+	if (sflag)
+		crc_total = ~crctot;
+	return 0;
+}
diff --git a/usr.sbin/mtree/create.c b/usr.sbin/mtree/create.c
index b6c0ab44bc..82427d6c11 100644
--- a/usr.sbin/mtree/create.c
+++ b/usr.sbin/mtree/create.c
@@ -1,3 +1,5 @@
+/*	$NetBSD: create.c,v 1.75 2017/12/31 03:04:44 christos Exp $	*/
+
 /*-
  * Copyright (c) 1989, 1993
  *	The Regents of the University of California.  All rights reserved.
@@ -25,35 +27,55 @@
  * 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.
- *
- * @(#)create.c	8.1 (Berkeley) 6/6/93
- * $FreeBSD: src/usr.sbin/mtree/create.c,v 1.18.2.3 2001/01/12 19:17:18 phk Exp $
  */
 
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)create.c	8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: create.c,v 1.75 2017/12/31 03:04:44 christos Exp $");
+#endif
+#endif /* not lint */
+
 #include <sys/param.h>
 #include <sys/stat.h>
+
+#if ! HAVE_NBTOOL_CONFIG_H
 #include <dirent.h>
-#include <err.h>
+#endif
+
 #include <errno.h>
 #include <fcntl.h>
-#include <fts.h>
 #include <grp.h>
-#ifdef MD5
+#include <pwd.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifndef NO_MD5
 #include <md5.h>
 #endif
-#ifdef SHA1
+#ifndef NO_RMD160
+#include <rmd160.h>
+#endif
+#ifndef NO_SHA1
 #include <sha.h>
+#include <sha1hl.h>
 #endif
-#ifdef RMD160
-#include <ripemd.h>
+#ifndef NO_SHA2
+#include <sha256.h>
+#include <sha512.h>
 #endif
-#include <pwd.h>
-#include <stdio.h>
-#include <time.h>
-#include <unistd.h>
-#include <stdarg.h>
-#include <vis.h>
-#include "mtree.h"
+
 #include "extern.h"
 
 #define	INDENTNAMELEN	15
@@ -62,310 +84,360 @@
 static gid_t gid;
 static uid_t uid;
 static mode_t mode;
-static u_long flags = 0xffffffff;
+static u_long flags;
 
-static int	dsort(const FTSENT * const *, const FTSENT * const *);
-static void	output(int, int *, const char *, ...) __printflike(3, 4);
-static int	statd(FTS *, FTSENT *, uid_t *, gid_t *, mode_t *,
-			   u_long *);
-static void	statf(int, FTSENT *);
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+#define	FTS_CONST const
+#else
+#define	FTS_CONST
+#endif
+
+static int	dcmp(const FTSENT *FTS_CONST *, const FTSENT *FTS_CONST *);
+static void	output(FILE *, int, int *, const char *, ...)
+    __printflike(4, 5);
+static int	statd(FILE *, FTS *, FTSENT *, uid_t *, gid_t *, mode_t *,
+    u_long *);
+static void	statf(FILE *, int, FTSENT *);
 
 void
-cwalk(void)
+cwalk(FILE *fp)
 {
 	FTS *t;
 	FTSENT *p;
-	time_t clk;
-	char *argv[2], host[MAXHOSTNAMELEN], dot[] = ".";
+	time_t clocktime;
+	char host[MAXHOSTNAMELEN + 1];
+	const char *user;
+	char *argv[2];
+	char  dot[] = ".";
 	int indent = 0;
 
-	time(&clk);
-	gethostname(host, sizeof(host));
-	printf(
-	    "#\t   user: %s\n#\tmachine: %s\n#\t   tree: %s\n#\t   date: %s",
-	    getlogin(), host, fullpath, ctime(&clk));
-
 	argv[0] = dot;
 	argv[1] = NULL;
-	if ((t = fts_open(argv, ftsoptions, dsort)) == NULL)
-		err(1, "line %d: fts_open", lineno);
-	while ((p = fts_read(t))) {
-		if (iflag)
+
+	time(&clocktime);
+	gethostname(host, sizeof(host));
+	host[sizeof(host) - 1] = '\0';
+	if ((user = getlogin()) == NULL) {
+		struct passwd *pw;
+		user = (pw = getpwuid(getuid())) == NULL ? pw->pw_name :
+		    "<unknown>";
+	}
+
+	if (!nflag)
+		fprintf(fp,
+	    	    "#\t   user: %s\n#\tmachine: %s\n#\t   tree: %s\n"
+		    "#\t   date: %s",
+		    user, host, fullpath, ctime(&clocktime));
+
+	if ((t = fts_open(argv, ftsoptions, dcmp)) == NULL)
+		mtree_err("fts_open: %s", strerror(errno));
+	while ((p = fts_read(t)) != NULL) {
+		if (jflag)
 			indent = p->fts_level * 4;
 		if (check_excludes(p->fts_name, p->fts_path)) {
 			fts_set(t, p, FTS_SKIP);
 			continue;
 		}
+		if (!find_only(p->fts_path)) {
+			fts_set(t, p, FTS_SKIP);
+			continue;
+		}
 		switch(p->fts_info) {
 		case FTS_D:
-			if (!dflag)
-				printf("\n");
+			if (!bflag)
+				fprintf(fp, "\n");
 			if (!nflag)
-				printf("# %s\n", p->fts_path);
-			statd(t, p, &uid, &gid, &mode, &flags);
-			statf(indent, p);
+				fprintf(fp, "# %s\n", p->fts_path);
+			statd(fp, t, p, &uid, &gid, &mode, &flags);
+			statf(fp, indent, p);
 			break;
 		case FTS_DP:
-			if (!nflag && (p->fts_level > 0))
-				printf("%*s# %s\n", indent, "", p->fts_path);
-			printf("%*s..\n", indent, "");
-			if (!dflag)
-				printf("\n");
+			if (p->fts_level > 0)
+				if (!nflag)
+					fprintf(fp, "%*s# %s\n", indent, "",
+					    p->fts_path);
+			if (p->fts_level > 0 || flavor == F_FREEBSD9) {
+				fprintf(fp, "%*s..\n", indent, "");
+				if (!bflag)
+					fprintf(fp, "\n");
+			}
 			break;
 		case FTS_DNR:
 		case FTS_ERR:
 		case FTS_NS:
-			warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
+			mtree_err("%s: %s",
+			    p->fts_path, strerror(p->fts_errno));
 			break;
 		default:
 			if (!dflag)
-				statf(indent, p);
+				statf(fp, indent, p);
 			break;
 
 		}
 	}
 	fts_close(t);
 	if (sflag && keys & F_CKSUM)
-		warnx("%s checksum: %u", fullpath, crc_total);
+		mtree_err("%s checksum: %u", fullpath, crc_total);
 }
 
 static void
-statf(int indent, FTSENT *p)
+dosum(FILE *fp, int indent, FTSENT *p, int *offset, int flag,
+    char * (*func)(const char *, char *), const char *key)
 {
-	struct group *gr;
-	struct passwd *pw;
-	uint32_t val;
-	off_t len;
-	int fd, offset;
-	char *fflags;
-	char *escaped_name;
-
-	escaped_name = calloc(1, p->fts_namelen * 4  +  1);
-	if (escaped_name == NULL)
-		errx(1, "statf(): calloc() failed");
-	strvis(escaped_name, p->fts_name, VIS_WHITE | VIS_OCTAL);
-
-	if (iflag || S_ISDIR(p->fts_statp->st_mode))
-		offset = printf("%*s%s", indent, "", escaped_name);
-	else
-		offset = printf("%*s    %s", indent, "", escaped_name);
-	
-	free(escaped_name);
+	char *digestbuf;
+
+	if ((keys & flag) == 0)
+		return;
+
+	digestbuf = (*func)(p->fts_accpath, NULL);
+	if (digestbuf != NULL) {
+		output(fp, indent, offset, "%s=%s", key, digestbuf);
+		free(digestbuf);
+		return;
+	}
+
+	if (qflag) {
+		warn("%s: %s failed", p->fts_path, key);
+		return;
+	}
+
+	mtree_err("%s: %s failed: %s", p->fts_path, key, strerror(errno));
+}
+
+static char *
+crcFile(const char *fname, char *dummy __unused)
+{
+	char *ptr;
+	uint32_t val, len;
+	int fd, e;
+
+	if ((fd = open(fname, O_RDONLY)) == -1)
+		goto out;
+
+	e = crc(fd, &val, &len);
+	close(fd);
+	if (e)
+		goto out;
+
+	if (asprintf(&ptr, "%u", val) < 0)
+		goto out;
+
+	return ptr;
+out:
+	mtree_err("%s: %s", fname, strerror(errno));
+	return NULL;
+}
+
+static void
+statf(FILE *fp, int indent, FTSENT *p)
+{
+	int offset;
+	const char *name = NULL;
+
+	offset = fprintf(fp, "%*s%s%s", indent, "",
+	    S_ISDIR(p->fts_statp->st_mode) ? "" : "    ", vispath(p->fts_name));
 
 	if (offset > (INDENTNAMELEN + indent))
 		offset = MAXLINELEN;
 	else
-		offset += printf("%*s", (INDENTNAMELEN + indent) - offset, "");
-
-	if (!S_ISREG(p->fts_statp->st_mode) && !dflag)
-		output(indent, &offset, "type=%s", inotype(p->fts_statp->st_mode));
-	if (p->fts_statp->st_uid != uid) {
-		if (keys & F_UNAME) {
-			if ((pw = getpwuid(p->fts_statp->st_uid)) != NULL) {
-				output(indent, &offset, "uname=%s", pw->pw_name);
-			} else {
-				errx(1,
-				"line %d: could not get uname for uid=%u",
-				lineno, p->fts_statp->st_uid);
-			}
-		}
-		if (keys & F_UID)
-			output(indent, &offset, "uid=%u", p->fts_statp->st_uid);
+		offset += fprintf(fp, "%*s",
+		    (INDENTNAMELEN + indent) - offset, "");
+
+	if (!S_ISREG(p->fts_statp->st_mode) && (flavor == F_NETBSD6 || !dflag))
+		output(fp, indent, &offset, "type=%s",
+		    inotype(p->fts_statp->st_mode));
+	if (keys & (F_UID | F_UNAME) && p->fts_statp->st_uid != uid) {
+		if (keys & F_UNAME &&
+		    (name = user_from_uid(p->fts_statp->st_uid, 1)) != NULL)
+			output(fp, indent, &offset, "uname=%s", name);
+		if (keys & F_UID || (keys & F_UNAME && name == NULL))
+			output(fp, indent, &offset, "uid=%u",
+			    p->fts_statp->st_uid);
 	}
-	if (p->fts_statp->st_gid != gid) {
-		if (keys & F_GNAME) {
-			if ((gr = getgrgid(p->fts_statp->st_gid)) != NULL) {
-				output(indent, &offset, "gname=%s", gr->gr_name);
-			} else {
-				errx(1,
-				"line %d: could not get gname for gid=%u",
-				lineno, p->fts_statp->st_gid);
-			}
-		}
-		if (keys & F_GID)
-			output(indent, &offset, "gid=%u", p->fts_statp->st_gid);
+	if (keys & (F_GID | F_GNAME) && p->fts_statp->st_gid != gid) {
+		if (keys & F_GNAME &&
+		    (name = group_from_gid(p->fts_statp->st_gid, 1)) != NULL)
+			output(fp, indent, &offset, "gname=%s", name);
+		if (keys & F_GID || (keys & F_GNAME && name == NULL))
+			output(fp, indent, &offset, "gid=%u",
+			    p->fts_statp->st_gid);
 	}
 	if (keys & F_MODE && (p->fts_statp->st_mode & MBITS) != mode)
-		output(indent, &offset, "mode=%#o", p->fts_statp->st_mode & MBITS);
+		output(fp, indent, &offset, "mode=%#o",
+		    p->fts_statp->st_mode & MBITS);
+	if (keys & F_DEV &&
+	    (S_ISBLK(p->fts_statp->st_mode) || S_ISCHR(p->fts_statp->st_mode)))
+		output(fp, indent, &offset, "device=%#jx",
+		    (uintmax_t)p->fts_statp->st_rdev);
 	if (keys & F_NLINK && p->fts_statp->st_nlink != 1)
-		output(indent, &offset, "nlink=%u", p->fts_statp->st_nlink);
-	if (keys & F_SIZE)
-		output(indent, &offset, "size=%jd",
+		output(fp, indent, &offset, "nlink=%ju",
+		    (uintmax_t)p->fts_statp->st_nlink);
+	if (keys & F_SIZE &&
+	    (flavor == F_FREEBSD9 || S_ISREG(p->fts_statp->st_mode)))
+		output(fp, indent, &offset, "size=%ju",
 		    (uintmax_t)p->fts_statp->st_size);
 	if (keys & F_TIME)
-		output(indent, &offset, "time=%ld.%ld",
-		    p->fts_statp->st_mtimespec.tv_sec,
+#if defined(BSD4_4) && !defined(HAVE_NBTOOL_CONFIG_H)
+		output(fp, indent, &offset, "time=%jd.%09ld",
+		    (intmax_t)p->fts_statp->st_mtimespec.tv_sec,
 		    p->fts_statp->st_mtimespec.tv_nsec);
-	if (keys & F_CKSUM && S_ISREG(p->fts_statp->st_mode)) {
-		if ((fd = open(p->fts_accpath, O_RDONLY, 0)) < 0 ||
-		    crc(fd, &val, &len))
-			err(1, "line %d: %s", lineno, p->fts_accpath);
-		close(fd);
-		output(indent, &offset, "cksum=%u", val);
-	}
-#ifdef MD5
-	if (keys & F_MD5 && S_ISREG(p->fts_statp->st_mode)) {
-		char *digest, buf[33];
-
-		digest = MD5File(p->fts_accpath, buf);
-		if (!digest) {
-			err(1, "line %d: %s", lineno, p->fts_accpath);
-		} else {
-			output(indent, &offset, "md5digest=%s", digest);
-		}
-	}
-#endif /* MD5 */
-#ifdef SHA1
-	if (keys & F_SHA1 && S_ISREG(p->fts_statp->st_mode)) {
-		char *digest, buf[41];
-
-		digest = SHA1_File(p->fts_accpath, buf);
-		if (!digest) {
-			err(1, "line %d: %s", lineno, p->fts_accpath);
-		} else {
-			output(indent, &offset, "sha1digest=%s", digest);
-		}
-	}
-#endif /* SHA1 */
-#ifdef RMD160
-	if (keys & F_RMD160 && S_ISREG(p->fts_statp->st_mode)) {
-		char *digest, buf[41];
-
-		digest = RIPEMD160_File(p->fts_accpath, buf);
-		if (!digest) {
-			err(1, "line %d: %s", lineno, p->fts_accpath);
-		} else {
-			output(indent, &offset, "ripemd160digest=%s", digest);
-		}
+#else
+		output(fp, indent, &offset, "time=%jd.%09ld",
+		    (intmax_t)p->fts_statp->st_mtime, (long)0);
+#endif
+	if (S_ISREG(p->fts_statp->st_mode))  {
+		dosum(fp, indent, p, &offset, F_CKSUM, crcFile, "cksum");
+#ifndef NO_MD5
+		dosum(fp, indent, p, &offset, F_MD5, MD5File, MD5KEY);
+#endif	/* ! NO_MD5 */
+#ifndef NO_RMD160
+		dosum(fp, indent, p, &offset, F_RMD160, RMD160File, RMD160KEY);
+#endif	/* ! NO_RMD160 */
+#ifndef NO_SHA1
+		dosum(fp, indent, p, &offset, F_SHA1, SHA1_File, SHA1KEY);
+#endif	/* ! NO_SHA1 */
+#ifndef NO_SHA2
+		dosum(fp, indent, p, &offset, F_SHA256, SHA256_File, SHA256KEY);
+#if 0 /* XXX DragonFly */
+#ifdef SHA384_BLOCK_LENGTH
+		dosum(fp, indent, p, &offset, F_SHA384, SHA384_File, SHA384KEY);
+#endif
+#endif
+		dosum(fp, indent, p, &offset, F_SHA512, SHA512_File, SHA512KEY);
+#endif	/* ! NO_SHA2 */
 	}
-#endif /* RMD160 */
 	if (keys & F_SLINK &&
 	    (p->fts_info == FTS_SL || p->fts_info == FTS_SLNONE))
-		output(indent, &offset, "link=%s", rlink(p->fts_accpath));
+		output(fp, indent, &offset, "link=%s",
+		    vispath(rlink(p->fts_accpath)));
+#if HAVE_STRUCT_STAT_ST_FLAGS
 	if (keys & F_FLAGS && p->fts_statp->st_flags != flags) {
-		fflags = flags_to_string(p->fts_statp->st_flags);
-		output(indent, &offset, "flags=%s", fflags);
-		free(fflags);
+		char *str = flags_to_string(p->fts_statp->st_flags, "none");
+		output(fp, indent, &offset, "flags=%s", str);
+		free(str);
 	}
+#endif
 	putchar('\n');
 }
 
-#define	MAXGID	5000
-#define	MAXUID	5000
-#define	MAXMODE	MBITS + 1
-#define	MAXFLAGS 256
-#define	MAXS 16
+/* XXX
+ * FLAGS2INDEX will fail once the user and system settable bits need more
+ * than one byte, respectively.
+ */
+#define FLAGS2INDEX(x)  (((x >> 8) & 0x0000ff00) | (x & 0x000000ff))
+
+#define	MTREE_MAXGID	5000
+#define	MTREE_MAXUID	5000
+#define	MTREE_MAXMODE	(MBITS + 1)
+#if HAVE_STRUCT_STAT_ST_FLAGS
+#define	MTREE_MAXFLAGS  (FLAGS2INDEX(CH_MASK) + 1)   /* 1808 */
+#else
+#define MTREE_MAXFLAGS	1
+#endif
+#define	MTREE_MAXS 16
 
 static int
-statd(FTS *t, FTSENT *parent, uid_t *puid, gid_t *pgid, mode_t *pmode,
-      u_long *pflags)
+statd(FILE *fp, FTS *t, FTSENT *parent, uid_t *puid, gid_t *pgid, mode_t *pmode,
+    u_long *pflags)
 {
 	FTSENT *p;
 	gid_t sgid;
 	uid_t suid;
 	mode_t smode;
-	u_long sflags;
-	struct group *gr;
-	struct passwd *pw;
-	gid_t savegid = *pgid;
-	uid_t saveuid = *puid;
-	mode_t savemode = *pmode;
-	u_long saveflags = *pflags;
+	u_long sflags = 0;
+	const char *name = NULL;
+	gid_t savegid;
+	uid_t saveuid;
+	mode_t savemode;
+	u_long saveflags;
 	u_short maxgid, maxuid, maxmode, maxflags;
-	u_short g[MAXGID], u[MAXUID], m[MAXMODE], f[MAXFLAGS];
-	char *fflags;
+	u_short g[MTREE_MAXGID], u[MTREE_MAXUID],
+		m[MTREE_MAXMODE], f[MTREE_MAXFLAGS];
 	static int first = 1;
 
+	savegid = *pgid;
+	saveuid = *puid;
+	savemode = *pmode;
+	saveflags = *pflags;
 	if ((p = fts_children(t, 0)) == NULL) {
 		if (errno)
-			err(1, "line %d: %s", lineno, RP(parent));
+			mtree_err("%s: %s", RP(parent), strerror(errno));
 		return (1);
 	}
 
-	bzero(g, sizeof(g));
-	bzero(u, sizeof(u));
-	bzero(m, sizeof(m));
-	bzero(f, sizeof(f));
+	memset(g, 0, sizeof(g));
+	memset(u, 0, sizeof(u));
+	memset(m, 0, sizeof(m));
+	memset(f, 0, sizeof(f));
 
 	maxuid = maxgid = maxmode = maxflags = 0;
 	for (; p; p = p->fts_link) {
-		if (!dflag || (dflag && S_ISDIR(p->fts_statp->st_mode))) {
+		if (flavor == F_NETBSD6 || !dflag ||
+		    (dflag && S_ISDIR(p->fts_statp->st_mode))) {
 			smode = p->fts_statp->st_mode & MBITS;
-			if (smode < MAXMODE && ++m[smode] > maxmode) {
+			if (smode < MTREE_MAXMODE && ++m[smode] > maxmode) {
 				savemode = smode;
 				maxmode = m[smode];
 			}
 			sgid = p->fts_statp->st_gid;
-			if (sgid < MAXGID && ++g[sgid] > maxgid) {
+			if (sgid < MTREE_MAXGID && ++g[sgid] > maxgid) {
 				savegid = sgid;
 				maxgid = g[sgid];
 			}
 			suid = p->fts_statp->st_uid;
-			if (suid < MAXUID && ++u[suid] > maxuid) {
+			if (suid < MTREE_MAXUID && ++u[suid] > maxuid) {
 				saveuid = suid;
 				maxuid = u[suid];
 			}
 
-			/*
-			 * XXX
-			 * note that the below will break when file flags
-			 * are extended beyond the first 4 bytes of each
-			 * half word of the flags
-			 */
-#define FLAGS2IDX(f) ((f & 0xf) | ((f >> 12) & 0xf0))
-			sflags = p->fts_statp->st_flags;
-			if (FLAGS2IDX(sflags) < MAXFLAGS &&
-			    ++f[FLAGS2IDX(sflags)] > maxflags) {
-				saveflags = sflags;
-				maxflags = f[FLAGS2IDX(sflags)];
+#if HAVE_STRUCT_STAT_ST_FLAGS
+			sflags = FLAGS2INDEX(p->fts_statp->st_flags);
+			if (sflags < MTREE_MAXFLAGS && ++f[sflags] > maxflags) {
+				saveflags = p->fts_statp->st_flags;
+				maxflags = f[sflags];
 			}
+#endif
 		}
 	}
 	/*
-	 * If the /set record is the same as the last one we do not need to output
-	 * a new one.  So first we check to see if anything changed.  Note that we
-	 * always output a /set record for the first directory.
+	 * If the /set record is the same as the last one we do not need to
+	 * output a new one.  So first we check to see if anything changed.
+	 * Note that we always output a /set record for the first directory.
 	 */
-	if ((((keys & F_UNAME) | (keys & F_UID)) && (*puid != saveuid)) ||
-	    (((keys & F_GNAME) | (keys & F_GID)) && (*pgid != savegid)) ||
+	if (((keys & (F_UNAME | F_UID)) && (*puid != saveuid)) ||
+	    ((keys & (F_GNAME | F_GID)) && (*pgid != savegid)) ||
 	    ((keys & F_MODE) && (*pmode != savemode)) || 
 	    ((keys & F_FLAGS) && (*pflags != saveflags)) ||
-	    (first)) {
+	    first) {
 		first = 0;
-		if (dflag)
-			printf("/set type=dir");
+		if (flavor != F_NETBSD6 && dflag)
+			fprintf(fp, "/set type=dir");
 		else
-			printf("/set type=file");
-		if (keys & F_UNAME) {
-			if ((pw = getpwuid(saveuid)) != NULL)
-				printf(" uname=%s", pw->pw_name);
-			else
-				errx(1,
-				"line %d: could not get uname for uid=%u",
-				lineno, saveuid);
+			fprintf(fp, "/set type=file");
+		if (keys & (F_UID | F_UNAME)) {
+			if (keys & F_UNAME &&
+			    (name = user_from_uid(saveuid, 1)) != NULL)
+				fprintf(fp, " uname=%s", name);
+			if (keys & F_UID || (keys & F_UNAME && name == NULL))
+				fprintf(fp, " uid=%lu", (u_long)saveuid);
 		}
-		if (keys & F_UID)
-			printf(" uid=%lu", (u_long)saveuid);
-		if (keys & F_GNAME) {
-			if ((gr = getgrgid(savegid)) != NULL)
-				printf(" gname=%s", gr->gr_name);
-			else
-				errx(1,
-				"line %d: could not get gname for gid=%u",
-				lineno, savegid);
+		if (keys & (F_GID | F_GNAME)) {
+			if (keys & F_GNAME &&
+			    (name = group_from_gid(savegid, 1)) != NULL)
+				fprintf(fp, " gname=%s", name);
+			if (keys & F_GID || (keys & F_GNAME && name == NULL))
+				fprintf(fp, " gid=%lu", (u_long)savegid);
 		}
-		if (keys & F_GID)
-			printf(" gid=%lu", (u_long)savegid);
 		if (keys & F_MODE)
-			printf(" mode=%#o", savemode);
+			fprintf(fp, " mode=%#lo", (u_long)savemode);
 		if (keys & F_NLINK)
-			printf(" nlink=1");
+			fprintf(fp, " nlink=1");
 		if (keys & F_FLAGS) {
-			fflags = flags_to_string(saveflags);
-			printf(" flags=%s", fflags);
-			free(fflags);
+			char *str = flags_to_string(saveflags, "none");
+			fprintf(fp, " flags=%s", str);
+			free(str);
 		}
-		printf("\n");
+		fprintf(fp, "\n");
 		*puid = saveuid;
 		*pgid = savegid;
 		*pmode = savemode;
@@ -374,8 +446,17 @@ statd(FTS *t, FTSENT *parent, uid_t *puid, gid_t *pgid, mode_t *pmode,
 	return (0);
 }
 
+/*
+ * dcmp --
+ *	used as a comparison function passed to fts_open() to control
+ *	the order in which fts_read() returns results.	We make
+ *	directories sort after non-directories, but otherwise sort in
+ *	strcmp() order.
+ *
+ * Keep this in sync with nodecmp() in spec.c.
+ */
 static int
-dsort(const FTSENT * const *a, const FTSENT * const *b)
+dcmp(const FTSENT *FTS_CONST *a, const FTSENT *FTS_CONST *b)
 {
 
 	if (S_ISDIR((*a)->fts_statp->st_mode)) {
@@ -387,7 +468,7 @@ dsort(const FTSENT * const *a, const FTSENT * const *b)
 }
 
 void
-output(int indent, int *offset, const char *fmt, ...)
+output(FILE *fp, int indent, int *offset, const char *fmt, ...)
 {
 	va_list ap;
 	char buf[1024];
@@ -397,8 +478,8 @@ output(int indent, int *offset, const char *fmt, ...)
 	va_end(ap);
 
 	if (*offset + strlen(buf) > MAXLINELEN - 3) {
-		printf(" \\\n%*s", INDENTNAMELEN + indent, "");
+		fprintf(fp, " \\\n%*s", INDENTNAMELEN + indent, "");
 		*offset = INDENTNAMELEN + indent;
 	}
-	*offset += printf(" %s", buf) + 1;
+	*offset += fprintf(fp, " %s", buf) + 1;
 }
diff --git a/usr.sbin/mtree/excludes.c b/usr.sbin/mtree/excludes.c
index ffcf3c32fa..4b46b89092 100644
--- a/usr.sbin/mtree/excludes.c
+++ b/usr.sbin/mtree/excludes.c
@@ -1,3 +1,5 @@
+/*	$NetBSD: excludes.c,v 1.13 2004/06/20 22:20:18 jmc Exp $	*/
+
 /*
  * Copyright 2000 Massachusetts Institute of Technology
  *
@@ -12,7 +14,7 @@
  * no representations about the suitability of this software for any
  * purpose.  It is provided "as is" without express or implied
  * warranty.
- * 
+ *
  * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
  * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
@@ -25,25 +27,33 @@
  * 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: src/usr.sbin/mtree/excludes.c,v 1.1.2.4 2001/01/12 19:17:18 phk Exp $
  */
 
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+
+#if defined(__RCSID) && !defined(lint)
+__RCSID("$NetBSD: excludes.c,v 1.13 2004/06/20 22:20:18 jmc Exp $");
+#endif
+
 #include <sys/types.h>
-#include <sys/time.h>		/* XXX for mtree.h */
 #include <sys/queue.h>
 
-#include <err.h>
 #include <fnmatch.h>
-#include <fts.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <util.h>
 
-#include "mtree.h"		/* XXX for extern.h */
 #include "extern.h"
 
+
 /*
- * We're assuming that there won't be a whole lot of excludes, 
+ * We're assuming that there won't be a whole lot of excludes,
  * so it's OK to use a stupid algorithm.
  */
 struct exclude {
@@ -53,6 +63,7 @@ struct exclude {
 };
 static LIST_HEAD(, exclude) excludes;
 
+
 void
 init_excludes(void)
 {
@@ -64,28 +75,24 @@ void
 read_excludes_file(const char *name)
 {
 	FILE *fp;
-	char *line, *str;
+	char *line;
 	struct exclude *e;
-	size_t len;
 
 	fp = fopen(name, "r");
-	if (fp == NULL)
+	if (fp == 0)
 		err(1, "%s", name);
 
-	while ((line = fgetln(fp, &len)) != NULL) {
-		if (line[len - 1] == '\n')
-			len--;
-		if (len == 0)
+	while ((line = fparseln(fp, NULL, NULL, NULL,
+	    FPARSELN_UNESCCOMM | FPARSELN_UNESCCONT | FPARSELN_UNESCESC))
+	    != NULL) {
+		if (line[0] == '\0')
 			continue;
 
-		str = malloc(len + 1);
-		e = malloc(sizeof *e);
-		if (str == NULL || e == NULL)
-			errx(1, "memory allocation error");
-		e->glob = str;
-		memcpy(str, line, len);
-		str[len] = '\0';
-		if (strchr(str, '/'))
+		if ((e = malloc(sizeof *e)) == NULL)
+			mtree_err("memory allocation error");
+
+		e->glob = line;
+		if (strchr(e->glob, '/') != NULL)
 			e->pathname = 1;
 		else
 			e->pathname = 0;
@@ -102,10 +109,13 @@ check_excludes(const char *fname, const char *path)
 	/* fnmatch(3) has a funny return value convention... */
 #define MATCH(g, n) (fnmatch((g), (n), FNM_PATHNAME) == 0)
 
-	LIST_FOREACH(e, &excludes, link) {
-		if ((e->pathname && MATCH(e->glob, path)) ||
-		    MATCH(e->glob, fname))
-			return 1;
+	e = LIST_FIRST(&excludes);
+	while (e) {
+		if ((e->pathname && MATCH(e->glob, path))
+		    || MATCH(e->glob, fname)) {
+			return (1);
+		}
+		e = LIST_NEXT(e, link);
 	}
-	return 0;
+	return (0);
 }
diff --git a/usr.sbin/mtree/extern.h b/usr.sbin/mtree/extern.h
index 40b5fabcfb..78feacbafa 100644
--- a/usr.sbin/mtree/extern.h
+++ b/usr.sbin/mtree/extern.h
@@ -1,3 +1,5 @@
+/*	$NetBSD: extern.h,v 1.39 2014/04/24 17:22:41 christos Exp $	*/
+
 /*-
  * Copyright (c) 1991, 1993
  *	The Regents of the University of California.  All rights reserved.
@@ -27,29 +29,65 @@
  * SUCH DAMAGE.
  *
  *	@(#)extern.h	8.1 (Berkeley) 6/6/93
- * $FreeBSD: src/usr.sbin/mtree/extern.h,v 1.3.2.2 2000/06/28 02:33:17 joe Exp $
  */
 
-#ifdef MAXPATHLEN
-extern char fullpath[MAXPATHLEN];
+#include "mtree.h"
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#else 
+#define HAVE_STRUCT_STAT_ST_FLAGS 1
 #endif
-extern int dflag, eflag, iflag, nflag, qflag, rflag, sflag, uflag;
-extern int ftsoptions;
-extern int lineno;
-extern uint32_t crc_total;
-extern u_int keys;
+ 
+#include <err.h> 
+#include <fts.h>
+#include <libutil.h>
+#include <stdbool.h>
 
-int	 compare(NODE *, FTSENT *);
-int	 crc(int, uint32_t *, off_t *);
-void	 cwalk(void);
-char	*flags_to_string(u_long);
+#if HAVE_NETDB_H
+/* For MAXHOSTNAMELEN on some platforms. */
+#include <netdb.h>
+#endif
+
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 256
+#endif
 
-const char *inotype(u_int);
-u_int	 parsekey(char *, int *);
-char	*rlink(char *);
-NODE	*spec(void);
-int	 verify(void);
+enum flavor {
+	F_MTREE,
+	F_FREEBSD9,
+	F_NETBSD6
+};
 
+void	 addtag(slist_t *, char *);
 int	 check_excludes(const char *, const char *);
+int	 compare(NODE *, FTSENT *);
+int	 crc(int, u_int32_t *, u_int32_t *);
+void	 cwalk(FILE *);
+void	 dump_nodes(FILE *, const char *, NODE *, int);
 void	 init_excludes(void);
+int	 matchtags(NODE *);
+__dead2 __printflike(1,2) void	 mtree_err(const char *, ...);
+const char *nodetype(u_int);
+u_int	 parsekey(const char *, int *);
+void	 parsetags(slist_t *, char *);
+u_int	 parsetype(const char *);
 void	 read_excludes_file(const char *);
+const char *rlink(const char *);
+int	 verify(FILE *);
+void	 load_only(const char *fname);
+bool	 find_only(const char *path);
+
+char	*flags_to_string(unsigned long, const char *);
+int	 string_to_flags(char **, unsigned long *, unsigned long *);
+
+extern int	bflag, dflag, eflag, iflag, jflag, lflag, mflag,
+		nflag, qflag, rflag, sflag, tflag, uflag;
+extern int	mtree_Mflag, mtree_Sflag, mtree_Wflag;
+extern size_t	mtree_lineno;
+extern enum flavor	flavor;
+extern u_int32_t crc_total;
+extern int	ftsoptions, keys;
+extern char	fullpath[];
+extern slist_t	includetags, excludetags;
+
diff --git a/usr.sbin/mtree/getid.c b/usr.sbin/mtree/getid.c
new file mode 100644
index 0000000000..2c9e641500
--- /dev/null
+++ b/usr.sbin/mtree/getid.c
@@ -0,0 +1,446 @@
+/*	$NetBSD: getid.c,v 1.10 2014/10/27 21:46:45 christos Exp $	*/
+/*	from: NetBSD: getpwent.c,v 1.48 2000/10/03 03:22:26 enami Exp */
+/*	from: NetBSD: getgrent.c,v 1.41 2002/01/12 23:51:30 lukem Exp */
+
+/*
+ * Copyright (c) 1987, 1988, 1989, 1993, 1994, 1995
+ *	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.
+ */
+
+/*-
+ * Copyright (c) 2002 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn of Wasabi Systems.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: getid.c,v 1.10 2014/10/27 21:46:45 christos Exp $");
+
+#include <sys/param.h>
+
+#include <grp.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+static	struct group *	gi_getgrnam(const char *);
+static	struct group *	gi_getgrgid(gid_t);
+static	int		gi_setgroupent(int);
+static	void		gi_endgrent(void);
+static	int		grstart(void);
+static	int		grscan(int, gid_t, const char *);
+static	int		grmatchline(int, gid_t, const char *);
+
+static	struct passwd *	gi_getpwnam(const char *);
+static	struct passwd *	gi_getpwuid(uid_t);
+static	int		gi_setpassent(int);
+static	void		gi_endpwent(void);
+static	int		pwstart(void);
+static	int		pwscan(int, uid_t, const char *);
+static	int		pwmatchline(int, uid_t, const char *);
+
+#define	MAXGRP		200
+#define	MAXLINELENGTH	1024
+
+static	FILE		*_gr_fp;
+static	struct group	_gr_group;
+static	int		_gr_stayopen;
+static	int		_gr_filesdone;
+static	FILE		*_pw_fp;
+static	struct passwd	_pw_passwd;	/* password structure */
+static	int		_pw_stayopen;	/* keep fd's open */
+static	int		_pw_filesdone;
+
+static	char		grfile[MAXPATHLEN];
+static	char		pwfile[MAXPATHLEN];
+
+static	char		*members[MAXGRP];
+static	char		grline[MAXLINELENGTH];
+static	char		pwline[MAXLINELENGTH];
+
+int
+setup_getid(const char *dir)
+{
+	if (dir == NULL)
+		return (0);
+
+				/* close existing databases */
+	gi_endgrent();
+	gi_endpwent();
+
+				/* build paths to new databases */
+	snprintf(grfile, sizeof(grfile), "%s/group", dir);
+	snprintf(pwfile, sizeof(pwfile), "%s/master.passwd", dir);
+
+				/* try to open new databases */
+	if (!grstart() || !pwstart())
+		return (0);
+
+				/* switch pwcache(3) lookup functions */
+	if (pwcache_groupdb(gi_setgroupent, gi_endgrent,
+			    gi_getgrnam, gi_getgrgid) == -1
+	    || pwcache_userdb(gi_setpassent, gi_endpwent,
+			    gi_getpwnam, gi_getpwuid) == -1)
+		return (0);
+
+	return (1);
+}
+
+
+/*
+ * group lookup functions
+ */
+
+static struct group *
+gi_getgrnam(const char *name)
+{
+	int rval;
+
+	if (!grstart())
+		return NULL;
+	rval = grscan(1, 0, name);
+	if (!_gr_stayopen)
+		endgrent();
+	return (rval) ? &_gr_group : NULL;
+}
+
+static struct group *
+gi_getgrgid(gid_t gid)
+{
+	int rval;
+
+	if (!grstart())
+		return NULL;
+	rval = grscan(1, gid, NULL);
+	if (!_gr_stayopen)
+		endgrent();
+	return (rval) ? &_gr_group : NULL;
+}
+
+static int
+gi_setgroupent(int stayopen)
+{
+
+	if (!grstart())
+		return 0;
+	_gr_stayopen = stayopen;
+	return 1;
+}
+
+static void
+gi_endgrent(void)
+{
+
+	_gr_filesdone = 0;
+	if (_gr_fp) {
+		(void)fclose(_gr_fp);
+		_gr_fp = NULL;
+	}
+}
+
+static int
+grstart(void)
+{
+
+	_gr_filesdone = 0;
+	if (_gr_fp) {
+		rewind(_gr_fp);
+		return 1;
+	}
+	if (grfile[0] == '\0')			/* sanity check */
+		return 0;
+
+	_gr_fp = fopen(grfile, "r");
+	if (_gr_fp != NULL)
+		return 1;
+	warn("Can't open `%s'", grfile);
+	return 0;
+}
+
+
+static int
+grscan(int search, gid_t gid, const char *name)
+{
+
+	if (_gr_filesdone)
+		return 0;
+	for (;;) {
+		if (!fgets(grline, sizeof(grline), _gr_fp)) {
+			if (!search)
+				_gr_filesdone = 1;
+			return 0;
+		}
+		/* skip lines that are too big */
+		if (!strchr(grline, '\n')) {
+			int ch;
+
+			while ((ch = getc(_gr_fp)) != '\n' && ch != EOF)
+				;
+			continue;
+		}
+		/* skip comments */
+		if (grline[0] == '#')
+			continue;
+		if (grmatchline(search, gid, name))
+			return 1;
+	}
+	/* NOTREACHED */
+}
+
+static int
+grmatchline(int search, gid_t gid, const char *name)
+{
+	unsigned long	id;
+	char		**m;
+	char		*cp, *bp, *ep;
+
+	/* name may be NULL if search is nonzero */
+
+	bp = grline;
+	memset(&_gr_group, 0, sizeof(_gr_group));
+	_gr_group.gr_name = strsep(&bp, ":\n");
+	if (search && name && strcmp(_gr_group.gr_name, name))
+		return 0;
+	_gr_group.gr_passwd = strsep(&bp, ":\n");
+	if (!(cp = strsep(&bp, ":\n")))
+		return 0;
+	id = strtoul(cp, &ep, 10);
+	if (id > GID_MAX || *ep != '\0')
+		return 0;
+	_gr_group.gr_gid = (gid_t)id;
+	if (search && name == NULL && _gr_group.gr_gid != gid)
+		return 0;
+	cp = NULL;
+	if (bp == NULL)
+		return 0;
+	for (_gr_group.gr_mem = m = members;; bp++) {
+		if (m == &members[MAXGRP - 1])
+			break;
+		if (*bp == ',') {
+			if (cp) {
+				*bp = '\0';
+				*m++ = cp;
+				cp = NULL;
+			}
+		} else if (*bp == '\0' || *bp == '\n' || *bp == ' ') {
+			if (cp) {
+				*bp = '\0';
+				*m++ = cp;
+			}
+			break;
+		} else if (cp == NULL)
+			cp = bp;
+	}
+	*m = NULL;
+	return 1;
+}
+
+
+/*
+ * user lookup functions
+ */
+
+static struct passwd *
+gi_getpwnam(const char *name)
+{
+	int rval;
+
+	if (!pwstart())
+		return NULL;
+	rval = pwscan(1, 0, name);
+	if (!_pw_stayopen)
+		endpwent();
+	return (rval) ? &_pw_passwd : NULL;
+}
+
+static struct passwd *
+gi_getpwuid(uid_t uid)
+{
+	int rval;
+
+	if (!pwstart())
+		return NULL;
+	rval = pwscan(1, uid, NULL);
+	if (!_pw_stayopen)
+		endpwent();
+	return (rval) ? &_pw_passwd : NULL;
+}
+
+static int
+gi_setpassent(int stayopen)
+{
+
+	if (!pwstart())
+		return 0;
+	_pw_stayopen = stayopen;
+	return 1;
+}
+
+static void
+gi_endpwent(void)
+{
+
+	_pw_filesdone = 0;
+	if (_pw_fp) {
+		(void)fclose(_pw_fp);
+		_pw_fp = NULL;
+	}
+}
+
+static int
+pwstart(void)
+{
+
+	_pw_filesdone = 0;
+	if (_pw_fp) {
+		rewind(_pw_fp);
+		return 1;
+	}
+	if (pwfile[0] == '\0')			/* sanity check */
+		return 0;
+	_pw_fp = fopen(pwfile, "r");
+	if (_pw_fp != NULL)
+		return 1;
+	warn("Can't open `%s'", pwfile);
+	return 0;
+}
+
+
+static int
+pwscan(int search, uid_t uid, const char *name)
+{
+
+	if (_pw_filesdone)
+		return 0;
+	for (;;) {
+		if (!fgets(pwline, sizeof(pwline), _pw_fp)) {
+			if (!search)
+				_pw_filesdone = 1;
+			return 0;
+		}
+		/* skip lines that are too big */
+		if (!strchr(pwline, '\n')) {
+			int ch;
+
+			while ((ch = getc(_pw_fp)) != '\n' && ch != EOF)
+				;
+			continue;
+		}
+		/* skip comments */
+		if (pwline[0] == '#')
+			continue;
+		if (pwmatchline(search, uid, name))
+			return 1;
+	}
+	/* NOTREACHED */
+}
+
+static int
+pwmatchline(int search, uid_t uid, const char *name)
+{
+	unsigned long	id;
+	char		*cp, *bp, *ep;
+
+	/* name may be NULL if search is nonzero */
+
+	bp = pwline;
+	memset(&_pw_passwd, 0, sizeof(_pw_passwd));
+	_pw_passwd.pw_name = strsep(&bp, ":\n");		/* name */
+	if (search && name && strcmp(_pw_passwd.pw_name, name))
+		return 0;
+
+	_pw_passwd.pw_passwd = strsep(&bp, ":\n");		/* passwd */
+
+	if (!(cp = strsep(&bp, ":\n")))				/* uid */
+		return 0;
+	id = strtoul(cp, &ep, 10);
+	if (id > UID_MAX || *ep != '\0')
+		return 0;
+	_pw_passwd.pw_uid = (uid_t)id;
+	if (search && name == NULL && _pw_passwd.pw_uid != uid)
+		return 0;
+
+	if (!(cp = strsep(&bp, ":\n")))				/* gid */
+		return 0;
+	id = strtoul(cp, &ep, 10);
+	if (id > GID_MAX || *ep != '\0')
+		return 0;
+	_pw_passwd.pw_gid = (gid_t)id;
+
+	if (!(ep = strsep(&bp, ":")))				/* class */
+		return 0;
+	if (!(ep = strsep(&bp, ":")))				/* change */
+		return 0;
+	if (!(ep = strsep(&bp, ":")))				/* expire */
+		return 0;
+
+	if (!(_pw_passwd.pw_gecos = strsep(&bp, ":\n")))	/* gecos */
+		return 0;
+	if (!(_pw_passwd.pw_dir = strsep(&bp, ":\n")))		/* directory */
+		return 0;
+	if (!(_pw_passwd.pw_shell = strsep(&bp, ":\n")))	/* shell */
+		return 0;
+
+	if (strchr(bp, ':') != NULL)
+		return 0;
+
+	return 1;
+}
+
diff --git a/usr.sbin/mtree/misc.c b/usr.sbin/mtree/misc.c
index 7743f39633..b99f1ce1f7 100644
--- a/usr.sbin/mtree/misc.c
+++ b/usr.sbin/mtree/misc.c
@@ -1,3 +1,5 @@
+/*	$NetBSD: misc.c,v 1.34 2012/12/20 19:09:25 christos Exp $	*/
+
 /*-
  * Copyright (c) 1991, 1993
  *	The Regents of the University of California.  All rights reserved.
@@ -26,70 +28,128 @@
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
- * @(#)misc.c	8.1 (Berkeley) 6/6/93
- * $FreeBSD: src/usr.sbin/mtree/misc.c,v 1.8.2.1 2000/06/28 02:33:17 joe Exp $
- * $DragonFly: src/usr.sbin/mtree/misc.c,v 1.5 2004/03/15 16:24:22 dillon Exp $
+ *	@(#)misc.c	8.1 (Berkeley) 6/6/93
  */
 
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+__RCSID("$NetBSD: misc.c,v 1.34 2012/12/20 19:09:25 christos Exp $");
+#endif /* not lint */
+
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <err.h>
-#include <fts.h>
+
+#include <stdarg.h>
 #include <stdio.h>
-#include <unistd.h>
-#include "mtree.h"
+#include <stdlib.h>
+#include <string.h>
+
 #include "extern.h"
 
+enum flavor	flavor = F_MTREE;
+
 typedef struct _key {
-	const char *name;		/* key name */
-	u_int val;			/* value */
+	const char	*name;		/* key name */
+	u_int		val;		/* value */
 
 #define	NEEDVALUE	0x01
-	u_int flags;
+	u_int		flags;
 } KEY;
 
-/* NB: the following table must be sorted lexically. */
+/* NB: the following tables must be sorted lexically. */
 static KEY keylist[] = {
 	{"cksum",	F_CKSUM,	NEEDVALUE},
+	{"device",	F_DEV,		NEEDVALUE},
 	{"flags",	F_FLAGS,	NEEDVALUE},
 	{"gid",		F_GID,		NEEDVALUE},
 	{"gname",	F_GNAME,	NEEDVALUE},
 	{"ignore",	F_IGN,		0},
 	{"link",	F_SLINK,	NEEDVALUE},
-#ifdef MD5
+	{"md5",		F_MD5,		NEEDVALUE},
 	{"md5digest",	F_MD5,		NEEDVALUE},
-#endif
 	{"mode",	F_MODE,		NEEDVALUE},
 	{"nlink",	F_NLINK,	NEEDVALUE},
 	{"nochange",	F_NOCHANGE,	0},
-#ifdef RMD160
+	{"optional",	F_OPT,		0},
 	{"ripemd160digest", F_RMD160,	NEEDVALUE},
-#endif
-#ifdef SHA1
+	{"rmd160",	F_RMD160,	NEEDVALUE},
+	{"rmd160digest",F_RMD160,	NEEDVALUE},
+	{"sha1",	F_SHA1,		NEEDVALUE},
 	{"sha1digest",	F_SHA1,		NEEDVALUE},
-#endif
+	{"sha256",	F_SHA256,	NEEDVALUE},
+	{"sha256digest",F_SHA256,	NEEDVALUE},
+	{"sha384",	F_SHA384,	NEEDVALUE},
+	{"sha384digest",F_SHA384,	NEEDVALUE},
+	{"sha512",	F_SHA512,	NEEDVALUE},
+	{"sha512digest",F_SHA512,	NEEDVALUE},
 	{"size",	F_SIZE,		NEEDVALUE},
+	{"tags",	F_TAGS,		NEEDVALUE},
 	{"time",	F_TIME,		NEEDVALUE},
 	{"type",	F_TYPE,		NEEDVALUE},
 	{"uid",		F_UID,		NEEDVALUE},
-	{"uname",	F_UNAME,	NEEDVALUE},
+	{"uname",	F_UNAME,	NEEDVALUE}
+};
+
+static KEY typelist[] = {
+	{"block",	F_BLOCK,	0},
+	{"char",	F_CHAR,		0},
+	{"dir",		F_DIR,		0},
+#ifdef S_IFDOOR
+	{"door",	F_DOOR,		0},
+#endif
+	{"fifo",	F_FIFO,		0},
+	{"file",	F_FILE,		0},
+	{"link",	F_LINK,		0},
+	{"socket",	F_SOCK,		0},
 };
 
+slist_t	excludetags, includetags;
+int	keys = KEYDEFAULT;
+
+
 int keycompare(const void *, const void *);
 
 u_int
-parsekey(char *name, int *needvaluep)
+parsekey(const char *name, int *needvaluep)
 {
+	static int allbits;
 	KEY *k, tmp;
 
+	if (allbits == 0) {
+		size_t i;
+
+		for (i = 0; i < sizeof(keylist) / sizeof(KEY); i++)
+			allbits |= keylist[i].val;
+	}
 	tmp.name = name;
+	if (strcmp(name, "all") == 0)
+		return (allbits);
 	k = (KEY *)bsearch(&tmp, keylist, sizeof(keylist) / sizeof(KEY),
 	    sizeof(KEY), keycompare);
 	if (k == NULL)
-		errx(1, "line %d: unknown keyword %s", lineno, name);
+		mtree_err("unknown keyword `%s'", name);
 
 	if (needvaluep)
 		*needvaluep = k->flags & NEEDVALUE ? 1 : 0;
+
+	return (k->val);
+}
+
+u_int
+parsetype(const char *name)
+{
+	KEY *k, tmp;
+
+	tmp.name = name;
+	k = (KEY *)bsearch(&tmp, typelist, sizeof(typelist) / sizeof(KEY),
+	    sizeof(KEY), keycompare);
+	if (k == NULL)
+		mtree_err("unknown file type `%s'", name);
+
 	return (k->val);
 }
 
@@ -100,18 +160,153 @@ keycompare(const void *a, const void *b)
 	return (strcmp(((const KEY *)a)->name, ((const KEY *)b)->name));
 }
 
-char *
-flags_to_string(u_long fflags)
+void
+mtree_err(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	vwarnx(fmt, ap);
+	va_end(ap);
+	if (mtree_lineno)
+		warnx("failed at line %lu of the specification",
+		    (u_long) mtree_lineno);
+	exit(1);
+	/* NOTREACHED */
+}
+
+void
+addtag(slist_t *list, char *elem)
 {
-	char *string;
 
-	string = fflagstostr(fflags);
-	if (string != NULL && *string == '\0') {
-		free(string);
-		string = strdup("none");
+#define	TAG_CHUNK 20
+
+	if ((list->count % TAG_CHUNK) == 0) {
+		char **new;
+
+		new = (char **)realloc(list->list, (list->count + TAG_CHUNK)
+		    * sizeof(char *));
+		if (new == NULL)
+			mtree_err("memory allocation error");
+		list->list = new;
 	}
-	if (string == NULL)
-		err(1, NULL);
+	list->list[list->count] = elem;
+	list->count++;
+}
 
-	return string;
+void
+parsetags(slist_t *list, char *args)
+{
+	char	*p, *e;
+	int	len;
+
+	if (args == NULL) {
+		addtag(list, NULL);
+		return;
+	}
+	while ((p = strsep(&args, ",")) != NULL) {
+		if (*p == '\0')
+			continue;
+		len = strlen(p) + 3;	/* "," + p + ",\0" */
+		if ((e = malloc(len)) == NULL)
+			mtree_err("memory allocation error");
+		snprintf(e, len, ",%s,", p);
+		addtag(list, e);
+	}
+}
+
+/*
+ * matchtags
+ *	returns 0 if there's a match from the exclude list in the node's tags,
+ *	or there's an include list and no match.
+ *	return 1 otherwise.
+ */
+int
+matchtags(NODE *node)
+{
+	int	i;
+
+	if (node->tags) {
+		for (i = 0; i < excludetags.count; i++)
+			if (strstr(node->tags, excludetags.list[i]))
+				break;
+		if (i < excludetags.count)
+			return (0);
+
+		for (i = 0; i < includetags.count; i++)
+			if (strstr(node->tags, includetags.list[i]))
+				break;
+		if (i > 0 && i == includetags.count)
+			return (0);
+	} else if (includetags.count > 0) {
+		return (0);
+	}
+	return (1);
+}
+
+u_int
+nodetoino(u_int type)
+{
+
+	switch (type) {
+	case F_BLOCK:
+		return S_IFBLK;
+	case F_CHAR:
+		return S_IFCHR;
+	case F_DIR:
+		return S_IFDIR;
+	case F_FIFO:
+		return S_IFIFO;
+	case F_FILE:
+		return S_IFREG;
+	case F_LINK:
+		return S_IFLNK;
+#ifdef S_IFSOCK
+	case F_SOCK:
+		return S_IFSOCK;
+#endif
+	default:
+		printf("unknown type %d", type);
+		abort();
+	}
+	/* NOTREACHED */
+}
+
+const char *
+nodetype(u_int type)
+{
+
+	return (inotype(nodetoino(type)));
+}
+
+
+const char *
+inotype(u_int type)
+{
+
+	switch (type & S_IFMT) {
+	case S_IFBLK:
+		return ("block");
+	case S_IFCHR:
+		return ("char");
+	case S_IFDIR:
+		return ("dir");
+	case S_IFIFO:
+		return ("fifo");
+	case S_IFREG:
+		return ("file");
+	case S_IFLNK:
+		return ("link");
+#ifdef S_IFSOCK
+	case S_IFSOCK:
+		return ("socket");
+#endif
+#ifdef S_IFDOOR
+	case S_IFDOOR:
+		return ("door");
+#endif
+	default:
+		return ("unknown");
+	}
+	/* NOTREACHED */
 }
diff --git a/usr.sbin/mtree/mtree.8 b/usr.sbin/mtree/mtree.8
index d18c06fb7a..eaef44fbeb 100644
--- a/usr.sbin/mtree/mtree.8
+++ b/usr.sbin/mtree/mtree.8
@@ -1,3 +1,5 @@
+.\"	$NetBSD: mtree.8,v 1.72 2017/02/22 14:15:15 abhinav Exp $
+.\"
 .\" Copyright (c) 1989, 1990, 1993
 .\"	The Regents of the University of California.  All rights reserved.
 .\"
@@ -25,10 +27,36 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.\"     From: @(#)mtree.8	8.2 (Berkeley) 12/11/93
-.\" $FreeBSD: src/usr.sbin/mtree/mtree.8,v 1.16.2.11 2003/03/11 22:31:29 trhodes Exp $
+.\" Copyright (c) 2001-2004 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Luke Mewburn of Wasabi Systems.
+.\"
+.\" 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 .\"
-.Dd February 26, 1999
+.\"     @(#)mtree.8	8.2 (Berkeley) 12/11/93
+.\"
+.Dd January 22, 2015
 .Dt MTREE 8
 .Os
 .Sh NAME
@@ -36,106 +64,305 @@
 .Nd map a directory hierarchy
 .Sh SYNOPSIS
 .Nm
-.Op Fl LPUcdeinqrux
-.Bk -words
+.Op Fl bCcDdejLlMnPqrStUuWx
+.Op Fl i | Fl m
+.Op Fl E Ar tags
+.Op Fl F Ar flavor
 .Op Fl f Ar spec
-.Ek
-.Bk -words
+.Op Fl I Ar tags
 .Op Fl K Ar keywords
-.Ek
-.Bk -words
 .Op Fl k Ar keywords
-.Ek
-.Bk -words
+.Op Fl N Ar dbdir
+.Op Fl O Ar onlyfile
 .Op Fl p Ar path
-.Ek
-.Bk -words
+.Op Fl R Ar keywords
 .Op Fl s Ar seed
-.Ek
-.Bk -words
-.Op Fl X Ar exclude-list
-.Ek
+.Op Fl X Ar exclude-file
 .Sh DESCRIPTION
 The
 .Nm
-utility compares the file hierarchy rooted in the current directory against a
-specification read from the standard input.
+utility compares a file hierarchy against a specification,
+creates a specification for a file hierarchy, or modifies
+a specification.
+.Pp
+The default action, if not overridden by command line options,
+is to compare the file hierarchy rooted in the current directory
+against a specification read from the standard input.
 Messages are written to the standard output for any files whose
-characteristics do not match the specifications, or which are
+characteristics do not match the specification, or which are
 missing from either the file hierarchy or the specification.
 .Pp
 The options are as follows:
-.Bl -tag -width flag
-.It Fl L
-Follow all symbolic links in the file hierarchy.
-.It Fl P
-Don't follow symbolic links in the file hierarchy, instead consider
-the symbolic link itself in any comparisons. This is the default.
-.It Fl U
-Modify the owner, group and permissions of existing files to match
-the specification and create any missing directories or symbolic links.
-User, group and permissions must all be specified for missing directories
-to be created.
-Corrected mismatches are not considered errors.
+.Bl -tag -width Xxxexcludexfilexx
+.It Fl b
+Suppress blank lines before entering and after exiting directories.
+.It Fl C
+Convert a specification into
+a format that's easier to parse with various tools.
+The input specification is read from standard input or
+from the file given by
+.Fl f Ar spec .
+In the output, each file or directory is represented using a single line
+(which might be very long).
+The full path name
+(beginning with
+.Dq \&./ )
+is always printed as the first field;
+.Fl K ,
+.Fl k ,
+and
+.Fl R
+can be used to control which other keywords are printed;
+.Fl E
+and
+.Fl I
+can be used to control which files are printed;
+and the
+.Fl S
+option can be used to sort the output.
 .It Fl c
-Print a specification for the file hierarchy to the standard output.
+Print a specification for the file hierarchy originating at
+the current working directory (or the directory provided by
+.Fl p Ar path )
+to the standard output.
+The output is in a style using relative path names.
+.It Fl D
+As per
+.Fl C ,
+except that the path name is always printed as the last field instead of
+the first.
 .It Fl d
 Ignore everything except directory type files.
+.It Fl E Ar tags
+Add the comma separated tags to the
+.Dq exclusion
+list.
+Non-directories with tags which are in the exclusion list are not printed with
+.Fl C
+and
+.Fl D .
 .It Fl e
 Don't complain about files that are in the file hierarchy, but not in the
 specification.
+.It Fl F Ar flavor
+Set the compatibility flavor of the
+.Nm
+utility.
+The
+.Ar flavor
+can be one of
+.Sy mtree ,
+.Sy freebsd9 ,
+or
+.Sy netbsd6 .
+The default is
+.Sy mtree .
+The
+.Sy freebsd9
+and
+.Sy netbsd6
+flavors attempt to preserve output compatibility and command line option
+backward compatibility with
+.Fx 9.0
+and
+.Nx 6.0
+respectively.
+.It Fl f Ar spec
+Read the specification from
+.Ar file  ,
+instead of from the standard input.
+.Pp
+If this option is specified twice, the two specifications are compared
+to each other rather than to the file hierarchy.
+The specifications will be sorted like output generated using
+.Fl c .
+The output format in this case is somewhat reminiscent of
+.Xr comm 1 ,
+having "in first spec only", "in second spec only", and "different"
+columns, prefixed by zero, one and two TAB characters respectively.
+Each entry in the "different" column occupies two lines, one from each
+specification.
+.It Fl I Ar tags
+Add the comma separated tags to the
+.Dq inclusion
+list.
+Non-directories with tags which are in the inclusion list are printed with
+.Fl C
+and
+.Fl D .
+If no inclusion list is provided, the default is to display all files.
 .It Fl i
+If specified, set the schg and/or sappnd flags.
+.It Fl j
 Indent the output 4 spaces each time a directory level is descended when
-create a specification with the
+creating a specification with the
 .Fl c
 option.
 This does not affect either the /set statements or the comment before each
 directory.
 It does however affect the comment before the close of each directory.
+This is the equivalent of the
+.Fl i
+option in the
+.Fx
+version of
+.Nm .
+.It Fl K Ar keywords
+Add the specified (whitespace or comma separated) keywords to the current
+set of keywords.
+If
+.Ql all
+is specified, add all of the other keywords.
+.It Fl k Ar keywords
+Use the
+.Sy type
+keyword plus the specified (whitespace or comma separated)
+keywords instead of the current set of keywords.
+If
+.Ql all
+is specified, use all of the other keywords.
+If the
+.Sy type
+keyword is not desired, suppress it with
+.Fl R Ar type .
+.It Fl L
+Follow all symbolic links in the file hierarchy.
+.It Fl l
+Do
+.Dq loose
+permissions checks, in which more stringent permissions
+will match less stringent ones.
+For example, a file marked mode 0444
+will pass a check for mode 0644.
+.Dq Loose
+checks apply only to read, write and execute permissions -- in
+particular, if other bits like the sticky bit or suid/sgid bits are
+set either in the specification or the file, exact checking will be
+performed.
+This option may not be set at the same time as the
+.Fl U
+or
+.Fl u
+option.
+.It Fl M
+Permit merging of specification entries with different types,
+with the last entry taking precedence.
+.It Fl m
+If the schg and/or sappnd flags are specified, reset these flags.
+Note that this is only possible with securelevel less than 1 (i.e.,
+in single user mode or while the system is running in insecure
+mode).
+See
+.Xr init 8
+for information on security levels.
 .It Fl n
-Do not emit pathname comments when creating a specification.  Normally
+Do not emit pathname comments when creating a specification.
+Normally
 a comment is emitted before each directory and before the close of that
 directory when using the
 .Fl c
 option.
+.It Fl N Ar dbdir
+Use the user database text file
+.Pa master.passwd
+and group database text file
+.Pa group
+from
+.Ar dbdir ,
+rather than using the results from the system's
+.Xr getpwnam 3
+and
+.Xr getgrnam 3
+(and related) library calls.
+.It Fl O Ar onlypaths
+Only include files included in this list of pathnames.
+.It Fl P
+Don't follow symbolic links in the file hierarchy, instead consider
+the symbolic link itself in any comparisons.
+This is the default.
+.It Fl p Ar path
+Use the file hierarchy rooted in
+.Ar path  ,
+instead of the current directory.
 .It Fl q
-Quiet mode.  Do not complain when a
+Quiet mode.
+Do not complain when a
 .Dq missing
 directory cannot be created because it already exists.
 This occurs when the directory is a symbolic link.
+.It Fl R Ar keywords
+Remove the specified (whitespace or comma separated) keywords from the current
+set of keywords.
+If
+.Ql all
+is specified, remove all of the other keywords.
 .It Fl r
 Remove any files in the file hierarchy that are not described in the
 specification.
-.It Fl u
-Same as
-.Fl U
-except a status of 2 is returned if the file hierarchy did not match
-the specification.
-.It Fl x
-Don't descend below mount points in the file hierarchy.
-.It Fl f Ar file
-Read the specification from
-.Ar file ,
-instead of from the standard input.
-.It Fl K Ar keywords
-Add the specified (whitespace or comma separated)
-.Ar keywords
-to the current set of keywords.
-.It Fl k Ar keywords
-Use the ``type'' keyword plus the specified (whitespace or comma separated)
-.Ar keywords
-instead of the current set of keywords.
-.It Fl p Ar path
-Use the file hierarchy rooted in
-.Ar path ,
-instead of the current directory.
+Repeating the flag more than once will attempt to reset all the
+file flags via
+.Xr lchflags 2
+before attempting to remove the file in case the file was immutable.
+.It Fl S
+When reading a specification into an internal data structure,
+sort the entries.
+Sorting will affect the order of the output produced by the
+.Fl C
+or
+.Fl D
+options, and will also affect the order in which
+missing entries are created or reported when a directory tree is checked
+against a specification.
+.Pp
+The sort order is the same as that used by the
+.Fl c
+option, which is that entries within the same directory are
+sorted in the order used by
+.Xr strcmp 3 ,
+except that entries for subdirectories sort after other entries.
+By default, if the
+.Fl S
+option is not used, entries within the same directory are collected
+together (separated from entries for other directories), but not sorted.
 .It Fl s Ar seed
 Display a single checksum to the standard error output that represents all
 of the files for which the keyword
-.Cm cksum
+.Sy cksum
 was specified.
 The checksum is seeded with the specified value.
-.It Fl X Ar exclude-list
+.It Fl t
+Modify the modified time of existing files, the device type of devices, and
+symbolic link targets, to match the specification.
+.It Fl U
+Same as
+.Fl u
+except that a mismatch is not considered to be an error if it was corrected.
+.It Fl u
+Modify the owner, group, permissions, and flags of existing files,
+the device type of devices, and symbolic link targets,
+to match the specification.
+Create any missing directories, devices or symbolic links.
+User, group, and permissions must all be specified for missing directories
+to be created.
+Note that unless the
+.Fl i
+option is given, the schg and sappnd flags will not be set, even if
+specified.
+If
+.Fl m
+is given, these flags will be reset.
+Exit with a status of 0 on success,
+2 if the file hierarchy did not match the specification, and
+1 if any other error occurred.
+.It Fl W
+Don't attempt to set various file attributes such as the
+ownership, mode, flags, or time
+when creating new directories or changing existing entries.
+This option will be most useful when used in conjunction with
+.Fl U
+or
+.Fl u .
+.It Fl X Ar exclude-file
 The specified file contains
 .Xr fnmatch 3
 patterns matching files to be excluded from
@@ -144,224 +371,442 @@ If the pattern contains a
 .Ql \&/
 character, it will be matched against entire pathnames (relative to
 the starting directory); otherwise,
-it will be matched against basenames only.  No comments are allowed in
+it will be matched against basenames only.
+Comments are permitted in
 the
 .Ar exclude-list
 file.
+.It Fl x
+Don't descend below mount points in the file hierarchy.
 .El
 .Pp
-Specifications are mostly composed of ``keywords'', i.e. strings
+Specifications are mostly composed of
+.Dq keywords ,
+i.e. strings that
 that specify values relating to files.
 No keywords have default values, and if a keyword has no value set, no
 checks based on it are performed.
 .Pp
 Currently supported keywords are as follows:
-.Bl -tag -width Cm
-.It Cm cksum
+.Bl -tag -width sha384digestxx
+.It Sy cksum
 The checksum of the file using the default algorithm specified by
 the
 .Xr cksum 1
 utility.
-.It Cm flags
-The file flags as a symbolic name.  See
+.It Sy device
+The device number to use for
+.Sy block
+or
+.Sy char
+file types.
+The argument must be one of the following forms:
+.Bl -tag -width 4n
+.It Ar format , Ns Ar major , Ns Ar minor
+A device with
+.Ar major
+and
+.Ar minor
+fields, for an operating system specified with
+.Ar format .
+See below for valid formats.
+.It Ar format , Ns Ar major , Ns Ar unit , Ns Ar subunit
+A device with
+.Ar major ,
+.Ar unit ,
+and
+.Ar subunit
+fields, for an operating system specified with
+.Ar format .
+(Currently this is only supported by the
+.Sy bsdos
+format.)
+.It Ar number
+Opaque number (as stored on the file system).
+.El
+.Pp
+The following values for
+.Ar format
+are recognized:
+.Sy native ,
+.Sy 386bsd ,
+.Sy 4bsd ,
+.Sy bsdos ,
+.Sy freebsd ,
+.Sy hpux ,
+.Sy isc ,
+.Sy linux ,
+.Sy netbsd ,
+.Sy osf1 ,
+.Sy sco ,
+.Sy solaris ,
+.Sy sunos ,
+.Sy svr3 ,
+.Sy svr4 ,
+and
+.Sy ultrix .
+.Pp
+See
+.Xr mknod 8
+for more details.
+.It Sy flags
+The file flags as a symbolic name.
+See
 .Xr chflags 1
-for information on these names.  If no flags are to be set the string
-.Dq none
+for information on these names.
+If no flags are to be set the string
+.Ql none
 may be used to override the current default.
-.It Cm ignore
+Note that the schg and sappnd flags are treated specially (see the
+.Fl i
+and
+.Fl m
+options).
+.It Sy ignore
 Ignore any file hierarchy below this file.
-.It Cm gid
+.It Sy gid
 The file group as a numeric value.
-.It Cm gname
+.It Sy gname
 The file group as a symbolic name.
-.It Cm md5digest
-The MD5 message digest of the file.
-.It Cm sha1digest
-The
-.Tn FIPS
-160-1
-.Pq Dq Tn SHA-1
-message digest of the file.
-.It Cm ripemd160digest
+.It Sy link
+The file the symbolic link is expected to reference.
+.It Sy md5
 The
-.Tn RIPEMD160
-message digest of the file.
-.It Cm mode
+.Tn MD5
+cryptographic message digest of the file.
+.It Sy md5digest
+Synonym for
+.Sy md5 .
+.It Sy mode
 The current file's permissions as a numeric (octal) or symbolic
 value.
-.It Cm nlink
+.It Sy nlink
 The number of hard links the file is expected to have.
-.It Cm nochange
+.It Sy nochange
 Make sure this file or directory exists but otherwise ignore all attributes.
-.It Cm uid
-The file owner as a numeric value.
-.It Cm uname
-The file owner as a symbolic name.
-.It Cm size
+.It Sy optional
+The file is optional; don't complain about the file if it's
+not in the file hierarchy.
+.It Sy ripemd160digest
+Synonym for
+.Sy rmd160 .
+.It Sy rmd160
+The
+.Tn RMD-160
+cryptographic message digest of the file.
+.It Sy rmd160digest
+Synonym for
+.Sy rmd160 .
+.It Sy sha1
+The
+.Tn SHA-1
+cryptographic message digest of the file.
+.It Sy sha1digest
+Synonym for
+.Sy sha1 .
+.It Sy sha256
+The 256-bits
+.Tn SHA-2
+cryptographic message digest of the file.
+.It Sy sha256digest
+Synonym for
+.Sy sha256 .
+.It Sy sha384
+The 384-bits
+.Tn SHA-2
+cryptographic message digest of the file.
+.It Sy sha384digest
+Synonym for
+.Sy sha384 .
+.It Sy sha512
+The 512-bits
+.Tn SHA-2
+cryptographic message digest of the file.
+.It Sy sha512digest
+Synonym for
+.Sy sha512 .
+.It Sy size
 The size, in bytes, of the file.
-.It Cm link
-The file the symbolic link is expected to reference.
-.It Cm time
-The last modification time of the file.
-.It Cm type
+.It Sy tags
+Comma delimited tags to be matched with
+.Fl E
+and
+.Fl I .
+These may be specified without leading or trailing commas, but will be
+stored internally with them.
+.It Sy time
+The last modification time of the file,
+in second and nanoseconds.
+The value should include a period character and exactly nine digits after
+the period.
+.It Sy type
 The type of the file; may be set to any one of the following:
 .Pp
-.Bl -tag -width Cm -compact
-.It Cm block
+.Bl -tag -width Sy -compact
+.It Sy block
 block special device
-.It Cm char
+.It Sy char
 character special device
-.It Cm dir
+.It Sy dir
 directory
-.It Cm fifo
+.It Sy fifo
 fifo
-.It Cm file
+.It Sy file
 regular file
-.It Cm link
+.It Sy link
 symbolic link
-.It Cm socket
+.It Sy socket
 socket
 .El
+.It Sy uid
+The file owner as a numeric value.
+.It Sy uname
+The file owner as a symbolic name.
 .El
 .Pp
 The default set of keywords are
-.Cm flags ,
-.Cm gid ,
-.Cm mode ,
-.Cm nlink ,
-.Cm size ,
-.Cm link ,
-.Cm time ,
+.Sy flags ,
+.Sy gid ,
+.Sy link ,
+.Sy mode ,
+.Sy nlink ,
+.Sy size ,
+.Sy time ,
+.Sy type ,
 and
-.Cm uid .
-.Pp
-There are four types of lines in a specification.
+.Sy uid .
 .Pp
-The first type of line sets a global value for a keyword, and consists of
-the string ``/set'' followed by whitespace, followed by sets of keyword/value
+There are four types of lines in a specification:
+.Bl -enum
+.It
+Set global values for a keyword.
+This consists of the string
+.Ql /set
+followed by whitespace, followed by sets of keyword/value
 pairs, separated by whitespace.
 Keyword/value pairs consist of a keyword, followed by an equals sign
-(``=''), followed by a value, without whitespace characters.
+.Pq Ql = ,
+followed by a value, without whitespace characters.
 Once a keyword has been set, its value remains unchanged until either
 reset or unset.
-.Pp
-The second type of line unsets keywords and consists of the string
-``/unset'', followed by whitespace, followed by one or more keywords,
+.It
+Unset global values for a keyword.
+This consists of the string
+.Ql /unset ,
+followed by whitespace, followed by one or more keywords,
 separated by whitespace.
+If
+.Ql all
+is specified, unset all of the keywords.
+.It
+A file specification, consisting of a path name, followed by whitespace,
+followed by zero or more whitespace separated keyword/value pairs.
 .Pp
-The third type of line is a file specification and consists of a file
-name, followed by whitespace, followed by zero or more whitespace
-separated keyword/value pairs.
-The file name may be preceded by whitespace characters.
-The file name may contain any of the standard file name matching
-characters (``['', ``]'', ``?'' or ``*''), in which case files
+The path name may be preceded by whitespace characters.
+The path name may contain any of the standard path name matching
+characters
+.Po
+.Ql \&[ ,
+.Ql \&] ,
+.Ql \&?
+or
+.Ql *
+.Pc ,
+in which case files
 in the hierarchy will be associated with the first pattern that
 they match.
+.Nm
+uses
+.Xr strsvis 3
+(in VIS_CSTYLE format) to encode path names containing
+non-printable characters.
+Whitespace characters are encoded as
+.Ql \es
+(space),
+.Ql \et
+(tab), and
+.Ql \en
+(new line).
+.Ql #
+characters in path names are escaped by a preceding backslash
+.Ql \e
+to distinguish them from comments.
 .Pp
 Each of the keyword/value pairs consist of a keyword, followed by an
-equals sign (``=''), followed by the keyword's value, without
+equals sign
+.Pq Ql = ,
+followed by the keyword's value, without
 whitespace characters.
 These values override, without changing, the global value of the
 corresponding keyword.
 .Pp
-All paths are relative.
+The first path name entry listed must be a directory named
+.Ql \&. ,
+as this ensures that intermixing full and relative path names will
+work consistently and correctly.
+Multiple entries for a directory named
+.Ql \&.
+are permitted; the settings for the last such entry override those
+of the existing entry.
+.Pp
+A path name that contains a slash
+.Pq Ql /
+that is not the first character will be treated as a full path
+(relative to the root of the tree).
+All parent directories referenced in the path name must exist.
+The current directory path used by relative path names will be updated
+appropriately.
+Multiple entries for the same full path are permitted if the types
+are the same (unless
+.Fl M
+is given, in which case the types may differ);
+in this case the settings for the last entry take precedence.
+.Pp
+A path name that does not contain a slash will be treated as a relative path.
 Specifying a directory will cause subsequent files to be searched
 for in that directory hierarchy.
-Which brings us to the last type of line in a specification: a line
-containing only the string
-.Dq Pa ..\&
-causes the current directory
-path to ascend one level.
+.It
+A line containing only the string
+.Ql \&..
+which causes the current directory path (used by relative paths)
+to ascend one level.
+.El
 .Pp
 Empty lines and lines whose first non-whitespace character is a hash
-mark (``#'') are ignored.
+mark
+.Pq Ql #
+are ignored.
 .Pp
 The
 .Nm
 utility exits with a status of 0 on success, 1 if any error occurred,
 and 2 if the file hierarchy did not match the specification.
-A status of 2 is converted to a status of 0 if the
-.Fl U
-option is used.
 .Sh FILES
 .Bl -tag -width /etc/mtree -compact
 .It Pa /etc/mtree
 system specification directory
 .El
-.Sh EXIT STATUS
-.Ex -std
 .Sh EXAMPLES
-To detect system binaries that have been ``trojan horsed'', it is recommended
-that
+To detect system binaries that have been
+.Dq trojan horsed ,
+it is recommended that
 .Nm
-.Fl K
-.Cm sha1digest
 be run on the file systems, and a copy of the results stored on a different
 machine, or, at least, in encrypted form.
-The output file itself should be digested using the
-.Xr md5 1
-utility.
+The seed for the
+.Fl s
+option should not be an obvious value and the final checksum should not be
+stored on-line under any circumstances!
 Then, periodically,
 .Nm
-and
-.Xr md5 1
-should be run against the on-line specifications.
+should be run against the on-line specifications and the final checksum
+compared with the previous value.
 While it is possible for the bad guys to change the on-line specifications
-to conform to their modified binaries, it is believed to be
-impractical for them to create a modified specification which has
-the same MD5 digest as the original.
+to conform to their modified binaries, it shouldn't be possible for them
+to make it produce the same final checksum value.
+If the final checksum value changes, the off-line copies of the specification
+can be used to detect which of the binaries have actually been modified.
 .Pp
 The
 .Fl d
-and
+option can be used in combination with
+.Fl U
+or
 .Fl u
-options can be used in combination to create directory hierarchies
-for distributions and other such things; the files in
-.Pa /etc/mtree
-were used to create almost all directories in this
-.Dx
-distribution.
+to create directory hierarchies for, for example, distributions.
+.Sh COMPATIBILITY
+The compatibility shims provided by the
+.Fl F
+option are incomplete by design.
+Known limitations are described below.
 .Pp
-To create an
-.Pa /etc/mtree
-style BSD.*.dist file, use
-.Nm
-.Fl c
-.Fl d
-.Fl i
-.Fl n
-.Fl k
-.Cm uname,gname,mode,nochange .
+The
+.Sy freebsd9
+flavor retains the default handling of lookup failures for the
+.Sy uname
+and
+.Sy group
+keywords by replacing them with appropriate
+.Sy uid
+and
+.Sy gid
+keywords rather than failing and reporting an error.
+The related
+.Fl w
+flag is a no-op rather than causing a warning to be printed and no
+keyword to be emitted.
+The latter behavior is not emulated as it is potentially dangerous in
+the face of /set statements.
+.Pp
+The
+.Sy netbsd6
+flavor does not replicate the historical bug that reported time as
+seconds.nanoseconds without zero padding nanosecond values less than
+100000000.
 .Sh SEE ALSO
 .Xr chflags 1 ,
 .Xr chgrp 1 ,
 .Xr chmod 1 ,
 .Xr cksum 1 ,
-.Xr md5 1 ,
 .Xr stat 2 ,
+.Xr fnmatch 3 ,
 .Xr fts 3 ,
-.Xr md5 3 ,
-.Xr chown 8
+.Xr strsvis 3 ,
+.Xr chown 8 ,
+.Xr mknod 8
 .Sh HISTORY
 The
 .Nm
 utility appeared in
 .Bx 4.3 Reno .
 The
-.Tn MD5
-digest capability was added in
-.Fx 2.1 ,
-in response to the widespread use of programs which can spoof
-.Xr cksum 1 .
+.Sy optional
+keyword appeared in
+.Nx 1.2 .
 The
-.Tn SHA-1
+.Fl U
+option appeared in
+.Nx 1.3 .
+The
+.Sy flags
 and
-.Tn RIPEMD160
-digests were added in
-.Fx 4.0 ,
-as new attacks have demonstrated weaknesses in
-.Tn MD5 .
-Support for file flags was added in
-.Fx 4.0 ,
-and mostly comes from
-.Nx .
+.Sy md5
+keywords, and
+.Fl i
+and
+.Fl m
+options
+appeared in
+.Nx 1.4 .
+The
+.Sy device ,
+.Sy rmd160 ,
+.Sy sha1 ,
+.Sy tags ,
+and
+.Sy all
+keywords,
+.Fl D ,
+.Fl E ,
+.Fl I ,
+.Fl L ,
+.Fl l ,
+.Fl N ,
+.Fl P ,
+.Fl R ,
+.Fl W ,
+and
+.Fl X
+options, and support for full paths appeared in
+.Nx 1.6 .
+The
+.Sy sha256 ,
+.Sy sha384 ,
+and
+.Sy sha512
+keywords appeared in
+.Nx 3.0 .
+The
+.Fl S
+option appeared in
+.Nx 6.0 .
diff --git a/usr.sbin/mtree/mtree.c b/usr.sbin/mtree/mtree.c
index 74c6908dbc..f518681054 100644
--- a/usr.sbin/mtree/mtree.c
+++ b/usr.sbin/mtree/mtree.c
@@ -1,3 +1,5 @@
+/*	$NetBSD: mtree.c,v 1.50 2015/01/23 02:27:01 christos Exp $	*/
+
 /*-
  * Copyright (c) 1989, 1990, 1993
  *	The Regents of the University of California.  All rights reserved.
@@ -25,102 +27,205 @@
  * 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) 1989, 1990, 1993 The Regents of the University of California.  All rights reserved.
- * @(#)mtree.c	8.1 (Berkeley) 6/6/93
- * $FreeBSD: src/usr.sbin/mtree/mtree.c,v 1.8.2.3 2003/05/07 17:55:17 tobez Exp $
  */
 
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__COPYRIGHT) && !defined(lint)
+__COPYRIGHT("@(#) Copyright (c) 1989, 1990, 1993\
+ The Regents of the University of California.  All rights reserved.");
+#endif /* not lint */
+
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)mtree.c	8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: mtree.c,v 1.50 2015/01/23 02:27:01 christos Exp $");
+#endif
+#endif /* not lint */
+
 #include <sys/param.h>
 #include <sys/stat.h>
-#include <err.h>
+
 #include <errno.h>
-#include <fts.h>
 #include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 #include <unistd.h>
-#include "mtree.h"
+
 #include "extern.h"
 
-int ftsoptions = FTS_PHYSICAL;
-int cflag, dflag, eflag, iflag, nflag, qflag, rflag, sflag, uflag, Uflag;
-u_int keys;
-char fullpath[MAXPATHLEN];
+int	ftsoptions = FTS_PHYSICAL;
+int	bflag, dflag, eflag, iflag, jflag, lflag, mflag, nflag, qflag, rflag,
+	sflag, tflag, uflag;
+char	fullpath[MAXPATHLEN];
+
+static struct {
+	enum flavor flavor;
+	const char name[9];
+} flavors[] = {
+	{F_MTREE, "mtree"},
+	{F_FREEBSD9, "freebsd9"},
+	{F_NETBSD6, "netbsd6"},
+};
 
-static void usage(void);
+__dead2 static	void	usage(void);
 
 int
-main(int argc, char *argv[])
+main(int argc, char **argv)
 {
-	int ch;
-	char *dir, *p;
-	int status;
+	int	ch, status;
+	unsigned int	i;
+	int	cflag, Cflag, Dflag, Uflag, wflag;
+	char	*dir, *p;
+	FILE	*spec1, *spec2;
 
+	setprogname(argv[0]);
+
+	cflag = Cflag = Dflag = Uflag = wflag = 0;
 	dir = NULL;
-	keys = KEYDEFAULT;
 	init_excludes();
+	spec1 = stdin;
+	spec2 = NULL;
 
-	while ((ch = getopt(argc, argv, "cdef:iK:k:LnPp:qrs:UuxX:")) != -1)
+	while ((ch = getopt(argc, argv,
+	    "bcCdDeE:f:F:I:ijk:K:lLmMnN:O:p:PqrR:s:StuUwWxX:"))
+	    != -1) {
 		switch((char)ch) {
+		case 'b':
+			bflag = 1;
+			break;
 		case 'c':
 			cflag = 1;
 			break;
+		case 'C':
+			Cflag = 1;
+			break;
 		case 'd':
 			dflag = 1;
 			break;
+		case 'D':
+			Dflag = 1;
+			break;
+		case 'E':
+			parsetags(&excludetags, optarg);
+			break;
 		case 'e':
 			eflag = 1;
 			break;
 		case 'f':
-			if (!(freopen(optarg, "r", stdin)))
-				err(1, "%s", optarg);
+			if (spec1 == stdin) {
+				spec1 = fopen(optarg, "r");
+				if (spec1 == NULL)
+					mtree_err("%s: %s", optarg,
+					    strerror(errno));
+			} else if (spec2 == NULL) {
+				spec2 = fopen(optarg, "r");
+				if (spec2 == NULL)
+					mtree_err("%s: %s", optarg,
+					    strerror(errno));
+			} else
+				usage();
+			break;
+		case 'F':
+			for (i = 0; i < NELEM(flavors); i++)
+				if (strcmp(optarg, flavors[i].name) == 0) {
+					flavor = flavors[i].flavor;
+					break;
+				}
+			if (i == NELEM(flavors))
+				usage();
 			break;
 		case 'i':
 			iflag = 1;
 			break;
-		case 'K':
+		case 'I':
+			parsetags(&includetags, optarg);
+			break;
+		case 'j':
+			jflag = 1;
+			break;
+		case 'k':
+			keys = F_TYPE;
 			while ((p = strsep(&optarg, " \t,")) != NULL)
 				if (*p != '\0')
 					keys |= parsekey(p, NULL);
 			break;
-		case 'k':
-			keys = F_TYPE;
+		case 'K':
 			while ((p = strsep(&optarg, " \t,")) != NULL)
 				if (*p != '\0')
 					keys |= parsekey(p, NULL);
 			break;
+		case 'l':
+			lflag = 1;
+			break;
 		case 'L':
 			ftsoptions &= ~FTS_PHYSICAL;
 			ftsoptions |= FTS_LOGICAL;
 			break;
+		case 'm':
+			mflag = 1;
+			break;
+		case 'M':
+			mtree_Mflag = 1;
+			break;
 		case 'n':
 			nflag = 1;
 			break;
-		case 'P':
-			ftsoptions &= ~FTS_LOGICAL;
-			ftsoptions |= FTS_PHYSICAL;
+		case 'N':
+			if (! setup_getid(optarg))
+				mtree_err(
+			    "Unable to use user and group databases in `%s'",
+				    optarg);
+			break;
+		case 'O':
+			load_only(optarg);
 			break;
 		case 'p':
 			dir = optarg;
 			break;
+		case 'P':
+			ftsoptions &= ~FTS_LOGICAL;
+			ftsoptions |= FTS_PHYSICAL;
+			break;
 		case 'q':
 			qflag = 1;
 			break;
 		case 'r':
-			rflag = 1;
+			rflag++;
+			break;
+		case 'R':
+			while ((p = strsep(&optarg, " \t,")) != NULL)
+				if (*p != '\0')
+					keys &= ~parsekey(p, NULL);
 			break;
 		case 's':
 			sflag = 1;
 			crc_total = ~strtol(optarg, &p, 0);
 			if (*p)
-				errx(1, "illegal seed value -- %s", optarg);
+				mtree_err("illegal seed value -- %s", optarg);
 			break;
-		case 'U':
-			Uflag = 1;
-			uflag = 1;
+		case 'S':
+			mtree_Sflag = 1;
+			break;
+		case 't':
+			tflag = 1;
 			break;
 		case 'u':
 			uflag = 1;
 			break;
+		case 'U':
+			Uflag = uflag = 1;
+			break;
+		case 'w':
+			wflag = 1;
+			break;
+		case 'W':
+			mtree_Wflag = 1;
+			break;
 		case 'x':
 			ftsoptions |= FTS_XDEV;
 			break;
@@ -131,24 +236,78 @@ main(int argc, char *argv[])
 		default:
 			usage();
 		}
+	}
 	argc -= optind;
 	argv += optind;
 
 	if (argc)
 		usage();
 
+	switch (flavor) {
+	case F_FREEBSD9:
+		if (cflag && iflag) {
+			warnx("-c and -i passed, replacing -i with -j for "
+			    "FreeBSD compatibility");
+			iflag = 0;
+			jflag = 1;
+		}
+		if (dflag && !bflag) {
+			warnx("Adding -b to -d for FreeBSD compatibility");
+			bflag = 1;
+		}
+		if (uflag && !iflag) {
+			warnx("Adding -i to -%c for FreeBSD compatibility",
+			    Uflag ? 'U' : 'u');
+			iflag = 1;
+		}
+		if (uflag && !tflag) {
+			warnx("Adding -t to -%c for FreeBSD compatibility",
+			    Uflag ? 'U' : 'u');
+			tflag = 1;
+		}
+		if (wflag)
+			warnx("The -w flag is a no-op");
+		break;
+	default:
+		if (wflag)
+			usage();
+	}
+
+	if (spec2 && (cflag || Cflag || Dflag))
+		mtree_err("Double -f, -c, -C and -D flags are mutually "
+		    "exclusive");
+
+	if (dir && spec2)
+		mtree_err("Double -f and -p flags are mutually exclusive");
+
 	if (dir && chdir(dir))
-		err(1, "%s", dir);
+		mtree_err("%s: %s", dir, strerror(errno));
 
 	if ((cflag || sflag) && !getcwd(fullpath, sizeof(fullpath)))
-		errx(1, "%s", fullpath);
+		mtree_err("%s", strerror(errno));
+
+	if ((cflag && Cflag) || (cflag && Dflag) || (Cflag && Dflag))
+		mtree_err("-c, -C and -D flags are mutually exclusive");
+
+	if (iflag && mflag)
+		mtree_err("-i and -m flags are mutually exclusive");
+
+	if (lflag && uflag)
+		mtree_err("-l and -u flags are mutually exclusive");
 
 	if (cflag) {
-		cwalk();
+		cwalk(stdout);
+		exit(0);
+	}
+	if (Cflag || Dflag) {
+		dump_nodes(stdout, "", spec(spec1), Dflag);
 		exit(0);
 	}
-	status = verify();
-	if (Uflag & (status == MISMATCHEXIT))
+	if (spec2 != NULL)
+		status = mtree_specspec(spec1, spec2);
+	else
+		status = verify(spec1);
+	if (Uflag && (status == MISMATCHEXIT))
 		status = 0;
 	exit(status);
 }
@@ -156,9 +315,18 @@ main(int argc, char *argv[])
 static void
 usage(void)
 {
+	unsigned int i;
 
 	fprintf(stderr,
-"usage: mtree [-LPUcdeinqrux] [-f spec] [-K key] [-k key] [-p path] [-s seed]\n"
-"\t[-X excludes]\n");
+	    "usage: %s [-bCcDdejLlMnPqrStUuWx] [-i|-m] [-E tags]\n"
+	    "\t\t[-f spec] [-f spec]\n"
+	    "\t\t[-I tags] [-K keywords] [-k keywords] [-N dbdir] [-p path]\n"
+	    "\t\t[-R keywords] [-s seed] [-X exclude-file]\n"
+	    "\t\t[-F flavor]\n",
+	    getprogname());
+	fprintf(stderr, "\nflavors:");
+	for (i = 0; i < NELEM(flavors); i++)
+		fprintf(stderr, " %s", flavors[i].name);
+	fprintf(stderr, "\n");
 	exit(1);
 }
diff --git a/usr.sbin/mtree/mtree.h b/usr.sbin/mtree/mtree.h
index be38ef9584..93d6cdfb0e 100644
--- a/usr.sbin/mtree/mtree.h
+++ b/usr.sbin/mtree/mtree.h
@@ -1,3 +1,5 @@
+/*	$NetBSD: mtree.h,v 1.31 2012/10/05 09:17:29 wiz Exp $	*/
+
 /*-
  * Copyright (c) 1990, 1993
  *	The Regents of the University of California.  All rights reserved.
@@ -27,15 +29,13 @@
  * SUCH DAMAGE.
  *
  *	@(#)mtree.h	8.1 (Berkeley) 6/6/93
- * $FreeBSD: src/usr.sbin/mtree/mtree.h,v 1.5 1999/12/09 20:38:35 joe Exp $
- * $DragonFly: src/usr.sbin/mtree/mtree.h,v 1.2 2003/06/17 04:29:57 dillon Exp $
  */
 
-#include <string.h>
-#include <stdlib.h>
+#ifndef _MTREE_H_
+#define	_MTREE_H_
 
-#define	KEYDEFAULT \
-	(F_GID | F_MODE | F_NLINK | F_SIZE | F_SLINK | F_TIME | F_UID | F_FLAGS)
+#define	KEYDEFAULT	(F_GID | F_MODE | F_NLINK | F_SIZE | F_SLINK | \
+			F_TIME | F_TYPE | F_UID | F_FLAGS)
 
 #define	MISMATCHEXIT	2
 
@@ -44,40 +44,55 @@ typedef struct _node {
 	struct _node	*prev, *next;		/* left, right */
 	off_t	st_size;			/* size */
 	struct timespec	st_mtimespec;		/* last modification time */
-	u_long	cksum;				/* check sum */
-	char	*md5digest;			/* MD5 digest */
-	char	*sha1digest;			/* SHA-1 digest */
-	char	*rmd160digest;			/* RIPEMD160 digest */
 	char	*slink;				/* symbolic link reference */
 	uid_t	st_uid;				/* uid */
 	gid_t	st_gid;				/* gid */
 #define	MBITS	(S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO)
 	mode_t	st_mode;			/* mode */
+	dev_t	st_rdev;			/* device type */
 	u_long	st_flags;			/* flags */
 	nlink_t	st_nlink;			/* link count */
+	u_long	cksum;				/* check sum */
+	char	*md5digest;			/* MD5 digest */
+	char	*rmd160digest;			/* RMD-160 digest */
+	char	*sha1digest;			/* SHA1 digest */
+	char	*sha256digest;			/* SHA256 digest */
+	char	*sha384digest;			/* SHA384 digest */
+	char	*sha512digest;			/* SHA512 digest */
+	char	*tags;				/* tags, comma delimited,
+						 * also with leading and
+						 * trailing commas */
+	size_t	lineno;				/* line # entry came from */
+
+#define	F_CKSUM		0x00000001		/* cksum(1) check sum */
+#define	F_DEV		0x00000002		/* device type */
+#define	F_DONE		0x00000004		/* directory done */
+#define	F_FLAGS		0x00000008		/* file flags */
+#define	F_GID		0x00000010		/* gid */
+#define	F_GNAME		0x00000020		/* group name */
+#define	F_IGN		0x00000040		/* ignore */
+#define	F_MAGIC		0x00000080		/* name has magic chars */
+#define	F_MD5		0x00000100		/* MD5 digest */
+#define	F_MODE		0x00000200		/* mode */
+#define	F_NLINK		0x00000400		/* number of links */
+#define	F_OPT		0x00000800		/* existence optional */
+#define	F_RMD160	0x00001000		/* RMD-160 digest */
+#define	F_SHA1		0x00002000		/* SHA1 digest */
+#define	F_SIZE		0x00004000		/* size */
+#define	F_SLINK		0x00008000		/* symbolic link */
+#define	F_TAGS		0x00010000		/* tags */
+#define	F_TIME		0x00020000		/* modification time */
+#define	F_TYPE		0x00040000		/* file type */
+#define	F_UID		0x00080000		/* uid */
+#define	F_UNAME		0x00100000		/* user name */
+#define	F_VISIT		0x00200000		/* file visited */
+#define	F_NOCHANGE	0x00400000		/* check existence, but not */
+						/* other properties */
+#define	F_SHA256	0x00800000		/* SHA256 digest */
+#define	F_SHA384	0x01000000		/* SHA384 digest */
+#define	F_SHA512	0x02000000		/* SHA512 digest */
 
-#define	F_CKSUM	0x0001				/* check sum */
-#define	F_DONE	0x0002				/* directory done */
-#define	F_GID	0x0004				/* gid */
-#define	F_GNAME	0x0008				/* group name */
-#define	F_IGN	0x0010				/* ignore */
-#define	F_MAGIC	0x0020				/* name has magic chars */
-#define	F_MODE	0x0040				/* mode */
-#define	F_NLINK	0x0080				/* number of links */
-#define	F_SIZE	0x0100				/* size */
-#define	F_SLINK	0x0200				/* link count */
-#define	F_TIME	0x0400				/* modification time */
-#define	F_TYPE	0x0800				/* file type */
-#define	F_UID	0x1000				/* uid */
-#define	F_UNAME	0x2000				/* user name */
-#define	F_VISIT	0x4000				/* file visited */
-#define F_MD5	0x8000				/* MD5 digest */
-#define F_NOCHANGE 0x10000			/* If owner/mode "wrong", do */
-						/* not change */
-#define	F_SHA1	0x20000				/* SHA-1 digest */
-#define	F_RMD160 0x40000			/* RIPEMD160 digest */
-#define	F_FLAGS	0x80000				/* file flags */
-	u_int	flags;				/* items set */
+	int	flags;				/* items set */
 
 #define	F_BLOCK	0x001				/* block special */
 #define	F_CHAR	0x002				/* char special */
@@ -86,11 +101,58 @@ typedef struct _node {
 #define	F_FILE	0x010				/* regular file */
 #define	F_LINK	0x020				/* symbolic link */
 #define	F_SOCK	0x040				/* socket */
-	u_char	type;				/* file type */
+#define	F_DOOR	0x080				/* door */
+	int	type;				/* file type */
 
 	char	name[1];			/* file name (must be last) */
 } NODE;
 
+
+typedef struct {
+	char  **list;
+	int	count;
+} slist_t;
+
+
+/*
+ * prototypes for functions published to other programs which want to use
+ * the specfile parser but don't want to pull in all of "extern.h"
+ */
+const char	*inotype(u_int);
+u_int		 nodetoino(u_int);
+int		 setup_getid(const char *);
+NODE		*spec(FILE *);
+int		 mtree_specspec(FILE *, FILE *);
+void		 free_nodes(NODE *);
+char		*vispath(const char *);
+
+#ifdef __FreeBSD__
+#define KEY_DIGEST "digest"
+#else
+#define KEY_DIGEST
+#endif
+
+#define	MD5KEY		"md5"		KEY_DIGEST
+#ifdef __FreeBSD__
+#define	RMD160KEY	"ripemd160"	KEY_DIGEST
+#else
+#define	RMD160KEY	"rmd160"	KEY_DIGEST
+#endif
+#define	SHA1KEY		"sha1"		KEY_DIGEST
+#define	SHA256KEY	"sha256"	KEY_DIGEST
+#define	SHA384KEY	"sha384"
+#define	SHA512KEY	"sha512"
+
 #define	RP(p)	\
 	((p)->fts_path[0] == '.' && (p)->fts_path[1] == '/' ? \
 	    (p)->fts_path + 2 : (p)->fts_path)
+
+#define	UF_MASK ((UF_NODUMP | UF_IMMUTABLE |   \
+                  UF_APPEND | UF_OPAQUE)       \
+                    & UF_SETTABLE)              /* user settable flags */
+#define	SF_MASK ((SF_ARCHIVED | SF_IMMUTABLE | \
+                  SF_APPEND) & SF_SETTABLE)     /* root settable flags */
+#define	CH_MASK  (UF_MASK | SF_MASK)            /* all settable flags */
+#define	SP_FLGS  (SF_IMMUTABLE | SF_APPEND)     /* special flags */
+
+#endif /* _MTREE_H_ */
diff --git a/usr.sbin/mtree/only.c b/usr.sbin/mtree/only.c
new file mode 100644
index 0000000000..cf63cbe035
--- /dev/null
+++ b/usr.sbin/mtree/only.c
@@ -0,0 +1,153 @@
+/*	$NetBSD: only.c,v 1.3 2017/09/07 04:04:13 nakayama Exp $	*/
+
+/*-
+ * Copyright (c) 2013 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * 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 NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+
+#if defined(__RCSID) && !defined(lint)
+__RCSID("$NetBSD: only.c,v 1.3 2017/09/07 04:04:13 nakayama Exp $");
+#endif
+
+#include <sys/param.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <time.h>
+#include <err.h>
+#include <util.h>
+
+#include "extern.h"
+
+struct hentry {
+	char *str;
+	uint32_t hash;
+	struct hentry *next;
+};
+
+static struct hentry *table[1024];
+static bool loaded;
+
+static uint32_t
+hash_str(const char *str)
+{
+	const uint8_t *s = (const uint8_t *)str;
+	uint8_t c;
+	uint32_t hash = 0;
+	while ((c = *s++) != '\0') 
+		hash = hash * 33 + c;           /* "perl": k=33, r=r+r/32 */
+	return hash + (hash >> 5);
+}
+
+static bool
+hash_find(const char *str, uint32_t *h)
+{
+	struct hentry *e;
+	*h = hash_str(str) % NELEM(table);
+
+	for (e = table[*h]; e; e = e->next)
+		if (e->hash == *h && strcmp(e->str, str) == 0)
+			return true;
+	return false;
+}
+
+static void
+hash_insert(char *str, uint32_t h)
+{
+	struct hentry *e;
+	char *x;
+
+	if ((e = malloc(sizeof(*e))) == NULL)
+		mtree_err("memory allocation error");
+	if ((x = strdup(str)) == NULL)
+		mtree_err("memory allocation error");
+
+	e->str = x;
+	e->hash = h;
+	e->next = table[h];
+	table[h] = e;
+}
+
+static void
+fill(char *str)
+{
+	uint32_t h;
+	char *ptr = strrchr(str, '/');
+
+	if (ptr == NULL)
+		return;
+
+	*ptr = '\0';
+	if (!hash_find(str, &h)) {
+		hash_insert(str, h);
+		fill(str);
+	}
+	*ptr = '/';
+}
+
+void
+load_only(const char *fname)
+{
+	FILE *fp;
+	char *line;
+	size_t len, lineno;
+
+	if ((fp = fopen(fname, "r")) == NULL)
+		err(1, "Cannot open `%s'", fname);
+
+	while ((line = fparseln(fp, &len, &lineno, NULL, FPARSELN_UNESCALL))) {
+		uint32_t h;
+		if (hash_find(line, &h))
+			err(1, "Duplicate entry %s", line);
+		hash_insert(line, h);
+		fill(line);
+		free(line);
+	}
+
+	fclose(fp);
+	loaded = true;
+}
+
+bool
+find_only(const char *path)
+{
+	uint32_t h;
+
+	if (!loaded)
+		return true;
+	return hash_find(path, &h);
+}
diff --git a/usr.sbin/mtree/pack_dev.c b/usr.sbin/mtree/pack_dev.c
new file mode 100644
index 0000000000..6f8a0b9e38
--- /dev/null
+++ b/usr.sbin/mtree/pack_dev.c
@@ -0,0 +1,290 @@
+/*	$NetBSD: pack_dev.c,v 1.12 2013/06/14 16:28:20 tsutsui Exp $	*/
+
+/*-
+ * Copyright (c) 1998, 2001 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Charles M. Hannum.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__RCSID("$NetBSD: pack_dev.c,v 1.12 2013/06/14 16:28:20 tsutsui Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pack_dev.h"
+
+static	pack_t	pack_netbsd;
+static	pack_t	pack_freebsd;
+static	pack_t	pack_8_8;
+static	pack_t	pack_12_20;
+static	pack_t	pack_14_18;
+static	pack_t	pack_8_24;
+static	pack_t	pack_bsdos;
+static	int	compare_format(const void *, const void *);
+
+static const char iMajorError[] = "invalid major number";
+static const char iMinorError[] = "invalid minor number";
+static const char tooManyFields[] = "too many fields for format";
+
+	/* exported */
+dev_t
+pack_native(int n, u_long numbers[], const char **error)
+{
+	dev_t dev = 0;
+
+	if (n == 2) {
+		dev = makedev(numbers[0], numbers[1]);
+		if ((u_long)major(dev) != numbers[0])
+			*error = iMajorError;
+		else if ((u_long)minor(dev) != numbers[1])
+			*error = iMinorError;
+	} else
+		*error = tooManyFields;
+	return (dev);
+}
+
+
+static dev_t
+pack_netbsd(int n, u_long numbers[], const char **error)
+{
+	dev_t dev = 0;
+
+	if (n == 2) {
+		dev = makedev_netbsd(numbers[0], numbers[1]);
+		if ((u_long)major_netbsd(dev) != numbers[0])
+			*error = iMajorError;
+		else if ((u_long)minor_netbsd(dev) != numbers[1])
+			*error = iMinorError;
+	} else
+		*error = tooManyFields;
+	return (dev);
+}
+
+
+#define	major_freebsd(x)	((int32_t)(((x) & 0x0000ff00) >> 8))
+#define	minor_freebsd(x)	((int32_t)(((x) & 0xffff00ff) >> 0))
+#define	makedev_freebsd(x,y)	((dev_t)((((x) << 8) & 0x0000ff00) | \
+					 (((y) << 0) & 0xffff00ff)))
+
+static dev_t
+pack_freebsd(int n, u_long numbers[], const char **error)
+{
+	dev_t dev = 0;
+
+	if (n == 2) {
+		dev = makedev_freebsd(numbers[0], numbers[1]);
+		if ((u_long)major_freebsd(dev) != numbers[0])
+			*error = iMajorError;
+		if ((u_long)minor_freebsd(dev) != numbers[1])
+			*error = iMinorError;
+	} else
+		*error = tooManyFields;
+	return (dev);
+}
+
+
+#define	major_8_8(x)		((int32_t)(((x) & 0x0000ff00) >> 8))
+#define	minor_8_8(x)		((int32_t)(((x) & 0x000000ff) >> 0))
+#define	makedev_8_8(x,y)	((dev_t)((((x) << 8) & 0x0000ff00) | \
+					 (((y) << 0) & 0x000000ff)))
+
+static dev_t
+pack_8_8(int n, u_long numbers[], const char **error)
+{
+	dev_t dev = 0;
+
+	if (n == 2) {
+		dev = makedev_8_8(numbers[0], numbers[1]);
+		if ((u_long)major_8_8(dev) != numbers[0])
+			*error = iMajorError;
+		if ((u_long)minor_8_8(dev) != numbers[1])
+			*error = iMinorError;
+	} else
+		*error = tooManyFields;
+	return (dev);
+}
+
+
+#define	major_12_20(x)		((int32_t)(((x) & 0xfff00000) >> 20))
+#define	minor_12_20(x)		((int32_t)(((x) & 0x000fffff) >>  0))
+#define	makedev_12_20(x,y)	((dev_t)((((x) << 20) & 0xfff00000) | \
+					 (((y) <<  0) & 0x000fffff)))
+
+static dev_t
+pack_12_20(int n, u_long numbers[], const char **error)
+{
+	dev_t dev = 0;
+
+	if (n == 2) {
+		dev = makedev_12_20(numbers[0], numbers[1]);
+		if ((u_long)major_12_20(dev) != numbers[0])
+			*error = iMajorError;
+		if ((u_long)minor_12_20(dev) != numbers[1])
+			*error = iMinorError;
+	} else
+		*error = tooManyFields;
+	return (dev);
+}
+
+
+#define	major_14_18(x)		((int32_t)(((x) & 0xfffc0000) >> 18))
+#define	minor_14_18(x)		((int32_t)(((x) & 0x0003ffff) >>  0))
+#define	makedev_14_18(x,y)	((dev_t)((((x) << 18) & 0xfffc0000) | \
+					 (((y) <<  0) & 0x0003ffff)))
+
+static dev_t
+pack_14_18(int n, u_long numbers[], const char **error)
+{
+	dev_t dev = 0;
+
+	if (n == 2) {
+		dev = makedev_14_18(numbers[0], numbers[1]);
+		if ((u_long)major_14_18(dev) != numbers[0])
+			*error = iMajorError;
+		if ((u_long)minor_14_18(dev) != numbers[1])
+			*error = iMinorError;
+	} else
+		*error = tooManyFields;
+	return (dev);
+}
+
+
+#define	major_8_24(x)		((int32_t)(((x) & 0xff000000) >> 24))
+#define	minor_8_24(x)		((int32_t)(((x) & 0x00ffffff) >>  0))
+#define	makedev_8_24(x,y)	((dev_t)((((x) << 24) & 0xff000000) | \
+					 (((y) <<  0) & 0x00ffffff)))
+
+static dev_t
+pack_8_24(int n, u_long numbers[], const char **error)
+{
+	dev_t dev = 0;
+
+	if (n == 2) {
+		dev = makedev_8_24(numbers[0], numbers[1]);
+		if ((u_long)major_8_24(dev) != numbers[0])
+			*error = iMajorError;
+		if ((u_long)minor_8_24(dev) != numbers[1])
+			*error = iMinorError;
+	} else
+		*error = tooManyFields;
+	return (dev);
+}
+
+
+#define	major_12_12_8(x)	((int32_t)(((x) & 0xfff00000) >> 20))
+#define	unit_12_12_8(x)		((int32_t)(((x) & 0x000fff00) >>  8))
+#define	subunit_12_12_8(x)	((int32_t)(((x) & 0x000000ff) >>  0))
+#define	makedev_12_12_8(x,y,z)	((dev_t)((((x) << 20) & 0xfff00000) | \
+					 (((y) <<  8) & 0x000fff00) | \
+					 (((z) <<  0) & 0x000000ff)))
+
+static dev_t
+pack_bsdos(int n, u_long numbers[], const char **error)
+{
+	dev_t dev = 0;
+
+	if (n == 2) {
+		dev = makedev_12_20(numbers[0], numbers[1]);
+		if ((u_long)major_12_20(dev) != numbers[0])
+			*error = iMajorError;
+		if ((u_long)minor_12_20(dev) != numbers[1])
+			*error = iMinorError;
+	} else if (n == 3) {
+		dev = makedev_12_12_8(numbers[0], numbers[1], numbers[2]);
+		if ((u_long)major_12_12_8(dev) != numbers[0])
+			*error = iMajorError;
+		if ((u_long)unit_12_12_8(dev) != numbers[1])
+			*error = "invalid unit number";
+		if ((u_long)subunit_12_12_8(dev) != numbers[2])
+			*error = "invalid subunit number";
+	} else
+		*error = tooManyFields;
+	return (dev);
+}
+
+
+		/* list of formats and pack functions */
+		/* this list must be sorted lexically */
+static struct format {
+	const char	*name;
+	pack_t		*pack;
+} formats[] = {
+	{"386bsd",  pack_8_8},
+	{"4bsd",    pack_8_8},
+	{"bsdos",   pack_bsdos},
+	{"freebsd", pack_freebsd},
+	{"hpux",    pack_8_24},
+	{"isc",     pack_8_8},
+	{"linux",   pack_8_8},
+	{"native",  pack_native},
+	{"netbsd",  pack_netbsd},
+	{"osf1",    pack_12_20},
+	{"sco",     pack_8_8},
+	{"solaris", pack_14_18},
+	{"sunos",   pack_8_8},
+	{"svr3",    pack_8_8},
+	{"svr4",    pack_14_18},
+	{"ultrix",  pack_8_8},
+};
+
+static int
+compare_format(const void *key, const void *element)
+{
+	const char		*name;
+	const struct format	*format;
+
+	name = key;
+	format = element;
+
+	return (strcmp(name, format->name));
+}
+
+
+pack_t *
+pack_find(const char *name)
+{
+	struct format	*format;
+
+	format = bsearch(name, formats,
+	    sizeof(formats)/sizeof(formats[0]),
+	    sizeof(formats[0]), compare_format);
+	if (format == 0)
+		return (NULL);
+	return (format->pack);
+}
diff --git a/usr.sbin/mtree/pack_dev.h b/usr.sbin/mtree/pack_dev.h
new file mode 100644
index 0000000000..d0cb75e6dc
--- /dev/null
+++ b/usr.sbin/mtree/pack_dev.h
@@ -0,0 +1,47 @@
+/*	$NetBSD: pack_dev.h,v 1.8 2013/06/14 16:28:20 tsutsui Exp $	*/
+
+/*-
+ * Copyright (c) 1998, 2001 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Charles M. Hannum.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#ifndef	_PACK_DEV_H
+#define	_PACK_DEV_H
+
+typedef	dev_t pack_t(int, u_long [], const char **);
+
+pack_t	*pack_find(const char *);
+pack_t	 pack_native;
+
+#define	major_netbsd(x)		((int32_t)((((x) & 0x000fff00) >>  8)))
+#define	minor_netbsd(x)		((int32_t)((((x) & 0xfff00000) >> 12) | \
+					   (((x) & 0x000000ff) >>  0)))
+#define	makedev_netbsd(x,y)	((dev_t)((((x) <<  8) & 0x000fff00) | \
+					 (((y) << 12) & 0xfff00000) | \
+					 (((y) <<  0) & 0x000000ff)))
+
+#endif	/* _PACK_DEV_H */
diff --git a/usr.sbin/mtree/spec.c b/usr.sbin/mtree/spec.c
index 40634c27f8..57891b10c4 100644
--- a/usr.sbin/mtree/spec.c
+++ b/usr.sbin/mtree/spec.c
@@ -1,3 +1,5 @@
+/*	$NetBSD: spec.c,v 1.90 2017/12/14 18:34:41 christos Exp $	*/
+
 /*-
  * Copyright (c) 1989, 1993
  *	The Regents of the University of California.  All rights reserved.
@@ -25,102 +27,135 @@
  * 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-2004 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn of Wasabi Systems.
  *
- * @(#)spec.c	8.1 (Berkeley) 6/6/93
- * $FreeBSD: src/usr.sbin/mtree/spec.c,v 1.13.2.1 2000/06/28 02:33:17 joe Exp $
- * $DragonFly: src/usr.sbin/mtree/spec.c,v 1.6 2004/08/30 19:27:22 eirikn Exp $
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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/types.h>
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)spec.c	8.2 (Berkeley) 4/28/95";
+#else
+__RCSID("$NetBSD: spec.c,v 1.90 2017/12/14 18:34:41 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
 #include <sys/stat.h>
+
+#include <assert.h>
 #include <ctype.h>
-#include <err.h>
 #include <errno.h>
-#include <fts.h>
 #include <grp.h>
 #include <pwd.h>
+#include <stdarg.h>
 #include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
 #include <unistd.h>
 #include <vis.h>
-#include "mtree.h"
+#include <util.h>
+
 #include "extern.h"
+#include "pack_dev.h"
+
+size_t	mtree_lineno;			/* Current spec line number */
+int	mtree_Mflag;			/* Merge duplicate entries */
+int	mtree_Wflag;			/* Don't "whack" permissions */
+int	mtree_Sflag;			/* Sort entries */
 
-int lineno;				/* Current spec line number. */
+static	dev_t	parsedev(char *);
+static	void	replacenode(NODE *, NODE *);
+static	void	set(char *, NODE *);
+static	void	unset(char *, NODE *);
+static	void	addchild(NODE *, NODE *);
+static	int	nodecmp(const NODE *, const NODE *);
+static	int	appendfield(FILE *, int, const char *, ...) __printflike(3, 4);
 
-static void	 set(char *, NODE *);
-static void	 unset(char *, NODE *);
+#define REPLACEPTR(x,v)	do { if ((x)) free((x)); (x) = (v); } while (0)
 
 NODE *
-spec(void)
+spec(FILE *fp)
 {
-	NODE *centry, *last;
-	char *p;
+	NODE *centry, *last, *pathparent, *cur;
+	char *p, *e, *next;
 	NODE ginfo, *root;
-	int c_cur, c_next;
-	char buf[2048];
-
-	centry = last = root = NULL;
-	bzero(&ginfo, sizeof(ginfo));
-	c_cur = c_next = 0;
-	for (lineno = 1; fgets(buf, sizeof(buf), stdin);
-	    ++lineno, c_cur = c_next, c_next = 0) {
-		/* Skip empty lines. */
-		if (buf[0] == '\n')
-			continue;
-
-		/* Find end of line. */
-		if ((p = strchr(buf, '\n')) == NULL)
-			errx(1, "line %d too long", lineno);
-
-		/* See if next line is continuation line. */
-		if (p[-1] == '\\') {
-			--p;
-			c_next = 1;
-		}
-
-		/* Null-terminate the line. */
-		*p = '\0';
+	char *buf, *tname, *ntname;
+	size_t tnamelen, plen;
 
+	root = NULL;
+	centry = last = NULL;
+	tname = NULL;
+	tnamelen = 0;
+	memset(&ginfo, 0, sizeof(ginfo));
+	for (mtree_lineno = 0;
+	    (buf = fparseln(fp, NULL, &mtree_lineno, NULL,
+		FPARSELN_UNESCCOMM));
+	    free(buf)) {
 		/* Skip leading whitespace. */
-		for (p = buf; *p && isspace(*p); ++p);
+		for (p = buf; *p && isspace((unsigned char)*p); ++p)
+			continue;
 
-		/* If nothing but whitespace or comment char, continue. */
-		if (!*p || *p == '#')
+		/* If nothing but whitespace, continue. */
+		if (!*p)
 			continue;
 
 #ifdef DEBUG
-		fprintf(stderr, "line %d: {%s}\n", lineno, p);
+		fprintf(stderr, "line %lu: {%s}\n",
+		    (u_long)mtree_lineno, p);
 #endif
-		if (c_cur) {
-			set(p, centry);
-			continue;
-		}
-
 		/* Grab file name, "$", "set", or "unset". */
-		if ((p = strtok(p, "\n\t ")) == NULL)
-			errx(1, "line %d: missing field", lineno);
-
-		if (p[0] == '/')
-			switch(p[1]) {
-			case 's':
-				if (strcmp(p + 1, "set"))
-					break;
-				set(NULL, &ginfo);
-				continue;
-			case 'u':
-				if (strcmp(p + 1, "unset"))
-					break;
-				unset(NULL, &ginfo);
-				continue;
-			}
+		next = buf;
+		while ((p = strsep(&next, " \t")) != NULL && *p == '\0')
+			continue;
+		if (p == NULL)
+			mtree_err("missing field");
 
-		if (strchr(p, '/'))
-			errx(1, "line %d: slash character in file name",
-			lineno);
+		if (p[0] == '/') {
+			if (strcmp(p + 1, "set") == 0)
+				set(next, &ginfo);
+			else if (strcmp(p + 1, "unset") == 0)
+				unset(next, &ginfo);
+			else
+				mtree_err("invalid specification `%s'", p);
+			continue;
+		}
 
-		if (!strcmp(p, "..")) {
+		if (strcmp(p, "..") == 0) {
 			/* Don't go up, if haven't gone down. */
-			if (!root)
+			if (root == NULL)
 				goto noparent;
 			if (last->type != F_DIR || last->flags & F_DONE) {
 				if (last == root)
@@ -130,173 +165,534 @@ spec(void)
 			last->flags |= F_DONE;
 			continue;
 
-noparent:		errx(1, "line %d: no parent node", lineno);
+noparent:		mtree_err("no parent node");
+		}
+
+		plen = strlen(p) + 1;
+		if (plen > tnamelen) {
+			if ((ntname = realloc(tname, plen)) == NULL)
+				mtree_err("realloc: %s", strerror(errno));
+			tname = ntname;
+			tnamelen = plen;
+		}
+		if (strunvis(tname, p) == -1)
+			mtree_err("strunvis failed on `%s'", p);
+		p = tname;
+
+		pathparent = NULL;
+		if (strchr(p, '/') != NULL) {
+			cur = root;
+			for (; (e = strchr(p, '/')) != NULL; p = e+1) {
+				if (p == e)
+					continue;	/* handle // */
+				*e = '\0';
+				if (strcmp(p, ".") != 0) {
+					while (cur &&
+					    strcmp(cur->name, p) != 0) {
+						cur = cur->next;
+					}
+				}
+				if (cur == NULL || cur->type != F_DIR) {
+					mtree_err("%s: %s", tname,
+					"missing directory in specification");
+				}
+				*e = '/';
+				pathparent = cur;
+				cur = cur->child;
+			}
+			if (*p == '\0')
+				mtree_err("%s: empty leaf element", tname);
 		}
 
 		if ((centry = calloc(1, sizeof(NODE) + strlen(p))) == NULL)
-			errx(1, "calloc");
+			mtree_err("%s", strerror(errno));
 		*centry = ginfo;
+		centry->lineno = mtree_lineno;
+		strcpy(centry->name, p);
 #define	MAGIC	"?*["
 		if (strpbrk(p, MAGIC))
 			centry->flags |= F_MAGIC;
-		if (strunvis(centry->name, p) == -1) {
-			warnx("filename %s is ill-encoded and literally used",
-			    p);
-			strcpy(centry->name, p);
-		}
-		set(NULL, centry);
+		set(next, centry);
 
-		if (!root) {
+		if (root == NULL) {
+				/*
+				 * empty tree
+				 */
+			/*
+			 * Allow a bare "." root node by forcing it to
+			 * type=dir for compatibility with FreeBSD.
+			 */
+			if (strcmp(centry->name, ".") == 0 && centry->type == 0)
+				centry->type = F_DIR;
+			if (strcmp(centry->name, ".") != 0 ||
+			    centry->type != F_DIR)
+				mtree_err(
+				    "root node must be the directory `.'");
 			last = root = centry;
 			root->parent = root;
+		} else if (pathparent != NULL) {
+				/*
+				 * full path entry; add or replace
+				 */
+			centry->parent = pathparent;
+			addchild(pathparent, centry);
+			last = centry;
+		} else if (strcmp(centry->name, ".") == 0) {
+				/*
+				 * duplicate "." entry; always replace
+				 */
+			replacenode(root, centry);
 		} else if (last->type == F_DIR && !(last->flags & F_DONE)) {
+				/*
+				 * new relative child in current dir;
+				 * add or replace
+				 */
 			centry->parent = last;
-			last = last->child = centry;
+			addchild(last, centry);
+			last = centry;
 		} else {
+				/*
+				 * new relative child in parent dir
+				 * (after encountering ".." entry);
+				 * add or replace
+				 */
 			centry->parent = last->parent;
-			centry->prev = last;
-			last = last->next = centry;
+			addchild(last->parent, centry);
+			last = centry;
 		}
 	}
 	return (root);
 }
 
+void
+free_nodes(NODE *root)
+{
+	NODE	*cur, *next;
+
+	if (root == NULL)
+		return;
+
+	next = NULL;
+	for (cur = root; cur != NULL; cur = next) {
+		next = cur->next;
+		free_nodes(cur->child);
+		REPLACEPTR(cur->slink, NULL);
+		REPLACEPTR(cur->md5digest, NULL);
+		REPLACEPTR(cur->rmd160digest, NULL);
+		REPLACEPTR(cur->sha1digest, NULL);
+		REPLACEPTR(cur->sha256digest, NULL);
+		REPLACEPTR(cur->sha384digest, NULL);
+		REPLACEPTR(cur->sha512digest, NULL);
+		REPLACEPTR(cur->tags, NULL);
+		REPLACEPTR(cur, NULL);
+	}
+}
+
+/*
+ * appendfield --
+ *	Like fprintf(), but output a space either before or after
+ *	the regular output, according to the pathlast flag.
+ */
+static int
+appendfield(FILE *fp, int pathlast, const char *fmt, ...)
+{
+	va_list ap;
+	int result;
+
+	va_start(ap, fmt);
+	if (!pathlast)
+		fprintf(fp, " ");
+	result = vprintf(fmt, ap);
+	if (pathlast)
+		fprintf(fp, " ");
+	va_end(ap);
+	return result;
+}
+
+/*
+ * dump_nodes --
+ *	dump the NODEs from `cur', based in the directory `dir'.
+ *	if pathlast is none zero, print the path last, otherwise print
+ *	it first.
+ */
+void
+dump_nodes(FILE *fp, const char *dir, NODE *root, int pathlast)
+{
+	NODE	*cur;
+	char	path[MAXPATHLEN];
+	const char *name;
+	char	*str;
+	char	*p, *q;
+
+	for (cur = root; cur != NULL; cur = cur->next) {
+		if (cur->type != F_DIR && !matchtags(cur))
+			continue;
+
+		if (snprintf(path, sizeof(path), "%s%s%s",
+		    dir, *dir ? "/" : "", cur->name)
+		    >= (int)sizeof(path))
+			mtree_err("Pathname too long.");
+
+		if (!pathlast)
+			fprintf(fp, "%s", vispath(path));
+
+#define MATCHFLAG(f)	((keys & (f)) && (cur->flags & (f)))
+		if (MATCHFLAG(F_TYPE))
+			appendfield(fp, pathlast, "type=%s",
+			    nodetype(cur->type));
+		if (MATCHFLAG(F_UID | F_UNAME)) {
+			if (keys & F_UNAME &&
+			    (name = user_from_uid(cur->st_uid, 1)) != NULL)
+				appendfield(fp, pathlast, "uname=%s", name);
+			else
+				appendfield(fp, pathlast, "uid=%u",
+				    cur->st_uid);
+		}
+		if (MATCHFLAG(F_GID | F_GNAME)) {
+			if (keys & F_GNAME &&
+			    (name = group_from_gid(cur->st_gid, 1)) != NULL)
+				appendfield(fp, pathlast, "gname=%s", name);
+			else
+				appendfield(fp, pathlast, "gid=%u",
+				    cur->st_gid);
+		}
+		if (MATCHFLAG(F_MODE))
+			appendfield(fp, pathlast, "mode=%#o", cur->st_mode);
+		if (MATCHFLAG(F_DEV) &&
+		    (cur->type == F_BLOCK || cur->type == F_CHAR))
+			appendfield(fp, pathlast, "device=%#jx",
+			    (uintmax_t)cur->st_rdev);
+		if (MATCHFLAG(F_NLINK))
+			appendfield(fp, pathlast, "nlink=%ju",
+			    (uintmax_t)cur->st_nlink);
+		if (MATCHFLAG(F_SLINK))
+			appendfield(fp, pathlast, "link=%s",
+			    vispath(cur->slink));
+		if (MATCHFLAG(F_SIZE))
+			appendfield(fp, pathlast, "size=%ju",
+			    (uintmax_t)cur->st_size);
+		if (MATCHFLAG(F_TIME))
+			appendfield(fp, pathlast, "time=%jd.%09ld",
+			    (intmax_t)cur->st_mtimespec.tv_sec,
+			    cur->st_mtimespec.tv_nsec);
+		if (MATCHFLAG(F_CKSUM))
+			appendfield(fp, pathlast, "cksum=%lu", cur->cksum);
+		if (MATCHFLAG(F_MD5))
+			appendfield(fp, pathlast, "%s=%s", MD5KEY,
+			    cur->md5digest);
+		if (MATCHFLAG(F_RMD160))
+			appendfield(fp, pathlast, "%s=%s", RMD160KEY,
+			    cur->rmd160digest);
+		if (MATCHFLAG(F_SHA1))
+			appendfield(fp, pathlast, "%s=%s", SHA1KEY,
+			    cur->sha1digest);
+		if (MATCHFLAG(F_SHA256))
+			appendfield(fp, pathlast, "%s=%s", SHA256KEY,
+			    cur->sha256digest);
+		if (MATCHFLAG(F_SHA384))
+			appendfield(fp, pathlast, "%s=%s", SHA384KEY,
+			    cur->sha384digest);
+		if (MATCHFLAG(F_SHA512))
+			appendfield(fp, pathlast, "%s=%s", SHA512KEY,
+			    cur->sha512digest);
+		if (MATCHFLAG(F_FLAGS)) {
+			str = flags_to_string(cur->st_flags, "none");
+			appendfield(fp, pathlast, "flags=%s", str);
+			free(str);
+		}
+		if (MATCHFLAG(F_IGN))
+			appendfield(fp, pathlast, "ignore");
+		if (MATCHFLAG(F_OPT))
+			appendfield(fp, pathlast, "optional");
+		if (MATCHFLAG(F_TAGS)) {
+			/* don't output leading or trailing commas */
+			p = cur->tags;
+			while (*p == ',')
+				p++;
+			q = p + strlen(p);
+			while(q > p && q[-1] == ',')
+				q--;
+			appendfield(fp, pathlast, "tags=%.*s", (int)(q - p), p);
+		}
+		puts(pathlast ? vispath(path) : "");
+
+		if (cur->child)
+			dump_nodes(fp, path, cur->child, pathlast);
+	}
+}
+
+/*
+ * vispath --
+ *	strsvis(3) encodes path, which must not be longer than MAXPATHLEN
+ *	characters long, and returns a pointer to a static buffer containing
+ *	the result.
+ */
+char *
+vispath(const char *path)
+{
+	static const char extra[] = { ' ', '\t', '\n', '\\', '#', '\0' };
+	static const char extra_glob[] = { ' ', '\t', '\n', '\\', '#', '*',
+	    '?', '[', '\0' };
+	static char pathbuf[4*MAXPATHLEN + 1];
+
+	if (flavor == F_NETBSD6)
+		strsvis(pathbuf, path, VIS_CSTYLE, extra);
+	else
+		strsvis(pathbuf, path, VIS_OCTAL, extra_glob);
+	return pathbuf;
+}
+
+
+static dev_t
+parsedev(char *arg)
+{
+#define MAX_PACK_ARGS	3
+	u_long	numbers[MAX_PACK_ARGS];
+	char	*p, *ep, *dev;
+	int	argc;
+	pack_t	*pack;
+	dev_t	result;
+	const char *error = NULL;
+
+	if ((dev = strchr(arg, ',')) != NULL) {
+		*dev++='\0';
+		if ((pack = pack_find(arg)) == NULL)
+			mtree_err("unknown format `%s'", arg);
+		argc = 0;
+		while ((p = strsep(&dev, ",")) != NULL) {
+			if (*p == '\0')
+				mtree_err("missing number");
+			numbers[argc++] = strtoul(p, &ep, 0);
+			if (*ep != '\0')
+				mtree_err("invalid number `%s'",
+				    p);
+			if (argc > MAX_PACK_ARGS)
+				mtree_err("too many arguments");
+		}
+		if (argc < 2)
+			mtree_err("not enough arguments");
+		result = (*pack)(argc, numbers, &error);
+		if (error != NULL)
+			mtree_err("%s", error);
+	} else {
+		result = (dev_t)strtoul(arg, &ep, 0);
+		if (*ep != '\0')
+			mtree_err("invalid device `%s'", arg);
+	}
+	return (result);
+}
+
+static void
+replacenode(NODE *cur, NODE *new)
+{
+
+#define REPLACE(x)	cur->x = new->x
+#define REPLACESTR(x)	REPLACEPTR(cur->x,new->x)
+
+	if (cur->type != new->type) {
+		if (mtree_Mflag) {
+				/*
+				 * merge entries with different types; we
+				 * don't want children retained in this case.
+				 */
+			REPLACE(type);
+			free_nodes(cur->child);
+			cur->child = NULL;
+		} else {
+			mtree_err(
+			    "existing entry for `%s', type `%s'"
+			    " does not match type `%s'",
+			    cur->name, nodetype(cur->type),
+			    nodetype(new->type));
+		}
+	}
+
+	REPLACE(st_size);
+	REPLACE(st_mtimespec);
+	REPLACESTR(slink);
+	if (cur->slink != NULL) {
+		if ((cur->slink = strdup(new->slink)) == NULL)
+			mtree_err("memory allocation error");
+		if (strunvis(cur->slink, new->slink) == -1)
+			mtree_err("strunvis failed on `%s'", new->slink);
+		free(new->slink);
+	}
+	REPLACE(st_uid);
+	REPLACE(st_gid);
+	REPLACE(st_mode);
+	REPLACE(st_rdev);
+	REPLACE(st_flags);
+	REPLACE(st_nlink);
+	REPLACE(cksum);
+	REPLACESTR(md5digest);
+	REPLACESTR(rmd160digest);
+	REPLACESTR(sha1digest);
+	REPLACESTR(sha256digest);
+	REPLACESTR(sha384digest);
+	REPLACESTR(sha512digest);
+	REPLACESTR(tags);
+	REPLACE(lineno);
+	REPLACE(flags);
+	free(new);
+}
+
 static void
 set(char *t, NODE *ip)
 {
-	int type;
-	char *kw, *val = NULL;
-	struct group *gr;
-	struct passwd *pw;
-	mode_t *m;
-	int value;
-	char *ep;
-
-	for (; (kw = strtok(t, "= \t\n")); t = NULL) {
+	int	type, value, len;
+	gid_t	gid;
+	uid_t	uid;
+	char	*kw, *val, *md, *ep;
+	void	*m;
+
+	while ((kw = strsep(&t, "= \t")) != NULL) {
+		if (*kw == '\0')
+			continue;
+		if (strcmp(kw, "all") == 0)
+			mtree_err("invalid keyword `all'");
 		ip->flags |= type = parsekey(kw, &value);
-		if (value && (val = strtok(NULL, " \t\n")) == NULL)
-			errx(1, "line %d: missing value", lineno);
-		switch(type) {
+		if (!value)
+			/* Just set flag bit (F_IGN and F_OPT) */
+			continue;
+		while ((val = strsep(&t, " \t")) != NULL && *val == '\0')
+			continue;
+		if (val == NULL)
+			mtree_err("missing value");
+		switch (type) {
 		case F_CKSUM:
 			ip->cksum = strtoul(val, &ep, 10);
 			if (*ep)
-				errx(1, "line %d: invalid checksum %s",
-				lineno, val);
-			break;
-		case F_MD5:
-			ip->md5digest = strdup(val);
-			if(!ip->md5digest) {
-				errx(1, "strdup");
-			}
-			break;
-		case F_SHA1:
-			ip->sha1digest = strdup(val);
-			if(!ip->sha1digest) {
-				errx(1, "strdup");
-			}
+				mtree_err("invalid checksum `%s'", val);
 			break;
-		case F_RMD160:
-			ip->rmd160digest = strdup(val);
-			if(!ip->rmd160digest) {
-				errx(1, "strdup");
-			}
+		case F_DEV:
+			ip->st_rdev = parsedev(val);
 			break;
 		case F_FLAGS:
 			if (strcmp("none", val) == 0)
 				ip->st_flags = 0;
-			else if (strtofflags(&val, &ip->st_flags, NULL) != 0)
-				errx(1, "line %d: invalid flag %s",lineno, val);
- 			break;
+			else if (string_to_flags(&val, &ip->st_flags, NULL)
+			    != 0)
+				mtree_err("invalid flag `%s'", val);
+			break;
 		case F_GID:
-			ip->st_gid = strtoul(val, &ep, 10);
+			ip->st_gid = (gid_t)strtoul(val, &ep, 10);
 			if (*ep)
-				errx(1, "line %d: invalid gid %s", lineno, val);
+				mtree_err("invalid gid `%s'", val);
 			break;
 		case F_GNAME:
-			if ((gr = getgrnam(val)) == NULL)
-			    errx(1, "line %d: unknown group %s", lineno, val);
-			ip->st_gid = gr->gr_gid;
+			if (mtree_Wflag)	/* don't parse if whacking */
+				break;
+			if (gid_from_group(val, &gid) == -1)
+				mtree_err("unknown group `%s'", val);
+			ip->st_gid = gid;
 			break;
-		case F_IGN:
-			/* just set flag bit */
+		case F_MD5:
+			if (val[0]=='0' && val[1]=='x')
+				md=&val[2];
+			else
+				md=val;
+			if ((ip->md5digest = strdup(md)) == NULL)
+				mtree_err("memory allocation error");
 			break;
 		case F_MODE:
 			if ((m = setmode(val)) == NULL)
-				errx(1, "line %d: invalid file mode %s",
-				lineno, val);
+				mtree_err("cannot set file mode `%s' (%s)",
+				    val, strerror(errno));
 			ip->st_mode = getmode(m, 0);
 			free(m);
 			break;
 		case F_NLINK:
-			ip->st_nlink = strtoul(val, &ep, 10);
+			ip->st_nlink = (nlink_t)strtoul(val, &ep, 10);
 			if (*ep)
-				errx(1, "line %d: invalid link count %s",
-				lineno,  val);
+				mtree_err("invalid link count `%s'", val);
+			break;
+		case F_RMD160:
+			if (val[0]=='0' && val[1]=='x')
+				md=&val[2];
+			else
+				md=val;
+			if ((ip->rmd160digest = strdup(md)) == NULL)
+				mtree_err("memory allocation error");
+			break;
+		case F_SHA1:
+			if (val[0]=='0' && val[1]=='x')
+				md=&val[2];
+			else
+				md=val;
+			if ((ip->sha1digest = strdup(md)) == NULL)
+				mtree_err("memory allocation error");
 			break;
 		case F_SIZE:
-			ip->st_size = strtoq(val, &ep, 10);
+			ip->st_size = (off_t)strtoll(val, &ep, 10);
 			if (*ep)
-				errx(1, "line %d: invalid size %s",
-				lineno, val);
+				mtree_err("invalid size `%s'", val);
 			break;
 		case F_SLINK:
 			if ((ip->slink = strdup(val)) == NULL)
-				errx(1, "strdup");
+				mtree_err("memory allocation error");
+			if (strunvis(ip->slink, val) == -1)
+				mtree_err("strunvis failed on `%s'", val);
+			break;
+		case F_TAGS:
+			len = strlen(val) + 3;	/* "," + str + ",\0" */
+			if ((ip->tags = malloc(len)) == NULL)
+				mtree_err("memory allocation error");
+			snprintf(ip->tags, len, ",%s,", val);
 			break;
 		case F_TIME:
-			ip->st_mtimespec.tv_sec = strtoul(val, &ep, 10);
+			ip->st_mtimespec.tv_sec =
+			    (time_t)strtoll(val, &ep, 10);
 			if (*ep != '.')
-				errx(1, "line %d: invalid time %s",
-				lineno, val);
+				mtree_err("invalid time `%s'", val);
 			val = ep + 1;
-			ip->st_mtimespec.tv_nsec = strtoul(val, &ep, 10);
+			ip->st_mtimespec.tv_nsec = strtol(val, &ep, 10);
 			if (*ep)
-				errx(1, "line %d: invalid time %s",
-				lineno, val);
+				mtree_err("invalid time `%s'", val);
 			break;
 		case F_TYPE:
-			switch(*val) {
-			case 'b':
-				if (!strcmp(val, "block"))
-					ip->type = F_BLOCK;
-				break;
-			case 'c':
-				if (!strcmp(val, "char"))
-					ip->type = F_CHAR;
-				break;
-			case 'd':
-				if (!strcmp(val, "dir"))
-					ip->type = F_DIR;
-				break;
-			case 'f':
-				if (!strcmp(val, "file"))
-					ip->type = F_FILE;
-				if (!strcmp(val, "fifo"))
-					ip->type = F_FIFO;
-				break;
-			case 'l':
-				if (!strcmp(val, "link"))
-					ip->type = F_LINK;
-				break;
-			case 's':
-				if (!strcmp(val, "socket"))
-					ip->type = F_SOCK;
-				break;
-			default:
-				errx(1, "line %d: unknown file type %s",
-				lineno, val);
-			}
+			ip->type = parsetype(val);
 			break;
 		case F_UID:
-			ip->st_uid = strtoul(val, &ep, 10);
+			ip->st_uid = (uid_t)strtoul(val, &ep, 10);
 			if (*ep)
-				errx(1, "line %d: invalid uid %s", lineno, val);
+				mtree_err("invalid uid `%s'", val);
 			break;
 		case F_UNAME:
-			if ((pw = getpwnam(val)) == NULL)
-			    errx(1, "line %d: unknown user %s", lineno, val);
-			ip->st_uid = pw->pw_uid;
+			if (mtree_Wflag)	/* don't parse if whacking */
+				break;
+			if (uid_from_user(val, &uid) == -1)
+				mtree_err("unknown user `%s'", val);
+			ip->st_uid = uid;
+			break;
+		case F_SHA256:
+			if (val[0]=='0' && val[1]=='x')
+				md=&val[2];
+			else
+				md=val;
+			if ((ip->sha256digest = strdup(md)) == NULL)
+				mtree_err("memory allocation error");
 			break;
+		case F_SHA384:
+			if (val[0]=='0' && val[1]=='x')
+				md=&val[2];
+			else
+				md=val;
+			if ((ip->sha384digest = strdup(md)) == NULL)
+				mtree_err("memory allocation error");
+			break;
+		case F_SHA512:
+			if (val[0]=='0' && val[1]=='x')
+				md=&val[2];
+			else
+				md=val;
+			if ((ip->sha512digest = strdup(md)) == NULL)
+				mtree_err("memory allocation error");
+			break;
+		default:
+			mtree_err(
+			    "set(): unsupported key type 0x%x (INTERNAL ERROR)",
+			    type);
+			/* NOTREACHED */
 		}
 	}
 }
@@ -306,6 +702,152 @@ unset(char *t, NODE *ip)
 {
 	char *p;
 
-	while ((p = strtok(t, "\n\t ")))
+	while ((p = strsep(&t, " \t")) != NULL) {
+		if (*p == '\0')
+			continue;
 		ip->flags &= ~parsekey(p, NULL);
+	}
+}
+
+/*
+ * addchild --
+ *	Add the centry node as a child of the pathparent node.	If
+ *	centry is a duplicate, call replacenode().  If centry is not
+ *	a duplicate, insert it into the linked list referenced by
+ *	pathparent->child.  Keep the list sorted if Sflag is set.
+ */
+static void
+addchild(NODE *pathparent, NODE *centry)
+{
+	NODE *samename;      /* node with the same name as centry */
+	NODE *replacepos;    /* if non-NULL, centry should replace this node */
+	NODE *insertpos;     /* if non-NULL, centry should be inserted
+			      * after this node */
+	NODE *cur;           /* for stepping through the list */
+	NODE *last;          /* the last node in the list */
+	int cmp;
+
+	samename = NULL;
+	replacepos = NULL;
+	insertpos = NULL;
+	last = NULL;
+	cur = pathparent->child;
+	if (cur == NULL) {
+		/* centry is pathparent's first and only child node so far */
+		pathparent->child = centry;
+		return;
+	}
+
+	/*
+	 * pathparent already has at least one other child, so add the
+	 * centry node to the list.
+	 *
+	 * We first scan through the list looking for an existing node
+	 * with the same name (setting samename), and also looking
+	 * for the correct position to replace or insert the new node
+	 * (setting replacepos and/or insertpos).
+	 */
+	for (; cur != NULL; last = cur, cur = cur->next) {
+		if (strcmp(centry->name, cur->name) == 0) {
+			samename = cur;
+		}
+		if (mtree_Sflag) {
+			cmp = nodecmp(centry, cur);
+			if (cmp == 0) {
+				replacepos = cur;
+			} else if (cmp > 0) {
+				insertpos = cur;
+			}
+		}
+	}
+	if (! mtree_Sflag) {
+		if (samename != NULL) {
+			/* replace node with same name */
+			replacepos = samename;
+		} else {
+			/* add new node at end of list */
+			insertpos = last;
+		}
+	}
+
+	if (samename != NULL) {
+		/*
+		 * We found a node with the same name above.  Call
+		 * replacenode(), which will either exit with an error,
+		 * or replace the information in the samename node and
+		 * free the information in the centry node.
+		 */
+		replacenode(samename, centry);
+		if (samename == replacepos) {
+			/* The just-replaced node was in the correct position */
+			return;
+		}
+		if (samename == insertpos || samename->prev == insertpos) {
+			/*
+			 * We thought the new node should be just before
+			 * or just after the replaced node, but that would
+			 * be equivalent to just retaining the replaced node.
+			 */
+			return;
+		}
+
+		/*
+		 * The just-replaced node is in the wrong position in
+		 * the list.  This can happen if sort order depends on
+		 * criteria other than the node name.
+		 *
+		 * Make centry point to the just-replaced node.	 Unlink
+		 * the just-replaced node from the list, and allow it to
+		 * be insterted in the correct position later.
+		 */
+		centry = samename;
+		if (centry->prev)
+			centry->prev->next = centry->next;
+		else {
+			/* centry->next is the new head of the list */
+			pathparent->child = centry->next;
+			assert(centry->next != NULL);
+		}
+		if (centry->next)
+			centry->next->prev = centry->prev;
+		centry->prev = NULL;
+		centry->next = NULL;
+	}
+
+	if (insertpos == NULL) {
+		/* insert centry at the beginning of the list */
+		pathparent->child->prev = centry;
+		centry->next = pathparent->child;
+		centry->prev = NULL;
+		pathparent->child = centry;
+	} else {
+		/* insert centry into the list just after insertpos */
+		centry->next = insertpos->next;
+		insertpos->next = centry;
+		centry->prev = insertpos;
+		if (centry->next)
+			centry->next->prev = centry;
+	}
+	return;
+}
+
+/*
+ * nodecmp --
+ *	used as a comparison function by addchild() to control the order
+ *	in which entries appear within a list of sibling nodes.	 We make
+ *	directories sort after non-directories, but otherwise sort in
+ *	strcmp() order.
+ *
+ * Keep this in sync with dcmp() in create.c.
+ */
+static int
+nodecmp(const NODE *a, const NODE *b)
+{
+
+	if ((a->type & F_DIR) != 0) {
+		if ((b->type & F_DIR) == 0)
+			return 1;
+	} else if ((b->type & F_DIR) != 0)
+		return -1;
+	return strcmp(a->name, b->name);
 }
diff --git a/usr.sbin/mtree/specspec.c b/usr.sbin/mtree/specspec.c
new file mode 100644
index 0000000000..149e7a3078
--- /dev/null
+++ b/usr.sbin/mtree/specspec.c
@@ -0,0 +1,273 @@
+/*	$NetBSD: specspec.c,v 1.4 2017/12/14 18:34:41 christos Exp $	*/
+
+/*-
+ * Copyright (c) 2003 Poul-Henning Kamp
+ * 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 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 AUTHOR 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.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: specspec.c,v 1.4 2017/12/14 18:34:41 christos Exp $");
+
+#include <err.h>
+#include <grp.h>
+#include <pwd.h>
+#include <time.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "mtree.h"
+#include "extern.h"
+
+#define FF(a, b, c, d) \
+	(((a)->flags & (c)) && ((b)->flags & (c)) && ((a)->d) != ((b)->d))
+#define FS(a, b, c, d) \
+	(((a)->flags & (c)) && ((b)->flags & (c)) && strcmp((a)->d,(b)->d))
+#define FM(a, b, c, d) \
+	(((a)->flags & (c)) && ((b)->flags & (c)) && memcmp(&(a)->d,&(b)->d, sizeof (a)->d))
+
+static void
+shownode(NODE *n, int f, char const *path)
+{
+	struct group *gr;
+	struct passwd *pw;
+
+	printf("%s%s %s", path, n->name, inotype(nodetoino(n->type)));
+	if (f & F_CKSUM)
+		printf(" cksum=%lu", n->cksum);
+	if (f & F_GID)
+		printf(" gid=%d", n->st_gid);
+	if (f & F_GNAME) {
+		gr = getgrgid(n->st_gid);
+		if (gr == NULL)
+			printf(" gid=%d", n->st_gid);
+		else
+			printf(" gname=%s", gr->gr_name);
+	}
+	if (f & F_MODE)
+		printf(" mode=%o", n->st_mode);
+	if (f & F_NLINK)
+		printf(" nlink=%ju", (uintmax_t)n->st_nlink);
+	if (f & F_SIZE)
+		printf(" size=%jd", (intmax_t)n->st_size);
+	if (f & F_UID)
+		printf(" uid=%d", n->st_uid);
+	if (f & F_UNAME) {
+		pw = getpwuid(n->st_uid);
+		if (pw == NULL)
+			printf(" uid=%d", n->st_uid);
+		else
+			printf(" uname=%s", pw->pw_name);
+	}
+	if (f & F_MD5)
+		printf(" %s=%s", MD5KEY, n->md5digest);
+	if (f & F_SHA1)
+		printf(" %s=%s", SHA1KEY, n->sha1digest);
+	if (f & F_RMD160)
+		printf(" %s=%s", RMD160KEY, n->rmd160digest);
+	if (f & F_SHA256)
+		printf(" %s=%s", SHA256KEY, n->sha256digest);
+	if (f & F_SHA384)
+		printf(" %s=%s", SHA384KEY, n->sha384digest);
+	if (f & F_SHA512)
+		printf(" %s=%s", SHA512KEY, n->sha512digest);
+	if (f & F_FLAGS)
+		printf(" flags=%s", flags_to_string(n->st_flags, "none"));
+	printf("\n");
+}
+
+static int
+mismatch(NODE *n1, NODE *n2, int differ, char const *path)
+{
+
+	if (n2 == NULL) {
+		shownode(n1, differ, path);
+		return (1);
+	}
+	if (n1 == NULL) {
+		printf("\t");
+		shownode(n2, differ, path);
+		return (1);
+	}
+	if (!(differ & keys))
+		return(0);
+	printf("\t\t");
+	shownode(n1, differ, path);
+	printf("\t\t");
+	shownode(n2, differ, path);
+	return (1);
+}
+
+static int
+compare_nodes(NODE *n1, NODE *n2, char const *path)
+{
+	int differs;
+	
+	if (n1 != NULL && n1->type == F_LINK)
+		n1->flags &= ~F_MODE;
+	if (n2 != NULL && n2->type == F_LINK)
+		n2->flags &= ~F_MODE;
+	differs = 0;
+	if (n1 == NULL && n2 != NULL) {
+		differs = n2->flags;
+		mismatch(n1, n2, differs, path);
+		return (1);
+	}
+	if (n1 != NULL && n2 == NULL) {
+		differs = n1->flags;
+		mismatch(n1, n2, differs, path);
+		return (1);
+	}
+	if (n1->type != n2->type) {
+		differs = 0;
+		mismatch(n1, n2, differs, path);
+		return (1);
+	}
+	if (FF(n1, n2, F_CKSUM, cksum))
+		differs |= F_CKSUM;
+	if (FF(n1, n2, F_GID, st_gid))
+		differs |= F_GID;
+	if (FF(n1, n2, F_GNAME, st_gid))
+		differs |= F_GNAME;
+	if (FF(n1, n2, F_MODE, st_mode))
+		differs |= F_MODE;
+	if (FF(n1, n2, F_NLINK, st_nlink))
+		differs |= F_NLINK;
+	if (FF(n1, n2, F_SIZE, st_size))
+		differs |= F_SIZE;
+	if (FS(n1, n2, F_SLINK, slink))
+		differs |= F_SLINK;
+	if (FM(n1, n2, F_TIME, st_mtimespec))
+		differs |= F_TIME;
+	if (FF(n1, n2, F_UID, st_uid))
+		differs |= F_UID;
+	if (FF(n1, n2, F_UNAME, st_uid))
+		differs |= F_UNAME;
+	if (FS(n1, n2, F_MD5, md5digest))
+		differs |= F_MD5;
+	if (FS(n1, n2, F_SHA1, sha1digest))
+		differs |= F_SHA1;
+	if (FS(n1, n2, F_RMD160, rmd160digest))
+		differs |= F_RMD160;
+	if (FS(n1, n2, F_SHA256, sha256digest))
+		differs |= F_SHA256;
+	if (FS(n1, n2, F_SHA384, sha384digest))
+		differs |= F_SHA384;
+	if (FS(n1, n2, F_SHA512, sha512digest))
+		differs |= F_SHA512;
+	if (FF(n1, n2, F_FLAGS, st_flags))
+		differs |= F_FLAGS;
+	if (differs) {
+		mismatch(n1, n2, differs, path);
+		return (1);
+	}
+	return (0);	
+}
+static int
+walk_in_the_forest(NODE *t1, NODE *t2, char const *path)
+{
+	int r, i;
+	NODE *c1, *c2, *n1, *n2;
+	char *np;
+
+	r = 0;
+
+	if (t1 != NULL)
+		c1 = t1->child;
+	else
+		c1 = NULL;
+	if (t2 != NULL)
+		c2 = t2->child;
+	else
+		c2 = NULL;
+	while (c1 != NULL || c2 != NULL) {
+		n1 = n2 = NULL;
+		if (c1 != NULL)
+			n1 = c1->next;
+		if (c2 != NULL)
+			n2 = c2->next;
+		if (c1 != NULL && c2 != NULL) {
+			if (c1->type != F_DIR && c2->type == F_DIR) {
+				n2 = c2;
+				c2 = NULL;
+			} else if (c1->type == F_DIR && c2->type != F_DIR) {
+				n1 = c1;
+				c1 = NULL;
+			} else {
+				i = strcmp(c1->name, c2->name);
+				if (i > 0) {
+					n1 = c1;
+					c1 = NULL;
+				} else if (i < 0) {
+					n2 = c2;
+					c2 = NULL;
+				}
+			}
+		}
+		if (c1 == NULL && c2->type == F_DIR) {
+			asprintf(&np, "%s%s/", path, c2->name);
+			i = walk_in_the_forest(c1, c2, np);
+			free(np);
+			i += compare_nodes(c1, c2, path);
+		} else if (c2 == NULL && c1->type == F_DIR) {
+			asprintf(&np, "%s%s/", path, c1->name);
+			i = walk_in_the_forest(c1, c2, np);
+			free(np);
+			i += compare_nodes(c1, c2, path);
+		} else if (c1 == NULL || c2 == NULL) {
+			i = compare_nodes(c1, c2, path);
+		} else if (c1->type == F_DIR && c2->type == F_DIR) {
+			asprintf(&np, "%s%s/", path, c1->name);
+			i = walk_in_the_forest(c1, c2, np);
+			free(np);
+			i += compare_nodes(c1, c2, path);
+		} else {
+			i = compare_nodes(c1, c2, path);
+		}
+		r += i;
+		c1 = n1;
+		c2 = n2;
+	}
+	return (r);	
+}
+
+int
+mtree_specspec(FILE *fi, FILE *fj)
+{
+	int rval;
+	NODE *root1, *root2;
+
+	root1 = spec(fi);
+	root2 = spec(fj);
+	rval = walk_in_the_forest(root1, root2, "");
+	rval += compare_nodes(root1, root2, "");
+	if (rval > 0)
+		return (MISMATCHEXIT);
+	return (0);
+}
diff --git a/usr.sbin/mtree/stat_flags.c b/usr.sbin/mtree/stat_flags.c
new file mode 100644
index 0000000000..5724370b3e
--- /dev/null
+++ b/usr.sbin/mtree/stat_flags.c
@@ -0,0 +1,187 @@
+/*	$NetBSD: stat_flags.c,v 1.2 2007/01/16 17:34:02 cbiere Exp $	*/
+
+/*-
+ * Copyright (c) 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.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#else
+#define HAVE_STRUCT_STAT_ST_FLAGS 1
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+#if 0
+static char sccsid[] = "@(#)stat_flags.c	8.2 (Berkeley) 7/28/94";
+#else
+__RCSID("$NetBSD: stat_flags.c,v 1.2 2007/01/16 17:34:02 cbiere Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fts.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "extern.h"
+
+#define	SAPPEND(s) do {							\
+	if (prefix != NULL)						\
+		(void)strlcat(string, prefix, sizeof(string));		\
+	(void)strlcat(string, s, sizeof(string));			\
+	prefix = ",";							\
+} while (/* CONSTCOND */ 0)
+
+/*
+ * flags_to_string --
+ *	Convert stat flags to a comma-separated string.  If no flags
+ *	are set, return the default string.
+ */
+char *
+flags_to_string(u_long flags, const char *def)
+{
+	char string[128];
+	const char *prefix;
+
+	string[0] = '\0';
+	prefix = NULL;
+#if HAVE_STRUCT_STAT_ST_FLAGS
+	if (flags & UF_APPEND)
+		SAPPEND("uappnd");
+	if (flags & UF_IMMUTABLE)
+		SAPPEND("uchg");
+	if (flags & UF_NODUMP)
+		SAPPEND("nodump");
+	if (flags & UF_OPAQUE)
+		SAPPEND("opaque");
+	if (flags & SF_APPEND)
+		SAPPEND("sappnd");
+	if (flags & SF_ARCHIVED)
+		SAPPEND("arch");
+	if (flags & SF_IMMUTABLE)
+		SAPPEND("schg");
+#ifdef SF_SNAPSHOT
+	if (flags & SF_SNAPSHOT)
+		SAPPEND("snap");
+#endif
+#endif
+	if (prefix != NULL)
+		return strdup(string);
+	return strdup(def);
+}
+
+#define	TEST(a, b, f) {							\
+	if (!strcmp(a, b)) {						\
+		if (clear) {						\
+			if (clrp)					\
+				*clrp |= (f);				\
+			if (setp)					\
+				*setp &= ~(f);				\
+		} else {						\
+			if (setp)					\
+				*setp |= (f);				\
+			if (clrp)					\
+				*clrp &= ~(f);				\
+		}							\
+		break;							\
+	}								\
+}
+
+/*
+ * string_to_flags --
+ *	Take string of arguments and return stat flags.  Return 0 on
+ *	success, 1 on failure.  On failure, stringp is set to point
+ *	to the offending token.
+ */
+int
+string_to_flags(char **stringp, u_long *setp, u_long *clrp)
+{
+	int clear;
+	char *string, *p;
+
+	if (setp)
+		*setp = 0;
+	if (clrp)
+		*clrp = 0;
+
+#if HAVE_STRUCT_STAT_ST_FLAGS
+	string = *stringp;
+	while ((p = strsep(&string, "\t ,")) != NULL) {
+		clear = 0;
+		*stringp = p;
+		if (*p == '\0')
+			continue;
+		if (p[0] == 'n' && p[1] == 'o') {
+			clear = 1;
+			p += 2;
+		}
+		switch (p[0]) {
+		case 'a':
+			TEST(p, "arch", SF_ARCHIVED);
+			TEST(p, "archived", SF_ARCHIVED);
+			return (1);
+		case 'd':
+			clear = !clear;
+			TEST(p, "dump", UF_NODUMP);
+			return (1);
+		case 'n':
+				/*
+				 * Support `nonodump'. Note that
+				 * the state of clear is not changed.
+				 */
+			TEST(p, "nodump", UF_NODUMP);
+			return (1);
+		case 'o':
+			TEST(p, "opaque", UF_OPAQUE);
+			return (1);
+		case 's':
+			TEST(p, "sappnd", SF_APPEND);
+			TEST(p, "sappend", SF_APPEND);
+			TEST(p, "schg", SF_IMMUTABLE);
+			TEST(p, "schange", SF_IMMUTABLE);
+			TEST(p, "simmutable", SF_IMMUTABLE);
+			return (1);
+		case 'u':
+			TEST(p, "uappnd", UF_APPEND);
+			TEST(p, "uappend", UF_APPEND);
+			TEST(p, "uchg", UF_IMMUTABLE);
+			TEST(p, "uchange", UF_IMMUTABLE);
+			TEST(p, "uimmutable", UF_IMMUTABLE);
+			return (1);
+		default:
+			return (1);
+		}
+	}
+#endif
+
+	return (0);
+}
diff --git a/usr.sbin/mtree/verify.c b/usr.sbin/mtree/verify.c
index 2e6ca80a4c..b320b32287 100644
--- a/usr.sbin/mtree/verify.c
+++ b/usr.sbin/mtree/verify.c
@@ -1,3 +1,5 @@
+/*	$NetBSD: verify.c,v 1.46 2015/01/23 20:28:24 christos Exp $	*/
+
 /*-
  * Copyright (c) 1990, 1993
  *	The Regents of the University of California.  All rights reserved.
@@ -25,21 +27,34 @@
  * 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.
- *
- * @(#)verify.c	8.1 (Berkeley) 6/6/93
- * $FreeBSD: src/usr.sbin/mtree/verify.c,v 1.10.2.2 2001/01/12 19:17:18 phk Exp $
  */
 
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)verify.c	8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: verify.c,v 1.46 2015/01/23 20:28:24 christos Exp $");
+#endif
+#endif /* not lint */
+
 #include <sys/param.h>
 #include <sys/stat.h>
+
+#if ! HAVE_NBTOOL_CONFIG_H
 #include <dirent.h>
-#include <err.h>
+#endif
+
 #include <errno.h>
-#include <fts.h>
 #include <fnmatch.h>
 #include <stdio.h>
+#include <string.h>
 #include <unistd.h>
-#include "mtree.h"
+
 #include "extern.h"
 
 static NODE *root;
@@ -49,11 +64,11 @@ static void	miss(NODE *, char *);
 static int	vwalk(void);
 
 int
-verify(void)
+verify(FILE *fi)
 {
 	int rval;
 
-	root = spec();
+	root = spec(fi);
 	rval = vwalk();
 	miss(root, path);
 	return (rval);
@@ -66,19 +81,24 @@ vwalk(void)
 	FTSENT *p;
 	NODE *ep, *level;
 	int specdepth, rval;
-	char *argv[2], dot[] = ".";
-
+	char *argv[2];
+	char  dot[] = ".";
 	argv[0] = dot;
 	argv[1] = NULL;
+
 	if ((t = fts_open(argv, ftsoptions, NULL)) == NULL)
-		err(1, "line %d: fts_open", lineno);
+		mtree_err("fts_open: %s", strerror(errno));
 	level = root;
 	specdepth = rval = 0;
-	while ((p = fts_read(t))) {
+	while ((p = fts_read(t)) != NULL) {
 		if (check_excludes(p->fts_name, p->fts_path)) {
 			fts_set(t, p, FTS_SKIP);
 			continue;
 		}
+		if (!find_only(p->fts_path)) {
+			fts_set(t, p, FTS_SKIP);
+			continue;
+		}
 		switch(p->fts_info) {
 		case FTS_D:
 		case FTS_SL:
@@ -86,7 +106,8 @@ vwalk(void)
 		case FTS_DP:
 			if (specdepth > p->fts_level) {
 				for (level = level->parent; level->prev;
-				      level = level->prev);
+				    level = level->prev)
+					continue;
 				--specdepth;
 			}
 			continue;
@@ -110,22 +131,30 @@ vwalk(void)
 				if ((ep->flags & F_NOCHANGE) == 0 &&
 				    compare(ep, p))
 					rval = MISMATCHEXIT;
-				if (ep->flags & F_IGN)
-					fts_set(t, p, FTS_SKIP);
-				else if (ep->child && ep->type == F_DIR &&
+				if (!(ep->flags & F_IGN) &&
+				    ep->type == F_DIR &&
 				    p->fts_info == FTS_D) {
-					level = ep->child;
-					++specdepth;
-				}
+					if (ep->child) {
+						level = ep->child;
+						++specdepth;
+					}
+				} else
+					fts_set(t, p, FTS_SKIP);
 				break;
 			}
 
 		if (ep)
 			continue;
-extra:
-		if (!eflag) {
-			printf("%s extra", RP(p));
+ extra:
+		if (!eflag && !(dflag && p->fts_info == FTS_SL)) {
+			printf("extra: %s", RP(p));
 			if (rflag) {
+#if HAVE_STRUCT_STAT_ST_FLAGS
+				if (rflag > 1 &&
+				    lchflags(p->fts_accpath, 0) == -1)
+					printf(" (chflags %s)",
+					    strerror(errno));
+#endif
 				if ((S_ISDIR(p->fts_statp->st_mode)
 				    ? rmdir : unlink)(p->fts_accpath)) {
 					printf(", not removed: %s",
@@ -149,79 +178,136 @@ miss(NODE *p, char *tail)
 	int create;
 	char *tp;
 	const char *type;
+	u_int32_t flags;
 
 	for (; p; p = p->next) {
+		if (p->flags & F_OPT && !(p->flags & F_VISIT))
+			continue;
 		if (p->type != F_DIR && (dflag || p->flags & F_VISIT))
 			continue;
 		strcpy(tail, p->name);
 		if (!(p->flags & F_VISIT)) {
-			/* Don't print missing message if file exists as a
+			/* Don't print missing message if file exists as a 
 			   symbolic link and the -q flag is set. */
 			struct stat statbuf;
- 
-			if (qflag && stat(path, &statbuf) == 0)
+
+			if (qflag && stat(path, &statbuf) == 0 &&
+			    S_ISDIR(statbuf.st_mode))
 				p->flags |= F_VISIT;
 			else
-				printf("%s missing", path);
+				(void)printf("%s missing", path);
 		}
-		if (p->type != F_DIR && p->type != F_LINK) {
+		switch (p->type) {
+		case F_BLOCK:
+		case F_CHAR:
+			type = "device";
+			break;
+		case F_DIR:
+			type = "directory";
+			break;
+		case F_LINK:
+			type = "symlink";
+			break;
+		default:
 			putchar('\n');
 			continue;
 		}
 
 		create = 0;
-		if (p->type == F_LINK)
-			type = "symlink";
-		else
-			type = "directory";
 		if (!(p->flags & F_VISIT) && uflag) {
+			if (mtree_Wflag || p->type == F_LINK)
+				goto createit;
 			if (!(p->flags & (F_UID | F_UNAME)))
-				printf(" (%s not created: user not specified)", type);
+			    printf(
+				" (%s not created: user not specified)", type);
 			else if (!(p->flags & (F_GID | F_GNAME)))
-				printf(" (%s not created: group not specified)", type);
-			else if (p->type == F_LINK) {
-				if (symlink(p->slink, path))
-					printf(" (symlink not created: %s)\n",
+			    printf(
+				" (%s not created: group not specified)", type);
+			else if (!(p->flags & F_MODE))
+			    printf(
+				" (%s not created: mode not specified)", type);
+			else
+ createit:
+			switch (p->type) {
+			case F_BLOCK:
+			case F_CHAR:
+				if (mtree_Wflag)
+					continue;
+				if (!(p->flags & F_DEV))
+					printf(
+				    " (%s not created: device not specified)",
+					    type);
+				else if (mknod(path,
+				    p->st_mode | nodetoino(p->type),
+				    p->st_rdev) == -1)
+					printf(" (%s not created: %s)\n",
+					    type, strerror(errno));
+				else
+					create = 1;
+				break;
+			case F_LINK:
+				if (!(p->flags & F_SLINK))
+					printf(
+				    " (%s not created: link not specified)\n",
+					    type);
+				else if (symlink(p->slink, path))
+					printf(
+					    " (%s not created: %s)\n",
+					    type, strerror(errno));
+				else
+					create = 1;
+				break;
+			case F_DIR:
+				if (mkdir(path, S_IRWXU|S_IRWXG|S_IRWXO))
+					printf(" (not created: %s)",
 					    strerror(errno));
 				else
-					printf(" (created)\n");
-				if (lchown(path, p->st_uid, p->st_gid))
-					printf("%s: user/group not modified: %s\n",
-					    path, strerror(errno));
-				continue;
-			} else if (!(p->flags & F_MODE))
-			    printf(" (directory not created: mode not specified)");
-			else if (mkdir(path, S_IRWXU))
-				printf(" (directory not created: %s)",
-				    strerror(errno));
-			else {
-				create = 1;
-				printf(" (created)");
+					create = 1;
+				break;
+			default:
+				mtree_err("can't create create %s",
+				    nodetype(p->type));
 			}
 		}
-		if (!(p->flags & F_VISIT))
+		if (create)
+			printf(" (created)");
+		if (p->type == F_DIR) {
+			if (!(p->flags & F_VISIT))
+				putchar('\n');
+			for (tp = tail; *tp; ++tp)
+				continue;
+			*tp = '/';
+			miss(p->child, tp + 1);
+			*tp = '\0';
+		} else
 			putchar('\n');
 
-		for (tp = tail; *tp; ++tp);
-		*tp = '/';
-		miss(p->child, tp + 1);
-		*tp = '\0';
-
-		if (!create)
+		if (!create || mtree_Wflag)
 			continue;
-		if (chown(path, p->st_uid, p->st_gid)) {
+		if ((p->flags & (F_UID | F_UNAME)) &&
+		    (p->flags & (F_GID | F_GNAME)) &&
+		    (lchown(path, p->st_uid, p->st_gid))) {
 			printf("%s: user/group/mode not modified: %s\n",
 			    path, strerror(errno));
 			printf("%s: warning: file mode %snot set\n", path,
 			    (p->flags & F_FLAGS) ? "and file flags " : "");
 			continue;
 		}
-		if (chmod(path, p->st_mode))
-			printf("%s: permissions not set: %s\n",
-			    path, strerror(errno));
-		if ((p->flags & F_FLAGS) && p->st_flags &&
-		    chflags(path, p->st_flags))
-			printf("%s: file flags not set: %s\n",
-			    path, strerror(errno));
+		if (p->flags & F_MODE) {
+			if (lchmod(path, p->st_mode))
+				printf("%s: permissions not set: %s\n",
+				    path, strerror(errno));
+		}
+#if HAVE_STRUCT_STAT_ST_FLAGS
+		if ((p->flags & F_FLAGS) && p->st_flags) {
+			if (iflag)
+				flags = p->st_flags;
+			else
+				flags = p->st_flags & ~SP_FLGS;
+			if (lchflags(path, flags))
+				printf("%s: file flags not set: %s\n",
+				    path, strerror(errno));
+		}
+#endif	/* HAVE_STRUCT_STAT_ST_FLAGS */
 	}
 }
-- 
2.17.1

