From: Karel Zak Date: Thu, 31 Oct 2024 09:51:11 +0000 (+0100) Subject: hardlink: implement --exclude-subtree X-Git-Tag: v2.42-start~163^2~2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3001da7fa9458c3cc0bdbd26047ed43e618e1071;p=thirdparty%2Futil-linux.git hardlink: implement --exclude-subtree Now, it is possible to exclude files by their names, but it does not allow for ignoring entire subtrees of the scanned hierarchy. The new option only applies to directory names and forces the file-tree-walk to skip the directory and all of its subdirectories. This is based on FTW_SKIP_SUBTREE, which was originally only available in glibc (since 2004). Therefore, the code is #ifdef-ed to make it portable to other libc versions. Addresses: https://github.com/util-linux/util-linux/discussions/3244 Signed-off-by: Karel Zak --- diff --git a/bash-completion/hardlink b/bash-completion/hardlink index a7f197d48..22e5b5fa8 100644 --- a/bash-completion/hardlink +++ b/bash-completion/hardlink @@ -9,6 +9,10 @@ _hardlink_module() COMPREPLY=( $(compgen -W "regex" -- $cur) ) return 0 ;; + '--exclude-subtree') + COMPREPLY=( $(compgen -W "regex" -- $cur) ) + return 0 + ;; '-i'|'--include') COMPREPLY=( $(compgen -W "regex" -- $cur) ) return 0 diff --git a/misc-utils/hardlink.1.adoc b/misc-utils/hardlink.1.adoc index d11045941..1a353f06a 100644 --- a/misc-utils/hardlink.1.adoc +++ b/misc-utils/hardlink.1.adoc @@ -107,7 +107,10 @@ Link and compare files even if their time of modification is different. This is Verbose output, explain to the user what is being done. If specified once, every hardlinked file is displayed. If specified twice, it also shows every comparison. *-x*, *--exclude* _regex_:: -A regular expression which excludes files from being compared and linked. +A regular expression that excludes files from being compared and linked. This option can be used multiple times. + +*--exclude-subtree* _regex_:: +A regular expression that excludes entire directories from being compared and linked. This option can also be used multiple times. *-X*, *--respect-xattrs*:: Only try to link files with the same extended attributes. diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index e4b44c6c6..d789fa213 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -46,6 +46,10 @@ # endif #endif +#if defined(FTW_ACTIONRETVAL) && defined(FTW_SKIP_SUBTREE) +# define USE_SKIP_SUBTREE 1 +#endif + #include "nls.h" #include "c.h" #include "xalloc.h" @@ -158,6 +162,7 @@ struct hdl_regex { * struct options - Processed command-line options * @include: A linked list of regular expressions for the --include option * @exclude: A linked list of regular expressions for the --exclude option + * @exclude_subtree: A linked list of regular expressions for the --exclude-subtree options * @verbosity: The verbosity. Should be one of #enum log_level * @respect_mode: Whether to respect file modes (default = TRUE) * @respect_owner: Whether to respect file owners (uid, gid; default = TRUE) @@ -175,6 +180,7 @@ struct hdl_regex { static struct options { struct hdl_regex *include; struct hdl_regex *exclude; + struct hdl_regex *exclude_subtree; const char *method; signed int verbosity; @@ -847,6 +853,12 @@ static int inserter(const char *fpath, const struct stat *sb, return 1; if (typeflag == FTW_DNR || typeflag == FTW_NS) warn(_("cannot read %s"), fpath); +#ifdef USE_SKIP_SUBTREE + if (opts.exclude_subtree + && typeflag == FTW_D + && match_any_regex(opts.exclude_subtree, fpath)) + return FTW_SKIP_SUBTREE; +#endif if (typeflag != FTW_F || !S_ISREG(sb->st_mode)) return 0; @@ -1196,6 +1208,9 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" -t, --ignore-time ignore timestamps (when testing for equality)\n"), out); fputs(_(" -v, --verbose verbose output (repeat for more verbosity)\n"), out); fputs(_(" -x, --exclude regular expression to exclude files\n"), out); +#ifdef USE_SKIP_SUBTREE + fputs(_(" --exclude-subtree regular expression to exclude directories\n"), out); +#endif #ifdef USE_XATTR fputs(_(" -X, --respect-xattrs respect extended attributes\n"), out); #endif @@ -1221,7 +1236,8 @@ static int parse_options(int argc, char *argv[]) { enum { OPT_REFLINK = CHAR_MAX + 1, - OPT_SKIP_RELINKS + OPT_SKIP_RELINKS, + OPT_EXCLUDE_SUBTREE }; static const char optstr[] = "VhvndfpotXcmMFOx:y:i:r:S:s:b:q"; static const struct option long_options[] = { @@ -1241,6 +1257,9 @@ static int parse_options(int argc, char *argv[]) {"keep-oldest", no_argument, NULL, 'O'}, {"exclude", required_argument, NULL, 'x'}, {"include", required_argument, NULL, 'i'}, +#ifdef USE_SKIP_SUBTREE + {"exclude-subtree", required_argument, NULL, OPT_EXCLUDE_SUBTREE}, +#endif {"method", required_argument, NULL, 'y' }, {"minimum-size", required_argument, NULL, 's'}, {"maximum-size", required_argument, NULL, 'S'}, @@ -1311,6 +1330,11 @@ static int parse_options(int argc, char *argv[]) case 'x': register_regex(&opts.exclude, optarg); break; +#ifdef USE_SKIP_SUBTREE + case OPT_EXCLUDE_SUBTREE: + register_regex(&opts.exclude_subtree, optarg); + break; +#endif case 'y': opts.method = optarg; break; @@ -1359,6 +1383,9 @@ static int parse_options(int argc, char *argv[]) #endif #ifdef USE_FILEEQ_CRYPTOAPI "cryptoapi", +#endif +#ifdef USE_SKIP_SUBTREE + "ftw_skip_subtree", #endif NULL }; @@ -1407,6 +1434,7 @@ int main(int argc, char *argv[]) { struct sigaction sa; int rc; + int ftw_flags; sa.sa_handler = sighandler; sa.sa_flags = SA_RESTART; @@ -1450,6 +1478,12 @@ int main(int argc, char *argv[]) stats.started = TRUE; + ftw_flags = FTW_PHYS; +#ifdef USE_SKIP_SUBTREE + if (opts.exclude_subtree) + ftw_flags |= FTW_ACTIONRETVAL; +#endif + jlog(JLOG_VERBOSE2, _("Scanning [device/inode/links]:")); for (; optind < argc; optind++) { char *path = realpath(argv[optind], NULL); @@ -1463,7 +1497,7 @@ int main(int argc, char *argv[]) if (opts.prio_trees) ++curr_tree; - if (nftw(path, inserter, 20, FTW_PHYS) == -1) + if (nftw(path, inserter, 20, ftw_flags) == -1) warn(_("cannot process %s"), path); free(path);