]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/hardlink.c
98c9f8d2912ad8da325d87e1b4e97b97b0d0f8dc
[thirdparty/util-linux.git] / misc-utils / hardlink.c
1 /* hardlink.c - Link multiple identical files together
2 *
3 * Copyright (C) 2008 - 2014 Julian Andres Klode <jak@jak-linux.org>
4 * Copyright (C) 2021 Karel Zak <kzak@redhat.com>
5 *
6 * SPDX-License-Identifier: MIT
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 * THE SOFTWARE.
25 */
26 #define _POSIX_C_SOURCE 200112L /* POSIX functions */
27 #define _XOPEN_SOURCE 600 /* nftw() */
28
29 #include <sys/types.h> /* stat */
30 #include <sys/stat.h> /* stat */
31 #include <sys/time.h> /* getrlimit, getrusage */
32 #include <sys/resource.h> /* getrlimit, getrusage */
33 #include <fcntl.h> /* posix_fadvise */
34 #include <ftw.h> /* ftw */
35 #include <search.h> /* tsearch() and friends */
36 #include <signal.h> /* SIG*, sigaction */
37 #include <getopt.h> /* getopt_long() */
38 #include <ctype.h> /* tolower() */
39 #include <sys/ioctl.h>
40
41 #if defined(HAVE_LINUX_FIEMAP_H) && defined(HAVE_SYS_VFS_H)
42 # include <linux/fs.h>
43 # include <linux/fiemap.h>
44 # ifdef FICLONE
45 # define USE_REFLINK 1
46 # endif
47 #endif
48
49 #include "nls.h"
50 #include "c.h"
51 #include "xalloc.h"
52 #include "strutils.h"
53 #include "monotonic.h"
54 #include "optutils.h"
55 #include "fileeq.h"
56
57 #ifdef USE_REFLINK
58 # include "statfs_magic.h"
59 #endif
60
61 #include <regex.h> /* regcomp(), regexec() */
62
63 #if defined(HAVE_SYS_XATTR_H) && defined(HAVE_LLISTXATTR) && defined(HAVE_LGETXATTR)
64 # include <sys/xattr.h>
65 # define USE_XATTR 1
66 #endif
67
68 static int quiet; /* don't print anything */
69 static int rootbasesz; /* size of the directory for nftw() */
70
71 #ifdef USE_REFLINK
72 enum {
73 REFLINK_NEVER = 0,
74 REFLINK_AUTO,
75 REFLINK_ALWAYS
76 };
77 static int reflink_mode = REFLINK_NEVER;
78 static int reflinks_skip;
79 #endif
80
81 static struct ul_fileeq fileeq;
82
83 /**
84 * struct file - Information about a file
85 * @st: The stat buffer associated with the file
86 * @next: Next file with the same size
87 * @basename: The offset off the basename in the filename
88 * @path: The path of the file
89 *
90 * This contains all information we need about a file.
91 */
92 struct file {
93 struct stat st;
94 struct ul_fileeq_data data;
95
96 struct file *next;
97 struct link {
98 struct link *next;
99 int basename;
100 int dirname;
101 #if __STDC_VERSION__ >= 199901L
102 char path[];
103 #elif __GNUC__
104 char path[0];
105 #else
106 char path[1];
107 #endif
108 } *links;
109 };
110
111 /**
112 * enum log_level - Logging levels
113 * @JLOG_SUMMARY: Default log level
114 * @JLOG_INFO: Verbose logging (verbose == 1)
115 * @JLOG_VERBOSE1: Verbosity 2
116 * @JLOG_VERBOSE2: Verbosity 3
117 */
118 enum log_level {
119 JLOG_SUMMARY,
120 JLOG_INFO,
121 JLOG_VERBOSE1,
122 JLOG_VERBOSE2
123 };
124
125 /**
126 * struct statistic - Statistics about the file
127 * @started: Whether we are post command-line processing
128 * @files: The number of files worked on
129 * @linked: The number of files replaced by a hardlink to a master
130 * @xattr_comparisons: The number of extended attribute comparisons
131 * @comparisons: The number of comparisons
132 * @saved: The (exaggerated) amount of space saved
133 * @start_time: The time we started at
134 */
135 static struct statistics {
136 int started;
137 size_t files;
138 size_t linked;
139 size_t xattr_comparisons;
140 size_t comparisons;
141 size_t ignored_reflinks;
142 double saved;
143 struct timeval start_time;
144 } stats;
145
146
147 struct hdl_regex {
148 regex_t re; /* POSIX compatible regex handler */
149
150 struct hdl_regex *next;
151 };
152
153 /**
154 * struct options - Processed command-line options
155 * @include: A linked list of regular expressions for the --include option
156 * @exclude: A linked list of regular expressions for the --exclude option
157 * @verbosity: The verbosity. Should be one of #enum log_level
158 * @respect_mode: Whether to respect file modes (default = TRUE)
159 * @respect_owner: Whether to respect file owners (uid, gid; default = TRUE)
160 * @respect_name: Whether to respect file names (default = FALSE)
161 * @respect_time: Whether to respect file modification times (default = TRUE)
162 * @respect_xattrs: Whether to respect extended attributes (default = FALSE)
163 * @maximise: Chose the file with the highest link count as master
164 * @minimise: Chose the file with the lowest link count as master
165 * @keep_oldest: Choose the file with oldest timestamp as master (default = FALSE)
166 * @dry_run: Specifies whether hardlink should not link files (default = FALSE)
167 * @min_size: Minimum size of files to consider. (default = 1 byte)
168 * @max_size: Maximum size of files to consider, 0 means umlimited. (default = 0 byte)
169 */
170 static struct options {
171 struct hdl_regex *include;
172 struct hdl_regex *exclude;
173
174 const char *method;
175 signed int verbosity;
176 unsigned int respect_mode:1;
177 unsigned int respect_owner:1;
178 unsigned int respect_name:1;
179 unsigned int respect_dir:1;
180 unsigned int respect_time:1;
181 unsigned int respect_xattrs:1;
182 unsigned int maximise:1;
183 unsigned int minimise:1;
184 unsigned int keep_oldest:1;
185 unsigned int dry_run:1;
186 uintmax_t min_size;
187 uintmax_t max_size;
188 size_t io_size;
189 size_t cache_size;
190 } opts = {
191 /* default setting */
192 #ifdef USE_FILEEQ_CRYPTOAPI
193 .method = "sha256",
194 #else
195 .method = "memcmp",
196 #endif
197 .respect_mode = TRUE,
198 .respect_owner = TRUE,
199 .respect_time = TRUE,
200 .respect_xattrs = FALSE,
201 .keep_oldest = FALSE,
202 .min_size = 1,
203 .cache_size = 10*1024*1024
204 };
205
206 /*
207 * files
208 *
209 * A binary tree of files, managed using tsearch(). To see which nodes
210 * are considered equal, see compare_nodes()
211 */
212 static void *files;
213 static void *files_by_ino;
214
215 /*
216 * last_signal
217 *
218 * The last signal we received. We store the signal here in order to be able
219 * to break out of loops gracefully and to return from our nftw() handler.
220 */
221 static volatile sig_atomic_t last_signal;
222
223
224 #define is_log_enabled(_level) (quiet == 0 && (_level) <= (unsigned int)opts.verbosity)
225
226 /**
227 * jlog - Logging for hardlink
228 * @level: The log level
229 * @format: A format string for printf()
230 */
231 __attribute__((format(printf, 2, 3)))
232 static void jlog(enum log_level level, const char *format, ...)
233 {
234 va_list args;
235
236 if (!is_log_enabled(level))
237 return;
238
239 va_start(args, format);
240 vfprintf(stdout, format, args);
241 va_end(args);
242 fputc('\n', stdout);
243 }
244
245 /**
246 * CMP - Compare two numerical values, return 1, 0, or -1
247 * @a: First value
248 * @b: Second value
249 *
250 * Used to compare two integers of any size while avoiding overflow.
251 */
252 #define CMP(a, b) ((a) > (b) ? 1 : ((a) < (b) ? -1 : 0))
253
254 /**
255 * register_regex - Compile and insert a regular expression into list
256 * @pregs: Pointer to a linked list of regular expressions
257 * @regex: String containing the regular expression to be compiled
258 */
259 static void register_regex(struct hdl_regex **pregs, const char *regex)
260 {
261 struct hdl_regex *link;
262 int err;
263
264 link = xmalloc(sizeof(*link));
265
266 if ((err = regcomp(&link->re, regex, REG_NOSUB | REG_EXTENDED)) != 0) {
267 size_t size = regerror(err, &link->re, NULL, 0);
268 char *buf = xmalloc(size + 1);
269
270 regerror(err, &link->re, buf, size);
271
272 errx(EXIT_FAILURE, _("could not compile regular expression %s: %s"),
273 regex, buf);
274 }
275 link->next = *pregs; *pregs = link;
276 }
277
278 /**
279 * match_any_regex - Match against multiple regular expressions
280 * @pregs: A linked list of regular expressions
281 * @what: The string to match against
282 *
283 * Checks whether any of the regular expressions in the list matches the
284 * string.
285 */
286 static int match_any_regex(struct hdl_regex *pregs, const char *what)
287 {
288 for (; pregs != NULL; pregs = pregs->next) {
289 if (regexec(&pregs->re, what, 0, NULL, 0) == 0)
290 return TRUE;
291 }
292 return FALSE;
293 }
294
295 /**
296 * compare_nodes - Node comparison function
297 * @_a: The first node (a #struct file)
298 * @_b: The second node (a #struct file)
299 *
300 * Compare the two nodes for the binary tree.
301 */
302 static int compare_nodes(const void *_a, const void *_b)
303 {
304 const struct file *a = _a;
305 const struct file *b = _b;
306 int diff = 0;
307
308 if (diff == 0)
309 diff = CMP(a->st.st_dev, b->st.st_dev);
310 if (diff == 0)
311 diff = CMP(a->st.st_size, b->st.st_size);
312
313 return diff;
314 }
315
316 /* Compare only filenames */
317 static inline int filename_strcmp(const struct file *a, const struct file *b)
318 {
319 return strcmp( a->links->path + a->links->basename,
320 b->links->path + b->links->basename);
321 }
322
323 /**
324 * Compare only directory names (ignores root directory and basename (filename))
325 *
326 * The complete path conrains three fragments:
327 *
328 * <rootdir> is specified on hardlink command line
329 * <dirname> is all betweehn rootdir and filename
330 * <filename> is last component (aka basename)
331 */
332 static inline int dirname_strcmp(const struct file *a, const struct file *b)
333 {
334 int diff = 0;
335 int asz = a->links->basename - a->links->dirname,
336 bsz = b->links->basename - b->links->dirname;
337
338 diff = CMP(asz, bsz);
339
340 if (diff == 0) {
341 const char *a_start, *b_start;
342
343 a_start = a->links->path + a->links->dirname;
344 b_start = b->links->path + b->links->dirname;
345
346 diff = strncmp(a_start, b_start, asz);
347 }
348 return diff;
349 }
350
351 /**
352 * compare_nodes_ino - Node comparison function
353 * @_a: The first node (a #struct file)
354 * @_b: The second node (a #struct file)
355 *
356 * Compare the two nodes for the binary tree.
357 */
358 static int compare_nodes_ino(const void *_a, const void *_b)
359 {
360 const struct file *a = _a;
361 const struct file *b = _b;
362 int diff = 0;
363
364 if (diff == 0)
365 diff = CMP(a->st.st_dev, b->st.st_dev);
366 if (diff == 0)
367 diff = CMP(a->st.st_ino, b->st.st_ino);
368
369 /* If opts.respect_name is used, we will restrict a struct file to
370 * contain only links with the same basename to keep the rest simple.
371 */
372 if (diff == 0 && opts.respect_name)
373 diff = filename_strcmp(a, b);
374 if (diff == 0 && opts.respect_dir)
375 diff = dirname_strcmp(a, b);
376
377 return diff;
378 }
379
380 /**
381 * print_stats - Print statistics to stdout
382 */
383 static void print_stats(void)
384 {
385 struct timeval end = { 0, 0 }, delta = { 0, 0 };
386 char *ssz;
387
388 gettime_monotonic(&end);
389 timersub(&end, &stats.start_time, &delta);
390
391 jlog(JLOG_SUMMARY, "%-25s %s", _("Mode:"),
392 opts.dry_run ? _("dry-run") : _("real"));
393 jlog(JLOG_SUMMARY, "%-25s %s", _("Method:"), opts.method);
394 jlog(JLOG_SUMMARY, "%-25s %zu", _("Files:"), stats.files);
395 jlog(JLOG_SUMMARY, _("%-25s %zu files"), _("Linked:"), stats.linked);
396
397 #ifdef USE_XATTR
398 jlog(JLOG_SUMMARY, _("%-25s %zu xattrs"), _("Compared:"),
399 stats.xattr_comparisons);
400 #endif
401 jlog(JLOG_SUMMARY, _("%-25s %zu files"), _("Compared:"),
402 stats.comparisons);
403 #ifdef USE_REFLINK
404 if (reflinks_skip)
405 jlog(JLOG_SUMMARY, _("%-25s %zu files"), _("Skipped reflinks:"),
406 stats.ignored_reflinks);
407 #endif
408 ssz = size_to_human_string(SIZE_SUFFIX_3LETTER |
409 SIZE_SUFFIX_SPACE |
410 SIZE_DECIMAL_2DIGITS, stats.saved);
411
412 jlog(JLOG_SUMMARY, "%-25s %s", _("Saved:"), ssz);
413 free(ssz);
414
415 jlog(JLOG_SUMMARY, _("%-25s %"PRId64".%06"PRId64" seconds"), _("Duration:"),
416 (int64_t)delta.tv_sec, (int64_t)delta.tv_usec);
417 }
418
419 /**
420 * handle_interrupt - Handle a signal
421 *
422 * Returns: %TRUE on SIGINT, SIGTERM; %FALSE on all other signals.
423 */
424 static int handle_interrupt(void)
425 {
426 switch (last_signal) {
427 case SIGINT:
428 case SIGTERM:
429 return TRUE;
430 case SIGUSR1:
431 print_stats();
432 putchar('\n');
433 break;
434 }
435
436 last_signal = 0;
437 return FALSE;
438 }
439
440 #ifdef USE_XATTR
441
442 /**
443 * llistxattr_or_die - Wrapper for llistxattr()
444 *
445 * This does the same thing as llistxattr() except that it aborts if any error
446 * other than "not supported" is detected.
447 */
448 static ssize_t llistxattr_or_die(const char *path, char *list, size_t size)
449 {
450 ssize_t len = llistxattr(path, list, size);
451
452 if (len < 0 && errno != ENOTSUP)
453 err(EXIT_FAILURE, _("cannot get xattr names for %s"), path);
454
455 return len;
456 }
457
458 /**
459 * lgetxattr_or_die - Wrapper for lgetxattr()
460 *
461 * This does the same thing as lgetxattr() except that it aborts upon error.
462 */
463 static ssize_t lgetxattr_or_die(const char *path,
464 const char *name, void *value, size_t size)
465 {
466 ssize_t len = lgetxattr(path, name, value, size);
467
468 if (len < 0)
469 err(EXIT_FAILURE, _("cannot get xattr value of %s for %s"),
470 name, path);
471
472 return len;
473 }
474
475 /**
476 * get_xattr_name_count - Count the number of xattr names
477 * @names: a non-empty table of concatenated, null-terminated xattr names
478 * @len: the total length of the table
479 *
480 * @Returns the number of xattr names
481 */
482 static int get_xattr_name_count(const char *const names, ssize_t len)
483 {
484 int count = 0;
485 const char *name;
486
487 for (name = names; name < (names + len); name += strlen(name) + 1)
488 count++;
489
490 return count;
491 }
492
493 /**
494 * cmp_xattr_name_ptrs - Compare two pointers to xattr names by comparing
495 * the names they point to.
496 */
497 static int cmp_xattr_name_ptrs(const void *ptr1, const void *ptr2)
498 {
499 return strcmp(*(char *const *)ptr1, *(char *const *)ptr2);
500 }
501
502 /**
503 * get_sorted_xattr_name_table - Create a sorted table of xattr names.
504 * @names - table of concatenated, null-terminated xattr names
505 * @n - the number of names
506 *
507 * @Returns allocated table of pointers to the names, sorted alphabetically
508 */
509 static const char **get_sorted_xattr_name_table(const char *names, int n)
510 {
511 const char **table = xmalloc(n * sizeof(char *));
512 int i;
513
514 for (i = 0; i < n; i++) {
515 table[i] = names;
516 names += strlen(names) + 1;
517 }
518
519 qsort(table, n, sizeof(char *), cmp_xattr_name_ptrs);
520
521 return table;
522 }
523
524 /**
525 * file_xattrs_equal - Compare the extended attributes of two files
526 * @a: The first file
527 * @b: The second file
528 *
529 * @Returns: %TRUE if and only if extended attributes are equal
530 */
531 static int file_xattrs_equal(const struct file *a, const struct file *b)
532 {
533 ssize_t len_a;
534 ssize_t len_b;
535 char *names_a = NULL;
536 char *names_b = NULL;
537 int n_a;
538 int n_b;
539 const char **name_ptrs_a = NULL;
540 const char **name_ptrs_b = NULL;
541 void *value_a = NULL;
542 void *value_b = NULL;
543 int ret = FALSE;
544 int i;
545
546 assert(a->links != NULL);
547 assert(b->links != NULL);
548
549 jlog(JLOG_VERBOSE1, _("Comparing xattrs of %s to %s"), a->links->path,
550 b->links->path);
551
552 stats.xattr_comparisons++;
553
554 len_a = llistxattr_or_die(a->links->path, NULL, 0);
555 len_b = llistxattr_or_die(b->links->path, NULL, 0);
556
557 if (len_a <= 0 && len_b <= 0)
558 return TRUE; // xattrs not supported or neither file has any
559
560 if (len_a != len_b)
561 return FALSE; // total lengths of xattr names differ
562
563 names_a = xmalloc(len_a);
564 names_b = xmalloc(len_b);
565
566 len_a = llistxattr_or_die(a->links->path, names_a, len_a);
567 len_b = llistxattr_or_die(b->links->path, names_b, len_b);
568 assert((len_a > 0) && (len_a == len_b));
569
570 n_a = get_xattr_name_count(names_a, len_a);
571 n_b = get_xattr_name_count(names_b, len_b);
572
573 if (n_a != n_b)
574 goto exit; // numbers of xattrs differ
575
576 name_ptrs_a = get_sorted_xattr_name_table(names_a, n_a);
577 name_ptrs_b = get_sorted_xattr_name_table(names_b, n_b);
578
579 // We now have two sorted tables of xattr names.
580
581 for (i = 0; i < n_a; i++) {
582 if (handle_interrupt())
583 goto exit; // user wants to quit
584
585 if (strcmp(name_ptrs_a[i], name_ptrs_b[i]) != 0)
586 goto exit; // names at same slot differ
587
588 len_a =
589 lgetxattr_or_die(a->links->path, name_ptrs_a[i], NULL, 0);
590 len_b =
591 lgetxattr_or_die(b->links->path, name_ptrs_b[i], NULL, 0);
592
593 if (len_a != len_b)
594 goto exit; // xattrs with same name, different value lengths
595
596 value_a = xmalloc(len_a);
597 value_b = xmalloc(len_b);
598
599 len_a = lgetxattr_or_die(a->links->path, name_ptrs_a[i],
600 value_a, len_a);
601 len_b = lgetxattr_or_die(b->links->path, name_ptrs_b[i],
602 value_b, len_b);
603 assert((len_a >= 0) && (len_a == len_b));
604
605 if (memcmp(value_a, value_b, len_a) != 0)
606 goto exit; // xattrs with same name, different values
607
608 free(value_a);
609 free(value_b);
610 value_a = NULL;
611 value_b = NULL;
612 }
613
614 ret = TRUE;
615
616 exit:
617 free(names_a);
618 free(names_b);
619 free(name_ptrs_a);
620 free(name_ptrs_b);
621 free(value_a);
622 free(value_b);
623 return ret;
624 }
625 #else /* !USE_XATTR */
626 static int file_xattrs_equal(const struct file *a, const struct file *b)
627 {
628 return TRUE;
629 }
630 #endif /* USE_XATTR */
631
632 /**
633 * file_may_link_to - Check whether a file may replace another one
634 * @a: The first file
635 * @b: The second file
636 *
637 * Check whether the two files are considered equal attributes and can be
638 * linked. This function does not compare content od the files!
639 */
640 static int file_may_link_to(const struct file *a, const struct file *b)
641 {
642 return (a->st.st_size != 0 &&
643 a->st.st_size == b->st.st_size &&
644 a->links != NULL && b->links != NULL &&
645 a->st.st_dev == b->st.st_dev &&
646 a->st.st_ino != b->st.st_ino &&
647 (!opts.respect_mode || a->st.st_mode == b->st.st_mode) &&
648 (!opts.respect_owner || a->st.st_uid == b->st.st_uid) &&
649 (!opts.respect_owner || a->st.st_gid == b->st.st_gid) &&
650 (!opts.respect_time || a->st.st_mtime == b->st.st_mtime) &&
651 (!opts.respect_name || filename_strcmp(a, b) == 0) &&
652 (!opts.respect_dir || dirname_strcmp(a, b) == 0) &&
653 (!opts.respect_xattrs || file_xattrs_equal(a, b)));
654 }
655
656 /**
657 * file_compare - Compare two files to decide which should be master
658 * @a: The first file
659 * @b: The second file
660 *
661 * Check which of the files should be considered greater and thus serve
662 * as the master when linking (the master is the file that all equal files
663 * will be replaced with).
664 */
665 static int file_compare(const struct file *a, const struct file *b)
666 {
667 int res = 0;
668 if (a->st.st_dev == b->st.st_dev && a->st.st_ino == b->st.st_ino)
669 return 0;
670
671 if (res == 0 && opts.maximise)
672 res = CMP(a->st.st_nlink, b->st.st_nlink);
673 if (res == 0 && opts.minimise)
674 res = CMP(b->st.st_nlink, a->st.st_nlink);
675 if (res == 0)
676 res = opts.keep_oldest ? CMP(b->st.st_mtime, a->st.st_mtime)
677 : CMP(a->st.st_mtime, b->st.st_mtime);
678 if (res == 0)
679 res = CMP(b->st.st_ino, a->st.st_ino);
680
681 return res;
682 }
683
684 #ifdef USE_REFLINK
685 static inline int do_link(struct file *a, struct file *b,
686 const char *new_name, int reflink)
687 {
688 if (reflink) {
689 int dest = -1, src = -1;
690
691 dest = open(new_name, O_CREAT|O_WRONLY|O_TRUNC, 0600);
692 if (dest < 0)
693 goto fallback;
694 if (fchmod(dest, b->st.st_mode) != 0)
695 goto fallback;
696 if (fchown(dest, b->st.st_uid, b->st.st_gid) != 0)
697 goto fallback;
698 src = open(a->links->path, O_RDONLY);
699 if (src < 0)
700 goto fallback;
701 if (ioctl(dest, FICLONE, src) != 0)
702 goto fallback;
703 close(dest);
704 close(src);
705 return 0;
706 fallback:
707 if (dest >= 0) {
708 close(dest);
709 unlink(new_name);
710 }
711 if (src >= 0)
712 close(src);
713
714 if (reflink_mode == REFLINK_ALWAYS)
715 return -errno;
716 jlog(JLOG_VERBOSE2,_("Reflinking failed, fallback to hardlinking"));
717 }
718
719 return link(a->links->path, new_name);
720 }
721 #else
722 static inline int do_link(struct file *a,
723 struct file *b __attribute__((__unused__)),
724 const char *new_name,
725 int reflink __attribute__((__unused__)))
726 {
727 return link(a->links->path, new_name);
728 }
729 #endif /* USE_REFLINK */
730
731 /**
732 * file_link - Replace b with a link to a
733 * @a: The first file
734 * @b: The second file
735 *
736 * Link the file, replacing @b with the current one. The file is first
737 * linked to a temporary name, and then renamed to the name of @b, making
738 * the replace atomic (@b will always exist).
739 */
740 static int file_link(struct file *a, struct file *b, int reflink)
741 {
742
743 file_link:
744 assert(a->links != NULL);
745 assert(b->links != NULL);
746
747 if (is_log_enabled(JLOG_INFO)) {
748 char *ssz = size_to_human_string(SIZE_SUFFIX_3LETTER |
749 SIZE_SUFFIX_SPACE |
750 SIZE_DECIMAL_2DIGITS, a->st.st_size);
751 jlog(JLOG_INFO, _("%s%sLinking %s to %s (-%s)"),
752 opts.dry_run ? _("[DryRun] ") : "",
753 reflink ? "Ref" : "",
754 a->links->path, b->links->path,
755 ssz);
756 free(ssz);
757 }
758
759 if (!opts.dry_run) {
760 char *new_path;
761 int failed = 1;
762
763 xasprintf(&new_path, "%s.hardlink-temporary", b->links->path);
764
765 if (do_link(a, b, new_path, reflink) != 0)
766 warn(_("cannot link %s to %s"), a->links->path, new_path);
767
768 else if (rename(new_path, b->links->path) != 0) {
769 warn(_("cannot rename %s to %s"), a->links->path, new_path);
770 unlink(new_path);
771 } else
772 failed = 0;
773
774 free(new_path);
775 if (failed)
776 return FALSE;
777 }
778
779 /* Update statistics */
780 stats.linked++;
781
782 /* Increase the link count of this file, and set stat() of other file */
783 a->st.st_nlink++;
784 b->st.st_nlink--;
785
786 if (b->st.st_nlink == 0)
787 stats.saved += a->st.st_size;
788
789 /* Move the link from file b to a */
790 {
791 struct link *new_link = b->links;
792
793 b->links = b->links->next;
794 new_link->next = a->links->next;
795 a->links->next = new_link;
796 }
797
798 /* Do it again */
799 if (b->links)
800 goto file_link;
801
802 return TRUE;
803 }
804
805 static int has_fpath(struct file *node, const char *path)
806 {
807 struct link *l;
808
809 for (l = node->links; l; l = l->next) {
810 if (strcmp(l->path, path) == 0)
811 return 1;
812 }
813
814 return 0;
815 }
816
817
818 /**
819 * inserter - Callback function for nftw()
820 * @fpath: The path of the file being visited
821 * @sb: The stat information of the file
822 * @typeflag: The type flag
823 * @ftwbuf: Contains current level of nesting and offset of basename
824 *
825 * Called by nftw() for the files. See the manual page for nftw() for
826 * further information.
827 */
828 static int inserter(const char *fpath, const struct stat *sb,
829 int typeflag, struct FTW *ftwbuf)
830 {
831 struct file *fil;
832 struct file **node;
833 size_t pathlen;
834 int included;
835 int excluded;
836
837 if (handle_interrupt())
838 return 1;
839 if (typeflag == FTW_DNR || typeflag == FTW_NS)
840 warn(_("cannot read %s"), fpath);
841 if (typeflag != FTW_F || !S_ISREG(sb->st_mode))
842 return 0;
843
844 included = match_any_regex(opts.include, fpath);
845 excluded = match_any_regex(opts.exclude, fpath);
846
847 if ((opts.exclude && excluded && !included) ||
848 (!opts.exclude && opts.include && !included))
849 return 0;
850
851 stats.files++;
852
853 if ((uintmax_t) sb->st_size < opts.min_size) {
854 jlog(JLOG_VERBOSE1,
855 _("Skipped %s (smaller than configured size)"), fpath);
856 return 0;
857 }
858
859 jlog(JLOG_VERBOSE2, " %5zu: [%" PRIu64 "/%" PRIu64 "/%zu] %s",
860 stats.files, sb->st_dev, sb->st_ino,
861 (size_t) sb->st_nlink, fpath);
862
863 if ((opts.max_size > 0) && ((uintmax_t) sb->st_size > opts.max_size)) {
864 jlog(JLOG_VERBOSE1,
865 _("Skipped %s (greater than configured size)"), fpath);
866 return 0;
867 }
868
869 pathlen = strlen(fpath) + 1;
870
871 fil = xcalloc(1, sizeof(*fil));
872 fil->links = xcalloc(1, sizeof(struct link) + pathlen);
873
874 fil->st = *sb;
875 fil->links->basename = ftwbuf->base;
876 fil->links->dirname = rootbasesz;
877 fil->links->next = NULL;
878
879 memcpy(fil->links->path, fpath, pathlen);
880
881 node = tsearch(fil, &files_by_ino, compare_nodes_ino);
882
883 if (node == NULL)
884 goto fail;
885
886 if (*node != fil) {
887 /* Already known inode, add link to inode information */
888 assert((*node)->st.st_dev == sb->st_dev);
889 assert((*node)->st.st_ino == sb->st_ino);
890
891 if (has_fpath(*node, fpath)) {
892 jlog(JLOG_VERBOSE1,
893 _("Skipped %s (specified more than once)"), fpath);
894 free(fil->links);
895 } else {
896 fil->links->next = (*node)->links;
897 (*node)->links = fil->links;
898 }
899
900 free(fil);
901 } else {
902 /* New inode, insert into by-size table */
903 node = tsearch(fil, &files, compare_nodes);
904
905 if (node == NULL)
906 goto fail;
907
908 if (*node != fil) {
909 struct file *l;
910
911 if (file_compare(fil, *node) >= 0) {
912 fil->next = *node;
913 *node = fil;
914 } else {
915 for (l = *node; l != NULL; l = l->next) {
916 if (l->next != NULL
917 && file_compare(fil, l->next) < 0)
918 continue;
919
920 fil->next = l->next;
921 l->next = fil;
922
923 break;
924 }
925 }
926 }
927 }
928
929 return 0;
930
931 fail:
932 warn(_("cannot continue")); /* probably ENOMEM */
933 return 0;
934 }
935
936 #ifdef USE_REFLINK
937 static int is_reflink_compatible(dev_t devno, const char *filename)
938 {
939 static dev_t last_dev = 0;
940 static int last_status = 0;
941
942 if (last_dev != devno) {
943 struct statfs vfs;
944
945 if (statfs(filename, &vfs) != 0)
946 return 0;
947
948 last_dev = devno;
949 switch (vfs.f_type) {
950 case STATFS_BTRFS_MAGIC:
951 case STATFS_XFS_MAGIC:
952 last_status = 1;
953 break;
954 default:
955 last_status = 0;
956 break;
957 }
958 }
959
960 return last_status;
961 }
962
963 static int is_reflink(struct file *xa, struct file *xb)
964 {
965 int last = 0, rc = 0;
966 char abuf[BUFSIZ] = { 0 },
967 bbuf[BUFSIZ] = { 0 };
968
969 struct fiemap *amap = (struct fiemap *) abuf,
970 *bmap = (struct fiemap *) bbuf;
971
972 int af = open(xa->links->path, O_RDONLY),
973 bf = open(xb->links->path, O_RDONLY);
974
975 if (af < 0 || bf < 0)
976 goto done;
977
978 do {
979 size_t i;
980
981 amap->fm_length = ~0ULL;
982 amap->fm_flags = FIEMAP_FLAG_SYNC;
983 amap->fm_extent_count = (sizeof(abuf) - sizeof(*amap)) / sizeof(struct fiemap_extent);
984
985 bmap->fm_length = ~0ULL;
986 bmap->fm_flags = FIEMAP_FLAG_SYNC;
987 bmap->fm_extent_count = (sizeof(bbuf) - sizeof(*bmap)) / sizeof(struct fiemap_extent);
988
989 if (ioctl(af, FS_IOC_FIEMAP, (unsigned long) amap) < 0)
990 goto done;
991 if (ioctl(bf, FS_IOC_FIEMAP, (unsigned long) bmap) < 0)
992 goto done;
993
994 if (amap->fm_mapped_extents != bmap->fm_mapped_extents)
995 goto done;
996
997 for (i = 0; i < amap->fm_mapped_extents; i++) {
998 struct fiemap_extent *a = &amap->fm_extents[i];
999 struct fiemap_extent *b = &bmap->fm_extents[i];
1000
1001 if (a->fe_logical != b->fe_logical ||
1002 a->fe_length != b->fe_length ||
1003 a->fe_physical != b->fe_physical)
1004 goto done;
1005 if (!(a->fe_flags & FIEMAP_EXTENT_SHARED) ||
1006 !(b->fe_flags & FIEMAP_EXTENT_SHARED))
1007 goto done;
1008 if (a->fe_flags & FIEMAP_EXTENT_LAST)
1009 last = 1;
1010 }
1011
1012 bmap->fm_start = amap->fm_start =
1013 amap->fm_extents[amap->fm_mapped_extents - 1].fe_logical +
1014 amap->fm_extents[amap->fm_mapped_extents - 1].fe_length;
1015 } while (last == 0);
1016
1017 rc = 1;
1018 done:
1019 if (af >= 0)
1020 close(af);
1021 if (bf >= 0)
1022 close(bf);
1023 return rc;
1024 }
1025 #endif /* USE_REFLINK */
1026
1027 static inline size_t count_nodes(struct file *x)
1028 {
1029 size_t ct = 0;
1030
1031 for ( ; x != NULL; x = x->next)
1032 ct++;
1033
1034 return ct;
1035 }
1036
1037 /**
1038 * visitor - Callback for twalk()
1039 * @nodep: Pointer to a pointer to a #struct file
1040 * @which: At which point this visit is (preorder, postorder, endorder)
1041 * @depth: The depth of the node in the tree
1042 *
1043 * Visit the nodes in the binary tree. For each node, call hardlinker()
1044 * on each #struct file in the linked list of #struct file instances located
1045 * at that node.
1046 */
1047 static void visitor(const void *nodep, const VISIT which, const int depth)
1048 {
1049 struct file *master = *(struct file **)nodep;
1050 struct file *begin = master;
1051 struct file *other;
1052
1053 (void)depth;
1054
1055 if (which != leaf && which != endorder)
1056 return;
1057
1058 for (; master != NULL; master = master->next) {
1059 size_t nnodes, memsiz;
1060 int may_reflink = 0;
1061
1062 if (handle_interrupt())
1063 exit(EXIT_FAILURE);
1064 if (master->links == NULL)
1065 continue;
1066
1067 /* calculate per file max memory use */
1068 nnodes = count_nodes(master);
1069 if (!nnodes)
1070 continue;
1071
1072 /* per-file cache size */
1073 memsiz = opts.cache_size / nnodes;
1074 /* filesiz, readsiz, memsiz */
1075 ul_fileeq_set_size(&fileeq, master->st.st_size, opts.io_size, memsiz);
1076
1077 #ifdef USE_REFLINK
1078 if (reflink_mode || reflinks_skip) {
1079 may_reflink =
1080 reflink_mode == REFLINK_ALWAYS ? 1 :
1081 is_reflink_compatible(master->st.st_dev,
1082 master->links->path);
1083 }
1084 #endif
1085 for (other = master->next; other != NULL; other = other->next) {
1086 int eq;
1087
1088 if (handle_interrupt())
1089 exit(EXIT_FAILURE);
1090
1091 assert(other != other->next);
1092 assert(other->st.st_size == master->st.st_size);
1093
1094 if (!other->links)
1095 continue;
1096
1097 /* check file attributes, etc. */
1098 if (!file_may_link_to(master, other)) {
1099 jlog(JLOG_VERBOSE2,
1100 _("Skipped (attributes mismatch) %s"), other->links->path);
1101 continue;
1102 }
1103 #ifdef USE_REFLINK
1104 if (may_reflink && reflinks_skip && is_reflink(master, other)) {
1105 jlog(JLOG_VERBOSE2,
1106 _("Skipped (already reflink) %s"), other->links->path);
1107 stats.ignored_reflinks++;
1108 continue;
1109 }
1110 #endif
1111 /* initialize content comparison */
1112 if (!ul_fileeq_data_associated(&master->data))
1113 ul_fileeq_data_set_file(&master->data, master->links->path);
1114 if (!ul_fileeq_data_associated(&other->data))
1115 ul_fileeq_data_set_file(&other->data, other->links->path);
1116
1117 /* compare files */
1118 eq = ul_fileeq(&fileeq, &master->data, &other->data);
1119
1120 /* reduce number of open files, keep only master open */
1121 ul_fileeq_data_close_file(&other->data);
1122
1123 stats.comparisons++;
1124
1125 if (!eq) {
1126 jlog(JLOG_VERBOSE2,
1127 _("Skipped (content mismatch) %s"), other->links->path);
1128 continue;
1129 }
1130
1131 /* link files */
1132 if (!file_link(master, other, may_reflink) && errno == EMLINK) {
1133 ul_fileeq_data_deinit(&master->data);
1134 master = other;
1135 }
1136 }
1137
1138 /* don't keep master data in memory */
1139 ul_fileeq_data_deinit(&master->data);
1140 }
1141
1142 /* final cleanup */
1143 for (other = begin; other != NULL; other = other->next) {
1144 if (ul_fileeq_data_associated(&other->data))
1145 ul_fileeq_data_deinit(&other->data);
1146 }
1147 }
1148
1149 /**
1150 * usage - Print the program help and exit
1151 */
1152 static void __attribute__((__noreturn__)) usage(void)
1153 {
1154 FILE *out = stdout;
1155
1156 fputs(USAGE_HEADER, out);
1157 fprintf(out, _(" %s [options] <directory>|<file> ...\n"),
1158 program_invocation_short_name);
1159
1160 fputs(USAGE_SEPARATOR, out);
1161 fputs(_("Consolidate duplicate files using hardlinks.\n"), out);
1162
1163 fputs(USAGE_OPTIONS, out);
1164 fputs(_(" -c, --content compare only file contents, same as -pot\n"), out);
1165 fputs(_(" -b, --io-size <size> I/O buffer size for file reading\n"
1166 " (speedup, using more RAM)\n"), out);
1167 fputs(_(" -d, --respect-dir directory names have to be identical\n"), out);
1168 fputs(_(" -f, --respect-name filenames have to be identical\n"), out);
1169 fputs(_(" -i, --include <regex> regular expression to include files/dirs\n"), out);
1170 fputs(_(" -m, --maximize maximize the hardlink count, remove the file with\n"
1171 " lowest hardlink count\n"), out);
1172 fputs(_(" -M, --minimize reverse the meaning of -m\n"), out);
1173 fputs(_(" -n, --dry-run don't actually link anything\n"), out);
1174 fputs(_(" -o, --ignore-owner ignore owner changes\n"), out);
1175 fputs(_(" -O, --keep-oldest keep the oldest file of multiple equal files\n"
1176 " (lower precedence than minimize/maximize)\n"), out);
1177 fputs(_(" -p, --ignore-mode ignore changes of file mode\n"), out);
1178 fputs(_(" -q, --quiet quiet mode - don't print anything\n"), out);
1179 fputs(_(" -r, --cache-size <size> memory limit for cached file content data\n"), out);
1180 fputs(_(" -s, --minimum-size <size> minimum size for files.\n"), out);
1181 fputs(_(" -S, --maximum-size <size> maximum size for files.\n"), out);
1182 fputs(_(" -t, --ignore-time ignore timestamps (when testing for equality)\n"), out);
1183 fputs(_(" -v, --verbose verbose output (repeat for more verbosity)\n"), out);
1184 fputs(_(" -x, --exclude <regex> regular expression to exclude files\n"), out);
1185 #ifdef USE_XATTR
1186 fputs(_(" -X, --respect-xattrs respect extended attributes\n"), out);
1187 #endif
1188 fputs(_(" -y, --method <name> file content comparison method\n"), out);
1189
1190 #ifdef USE_REFLINK
1191 fputs(_(" --reflink[=<when>] create clone/CoW copies (auto, always, never)\n"), out);
1192 fputs(_(" --skip-reflinks skip already cloned files (enabled on --reflink)\n"), out);
1193 #endif
1194 fputs(USAGE_SEPARATOR, out);
1195 fprintf(out, USAGE_HELP_OPTIONS(28));
1196 fprintf(out, USAGE_MAN_TAIL("hardlink(1)"));
1197
1198 exit(EXIT_SUCCESS);
1199 }
1200
1201 /**
1202 * parse_options - Parse the command line options
1203 * @argc: Number of options
1204 * @argv: Array of options
1205 */
1206 static int parse_options(int argc, char *argv[])
1207 {
1208 enum {
1209 OPT_REFLINK = CHAR_MAX + 1,
1210 OPT_SKIP_RELINKS
1211 };
1212 static const char optstr[] = "VhvndfpotXcmMOx:y:i:r:S:s:b:q";
1213 static const struct option long_options[] = {
1214 {"version", no_argument, NULL, 'V'},
1215 {"help", no_argument, NULL, 'h'},
1216 {"verbose", no_argument, NULL, 'v'},
1217 {"dry-run", no_argument, NULL, 'n'},
1218 {"respect-name", no_argument, NULL, 'f'},
1219 {"respect-dir", no_argument, NULL, 'd'},
1220 {"ignore-mode", no_argument, NULL, 'p'},
1221 {"ignore-owner", no_argument, NULL, 'o'},
1222 {"ignore-time", no_argument, NULL, 't'},
1223 {"respect-xattrs", no_argument, NULL, 'X'},
1224 {"maximize", no_argument, NULL, 'm'},
1225 {"minimize", no_argument, NULL, 'M'},
1226 {"keep-oldest", no_argument, NULL, 'O'},
1227 {"exclude", required_argument, NULL, 'x'},
1228 {"include", required_argument, NULL, 'i'},
1229 {"method", required_argument, NULL, 'y' },
1230 {"minimum-size", required_argument, NULL, 's'},
1231 {"maximum-size", required_argument, NULL, 'S'},
1232 #ifdef USE_REFLINK
1233 {"reflink", optional_argument, NULL, OPT_REFLINK },
1234 {"skip-reflinks", no_argument, NULL, OPT_SKIP_RELINKS },
1235 #endif
1236 {"io-size", required_argument, NULL, 'b'},
1237 {"content", no_argument, NULL, 'c'},
1238 {"quiet", no_argument, NULL, 'q'},
1239 {"cache-size", required_argument, NULL, 'r'},
1240 {NULL, 0, NULL, 0}
1241 };
1242 static const ul_excl_t excl[] = {
1243 {'q', 'v'},
1244 {0}
1245 };
1246 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
1247 int c, content_only = 0;
1248
1249 while ((c = getopt_long(argc, argv, optstr, long_options, NULL)) != -1) {
1250
1251 err_exclusive_options(c, long_options, excl, excl_st);
1252
1253 switch (c) {
1254 case 'p':
1255 opts.respect_mode = FALSE;
1256 break;
1257 case 'o':
1258 opts.respect_owner = FALSE;
1259 break;
1260 case 't':
1261 opts.respect_time = FALSE;
1262 break;
1263 case 'X':
1264 opts.respect_xattrs = TRUE;
1265 break;
1266 case 'm':
1267 opts.maximise = TRUE;
1268 break;
1269 case 'M':
1270 opts.minimise = TRUE;
1271 break;
1272 case 'O':
1273 opts.keep_oldest = TRUE;
1274 break;
1275 case 'f':
1276 opts.respect_name = TRUE;
1277 break;
1278 case 'd':
1279 opts.respect_dir = TRUE;
1280 break;
1281 case 'v':
1282 opts.verbosity++;
1283 break;
1284 case 'q':
1285 quiet = TRUE;
1286 break;
1287 case 'c':
1288 content_only = 1;
1289 break;
1290 case 'n':
1291 opts.dry_run = 1;
1292 break;
1293 case 'x':
1294 register_regex(&opts.exclude, optarg);
1295 break;
1296 case 'y':
1297 opts.method = optarg;
1298 break;
1299 case 'i':
1300 register_regex(&opts.include, optarg);
1301 break;
1302 case 's':
1303 opts.min_size = strtosize_or_err(optarg, _("failed to parse minimum size"));
1304 break;
1305 case 'S':
1306 opts.max_size = strtosize_or_err(optarg, _("failed to parse maximum size"));
1307 break;
1308 case 'r':
1309 opts.cache_size = strtosize_or_err(optarg, _("failed to parse cache size"));
1310 break;
1311 case 'b':
1312 opts.io_size = strtosize_or_err(optarg, _("failed to parse I/O size"));
1313 break;
1314 #ifdef USE_REFLINK
1315 case OPT_REFLINK:
1316 reflink_mode = REFLINK_AUTO;
1317 if (optarg) {
1318 if (strcmp(optarg, "auto") == 0)
1319 reflink_mode = REFLINK_AUTO;
1320 else if (strcmp(optarg, "always") == 0)
1321 reflink_mode = REFLINK_ALWAYS;
1322 else if (strcmp(optarg, "never") == 0)
1323 reflink_mode = REFLINK_NEVER;
1324 else
1325 errx(EXIT_FAILURE, _("unsupported reflink mode; %s"), optarg);
1326 }
1327 if (reflink_mode != REFLINK_NEVER)
1328 reflinks_skip = 1;
1329 break;
1330 case OPT_SKIP_RELINKS:
1331 reflinks_skip = 1;
1332 break;
1333 #endif
1334 case 'h':
1335 usage();
1336 case 'V':
1337 {
1338 static const char *features[] = {
1339 #ifdef USE_REFLINK
1340 "reflink",
1341 #endif
1342 #ifdef USE_FILEEQ_CRYPTOAPI
1343 "cryptoapi",
1344 #endif
1345 NULL
1346 };
1347 print_version_with_features(EXIT_SUCCESS, features);
1348 }
1349 default:
1350 errtryhelp(EXIT_FAILURE);
1351 }
1352 }
1353
1354 if (content_only) {
1355 opts.respect_mode = FALSE;
1356 opts.respect_name = FALSE;
1357 opts.respect_dir = FALSE;
1358 opts.respect_owner = FALSE;
1359 opts.respect_time = FALSE;
1360 opts.respect_xattrs = FALSE;
1361 }
1362 return 0;
1363 }
1364
1365 /**
1366 * to_be_called_atexit - Cleanup handler, also prints statistics.
1367 */
1368 static void to_be_called_atexit(void)
1369 {
1370 if (stats.started)
1371 print_stats();
1372 }
1373
1374 /**
1375 * sighandler - Signal handler, sets the global last_signal variable
1376 * @i: The signal number
1377 */
1378 static void sighandler(int i)
1379 {
1380 if (last_signal != SIGINT)
1381 last_signal = i;
1382 if (i == SIGINT)
1383 /* can't use stdio on signal handler */
1384 ignore_result(write(STDOUT_FILENO, "\n", sizeof("\n")-1));
1385 }
1386
1387 int main(int argc, char *argv[])
1388 {
1389 struct sigaction sa;
1390 int rc;
1391
1392 sa.sa_handler = sighandler;
1393 sa.sa_flags = SA_RESTART;
1394 sigfillset(&sa.sa_mask);
1395
1396 /* If we receive a SIGINT, end the processing */
1397 sigaction(SIGINT, &sa, NULL);
1398 sigaction(SIGUSR1, &sa, NULL);
1399
1400 /* Localize messages, number formatting, and anything else. */
1401 setlocale(LC_ALL, "");
1402 bindtextdomain(PACKAGE, LOCALEDIR);
1403 textdomain(PACKAGE);
1404
1405 if (atexit(to_be_called_atexit) != 0)
1406 err(EXIT_FAILURE, _("cannot register exit handler"));
1407
1408 parse_options(argc, argv);
1409
1410 if (optind == argc)
1411 errx(EXIT_FAILURE, _("no directory or file specified"));
1412
1413 gettime_monotonic(&stats.start_time);
1414
1415 rc = ul_fileeq_init(&fileeq, opts.method);
1416 if (rc != 0 && strcmp(opts.method, "memcmp") != 0) {
1417 jlog(JLOG_INFO, _("cannot initialize %s method, use 'memcmp' fallback"), opts.method);
1418 opts.method = "memcmp";
1419 rc = ul_fileeq_init(&fileeq, opts.method);
1420 }
1421 if (rc < 0)
1422 err(EXIT_FAILURE, _("failed to initialize files comparior"));
1423
1424 /* defautl I/O size */
1425 if (!opts.io_size) {
1426 if (strcmp(opts.method, "memcmp") == 0)
1427 opts.io_size = 8*1024;
1428 else
1429 opts.io_size = 1024*1024;
1430 }
1431
1432 stats.started = TRUE;
1433
1434 jlog(JLOG_VERBOSE2, _("Scanning [device/inode/links]:"));
1435 for (; optind < argc; optind++) {
1436 char *path = realpath(argv[optind], NULL);
1437
1438 if (!path) {
1439 warn(_("cannot get realpath: %s"), argv[optind]);
1440 continue;
1441 }
1442 if (opts.respect_dir)
1443 rootbasesz = strlen(path);
1444 if (nftw(path, inserter, 20, FTW_PHYS) == -1)
1445 warn(_("cannot process %s"), path);
1446 free(path);
1447 rootbasesz = 0;
1448 }
1449
1450 twalk(files, visitor);
1451
1452 ul_fileeq_deinit(&fileeq);
1453 return 0;
1454 }