* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
-#define _POSIX_C_SOURCE 200112L /* POSIX functions */
-#define _XOPEN_SOURCE 600 /* nftw() */
-
-#include <sys/types.h> /* stat */
-#include <sys/stat.h> /* stat */
-#include <sys/time.h> /* getrlimit, getrusage */
-#include <sys/resource.h> /* getrlimit, getrusage */
-#include <fcntl.h> /* posix_fadvise */
-#include <ftw.h> /* ftw */
-#include <search.h> /* tsearch() and friends */
+#define _POSIX_C_SOURCE 200112L /* POSIX functions */
+#define _XOPEN_SOURCE 600 /* nftw() */
+
+#include <sys/types.h> /* stat */
+#include <sys/stat.h> /* stat */
+#include <sys/time.h> /* getrlimit, getrusage */
+#include <sys/resource.h> /* getrlimit, getrusage */
+#include <fcntl.h> /* posix_fadvise */
+#include <ftw.h> /* ftw */
+#include <search.h> /* tsearch() and friends */
#include <signal.h> /* SIG*, sigaction */
#include <getopt.h> /* getopt_long() */
-#include <ctype.h> /* tolower() */
+#include <ctype.h> /* tolower() */
#include "nls.h"
#include "c.h"
#ifdef HAVE_PCRE2_POSIX
# include <pcre2posix.h>
# undef REG_NOSUB
-# define REG_NOSUB 0 /* we do want backreferences in PCRE mode */
+# define REG_NOSUB 0 /* we do want backreferences in PCRE mode */
#else
-# include <regex.h> /* regcomp(), regsearch() */
+# include <regex.h> /* regcomp(), regsearch() */
#endif
#ifdef HAVE_SYS_XATTR_H
-# include <sys/xattr.h> /* listxattr, getxattr */
+# include <sys/xattr.h> /* listxattr, getxattr */
#endif
static int quiet; /* don't print anything */
* This contains all information we need about a file.
*/
struct file {
- struct stat st;
- struct file *next;
- struct link {
- struct link *next;
- int basename;
+ struct stat st;
+ struct file *next;
+ struct link {
+ struct link *next;
+ int basename;
#if __STDC_VERSION__ >= 199901L
- char path[];
+ char path[];
#elif __GNUC__
- char path[0];
+ char path[0];
#else
- char path[1];
+ char path[1];
#endif
- } *links;
+ } *links;
};
/**
* @JLOG_VERBOSE2: Verbosity 3
*/
enum log_level {
- JLOG_SUMMARY,
- JLOG_INFO,
- JLOG_VERBOSE1,
- JLOG_VERBOSE2
+ JLOG_SUMMARY,
+ JLOG_INFO,
+ JLOG_VERBOSE1,
+ JLOG_VERBOSE2
};
/**
* @start_time: The time we started at
*/
static struct statistics {
- int started;
- size_t files;
- size_t linked;
- size_t xattr_comparisons;
- size_t comparisons;
- double saved;
- struct timeval start_time;
+ int started;
+ size_t files;
+ size_t linked;
+ size_t xattr_comparisons;
+ size_t comparisons;
+ double saved;
+ struct timeval start_time;
} stats;
/**
* @min_size: Minimum size of files to consider. (default = 1 byte)
*/
static struct options {
- struct regex_link {
- regex_t preg;
- struct regex_link *next;
- } *include, *exclude;
- signed int verbosity;
- unsigned int respect_mode:1;
- unsigned int respect_owner:1;
- unsigned int respect_name:1;
- unsigned int respect_time:1;
- unsigned int respect_xattrs:1;
- unsigned int maximise:1;
- unsigned int minimise:1;
- unsigned int keep_oldest:1;
- unsigned int dry_run:1;
- uintmax_t min_size;
+ struct regex_link {
+ regex_t preg;
+ struct regex_link *next;
+ } *include, *exclude;
+
+ signed int verbosity;
+ unsigned int respect_mode:1;
+ unsigned int respect_owner:1;
+ unsigned int respect_name:1;
+ unsigned int respect_time:1;
+ unsigned int respect_xattrs:1;
+ unsigned int maximise:1;
+ unsigned int minimise:1;
+ unsigned int keep_oldest:1;
+ unsigned int dry_run:1;
+ uintmax_t min_size;
} opts = {
- /* default setting */
- .respect_mode = TRUE,
- .respect_owner = TRUE,
- .respect_time = TRUE,
- .respect_xattrs = FALSE,
- .keep_oldest = FALSE,
- .min_size = 1
+ /* default setting */
+ .respect_mode = TRUE,
+ .respect_owner = TRUE,
+ .respect_time = TRUE,
+ .respect_xattrs = FALSE,
+ .keep_oldest = FALSE,
+ .min_size = 1
};
/*
* @level: The log level
* @format: A format string for printf()
*/
-__attribute__ ((format(printf, 2, 3)))
+__attribute__((format(printf, 2, 3)))
static void jlog(enum log_level level, const char *format, ...)
{
- va_list args;
+ va_list args;
- if (quiet || level > (unsigned int) opts.verbosity)
- return;
+ if (quiet || level > (unsigned int)opts.verbosity)
+ return;
- va_start(args, format);
- vfprintf(stdout, format, args);
- va_end(args);
- fputc('\n', stdout);
+ va_start(args, format);
+ vfprintf(stdout, format, args);
+ va_end(args);
+ fputc('\n', stdout);
}
/**
*/
static int regexec_any(struct regex_link *pregs, const char *what)
{
- for (; pregs != NULL; pregs = pregs->next)
- if (regexec(&pregs->preg, what, 0, NULL, 0) == 0)
- return TRUE;
- return FALSE;
+ for (; pregs != NULL; pregs = pregs->next) {
+ if (regexec(&pregs->preg, what, 0, NULL, 0) == 0)
+ return TRUE;
+ }
+ return FALSE;
}
/**
*/
static int compare_nodes(const void *_a, const void *_b)
{
- const struct file *a = _a;
- const struct file *b = _b;
- int diff = 0;
+ const struct file *a = _a;
+ const struct file *b = _b;
+ int diff = 0;
- if (diff == 0)
- diff = CMP(a->st.st_dev, b->st.st_dev);
- if (diff == 0)
- diff = CMP(a->st.st_size, b->st.st_size);
+ if (diff == 0)
+ diff = CMP(a->st.st_dev, b->st.st_dev);
+ if (diff == 0)
+ diff = CMP(a->st.st_size, b->st.st_size);
- return diff;
+ return diff;
}
/**
*/
static int compare_nodes_ino(const void *_a, const void *_b)
{
- const struct file *a = _a;
- const struct file *b = _b;
- int diff = 0;
-
- if (diff == 0)
- diff = CMP(a->st.st_dev, b->st.st_dev);
- if (diff == 0)
- diff = CMP(a->st.st_ino, b->st.st_ino);
-
- /* If opts.respect_name is used, we will restrict a struct file to
- * contain only links with the same basename to keep the rest simple.
- */
- if (diff == 0 && opts.respect_name)
- diff = strcmp(a->links->path + a->links->basename,
- b->links->path + b->links->basename);
-
- return diff;
+ const struct file *a = _a;
+ const struct file *b = _b;
+ int diff = 0;
+
+ if (diff == 0)
+ diff = CMP(a->st.st_dev, b->st.st_dev);
+ if (diff == 0)
+ diff = CMP(a->st.st_ino, b->st.st_ino);
+
+ /* If opts.respect_name is used, we will restrict a struct file to
+ * contain only links with the same basename to keep the rest simple.
+ */
+ if (diff == 0 && opts.respect_name)
+ diff = strcmp(a->links->path + a->links->basename,
+ b->links->path + b->links->basename);
+
+ return diff;
}
/**
*/
static void print_stats(void)
{
- struct timeval end = { 0, 0 }, delta = { 0, 0 };
- char *ssz;
+ struct timeval end = { 0, 0 }, delta = { 0, 0 };
+ char *ssz;
- gettime_monotonic(&end);
- timersub(&end, &stats.start_time, &delta);
+ gettime_monotonic(&end);
+ timersub(&end, &stats.start_time, &delta);
+
+ jlog(JLOG_SUMMARY, "%-15s %s", _("Mode:"),
+ opts.dry_run ? _("dry-run") : _("real"));
+ jlog(JLOG_SUMMARY, "%-15s %zu", _("Files:"), stats.files);
+ jlog(JLOG_SUMMARY, _("%-15s %zu files"), _("Linked:"), stats.linked);
- jlog(JLOG_SUMMARY, "%-15s %s", _("Mode:"),
- opts.dry_run ? _("dry-run") : _("real"));
- jlog(JLOG_SUMMARY, "%-15s %zu", _("Files:"), stats.files);
- jlog(JLOG_SUMMARY, _("%-15s %zu files"), _("Linked:"), stats.linked);
#ifdef HAVE_SYS_XATTR_H
- jlog(JLOG_SUMMARY, _("%-15s %zu xattrs"), _("Compared:"), stats.xattr_comparisons);
+ jlog(JLOG_SUMMARY, _("%-15s %zu xattrs"), _("Compared:"),
+ stats.xattr_comparisons);
#endif
- jlog(JLOG_SUMMARY, _("%-15s %zu files"), _("Compared:"), stats.comparisons);
+ jlog(JLOG_SUMMARY, _("%-15s %zu files"), _("Compared:"),
+ stats.comparisons);
- ssz = size_to_human_string(SIZE_SUFFIX_3LETTER|
- SIZE_SUFFIX_SPACE|
- SIZE_DECIMAL_2DIGITS, stats.saved);
+ ssz = size_to_human_string(SIZE_SUFFIX_3LETTER |
+ SIZE_SUFFIX_SPACE |
+ SIZE_DECIMAL_2DIGITS, stats.saved);
- jlog(JLOG_SUMMARY, "%-15s %s", _("Saved:"), ssz);
- free(ssz);
+ jlog(JLOG_SUMMARY, "%-15s %s", _("Saved:"), ssz);
+ free(ssz);
- jlog(JLOG_SUMMARY, _("%-15s %ld.%06ld seconds"), _("Duration:"),
- (long)delta.tv_sec, (long)delta.tv_usec);
+ jlog(JLOG_SUMMARY, _("%-15s %ld.%06ld seconds"), _("Duration:"),
+ (long)delta.tv_sec, (long)delta.tv_usec);
}
/**
*/
static int handle_interrupt(void)
{
- switch (last_signal) {
- case SIGINT:
- case SIGTERM:
- return TRUE;
- case SIGUSR1:
- print_stats();
- putchar('\n');
- break;
- }
-
- last_signal = 0;
- return FALSE;
+ switch (last_signal) {
+ case SIGINT:
+ case SIGTERM:
+ return TRUE;
+ case SIGUSR1:
+ print_stats();
+ putchar('\n');
+ break;
+ }
+
+ last_signal = 0;
+ return FALSE;
}
#ifdef HAVE_SYS_XATTR_H
*/
static ssize_t llistxattr_or_die(const char *path, char *list, size_t size)
{
- ssize_t len = llistxattr(path, list, size);
+ ssize_t len = llistxattr(path, list, size);
- if (len < 0 && errno != ENOTSUP)
- err(EXIT_FAILURE, _("cannot get xattr names for %s"), path);
+ if (len < 0 && errno != ENOTSUP)
+ err(EXIT_FAILURE, _("cannot get xattr names for %s"), path);
- return len;
+ return len;
}
/**
*
* This does the same thing as lgetxattr() except that it aborts upon error.
*/
-static ssize_t lgetxattr_or_die(const char *path, const char *name, void *value,
- size_t size)
+static ssize_t lgetxattr_or_die(const char *path,
+ const char *name, void *value, size_t size)
{
- ssize_t len = lgetxattr(path, name, value, size);
+ ssize_t len = lgetxattr(path, name, value, size);
- if (len < 0)
- err(EXIT_FAILURE, _("cannot get xattr value of %s for %s"), name, path);
+ if (len < 0)
+ err(EXIT_FAILURE, _("cannot get xattr value of %s for %s"),
+ name, path);
- return len;
+ return len;
}
/**
*/
static int get_xattr_name_count(const char *const names, ssize_t len)
{
- int count = 0;
- const char *name;
+ int count = 0;
+ const char *name;
- for (name = names; name < (names + len); name += strlen(name) + 1)
- count++;
+ for (name = names; name < (names + len); name += strlen(name) + 1)
+ count++;
- return count;
+ return count;
}
/**
*/
static int cmp_xattr_name_ptrs(const void *ptr1, const void *ptr2)
{
- return strcmp(*(char *const *) ptr1, *(char *const *) ptr2);
+ return strcmp(*(char *const *)ptr1, *(char *const *)ptr2);
}
/**
*/
static const char **get_sorted_xattr_name_table(const char *names, int n)
{
- const char **table = xmalloc(n * sizeof(char *));
- int i;
+ const char **table = xmalloc(n * sizeof(char *));
+ int i;
- for (i = 0; i < n; i++) {
- table[i] = names;
- names += strlen(names) + 1;
- }
+ for (i = 0; i < n; i++) {
+ table[i] = names;
+ names += strlen(names) + 1;
+ }
- qsort(table, n, sizeof(char *), cmp_xattr_name_ptrs);
+ qsort(table, n, sizeof(char *), cmp_xattr_name_ptrs);
- return table;
+ return table;
}
/**
*/
static int file_xattrs_equal(const struct file *a, const struct file *b)
{
- ssize_t len_a;
- ssize_t len_b;
- char *names_a = NULL;
- char *names_b = NULL;
- int n_a;
- int n_b;
- const char **name_ptrs_a = NULL;
- const char **name_ptrs_b = NULL;
- void *value_a = NULL;
- void *value_b = NULL;
- int ret = FALSE;
- int i;
+ ssize_t len_a;
+ ssize_t len_b;
+ char *names_a = NULL;
+ char *names_b = NULL;
+ int n_a;
+ int n_b;
+ const char **name_ptrs_a = NULL;
+ const char **name_ptrs_b = NULL;
+ void *value_a = NULL;
+ void *value_b = NULL;
+ int ret = FALSE;
+ int i;
- assert(a->links != NULL);
- assert(b->links != NULL);
+ assert(a->links != NULL);
+ assert(b->links != NULL);
- jlog(JLOG_VERBOSE1, _("Comparing xattrs of %s to %s"), a->links->path,
- b->links->path);
+ jlog(JLOG_VERBOSE1, _("Comparing xattrs of %s to %s"), a->links->path,
+ b->links->path);
- stats.xattr_comparisons++;
+ stats.xattr_comparisons++;
- len_a = llistxattr_or_die(a->links->path, NULL, 0);
- len_b = llistxattr_or_die(b->links->path, NULL, 0);
+ len_a = llistxattr_or_die(a->links->path, NULL, 0);
+ len_b = llistxattr_or_die(b->links->path, NULL, 0);
- if (len_a <= 0 && len_b <= 0)
- return TRUE; // xattrs not supported or neither file has any
+ if (len_a <= 0 && len_b <= 0)
+ return TRUE; // xattrs not supported or neither file has any
- if (len_a != len_b)
- return FALSE; // total lengths of xattr names differ
+ if (len_a != len_b)
+ return FALSE; // total lengths of xattr names differ
- names_a = xmalloc(len_a);
- names_b = xmalloc(len_b);
+ names_a = xmalloc(len_a);
+ names_b = xmalloc(len_b);
- len_a = llistxattr_or_die(a->links->path, names_a, len_a);
- len_b = llistxattr_or_die(b->links->path, names_b, len_b);
- assert((len_a > 0) && (len_a == len_b));
+ len_a = llistxattr_or_die(a->links->path, names_a, len_a);
+ len_b = llistxattr_or_die(b->links->path, names_b, len_b);
+ assert((len_a > 0) && (len_a == len_b));
- n_a = get_xattr_name_count(names_a, len_a);
- n_b = get_xattr_name_count(names_b, len_b);
+ n_a = get_xattr_name_count(names_a, len_a);
+ n_b = get_xattr_name_count(names_b, len_b);
- if (n_a != n_b)
- goto exit; // numbers of xattrs differ
+ if (n_a != n_b)
+ goto exit; // numbers of xattrs differ
- name_ptrs_a = get_sorted_xattr_name_table(names_a, n_a);
- name_ptrs_b = get_sorted_xattr_name_table(names_b, n_b);
+ name_ptrs_a = get_sorted_xattr_name_table(names_a, n_a);
+ name_ptrs_b = get_sorted_xattr_name_table(names_b, n_b);
- // We now have two sorted tables of xattr names.
+ // We now have two sorted tables of xattr names.
- for (i = 0; i < n_a; i++) {
- if (handle_interrupt())
- goto exit; // user wants to quit
+ for (i = 0; i < n_a; i++) {
+ if (handle_interrupt())
+ goto exit; // user wants to quit
- if (strcmp(name_ptrs_a[i], name_ptrs_b[i]) != 0)
- goto exit; // names at same slot differ
+ if (strcmp(name_ptrs_a[i], name_ptrs_b[i]) != 0)
+ goto exit; // names at same slot differ
- len_a = lgetxattr_or_die(a->links->path, name_ptrs_a[i], NULL, 0);
- len_b = lgetxattr_or_die(b->links->path, name_ptrs_b[i], NULL, 0);
+ len_a =
+ lgetxattr_or_die(a->links->path, name_ptrs_a[i], NULL, 0);
+ len_b =
+ lgetxattr_or_die(b->links->path, name_ptrs_b[i], NULL, 0);
- if (len_a != len_b)
- goto exit; // xattrs with same name, different value lengths
+ if (len_a != len_b)
+ goto exit; // xattrs with same name, different value lengths
- value_a = xmalloc(len_a);
- value_b = xmalloc(len_b);
+ value_a = xmalloc(len_a);
+ value_b = xmalloc(len_b);
- len_a = lgetxattr_or_die(a->links->path, name_ptrs_a[i],
- value_a, len_a);
- len_b = lgetxattr_or_die(b->links->path, name_ptrs_b[i],
- value_b, len_b);
- assert((len_a >= 0) && (len_a == len_b));
+ len_a = lgetxattr_or_die(a->links->path, name_ptrs_a[i],
+ value_a, len_a);
+ len_b = lgetxattr_or_die(b->links->path, name_ptrs_b[i],
+ value_b, len_b);
+ assert((len_a >= 0) && (len_a == len_b));
- if (memcmp(value_a, value_b, len_a) != 0)
- goto exit; // xattrs with same name, different values
+ if (memcmp(value_a, value_b, len_a) != 0)
+ goto exit; // xattrs with same name, different values
- free(value_a);
- free(value_b);
- value_a = NULL;
- value_b = NULL;
- }
+ free(value_a);
+ free(value_b);
+ value_a = NULL;
+ value_b = NULL;
+ }
- ret = TRUE;
+ ret = TRUE;
- exit:
- free(names_a);
- free(names_b);
- free(name_ptrs_a);
- free(name_ptrs_b);
- free(value_a);
- free(value_b);
- return ret;
+ exit:
+ free(names_a);
+ free(names_b);
+ free(name_ptrs_a);
+ free(name_ptrs_b);
+ free(value_a);
+ free(value_b);
+ return ret;
}
-#else
+#else /* !HAVE_SYS_XATTR_H */
static int file_xattrs_equal(const struct file *a, const struct file *b)
{
- return TRUE;
+ return TRUE;
}
-#endif
+#endif /* HAVE_SYS_XATTR_H */
/**
* file_contents_equal - Compare contents of two files for equality
*/
static int file_contents_equal(const struct file *a, const struct file *b)
{
- FILE *fa = NULL;
- FILE *fb = NULL;
- char buf_a[8192];
- char buf_b[8192];
- int cmp = 0; /* zero => equal */
- off_t off = 0; /* current offset */
+ FILE *fa = NULL;
+ FILE *fb = NULL;
+ char buf_a[8192];
+ char buf_b[8192];
+ int cmp = 0; /* zero => equal */
+ off_t off = 0; /* current offset */
- assert(a->links != NULL);
- assert(b->links != NULL);
+ assert(a->links != NULL);
+ assert(b->links != NULL);
- jlog(JLOG_VERBOSE1, _("Comparing %s to %s"), a->links->path, b->links->path);
+ jlog(JLOG_VERBOSE1, _("Comparing %s to %s"), a->links->path,
+ b->links->path);
- stats.comparisons++;
+ stats.comparisons++;
- if ((fa = fopen(a->links->path, "rb")) == NULL)
- goto err;
- if ((fb = fopen(b->links->path, "rb")) == NULL)
- goto err;
+ if ((fa = fopen(a->links->path, "rb")) == NULL)
+ goto err;
+ if ((fb = fopen(b->links->path, "rb")) == NULL)
+ goto err;
#if defined(POSIX_FADV_SEQUENTIAL) && defined(HAVE_POSIX_FADVISE)
- posix_fadvise(fileno(fa), 0, 0, POSIX_FADV_SEQUENTIAL);
- posix_fadvise(fileno(fb), 0, 0, POSIX_FADV_SEQUENTIAL);
+ posix_fadvise(fileno(fa), 0, 0, POSIX_FADV_SEQUENTIAL);
+ posix_fadvise(fileno(fb), 0, 0, POSIX_FADV_SEQUENTIAL);
#endif
- while (!handle_interrupt() && cmp == 0) {
- size_t ca;
- size_t cb;
-
- ca = fread(buf_a, 1, sizeof(buf_a), fa);
- if (ca < sizeof(buf_a) && ferror(fa))
- goto err;
-
- cb = fread(buf_b, 1, sizeof(buf_b), fb);
- if (cb < sizeof(buf_b) && ferror(fb))
- goto err;
-
- off += ca;
-
- if ((ca != cb || ca == 0)) {
- cmp = CMP(ca, cb);
- break;
- }
- cmp = memcmp(buf_a, buf_b, ca);
- }
- out:
- if (fa != NULL)
- fclose(fa);
- if (fb != NULL)
- fclose(fb);
- return !handle_interrupt() && cmp == 0;
- err:
- if (fa == NULL || fb == NULL)
- warn(_("cannot open %s"), fa ? b->links->path : a->links->path);
- else
- warn(_("cannot read %s"), ferror(fa) ? a->links->path : b->links->path);
- cmp = 1;
- goto out;
+ while (!handle_interrupt() && cmp == 0) {
+ size_t ca;
+ size_t cb;
+
+ ca = fread(buf_a, 1, sizeof(buf_a), fa);
+ if (ca < sizeof(buf_a) && ferror(fa))
+ goto err;
+
+ cb = fread(buf_b, 1, sizeof(buf_b), fb);
+ if (cb < sizeof(buf_b) && ferror(fb))
+ goto err;
+
+ off += ca;
+
+ if ((ca != cb || ca == 0)) {
+ cmp = CMP(ca, cb);
+ break;
+ }
+ cmp = memcmp(buf_a, buf_b, ca);
+ }
+ out:
+ if (fa != NULL)
+ fclose(fa);
+ if (fb != NULL)
+ fclose(fb);
+ return !handle_interrupt() && cmp == 0;
+ err:
+ if (fa == NULL || fb == NULL)
+ warn(_("cannot open %s"), fa ? b->links->path : a->links->path);
+ else
+ warn(_("cannot read %s"),
+ ferror(fa) ? a->links->path : b->links->path);
+ cmp = 1;
+ goto out;
}
/**
*/
static int file_may_link_to(const struct file *a, const struct file *b)
{
- return (a->st.st_size != 0 &&
- a->st.st_size == b->st.st_size &&
- a->links != NULL && b->links != NULL &&
- a->st.st_dev == b->st.st_dev &&
- a->st.st_ino != b->st.st_ino &&
- (!opts.respect_mode || a->st.st_mode == b->st.st_mode) &&
- (!opts.respect_owner || a->st.st_uid == b->st.st_uid) &&
- (!opts.respect_owner || a->st.st_gid == b->st.st_gid) &&
- (!opts.respect_time || a->st.st_mtime == b->st.st_mtime) &&
- (!opts.respect_name
- || strcmp(a->links->path + a->links->basename,
- b->links->path + b->links->basename) == 0) &&
- (!opts.respect_xattrs || file_xattrs_equal(a, b)) &&
- file_contents_equal(a, b));
+ return (a->st.st_size != 0 &&
+ a->st.st_size == b->st.st_size &&
+ a->links != NULL && b->links != NULL &&
+ a->st.st_dev == b->st.st_dev &&
+ a->st.st_ino != b->st.st_ino &&
+ (!opts.respect_mode || a->st.st_mode == b->st.st_mode) &&
+ (!opts.respect_owner || a->st.st_uid == b->st.st_uid) &&
+ (!opts.respect_owner || a->st.st_gid == b->st.st_gid) &&
+ (!opts.respect_time || a->st.st_mtime == b->st.st_mtime) &&
+ (!opts.respect_name
+ || strcmp(a->links->path + a->links->basename,
+ b->links->path + b->links->basename) == 0) &&
+ (!opts.respect_xattrs || file_xattrs_equal(a, b)) &&
+ file_contents_equal(a, b));
}
/**
*/
static int file_compare(const struct file *a, const struct file *b)
{
- int res = 0;
- if (a->st.st_dev == b->st.st_dev && a->st.st_ino == b->st.st_ino)
- return 0;
-
- if (res == 0 && opts.maximise)
- res = CMP(a->st.st_nlink, b->st.st_nlink);
- if (res == 0 && opts.minimise)
- res = CMP(b->st.st_nlink, a->st.st_nlink);
- if (res == 0)
- res = opts.keep_oldest ? CMP(b->st.st_mtime, a->st.st_mtime)
- : CMP(a->st.st_mtime, b->st.st_mtime);
- if (res == 0)
- res = CMP(b->st.st_ino, a->st.st_ino);
-
- return res;
+ int res = 0;
+ if (a->st.st_dev == b->st.st_dev && a->st.st_ino == b->st.st_ino)
+ return 0;
+
+ if (res == 0 && opts.maximise)
+ res = CMP(a->st.st_nlink, b->st.st_nlink);
+ if (res == 0 && opts.minimise)
+ res = CMP(b->st.st_nlink, a->st.st_nlink);
+ if (res == 0)
+ res = opts.keep_oldest ? CMP(b->st.st_mtime, a->st.st_mtime)
+ : CMP(a->st.st_mtime, b->st.st_mtime);
+ if (res == 0)
+ res = CMP(b->st.st_ino, a->st.st_ino);
+
+ return res;
}
/**
*/
static int file_link(struct file *a, struct file *b)
{
- char *ssz;
-
- file_link:
- assert(a->links != NULL);
- assert(b->links != NULL);
-
- ssz = size_to_human_string(SIZE_SUFFIX_3LETTER|
- SIZE_SUFFIX_SPACE|
- SIZE_DECIMAL_2DIGITS, a->st.st_size);
- jlog(JLOG_INFO, _("%sLinking %s to %s (-%s)"),
- opts.dry_run ? _("[DryRun] ") : "", a->links->path, b->links->path, ssz);
- free(ssz);
-
- if (!opts.dry_run) {
- size_t len = strlen(b->links->path) + strlen(".hardlink-temporary") + 1;
- char *new_path = xmalloc(len);
-
- snprintf(new_path, len, "%s.hardlink-temporary", b->links->path);
-
- if (link(a->links->path, new_path) != 0) {
- warn(_("cannot link %s to %s"), a->links->path, new_path);
- free(new_path);
- return FALSE;
- } else if (rename(new_path, b->links->path) != 0) {
- warn(_("cannot rename %s to %s"), a->links->path, new_path);
- unlink(new_path); /* cleanup failed rename */
- free(new_path);
- return FALSE;
- }
- free(new_path);
- }
-
- /* Update statistics */
- stats.linked++;
-
- /* Increase the link count of this file, and set stat() of other file */
- a->st.st_nlink++;
- b->st.st_nlink--;
-
- if (b->st.st_nlink == 0)
- stats.saved += a->st.st_size;
-
- /* Move the link from file b to a */
- {
- struct link *new_link = b->links;
-
- b->links = b->links->next;
- new_link->next = a->links->next;
- a->links->next = new_link;
- }
-
- // Do it again
- if (b->links)
- goto file_link;
-
- return TRUE;
+ char *ssz;
+
+ file_link:
+ assert(a->links != NULL);
+ assert(b->links != NULL);
+
+ ssz = size_to_human_string(SIZE_SUFFIX_3LETTER |
+ SIZE_SUFFIX_SPACE |
+ SIZE_DECIMAL_2DIGITS, a->st.st_size);
+ jlog(JLOG_INFO, _("%sLinking %s to %s (-%s)"),
+ opts.dry_run ? _("[DryRun] ") : "", a->links->path, b->links->path,
+ ssz);
+ free(ssz);
+
+ if (!opts.dry_run) {
+ size_t len =
+ strlen(b->links->path) + strlen(".hardlink-temporary") + 1;
+ char *new_path = xmalloc(len);
+
+ snprintf(new_path, len, "%s.hardlink-temporary",
+ b->links->path);
+
+ if (link(a->links->path, new_path) != 0) {
+ warn(_("cannot link %s to %s"), a->links->path,
+ new_path);
+ free(new_path);
+ return FALSE;
+ } else if (rename(new_path, b->links->path) != 0) {
+ warn(_("cannot rename %s to %s"), a->links->path,
+ new_path);
+ unlink(new_path); /* cleanup failed rename */
+ free(new_path);
+ return FALSE;
+ }
+ free(new_path);
+ }
+
+ /* Update statistics */
+ stats.linked++;
+
+ /* Increase the link count of this file, and set stat() of other file */
+ a->st.st_nlink++;
+ b->st.st_nlink--;
+
+ if (b->st.st_nlink == 0)
+ stats.saved += a->st.st_size;
+
+ /* Move the link from file b to a */
+ {
+ struct link *new_link = b->links;
+
+ b->links = b->links->next;
+ new_link->next = a->links->next;
+ a->links->next = new_link;
+ }
+
+ /* Do it again */
+ if (b->links)
+ goto file_link;
+
+ return TRUE;
}
/**
* Called by nftw() for the files. See the manual page for nftw() for
* further information.
*/
-static int inserter(const char *fpath, const struct stat *sb, int typeflag,
- struct FTW *ftwbuf)
+static int inserter(const char *fpath, const struct stat *sb,
+ int typeflag, struct FTW *ftwbuf)
{
- struct file *fil;
- struct file **node;
- size_t pathlen;
- int included;
- int excluded;
+ struct file *fil;
+ struct file **node;
+ size_t pathlen;
+ int included;
+ int excluded;
- if (handle_interrupt())
- return 1;
- if (typeflag == FTW_DNR || typeflag == FTW_NS)
- warn(_("cannot read %s"), fpath);
- if (typeflag != FTW_F || !S_ISREG(sb->st_mode))
- return 0;
+ if (handle_interrupt())
+ return 1;
+ if (typeflag == FTW_DNR || typeflag == FTW_NS)
+ warn(_("cannot read %s"), fpath);
+ if (typeflag != FTW_F || !S_ISREG(sb->st_mode))
+ return 0;
- included = regexec_any(opts.include, fpath);
- excluded = regexec_any(opts.exclude, fpath);
+ included = regexec_any(opts.include, fpath);
+ excluded = regexec_any(opts.exclude, fpath);
- if ((opts.exclude && excluded && !included) ||
- (!opts.exclude && opts.include && !included))
- return 0;
+ if ((opts.exclude && excluded && !included) ||
+ (!opts.exclude && opts.include && !included))
+ return 0;
- stats.files++;
+ stats.files++;
- if ((uintmax_t) sb->st_size < opts.min_size) {
- jlog(JLOG_VERBOSE1, _("Skipped %s (smaller than configured size)"), fpath);
- return 0;
- }
+ if ((uintmax_t) sb->st_size < opts.min_size) {
+ jlog(JLOG_VERBOSE1,
+ _("Skipped %s (smaller than configured size)"), fpath);
+ return 0;
+ }
- jlog(JLOG_VERBOSE2, _("Visiting %s (file %zu)"), fpath, stats.files);
+ jlog(JLOG_VERBOSE2, _("Visiting %s (file %zu)"), fpath, stats.files);
- pathlen = strlen(fpath) + 1;
+ pathlen = strlen(fpath) + 1;
- fil = xcalloc(1, sizeof(*fil));
- fil->links = xcalloc(1, sizeof(struct link) + pathlen);
+ fil = xcalloc(1, sizeof(*fil));
+ fil->links = xcalloc(1, sizeof(struct link) + pathlen);
- fil->st = *sb;
- fil->links->basename = ftwbuf->base;
- fil->links->next = NULL;
+ fil->st = *sb;
+ fil->links->basename = ftwbuf->base;
+ fil->links->next = NULL;
- memcpy(fil->links->path, fpath, pathlen);
+ memcpy(fil->links->path, fpath, pathlen);
- node = tsearch(fil, &files_by_ino, compare_nodes_ino);
+ node = tsearch(fil, &files_by_ino, compare_nodes_ino);
- if (node == NULL)
- goto fail;
+ if (node == NULL)
+ goto fail;
- if (*node != fil) {
- /* Already known inode, add link to inode information */
- assert((*node)->st.st_dev == sb->st_dev);
- assert((*node)->st.st_ino == sb->st_ino);
+ if (*node != fil) {
+ /* Already known inode, add link to inode information */
+ assert((*node)->st.st_dev == sb->st_dev);
+ assert((*node)->st.st_ino == sb->st_ino);
- fil->links->next = (*node)->links;
- (*node)->links = fil->links;
+ fil->links->next = (*node)->links;
+ (*node)->links = fil->links;
- free(fil);
- } else {
- /* New inode, insert into by-size table */
- node = tsearch(fil, &files, compare_nodes);
+ free(fil);
+ } else {
+ /* New inode, insert into by-size table */
+ node = tsearch(fil, &files, compare_nodes);
- if (node == NULL)
- goto fail;
+ if (node == NULL)
+ goto fail;
- if (*node != fil) {
- struct file *l;
+ if (*node != fil) {
+ struct file *l;
- if (file_compare(fil, *node) >= 0) {
- fil->next = *node;
- *node = fil;
- } else {
- for (l = *node; l != NULL; l = l->next) {
- if (l->next != NULL && file_compare(fil, l->next) < 0)
- continue;
+ if (file_compare(fil, *node) >= 0) {
+ fil->next = *node;
+ *node = fil;
+ } else {
+ for (l = *node; l != NULL; l = l->next) {
+ if (l->next != NULL
+ && file_compare(fil, l->next) < 0)
+ continue;
- fil->next = l->next;
- l->next = fil;
+ fil->next = l->next;
+ l->next = fil;
- break;
- }
- }
- }
- }
+ break;
+ }
+ }
+ }
+ }
- return 0;
+ return 0;
-fail:
- warn(_("cannot continue")); /* probably ENOMEM */
- return 0;
+ fail:
+ warn(_("cannot continue")); /* probably ENOMEM */
+ return 0;
}
/**
*/
static void visitor(const void *nodep, const VISIT which, const int depth)
{
- struct file *master = *(struct file **) nodep;
- struct file *other;
+ struct file *master = *(struct file **)nodep;
+ struct file *other;
- (void) depth;
+ (void)depth;
- if (which != leaf && which != endorder)
- return;
+ if (which != leaf && which != endorder)
+ return;
- for (; master != NULL; master = master->next) {
- if (handle_interrupt())
- exit(EXIT_FAILURE);
- if (master->links == NULL)
- continue;
+ for (; master != NULL; master = master->next) {
+ if (handle_interrupt())
+ exit(EXIT_FAILURE);
+ if (master->links == NULL)
+ continue;
- for (other = master->next; other != NULL; other = other->next) {
- if (handle_interrupt())
- exit(EXIT_FAILURE);
+ for (other = master->next; other != NULL; other = other->next) {
+ if (handle_interrupt())
+ exit(EXIT_FAILURE);
- assert(other != other->next);
- assert(other->st.st_size == master->st.st_size);
+ assert(other != other->next);
+ assert(other->st.st_size == master->st.st_size);
- if (other->links == NULL || !file_may_link_to(master, other))
- continue;
+ if (other->links == NULL
+ || !file_may_link_to(master, other))
+ continue;
- if (!file_link(master, other) && errno == EMLINK)
- master = other;
- }
- }
+ if (!file_link(master, other) && errno == EMLINK)
+ master = other;
+ }
+ }
}
/**
*/
static void __attribute__((__noreturn__)) usage(void)
{
- FILE *out = stdout;
-
- fputs(USAGE_HEADER, out);
- fprintf(out, _(" %s [options] <directory>|<file> ...\n"), program_invocation_short_name);
-
- fputs(USAGE_SEPARATOR, out);
- fputs(_("Consolidate duplicate files using hardlinks.\n"), out);
-
- fputs(USAGE_OPTIONS, out);
- fputs(_(" -v, --verbose verbose output (repeat for more verbosity)\n"), out);
- fputs(_(" -q, --quiet quiet mode - don't print anything\n"
- fputs(_(" -n, --dry-run don't actually link anything\n"), out);
- fputs(_(" -f, --respect-name filenames have to be identical\n"), out);
- fputs(_(" -p, --ignore-mode ignore changes of file mode\n"), out);
- fputs(_(" -o, --ignore-owner ignore owner changes\n"), out);
- fputs(_(" -t, --ignore-time ignore timestamps (when testing for equality)\n"), out);
+ FILE *out = stdout;
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options] <directory>|<file> ...\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Consolidate duplicate files using hardlinks.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -v, --verbose verbose output (repeat for more verbosity)\n"), out);
+ fputs(_(" -q, --quiet quiet mode - don't print anything\n"), out);
+ fputs(_(" -n, --dry-run don't actually link anything\n"), out);
+ fputs(_(" -f, --respect-name filenames have to be identical\n"), out);
+ fputs(_(" -p, --ignore-mode ignore changes of file mode\n"), out);
+ fputs(_(" -o, --ignore-owner ignore owner changes\n"), out);
+ fputs(_(" -t, --ignore-time ignore timestamps (when testing for equality)\n"), out);
#ifdef HAVE_SYS_XATTR_H
- fputs(_(" -X, --respect-xattrs respect extended attributes\n"), out);
+ fputs(_(" -X, --respect-xattrs respect extended attributes\n"), out);
#endif
- fputs(_(" -m, --maximize maximize the hardlink count, remove the file with\n"
- " lowest hardlink count\n"), out);
- fputs(_(" -M, --minimize reverse the meaning of -m\n"), out);
- fputs(_(" -O, --keep-oldest keep the oldest file of multiple equal files\n"
- " (lower precedence than minimize/maximize)\n"), out);
- fputs(_(" -x, --exclude <regex> regular expression to exclude files\n"), out);
- fputs(_(" -i, --include <regex> regular expression to include files/dirs\n"), out);
- fputs(_(" -s, --minimum-size <size> minimum size for files.\n"), out);
- fputs(_(" -c, --content compare only file contents, same as -pot\n"), out);
-
- fputs(USAGE_SEPARATOR, out);
- printf(USAGE_HELP_OPTIONS(28));
-
- printf(USAGE_MAN_TAIL("hardlink(1)"));
-
- exit(EXIT_SUCCESS);
+ fputs(_(" -m, --maximize maximize the hardlink count, remove the file with\n"
+ " lowest hardlink count\n"), out);
+ fputs(_(" -M, --minimize reverse the meaning of -m\n"), out);
+ fputs(_(" -O, --keep-oldest keep the oldest file of multiple equal files\n"
+ " (lower precedence than minimize/maximize)\n"), out);
+ fputs(_(" -x, --exclude <regex> regular expression to exclude files\n"), out);
+ fputs(_(" -i, --include <regex> regular expression to include files/dirs\n"), out);
+ fputs(_(" -s, --minimum-size <size> minimum size for files.\n"), out);
+ fputs(_(" -c, --content compare only file contents, same as -pot\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(28));
+ printf(USAGE_MAN_TAIL("hardlink(1)"));
+
+ exit(EXIT_SUCCESS);
}
/**
*/
static void register_regex(struct regex_link **pregs, const char *regex)
{
- struct regex_link *link = xmalloc(sizeof(*link));
- int err;
+ struct regex_link *link;
+ int err;
- if ((err = regcomp(&link->preg, regex, REG_NOSUB | REG_EXTENDED)) != 0) {
- size_t size = regerror(err, &link->preg, NULL, 0);
- char *buf = xmalloc(size + 1);
+ link = xmalloc(sizeof(*link));
- regerror(err, &link->preg, buf, size);
+ if ((err = regcomp(&link->preg, regex, REG_NOSUB | REG_EXTENDED)) != 0) {
+ size_t size = regerror(err, &link->preg, NULL, 0);
+ char *buf = xmalloc(size + 1);
- errx(EXIT_FAILURE, _("could not compile regular expression %s: %s"),
- regex, buf);
- }
+ regerror(err, &link->preg, buf, size);
- link->next = *pregs;
- *pregs = link;
+ errx(EXIT_FAILURE, _("could not compile regular expression %s: %s"),
+ regex, buf);
+ }
+ link->next = *pregs; *pregs = link;
}
/**
*/
static int parse_options(int argc, char *argv[])
{
- static const char optstr[] = "VhvnfpotXcmMOx:i:s:q";
- static const struct option long_options[] = {
- {"version", no_argument, NULL, 'V'},
- {"help", no_argument, NULL, 'h'},
- {"verbose", no_argument, NULL, 'v'},
- {"dry-run", no_argument, NULL, 'n'},
- {"respect-name", no_argument, NULL, 'f'},
- {"ignore-mode", no_argument, NULL, 'p'},
- {"ignore-owner", no_argument, NULL, 'o'},
- {"ignore-time", no_argument, NULL, 't'},
- {"respect-xattrs", no_argument, NULL, 'X'},
- {"maximize", no_argument, NULL, 'm'},
- {"minimize", no_argument, NULL, 'M'},
- {"keep-oldest", no_argument, NULL, 'O'},
- {"exclude", required_argument, NULL, 'x'},
- {"include", required_argument, NULL, 'i'},
- {"minimum-size", required_argument, NULL, 's'},
- {"content", no_argument, NULL, 'c'},
- {"quiet", no_argument, NULL, 'q'},
- {NULL, 0, NULL, 0}
- };
- static const ul_excl_t excl[] = {
- { 'q','v' },
- { 0 }
- };
- int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
- int c;
-
- while ((c = getopt_long(argc, argv, optstr, long_options, NULL)) != -1) {
-
- err_exclusive_options(c, long_options, excl, excl_st);
-
- switch (c) {
- case 'p':
- opts.respect_mode = FALSE;
- break;
- case 'o':
- opts.respect_owner = FALSE;
- break;
- case 't':
- opts.respect_time = FALSE;
- break;
- case 'X':
- opts.respect_xattrs = TRUE;
- break;
- case 'm':
- opts.maximise = TRUE;
- break;
- case 'M':
- opts.minimise = TRUE;
- break;
- case 'O':
- opts.keep_oldest = TRUE;
- break;
- case 'f':
- opts.respect_name = TRUE;
- break;
- case 'v':
- opts.verbosity++;
- break;
- case 'q':
- quiet = TRUE;
- break;
- case 'c':
- opts.respect_mode = FALSE;
- opts.respect_name = FALSE;
- opts.respect_owner = FALSE;
- opts.respect_time = FALSE;
- opts.respect_xattrs = FALSE;
- break;
- case 'n':
- opts.dry_run = 1;
- break;
- case 'x':
- register_regex(&opts.exclude, optarg);
- break;
- case 'i':
- register_regex(&opts.include, optarg);
- break;
- case 's':
- opts.min_size = strtosize_or_err(optarg, _("failed to parse size"));
- break;
-
- case 'h':
- usage();
- case 'V':
- print_version(EXIT_SUCCESS);
- default:
- errtryhelp(EXIT_FAILURE);
- }
- }
- return 0;
+ static const char optstr[] = "VhvnfpotXcmMOx:i:s:q";
+ static const struct option long_options[] = {
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {"verbose", no_argument, NULL, 'v'},
+ {"dry-run", no_argument, NULL, 'n'},
+ {"respect-name", no_argument, NULL, 'f'},
+ {"ignore-mode", no_argument, NULL, 'p'},
+ {"ignore-owner", no_argument, NULL, 'o'},
+ {"ignore-time", no_argument, NULL, 't'},
+ {"respect-xattrs", no_argument, NULL, 'X'},
+ {"maximize", no_argument, NULL, 'm'},
+ {"minimize", no_argument, NULL, 'M'},
+ {"keep-oldest", no_argument, NULL, 'O'},
+ {"exclude", required_argument, NULL, 'x'},
+ {"include", required_argument, NULL, 'i'},
+ {"minimum-size", required_argument, NULL, 's'},
+ {"content", no_argument, NULL, 'c'},
+ {"quiet", no_argument, NULL, 'q'},
+ {NULL, 0, NULL, 0}
+ };
+ static const ul_excl_t excl[] = {
+ {'q', 'v'},
+ {0}
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+ int c;
+
+ while ((c = getopt_long(argc, argv, optstr, long_options, NULL)) != -1) {
+
+ err_exclusive_options(c, long_options, excl, excl_st);
+
+ switch (c) {
+ case 'p':
+ opts.respect_mode = FALSE;
+ break;
+ case 'o':
+ opts.respect_owner = FALSE;
+ break;
+ case 't':
+ opts.respect_time = FALSE;
+ break;
+ case 'X':
+ opts.respect_xattrs = TRUE;
+ break;
+ case 'm':
+ opts.maximise = TRUE;
+ break;
+ case 'M':
+ opts.minimise = TRUE;
+ break;
+ case 'O':
+ opts.keep_oldest = TRUE;
+ break;
+ case 'f':
+ opts.respect_name = TRUE;
+ break;
+ case 'v':
+ opts.verbosity++;
+ break;
+ case 'q':
+ quiet = TRUE;
+ break;
+ case 'c':
+ opts.respect_mode = FALSE;
+ opts.respect_name = FALSE;
+ opts.respect_owner = FALSE;
+ opts.respect_time = FALSE;
+ opts.respect_xattrs = FALSE;
+ break;
+ case 'n':
+ opts.dry_run = 1;
+ break;
+ case 'x':
+ register_regex(&opts.exclude, optarg);
+ break;
+ case 'i':
+ register_regex(&opts.include, optarg);
+ break;
+ case 's':
+ opts.min_size = strtosize_or_err(optarg, _("failed to parse size"));
+ break;
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);}
+ }
+
+ return 0;
}
/**
- * to_be_called_atexit - Cleanup handler, also prints statistics.
- */
+* to_be_called_atexit - Cleanup handler, also prints statistics.
+*/
static void to_be_called_atexit(void)
{
- if (stats.started)
- print_stats();
+ if (stats.started)
+ print_stats();
}
/**
- * sighandler - Signal handler, sets the global last_signal variable
- * @i: The signal number
- */
+* sighandler - Signal handler, sets the global last_signal variable
+* @i: The signal number
+*/
static void sighandler(int i)
{
- if (last_signal != SIGINT)
- last_signal = i;
- if (i == SIGINT)
- putchar('\n');
+ if (last_signal != SIGINT)
+ last_signal = i;
+ if (i == SIGINT)
+ putchar('\n');
}
int main(int argc, char *argv[])
{
- struct sigaction sa;
-
- sa.sa_handler = sighandler;
- sa.sa_flags = SA_RESTART;
- sigfillset(&sa.sa_mask);
+ struct sigaction sa;
- /* If we receive a SIGINT, end the processing */
- sigaction(SIGINT, &sa, NULL);
- sigaction(SIGUSR1, &sa, NULL);
+ sa.sa_handler = sighandler;
+ sa.sa_flags = SA_RESTART;
+ sigfillset(&sa.sa_mask);
- /* Pretty print numeric output */
- setlocale(LC_NUMERIC, "");
+ /* If we receive a SIGINT, end the processing */
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGUSR1, &sa, NULL);
- if (atexit(to_be_called_atexit) != 0)
- err(EXIT_FAILURE, _("cannot register exit handler"));
+ /* Pretty print numeric output */
+ setlocale(LC_NUMERIC, "");
- parse_options(argc, argv);
+ if (atexit(to_be_called_atexit) != 0)
+ err(EXIT_FAILURE, _("cannot register exit handler"));
- if (optind == argc)
- errx(EXIT_FAILURE, _("no directory of dile specified"));
+ parse_options(argc, argv);
- gettime_monotonic(&stats.start_time);
- stats.started = TRUE;
+ if (optind == argc)
+ errx(EXIT_FAILURE, _("no directory of dile specified"));
- for (; optind < argc; optind++)
- if (nftw(argv[optind], inserter, 20, FTW_PHYS) == -1)
- warn(_("cannot process %s"), argv[optind]);
+ gettime_monotonic(&stats.start_time);
+ stats.started = TRUE;
- twalk(files, visitor);
+ for (; optind < argc; optind++) {
+ if (nftw(argv[optind], inserter, 20, FTW_PHYS) == -1)
+ warn(_("cannot process %s"), argv[optind]);
+ }
- return 0;
+ twalk(files, visitor);
+ return 0;
}