unsigned aindex = 0, flag_new_acl = 0;
acl_entry_t newent = NULL;
acl_entry_t entry = NULL;
- unsigned retval = 0 ;
+ unsigned retval = 0;
extern int fflag;
if (optflags & ACL_CLEAR_FLAG) {
filesec_t fsec = filesec_init();
- if (fsec == NULL)
+ if (fsec == NULL) {
err(1, "filesec_init() failed");
- if (filesec_set_property(fsec, FILESEC_ACL,
- _FILESEC_REMOVE_ACL) != 0)
+ }
+ if (filesec_set_property(fsec, FILESEC_ACL, _FILESEC_REMOVE_ACL) != 0) {
err(1, "filesec_set_property() failed");
- if (chmodx_np(path, fsec) != 0) {
- if (!fflag)
- warn("Failed to clear ACL on file %s", path);
- retval = 1;
- } else
- retval = 0;
+ }
+ if (follow) {
+ if (chmodx_np(path, fsec) != 0) {
+ if (!fflag) {
+ warn("Failed to clear ACL on file %s", path);
+ }
+ retval = 1;
+ }
+ } else {
+ int fd = open(path, O_SYMLINK);
+ if (fd != -1) {
+ if (fchmodx_np(fd, fsec) != 0) {
+ if (!fflag) {
+ warn("Failed to clear ACL on file %s", path);
+ }
+ retval = 1;
+ }
+ close(fd);
+ } else {
+ if (!fflag) {
+ warn("Failed to open file %s", path);
+ }
+ retval = 1;
+ }
+ }
filesec_free(fsec);
return (retval);
}
static int semrm(key_t key, int id)
{
- union semun arg;
-
if (key) {
id = semget(key, 0, 0);
if (id == -1)
return -1;
}
- return semctl(id, 0, IPC_RMID, arg);
+ return semctl(id, 0, IPC_RMID);
}
static void not_configured(__unused int unused)
#include <os/assumes.h>
#include <errno.h>
#include <fcntl.h>
+#include <string.h>
+#include <sys/xattr.h>
+#include <stdbool.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/attr.h>
+#include <unistd.h>
#include "commoncrypto.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)
{
- static const char hex[] = "0123456789abcdef";
int fd;
__block CCDigestCtx ctx;
dispatch_queue_t queue;
buf[i+i+1] = hex[digest[i] & 0x0f];
}
buf[i+i] = '\0';
+
+ return buf;
+}
+
+char *SHA256_Path_XATTRs(char *path, char *buf)
+{
+ char *xattrsSummary = NULL;
+ int options = XATTR_SHOWCOMPRESSION | XATTR_NOFOLLOW;
+ ssize_t nameBufSize = listxattr(path, NULL, 0, options);
+ 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;
+ 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)
+ err(1, "SHA256_Path_XATTRs: getxattr");
+
+ 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);
+ }
+ }
+
+ free(xattrBuf);
+ free(nameBuf);
+ free(xattrs);
+
+ digest = SHA256_Data(xattrsSummary, strlen(xattrsSummary) * sizeof(char), buf);
+ if (!digest)
+ err(1, "%s", xattrsSummary);
+
+ free(xattrsSummary);
+ return digest;
+ }
+ return kNone;
+}
+
+char *SHA256_Path_ACL(char *path, char *buf)
+{
+ 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)
+ err(1, "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;
}
+
#include <CommonCrypto/CommonDigestSPI.h>
+#define kNone "none"
+
+extern const int kSHA256NullTerminatedBuffLen;
+
#define MD5File(f, b) Digest_File(kCCDigestMD5, f, b)
#define SHA1_File(f, b) Digest_File(kCCDigestSHA1, f, b)
#define RIPEMD160_File(f, b) Digest_File(kCCDigestRMD160, f, b)
#define SHA256_File(f, b) Digest_File(kCCDigestSHA256, f, b)
char *Digest_File(CCDigestAlg algorithm, const char *filename, char *buf);
+
+char *SHA256_Path_XATTRs(char *path, char *buf);
+char *SHA256_Path_ACL(char *path, char *buf);
\ No newline at end of file
off_t len;
char *cp;
const char *tab = "";
- char *fflags;
+ char *fflags, *badflags;
+ u_long flags;
label = 0;
switch(s->type) {
(intmax_t)s->st_size, (intmax_t)p->fts_statp->st_size);
tab = "\t";
}
- /*
- * XXX
- * Catches nano-second differences, but doesn't display them.
- */
if ((s->flags & F_TIME) &&
((s->st_mtimespec.tv_sec != p->fts_statp->st_mtimespec.tv_sec) ||
(s->st_mtimespec.tv_nsec != p->fts_statp->st_mtimespec.tv_nsec))) {
LABEL;
- (void)printf("%smodification time expected %.24s ",
- tab, ctime(&s->st_mtimespec.tv_sec));
- (void)printf("found %.24s",
- ctime(&p->fts_statp->st_mtimespec.tv_sec));
+ (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;
}
}
}
- if ((s->flags & F_FLAGS) && s->st_flags != p->fts_statp->st_flags) {
- LABEL;
- fflags = flags_to_string(s->st_flags);
- (void)printf("%sflags expected \"%s\"", tab, fflags);
- free(fflags);
-
- fflags = flags_to_string(p->fts_statp->st_flags);
- (void)printf(" found \"%s\"", fflags);
+ 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 (uflag)
- if (chflags(p->fts_accpath, (u_int)s->st_flags))
- (void)printf(" not modified: %s\n",
- strerror(errno));
- else
- (void)printf(" modified\n");
- else
- (void)printf("\n");
- tab = "\t";
+ 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))
+ (void)printf(" not modified: %s\n",
+ strerror(errno));
+ else
+ (void)printf(" modified\n");
+ else
+ (void)printf("\n");
+ tab = "\t";
+ }
}
#ifdef ENABLE_MD5
if (s->flags & F_MD5) {
#endif /* ENABLE_RMD160 */
#ifdef ENABLE_SHA256
if (s->flags & F_SHA256) {
- char *new_digest, buf[65];
+ char *new_digest, buf[kSHA256NullTerminatedBuffLen];
new_digest = SHA256_File(p->fts_accpath, buf);
if (!new_digest) {
(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))) {
+ 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 ((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))) {
+ 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 ((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))) {
+ 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 (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 ((s->st_ptimespec.tv_sec != ptimespec.tv_sec) ||
+ (s->st_ptimespec.tv_nsec != ptimespec.tv_nsec)) {
+ 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";
+ }
+ }
+ if (s->flags & F_XATTRS) {
+ char *new_digest, buf[kSHA256NullTerminatedBuffLen];
+ new_digest = SHA256_Path_XATTRs(p->fts_accpath, buf);
+ if (!new_digest) {
+ LABEL;
+ printf("%sxattrsdigest missing, expected: %s\n", tab, s->xattrsdigest);
+ tab = "\t";
+ } else if (strcmp(new_digest, s->xattrsdigest)) {
+ LABEL;
+ printf("%sxattrsdigest expected %s found %s\n",
+ tab, s->xattrsdigest, new_digest);
+ tab = "\t";
+ }
+ }
+ if ((s->flags & F_INODE) &&
+ (p->fts_statp->st_ino != s->st_ino)) {
+ LABEL;
+ (void)printf("%sinode expected %llu found %llu\n",
+ tab, s->st_ino, p->fts_ino);
+ tab = "\t";
+ }
+ 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";
+ }
+ }
+
return (label);
}
static uid_t uid;
static mode_t mode;
static u_long flags = 0xffffffff;
+static char *xattrs = kNone;
+static char *acl = kNone;
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 *);
+static int statd(FTS *, FTSENT *, uid_t *, gid_t *, mode_t *, u_long *, char **, char **);
static void statf(int, FTSENT *);
void
char *argv[2], host[MAXHOSTNAMELEN];
char dot[] = ".";
int indent = 0;
+ char *path;
if (!nflag) {
(void)time(&cl);
case FTS_D:
if (!dflag)
(void)printf("\n");
- if (!nflag)
- (void)printf("# %s\n", p->fts_path);
- statd(t, p, &uid, &gid, &mode, &flags);
+ if (!nflag) {
+ path = escape_path(p->fts_path);
+ (void)printf("# %s\n", path);
+ free(path);
+ }
+ statd(t, p, &uid, &gid, &mode, &flags, &xattrs, &acl);
statf(indent, p);
break;
case FTS_DP:
- if (!nflag && (p->fts_level > 0))
- (void)printf("%*s# %s\n", indent, "", p->fts_path);
+ 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");
output(indent, &offset, "size=%jd",
(intmax_t)p->fts_statp->st_size);
if (keys & F_TIME)
- output(indent, &offset, "time=%ld.%ld",
+ 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)) {
#endif /* ENABLE_RMD160 */
#ifdef ENABLE_SHA256
if (keys & F_SHA256 && S_ISREG(p->fts_statp->st_mode)) {
- char *digest, buf[65];
+ char *digest, buf[kSHA256NullTerminatedBuffLen];
digest = SHA256_File(p->fts_accpath, buf);
if (!digest)
output(indent, &offset, "flags=%s", fflags);
free(fflags);
}
+ if (keys & F_BTIME) {
+ 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)) {
+ output(indent, &offset, "atime=%ld.%09ld",
+ p->fts_statp->st_atimespec.tv_sec,
+ p->fts_statp->st_atimespec.tv_nsec);
+ }
+ if (keys & F_CTIME) {
+ 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 (supported) {
+ output(indent, &offset, "ptime=%ld.%09ld",
+ ptimespec.tv_sec,
+ ptimespec.tv_nsec);
+ }
+ }
+ if (keys & F_XATTRS) {
+ char *digest, buf[kSHA256NullTerminatedBuffLen];
+
+ digest = SHA256_Path_XATTRs(p->fts_accpath, buf);
+ if (digest && (strcmp(digest, xattrs) != 0)) {
+ output(indent, &offset, "xattrsdigest=%s", digest);
+ }
+ }
+ 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);
+ }
+ }
+
(void)putchar('\n');
}
#define MAXS 16
static int
-statd(FTS *t, FTSENT *parent, uid_t *puid, gid_t *pgid, mode_t *pmode, u_long *pflags)
+statd(FTS *t, FTSENT *parent, uid_t *puid, gid_t *pgid, mode_t *pmode, u_long *pflags, char **pxattrs, char **pacl)
{
FTSENT *p;
gid_t sgid;
uid_t saveuid = *puid;
mode_t savemode = *pmode;
u_long saveflags = *pflags;
+ char *savexattrs = *pxattrs;
+ char *saveacl = *pacl;
u_short maxgid, maxuid, maxmode, maxflags;
u_short g[MAXGID], u[MAXUID], m[MAXMODE], f[MAXFLAGS];
char *fflags;
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
(void)printf(" flags=%s", fflags);
free(fflags);
}
+ if (keys & F_XATTRS)
+ (void)printf(" xattrsdigest=%s", savexattrs);
+ if (keys & F_ACL)
+ (void)printf(" acldigest=%s", saveacl);
(void)printf("\n");
*puid = saveuid;
*pgid = savegid;
*pmode = savemode;
*pflags = saveflags;
+ *pxattrs = savexattrs;
+ *pacl = saveacl;
}
return (0);
}
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 *);
#include <unistd.h>
#include "mtree.h"
#include "extern.h"
+#import <sys/attr.h>
+#include <vis.h>
typedef struct _key {
const char *name; /* key name */
/* NB: the following table must be sorted lexically. */
static KEY keylist[] = {
- {"cksum", F_CKSUM, NEEDVALUE},
- {"flags", F_FLAGS, NEEDVALUE},
- {"gid", F_GID, NEEDVALUE},
- {"gname", F_GNAME, NEEDVALUE},
- {"ignore", F_IGN, 0},
- {"link", F_SLINK, NEEDVALUE},
+ {"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},
+ {"md5digest", F_MD5, NEEDVALUE},
#endif
- {"mode", F_MODE, NEEDVALUE},
- {"nlink", F_NLINK, NEEDVALUE},
- {"nochange", F_NOCHANGE, 0},
+ {"mode", F_MODE, NEEDVALUE},
+ {"nlink", F_NLINK, NEEDVALUE},
+ {"nochange", F_NOCHANGE, 0},
+ {"ptime", F_PTIME, NEEDVALUE},
#ifdef ENABLE_RMD160
- {"ripemd160digest", F_RMD160, NEEDVALUE},
+ {"ripemd160digest", F_RMD160, NEEDVALUE},
#endif
#ifdef ENABLE_SHA1
- {"sha1digest", F_SHA1, NEEDVALUE},
+ {"sha1digest", F_SHA1, NEEDVALUE},
#endif
#ifdef ENABLE_SHA256
- {"sha256digest", F_SHA256, NEEDVALUE},
+ {"sha256digest", F_SHA256, NEEDVALUE},
#endif
- {"size", F_SIZE, NEEDVALUE},
- {"time", F_TIME, NEEDVALUE},
- {"type", F_TYPE, NEEDVALUE},
- {"uid", F_UID, NEEDVALUE},
- {"uname", F_UNAME, 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 *);
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)
+ 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 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) {
+ err(1, "ptime: getattrlist");
+ }
+
+ *supported = 0;
+ if (buf.returned_attrs.commonattr & ATTR_CMN_ADDEDTIME) {
+ *supported = 1;
+ }
+
+ return buf.st_ptimespec;
+
+}
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
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 */
+ ino_t st_ino; /* inode */
+ char *acldigest; /* digest of access control list */
-#define F_CKSUM 0x0001 /* check sum */
-#define F_DONE 0x0002 /* directory done */
-#define F_GID 0x0004 /* gid */
-#define F_GNAME 0x0008 /* group name */
-#define F_IGN 0x0010 /* ignore */
-#define F_MAGIC 0x0020 /* name has magic chars */
-#define F_MODE 0x0040 /* mode */
-#define F_NLINK 0x0080 /* number of links */
-#define F_SIZE 0x0100 /* size */
-#define F_SLINK 0x0200 /* link count */
-#define F_TIME 0x0400 /* modification time */
-#define F_TYPE 0x0800 /* file type */
-#define F_UID 0x1000 /* uid */
-#define F_UNAME 0x2000 /* user name */
-#define F_VISIT 0x4000 /* file visited */
-#define F_MD5 0x8000 /* MD5 digest */
-#define F_NOCHANGE 0x10000 /* If owner/mode "wrong", do */
+#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 0x20000 /* SHA-1 digest */
-#define F_RMD160 0x40000 /* RIPEMD160 digest */
-#define F_FLAGS 0x80000 /* file flags */
-#define F_SHA256 0x100000 /* SHA-256 digest */
+#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 */
u_int flags; /* items set */
#define F_BLOCK 0x001 /* block special */
if ((value == 0) || (val = strtok(NULL, " \t\n")) == NULL)
errx(1, "line %d: missing value", lineno);
switch(type) {
- case F_CKSUM:
- ip->cksum = strtoul(val, &ep, 10);
- if (*ep)
- errx(1, "line %d: invalid checksum %s",
- lineno, val);
- break;
- case F_MD5:
- ip->md5digest = strdup(val);
- if(!ip->md5digest)
- errx(1, "strdup");
- break;
- case F_SHA1:
- ip->sha1digest = strdup(val);
- if(!ip->sha1digest)
- errx(1, "strdup");
- break;
- case F_SHA256:
- ip->sha256digest = strdup(val);
- if(!ip->sha256digest)
- errx(1, "strdup");
- break;
- case F_RMD160:
- ip->rmd160digest = strdup(val);
- if(!ip->rmd160digest)
- 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)
- errx(1, "line %d: invalid flag %s",lineno, val);
- break;
- case F_GID:
- ip->st_gid = (gid_t)strtoul(val, &ep, 10);
- if (*ep)
- errx(1, "line %d: invalid gid %s", lineno, val);
- break;
- case F_GNAME:
- if ((gr = getgrnam(val)) == NULL)
- errx(1, "line %d: unknown group %s", lineno, val);
- ip->st_gid = gr->gr_gid;
- break;
- case F_IGN:
- /* just set flag bit */
- break;
- case F_MODE:
- if ((m = setmode(val)) == NULL)
- 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)
- errx(1, "line %d: invalid link count %s",
- lineno, val);
- break;
- case F_SIZE:
- ip->st_size = strtoq(val, &ep, 10);
- if (*ep)
- errx(1, "line %d: invalid size %s",
- lineno, val);
- break;
- case F_SLINK:
- ip->slink = malloc(strlen(val) + 1);
- if (ip->slink == NULL)
- errx(1, "malloc");
- if (strunvis(ip->slink, val) == -1)
- errx(1, "symlink %s is ill-encoded", val);
- break;
- case F_TIME:
- ip->st_mtimespec.tv_sec = strtoul(val, &ep, 10);
- if (*ep != '.')
- errx(1, "line %d: invalid time %s",
- lineno, val);
- val = ep + 1;
- ip->st_mtimespec.tv_nsec = strtoul(val, &ep, 10);
- if (*ep)
- 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;
+ case F_CKSUM:
+ ip->cksum = strtoul(val, &ep, 10);
+ if (*ep)
+ errx(1, "line %d: invalid checksum %s",
+ lineno, val);
break;
- case 'c':
- if (!strcmp(val, "char"))
- ip->type = F_CHAR;
+ case F_MD5:
+ ip->md5digest = strdup(val);
+ if(!ip->md5digest)
+ errx(1, "strdup");
break;
- case 'd':
- if (!strcmp(val, "dir"))
- ip->type = F_DIR;
+ case F_SHA1:
+ ip->sha1digest = strdup(val);
+ if(!ip->sha1digest)
+ errx(1, "strdup");
break;
- case 'f':
- if (!strcmp(val, "file"))
- ip->type = F_FILE;
- if (!strcmp(val, "fifo"))
- ip->type = F_FIFO;
+ case F_SHA256:
+ ip->sha256digest = strdup(val);
+ if(!ip->sha256digest)
+ errx(1, "strdup");
break;
- case 'l':
- if (!strcmp(val, "link"))
- ip->type = F_LINK;
+ case F_RMD160:
+ ip->rmd160digest = strdup(val);
+ if(!ip->rmd160digest)
+ errx(1, "strdup");
break;
- case 's':
- if (!strcmp(val, "socket"))
- ip->type = F_SOCK;
+ case F_FLAGS:
+ if (strcmp("none", val) == 0)
+ ip->st_flags = 0;
+ else if (strtofflags(&val, &ip->st_flags, NULL) != 0)
+ errx(1, "line %d: invalid flag %s",lineno, val);
break;
- default:
- 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)
- errx(1, "line %d: invalid uid %s", lineno, val);
- break;
- case F_UNAME:
- if ((pw = getpwnam(val)) == NULL)
- errx(1, "line %d: unknown user %s", lineno, val);
- ip->st_uid = pw->pw_uid;
- break;
+ case F_GID:
+ ip->st_gid = (gid_t)strtoul(val, &ep, 10);
+ if (*ep)
+ errx(1, "line %d: invalid gid %s", lineno, val);
+ break;
+ case F_GNAME:
+ if ((gr = getgrnam(val)) == NULL)
+ errx(1, "line %d: unknown group %s", lineno, val);
+ ip->st_gid = gr->gr_gid;
+ break;
+ case F_IGN:
+ /* just set flag bit */
+ break;
+ case F_MODE:
+ if ((m = setmode(val)) == NULL)
+ 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)
+ errx(1, "line %d: invalid link count %s",
+ lineno, val);
+ break;
+ case F_SIZE:
+ ip->st_size = strtoq(val, &ep, 10);
+ if (*ep)
+ errx(1, "line %d: invalid size %s",
+ lineno, val);
+ break;
+ case F_SLINK:
+ ip->slink = malloc(strlen(val) + 1);
+ if (ip->slink == NULL)
+ errx(1, "malloc");
+ if (strunvis(ip->slink, val) == -1)
+ errx(1, "symlink %s is ill-encoded", val);
+ break;
+ case F_TIME:
+ ip->st_mtimespec.tv_sec = strtoul(val, &ep, 10);
+ if (*ep != '.')
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ val = ep + 1;
+ ip->st_mtimespec.tv_nsec = strtoul(val, &ep, 10);
+ if (*ep)
+ 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:
+ 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)
+ errx(1, "line %d: invalid uid %s", lineno, val);
+ break;
+ case F_UNAME:
+ if ((pw = getpwnam(val)) == NULL)
+ errx(1, "line %d: unknown user %s", lineno, val);
+ ip->st_uid = pw->pw_uid;
+ break;
+ case F_BTIME:
+ ip->st_birthtimespec.tv_sec = strtoul(val, &ep, 10);
+ if (*ep != '.')
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ val = ep + 1;
+ ip->st_birthtimespec.tv_nsec = strtoul(val, &ep, 10);
+ if (*ep)
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ break;
+ case F_ATIME:
+ ip->st_atimespec.tv_sec = strtoul(val, &ep, 10);
+ if (*ep != '.')
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ val = ep + 1;
+ ip->st_atimespec.tv_nsec = strtoul(val, &ep, 10);
+ if (*ep)
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ break;
+ case F_CTIME:
+ ip->st_ctimespec.tv_sec = strtoul(val, &ep, 10);
+ if (*ep != '.')
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ val = ep + 1;
+ ip->st_ctimespec.tv_nsec = strtoul(val, &ep, 10);
+ if (*ep)
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ break;
+ case F_PTIME:
+ ip->st_ptimespec.tv_sec = strtoul(val, &ep, 10);
+ if (*ep != '.')
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ val = ep + 1;
+ ip->st_ptimespec.tv_nsec = strtoul(val, &ep, 10);
+ if (*ep)
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ break;
+ case F_XATTRS:
+ ip->xattrsdigest = strdup(val);
+ if(!ip->xattrsdigest)
+ err(1, "strdup");
+ break;
+ case F_INODE:
+ ip->st_ino = (ino_t)strtoull(val, &ep, 10);
+ if (*ep)
+ errx(1, "line %d: invalid inode %s", lineno, val);
+ break;
+ case F_ACL:
+ ip->acldigest = strdup(val);
+ if(!ip->acldigest)
+ err(1, "strdup");
}
}
}
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) {
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", n->xattrsdigest);
+ if (f & F_INODE)
+ printf(" inode=%llu", n->st_ino);
+ if (f & F_ACL)
+ printf(" acldigest=%s", n->acldigest);
+
printf("\n");
}
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 (differs) {
mismatch(n1, n2, differs, path);
return (1);
static NODE *root;
static char path[MAXPATHLEN];
-static void miss(NODE *, char *);
+static int miss(NODE *, char *);
static int vwalk(void);
int
mtree_verifyspec(FILE *fi)
{
- int rval;
+ int rval, mval;
root = mtree_readspec(fi);
rval = vwalk();
- miss(root, path);
- return (rval);
+ mval = miss(root, path);
+
+ if (rval != 0)
+ return rval;
+ else
+ return mval;
}
static int
return (rval);
}
-static void
+static int
miss(NODE *p, char *tail)
{
int create;
char *tp;
const char *type, *what;
int serr;
+ int rval = 0;
+ int rrval = 0;
for (; p; p = p->next) {
if (p->type != F_DIR && (dflag || p->flags & F_VISIT))
symbolic link and the -q flag is set. */
struct stat statbuf;
- if (qflag && stat(path, &statbuf) == 0)
+ if (qflag && stat(path, &statbuf) == 0) {
p->flags |= F_VISIT;
- else
+ } else {
(void)printf("%s missing", path);
+ rval = MISMATCHEXIT;
+ }
}
if (p->type != F_DIR && p->type != F_LINK) {
putchar('\n');
for (tp = tail; *tp; ++tp);
*tp = '/';
- miss(p->child, tp + 1);
+ rrval = miss(p->child, tp + 1);
+ if (rrval != 0)
+ rval = rrval;
*tp = '\0';
if (!create)
(void)printf("%s: file flags not set: %s\n",
path, strerror(errno));
}
+ return rval;
}
{
int pid, status;
- /* posix_spawn mv from to && rm from */
+ /* posix_spawn cp from to && rm from */
if ((pid = fork()) == 0) {
execl(_PATH_CP, "mv", vflg ? "-PRpv" : "-PRp", "--", from, to,
complained = 0;
for (t = argv; *t;) {
- if ((p = strrchr(*t, '/')) != NULL)
- ++p;
- else
+ size_t len = strlen(*t);
+ char truncated[len];
+
+ if ((p = strrchr(*t, '/')) != NULL) {
+ if (p[1] == '\0') { // trailing / -- treat as if not present
+ strlcpy(truncated, *t, len);
+ p = strrchr(truncated, '/');
+ if (p) {
+ ++p;
+ } else {
+ p = truncated;
+ }
+ } else {
+ ++p;
+ }
+ } else {
p = *t;
+ }
if (ISDOT(p)) {
if (!complained++)
warnx("\".\" and \"..\" may not be removed");