src/fssum: Fix duplicate CLI arguments
[xfstests-dev.git] / src / fssum.c
index 88e75621ccf2772434e9f77e439fa9abcb5760ef..3d97a70bf02f1f87a436d647fbf62f8998f0cb5a 100644 (file)
@@ -1,22 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
 /*
  * Copyright (C) 2012 STRATO AG.  All rights reserved.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public
- * License v2 as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public
- * License along with this program; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 021110-1307, USA.
  */
 #ifdef __linux__
 #define _BSD_SOURCE
+#define _DEFAULT_SOURCE
 #define _LARGEFILE64_SOURCE
 #ifndef _GNU_SOURCE
 #define _GNU_SOURCE
@@ -31,6 +19,8 @@
 #include <errno.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/xattr.h>
 #ifdef __SOLARIS__
 #include <sys/mkdev.h>
 #endif
@@ -38,6 +28,7 @@
 #include <netinet/in.h>
 #include <inttypes.h>
 #include <assert.h>
+#include <endian.h>
 
 #define CS_SIZE 16
 #define CHUNKS 128
 #define SEEK_DATA 3
 #define SEEK_HOLE 4
 #endif
-
-#if __BYTE_ORDER == __LITTLE_ENDIAN
-#define htonll(x)     __bswap_64 (x)
-#else
-#define htonll(x)     (x)
-#endif
 #endif
 
 /* TODO: add hardlink recognition */
-/* TODO: add xattr/acl */
 
 struct excludes {
        char *path;
@@ -87,15 +71,16 @@ enum _flags {
        FLAG_MTIME,
        FLAG_CTIME,
        FLAG_DATA,
+       FLAG_XATTRS,
        FLAG_OPEN_ERROR,
        FLAG_STRUCTURE,
        NUM_FLAGS
 };
 
-const char flchar[] = "ugoamcdes";
+const char flchar[] = "ugoamcdtes";
 char line[65536];
 
-int flags[NUM_FLAGS] = {1, 1, 1, 1, 1, 0, 1, 0, 0};
+int flags[NUM_FLAGS] = {1, 1, 1, 1, 1, 0, 1, 1, 0, 0};
 
 char *
 getln(char *buf, int size, FILE *fp)
@@ -146,27 +131,27 @@ usage(void)
 {
        fprintf(stderr, "usage: fssum <options> <path>\n");
        fprintf(stderr, "  options:\n");
-       fprintf(stderr, "    -f          : write out a full manifest file\n");
-       fprintf(stderr, "    -w <file>   : send output to file\n");
-       fprintf(stderr, "    -v          : verbose mode (debugging only)\n");
-       fprintf(stderr,
-               "    -r <file>   : read checksum or manifest from file\n");
-       fprintf(stderr, "    -[ugoamcde] : specify which fields to include in checksum calculation.\n");
-       fprintf(stderr, "         u      : include uid\n");
-       fprintf(stderr, "         g      : include gid\n");
-       fprintf(stderr, "         o      : include mode\n");
-       fprintf(stderr, "         m      : include mtime\n");
-       fprintf(stderr, "         a      : include atime\n");
-       fprintf(stderr, "         c      : include ctime\n");
-       fprintf(stderr, "         d      : include file data\n");
-       fprintf(stderr, "         e      : include open errors (aborts otherwise)\n");
-       fprintf(stderr, "         s      : include block structure (holes)\n");
-       fprintf(stderr, "    -[UGOAMCDES]: exclude respective field from calculation\n");
-       fprintf(stderr, "    -n          : reset all flags\n");
-       fprintf(stderr, "    -N          : set all flags\n");
-       fprintf(stderr, "    -x path     : exclude path when building checksum (multiple ok)\n");
-       fprintf(stderr, "    -h          : this help\n\n");
-       fprintf(stderr, "The default field mask is ugoamCdES. If the checksum/manifest is read from a\n");
+       fprintf(stderr, "    -f           : write out a full manifest file\n");
+       fprintf(stderr, "    -w <file>    : send output to file\n");
+       fprintf(stderr, "    -v           : verbose mode (debugging only)\n");
+       fprintf(stderr, "    -r <file>    : read checksum or manifest from file\n");
+       fprintf(stderr, "    -[ugoamcdtes]: specify which fields to include in checksum calculation.\n");
+       fprintf(stderr, "         u       : include uid\n");
+       fprintf(stderr, "         g       : include gid\n");
+       fprintf(stderr, "         o       : include mode\n");
+       fprintf(stderr, "         m       : include mtime\n");
+       fprintf(stderr, "         a       : include atime\n");
+       fprintf(stderr, "         c       : include ctime\n");
+       fprintf(stderr, "         d       : include file data\n");
+       fprintf(stderr, "         t       : include xattrs\n");
+       fprintf(stderr, "         e       : include open errors (aborts otherwise)\n");
+       fprintf(stderr, "         s       : include block structure (holes)\n");
+       fprintf(stderr, "    -[UGOAMCDTES]: exclude respective field from calculation\n");
+       fprintf(stderr, "    -n           : reset all flags\n");
+       fprintf(stderr, "    -N           : set all flags\n");
+       fprintf(stderr, "    -x path      : exclude path when building checksum (multiple ok)\n");
+       fprintf(stderr, "    -h           : this help\n\n");
+       fprintf(stderr, "The default field mask is ugoamCdtES. If the checksum/manifest is read from a\n");
        fprintf(stderr, "file, the mask is taken from there and the values given on the command line\n");
        fprintf(stderr, "are ignored.\n");
        exit(-1);
@@ -214,7 +199,7 @@ sum_add_sum(sum_t *dst, sum_t *src)
 void
 sum_add_u64(sum_t *dst, uint64_t val)
 {
-       uint64_t v = htonll(val);
+       uint64_t v = htobe64(val);
        sum_add(dst, &v, sizeof(v));
 }
 
@@ -237,76 +222,121 @@ sum_to_string(sum_t *dst)
 }
 
 int
-sum_file_data_permissive(int fd, sum_t *dst)
+namecmp(const void *aa, const void *bb)
 {
-       int ret;
-       off_t pos;
-       off_t old;
+       char * const *a = aa;
+       char * const *b = bb;
+
+       return strcmp(*a, *b);
+}
+
+int
+sum_xattrs(int fd, sum_t *dst)
+{
+       ssize_t buflen;
+       ssize_t len;
+       char *buf;
+       char *p;
+       char **names = NULL;
+       int num_xattrs = 0;
+       int ret = 0;
        int i;
-       uint64_t zeros = 0;
 
-       pos = lseek(fd, 0, SEEK_CUR);
-       if (pos == (off_t)-1)
-               return errno == ENXIO ? 0 : -2;
+       buflen = flistxattr(fd, NULL, 0);
+       if (buflen < 0)
+               return -errno;
+       /* no xattrs exist */
+       if (buflen == 0)
+               return 0;
+
+       buf = malloc(buflen);
+       if (!buf)
+               return -ENOMEM;
+
+       buflen = flistxattr(fd, buf, buflen);
+       if (buflen < 0) {
+               ret = -errno;
+               goto out;
+       }
 
-       while (1) {
-               old = pos;
-               pos = lseek(fd, pos, SEEK_DATA);
-               if (pos == (off_t)-1) {
-                       if (errno == ENXIO) {
-                               ret = 0;
-                               pos = lseek(fd, 0, SEEK_END);
-                               if (pos != (off_t)-1)
-                                       zeros += pos - old;
-                       } else {
-                               ret = -2;
-                       }
-                       break;
-               }
-               ret = read(fd, buf, sizeof(buf));
-               assert(ret); /* eof found by lseek */
-               if (ret <= 0)
-                       break;
-               if (old < pos) /* hole */
-                       zeros += pos - old;
-               for (i = 0; i < ret; ++i) {
-                       for (old = i; buf[i] == 0 && i < ret; ++i)
-                               ;
-                       if (old < i) /* code like a hole */
-                               zeros += i - old;
-                       if (i == ret)
-                               break;
-                       if (zeros) {
-                               if (verbose >= 2)
-                                       fprintf(stderr,
-                                               "adding %llu zeros to sum\n",
-                                               (unsigned long long)zeros);
-                               sum_add_u64(dst, 0);
-                               sum_add_u64(dst, zeros);
-                               zeros = 0;
-                       }
-                       for (old = i; buf[i] != 0 && i < ret; ++i)
-                               ;
-                       if (verbose >= 2)
-                               fprintf(stderr, "adding %u non-zeros to sum\n",
-                                       i - (int)old);
-                       sum_add(dst, buf + old, i - old);
-               }
-               pos += ret;
+       /*
+        * Keep the list of xattrs sorted, because the order in which they are
+        * listed is filesystem dependent, so we want to get the same checksum
+        * on different filesystems.
+        */
+
+       p = buf;
+       len = buflen;
+       while (len > 0) {
+               int keylen;
+
+               keylen = strlen(p) + 1; /* +1 for NULL terminator */
+               len -= keylen;
+               p += keylen;
+               num_xattrs++;
        }
 
-       if (zeros) {
-               if (verbose >= 2)
-                       fprintf(stderr,
-                               "adding %llu zeros to sum (finishing)\n",
-                               (unsigned long long)zeros);
-               sum_add_u64(dst, 0);
-               sum_add_u64(dst, zeros);
+       names = malloc(sizeof(char *) * num_xattrs);
+       if (!names) {
+               ret = -ENOMEM;
+               goto out;
        }
 
+       p = buf;
+       for (i = 0; i < num_xattrs; i++) {
+               names[i] = p;
+               p += strlen(p) + 1; /* +1 for NULL terminator */
+       }
+
+       qsort(names, num_xattrs, sizeof(char *), namecmp);
+
+       for (i = 0; i < num_xattrs; i++) {
+               len = fgetxattr(fd, names[i], NULL, 0);
+               if (len < 0) {
+                       ret = -errno;
+                       goto out;
+               }
+               sum_add(dst, names[i], strlen(names[i]));
+               /* no value */
+               if (len == 0)
+                       continue;
+               p = malloc(len);
+               if (!p) {
+                       ret = -ENOMEM;
+                       goto out;
+               }
+               len = fgetxattr(fd, names[i], p, len);
+               if (len < 0) {
+                       ret = -errno;
+                       free(p);
+                       goto out;
+               }
+               sum_add(dst, p, len);
+               free(p);
+       }
+out:
+       free(buf);
+       free(names);
+
        return ret;
 }
 
+int
+sum_file_data_permissive(int fd, sum_t *dst)
+{
+       int ret;
+
+       while (1) {
+               ret = read(fd, buf, sizeof(buf));
+               if (ret < 0)
+                       return -errno;
+               sum_add(dst, buf, ret);
+               if (ret < sizeof(buf))
+                       break;
+       }
+       return 0;
+}
+
 int
 sum_file_data_strict(int fd, sum_t *dst)
 {
@@ -472,15 +502,6 @@ malformed:
                excess_file(fn);
 }
 
-int
-namecmp(const void *aa, const void *bb)
-{
-       char * const *a = aa;
-       char * const *b = bb;
-
-       return strcmp(*a, *b);
-}
-
 void
 sum(int dirfd, int level, sum_t *dircs, char *path_prefix, char *path_in)
 {
@@ -495,6 +516,12 @@ sum(int dirfd, int level, sum_t *dircs, char *path_prefix, char *path_in)
        int excl;
        sum_file_data_t sum_file_data = flags[FLAG_STRUCTURE] ?
                        sum_file_data_strict : sum_file_data_permissive;
+       struct stat64 dir_st;
+
+       if (fstat64(dirfd, &dir_st)) {
+               perror("fstat");
+               exit(-1);
+       }
 
        d = fdopendir(dirfd);
        if (!d) {
@@ -548,6 +575,11 @@ sum(int dirfd, int level, sum_t *dircs, char *path_prefix, char *path_in)
                                path_prefix, path, strerror(errno));
                        exit(-1);
                }
+
+               /* We are crossing into a different subvol, skip this subtree. */
+               if (st.st_dev != dir_st.st_dev)
+                       goto next;
+
                sum_add_u64(&meta, level);
                sum_add(&meta, namelist[i], strlen(namelist[i]));
                if (!S_ISDIR(st.st_mode))
@@ -564,6 +596,28 @@ sum(int dirfd, int level, sum_t *dircs, char *path_prefix, char *path_in)
                        sum_add_time(&meta, st.st_mtime);
                if (flags[FLAG_CTIME])
                        sum_add_time(&meta, st.st_ctime);
+               if (flags[FLAG_XATTRS] &&
+                   (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode))) {
+                       fd = openat(dirfd, namelist[i], 0);
+                       if (fd == -1 && flags[FLAG_OPEN_ERROR]) {
+                               sum_add_u64(&meta, errno);
+                       } else if (fd == -1) {
+                               fprintf(stderr, "open failed for %s/%s: %s\n",
+                                       path_prefix, path, strerror(errno));
+                               exit(-1);
+                       } else {
+                               ret = sum_xattrs(fd, &meta);
+                               close(fd);
+                               if (ret < 0) {
+                                       fprintf(stderr,
+                                               "failed to read xattrs from "
+                                               "%s/%s: %s\n",
+                                               path_prefix, path,
+                                               strerror(-ret));
+                                       exit(-1);
+                               }
+                       }
+               }
                if (S_ISDIR(st.st_mode)) {
                        fd = openat(dirfd, namelist[i], 0);
                        if (fd == -1 && flags[FLAG_OPEN_ERROR]) {
@@ -658,7 +712,7 @@ main(int argc, char *argv[])
        int plen;
        int elen;
        int n_flags = 0;
-       const char *allopts = "heEfuUgGoOaAmMcCdDsSnNw:r:vx:";
+       const char *allopts = "heEfuUgGoOaAmMcCdDtTsSnNw:r:vx:";
 
        out_fp = stdout;
        while ((c = getopt(argc, argv, allopts)) != EOF) {
@@ -680,6 +734,8 @@ main(int argc, char *argv[])
                case 'C':
                case 'd':
                case 'D':
+               case 'T':
+               case 't':
                case 'e':
                case 'E':
                case 's':