81 | 81 |
#define REG_NOSUB 0 /* we do want backreferences in PCRE mode */
|
82 | 82 |
#else
|
83 | 83 |
#include <regex.h> /* regcomp(), regsearch() */
|
|
84 |
#endif
|
|
85 |
|
|
86 |
#ifdef HAVE_XATTR
|
|
87 |
#include <attr/xattr.h> /* listxattr, getxattr */
|
84 | 88 |
#endif
|
85 | 89 |
|
86 | 90 |
/**
|
|
135 | 139 |
* @started: Whether we are post command-line processing
|
136 | 140 |
* @files: The number of files worked on
|
137 | 141 |
* @linked: The number of files replaced by a hardlink to a master
|
|
142 |
* @xattr_comparisons: The number of extended attribute comparisons
|
138 | 143 |
* @comparisons: The number of comparisons
|
139 | 144 |
* @saved: The (exaggerated) amount of space saved
|
140 | 145 |
* @start_time: The time we started at, in seconds since some unspecified point
|
|
143 | 148 |
hl_bool started;
|
144 | 149 |
size_t files;
|
145 | 150 |
size_t linked;
|
|
151 |
size_t xattr_comparisons;
|
146 | 152 |
size_t comparisons;
|
147 | 153 |
double saved;
|
148 | 154 |
double start_time;
|
|
157 | 163 |
* @respect_owner: Whether to respect file owners (uid, gid; default = TRUE)
|
158 | 164 |
* @respect_name: Whether to respect file names (default = FALSE)
|
159 | 165 |
* @respect_time: Whether to respect file modification times (default = TRUE)
|
|
166 |
* @respect_xattrs: Whether to respect extended attributes (default = FALSE)
|
160 | 167 |
* @maximise: Chose the file with the highest link count as master
|
161 | 168 |
* @minimise: Chose the file with the lowest link count as master
|
162 | 169 |
* @dry_run: Specifies whether hardlink should not link files (default = FALSE)
|
|
171 | 178 |
unsigned int respect_owner:1;
|
172 | 179 |
unsigned int respect_name:1;
|
173 | 180 |
unsigned int respect_time:1;
|
|
181 |
unsigned int respect_xattrs:1;
|
174 | 182 |
unsigned int maximise:1;
|
175 | 183 |
unsigned int minimise:1;
|
176 | 184 |
unsigned int dry_run:1;
|
|
301 | 309 |
|
302 | 310 |
return diff;
|
303 | 311 |
}
|
|
312 |
|
304 | 313 |
/**
|
305 | 314 |
* compare_nodes_ino - Node comparison function
|
306 | 315 |
* @_a: The first node (a #struct file)
|
|
337 | 346 |
jlog(JLOG_SUMMARY, "Mode: %s", opts.dry_run ? "dry-run" : "real");
|
338 | 347 |
jlog(JLOG_SUMMARY, "Files: %zu", stats.files);
|
339 | 348 |
jlog(JLOG_SUMMARY, "Linked: %zu files", stats.linked);
|
|
349 |
#ifdef HAVE_XATTR
|
|
350 |
jlog(JLOG_SUMMARY, "Compared: %zu xattrs", stats.xattr_comparisons);
|
|
351 |
#endif
|
340 | 352 |
jlog(JLOG_SUMMARY, "Compared: %zu files", stats.comparisons);
|
341 | 353 |
jlog(JLOG_SUMMARY, "Saved: %s", format(stats.saved));
|
342 | 354 |
jlog(JLOG_SUMMARY, "Duration: %.2f seconds", gettime() - stats.start_time);
|
|
362 | 374 |
last_signal = 0;
|
363 | 375 |
return FALSE;
|
364 | 376 |
}
|
|
377 |
|
|
378 |
#ifdef HAVE_XATTR
|
|
379 |
|
|
380 |
/**
|
|
381 |
* malloc_or_die -- Wrapper for malloc()
|
|
382 |
*
|
|
383 |
* This does the same thing as malloc() except that it aborts if memory
|
|
384 |
* can't be allocated.
|
|
385 |
*/
|
|
386 |
static void *malloc_or_die(size_t size)
|
|
387 |
{
|
|
388 |
void *mem = malloc(size);
|
|
389 |
|
|
390 |
if (!mem) {
|
|
391 |
jlog(JLOG_SYSFAT, "Cannot allocate memory");
|
|
392 |
exit(1);
|
|
393 |
}
|
|
394 |
return mem;
|
|
395 |
}
|
|
396 |
|
|
397 |
/**
|
|
398 |
* llistxattr_or_die - Wrapper for llistxattr()
|
|
399 |
*
|
|
400 |
* This does the same thing as llistxattr() except that it aborts if any error
|
|
401 |
* other than "not supported" is detected.
|
|
402 |
*/
|
|
403 |
static ssize_t llistxattr_or_die(const char *path, char *list, size_t size)
|
|
404 |
{
|
|
405 |
ssize_t len = llistxattr(path, list, size);
|
|
406 |
|
|
407 |
if (len < 0 && errno != ENOTSUP) {
|
|
408 |
jlog(JLOG_SYSFAT, "Cannot get xattr names for %s", path);
|
|
409 |
exit(1);
|
|
410 |
}
|
|
411 |
return len;
|
|
412 |
}
|
|
413 |
|
|
414 |
/**
|
|
415 |
* lgetxattr_or_die - Wrapper for lgetxattr()
|
|
416 |
*
|
|
417 |
* This does the same thing as lgetxattr() except that it aborts upon error.
|
|
418 |
*/
|
|
419 |
static ssize_t lgetxattr_or_die(const char *path, const char *name, void *value,
|
|
420 |
size_t size)
|
|
421 |
{
|
|
422 |
ssize_t len = lgetxattr(path, name, value, size);
|
|
423 |
|
|
424 |
if (len < 0) {
|
|
425 |
jlog(JLOG_SYSFAT, "Cannot get xattr value of %s for %s", name, path);
|
|
426 |
exit(1);
|
|
427 |
}
|
|
428 |
return len;
|
|
429 |
}
|
|
430 |
|
|
431 |
/**
|
|
432 |
* get_xattr_name_count - Count the number of xattr names
|
|
433 |
* @names: a non-empty table of concatenated, null-terminated xattr names
|
|
434 |
* @len: the total length of the table
|
|
435 |
*
|
|
436 |
* @Returns the number of xattr names
|
|
437 |
*/
|
|
438 |
static int get_xattr_name_count(const char *const names, ssize_t len)
|
|
439 |
{
|
|
440 |
int count = 0;
|
|
441 |
const char *name;
|
|
442 |
|
|
443 |
for (name = names; name < (names + len); name += strlen(name) + 1)
|
|
444 |
count++;
|
|
445 |
|
|
446 |
return count;
|
|
447 |
}
|
|
448 |
|
|
449 |
/**
|
|
450 |
* cmp_xattr_name_ptrs - Compare two pointers to xattr names by comparing
|
|
451 |
* the names they point to.
|
|
452 |
*/
|
|
453 |
static int cmp_xattr_name_ptrs(const void *ptr1, const void *ptr2)
|
|
454 |
{
|
|
455 |
return strcmp(*(char *const *) ptr1, *(char *const *) ptr2);
|
|
456 |
}
|
|
457 |
|
|
458 |
/**
|
|
459 |
* get_sorted_xattr_name_table - Create a sorted table of xattr names.
|
|
460 |
* @names - table of concatentated, null-terminated xattr names
|
|
461 |
* @n - the number of names
|
|
462 |
*
|
|
463 |
* @Returns allocated table of pointers to the names, sorted alphabetically
|
|
464 |
*/
|
|
465 |
static const char **get_sorted_xattr_name_table(const char *names, int n)
|
|
466 |
{
|
|
467 |
const char **table = malloc_or_die(n * sizeof(char *));
|
|
468 |
int i;
|
|
469 |
|
|
470 |
for (i = 0; i < n; i++) {
|
|
471 |
table[i] = names;
|
|
472 |
names += strlen(names) + 1;
|
|
473 |
}
|
|
474 |
|
|
475 |
qsort(table, n, sizeof(char *), cmp_xattr_name_ptrs);
|
|
476 |
|
|
477 |
return table;
|
|
478 |
}
|
|
479 |
|
|
480 |
/**
|
|
481 |
* file_xattrs_equal - Compare the extended attributes of two files
|
|
482 |
* @a: The first file
|
|
483 |
* @b: The second file
|
|
484 |
*
|
|
485 |
* @Returns: %TRUE if and only if extended attributes are equal
|
|
486 |
*/
|
|
487 |
static hl_bool file_xattrs_equal(const struct file *a, const struct file *b)
|
|
488 |
{
|
|
489 |
ssize_t len_a;
|
|
490 |
ssize_t len_b;
|
|
491 |
char *names_a = NULL;
|
|
492 |
char *names_b = NULL;
|
|
493 |
int n_a;
|
|
494 |
int n_b;
|
|
495 |
const char **name_ptrs_a = NULL;
|
|
496 |
const char **name_ptrs_b = NULL;
|
|
497 |
void *value_a = NULL;
|
|
498 |
void *value_b = NULL;
|
|
499 |
hl_bool ret = FALSE;
|
|
500 |
int i;
|
|
501 |
|
|
502 |
assert(a->links != NULL);
|
|
503 |
assert(b->links != NULL);
|
|
504 |
|
|
505 |
jlog(JLOG_DEBUG1, "Comparing xattrs of %s to %s", a->links->path,
|
|
506 |
b->links->path);
|
|
507 |
|
|
508 |
stats.xattr_comparisons++;
|
|
509 |
|
|
510 |
len_a = llistxattr_or_die(a->links->path, NULL, 0);
|
|
511 |
len_b = llistxattr_or_die(b->links->path, NULL, 0);
|
|
512 |
|
|
513 |
if (len_a <= 0 && len_b <= 0)
|
|
514 |
return TRUE; // xattrs not supported or neither file has any
|
|
515 |
|
|
516 |
if (len_a != len_b)
|
|
517 |
return FALSE; // total lengths of xattr names differ
|
|
518 |
|
|
519 |
names_a = malloc_or_die(len_a);
|
|
520 |
names_b = malloc_or_die(len_b);
|
|
521 |
|
|
522 |
len_a = llistxattr_or_die(a->links->path, names_a, len_a);
|
|
523 |
len_b = llistxattr_or_die(b->links->path, names_b, len_b);
|
|
524 |
assert((len_a > 0) && (len_a == len_b));
|
|
525 |
|
|
526 |
n_a = get_xattr_name_count(names_a, len_a);
|
|
527 |
n_b = get_xattr_name_count(names_b, len_b);
|
|
528 |
|
|
529 |
if (n_a != n_b)
|
|
530 |
goto exit; // numbers of xattrs differ
|
|
531 |
|
|
532 |
name_ptrs_a = get_sorted_xattr_name_table(names_a, n_a);
|
|
533 |
name_ptrs_b = get_sorted_xattr_name_table(names_b, n_b);
|
|
534 |
|
|
535 |
// We now have two sorted tables of xattr names.
|
|
536 |
|
|
537 |
for (i = 0; i < n_a; i++) {
|
|
538 |
if (handle_interrupt())
|
|
539 |
goto exit; // user wants to quit
|
|
540 |
|
|
541 |
if (strcmp(name_ptrs_a[i], name_ptrs_b[i]) != 0)
|
|
542 |
goto exit; // names at same slot differ
|
|
543 |
|
|
544 |
len_a = lgetxattr_or_die(a->links->path, name_ptrs_a[i], NULL, 0);
|
|
545 |
len_b = lgetxattr_or_die(b->links->path, name_ptrs_b[i], NULL, 0);
|
|
546 |
|
|
547 |
if (len_a != len_b)
|
|
548 |
goto exit; // xattrs with same name, different value lengths
|
|
549 |
|
|
550 |
value_a = malloc_or_die(len_a);
|
|
551 |
value_b = malloc_or_die(len_b);
|
|
552 |
|
|
553 |
len_a = lgetxattr_or_die(a->links->path, name_ptrs_a[i],
|
|
554 |
value_a, len_a);
|
|
555 |
len_b = lgetxattr_or_die(b->links->path, name_ptrs_b[i],
|
|
556 |
value_b, len_b);
|
|
557 |
assert((len_a >= 0) && (len_a == len_b));
|
|
558 |
|
|
559 |
if (memcmp(value_a, value_b, len_a) != 0)
|
|
560 |
goto exit; // xattrs with same name, different values
|
|
561 |
|
|
562 |
free(value_a);
|
|
563 |
free(value_b);
|
|
564 |
value_a = NULL;
|
|
565 |
value_b = NULL;
|
|
566 |
}
|
|
567 |
|
|
568 |
ret = TRUE;
|
|
569 |
|
|
570 |
exit:
|
|
571 |
free(names_a);
|
|
572 |
free(names_b);
|
|
573 |
free(name_ptrs_a);
|
|
574 |
free(name_ptrs_b);
|
|
575 |
free(value_a);
|
|
576 |
free(value_b);
|
|
577 |
return ret;
|
|
578 |
}
|
|
579 |
#else
|
|
580 |
static hl_bool file_xattrs_equal(const struct file *a, const struct file *b)
|
|
581 |
{
|
|
582 |
return TRUE;
|
|
583 |
}
|
|
584 |
#endif
|
365 | 585 |
|
366 | 586 |
/**
|
367 | 587 |
* file_contents_equal - Compare contents of two files for equality
|
|
453 | 673 |
(!opts.respect_time || a->st.st_mtime == b->st.st_mtime) &&
|
454 | 674 |
(!opts.respect_name
|
455 | 675 |
|| strcmp(a->links->path + a->links->basename,
|
456 | |
b->links->path + b->links->basename) == 0)
|
457 | |
&& file_contents_equal(a, b));
|
|
676 |
b->links->path + b->links->basename) == 0) &&
|
|
677 |
(!opts.respect_xattrs || file_xattrs_equal(a, b)) &&
|
|
678 |
file_contents_equal(a, b));
|
458 | 679 |
}
|
459 | 680 |
|
460 | 681 |
/**
|
|
725 | 946 |
puts(" -o, --ignore-owner Ignore owner changes");
|
726 | 947 |
puts(" -t, --ignore-time Ignore timestamps. Will retain the newer timestamp,");
|
727 | 948 |
puts(" unless -m or -M is given");
|
|
949 |
#ifdef HAVE_XATTR
|
|
950 |
puts(" -X, --respect-xattrs Respect extended attributes");
|
|
951 |
#endif
|
728 | 952 |
puts(" -m, --maximize Maximize the hardlink count, remove the file with");
|
729 | 953 |
puts(" lowest hardlink cout");
|
730 | 954 |
puts(" -M, --minimize Reverse the meaning of -m");
|
|
790 | 1014 |
*/
|
791 | 1015 |
static int parse_options(int argc, char *argv[])
|
792 | 1016 |
{
|
793 | |
static const char optstr[] = "VhvnfpotcmMx:i:";
|
|
1017 |
static const char optstr[] = "VhvnfpotXcmMx:i:";
|
794 | 1018 |
#ifdef HAVE_GETOPT_LONG
|
795 | 1019 |
static const struct option long_options[] = {
|
796 | 1020 |
{"version", no_argument, NULL, 'V'},
|
|
801 | 1025 |
{"ignore-mode", no_argument, NULL, 'p'},
|
802 | 1026 |
{"ignore-owner", no_argument, NULL, 'o'},
|
803 | 1027 |
{"ignore-time", no_argument, NULL, 't'},
|
|
1028 |
{"respect-xattrs", no_argument, NULL, 'X'},
|
804 | 1029 |
{"maximize", no_argument, NULL, 'm'},
|
805 | 1030 |
{"minimize", no_argument, NULL, 'M'},
|
806 | 1031 |
{"exclude", required_argument, NULL, 'x'},
|
|
814 | 1039 |
opts.respect_mode = TRUE;
|
815 | 1040 |
opts.respect_owner = TRUE;
|
816 | 1041 |
opts.respect_time = TRUE;
|
|
1042 |
opts.respect_xattrs = FALSE;
|
817 | 1043 |
|
818 | 1044 |
while ((opt = getopt_long(argc, argv, optstr, long_options, NULL)) != -1) {
|
819 | 1045 |
switch (opt) {
|
|
825 | 1051 |
break;
|
826 | 1052 |
case 't':
|
827 | 1053 |
opts.respect_time = FALSE;
|
|
1054 |
break;
|
|
1055 |
case 'X':
|
|
1056 |
opts.respect_xattrs = TRUE;
|
828 | 1057 |
break;
|
829 | 1058 |
case 'm':
|
830 | 1059 |
opts.maximise = TRUE;
|
|
843 | 1072 |
opts.respect_name = FALSE;
|
844 | 1073 |
opts.respect_owner = FALSE;
|
845 | 1074 |
opts.respect_time = FALSE;
|
|
1075 |
opts.respect_xattrs = FALSE;
|
846 | 1076 |
break;
|
847 | 1077 |
case 'n':
|
848 | 1078 |
opts.dry_run = 1;
|