summaryrefslogtreecommitdiffstats
path: root/file_cmds/mtree
diff options
context:
space:
mode:
Diffstat (limited to 'file_cmds/mtree')
-rw-r--r--file_cmds/mtree/commoncrypto.c378
-rw-r--r--file_cmds/mtree/commoncrypto.h36
-rw-r--r--file_cmds/mtree/compare.c688
-rw-r--r--file_cmds/mtree/create.c611
-rw-r--r--file_cmds/mtree/excludes.c114
-rw-r--r--file_cmds/mtree/extern.h71
-rwxr-xr-xfile_cmds/mtree/fix_failure_locations.py77
-rw-r--r--file_cmds/mtree/metrics.c155
-rw-r--r--file_cmds/mtree/metrics.h48
-rw-r--r--file_cmds/mtree/misc.c189
-rw-r--r--file_cmds/mtree/mtree.8393
-rw-r--r--file_cmds/mtree/mtree.c358
-rw-r--r--file_cmds/mtree/mtree.h120
-rw-r--r--file_cmds/mtree/spec.c470
-rw-r--r--file_cmds/mtree/specspec.c327
-rw-r--r--file_cmds/mtree/test/test00.sh67
-rw-r--r--file_cmds/mtree/test/test01.sh40
-rw-r--r--file_cmds/mtree/test/test02.sh36
-rw-r--r--file_cmds/mtree/test/test03.sh60
-rw-r--r--file_cmds/mtree/test/test04.sh51
-rw-r--r--file_cmds/mtree/verify.c341
21 files changed, 4630 insertions, 0 deletions
diff --git a/file_cmds/mtree/commoncrypto.c b/file_cmds/mtree/commoncrypto.c
new file mode 100644
index 0000000..11e97ce
--- /dev/null
+++ b/file_cmds/mtree/commoncrypto.c
@@ -0,0 +1,378 @@
+#include <dispatch/dispatch.h>
+#include <os/assumes.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/xattr.h>
+#include <stdbool.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/attr.h>
+#include <unistd.h>
+#include <sys/xattr.h>
+#include <sys/mount.h>
+#include <apfs/apfs_fsctl.h>
+
+#include "commoncrypto.h"
+#include "extern.h"
+#include "metrics.h"
+
+const int kSHA256NullTerminatedBuffLen = 65;
+static const char hex[] = "0123456789abcdef";
+
+/* Functions for SHA256_File_XATTRs */
+#define SHA256_Data(d, s, b) Digest_Data(kCCDigestSHA256, d, s, b)
+char *Digest_Data(CCDigestAlg algorithm, void *data, size_t size, char *buf);
+void Quicksort(char **array, int num);
+
+/* Generic version of libmd's *_File() functions. */
+char *
+Digest_File(CCDigestAlg algorithm, const char *filename, char *buf)
+{
+ int fd;
+ __block CCDigestCtx ctx;
+ dispatch_queue_t queue;
+ dispatch_semaphore_t sema;
+ dispatch_io_t io;
+ __block int s_error = 0;
+ uint8_t digest[32]; // SHA256 is the biggest
+ size_t i, length;
+
+ /* dispatch_io_create_with_path requires an absolute path */
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ return NULL;
+ }
+
+ (void)fcntl(fd, F_NOCACHE, 1);
+
+ (void)os_assumes_zero(CCDigestInit(algorithm, &ctx));
+
+ queue = dispatch_queue_create("com.apple.mtree.io", NULL);
+ os_assert(queue);
+ sema = dispatch_semaphore_create(0);
+ os_assert(sema);
+
+ io = dispatch_io_create(DISPATCH_IO_STREAM, fd, queue, ^(int error) {
+ if (error != 0) {
+ s_error = error;
+ RECORD_FAILURE(27440, s_error);
+ }
+ (void)close(fd);
+ (void)dispatch_semaphore_signal(sema);
+ });
+ os_assert(io);
+ dispatch_io_read(io, 0, SIZE_MAX, queue, ^(__unused bool done, dispatch_data_t data, int error) {
+ if (data != NULL) {
+ (void)dispatch_data_apply(data, ^(__unused dispatch_data_t region, __unused size_t offset, const void *buffer, size_t size) {
+ (void)os_assumes_zero(CCDigestUpdate(&ctx, buffer, size));
+ return (bool)true;
+ });
+ }
+
+ if (error != 0) {
+ s_error = error;
+ RECORD_FAILURE(27441, s_error);
+ }
+ });
+ dispatch_release(io); // it will close on its own
+
+ (void)dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
+
+ dispatch_release(queue);
+ dispatch_release(sema);
+
+ if (s_error != 0) {
+ errno = s_error;
+ return NULL;
+ }
+
+ /* Finalize and convert to hex. */
+ (void)os_assumes_zero(CCDigestFinal(&ctx, digest));
+ length = CCDigestOutputSize(&ctx);
+ os_assert(length <= sizeof(digest));
+ for (i = 0; i < length; i++) {
+ buf[i+i] = hex[digest[i] >> 4];
+ buf[i+i+1] = hex[digest[i] & 0x0f];
+ }
+ buf[i+i] = '\0';
+
+ return buf;
+}
+
+xattr_info *
+SHA256_Path_XATTRs(char *path, char *buf) {
+ xattr_info *ai = NULL;
+
+ if (mflag) {
+ ai = get_xdstream_privateid(path, buf);
+ } else {
+ ai = calculate_SHA256_XATTRs(path, buf);
+ }
+
+ return ai;
+}
+
+
+xattr_info *
+calculate_SHA256_XATTRs(char *path, char *buf)
+{
+ errno_t error = 0;
+ char *xattrsSummary = NULL;
+ int options = XATTR_SHOWCOMPRESSION | XATTR_NOFOLLOW;
+ ssize_t nameBufSize = listxattr(path, NULL, 0, options);
+ uint64_t xd_obj_id = 0;
+ if (nameBufSize > 0) {
+ char *nameBuf = malloc(nameBufSize);
+
+ listxattr(path, nameBuf, nameBufSize, options);
+
+ size_t xattrsLen = 1;
+ size_t xattrIndex = 0;
+ char **xattrs = malloc(xattrsLen * sizeof(char *));
+ char *nextName = nameBuf;
+ while (nextName < nameBuf + nameBufSize)
+ {
+ char *name = nextName;
+ if (xattrIndex == xattrsLen) {
+ xattrsLen *= 2;
+ xattrs = realloc(xattrs, xattrsLen * sizeof(char *));
+ }
+ xattrs[xattrIndex++] = name;
+ nextName += strlen(name) + 1;
+ }
+
+ // sort the xattr array as they're not guaranteed to come in the same order
+ qsort_b(xattrs, xattrIndex, sizeof(char *), ^(const void *l, const void *r) {
+ char *left = *(char **)l;
+ char *right = *(char **)r;
+ return strcmp(left, right);
+ });
+
+ // gather the data for the xattrs
+ bool didAddXATTR = false;
+ int xattrBufLen = kSHA256NullTerminatedBuffLen;
+ void *xattrBuf = malloc(xattrBufLen); // resized if necessary
+ char *digest;
+ ssize_t result = 0;
+ char *oldSummary = NULL;
+ //XXX Make xattr_info an array of structs if necessary
+ xattr_info *ai = (xattr_info *) malloc(sizeof(xattr_info));
+ for (int i = 0; i < xattrIndex; i++) {
+ char *name = xattrs[i];
+ ssize_t xlen = getxattr(path, name, NULL, 0, 0, options);
+ if (xlen > xattrBufLen) {
+ xattrBufLen = xlen;
+ xattrBuf = realloc(xattrBuf, xattrBufLen);
+ }
+ bzero(xattrBuf, xattrBufLen);
+ result = getxattr(path, name, xattrBuf, xattrBufLen, 0, options);
+ if (result < 0) {
+ error = errno;
+ RECORD_FAILURE(27442, error);
+ errc(1, error, "SHA256_Path_XATTRs getxattr of \"%s\" at path \"%s\" failed with error", name, path);
+ }
+
+ digest = SHA256_Data(xattrBuf, xattrBufLen, buf);
+ if (!digest)
+ err(1, "%s", xattrsSummary);
+ if (!didAddXATTR)
+ {
+ didAddXATTR = true;
+ asprintf(&xattrsSummary, "%s:%s", name, digest);
+ } else {
+ oldSummary = xattrsSummary;
+ asprintf(&xattrsSummary, "%s, %s:%s", oldSummary, name, digest);
+ free(oldSummary);
+ }
+#ifdef APFSIOC_XDSTREAM_OBJ_ID
+ // System volume has stream based xattrs only in form of resource forks
+ if (!strncmp(name, XATTR_RESOURCEFORK_NAME, XATTR_MAXNAMELEN)) {
+ struct xdstream_obj_id x_obj;
+ x_obj.xdi_name = name;
+ x_obj.xdi_xdtream_obj_id = 0;
+
+ result = fsctl(path, APFSIOC_XDSTREAM_OBJ_ID, &x_obj, 0);
+ if (!result) {
+ xd_obj_id = x_obj.xdi_xdtream_obj_id;
+ } else if (errno == ENOTTY) {
+ // Not an apfs filesystem, return zero.
+ xd_obj_id = 0;
+ } else {
+ error = errno;
+ RECORD_FAILURE(27444, error);
+ errc(1, error, "%s - SHA256_Path_XATTRs APFSIOC_XDSTREAM_OBJ_ID failed with %d", path, error);
+ }
+ }
+#endif
+ ai->xdstream_priv_id = xd_obj_id;
+ }
+
+ free(xattrBuf);
+ free(nameBuf);
+ free(xattrs);
+
+ digest = SHA256_Data(xattrsSummary, strlen(xattrsSummary) * sizeof(char), buf);
+ if (!digest)
+ err(1, "%s", xattrsSummary);
+
+ ai->digest = digest;
+
+ free(xattrsSummary);
+ return ai;
+ }
+ return NULL;
+}
+
+xattr_info *
+get_xdstream_privateid(char *path, char *buf) {
+ errno_t error = 0;
+ int options = XATTR_SHOWCOMPRESSION | XATTR_NOFOLLOW;
+ ssize_t nameBufSize = listxattr(path, NULL, 0, options);
+ uint64_t xd_obj_id = 0;
+
+ if (nameBufSize > 0) {
+ //XXX Make xattr_info an array of structs if necessary
+ xattr_info *ai = (xattr_info *) malloc(sizeof(xattr_info));
+ char *nameBuf = malloc(nameBufSize);
+ int result = 0;
+
+ listxattr(path, nameBuf, nameBufSize, options);
+
+ size_t xattrsLen = 1;
+ size_t xattrIndex = 0;
+ char **xattrs = malloc(xattrsLen * sizeof(char *));
+ char *nextName = nameBuf;
+ while (nextName < nameBuf + nameBufSize)
+ {
+ char *name = nextName;
+ if (xattrIndex == xattrsLen) {
+ xattrsLen *= 2;
+ xattrs = realloc(xattrs, xattrsLen * sizeof(char *));
+ }
+ xattrs[xattrIndex++] = name;
+ nextName += strlen(name) + 1;
+ }
+
+ for (int i = 0; i < xattrIndex; i++) {
+ char *name = xattrs[i];
+ // System volume has stream based xattrs only in form of resource forks
+ if (!strncmp(name, XATTR_RESOURCEFORK_NAME, XATTR_MAXNAMELEN)) {
+ struct xdstream_obj_id x_obj;
+ x_obj.xdi_name = name;
+ x_obj.xdi_xdtream_obj_id = 0;
+
+ result = fsctl(path, APFSIOC_XDSTREAM_OBJ_ID, &x_obj, 0);
+ if (!result && x_obj.xdi_xdtream_obj_id != 0) {
+ xd_obj_id = x_obj.xdi_xdtream_obj_id;
+ } else if (errno == ENOTTY) {
+ // Not an apfs filesystem, return zero.
+ xd_obj_id = 0;
+ } else {
+ error = errno;
+ RECORD_FAILURE(29983, error);
+ errc(1, error, "%s - SHA256_Path_XATTRs APFSIOC_XDSTREAM_OBJ_ID failed with %d", path, error);
+ }
+ }
+ }
+
+ ai->xdstream_priv_id = xd_obj_id;
+ // insert a dummy value as digest is not used in presence of mflag
+ ai->digest = "authapfs";
+
+ free(nameBuf);
+ free(xattrs);
+ return ai;
+ }
+
+ return NULL;
+}
+
+char *SHA256_Path_ACL(char *path, char *buf)
+{
+ errno_t error = 0;
+ int result = 0;
+ char *data = NULL;
+ char *digest = NULL;
+
+ struct attrlist list = {
+ .bitmapcount = ATTR_BIT_MAP_COUNT,
+ .commonattr = ATTR_CMN_RETURNED_ATTRS | ATTR_CMN_EXTENDED_SECURITY,
+ };
+
+ struct ACLBuf {
+ uint32_t len;
+ attribute_set_t returned_attrs;
+ attrreference_t acl;
+ char buf[8192]; // current acls are up to 3116 bytes, but they may increase in the future
+ } __attribute__((aligned(4), packed));
+
+ struct ACLBuf aclBuf;
+
+ result = getattrlist(path, &list, &aclBuf, sizeof(aclBuf), FSOPT_NOFOLLOW);
+
+ if (result) {
+ error = errno;
+ RECORD_FAILURE(27445, error);
+ errc(1, error, "SHA256_Path_ACL: getattrlist");
+ }
+
+ // if the path does not have an acl, return none
+ if ( ( ! ( aclBuf.returned_attrs.commonattr & ATTR_CMN_EXTENDED_SECURITY ) )
+ || ( aclBuf.acl.attr_length == 0 ) ) {
+ return kNone;
+ }
+
+ data = ((char*)&aclBuf.acl) + aclBuf.acl.attr_dataoffset;
+
+ digest = SHA256_Data(data, aclBuf.acl.attr_length, buf);
+ if (!digest)
+ err(1, "SHA256_Path_ACL: SHA256_Data");
+
+ return digest;
+}
+
+/* Functions for Digest_Path_* */
+char *
+Digest_Data(CCDigestAlg algorithm, void *data, size_t size, char *buf) {
+
+ uint8_t digest[32]; // SHA256 is the biggest
+ CCDigestCtx ctx;
+ size_t i, length;
+
+ (void)os_assumes_zero(CCDigestInit(algorithm, &ctx));
+ (void)os_assumes_zero(CCDigestUpdate(&ctx, data, size));
+
+ /* Finalize and convert to hex. */
+ (void)os_assumes_zero(CCDigestFinal(&ctx, digest));
+ length = CCDigestOutputSize(&ctx);
+ os_assert(length <= sizeof(digest));
+ for (i = 0; i < length; i++) {
+ buf[i+i] = hex[digest[i] >> 4];
+ buf[i+i+1] = hex[digest[i] & 0x0f];
+ }
+ buf[i+i] = '\0';
+
+ return buf;
+}
+
+uint64_t
+get_sibling_id(const char *path)
+{
+ struct attrlist attr_list = {0};
+ struct attrbuf attr_buf = {0};
+ errno_t error = 0;
+
+ attr_list.bitmapcount = ATTR_BIT_MAP_COUNT;
+ attr_list.forkattr = ATTR_CMNEXT_LINKID;
+
+ error = getattrlist(path, &attr_list, &attr_buf, sizeof(attr_buf), FSOPT_ATTR_CMN_EXTENDED | FSOPT_NOFOLLOW);
+ if (error) {
+ error = errno;
+ RECORD_FAILURE(27447, error);
+ errc(1, error, "get_sibling_id: getattrlist failed for %s\n", path);
+ }
+
+ return attr_buf.sibling_id;
+}
diff --git a/file_cmds/mtree/commoncrypto.h b/file_cmds/mtree/commoncrypto.h
new file mode 100644
index 0000000..c547035
--- /dev/null
+++ b/file_cmds/mtree/commoncrypto.h
@@ -0,0 +1,36 @@
+
+#ifndef _COMMON_CRYPTO_H_
+#define _COMMON_CRYPTO_H_
+
+#include <CommonCrypto/CommonDigestSPI.h>
+
+#define kNone "none"
+
+extern const int kSHA256NullTerminatedBuffLen;
+
+#define MD5File(f, b) Digest_File(kCCDigestMD5, f, b)
+#define SHA1_File(f, b) Digest_File(kCCDigestSHA1, f, b)
+#define RIPEMD160_File(f, b) Digest_File(kCCDigestRMD160, f, b)
+#define SHA256_File(f, b) Digest_File(kCCDigestSHA256, f, b)
+
+typedef struct {
+ char *digest;
+ uint64_t xdstream_priv_id;
+} xattr_info;
+
+struct attrbuf {
+ uint32_t info_length;
+ uint64_t sibling_id;
+} __attribute__((aligned, packed));
+
+typedef struct attrbuf attrbuf_t;
+
+char *Digest_File(CCDigestAlg algorithm, const char *filename, char *buf);
+
+xattr_info *calculate_SHA256_XATTRs(char *path, char *buf);
+xattr_info *SHA256_Path_XATTRs(char *path, char *buf);
+xattr_info *get_xdstream_privateid(char *path, char *buf);
+char *SHA256_Path_ACL(char *path, char *buf);
+uint64_t get_sibling_id(const char *path);
+
+#endif /* _COMMON_CRYPTO_H_ */
diff --git a/file_cmds/mtree/compare.c b/file_cmds/mtree/compare.c
new file mode 100644
index 0000000..e585928
--- /dev/null
+++ b/file_cmds/mtree/compare.c
@@ -0,0 +1,688 @@
+/*-
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)compare.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.sbin/mtree/compare.c,v 1.34 2005/03/29 11:44:17 tobez Exp $");
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fts.h>
+#ifndef __APPLE__
+#ifdef ENABLE_MD5
+#include <md5.h>
+#endif
+#ifdef ENABLE_RMD160
+#include <ripemd.h>
+#endif
+#ifdef ENABLE_SHA1
+#include <sha.h>
+#endif
+#ifdef ENABLE_SHA256
+#include <sha256.h>
+#endif
+#endif /* !__APPLE__ */
+#include <stdint.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+#include <vis.h>
+
+#include "metrics.h"
+#include "mtree.h"
+#include "extern.h"
+
+#ifdef __APPLE__
+#include "commoncrypto.h"
+#endif /* __APPLE__ */
+
+#define INDENTNAMELEN 8
+#define LABEL \
+ if (!label++) { \
+ len = printf("%s changed\n", RP(p)); \
+ tab = "\t"; \
+ }
+
+extern CFMutableDictionaryRef dict;
+
+// max/min times apfs can store on disk
+#define APFS_MAX_TIME 0x7fffffffffffffffLL
+#define APFS_MIN_TIME (-0x7fffffffffffffffLL-1)
+
+static uint64_t
+timespec_to_apfs_timestamp(struct timespec *ts)
+{
+ int64_t total;
+ int64_t seconds;
+
+ // `tv_nsec' can be > one billion, so we split it into two components:
+ // seconds and actual nanoseconds
+ // this allows us to detect overflow on the *total* number of nanoseconds
+ // e.g. if (MAX_SECONDS+2, -2billion) is passed in, we return MAX_SECONDS
+ seconds = ((int64_t)ts->tv_nsec / (int64_t)NSEC_PER_SEC);
+
+ // compute total nanoseconds, checking for overflow:
+ // seconds = sec + (ns/10e9)
+ // total = seconds*10e9 + ns%10e9
+ if (__builtin_saddll_overflow(ts->tv_sec, seconds, &seconds) ||
+ __builtin_smulll_overflow(seconds, NSEC_PER_SEC, &total) ||
+ __builtin_saddll_overflow(((int64_t)ts->tv_nsec % (int64_t)NSEC_PER_SEC), total, &total)) {
+ // checking the sign of "seconds" tells us whether to cap the value at
+ // the max or min time
+ total = (ts->tv_sec > 0) ? APFS_MAX_TIME : APFS_MIN_TIME;
+ }
+
+ return (uint64_t)total;
+}
+
+static void
+set_key_value_pair(void *in_key, uint64_t *in_val, bool is_string)
+{
+ CFStringRef key;
+ CFNumberRef val;
+
+ if (is_string) {
+ key = CFStringCreateWithCString(NULL, (const char*)in_key, kCFStringEncodingUTF8);
+
+ } else {
+ key = CFStringCreateWithFormat(NULL, NULL, CFSTR("%llu"), *(uint64_t*)in_key);
+ }
+
+ val = CFNumberCreate(NULL, kCFNumberSInt64Type, in_val);
+
+ // we always expect the key to be not present
+ if (key && val) {
+ CFDictionaryAddValue(dict, key, val);
+ } else {
+ if (key) {
+ CFRelease(key);
+ }
+ if (val) {
+ CFRelease(val);
+ }
+ RECORD_FAILURE(1, EINVAL);
+ errx(1, "set_key_value_pair: key/value is null");
+ }
+
+ if (key) {
+ CFRelease(key);
+ }
+ if (val) {
+ CFRelease(val);
+ }
+}
+
+int
+compare(char *name __unused, NODE *s, FTSENT *p)
+{
+ int error = 0;
+ struct timeval tv[2];
+ uint32_t val;
+ int fd, label;
+ off_t len;
+ char *cp;
+ const char *tab = "";
+ char *fflags, *badflags;
+ u_long flags;
+
+ label = 0;
+ switch(s->type) {
+ case F_BLOCK:
+ if (!S_ISBLK(p->fts_statp->st_mode)) {
+ RECORD_FAILURE(2, EINVAL);
+ goto typeerr;
+ }
+ break;
+ case F_CHAR:
+ if (!S_ISCHR(p->fts_statp->st_mode)) {
+ RECORD_FAILURE(3, EINVAL);
+ goto typeerr;
+ }
+ break;
+ case F_DIR:
+ if (!S_ISDIR(p->fts_statp->st_mode)) {
+ RECORD_FAILURE(4, EINVAL);
+ goto typeerr;
+ }
+ break;
+ case F_FIFO:
+ if (!S_ISFIFO(p->fts_statp->st_mode)) {
+ RECORD_FAILURE(5, EINVAL);
+ goto typeerr;
+ }
+ break;
+ case F_FILE:
+ if (!S_ISREG(p->fts_statp->st_mode)) {
+ RECORD_FAILURE(6, EINVAL);
+ goto typeerr;
+ }
+ break;
+ case F_LINK:
+ if (!S_ISLNK(p->fts_statp->st_mode)) {
+ RECORD_FAILURE(7, EINVAL);
+ goto typeerr;
+ }
+ break;
+ case F_SOCK:
+ if (!S_ISSOCK(p->fts_statp->st_mode)) {
+ RECORD_FAILURE(8, EINVAL);
+typeerr: LABEL;
+ (void)printf("\ttype expected %s found %s\n",
+ ftype(s->type), inotype(p->fts_statp->st_mode));
+ return (label);
+ }
+ break;
+ }
+ /* 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;
+ (void)printf("%suser expected %lu found %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)) {
+ error = errno;
+ RECORD_FAILURE(9, error);
+ (void)printf(" not modified: %s\n",
+ strerror(error));
+ } else {
+ (void)printf(" modified\n");
+ }
+ } else {
+ (void)printf("\n");
+ }
+ tab = "\t";
+ }
+ if (s->flags & (F_GID | F_GNAME) && s->st_gid != p->fts_statp->st_gid) {
+ LABEL;
+ (void)printf("%sgid expected %lu found %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)) {
+ error = errno;
+ RECORD_FAILURE(10, error);
+ (void)printf(" not modified: %s\n",
+ strerror(error));
+ } else {
+ (void)printf(" modified\n");
+ }
+ } else {
+ (void)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)) {
+ LABEL;
+ (void)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)) {
+ error = errno;
+ RECORD_FAILURE(11, error);
+ (void)printf(" not modified: %s\n",
+ strerror(error));
+ } else {
+ (void)printf(" modified\n");
+ }
+ } else {
+ (void)printf("\n");
+ }
+ tab = "\t";
+ }
+ if (s->flags & F_NLINK && s->type != F_DIR &&
+ s->st_nlink != p->fts_statp->st_nlink) {
+ LABEL;
+ (void)printf("%slink_count expected %u found %u\n",
+ tab, s->st_nlink, 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)) {
+ LABEL;
+ (void)printf("%ssize expected %jd found %jd\n", tab,
+ (intmax_t)s->st_size, (intmax_t)p->fts_statp->st_size);
+ tab = "\t";
+ }
+ 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))) {
+ if (!mflag) {
+ LABEL;
+ (void)printf("%smodification time expected %.24s.%09ld ",
+ tab, ctime(&s->st_mtimespec.tv_sec), s->st_mtimespec.tv_nsec);
+ (void)printf("found %.24s.%09ld",
+ ctime(&p->fts_statp->st_mtimespec.tv_sec), p->fts_statp->st_mtimespec.tv_nsec);
+ if (uflag) {
+ tv[0].tv_sec = s->st_mtimespec.tv_sec;
+ tv[0].tv_usec = s->st_mtimespec.tv_nsec / 1000;
+ tv[1] = tv[0];
+ if (utimes(p->fts_accpath, tv)) {
+ error = errno;
+ RECORD_FAILURE(12, error);
+ (void)printf(" not modified: %s\n",
+ strerror(error));
+ } else {
+ (void)printf(" modified\n");
+ }
+ } else {
+ (void)printf("\n");
+ }
+ tab = "\t";
+ }
+ if (!insert_mod && mflag) {
+ uint64_t s_mod_time = timespec_to_apfs_timestamp(&s->st_mtimespec);
+ char *mod_string = "MODIFICATION";
+ set_key_value_pair(mod_string, &s_mod_time, true);
+ insert_mod = 1;
+ }
+ }
+ if (s->flags & F_CKSUM) {
+ if ((fd = open(p->fts_accpath, O_RDONLY, 0)) < 0) {
+ LABEL;
+ error = errno;
+ RECORD_FAILURE(13, error);
+ (void)printf("%scksum: %s: %s\n",
+ tab, p->fts_accpath, strerror(error));
+ tab = "\t";
+ } else if (crc(fd, &val, &len)) {
+ (void)close(fd);
+ LABEL;
+ error = errno;
+ RECORD_FAILURE(14, error);
+ (void)printf("%scksum: %s: %s\n",
+ tab, p->fts_accpath, strerror(error));
+ tab = "\t";
+ } else {
+ (void)close(fd);
+ if (s->cksum != val) {
+ LABEL;
+ (void)printf("%scksum expected %lu found %lu\n",
+ tab, s->cksum, (unsigned long)val);
+ tab = "\t";
+ }
+ }
+ }
+ if (s->flags & F_FLAGS) {
+ // There are unpublished flags that should not fail comparison
+ // we convert to string and back to filter them out
+ fflags = badflags = flags_to_string(p->fts_statp->st_flags);
+ if (strcmp("none", fflags) == 0) {
+ flags = 0;
+ } else if (strtofflags(&badflags, &flags, NULL) != 0)
+ errx(1, "invalid flag %s", badflags);
+ free(fflags);
+ if (s->st_flags != flags) {
+ LABEL;
+ fflags = flags_to_string(s->st_flags);
+ (void)printf("%sflags expected \"%s\"", tab, fflags);
+ free(fflags);
+
+ fflags = flags_to_string(flags);
+ (void)printf(" found \"%s\"", fflags);
+ free(fflags);
+
+ if (uflag) {
+ if (chflags(p->fts_accpath, (u_int)s->st_flags)) {
+ error = errno;
+ RECORD_FAILURE(15, error);
+ (void)printf(" not modified: %s\n",
+ strerror(error));
+ } else {
+ (void)printf(" modified\n");
+ }
+ } else {
+ (void)printf("\n");
+ }
+ tab = "\t";
+ }
+ }
+#ifdef ENABLE_MD5
+ if (s->flags & F_MD5) {
+ char *new_digest, buf[33];
+#ifdef __clang__
+/* clang doesn't like MD5 due to security concerns, but it's used for file data/metadata integrity.. */
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ new_digest = MD5File(p->fts_accpath, buf);
+#pragma clang diagnostic pop
+#endif
+ if (!new_digest) {
+ LABEL;
+ error = errno;
+ RECORD_FAILURE(16, error);
+ printf("%sMD5: %s: %s\n", tab, p->fts_accpath,
+ strerror(error));
+ tab = "\t";
+ } else if (strcmp(new_digest, s->md5digest)) {
+ LABEL;
+ printf("%sMD5 expected %s found %s\n", tab, s->md5digest,
+ new_digest);
+ tab = "\t";
+ }
+ }
+#endif /* ENABLE_MD5 */
+#ifdef ENABLE_SHA1
+ if (s->flags & F_SHA1) {
+ char *new_digest, buf[41];
+#ifdef __clang__
+/* clang doesn't like SHA1 due to security concerns, but it's used for file data/metadata integrity.. */
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ new_digest = SHA1_File(p->fts_accpath, buf);
+#pragma clang diagnostic pop
+#endif
+ if (!new_digest) {
+ LABEL;
+ error = errno;
+ RECORD_FAILURE(17, error);
+ printf("%sSHA-1: %s: %s\n", tab, p->fts_accpath,
+ strerror(error));
+ tab = "\t";
+ } else if (strcmp(new_digest, s->sha1digest)) {
+ LABEL;
+ printf("%sSHA-1 expected %s found %s\n",
+ tab, s->sha1digest, new_digest);
+ tab = "\t";
+ }
+ }
+#endif /* ENABLE_SHA1 */
+#ifdef ENABLE_RMD160
+ if (s->flags & F_RMD160) {
+ char *new_digest, buf[41];
+#ifdef __clang__
+/* clang doesn't like RIPEMD160 due to security concerns, but it's used for file data/metadata integrity.. */
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ new_digest = RIPEMD160_File(p->fts_accpath, buf);
+#pragma clang diagnostic pop
+#endif
+ if (!new_digest) {
+ LABEL;
+ error = errno;
+ RECORD_FAILURE(18, error);
+ printf("%sRIPEMD160: %s: %s\n", tab,
+ p->fts_accpath, strerror(error));
+ tab = "\t";
+ } else if (strcmp(new_digest, s->rmd160digest)) {
+ LABEL;
+ printf("%sRIPEMD160 expected %s found %s\n",
+ tab, s->rmd160digest, new_digest);
+ tab = "\t";
+ }
+ }
+#endif /* ENABLE_RMD160 */
+#ifdef ENABLE_SHA256
+ if (s->flags & F_SHA256) {
+ char *new_digest, buf[kSHA256NullTerminatedBuffLen];
+
+ new_digest = SHA256_File(p->fts_accpath, buf);
+ if (!new_digest) {
+ LABEL;
+ error = errno;
+ RECORD_FAILURE(19, error);
+ printf("%sSHA-256: %s: %s\n", tab, p->fts_accpath,
+ strerror(error));
+ tab = "\t";
+ } else if (strcmp(new_digest, s->sha256digest)) {
+ LABEL;
+ printf("%sSHA-256 expected %s found %s\n",
+ tab, s->sha256digest, new_digest);
+ tab = "\t";
+ }
+ }
+#endif /* ENABLE_SHA256 */
+
+ if (s->flags & F_SLINK &&
+ strcmp(cp = rlink(p->fts_accpath), s->slink)) {
+ LABEL;
+ (void)printf("%slink_ref expected %s found %s\n",
+ tab, s->slink, cp);
+ }
+ if ((s->flags & F_BTIME) &&
+ ((s->st_birthtimespec.tv_sec != p->fts_statp->st_birthtimespec.tv_sec) ||
+ (s->st_birthtimespec.tv_nsec != p->fts_statp->st_birthtimespec.tv_nsec))) {
+ if (!mflag) {
+ LABEL;
+ (void)printf("%sbirth time expected %.24s.%09ld ",
+ tab, ctime(&s->st_birthtimespec.tv_sec), s->st_birthtimespec.tv_nsec);
+ (void)printf("found %.24s.%09ld\n",
+ ctime(&p->fts_statp->st_birthtimespec.tv_sec), p->fts_statp->st_birthtimespec.tv_nsec);
+ tab = "\t";
+ }
+ if (!insert_birth && mflag) {
+ uint64_t s_create_time = timespec_to_apfs_timestamp(&s->st_birthtimespec);
+ char *birth_string = "BIRTH";
+ set_key_value_pair(birth_string, &s_create_time, true);
+ insert_birth = 1;
+ }
+ }
+ if ((s->flags & F_ATIME) &&
+ ((s->st_atimespec.tv_sec != p->fts_statp->st_atimespec.tv_sec) ||
+ (s->st_atimespec.tv_nsec != p->fts_statp->st_atimespec.tv_nsec))) {
+ if (!mflag) {
+ LABEL;
+ (void)printf("%saccess time expected %.24s.%09ld ",
+ tab, ctime(&s->st_atimespec.tv_sec), s->st_atimespec.tv_nsec);
+ (void)printf("found %.24s.%09ld\n",
+ ctime(&p->fts_statp->st_atimespec.tv_sec), p->fts_statp->st_atimespec.tv_nsec);
+ tab = "\t";
+ }
+ if (!insert_access && mflag) {
+ uint64_t s_access_time = timespec_to_apfs_timestamp(&s->st_atimespec);
+ char *access_string = "ACCESS";
+ set_key_value_pair(access_string, &s_access_time, true);
+ insert_access = 1;
+
+ }
+ }
+ if ((s->flags & F_CTIME) &&
+ ((s->st_ctimespec.tv_sec != p->fts_statp->st_ctimespec.tv_sec) ||
+ (s->st_ctimespec.tv_nsec != p->fts_statp->st_ctimespec.tv_nsec))) {
+ if (!mflag) {
+ LABEL;
+ (void)printf("%smetadata modification time expected %.24s.%09ld ",
+ tab, ctime(&s->st_ctimespec.tv_sec), s->st_ctimespec.tv_nsec);
+ (void)printf("found %.24s.%09ld\n",
+ ctime(&p->fts_statp->st_ctimespec.tv_sec), p->fts_statp->st_ctimespec.tv_nsec);
+ tab = "\t";
+ }
+ if (!insert_change && mflag) {
+ uint64_t s_mod_time = timespec_to_apfs_timestamp(&s->st_ctimespec);
+ char *change_string = "CHANGE";
+ set_key_value_pair(change_string, &s_mod_time, true);
+ insert_change = 1;
+ }
+ }
+ if (s->flags & F_PTIME) {
+ int supported;
+ struct timespec ptimespec = ptime(p->fts_accpath, &supported);
+ if (!supported) {
+ LABEL;
+ (void)printf("%stime added to parent folder expected %.24s.%09ld found that it is not supported\n",
+ tab, ctime(&s->st_ptimespec.tv_sec), s->st_ptimespec.tv_nsec);
+ tab = "\t";
+ } else if (supported && ((s->st_ptimespec.tv_sec != ptimespec.tv_sec) ||
+ (s->st_ptimespec.tv_nsec != ptimespec.tv_nsec))) {
+ if (!mflag) {
+ LABEL;
+ (void)printf("%stime added to parent folder expected %.24s.%09ld ",
+ tab, ctime(&s->st_ptimespec.tv_sec), s->st_ptimespec.tv_nsec);
+ (void)printf("found %.24s.%09ld\n",
+ ctime(&ptimespec.tv_sec), ptimespec.tv_nsec);
+ tab = "\t";
+ } else if (!insert_parent && mflag) {
+ uint64_t s_added_time = timespec_to_apfs_timestamp(&s->st_ptimespec);
+ char *added_string = "DATEADDED";
+ set_key_value_pair(added_string, &s_added_time, true);
+ insert_parent = 1;
+ }
+ }
+ }
+ if (s->flags & F_XATTRS) {
+ char buf[kSHA256NullTerminatedBuffLen];
+ xattr_info *ai;
+ ai = SHA256_Path_XATTRs(p->fts_accpath, buf);
+ if (!mflag) {
+ if (ai && !ai->digest) {
+ LABEL;
+ printf("%sxattrsdigest missing, expected: %s\n", tab, s->xattrsdigest);
+ tab = "\t";
+ } else if (ai && strcmp(ai->digest, s->xattrsdigest)) {
+ LABEL;
+ printf("%sxattrsdigest expected %s found %s\n",
+ tab, s->xattrsdigest, ai->digest);
+ tab = "\t";
+ }
+ }
+ if (mflag) {
+ if (ai && ai->xdstream_priv_id != s->xdstream_priv_id) {
+ set_key_value_pair((void*)&ai->xdstream_priv_id, &s->xdstream_priv_id, false);
+ }
+ }
+ free(ai);
+ }
+ if ((s->flags & F_INODE) &&
+ (p->fts_statp->st_ino != s->st_ino)) {
+ if (!mflag) {
+ LABEL;
+ (void)printf("%sinode expected %llu found %llu\n",
+ tab, s->st_ino, p->fts_statp->st_ino);
+ tab = "\t";
+ }
+ if (mflag) {
+ set_key_value_pair((void*)&p->fts_statp->st_ino, &s->st_ino, false);
+ }
+ }
+ if (s->flags & F_ACL) {
+ char *new_digest, buf[kSHA256NullTerminatedBuffLen];
+ new_digest = SHA256_Path_ACL(p->fts_accpath, buf);
+ if (!new_digest) {
+ LABEL;
+ printf("%sacldigest missing, expected: %s\n", tab, s->acldigest);
+ tab = "\t";
+ } else if (strcmp(new_digest, s->acldigest)) {
+ LABEL;
+ printf("%sacldigest expected %s found %s\n",
+ tab, s->acldigest, new_digest);
+ tab = "\t";
+ }
+ }
+ if (s->flags & F_SIBLINGID) {
+ uint64_t new_sibling_id = get_sibling_id(p->fts_accpath);
+ new_sibling_id = (new_sibling_id != p->fts_statp->st_ino) ? new_sibling_id : 0;
+ if (new_sibling_id != s->sibling_id) {
+ if (!mflag) {
+ LABEL;
+ (void)printf("%ssibling id expected %llu found %llu\n",
+ tab, s->sibling_id, new_sibling_id);
+ tab = "\t";
+ }
+ if (mflag) {
+ set_key_value_pair((void*)&new_sibling_id, &s->sibling_id, false);
+ }
+ }
+ }
+
+ 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 */
+}
+
+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)
+{
+ int error = 0;
+ static char lbuf[MAXPATHLEN];
+ ssize_t len;
+
+ if ((len = readlink(name, lbuf, sizeof(lbuf) - 1)) == -1) {
+ error = errno;
+ RECORD_FAILURE(20, error);
+ errc(1, error, "line %d: %s", lineno, name);
+ }
+ lbuf[len] = '\0';
+ return (lbuf);
+}
diff --git a/file_cmds/mtree/create.c b/file_cmds/mtree/create.c
new file mode 100644
index 0000000..9300eaa
--- /dev/null
+++ b/file_cmds/mtree/create.c
@@ -0,0 +1,611 @@
+/*-
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)create.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.sbin/mtree/create.c,v 1.37 2005/03/29 11:44:17 tobez Exp $");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fts.h>
+#include <grp.h>
+#ifndef __APPLE__
+#ifdef ENABLE_MD5
+#include <md5.h>
+#endif
+#ifdef ENABLE_SHA1
+#include <sha.h>
+#endif
+#ifdef ENABLE_RMD160
+#include <ripemd.h>
+#endif
+#ifdef ENABLE_SHA256
+#include <sha256.h>
+#endif
+#endif /* !__APPLE__ */
+#include <pwd.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+#include <vis.h>
+#include "metrics.h"
+#include "mtree.h"
+#include "extern.h"
+
+#ifdef __APPLE__
+#include "commoncrypto.h"
+#endif /* __APPLE__ */
+
+#define INDENTNAMELEN 15
+#define MAXLINELEN 80
+
+static gid_t gid;
+static uid_t uid;
+static mode_t mode;
+static u_long flags = 0xffffffff;
+static char *xattrs = kNone;
+static char *acl = kNone;
+static u_quad_t xdstream_id;
+
+static int dsort(const FTSENT **, const FTSENT **);
+static void output(int, int *, const char *, ...) __printflike(3, 4);
+static int statd(FTS *, FTSENT *, uid_t *, gid_t *, mode_t *, u_long *, char **, char **, u_quad_t *);
+static void statf(int, FTSENT *);
+
+void
+cwalk(void)
+{
+ int error = 0;
+ FTS *t;
+ FTSENT *p;
+ time_t cl;
+ char *argv[2], host[MAXHOSTNAMELEN];
+ char dot[] = ".";
+ int indent = 0;
+ char *path;
+
+ if (!nflag) {
+ (void)time(&cl);
+ (void)gethostname(host, sizeof(host));
+ (void)printf(
+ "#\t user: %s\n#\tmachine: %s\n",
+ getlogin(), host);
+ (void)printf(
+ "#\t tree: %s\n#\t date: %s",
+ fullpath, ctime(&cl));
+ }
+
+ argv[0] = dot;
+ argv[1] = NULL;
+ if ((t = fts_open(argv, ftsoptions, dsort)) == NULL) {
+ error = errno;
+ RECORD_FAILURE(76, error);
+ errc(1, error, "fts_open()");
+ }
+ while ((p = fts_read(t))) {
+ if (iflag)
+ indent = p->fts_level * 4;
+ if (check_excludes(p->fts_name, p->fts_path)) {
+ fts_set(t, p, FTS_SKIP);
+ continue;
+ }
+ switch(p->fts_info) {
+ case FTS_D:
+ if (!dflag)
+ (void)printf("\n");
+ if (!nflag) {
+ path = escape_path(p->fts_path);
+ (void)printf("# %s\n", path);
+ free(path);
+ }
+ statd(t, p, &uid, &gid, &mode, &flags, &xattrs, &acl, &xdstream_id);
+ statf(indent, p);
+ break;
+ case FTS_DP:
+ if (!nflag && (p->fts_level > 0)) {
+ path = escape_path(p->fts_path);
+ (void)printf("%*s# %s\n", indent, "", path);
+ free(path);
+ }
+ (void)printf("%*s..\n", indent, "");
+ if (!dflag)
+ (void)printf("\n");
+ break;
+ case FTS_DNR:
+ case FTS_ERR:
+ case FTS_NS:
+ warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
+ break;
+ default:
+ if (!dflag)
+ statf(indent, p);
+ break;
+
+ }
+ }
+ (void)fts_close(t);
+ if (sflag && keys & F_CKSUM) {
+ RECORD_FAILURE(77, WARN_CHECKSUM);
+ warnx("%s checksum: %lu", fullpath, (unsigned long)crc_total);
+ }
+}
+
+static void
+statf(int indent, FTSENT *p)
+{
+ int error = 0;
+ 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) {
+ RECORD_FAILURE(78, ENOMEM);
+ errx(1, "statf(): calloc() failed");
+ }
+ strvis(escaped_name, p->fts_name, VIS_WHITE | VIS_OCTAL | VIS_GLOB);
+
+ 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);
+
+ 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) {
+ pw = getpwuid(p->fts_statp->st_uid);
+ if (pw != NULL) {
+ output(indent, &offset, "uname=%s", pw->pw_name);
+ } else if (wflag) {
+ RECORD_FAILURE(27448, WARN_UNAME);
+ warnx("Could not get uname for uid=%u",
+ p->fts_statp->st_uid);
+ } else {
+ RECORD_FAILURE(79, EINVAL);
+ errx(1,
+ "Could not get uname for uid=%u",
+ p->fts_statp->st_uid);
+ }
+ }
+ if (keys & F_UID)
+ output(indent, &offset, "uid=%u", p->fts_statp->st_uid);
+ }
+ if (p->fts_statp->st_gid != gid) {
+ if (keys & F_GNAME) {
+ gr = getgrgid(p->fts_statp->st_gid);
+ if (gr != NULL) {
+ output(indent, &offset, "gname=%s", gr->gr_name);
+ } else if (wflag) {
+ RECORD_FAILURE(27449, WARN_UNAME);
+ warnx("Could not get gname for gid=%u",
+ p->fts_statp->st_gid);
+ } else {
+ RECORD_FAILURE(80, EINVAL);
+ errx(1,
+ "Could not get gname for gid=%u",
+ p->fts_statp->st_gid);
+ }
+ }
+ if (keys & F_GID)
+ output(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);
+ 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",
+ (intmax_t)p->fts_statp->st_size);
+ if (keys & F_TIME) {
+ if (tflag && !insert_mod) {
+ output(indent, &offset, "time=%ld.%09ld",
+ (long)ts.tv_sec, ts.tv_nsec);
+ insert_mod = 1;
+ }
+ if (!tflag) {
+ output(indent, &offset, "time=%ld.%09ld",
+ (long)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)) {
+ error = errno;
+ RECORD_FAILURE(27450, error);
+ errc(1, error, "%s", p->fts_accpath);
+ }
+ (void)close(fd);
+ output(indent, &offset, "cksum=%lu", (unsigned long)val);
+ }
+#ifdef ENABLE_MD5
+ if (keys & F_MD5 && S_ISREG(p->fts_statp->st_mode)) {
+ char *digest, buf[33];
+#ifdef __clang__
+/* clang doesn't like MD5 due to security concerns, but it's used for file data/metadata integrity.. */
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ digest = MD5File(p->fts_accpath, buf);
+#pragma clang diagnostic pop
+#endif
+ if (!digest) {
+ error = errno;
+ RECORD_FAILURE(81, error);
+ errc(1, error, "%s", p->fts_accpath);
+ }
+ output(indent, &offset, "md5digest=%s", digest);
+ }
+#endif /* ENABLE_MD5 */
+#ifdef ENABLE_SHA1
+ if (keys & F_SHA1 && S_ISREG(p->fts_statp->st_mode)) {
+ char *digest, buf[41];
+#ifdef __clang__
+/* clang doesn't like SHA1 due to security concerns, but it's used for file data/metadata integrity.. */
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ digest = SHA1_File(p->fts_accpath, buf);
+#pragma clang diagnostic pop
+#endif
+ if (!digest) {
+ error = errno;
+ RECORD_FAILURE(82, error);
+ errc(1, error, "%s", p->fts_accpath);
+ }
+ output(indent, &offset, "sha1digest=%s", digest);
+ }
+#endif /* ENABLE_SHA1 */
+#ifdef ENABLE_RMD160
+ if (keys & F_RMD160 && S_ISREG(p->fts_statp->st_mode)) {
+ char *digest, buf[41];
+#ifdef __clang__
+/* clang doesn't like RIPEMD160 due to security concerns, but it's used for file data/metadata integrity.. */
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ digest = RIPEMD160_File(p->fts_accpath, buf);
+#pragma clang diagnostic pop
+#endif
+ if (!digest) {
+ error = errno;
+ RECORD_FAILURE(83, error);
+ errc(1, error, "%s", p->fts_accpath);
+ }
+ output(indent, &offset, "ripemd160digest=%s", digest);
+ }
+#endif /* ENABLE_RMD160 */
+#ifdef ENABLE_SHA256
+ if (keys & F_SHA256 && S_ISREG(p->fts_statp->st_mode)) {
+ char *digest, buf[kSHA256NullTerminatedBuffLen];
+
+ digest = SHA256_File(p->fts_accpath, buf);
+ if (!digest) {
+ error = errno;
+ RECORD_FAILURE(84, error);
+ errc(1, error, "%s", p->fts_accpath);
+ }
+ output(indent, &offset, "sha256digest=%s", digest);
+ }
+#endif /* ENABLE_SHA256 */
+ if (keys & F_SLINK &&
+ (p->fts_info == FTS_SL || p->fts_info == FTS_SLNONE)) {
+ char visbuf[MAXPATHLEN * 4];
+ char *s = rlink(p->fts_accpath);
+ strvis(visbuf, s, VIS_WHITE | VIS_OCTAL);
+ output(indent, &offset, "link=%s", visbuf);
+ }
+ 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);
+ }
+ if (keys & F_BTIME) {
+ if (tflag && !insert_birth) {
+ output(indent, &offset, "btime=%ld.%09ld",
+ ts.tv_sec, ts.tv_nsec);
+ insert_birth = 1;
+ }
+ if (!tflag) {
+ output(indent, &offset, "btime=%ld.%09ld",
+ p->fts_statp->st_birthtimespec.tv_sec,
+ p->fts_statp->st_birthtimespec.tv_nsec);
+ }
+ }
+ // only check access time on regular files, as traversing a folder will update its access time
+ if (keys & F_ATIME && S_ISREG(p->fts_statp->st_mode)) {
+ if (tflag && !insert_access) {
+ output(indent, &offset, "atime=%ld.%09ld",
+ ts.tv_sec, ts.tv_nsec);
+ insert_access = 1;
+ }
+ if (!tflag) {
+ output(indent, &offset, "atime=%ld.%09ld",
+ p->fts_statp->st_atimespec.tv_sec,
+ p->fts_statp->st_atimespec.tv_nsec);
+ }
+ }
+ if (keys & F_CTIME) {
+ if (tflag && !insert_change) {
+ output(indent, &offset, "ctime=%ld.%09ld",
+ ts.tv_sec, ts.tv_nsec);
+ insert_change = 1;
+ }
+ if (!tflag) {
+ output(indent, &offset, "ctime=%ld.%09ld",
+ p->fts_statp->st_ctimespec.tv_sec,
+ p->fts_statp->st_ctimespec.tv_nsec);
+ }
+ }
+ // date added to parent folder is only supported for files and directories
+ if (keys & F_PTIME && (S_ISREG(p->fts_statp->st_mode) ||
+ S_ISDIR(p->fts_statp->st_mode))) {
+ int supported;
+ struct timespec ptimespec = ptime(p->fts_accpath, &supported);
+ if (tflag && !insert_parent) {
+ output(indent, &offset, "ptime=%ld.%09ld",
+ ts.tv_sec, ts.tv_nsec);
+ insert_parent = 1;
+ }
+ if (!tflag && supported) {
+ output(indent, &offset, "ptime=%ld.%09ld",
+ ptimespec.tv_sec,
+ ptimespec.tv_nsec);
+ }
+ }
+ if (keys & F_XATTRS) {
+ char buf[kSHA256NullTerminatedBuffLen];
+ xattr_info *ai;
+
+ ai = SHA256_Path_XATTRs(p->fts_accpath, buf);
+ if (ai && ai->digest) {
+ if ((strcmp(ai->digest, xattrs) != 0) || (ai->xdstream_priv_id != xdstream_id)) {
+ output(indent, &offset, "xattrsdigest=%s.%llu", ai->digest, ai->xdstream_priv_id);
+ }
+ free(ai);
+ ai = NULL;
+ }
+ }
+ if (keys & F_INODE) {
+ output(indent, &offset, "inode=%llu", p->fts_statp->st_ino);
+ }
+ if (keys & F_ACL) {
+ char *digest, buf[kSHA256NullTerminatedBuffLen];
+
+ digest = SHA256_Path_ACL(p->fts_accpath, buf);
+ if (digest && (strcmp(digest, acl) != 0)) {
+ output(indent, &offset, "acldigest=%s", digest);
+ }
+ }
+ if (keys & F_SIBLINGID) {
+ uint64_t sibling_id = get_sibling_id(p->fts_accpath);
+ sibling_id = (sibling_id != p->fts_statp->st_ino) ? sibling_id : 0;
+ output(indent, &offset, "siblingid=%llu", sibling_id);
+ }
+
+ (void)putchar('\n');
+}
+
+#define MAXGID 5000
+#define MAXUID 5000
+#define MAXMODE MBITS + 1
+#define MAXFLAGS 256
+#define MAXS 16
+
+static int
+statd(FTS *t, FTSENT *parent, uid_t *puid, gid_t *pgid, mode_t *pmode, u_long *pflags, char **pxattrs, char **pacl, u_quad_t *xdstream_id)
+{
+ int error = 0;
+ 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;
+ char *savexattrs = *pxattrs;
+ char *saveacl = *pacl;
+ u_quad_t savexdstream_id = *xdstream_id;
+ u_short maxgid, maxuid, maxmode, maxflags;
+ u_short g[MAXGID], u[MAXUID], m[MAXMODE], f[MAXFLAGS];
+ char *fflags;
+ static int first = 1;
+
+ if ((p = fts_children(t, 0)) == NULL) {
+ error = errno;
+ if (error) {
+ RECORD_FAILURE(85, error);
+ errc(1, error, "%s", RP(parent));
+ }
+ return (1);
+ }
+
+ bzero(g, sizeof(g));
+ bzero(u, sizeof(u));
+ bzero(m, sizeof(m));
+ bzero(f, sizeof(f));
+
+ maxuid = maxgid = maxmode = maxflags = 0;
+ for (; p; p = p->fts_link) {
+ if (!dflag || (dflag && S_ISDIR(p->fts_statp->st_mode))) {
+ smode = p->fts_statp->st_mode & MBITS;
+ if (smode < MAXMODE && ++m[smode] > maxmode) {
+ savemode = smode;
+ maxmode = m[smode];
+ }
+ sgid = p->fts_statp->st_gid;
+ if (sgid < MAXGID && ++g[sgid] > maxgid) {
+ savegid = sgid;
+ maxgid = g[sgid];
+ }
+ suid = p->fts_statp->st_uid;
+ if (suid < MAXUID && ++u[suid] > maxuid) {
+ saveuid = suid;
+ maxuid = u[suid];
+ }
+
+ /*
+ * XXX
+ * note that we don't count the most common xattr/acl digest
+ * so set will always the default value (none)
+ */
+
+ /*
+ * 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 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)) ||
+ ((keys & F_MODE) && (*pmode != savemode)) ||
+ ((keys & F_FLAGS) && (*pflags != saveflags)) ||
+ (first)) {
+ first = 0;
+ if (dflag)
+ (void)printf("/set type=dir");
+ else
+ (void)printf("/set type=file");
+ if (keys & F_UNAME) {
+ pw = getpwuid(saveuid);
+ if (pw != NULL) {
+ (void)printf(" uname=%s", pw->pw_name);
+ } else if (wflag) {
+ RECORD_FAILURE(27451, WARN_UNAME);
+ warnx( "Could not get uname for uid=%u", saveuid);
+ } else {
+ RECORD_FAILURE(86, EINVAL);
+ errx(1, "Could not get uname for uid=%u", saveuid);
+ }
+ }
+ if (keys & F_UID)
+ (void)printf(" uid=%lu", (u_long)saveuid);
+ if (keys & F_GNAME) {
+ gr = getgrgid(savegid);
+ if (gr != NULL) {
+ (void)printf(" gname=%s", gr->gr_name);
+ } else if (wflag) {
+ RECORD_FAILURE(27452, WARN_UNAME);
+ warnx("Could not get gname for gid=%u", savegid);
+ } else {
+ RECORD_FAILURE(87, EINVAL);
+ errx(1, "Could not get gname for gid=%u", savegid);
+ }
+ }
+ if (keys & F_GID)
+ (void)printf(" gid=%lu", (u_long)savegid);
+ if (keys & F_MODE)
+ (void)printf(" mode=%#o", savemode);
+ if (keys & F_NLINK)
+ (void)printf(" nlink=1");
+ if (keys & F_FLAGS) {
+ fflags = flags_to_string(saveflags);
+ (void)printf(" flags=%s", fflags);
+ free(fflags);
+ }
+ if (keys & F_XATTRS)
+ (void)printf(" xattrsdigest=%s.%llu", savexattrs, savexdstream_id);
+ if (keys & F_ACL)
+ (void)printf(" acldigest=%s", saveacl);
+ (void)printf("\n");
+ *puid = saveuid;
+ *pgid = savegid;
+ *pmode = savemode;
+ *pflags = saveflags;
+ *pxattrs = savexattrs;
+ *pacl = saveacl;
+ *xdstream_id = savexdstream_id;
+ }
+ return (0);
+}
+
+static int
+dsort(const FTSENT **a, const FTSENT **b)
+{
+ if (S_ISDIR((*a)->fts_statp->st_mode)) {
+ if (!S_ISDIR((*b)->fts_statp->st_mode))
+ return (1);
+ } else if (S_ISDIR((*b)->fts_statp->st_mode))
+ return (-1);
+ return (strcmp((*a)->fts_name, (*b)->fts_name));
+}
+
+#include <stdarg.h>
+
+void
+output(int indent, int *offset, const char *fmt, ...)
+{
+ va_list ap;
+ char buf[1024];
+ va_start(ap, fmt);
+ (void)vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ if (*offset + strlen(buf) > MAXLINELEN - 3) {
+ (void)printf(" \\\n%*s", INDENTNAMELEN + indent, "");
+ *offset = INDENTNAMELEN + indent;
+ }
+ *offset += printf(" %s", buf) + 1;
+}
diff --git a/file_cmds/mtree/excludes.c b/file_cmds/mtree/excludes.c
new file mode 100644
index 0000000..cb432bf
--- /dev/null
+++ b/file_cmds/mtree/excludes.c
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2000 Massachusetts Institute of Technology
+ *
+ * Permission to use, copy, modify, and distribute this software and
+ * its documentation for any purpose and without fee is hereby
+ * granted, provided that both the above copyright notice and this
+ * permission notice appear in all copies, that both the above
+ * copyright notice and this permission notice appear in all
+ * supporting documentation, and that the name of M.I.T. not be used
+ * in advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission. M.I.T. makes
+ * 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
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
+ * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.sbin/mtree/excludes.c,v 1.8 2003/10/21 08:27:05 phk Exp $");
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <fts.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "metrics.h"
+#include "extern.h"
+
+/*
+ * We're assuming that there won't be a whole lot of excludes,
+ * so it's OK to use a stupid algorithm.
+ */
+struct exclude {
+ LIST_ENTRY(exclude) link;
+ const char *glob;
+ int pathname;
+};
+static LIST_HEAD(, exclude) excludes;
+
+void
+init_excludes(void)
+{
+ LIST_INIT(&excludes);
+}
+
+void
+read_excludes_file(const char *name)
+{
+ FILE *fp;
+ char *line, *str;
+ struct exclude *e;
+ size_t len;
+
+ fp = fopen(name, "r");
+ if (fp == 0)
+ err(1, "%s", name);
+
+ while ((line = fgetln(fp, &len)) != 0) {
+ if (line[len - 1] == '\n')
+ len--;
+ if (len == 0)
+ continue;
+
+ str = malloc(len + 1);
+ e = malloc(sizeof *e);
+ if (str == 0 || e == 0) {
+ RECORD_FAILURE(59, ENOMEM);
+ errx(1, "memory allocation error");
+ }
+ e->glob = str;
+ memcpy(str, line, len);
+ str[len] = '\0';
+ if (strchr(str, '/'))
+ e->pathname = 1;
+ else
+ e->pathname = 0;
+ LIST_INSERT_HEAD(&excludes, e, link);
+ }
+ fclose(fp);
+}
+
+int
+check_excludes(const char *fname, const char *path)
+{
+ struct exclude *e;
+
+ /* 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;
+ }
+ return 0;
+}
diff --git a/file_cmds/mtree/extern.h b/file_cmds/mtree/extern.h
new file mode 100644
index 0000000..47533c2
--- /dev/null
+++ b/file_cmds/mtree/extern.h
@@ -0,0 +1,71 @@
+/*-
+ * Copyright (c) 1991, 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.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 6/6/93
+ * $FreeBSD: src/usr.sbin/mtree/extern.h,v 1.13 2004/01/11 19:38:48 phk Exp $
+ */
+
+#ifndef _EXTERN_H_
+#define _EXTERN_H_
+
+#include "mtree.h"
+
+extern uint32_t crc_total;
+
+#ifdef _FTS_H_
+int compare(char *, NODE *, FTSENT *);
+#endif
+int crc(int, uint32_t *, off_t *);
+void cwalk(void);
+char *flags_to_string(u_long);
+char *escape_path(char *string);
+struct timespec ptime(char *path, int *supported);
+
+const char *inotype(u_int);
+u_int parsekey(char *, int *);
+char *rlink(char *);
+NODE *mtree_readspec(FILE *fi);
+int mtree_verifyspec(FILE *fi);
+int mtree_specspec(FILE *fi, FILE *fj);
+
+int check_excludes(const char *, const char *);
+void init_excludes(void);
+void read_excludes_file(const char *);
+const char * ftype(u_int type);
+
+extern int ftsoptions;
+extern u_int keys;
+extern int lineno;
+extern int dflag, eflag, iflag, nflag, qflag, rflag, sflag, uflag, wflag, mflag, tflag;
+extern int insert_mod, insert_birth, insert_access, insert_change, insert_parent;
+extern struct timespec ts;
+#ifdef MAXPATHLEN
+extern char fullpath[MAXPATHLEN];
+#endif
+
+#endif /* _EXTERN_H_ */
diff --git a/file_cmds/mtree/fix_failure_locations.py b/file_cmds/mtree/fix_failure_locations.py
new file mode 100755
index 0000000..a49906e
--- /dev/null
+++ b/file_cmds/mtree/fix_failure_locations.py
@@ -0,0 +1,77 @@
+#!/usr/bin/python
+
+#
+# This script is used to automatically fix up the location numbers in
+# calls to RECORD_FAILURE(). When adding a new call to RECORD_FAILURE,
+# write it like:
+# RECORD_FAILURE(0, ...);
+# Don't put any white space between the open parenthesis, zero and comma.
+# Once you have added the new calls to RECORD_FAILURE, then run this script,
+# passing it the path to the directory, like this:
+# python3 mtree/fix_failure_locations.py mtree/
+#
+# This script will edit the files, changing the "0" to the next available
+# location number. It will also detect and complain if you have duplicate
+# location numbers.
+#
+# DO NOT reuse location numbers! It is best if locations are consistent across
+# all versions that have that RECORD_FAILURE call.
+#
+
+import sys
+import os
+import re
+from collections import defaultdict
+from datetime import datetime,timezone
+
+class LocationUpdater(object):
+ epoch = datetime(2020, 6, 17, 23, 22, 46, 562458, tzinfo=timezone.utc)
+ location_base = int((datetime.now(timezone.utc) - epoch).total_seconds() / 60)
+ # Match the number in "RECORD_FAILURE(<number>,"
+ fail_re = (re.compile('(?<=\\bRECORD_FAILURE\\()\\d+(?=,)'),re.compile('(?<=\\bRECORD_FAILURE_MSG\\()\\d+(?=,)'))
+
+ def __init__(self, path):
+ self.location = self.location_base
+ self.path = path
+ # Counters for how often each location number was found
+ self.counts = defaultdict(int)
+ self.locations_changed = 0
+
+ # Replace the "0" in "RECORD_FAILURE(0," with next location number, in *.c
+ def fixLocations(self):
+ def replace_loc(match):
+ location = int(match.group(0))
+ if location == 0:
+ # Replace location 0 with the next available location
+ self.location += 1
+ self.locations_changed += 1
+ location = self.location
+ # Count the number of times this location number was used
+ self.counts[location] += 1
+ # Return the (possibly updated) location number
+ return str(location)
+ rootpath = self.path
+ for dirpath, dirnames, filenames in os.walk(rootpath):
+ for filename in filenames:
+ if filename.endswith(".c") or filename.endswith(".cpp"):
+ path = os.path.join(dirpath, filename)
+ content = open(path, "r").read()
+ for fail_re in self.fail_re:
+ if fail_re.search(content):
+ locations_changed_before = self.locations_changed
+ content = fail_re.sub(replace_loc, content)
+ if self.locations_changed != locations_changed_before:
+ # We updated a location number, so write the changed file
+ print("Updating file {}".format(path))
+ open(path,"w").write(content)
+
+ def duplicates(self):
+ # Return the list of keys whose count is greater than 1
+ return [k for (k,v) in iter(self.counts.items()) if v > 1]
+
+updater = LocationUpdater(sys.argv[1])
+updater.fixLocations()
+dups = updater.duplicates()
+if len(dups):
+ print("WARNING! Duplicate location numbers: {}".format(dups))
+ sys.exit(1)
diff --git a/file_cmds/mtree/metrics.c b/file_cmds/mtree/metrics.c
new file mode 100644
index 0000000..01a9fe2
--- /dev/null
+++ b/file_cmds/mtree/metrics.c
@@ -0,0 +1,155 @@
+/*
+* Copyright (c) 2020 Apple Inc. All rights reserved.
+*
+* @APPLE_LICENSE_HEADER_START@
+*
+* This file contains Original Code and/or Modifications of Original Code
+* as defined in and that are subject to the Apple Public Source License
+* Version 2.0 (the 'License'). You may not use this file except in
+* compliance with the License. Please obtain a copy of the License at
+* http://www.opensource.apple.com/apsl/ and read it before using this
+* file.
+*
+* The Original Code and all software distributed under the License are
+* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+* Please see the License for the specific language governing rights and
+* limitations under the License.
+*
+* @APPLE_LICENSE_HEADER_END@
+*/
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "metrics.h"
+
+#define MAX_WARNINGS_LOGGED 5
+#define MAX_ERRORS_LOGGED 5
+#define WARN_FIRST -1
+
+#ifndef ROUNDUP
+#define ROUNDUP(COUNT, MULTIPLE) ((((COUNT) + (MULTIPLE) - 1) / (MULTIPLE)) * (MULTIPLE))
+#endif
+
+typedef struct failure_info {
+ int location;
+ int code;
+} failure_info_t;
+
+typedef struct metrics {
+ FILE *file;
+ time_t start_time;
+ int warning_count;
+ failure_info_t warnings[MAX_WARNINGS_LOGGED];
+ int error_count;
+ failure_info_t errors[MAX_ERRORS_LOGGED];
+ int last_error_location;
+ char *path;
+ int result;
+} metrics_t;
+metrics_t metrics = {};
+
+void
+set_metrics_file(FILE *file)
+{
+ metrics.file = file;
+}
+
+void
+set_metric_start_time(time_t time)
+{
+ metrics.start_time = time;
+}
+
+void
+set_metric_path(char *path)
+{
+ metrics.path = strdup(path);
+}
+
+void
+mtree_record_failure(int location, int code)
+{
+ if (code <= WARN_FIRST) {
+ if (metrics.warning_count < MAX_WARNINGS_LOGGED) {
+ metrics.warning_count++;
+ } else {
+ // Shift up the warnings to make space for the latest one.
+ for (int index = 0; index < MAX_ERRORS_LOGGED - 1; index++) {
+ metrics.warnings[index] = metrics.warnings[index + 1];
+ }
+ }
+ metrics.warnings[metrics.warning_count - 1].location = location;
+ metrics.warnings[metrics.warning_count - 1].code = code;
+ } else {
+ int error_index = -1;
+ if (metrics.error_count <= MAX_ERRORS_LOGGED) {
+ if (metrics.error_count > 0) {
+ // Log all but the last error which occured in the location and
+ // code arrays. The last (location, error) is logged in
+ // (metrics.last_error_location, metrics.error)
+ error_index = metrics.error_count - 1;
+ }
+ metrics.error_count++;
+ } else {
+ // Shift up the errors to make space for the latest one.
+ for (int index = 0; index < MAX_ERRORS_LOGGED - 1; index++) {
+ metrics.errors[index] = metrics.errors[index + 1];
+ }
+ error_index = MAX_ERRORS_LOGGED - 1;
+ }
+ if (error_index >= 0) {
+ metrics.errors[error_index].location = metrics.last_error_location;
+ metrics.errors[error_index].code = metrics.result;
+ }
+ metrics.last_error_location = location;
+ metrics.result = code;
+ }
+}
+/*
+ * Note on format of metric string
+ * 1) dev points to the path
+ * 2) result is the overall result code from mtree
+ * 3) warnings and errors (upto 5 each) are printed in the format :
+ * w:(location1:code1),(location2:code2).... and
+ * e:(location1:code1),(location2:code2).... respectively.
+ * 4) fl is the last failure location of the run which is 0 if there is no failure
+ * 5) time is the total time taken for the run
+ */
+void
+print_metrics_to_file(void)
+{
+ if (metrics.file == NULL) {
+ return;
+ }
+
+ fprintf(metrics.file, "dev=%s result=%d ",
+ metrics.path ? metrics.path : "", metrics.result);
+ if (metrics.warning_count) {
+ fprintf(metrics.file, "w:");
+ for (int index = 0; index < metrics.warning_count; index++) {
+ fprintf(metrics.file, "(%d:%d)",
+ metrics.warnings[index].location, metrics.warnings[index].code);
+ }
+ fprintf(metrics.file, " ");
+ }
+ if (metrics.error_count > 1) {
+ fprintf(metrics.file, "e:");
+ for (int index = 0; index < metrics.error_count - 1; index++) {
+ fprintf(metrics.file, "(%d:%d)",
+ metrics.errors[index].location, metrics.errors[index].code);
+ }
+ fprintf(metrics.file, " ");
+ }
+ fprintf(metrics.file, "fl=%d time=%ld\n",
+ metrics.last_error_location, ROUNDUP((time(NULL) - metrics.start_time), 60) / 60);
+
+ fclose(metrics.file);
+ if (metrics.path) {
+ free(metrics.path);
+ metrics.path = NULL;
+ }
+}
diff --git a/file_cmds/mtree/metrics.h b/file_cmds/mtree/metrics.h
new file mode 100644
index 0000000..35dca43
--- /dev/null
+++ b/file_cmds/mtree/metrics.h
@@ -0,0 +1,48 @@
+/*
+* Copyright (c) 2020 Apple Inc. All rights reserved.
+*
+* @APPLE_LICENSE_HEADER_START@
+*
+* This file contains Original Code and/or Modifications of Original Code
+* as defined in and that are subject to the Apple Public Source License
+* Version 2.0 (the 'License'). You may not use this file except in
+* compliance with the License. Please obtain a copy of the License at
+* http://www.opensource.apple.com/apsl/ and read it before using this
+* file.
+*
+* The Original Code and all software distributed under the License are
+* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+* Please see the License for the specific language governing rights and
+* limitations under the License.
+*
+* @APPLE_LICENSE_HEADER_END@
+*/
+
+#ifndef _METRICS_H_
+#define _METRICS_H_
+
+#include <sys/time.h>
+#include <stdio.h>
+
+// mtree error logging
+enum mtree_result {
+ SUCCESS = 0,
+ WARN_TIME = -1,
+ WARN_USAGE = -2,
+ WARN_CHECKSUM = -3,
+ WARN_MISMATCH = -4,
+ WARN_UNAME = -5,
+ /* Could also be a POSIX errno value */
+};
+
+void set_metrics_file(FILE *file);
+void set_metric_start_time(time_t time);
+void set_metric_path(char *path);
+#define RECORD_FAILURE(location, error) mtree_record_failure(location, error)
+void mtree_record_failure(int location, int code);
+void print_metrics_to_file(void);
+
+#endif /* _METRICS_H_ */
diff --git a/file_cmds/mtree/misc.c b/file_cmds/mtree/misc.c
new file mode 100644
index 0000000..66e566e
--- /dev/null
+++ b/file_cmds/mtree/misc.c
@@ -0,0 +1,189 @@
+/*-
+ * Copyright (c) 1991, 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 0
+#ifndef lint
+static char sccsid[] = "@(#)misc.c 8.1 (Berkeley) 6/6/93";
+#endif /*not lint */
+#endif
+#include <sys/cdefs.h>
+#include <errno.h>
+__FBSDID("$FreeBSD: src/usr.sbin/mtree/misc.c,v 1.16 2005/03/29 11:44:17 tobez Exp $");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <err.h>
+#include <fts.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "metrics.h"
+#include "mtree.h"
+#include "extern.h"
+#import <sys/attr.h>
+#include <vis.h>
+
+typedef struct _key {
+ const char *name; /* key name */
+ u_int val; /* value */
+
+#define NEEDVALUE 0x01
+ u_int flags;
+} KEY;
+
+/* NB: the following table must be sorted lexically. */
+static KEY keylist[] = {
+ {"acldigest", F_ACL, NEEDVALUE},
+ {"atime", F_ATIME, NEEDVALUE},
+ {"btime", F_BTIME, NEEDVALUE},
+ {"cksum", F_CKSUM, NEEDVALUE},
+ {"ctime", F_CTIME, NEEDVALUE},
+ {"flags", F_FLAGS, NEEDVALUE},
+ {"gid", F_GID, NEEDVALUE},
+ {"gname", F_GNAME, NEEDVALUE},
+ {"ignore", F_IGN, 0},
+ {"inode", F_INODE, NEEDVALUE},
+ {"link", F_SLINK, NEEDVALUE},
+#ifdef ENABLE_MD5
+ {"md5digest", F_MD5, NEEDVALUE},
+#endif
+ {"mode", F_MODE, NEEDVALUE},
+ {"nlink", F_NLINK, NEEDVALUE},
+ {"nochange", F_NOCHANGE, 0},
+ {"ptime", F_PTIME, NEEDVALUE},
+#ifdef ENABLE_RMD160
+ {"ripemd160digest", F_RMD160, NEEDVALUE},
+#endif
+#ifdef ENABLE_SHA1
+ {"sha1digest", F_SHA1, NEEDVALUE},
+#endif
+#ifdef ENABLE_SHA256
+ {"sha256digest", F_SHA256, NEEDVALUE},
+#endif
+ {"siblingid", F_SIBLINGID, NEEDVALUE},
+ {"size", F_SIZE, NEEDVALUE},
+ {"time", F_TIME, NEEDVALUE},
+ {"type", F_TYPE, NEEDVALUE},
+ {"uid", F_UID, NEEDVALUE},
+ {"uname", F_UNAME, NEEDVALUE},
+ {"xattrsdigest", F_XATTRS, NEEDVALUE},
+};
+
+int keycompare(const void *, const void *);
+
+u_int
+parsekey(char *name, int *needvaluep)
+{
+ KEY *k, tmp;
+
+ tmp.name = name;
+ k = (KEY *)bsearch(&tmp, keylist, sizeof(keylist) / sizeof(KEY),
+ sizeof(KEY), keycompare);
+ if (k == NULL) {
+ RECORD_FAILURE(107, EINVAL);
+ errx(1, "line %d: unknown keyword %s", lineno, name);
+ }
+
+ if (needvaluep)
+ *needvaluep = k->flags & NEEDVALUE ? 1 : 0;
+ return (k->val);
+}
+
+int
+keycompare(const void *a, const void *b)
+{
+ return (strcmp(((const KEY *)a)->name, ((const KEY *)b)->name));
+}
+
+char *
+flags_to_string(u_long fflags)
+{
+ int error = 0;
+ char *string;
+
+ string = fflagstostr(fflags);
+ if (string != NULL && *string == '\0') {
+ free(string);
+ string = strdup("none");
+ }
+ if (string == NULL) {
+ error = errno;
+ RECORD_FAILURE(108, error);
+ errc(1, error, NULL);
+ }
+
+ return string;
+}
+
+// escape path and always return a new string so it can be freed
+char *
+escape_path(char *string)
+{
+ char *escapedPath = calloc(1, strlen(string) * 4 + 1);
+ if (escapedPath == NULL) {
+ RECORD_FAILURE(109, ENOMEM);
+ errx(1, "escape_path(): calloc() failed");
+ }
+ strvis(escapedPath, string, VIS_NL | VIS_CSTYLE | VIS_OCTAL);
+
+ return escapedPath;
+}
+
+struct ptimebuf {
+ uint32_t length;
+ attribute_set_t returned_attrs;
+ struct timespec st_ptimespec;
+} __attribute__((aligned(4), packed));
+
+// ptime is not supported on root filesystems or HFS filesystems older than the feature being introduced
+struct timespec
+ptime(char *path, int *supported) {
+
+ int error = 0;
+ int ret = 0;
+ struct ptimebuf buf;
+ struct attrlist list = {
+ .bitmapcount = ATTR_BIT_MAP_COUNT,
+ .commonattr = ATTR_CMN_RETURNED_ATTRS | ATTR_CMN_ADDEDTIME,
+ };
+ ret = getattrlist(path, &list, &buf, sizeof(buf), FSOPT_NOFOLLOW);
+ if (ret) {
+ error = errno;
+ RECORD_FAILURE(110, error);
+ errc(1, error, "ptime: getattrlist");
+ }
+
+ *supported = 0;
+ if (buf.returned_attrs.commonattr & ATTR_CMN_ADDEDTIME) {
+ *supported = 1;
+ }
+
+ return buf.st_ptimespec;
+
+}
diff --git a/file_cmds/mtree/mtree.8 b/file_cmds/mtree/mtree.8
new file mode 100644
index 0000000..1aa529a
--- /dev/null
+++ b/file_cmds/mtree/mtree.8
@@ -0,0 +1,393 @@
+.\" Copyright (c) 1989, 1990, 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.
+.\" 4. 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.
+.\"
+.\" From: @(#)mtree.8 8.2 (Berkeley) 12/11/93
+.\" $FreeBSD: src/usr.sbin/mtree/mtree.8,v 1.53 2005/07/31 03:30:47 keramida Exp $
+.\"
+.Dd March 29, 2005
+.Dt MTREE 8
+.Os
+.Sh NAME
+.Nm mtree
+.Nd map a directory hierarchy
+.Sh SYNOPSIS
+.Nm mtree
+.Op Fl LPUcdeinqruxw
+.Bk -words
+.Op Fl f Ar spec
+.Ek
+.Bk -words
+.Op Fl f Ar spec
+.Ek
+.Bk -words
+.Op Fl K Ar keywords
+.Ek
+.Bk -words
+.Op Fl k Ar keywords
+.Ek
+.Bk -words
+.Op Fl p Ar path
+.Ek
+.Bk -words
+.Op Fl s Ar seed
+.Ek
+.Bk -words
+.Op Fl X Ar exclude-list
+.Ek
+.Sh DESCRIPTION
+The
+.Nm mtree
+utility compares 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
+missing from either the file hierarchy or the specification.
+.Pp
+The options are as follows:
+.Bl -tag -width flag
+.\" ==========
+.It Fl c
+Print a specification for the file hierarchy to the standard output.
+.\" ==========
+.It Fl d
+Ignore everything except directory type files.
+.\" ==========
+.It Fl e
+Do not complain about files that are in the file hierarchy, but not in the
+specification.
+.\" ==========
+.It Fl f Ar file
+Read the specification from
+.Ar file ,
+instead of from the standard input.
+.Pp
+If this option is specified twice,
+the two specifications are compared with each other,
+rather than to the file hierarchy.
+The specifications be sorted like output generated using
+.Fl c .
+The output format in this case is somewhat remniscent 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
+Indent the output 4 spaces each time a directory level is descended when
+create 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.
+.\" ==========
+.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 L
+Follow all symbolic links in the file hierarchy.
+.\" ==========
+.It Fl n
+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 P
+Do not 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
+.Dq missing
+directory cannot be created because it already exists.
+This occurs when the directory is a symbolic link.
+.\" ==========
+.It Fl r
+Remove any files in the file hierarchy that are not described in the
+specification.
+.\" ==========
+.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
+was specified.
+The checksum is seeded with the specified value.
+.\" ==========
+.It Fl U
+Modify the owner, group, permissions, and modification time 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.
+.\" ==========
+.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 w
+Make some error conditions non-fatal warnings.
+.\" ==========
+.It Fl X Ar exclude-list
+The specified file contains
+.Xr fnmatch 3
+patterns matching files to be excluded from
+the specification, one to a line.
+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
+the
+.Ar exclude-list
+file.
+.\" ==========
+.It Fl x
+Do not descend below mount points in the file hierarchy.
+.El
+.Pp
+Specifications are mostly composed of ``keywords'', i.e., strings
+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
+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
+.Xr chflags 1
+for information on these names.
+If no flags are to be set the string
+.Dq none
+may be used to override the current default.
+.It Cm ignore
+Ignore any file hierarchy below this file.
+.It Cm gid
+The file group as a numeric value.
+.It Cm 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
+The
+.Tn RIPEMD160
+message digest of the file.
+.It Cm mode
+The current file's permissions as a numeric (octal) or symbolic
+value.
+.It Cm nlink
+The number of hard links the file is expected to have.
+.It Cm 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
+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 btime
+The creation (birth) time of the file.
+.It Cm atime
+The last access time of the file.
+.It Cm ctime
+The last metadata modification time of the file.
+.It Cm ptime
+The time the file was added to its parent folder.
+.It Cm inode
+The inode number of the file.
+.It Cm xattrsdigest
+Digest of the extended attributes of the file.
+.It Cm acldigest
+Digest of the access control list of the file.
+.It Cm type
+The type of the file; may be set to any one of the following:
+.Pp
+.Bl -tag -width Cm -compact
+.It Cm block
+block special device
+.It Cm char
+character special device
+.It Cm dir
+directory
+.It Cm fifo
+fifo
+.It Cm file
+regular file
+.It Cm link
+symbolic link
+.It Cm socket
+socket
+.El
+.El
+.Pp
+The default set of keywords are
+.Cm flags ,
+.Cm gid ,
+.Cm mode ,
+.Cm nlink ,
+.Cm size ,
+.Cm link ,
+.Cm time ,
+and
+.Cm uid .
+.Pp
+There are four types of lines in a specification.
+.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
+pairs, separated by whitespace.
+Keyword/value pairs consist of a keyword, followed by an equals sign
+(``=''), 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,
+separated by whitespace.
+.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
+in the hierarchy will be associated with the first pattern that
+they match.
+.Pp
+Each of the keyword/value pairs consist of a keyword, followed by an
+equals sign (``=''), 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.
+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.
+.Pp
+Empty lines and lines whose first non-whitespace character is a hash
+mark (``#'') are ignored.
+.Pp
+The
+.Nm mtree
+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
+The
+.Fl d
+and
+.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
+.Fx
+distribution.
+.Sh SEE ALSO
+.Xr chflags 1 ,
+.Xr chgrp 1 ,
+.Xr chmod 1 ,
+.Xr cksum 1 ,
+.Xr md5 1 ,
+.Xr stat 2 ,
+.Xr fts 3 ,
+.Xr md5 3 ,
+.Xr chown 8
+.Sh HISTORY
+The
+.Nm mtree
+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 .
+The
+.Tn SHA-1
+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 .
diff --git a/file_cmds/mtree/mtree.c b/file_cmds/mtree/mtree.c
new file mode 100644
index 0000000..edf0cce
--- /dev/null
+++ b/file_cmds/mtree/mtree.c
@@ -0,0 +1,358 @@
+/*-
+ * Copyright (c) 1989, 1990, 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 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1989, 1990, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)mtree.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.sbin/mtree/mtree.c,v 1.29 2004/06/04 19:29:28 ru Exp $");
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include "metrics.h"
+#include "mtree.h"
+#include "extern.h"
+
+#define SECONDS_IN_A_DAY (60 * 60 * 24)
+
+int ftsoptions = FTS_PHYSICAL;
+int cflag, dflag, eflag, iflag, nflag, qflag, rflag, sflag, uflag, Uflag, wflag, mflag, tflag;
+int insert_mod, insert_birth, insert_access, insert_change, insert_parent;
+struct timespec ts;
+u_int keys;
+char fullpath[MAXPATHLEN];
+CFMutableDictionaryRef dict;
+char *filepath;
+
+static void usage(void);
+static bool write_plist_to_file(void);
+
+static void
+do_cleanup(void) {
+
+ if (mflag) {
+ if (dict)
+ CFRelease(dict);
+ if (filepath)
+ free(filepath);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int error = 0;
+ int ch;
+ char *dir, *p;
+ int status;
+ FILE *spec1, *spec2;
+ char *timestamp = NULL;
+ char *timeformat = "%FT%T";
+ FILE *file = NULL;
+
+ dir = NULL;
+ keys = KEYDEFAULT;
+ init_excludes();
+ spec1 = stdin;
+ spec2 = NULL;
+ set_metric_start_time(time(NULL));
+
+ atexit(do_cleanup);
+ atexit(print_metrics_to_file);
+
+ while ((ch = getopt(argc, argv, "cdef:iK:k:LnPp:qrs:UuwxX:m:F:t:E:")) != -1)
+ switch((char)ch) {
+ case 'c':
+ cflag = 1;
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 'e':
+ eflag = 1;
+ break;
+ case 'f':
+ if (spec1 == stdin) {
+ spec1 = fopen(optarg, "r");
+ if (spec1 == NULL) {
+ error = errno;
+ RECORD_FAILURE(88, error);
+ errc(1, error, "%s", optarg);
+ }
+ } else if (spec2 == NULL) {
+ spec2 = fopen(optarg, "r");
+ if (spec2 == NULL) {
+ error = errno;
+ RECORD_FAILURE(89, error);
+ errc(1, error, "%s", optarg);
+ }
+ } else {
+ RECORD_FAILURE(90, WARN_USAGE);
+ usage();
+ }
+ break;
+ case 'i':
+ iflag = 1;
+ break;
+ case 'K':
+ while ((p = strsep(&optarg, " \t,")) != NULL)
+ if (*p != '\0')
+ keys |= parsekey(p, NULL);
+ break;
+ case 'k':
+ keys = F_TYPE;
+ while ((p = strsep(&optarg, " \t,")) != NULL)
+ if (*p != '\0')
+ keys |= parsekey(p, NULL);
+ break;
+ case 'L':
+ ftsoptions &= ~FTS_PHYSICAL;
+ ftsoptions |= FTS_LOGICAL;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'P':
+ ftsoptions &= ~FTS_LOGICAL;
+ ftsoptions |= FTS_PHYSICAL;
+ break;
+ case 'p':
+ dir = optarg;
+ break;
+ case 'q':
+ qflag = 1;
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ crc_total = (uint32_t)~strtoul(optarg, &p, 0);
+ if (*p) {
+ RECORD_FAILURE(91, WARN_USAGE);
+ errx(1, "illegal seed value -- %s", optarg);
+ }
+ break;
+ case 'U':
+ Uflag = 1;
+ uflag = 1;
+ break;
+ case 'u':
+ uflag = 1;
+ break;
+ case 'w':
+ wflag = 1;
+ break;
+ case 'x':
+ ftsoptions |= FTS_XDEV;
+ break;
+ case 'X':
+ read_excludes_file(optarg);
+ break;
+ case 'm':
+ mflag = 1;
+ filepath = strdup(optarg);
+ dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ insert_access = insert_mod = insert_birth = insert_change = 0;
+ break;
+ case 'F':
+ timeformat = optarg;
+ break;
+ case 't':
+ timestamp = optarg;
+ tflag = 1;
+ break;
+ case 'E':
+ if (!strcmp(optarg, "-")) {
+ file = stdout;
+ } else {
+ file = fopen(optarg, "w");
+ }
+ if (file == NULL) {
+ warnx("Could not open metrics log file %s", optarg);
+ } else {
+ set_metrics_file(file);
+ }
+ break;
+
+ case '?':
+ default:
+ RECORD_FAILURE(92, WARN_USAGE);
+ usage();
+ }
+ argc -= optind;
+// argv += optind;
+
+ if (argc) {
+ RECORD_FAILURE(93, WARN_USAGE);
+ usage();
+ }
+
+ if (timestamp) {
+ struct tm t = {};
+ char *r = strptime(timestamp, timeformat, &t);
+ if (r && r[0] == '\0') {
+ ts.tv_sec = mktime(&t);
+ if ((ts.tv_sec - time(NULL)) > 30 * SECONDS_IN_A_DAY) {
+ RECORD_FAILURE(94, WARN_TIME);
+ errx(1, "Time is more then 30 days in the future");
+ } else if (ts.tv_sec < 0) {
+ RECORD_FAILURE(95, WARN_TIME);
+ errx(1, "Time is too far in the past");
+ }
+ } else {
+ RECORD_FAILURE(96, WARN_TIME);
+ errx(1,"Cannot parse timestamp '%s' using format \"%s\"\n", timestamp, timeformat);
+ }
+ }
+
+ if (dir && chdir(dir)) {
+ error = errno;
+ RECORD_FAILURE(97, error);
+ errc(1, error, "%s", dir);
+ }
+
+ if ((cflag || sflag) && !getwd(fullpath)) {
+ RECORD_FAILURE(98, errno);
+ errx(1, "%s", fullpath);
+ }
+
+ if (dir) {
+ set_metric_path(dir);
+ }
+
+ if (cflag) {
+ cwalk();
+ exit(0);
+ }
+ if (spec2 != NULL) {
+ status = mtree_specspec(spec1, spec2);
+ if (Uflag & (status == MISMATCHEXIT)) {
+ status = 0;
+ } else {
+ RECORD_FAILURE(99, status);
+ }
+ } else {
+ status = mtree_verifyspec(spec1);
+ if (Uflag & (status == MISMATCHEXIT)) {
+ status = 0;
+ } else {
+ RECORD_FAILURE(100, status);
+ }
+ if (mflag && CFDictionaryGetCount(dict)) {
+ if (!write_plist_to_file()) {
+ RECORD_FAILURE(101, EIO);
+ errx(1, "could not write manifest to the file\n");
+ }
+ }
+ }
+ exit(status);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+"usage: mtree [-LPUcdeinqruxw] [-f spec] [-f spec] [-K key] [-k key] [-p path] [-s seed]\n"
+"\t[-X excludes]\n");
+ exit(1);
+}
+
+static bool
+write_plist_to_file(void)
+{
+ if (!dict || !filepath) {
+ RECORD_FAILURE(102, EINVAL);
+ return false;
+ }
+
+ CFIndex bytes_written = 0;
+ bool status = true;
+
+ CFStringRef file_path_str = CFStringCreateWithCString(kCFAllocatorDefault, (const char *)filepath, kCFStringEncodingUTF8);
+
+ // Create a URL specifying the file to hold the XML data.
+ CFURLRef fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
+ file_path_str, // file path name
+ kCFURLPOSIXPathStyle, // interpret as POSIX path
+ false); // is it a directory?
+
+ if (!fileURL) {
+ CFRelease(file_path_str);
+ RECORD_FAILURE(103, EINVAL);
+ return false;
+ }
+
+ CFWriteStreamRef output_stream = CFWriteStreamCreateWithFile(kCFAllocatorDefault, fileURL);
+
+ if (!output_stream) {
+ CFRelease(file_path_str);
+ CFRelease(fileURL);
+ RECORD_FAILURE(104, EIO);
+ return false;
+ }
+
+ if ((status = CFWriteStreamOpen(output_stream))) {
+ bytes_written = CFPropertyListWrite((CFPropertyListRef)dict, output_stream, kCFPropertyListXMLFormat_v1_0, 0, NULL);
+ CFWriteStreamClose(output_stream);
+ } else {
+ status = false;
+ RECORD_FAILURE(105, EIO);
+ goto out;
+ }
+
+ if (!bytes_written) {
+ status = false;
+ RECORD_FAILURE(106, EIO);
+ }
+
+out:
+ CFRelease(output_stream);
+ CFRelease(fileURL);
+ CFRelease(file_path_str);
+
+ return status;
+}
diff --git a/file_cmds/mtree/mtree.h b/file_cmds/mtree/mtree.h
new file mode 100644
index 0000000..16c927b
--- /dev/null
+++ b/file_cmds/mtree/mtree.h
@@ -0,0 +1,120 @@
+/*-
+ * Copyright (c) 1990, 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.
+ *
+ * @(#)mtree.h 8.1 (Berkeley) 6/6/93
+ * $FreeBSD: src/usr.sbin/mtree/mtree.h,v 1.7 2005/03/29 11:44:17 tobez Exp $
+ */
+
+#ifndef _MTREE_H_
+#define _MTREE_H_
+
+#include <sys/time.h>
+#include <string.h>
+#include <stdlib.h>
+
+#define KEYDEFAULT \
+ (F_GID | F_MODE | F_NLINK | F_SIZE | F_SLINK | F_TIME | F_UID | F_FLAGS)
+
+#define MISMATCHEXIT 2
+
+typedef struct _node {
+ struct _node *parent, *child; /* up, down */
+ 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 *sha256digest; /* SHA-256 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 */
+ u_long st_flags; /* flags */
+ nlink_t st_nlink; /* link count */
+ struct timespec st_birthtimespec; /* birth time (creation time) */
+ struct timespec st_atimespec; /* access time */
+ struct timespec st_ctimespec; /* metadata modification time */
+ struct timespec st_ptimespec; /* time added to parent folder */
+ char *xattrsdigest; /* digest of extended attributes */
+ u_quad_t xdstream_priv_id; /* private id of the xattr data stream */
+ ino_t st_ino; /* inode */
+ char *acldigest; /* digest of access control list */
+ u_quad_t sibling_id; /* sibling id */
+
+#define F_CKSUM 0x00000001 /* check sum */
+#define F_DONE 0x00000002 /* directory done */
+#define F_GID 0x00000004 /* gid */
+#define F_GNAME 0x00000008 /* group name */
+#define F_IGN 0x00000010 /* ignore */
+#define F_MAGIC 0x00000020 /* name has magic chars */
+#define F_MODE 0x00000040 /* mode */
+#define F_NLINK 0x00000080 /* number of links */
+#define F_SIZE 0x00000100 /* size */
+#define F_SLINK 0x00000200 /* The file the symbolic link is expected to reference */
+#define F_TIME 0x00000400 /* modification time (mtime) */
+#define F_TYPE 0x00000800 /* file type */
+#define F_UID 0x00001000 /* uid */
+#define F_UNAME 0x00002000 /* user name */
+#define F_VISIT 0x00004000 /* file visited */
+#define F_MD5 0x00008000 /* MD5 digest */
+#define F_NOCHANGE 0x00010000 /* If owner/mode "wrong", do */
+ /* not change */
+#define F_SHA1 0x00020000 /* SHA-1 digest */
+#define F_RMD160 0x00040000 /* RIPEMD160 digest */
+#define F_FLAGS 0x00080000 /* file flags */
+#define F_SHA256 0x00100000 /* SHA-256 digest */
+#define F_BTIME 0x00200000 /* creation time */
+#define F_ATIME 0x00400000 /* access time */
+#define F_CTIME 0x00800000 /* metadata modification time (ctime) */
+#define F_PTIME 0x01000000 /* time added to parent folder */
+#define F_XATTRS 0x02000000 /* digest of extended attributes */
+#define F_INODE 0x04000000 /* inode */
+#define F_ACL 0x08000000 /* digest of access control list */
+#define F_SIBLINGID 0x10000000 /* sibling id */
+ u_int flags; /* items set */
+
+#define F_BLOCK 0x001 /* block special */
+#define F_CHAR 0x002 /* char special */
+#define F_DIR 0x004 /* directory */
+#define F_FIFO 0x008 /* fifo */
+#define F_FILE 0x010 /* regular file */
+#define F_LINK 0x020 /* symbolic link */
+#define F_SOCK 0x040 /* socket */
+ u_char type; /* file type */
+
+ char name[1]; /* file name (must be last) */
+} NODE;
+
+#define RP(p) \
+ ((p)->fts_path[0] == '.' && (p)->fts_path[1] == '/' ? \
+ (p)->fts_path + 2 : (p)->fts_path)
+
+#endif /* _MTREE_H_ */
diff --git a/file_cmds/mtree/spec.c b/file_cmds/mtree/spec.c
new file mode 100644
index 0000000..f15d857
--- /dev/null
+++ b/file_cmds/mtree/spec.c
@@ -0,0 +1,470 @@
+/*-
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)spec.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.sbin/mtree/spec.c,v 1.22 2005/03/29 11:44:17 tobez Exp $");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <vis.h>
+#include "metrics.h"
+#include "mtree.h"
+#include "extern.h"
+
+int lineno; /* Current spec line number. */
+
+static void set(char *, NODE *);
+static void unset(char *, NODE *);
+
+NODE *
+mtree_readspec(FILE *fi)
+{
+ NODE *centry, *last;
+ char *p;
+ 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), fi);
+ ++lineno, c_cur = c_next, c_next = 0) {
+ /* Skip empty lines. */
+ if (buf[0] == '\n')
+ continue;
+
+ /* Find end of line. */
+ if ((p = index(buf, '\n')) == NULL) {
+ RECORD_FAILURE(21, ERANGE);
+ 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';
+
+ /* Skip leading whitespace. */
+ for (p = buf; *p && isspace(*p); ++p);
+
+ /* If nothing but whitespace or comment char, continue. */
+ if (!*p || *p == '#')
+ continue;
+
+#ifdef DEBUG
+ (void)fprintf(stderr, "line %d: {%s}\n", lineno, p);
+#endif
+ if (c_cur) {
+ set(p, centry);
+ continue;
+ }
+
+ /* Grab file name, "$", "set", or "unset". */
+ if ((p = strtok(p, "\n\t ")) == NULL) {
+ RECORD_FAILURE(22, EINVAL);
+ 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;
+ }
+
+ if (index(p, '/')) {
+ RECORD_FAILURE(23, EINVAL);
+ errx(1, "line %d: slash character in file name",
+ lineno);
+ }
+
+ if (!strcmp(p, "..")) {
+ /* Don't go up, if haven't gone down. */
+ if (!root)
+ goto noparent;
+ if (last->type != F_DIR || last->flags & F_DONE) {
+ if (last == root)
+ goto noparent;
+ last = last->parent;
+ }
+ last->flags |= F_DONE;
+ continue;
+
+noparent: RECORD_FAILURE(24, EINVAL);
+ errx(1, "line %d: no parent node", lineno);
+ }
+
+ if ((centry = calloc(1, sizeof(NODE) + strlen(p))) == NULL) {
+ RECORD_FAILURE(25, ENOMEM);
+ errx(1, "calloc");
+ }
+ *centry = ginfo;
+#define MAGIC "?*["
+ if (strpbrk(p, MAGIC))
+ centry->flags |= F_MAGIC;
+ if (strunvis(centry->name, p) == -1) {
+ RECORD_FAILURE(26, EILSEQ);
+ errx(1, "filename %s is ill-encoded", p);
+ }
+ set(NULL, centry);
+
+ if (!root) {
+ last = root = centry;
+ root->parent = root;
+ } else if (last->type == F_DIR && !(last->flags & F_DONE)) {
+ centry->parent = last;
+ last = last->child = centry;
+ } else {
+ centry->parent = last->parent;
+ centry->prev = last;
+ last = last->next = centry;
+ }
+ }
+ return (root);
+}
+
+static void
+set(char *t, NODE *ip)
+{
+ int error = 0;
+ 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) {
+ ip->flags |= type = parsekey(kw, &value);
+ if ((value == 0) || (val = strtok(NULL, " \t\n")) == NULL) {
+ RECORD_FAILURE(27, EINVAL);
+ errx(1, "line %d: missing value", lineno);
+ }
+ switch(type) {
+ case F_CKSUM:
+ ip->cksum = strtoul(val, &ep, 10);
+ if (*ep) {
+ RECORD_FAILURE(28, EINVAL);
+ errx(1, "line %d: invalid checksum %s",
+ lineno, val);
+ }
+ break;
+ case F_MD5:
+ ip->md5digest = strdup(val);
+ if (!ip->md5digest) {
+ RECORD_FAILURE(29, ENOMEM);
+ errx(1, "strdup");
+ }
+ break;
+ case F_SHA1:
+ ip->sha1digest = strdup(val);
+ if (!ip->sha1digest) {
+ RECORD_FAILURE(30, ENOMEM);
+ errx(1, "strdup");
+ }
+ break;
+ case F_SHA256:
+ ip->sha256digest = strdup(val);
+ if (!ip->sha256digest) {
+ RECORD_FAILURE(31, ENOMEM);
+ errx(1, "strdup");
+ }
+ break;
+ case F_RMD160:
+ ip->rmd160digest = strdup(val);
+ if (!ip->rmd160digest) {
+ RECORD_FAILURE(32, ENOMEM);
+ errx(1, "strdup");
+ }
+ break;
+ case F_FLAGS:
+ if (strcmp("none", val) == 0) {
+ ip->st_flags = 0;
+ } else if (strtofflags(&val, &ip->st_flags, NULL) != 0) {
+ RECORD_FAILURE(33, EINVAL);
+ errx(1, "line %d: invalid flag %s",lineno, val);
+ }
+ break;
+ case F_GID:
+ ip->st_gid = (gid_t)strtoul(val, &ep, 10);
+ if (*ep) {
+ RECORD_FAILURE(34, EINVAL);
+ errx(1, "line %d: invalid gid %s", lineno, val);
+ }
+ break;
+ case F_GNAME:
+ if ((gr = getgrnam(val)) == NULL) {
+ RECORD_FAILURE(35, EINVAL);
+ errx(1, "line %d: unknown group %s", lineno, val);
+ }
+ ip->st_gid = gr->gr_gid;
+ break;
+ case F_IGN:
+ /* just set flag bit */
+ break;
+ case F_MODE:
+ if ((m = setmode(val)) == NULL) {
+ RECORD_FAILURE(36, EINVAL);
+ errx(1, "line %d: invalid file mode %s",
+ lineno, val);
+ }
+ ip->st_mode = getmode(m, 0);
+ free(m);
+ break;
+ case F_NLINK:
+ ip->st_nlink = strtoul(val, &ep, 10);
+ if (*ep) {
+ RECORD_FAILURE(37, EINVAL);
+ errx(1, "line %d: invalid link count %s",
+ lineno, val);
+ }
+ break;
+ case F_SIZE:
+ ip->st_size = strtoq(val, &ep, 10);
+ if (*ep) {
+ RECORD_FAILURE(38, EINVAL);
+ errx(1, "line %d: invalid size %s",
+ lineno, val);
+ }
+ break;
+ case F_SLINK:
+ ip->slink = malloc(strlen(val) + 1);
+ if (ip->slink == NULL) {
+ RECORD_FAILURE(39, ENOMEM);
+ errx(1, "malloc");
+ }
+ if (strunvis(ip->slink, val) == -1) {
+ RECORD_FAILURE(40, EILSEQ);
+ errx(1, "symlink %s is ill-encoded", val);
+ }
+ break;
+ case F_TIME:
+ ip->st_mtimespec.tv_sec = strtoul(val, &ep, 10);
+ if (*ep != '.') {
+ RECORD_FAILURE(41, EINVAL);
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ }
+ val = ep + 1;
+ ip->st_mtimespec.tv_nsec = strtoul(val, &ep, 10);
+ if (*ep) {
+ RECORD_FAILURE(42, EINVAL);
+ errx(1, "line %d: invalid time %s",
+ lineno, 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:
+ RECORD_FAILURE(43, EINVAL);
+ errx(1, "line %d: unknown file type %s",
+ lineno, val);
+ }
+ break;
+ case F_UID:
+ ip->st_uid = (uid_t)strtoul(val, &ep, 10);
+ if (*ep) {
+ RECORD_FAILURE(44, EINVAL);
+ errx(1, "line %d: invalid uid %s", lineno, val);
+ }
+ break;
+ case F_UNAME:
+ if ((pw = getpwnam(val)) == NULL) {
+ RECORD_FAILURE(45, EINVAL);
+ errx(1, "line %d: unknown user %s", lineno, val);
+ }
+ ip->st_uid = pw->pw_uid;
+ break;
+ case F_BTIME:
+ ip->st_birthtimespec.tv_sec = strtoul(val, &ep, 10);
+ if (*ep != '.') {
+ RECORD_FAILURE(46, EINVAL);
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ }
+ val = ep + 1;
+ ip->st_birthtimespec.tv_nsec = strtoul(val, &ep, 10);
+ if (*ep) {
+ RECORD_FAILURE(47, EINVAL);
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ }
+ break;
+ case F_ATIME:
+ ip->st_atimespec.tv_sec = strtoul(val, &ep, 10);
+ if (*ep != '.') {
+ RECORD_FAILURE(48, EINVAL);
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ }
+ val = ep + 1;
+ ip->st_atimespec.tv_nsec = strtoul(val, &ep, 10);
+ if (*ep) {
+ RECORD_FAILURE(49, EINVAL);
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ }
+ break;
+ case F_CTIME:
+ ip->st_ctimespec.tv_sec = strtoul(val, &ep, 10);
+ if (*ep != '.') {
+ RECORD_FAILURE(50, EINVAL);
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ }
+ val = ep + 1;
+ ip->st_ctimespec.tv_nsec = strtoul(val, &ep, 10);
+ if (*ep) {
+ RECORD_FAILURE(51, EINVAL);
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ }
+ break;
+ case F_PTIME:
+ ip->st_ptimespec.tv_sec = strtoul(val, &ep, 10);
+ if (*ep != '.') {
+ RECORD_FAILURE(52, EINVAL);
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ }
+ val = ep + 1;
+ ip->st_ptimespec.tv_nsec = strtoul(val, &ep, 10);
+ if (*ep) {
+ RECORD_FAILURE(53, EINVAL);
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ }
+ break;
+ case F_XATTRS:
+ ep = strtok(val,".");
+ ip->xattrsdigest = strdup(ep);
+ if (!ip->xattrsdigest) {
+ error = errno;
+ RECORD_FAILURE(54, error);
+ errc(1, error, "strdup");
+ }
+ val = strtok(NULL,".");
+ if (val) {
+ ip->xdstream_priv_id = strtoull(val, &ep, 10);
+ if (*ep) {
+ RECORD_FAILURE(55, EINVAL);
+ errx(1, "line %d: invalid private id %s",
+ lineno, val);
+ }
+ } else {
+ ip->xdstream_priv_id = 0;
+ }
+ break;
+ case F_INODE:
+ ip->st_ino = (ino_t)strtoull(val, &ep, 10);
+ if (*ep) {
+ RECORD_FAILURE(56, EINVAL);
+ errx(1, "line %d: invalid inode %s",
+ lineno, val);
+ }
+ break;
+ case F_ACL:
+ ip->acldigest = strdup(val);
+ if (!ip->acldigest) {
+ error = errno;
+ RECORD_FAILURE(57, error);
+ errc(1, error, "strdup");
+ }
+ break;
+ case F_SIBLINGID:
+ ip->sibling_id = (quad_t)strtoull(val, &ep, 10);
+ if (*ep) {
+ RECORD_FAILURE(58, EINVAL);
+ errx(1, "line %d: invalid sibling id %s", lineno, val);
+ }
+ }
+ }
+}
+
+static void
+unset(char *t, NODE *ip)
+{
+ char *p;
+
+ while ((p = strtok(t, "\n\t ")))
+ ip->flags &= ~parsekey(p, NULL);
+}
diff --git a/file_cmds/mtree/specspec.c b/file_cmds/mtree/specspec.c
new file mode 100644
index 0000000..cb08384
--- /dev/null
+++ b/file_cmds/mtree/specspec.c
@@ -0,0 +1,327 @@
+/*-
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.sbin/mtree/specspec.c,v 1.6 2005/03/29 11:44:17 tobez Exp $");
+
+#include <sys/param.h>
+#include <err.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include "metrics.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, ftype(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=%d", n->st_nlink);
+ if (f & F_SIZE)
+ printf(" size=%jd", (intmax_t)n->st_size);
+ if (f & F_TIME)
+ printf(" time=%ld.%09ld", n->st_mtimespec.tv_sec, n->st_mtimespec.tv_nsec);
+ 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(" md5digest=%s", n->md5digest);
+ if (f & F_SHA1)
+ printf(" sha1digest=%s", n->sha1digest);
+ if (f & F_RMD160)
+ printf(" rmd160digest=%s", n->rmd160digest);
+ if (f & F_SHA256)
+ printf(" sha256digest=%s", n->sha256digest);
+ if (f & F_FLAGS)
+ printf(" flags=%s", flags_to_string(n->st_flags));
+ if (f & F_BTIME)
+ printf(" btime=%ld.%09ld", n->st_birthtimespec.tv_sec, n->st_birthtimespec.tv_nsec);
+ if (f & F_ATIME)
+ printf(" atime=%ld.%09ld", n->st_atimespec.tv_sec, n->st_atimespec.tv_nsec);
+ if (f & F_CTIME)
+ printf(" ctime=%ld.%09ld", n->st_ctimespec.tv_sec, n->st_ctimespec.tv_nsec);
+ if (f & F_PTIME)
+ printf(" ptime=%ld.%09ld", n->st_ptimespec.tv_sec, n->st_ptimespec.tv_nsec);
+ if (f & F_XATTRS)
+ printf(" xattrsdigest=%s.%llu", n->xattrsdigest, n->xdstream_priv_id);
+ if (f & F_INODE)
+ printf(" inode=%llu", n->st_ino);
+ if (f & F_ACL)
+ printf(" acldigest=%s", n->acldigest);
+ if (f & F_SIBLINGID)
+ printf(" siblingid=%llu", n->sibling_id);
+
+ 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)) {
+ return 0;
+ } else if (n1 == NULL) {
+ differs = n2->flags;
+ RECORD_FAILURE(111, WARN_MISMATCH);
+ mismatch(n1, n2, differs, path);
+ return (1);
+ } else if (n2 == NULL) {
+ differs = n1->flags;
+ RECORD_FAILURE(112, WARN_MISMATCH);
+ mismatch(n1, n2, differs, path);
+ return (1);
+ } else if (n1->type != n2->type) {
+ differs = 0;
+ RECORD_FAILURE(113, WARN_MISMATCH);
+ 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 (FF(n1, n2, F_FLAGS, st_flags))
+ differs |= F_FLAGS;
+ if (FM(n1, n2, F_BTIME, st_birthtimespec))
+ differs |= F_BTIME;
+ if (FM(n1, n2, F_ATIME, st_atimespec))
+ differs |= F_ATIME;
+ if (FM(n1, n2, F_CTIME, st_ctimespec))
+ differs |= F_CTIME;
+ if (FM(n1, n2, F_PTIME, st_ptimespec))
+ differs |= F_PTIME;
+ if (FS(n1, n2, F_XATTRS, xattrsdigest))
+ differs |= F_XATTRS;
+ if (FF(n1, n2, F_INODE, st_ino))
+ differs |= F_INODE;
+ if (FS(n1, n2, F_ACL, acldigest))
+ differs |= F_ACL;
+ if (FF(n1, n2, F_SIBLINGID, sibling_id))
+ differs |= F_SIBLINGID;
+
+ if (differs) {
+ RECORD_FAILURE(114, WARN_MISMATCH);
+ 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, c;
+ 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);
+ if (i) {
+ RECORD_FAILURE(115, WARN_MISMATCH);
+ }
+ free(np);
+ c = compare_nodes(c1, c2, path);
+ if (c) {
+ RECORD_FAILURE(116, WARN_MISMATCH);
+ }
+ i += c;
+ } else if (c2 == NULL && c1->type == F_DIR) {
+ asprintf(&np, "%s%s/", path, c1->name);
+ i = walk_in_the_forest(c1, c2, np);
+ if (i) {
+ RECORD_FAILURE(117, WARN_MISMATCH);
+ }
+ free(np);
+ c = compare_nodes(c1, c2, path);
+ if (c) {
+ RECORD_FAILURE(118, WARN_MISMATCH);
+ }
+ i += c;
+ } else if (c1 == NULL || c2 == NULL) {
+ i = compare_nodes(c1, c2, path);
+ if (i) {
+ RECORD_FAILURE(119, WARN_MISMATCH);
+ }
+ } 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);
+ if (i) {
+ RECORD_FAILURE(120, WARN_MISMATCH);
+ }
+ free(np);
+ c = compare_nodes(c1, c2, path);
+ if (c) {
+ RECORD_FAILURE(121, WARN_MISMATCH);
+ }
+ i += c;
+ } else {
+ i = compare_nodes(c1, c2, path);
+ if (i) {
+ RECORD_FAILURE(122, WARN_MISMATCH);
+ }
+ }
+ r += i;
+ c1 = n1;
+ c2 = n2;
+ }
+ return (r);
+}
+
+int
+mtree_specspec(FILE *fi, FILE *fj)
+{
+ int rval;
+ NODE *root1, *root2;
+
+ root1 = mtree_readspec(fi);
+ root2 = mtree_readspec(fj);
+ rval = walk_in_the_forest(root1, root2, "");
+ rval += compare_nodes(root1, root2, "");
+ if (rval > 0) {
+ RECORD_FAILURE(123, WARN_MISMATCH);
+ return (MISMATCHEXIT);
+ }
+ return (0);
+}
diff --git a/file_cmds/mtree/test/test00.sh b/file_cmds/mtree/test/test00.sh
new file mode 100644
index 0000000..a2e4b28
--- /dev/null
+++ b/file_cmds/mtree/test/test00.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+#
+# Copyright (c) 2003 Poul-Henning Kamp
+# All rights reserved.
+#
+# Please see src/share/examples/etc/bsd-style-copyright.
+#
+# $FreeBSD: src/usr.sbin/mtree/test/test00.sh,v 1.3 2003/11/05 22:26:08 phk Exp $
+#
+
+set -e
+
+TMP=/tmp/mtree.$$
+
+rm -rf ${TMP}
+mkdir -p ${TMP} ${TMP}/mr ${TMP}/mt
+
+
+mkdir ${TMP}/mt/foo
+mkdir ${TMP}/mr/\*
+mtree -c -p ${TMP}/mr | mtree -U -r -p ${TMP}/mt > /dev/null 2>&1
+if [ -d ${TMP}/mt/foo ] ; then
+ echo "ERROR Mtree create fell for filename with '*' char" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+rmdir ${TMP}/mr/\*
+
+mkdir -p ${TMP}/mt/foo
+mkdir ${TMP}/mr/\[f\]oo
+mtree -c -p ${TMP}/mr | mtree -U -r -p ${TMP}/mt > /dev/null 2>&1
+if [ -d ${TMP}/mt/foo ] ; then
+ echo "ERROR Mtree create fell for filename with '[' char" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+rmdir ${TMP}/mr/\[f\]oo
+
+mkdir -p ${TMP}/mt/foo
+mkdir ${TMP}/mr/\?oo
+mtree -c -p ${TMP}/mr | mtree -U -r -p ${TMP}/mt > /dev/null 2>&1
+if [ -d ${TMP}/mt/foo ] ; then
+ echo "ERROR Mtree create fell for filename with '?' char" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+rmdir ${TMP}/mr/\?oo
+
+mkdir ${TMP}/mr/\#
+mtree -c -p ${TMP}/mr > ${TMP}/_
+if mtree -U -r -p ${TMP}/mt < ${TMP}/_ > /dev/null 2>&1 ; then
+ true
+else
+ echo "ERROR Mtree create fell for filename with '#' char" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+
+if [ ! -d ${TMP}/mt/\# ] ; then
+ echo "ERROR Mtree update failed to create name with '#' char" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+rmdir ${TMP}/mr/\#
+
+rm -rf ${TMP}
+exit 0
diff --git a/file_cmds/mtree/test/test01.sh b/file_cmds/mtree/test/test01.sh
new file mode 100644
index 0000000..d056b91
--- /dev/null
+++ b/file_cmds/mtree/test/test01.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+#
+# Copyright (c) 2003 Poul-Henning Kamp
+# All rights reserved.
+#
+# Please see src/share/examples/etc/bsd-style-copyright.
+#
+# $FreeBSD: src/usr.sbin/mtree/test/test01.sh,v 1.1 2003/10/30 12:01:32 phk Exp $
+#
+
+set -e
+
+TMP=/tmp/mtree.$$
+
+rm -rf ${TMP}
+mkdir -p ${TMP} ${TMP}/mr ${TMP}/mt
+
+
+ln -s "xx this=is=wrong" ${TMP}/mr/foo
+mtree -c -p ${TMP}/mr > ${TMP}/_
+
+if mtree -U -r -p ${TMP}/mt < ${TMP}/_ > /dev/null 2>&1 ; then
+ true
+else
+ echo "ERROR Mtree failed on symlink with space char" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+
+x=x`(cd ${TMP}/mr ; ls -l foo 2>&1) || true`
+y=x`(cd ${TMP}/mt ; ls -l foo 2>&1) || true`
+
+if [ "$x" != "$y" ] ; then
+ echo "ERROR Recreation of spaced symlink failed" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+
+rm -rf ${TMP}
+exit 0
diff --git a/file_cmds/mtree/test/test02.sh b/file_cmds/mtree/test/test02.sh
new file mode 100644
index 0000000..450ebb3
--- /dev/null
+++ b/file_cmds/mtree/test/test02.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# Copyright (c) 2003 Dan Nelson
+# All rights reserved.
+#
+# Please see src/share/examples/etc/bsd-style-copyright.
+#
+# $FreeBSD: src/usr.sbin/mtree/test/test02.sh,v 1.1 2003/10/31 13:39:19 phk Exp $
+#
+
+set -e
+
+TMP=/tmp/mtree.$$
+
+rm -rf ${TMP}
+mkdir -p ${TMP} ${TMP}/mr ${TMP}/mt
+
+touch -t 199901020304 ${TMP}/mr/oldfile
+touch ${TMP}/mt/oldfile
+
+mtree -c -p ${TMP}/mr > ${TMP}/_
+
+mtree -U -r -p ${TMP}/mt < ${TMP}/_ > /dev/null
+
+x=x`(cd ${TMP}/mr ; ls -l 2>&1) || true`
+y=x`(cd ${TMP}/mt ; ls -l 2>&1) || true`
+
+if [ "$x" != "$y" ] ; then
+ echo "ERROR Update of mtime failed" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+
+rm -rf ${TMP}
+exit 0
+
diff --git a/file_cmds/mtree/test/test03.sh b/file_cmds/mtree/test/test03.sh
new file mode 100644
index 0000000..52e08c3
--- /dev/null
+++ b/file_cmds/mtree/test/test03.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+#
+# Copyright (c) 2003 Poul-Henning Kamp
+# All rights reserved.
+#
+# Please see src/share/examples/etc/bsd-style-copyright.
+#
+# $FreeBSD: src/usr.sbin/mtree/test/test03.sh,v 1.2 2005/03/29 11:44:17 tobez Exp $
+#
+
+set -e
+
+TMP=/tmp/mtree.$$
+
+rm -rf ${TMP}
+mkdir -p ${TMP}
+
+K=uid,uname,gid,gname,flags,md5digest,size,ripemd160digest,sha1digest,sha256digest,cksum
+
+rm -rf _FOO
+mkdir _FOO
+touch _FOO/_uid
+touch _FOO/_size
+touch _FOO/zztype
+
+touch _FOO/_bar
+mtree -c -K $K -p .. > ${TMP}/_r
+mtree -c -K $K -p .. > ${TMP}/_r2
+rm -rf _FOO/_bar
+
+rm -rf _FOO/zztype
+mkdir _FOO/zztype
+
+date > _FOO/_size
+
+chown nobody _FOO/_uid
+
+touch _FOO/_foo
+mtree -c -K $K -p .. > ${TMP}/_t
+
+rm -fr _FOO
+
+if mtree -f ${TMP}/_r -f ${TMP}/_r2 ; then
+ true
+else
+ echo "ERROR Compare identical failed" 1>&2
+ exit 1
+fi
+
+if mtree -f ${TMP}/_r -f ${TMP}/_t > ${TMP}/_ ; then
+ echo "ERROR Compare different succeeded" 1>&2
+ exit 1
+fi
+
+if [ `wc -l < ${TMP}/_` -ne 10 ] ; then
+ echo "ERROR wrong number of lines: `wc -l ${TMP}/_`" 1>&2
+ exit 1
+fi
+
+exit 0
diff --git a/file_cmds/mtree/test/test04.sh b/file_cmds/mtree/test/test04.sh
new file mode 100644
index 0000000..453d146
--- /dev/null
+++ b/file_cmds/mtree/test/test04.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+#
+# Copyright (c) 2003 Dan Nelson
+# All rights reserved.
+#
+# Please see src/share/examples/etc/bsd-style-copyright.
+#
+# $FreeBSD: src/usr.sbin/mtree/test/test04.sh,v 1.1 2003/11/13 11:02:57 phk Exp $
+#
+
+set -e
+
+TMP=/tmp/mtree.$$
+
+rm -rf ${TMP}
+mkdir -p ${TMP} ${TMP}/mr ${TMP}/mt
+
+mkdir ${TMP}/mr/a
+mkdir ${TMP}/mr/b
+mkdir ${TMP}/mt/a
+mkdir ${TMP}/mt/b
+touch ${TMP}/mt/z
+
+mtree -c -p ${TMP}/mr > ${TMP}/_r
+mtree -c -p ${TMP}/mt > ${TMP}/_t
+
+if mtree -f ${TMP}/_r -f ${TMP}/_t > ${TMP}/_ ; then
+ echo "ERROR wrong exit on difference" 1>&2
+ exit 1
+fi
+
+if [ `wc -l < ${TMP}/_` -ne 1 ] ; then
+ echo "ERROR spec/spec compare generated wrong output" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+
+if mtree -f ${TMP}/_t -f ${TMP}/_r > ${TMP}/_ ; then
+ echo "ERROR wrong exit on difference" 1>&2
+ exit 1
+fi
+
+if [ `wc -l < ${TMP}/_` -ne 1 ] ; then
+ echo "ERROR spec/spec compare generated wrong output" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+
+rm -rf ${TMP}
+exit 0
+
diff --git a/file_cmds/mtree/verify.c b/file_cmds/mtree/verify.c
new file mode 100644
index 0000000..7471652
--- /dev/null
+++ b/file_cmds/mtree/verify.c
@@ -0,0 +1,341 @@
+/*-
+ * Copyright (c) 1990, 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 0
+#ifndef lint
+static char sccsid[] = "@(#)verify.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.sbin/mtree/verify.c,v 1.24 2005/08/11 15:43:55 brian Exp $");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <fnmatch.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <removefile.h>
+#include "metrics.h"
+#include "mtree.h"
+#include "extern.h"
+
+static NODE *root;
+static char path[MAXPATHLEN];
+
+static int miss(NODE *, char *, size_t path_length);
+static int vwalk(void);
+
+int
+mtree_verifyspec(FILE *fi)
+{
+ int rval, mval;
+ size_t path_length = 0;
+
+ root = mtree_readspec(fi);
+ rval = vwalk();
+ mval = miss(root, path, path_length);
+
+ if (rval != 0) {
+ RECORD_FAILURE(60, WARN_MISMATCH);
+ return rval;
+ } else {
+ RECORD_FAILURE(61, WARN_MISMATCH);
+ return mval;
+ }
+}
+
+static int
+vwalk(void)
+{
+ int error = 0;
+ FTS *t;
+ FTSENT *p;
+ NODE *ep, *level;
+ int specdepth, rval;
+ char *argv[2];
+ char dot[] = ".";
+
+ argv[0] = dot;
+ argv[1] = NULL;
+ if ((t = fts_open(argv, ftsoptions, NULL)) == NULL) {
+ error = errno;
+ RECORD_FAILURE(62, error);
+ errc(1, error, "line %d: fts_open", lineno);
+ }
+ level = root;
+ specdepth = rval = 0;
+ while ((p = fts_read(t))) {
+ if (check_excludes(p->fts_name, p->fts_path)) {
+ fts_set(t, p, FTS_SKIP);
+ continue;
+ }
+ switch(p->fts_info) {
+ case FTS_D:
+ case FTS_SL:
+ break;
+ case FTS_DP:
+ if (level == NULL) {
+ RECORD_FAILURE(63, EINVAL);
+ errx(1 , "invalid root in vwalk");
+ }
+ if (specdepth > p->fts_level) {
+ for (level = level->parent; level->prev;
+ level = level->prev);
+ --specdepth;
+ }
+ continue;
+ case FTS_DNR:
+ case FTS_ERR:
+ case FTS_NS:
+ warnx("%s: %s", RP(p), strerror(p->fts_errno));
+ continue;
+ default:
+ if (dflag)
+ continue;
+ }
+
+ if (specdepth != p->fts_level)
+ goto extra;
+ for (ep = level; ep; ep = ep->next)
+ if ((ep->flags & F_MAGIC &&
+ !fnmatch(ep->name, p->fts_name, FNM_PATHNAME)) ||
+ !strcmp(ep->name, p->fts_name)) {
+ ep->flags |= F_VISIT;
+ if ((ep->flags & F_NOCHANGE) == 0 &&
+ compare(ep->name, ep, p)) {
+ RECORD_FAILURE(64, WARN_MISMATCH);
+ rval = MISMATCHEXIT;
+ }
+ if (ep->flags & F_IGN)
+ (void)fts_set(t, p, FTS_SKIP);
+ else if (ep->child && ep->type == F_DIR &&
+ p->fts_info == FTS_D) {
+ level = ep->child;
+ ++specdepth;
+ }
+ break;
+ }
+
+ if (ep)
+ continue;
+extra:
+ if (!eflag) {
+ (void)printf("%s extra", RP(p));
+
+ if (rflag) {
+ /* rflag implies: delete stuff if "extra" is observed" */
+ if (mflag) {
+ /* -mflag is used for sealing & verification -- use removefile for recursive behavior */
+ removefile_state_t rmstate;
+ rmstate = removefile_state_alloc();
+ if (removefile(p->fts_accpath, rmstate, (REMOVEFILE_RECURSIVE))) {
+ error = errno;
+ RECORD_FAILURE(65, error);
+ errx (1, "\n error deleting item (or descendant) at path %s (%s)", RP(p), strerror(error));
+ }
+ else {
+ /* removefile success */
+ (void) printf(", removed");
+ }
+ removefile_state_free(rmstate);
+
+ }
+ else {
+ /* legacy: use rmdir/unlink if "-m" not specified */
+ int syserr = 0;
+
+ if (S_ISDIR(p->fts_statp->st_mode)){
+ syserr = rmdir(p->fts_accpath);
+ }
+ else {
+ syserr = unlink(p->fts_accpath);
+ }
+
+ /* log failures */
+ if (syserr) {
+ error = errno;
+ RECORD_FAILURE(66, error);
+ (void) printf(", not removed :%s", strerror(error));
+ }
+ }
+ } else if (mflag) {
+ RECORD_FAILURE(68956, WARN_MISMATCH);
+ errx(1, "cannot generate the XML dictionary");
+ }
+ (void)putchar('\n');
+ }
+ (void)fts_set(t, p, FTS_SKIP);
+ }
+ (void)fts_close(t);
+ if (sflag) {
+ RECORD_FAILURE(67, WARN_CHECKSUM);
+ warnx("%s checksum: %lu", fullpath, (unsigned long)crc_total);
+ }
+ return (rval);
+}
+
+static int
+miss(NODE *p, char *tail, size_t path_length)
+{
+ int create;
+ char *tp;
+ const char *type, *what;
+ int serr;
+ int rval = 0;
+ int rrval = 0;
+ size_t file_name_length = 0;
+
+ for (; p; p = p->next) {
+ if (p->type != F_DIR && (dflag || p->flags & F_VISIT))
+ continue;
+ file_name_length = strnlen(p->name, MAXPATHLEN);
+ path_length += file_name_length;
+ if (path_length >= MAXPATHLEN) {
+ RECORD_FAILURE(61971, ENAMETOOLONG);
+ continue;
+ }
+ (void)strcpy(tail, p->name);
+ if (!(p->flags & F_VISIT)) {
+ /* 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) {
+ p->flags |= F_VISIT;
+ } else {
+ (void)printf("%s missing", path);
+ RECORD_FAILURE(68, WARN_MISMATCH);
+ rval = MISMATCHEXIT;
+ }
+ }
+ if (p->type != F_DIR && p->type != F_LINK) {
+ putchar('\n');
+ continue;
+ }
+
+ create = 0;
+ if (p->type == F_LINK)
+ type = "symlink";
+ else
+ type = "directory";
+ if (!(p->flags & F_VISIT) && uflag) {
+ if (!(p->flags & (F_UID | F_UNAME))) {
+ (void)printf(" (%s not created: user not specified)", type);
+ } else if (!(p->flags & (F_GID | F_GNAME))) {
+ (void)printf(" (%s not created: group not specified)", type);
+ } else if (p->type == F_LINK) {
+ if (symlink(p->slink, path)) {
+ serr = errno;
+ RECORD_FAILURE(69, serr);
+ (void)printf(" (symlink not created: %s)\n",
+ strerror(serr));
+ } else {
+ (void)printf(" (created)\n");
+ }
+ if (lchown(path, p->st_uid, p->st_gid) == -1) {
+ serr = errno;
+ if (p->st_uid == (uid_t)-1)
+ what = "group";
+ else if (lchown(path, (uid_t)-1,
+ p->st_gid) == -1)
+ what = "user & group";
+ else {
+ what = "user";
+ errno = serr;
+ }
+ serr = errno;
+ RECORD_FAILURE(70, serr);
+ (void)printf("%s: %s not modified: %s"
+ "\n", path, what, strerror(serr));
+ }
+ continue;
+ } else if (!(p->flags & F_MODE)) {
+ (void)printf(" (directory not created: mode not specified)");
+ } else if (mkdir(path, S_IRWXU)) {
+ serr = errno;
+ RECORD_FAILURE(71, serr);
+ (void)printf(" (directory not created: %s)",
+ strerror(serr));
+ } else {
+ create = 1;
+ (void)printf(" (created)");
+ }
+ }
+ if (!(p->flags & F_VISIT))
+ (void)putchar('\n');
+
+ for (tp = tail; *tp; ++tp);
+ *tp = '/';
+ ++path_length;
+ rrval = miss(p->child, tp + 1, path_length);
+ if (rrval != 0) {
+ RECORD_FAILURE(72, WARN_MISMATCH);
+ rval = rrval;
+ }
+ path_length -= (file_name_length + 1);
+ *tp = '\0';
+
+ if (!create)
+ continue;
+ if (chown(path, p->st_uid, p->st_gid) == -1) {
+ serr = errno;
+ if (p->st_uid == (uid_t)-1)
+ what = "group";
+ else if (chown(path, (uid_t)-1, p->st_gid) == -1)
+ what = "user & group";
+ else {
+ what = "user";
+ errno = serr;
+ }
+ serr = errno;
+ RECORD_FAILURE(73, serr);
+ (void)printf("%s: %s not modified: %s\n",
+ path, what, strerror(serr));
+ }
+ if (chmod(path, p->st_mode)) {
+ serr = errno;
+ RECORD_FAILURE(74, serr);
+ (void)printf("%s: permissions not set: %s\n",
+ path, strerror(serr));
+ }
+ if ((p->flags & F_FLAGS) && p->st_flags &&
+ chflags(path, (u_int)p->st_flags)) {
+ serr = errno;
+ RECORD_FAILURE(75, serr);
+ (void)printf("%s: file flags not set: %s\n",
+ path, strerror(serr));
+ }
+ }
+ return rval;
+}