]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
hardlink: implement --exclude-subtree
authorKarel Zak <kzak@redhat.com>
Thu, 31 Oct 2024 09:51:11 +0000 (10:51 +0100)
committerKarel Zak <kzak@redhat.com>
Thu, 31 Oct 2024 10:08:42 +0000 (11:08 +0100)
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 <kzak@redhat.com>
bash-completion/hardlink
misc-utils/hardlink.1.adoc
misc-utils/hardlink.c

index a7f197d48a43bb3f0f1f68fb8cf9b67a26f825ee..22e5b5fa8755424fc1748c8b8b515d58a360aba4 100644 (file)
@@ -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
index d11045941431f708f3c65b9516c0b18b9a47a345..1a353f06aba25e6b3e9a7c61b5370ef9891bbb95 100644 (file)
@@ -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.
index e4b44c6c6d504000b6d4f1cea20f1634bb63b052..d789fa213069524ecf6d9ed992f6225b7be1261b 100644 (file)
 # 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 <regex>      regular expression to exclude files\n"), out);
+#ifdef USE_SKIP_SUBTREE
+       fputs(_("     --exclude-subtree <regex>  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);