From 5fd83771641d15c418f747bd343ba6738d3875f7 Mon Sep 17 00:00:00 2001 From: Cameron Katri Date: Sun, 9 May 2021 14:20:58 -0400 Subject: Import macOS userland adv_cmds-176 basic_cmds-55 bootstrap_cmds-116.100.1 developer_cmds-66 diskdev_cmds-667.40.1 doc_cmds-53.60.1 file_cmds-321.40.3 mail_cmds-35 misc_cmds-34 network_cmds-606.40.1 patch_cmds-17 remote_cmds-63 shell_cmds-216.60.1 system_cmds-880.60.2 text_cmds-106 --- file_cmds/mtree/commoncrypto.c | 378 +++++++++++++++++ file_cmds/mtree/commoncrypto.h | 36 ++ file_cmds/mtree/compare.c | 688 +++++++++++++++++++++++++++++++ file_cmds/mtree/create.c | 611 +++++++++++++++++++++++++++ file_cmds/mtree/excludes.c | 114 +++++ file_cmds/mtree/extern.h | 71 ++++ file_cmds/mtree/fix_failure_locations.py | 77 ++++ file_cmds/mtree/metrics.c | 155 +++++++ file_cmds/mtree/metrics.h | 48 +++ file_cmds/mtree/misc.c | 189 +++++++++ file_cmds/mtree/mtree.8 | 393 ++++++++++++++++++ file_cmds/mtree/mtree.c | 358 ++++++++++++++++ file_cmds/mtree/mtree.h | 120 ++++++ file_cmds/mtree/spec.c | 470 +++++++++++++++++++++ file_cmds/mtree/specspec.c | 327 +++++++++++++++ file_cmds/mtree/test/test00.sh | 67 +++ file_cmds/mtree/test/test01.sh | 40 ++ file_cmds/mtree/test/test02.sh | 36 ++ file_cmds/mtree/test/test03.sh | 60 +++ file_cmds/mtree/test/test04.sh | 51 +++ file_cmds/mtree/verify.c | 341 +++++++++++++++ 21 files changed, 4630 insertions(+) create mode 100644 file_cmds/mtree/commoncrypto.c create mode 100644 file_cmds/mtree/commoncrypto.h create mode 100644 file_cmds/mtree/compare.c create mode 100644 file_cmds/mtree/create.c create mode 100644 file_cmds/mtree/excludes.c create mode 100644 file_cmds/mtree/extern.h create mode 100755 file_cmds/mtree/fix_failure_locations.py create mode 100644 file_cmds/mtree/metrics.c create mode 100644 file_cmds/mtree/metrics.h create mode 100644 file_cmds/mtree/misc.c create mode 100644 file_cmds/mtree/mtree.8 create mode 100644 file_cmds/mtree/mtree.c create mode 100644 file_cmds/mtree/mtree.h create mode 100644 file_cmds/mtree/spec.c create mode 100644 file_cmds/mtree/specspec.c create mode 100644 file_cmds/mtree/test/test00.sh create mode 100644 file_cmds/mtree/test/test01.sh create mode 100644 file_cmds/mtree/test/test02.sh create mode 100644 file_cmds/mtree/test/test03.sh create mode 100644 file_cmds/mtree/test/test04.sh create mode 100644 file_cmds/mtree/verify.c (limited to 'file_cmds/mtree') 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + +#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 +__FBSDID("$FreeBSD: src/usr.sbin/mtree/compare.c,v 1.34 2005/03/29 11:44:17 tobez Exp $"); + +#include +#include +#include +#include + +#include +#include +#include +#include +#ifndef __APPLE__ +#ifdef ENABLE_MD5 +#include +#endif +#ifdef ENABLE_RMD160 +#include +#endif +#ifdef ENABLE_SHA1 +#include +#endif +#ifdef ENABLE_SHA256 +#include +#endif +#endif /* !__APPLE__ */ +#include +#include +#include +#include +#include + +#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 +__FBSDID("$FreeBSD: src/usr.sbin/mtree/create.c,v 1.37 2005/03/29 11:44:17 tobez Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef __APPLE__ +#ifdef ENABLE_MD5 +#include +#endif +#ifdef ENABLE_SHA1 +#include +#endif +#ifdef ENABLE_RMD160 +#include +#endif +#ifdef ENABLE_SHA256 +#include +#endif +#endif /* !__APPLE__ */ +#include +#include +#include +#include +#include +#include +#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 + +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 +__FBSDID("$FreeBSD: src/usr.sbin/mtree/excludes.c,v 1.8 2003/10/21 08:27:05 phk Exp $"); + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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(," + 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 +#include + +#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 +#include + +// 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 +#include +__FBSDID("$FreeBSD: src/usr.sbin/mtree/misc.c,v 1.16 2005/03/29 11:44:17 tobez Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include "metrics.h" +#include "mtree.h" +#include "extern.h" +#import +#include + +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 +__FBSDID("$FreeBSD: src/usr.sbin/mtree/mtree.c,v 1.29 2004/06/04 19:29:28 ru Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include + +#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 +__FBSDID("$FreeBSD: src/usr.sbin/mtree/spec.c,v 1.22 2005/03/29 11:44:17 tobez Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +__FBSDID("$FreeBSD: src/usr.sbin/mtree/specspec.c,v 1.6 2005/03/29 11:44:17 tobez Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#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 +__FBSDID("$FreeBSD: src/usr.sbin/mtree/verify.c,v 1.24 2005/08/11 15:43:55 brian Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} -- cgit v1.2.3-56-ge451