+// 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__
+#ifdef __linux__
#define _BSD_SOURCE
+#define _DEFAULT_SOURCE
#define _LARGEFILE64_SOURCE
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#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
-#include <openssl/md5.h>
+#include "md5.h"
#include <netinet/in.h>
#include <inttypes.h>
#include <assert.h>
+#include <endian.h>
#define CS_SIZE 16
#define CHUNKS 128
-#ifdef __LINUX__
+#ifdef __linux__
#ifndef SEEK_DATA
#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;
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)
{
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);
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));
}
}
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)
{
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)
{
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) {
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))
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]) {
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) {
case 'C':
case 'd':
case 'D':
+ case 'T':
+ case 't':
case 'e':
case 'E':
case 's':