Whamcloud - gitweb
LU-7495 utils: add --links option for lfs find 86/50886/4
authorThomas Bertschinger <bertschinger@lanl.gov>
Sun, 7 May 2023 17:34:42 +0000 (13:34 -0400)
committerOleg Drokin <green@whamcloud.com>
Fri, 19 May 2023 07:10:40 +0000 (07:10 +0000)
This adds a "--links" option for lfs find to filter files and
directories by the number of hard links. It also adds a printf format
'%n' to print the number of links for a file.

This commit also fixes '-l' as a short option for '--lazy' which was
added in 11aa7f8704c490b011f60f234c3ac9929ce76948 but the short option
did not work.

Signed-off-by: Thomas Bertschinger <bertschinger@lanl.gov>
Change-Id: I5d15bc290df8e8a08402f8d5cfa0a7139791b0a4
Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/50886
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
Reviewed-by: Anjus George <georgea@ornl.gov>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
lustre/doc/lfs-find.1
lustre/include/lustre/lustreapi.h
lustre/tests/sanity.sh
lustre/utils/lfs.c
lustre/utils/liblustreapi.c

index a021dcb..d78e6af 100644 (file)
@@ -16,9 +16,9 @@ lfs-find \- Lustre client utility to list files with specific attributes
 [[\fB!\fR] \fB--gid\fR|\fB-g\fR|\fB--group\fR|\fB-G\fR \fIGNAME\fR|\fIGID\fR>]
       [\fB--help\fR|\fB-h\fR]
 [[\fB!\fR] \fB--layout\fR|\fB-L mdt\fR,\fBraid0\fR,\fBreleased\fR]
-[\fB--lazy\fR]
-      [\fB--maxdepth\fR|\fB-D\fI n\fR]
-[[\fB!\fR] \fB--mdt\fR|\fB--mdt-index\fR|\fB-m\fR \fIUUID\fR|\fIINDEX\fR,...]
+[\fB--lazy|-l\fR]
+      [[\fB!\fR] \fB--links\fR [\fB+-\fR]\fIn\fR] [\fB--maxdepth\fR|\fB-D\fI n\fR]
+      [[\fB!\fR] \fB--mdt\fR|\fB--mdt-index\fR|\fB-m\fR \fIUUID\fR|\fIINDEX\fR,...]
       [[\fB!\fR] \fB--mdt-count\fR|\fB-T\fR [\fB+-\fR]\fIn\fR]
 [[\fB!\fR] \fB--mdt-hash\fR|\fB-H \fR<[^]\fIHASHFLAG\fR,[^]\fIHASHTYPE\fR,...>]
       [[\fB!\fR] \fB--mirror-count|\fB-N\fR [\fB+-\fR]\fIn\fR]
@@ -145,9 +145,12 @@ HSM-archived files that are not resident in the filesystem.
 Files that have the first data component on an MDT.
 .RE
 .TP
-.BR --lazy
+.BR --lazy|-l
 Use file size and blocks from MDT, if available, to avoid extra RPCs.
 .TP
+.BR --links
+File has \fIn\fR links.
+.TP
 .BR --maxdepth
 Limits find to decend at most \fIn\fR levels of directory tree.
 .TP
@@ -332,6 +335,9 @@ File\'s numeric group ID.
 .B %m
 File permission bits (in octal).
 .TP
+.B %n
+Number of hard links to file.
+.TP
 .B %p
 File's name.
 .TP
index 1e486fd..0f0a23f 100644 (file)
@@ -246,6 +246,10 @@ enum lfs_find_perm {
        LFS_FIND_PERM_ALL   =  1,
 };
 
+/*
+ * new fields should be added to the end of this struct (unless filling a hole
+ * such as in a bitfield), to preserve the ABI
+ */
 struct find_param {
        unsigned int             fp_max_depth;
        dev_t                    fp_dev;
@@ -275,7 +279,7 @@ struct find_param {
                                 fp_blocks_sign:2,
                                 fp_ext_size_sign:2,
                                 fp_perm_sign:2,
-                                fp_unused2_sign:2, /* Once used we must add  */
+                                fp_nlink_sign:2,   /* Once used we must add  */
                                 fp_unused3_sign:2, /* a separate flag field  */
                                 fp_unused4_sign:2; /* at end of the struct.  */
        unsigned long long       fp_size;
@@ -342,7 +346,7 @@ struct find_param {
                                 fp_exclude_btime:1,
                                 fp_exclude_perm:1,
                                 fp_stop_on_error:1, /* stop iteration on error */
-                                fp_unused_bit5:1, /* are used we need to add */
+                                fp_exclude_nlink:1, /* Once used, we must add*/
                                 fp_unused_bit6:1, /* a separate flag field at*/
                                 fp_unused_bit7:1; /* the end of the struct.  */
 
@@ -419,6 +423,7 @@ struct find_param {
        unsigned int             fp_hash_exflags;
        /* Print all information (lfs find only) */
        char                     *fp_format_printf_str;
+       nlink_t                  fp_nlink;
 };
 
 int llapi_ostlist(char *path, struct find_param *param);
index 915af6d..83383de 100755 (executable)
@@ -8682,8 +8682,8 @@ test_56ea() { #LU-10378
        touch $path/$tfile || error "touch $path/$tfile failed"
 
        # Compare basic file attributes from -printf and stat
-       local attr_printf=$($LFS find $path/$tfile -printf "%A@ %T@ %C@ %U %G")
-       local attr_stat=$(stat -c "%X %Y %Z %u %g" $path/$tfile)
+       local attr_printf=$($LFS find $path/$tfile -printf "%A@ %T@ %C@ %U %G %n")
+       local attr_stat=$(stat -c "%X %Y %Z %u %g %h" $path/$tfile)
 
        [[ "${attr_printf}" == "${attr_stat}" ]] ||
                error "Attrs from lfs find and stat don't match"
@@ -8759,6 +8759,53 @@ test_56ec() {
 }
 run_test 56ec "check lfs getstripe,setstripe --hex --yaml"
 
+test_56eda() {
+       local dir=$DIR/$tdir
+       local subdir=$dir/subdir
+       local file1=$dir/$tfile
+       local file2=$dir/$tfile\2
+       local link=$dir/$tfile-link
+       local nfiles
+
+       test_mkdir -p $dir
+       $LFS setdirstripe -c1 $subdir
+       touch $file1
+       touch $file2
+       ln $file2 $link
+
+       nfiles=$($LFS find --links 1 $dir | wc -l)
+       (( $nfiles == 1 )) ||
+               error "lfs find --links expected 1 file, got $nfiles"
+
+       nfiles=$($LFS find --type f --links 2 $dir | wc -l)
+       (( $nfiles == 2 )) ||
+               error "lfs find --links expected 2 files, got $nfiles"
+
+       nfiles=$($LFS find --type d --links 2 $dir | wc -l)
+       (( $nfiles == 1 )) ||
+               error "lfs find --links expected 1 directory, got $nfiles"
+}
+run_test 56eda "check lfs find --links"
+
+test_56edb() {
+       [[ $MDSCOUNT -lt 2 ]] && skip_env "needs >= 2 MDTs"
+
+       local dir=$DIR/$tdir
+       local stripedir=$dir/stripedir
+       local nfiles
+
+       test_mkdir -p $dir
+
+       $LFS setdirstripe -c2 $stripedir
+
+       $LFS getdirstripe $stripedir
+
+       nfiles=$($LFS find --type d --links 2 $stripedir | wc -l)
+       (( $nfiles == 1 )) ||
+               error "lfs find --links expected 1 directory, got $nfiles"
+}
+run_test 56edb "check lfs find --links for directory striped on multiple MDTs"
+
 test_57a() {
        [ $PARALLEL == "yes" ] && skip "skip parallel run"
        # note test will not do anything if MDS is not local
index 2152d74..37d2e36 100644 (file)
@@ -426,7 +426,7 @@ command_t cmdlist[] = {
         "     [[!] --extension-size|--ext-size|-z [+-]N[kMGT]]\n"
         "     [[!] --foreign[=<foreign_type>]]\n"
         "     [[!] --gid|-g|--group|-G <gid>|<gname>] [--help|-h]\n"
-        "     [[!] --layout|-L released,raid0,mdt] [--lazy]\n"
+        "     [[!] --layout|-L released,raid0,mdt] [--lazy|-l] [[!] --links [+-]n]\n"
         "     [--maxdepth|-D N] [[!] --mdt-count|-T [+-]<stripes>]\n"
         "     [[!] --mdt-hash|-H <[^][blm],[^]fnv_1a_64,all_char,crush,...>\n"
         "     [[!] --mdt-index|--mdt|-m <uuid|index,...>]\n"
@@ -3528,7 +3528,8 @@ enum {
        LFS_NO_FOLLOW_OPT,
        LFS_HEX_IDX_OPT,
        LFS_STATS_OPT,
-       LFS_STATS_INTERVAL_OPT
+       LFS_STATS_INTERVAL_OPT,
+       LFS_LINKS_OPT
 };
 
 #ifndef LCME_USER_MIRROR_FLAGS
@@ -5136,6 +5137,8 @@ static int lfs_find(int argc, char **argv)
 /* getstripe { .val = 'I', .name = "comp-id",  .has_arg = required_argument }*/
        { .val = 'l',   .name = "lazy",         .has_arg = no_argument },
        { .val = 'L',   .name = "layout",       .has_arg = required_argument },
+       { .val = LFS_LINKS_OPT,
+                       .name = "links",        .has_arg = required_argument },
        { .val = 'm',   .name = "mdt",          .has_arg = required_argument },
        { .val = 'm',   .name = "mdt-index",    .has_arg = required_argument },
        { .val = 'm',   .name = "mdt_index",    .has_arg = required_argument },
@@ -5186,7 +5189,7 @@ static int lfs_find(int argc, char **argv)
 
        /* when getopt_long_only() hits '!' it returns 1, puts "!" in optarg */
        while ((c = getopt_long_only(argc, argv,
-               "-0A:b:B:c:C:D:E:g:G:hH:i:L:m:M:n:N:O:Ppqrs:S:t:T:u:U:z:",
+               "-0A:b:B:c:C:D:E:g:G:hH:i:lL:m:M:n:N:O:Ppqrs:S:t:T:u:U:z:",
                long_opts, &optidx)) >= 0) {
                xtime = NULL;
                xsign = NULL;
@@ -5633,6 +5636,24 @@ static int lfs_find(int argc, char **argv)
                        param.fp_exclude_layout = !!neg_opt;
                        param.fp_check_layout = 1;
                        break;
+               case LFS_LINKS_OPT:
+                       if (optarg[0] == '+') {
+                               param.fp_nlink_sign = -1;
+                               optarg++;
+                       } else if (optarg[0] == '-') {
+                               param.fp_nlink_sign =  1;
+                               optarg++;
+                       }
+                       errno = 0;
+                       param.fp_nlink = strtoul(optarg, &endptr, 0);
+                       if (errno != 0 || *endptr != '\0' || !param.fp_nlink) {
+                               fprintf(stderr, "error: bad link count '%s'\n",
+                                       optarg);
+                               ret = -1;
+                               goto err;
+                       }
+                       param.fp_exclude_nlink = !!neg_opt;
+                       break;
                case 'u':
                case 'U':
                        rc = name2uid(&param.fp_uid, optarg);
index 7a3666b..8cea04e 100644 (file)
@@ -5081,6 +5081,10 @@ int printf_format_directive(char *seq, char *buffer, size_t size, int *wrote,
        case 'm':       /* file mode in octal */
                *wrote = snprintf(buffer, size, "%#o", (mode & (~S_IFMT)));
                break;
+       case 'n':       /* number of links */
+               *wrote = snprintf(buffer, size, "%u",
+                                 param->fp_lmd->lmd_stx.stx_nlink);
+               break;
        case 'p':       /* Path name of file */
                *wrote = snprintf(buffer, size, "%s", path);
                break;
@@ -5301,6 +5305,7 @@ static int cb_find_init(char *path, int p, int *dp,
            find_check_lmm_info(param) ||
            param->fp_check_mdt_count || param->fp_hash_type ||
            param->fp_check_hash_flag || param->fp_perm_sign ||
+           param->fp_nlink ||
            gather_all)
                decision = 0;
 
@@ -5311,7 +5316,12 @@ static int cb_find_init(char *path, int p, int *dp,
                if (d != -1 &&
                    (param->fp_check_mdt_count || param->fp_hash_type ||
                     param->fp_check_hash_flag || param->fp_check_foreign ||
-                    gather_all)) {
+                    /*
+                     * cb_get_dirstripe is needed when checking nlink because
+                     * nlink is handled differently for multi-stripe directory
+                     * vs. single-stripe directory
+                     */
+                    param->fp_nlink || gather_all)) {
                        param->fp_get_lmv = 1;
                        ret = cb_get_dirstripe(path, &d, param);
                        if (ret != 0) {
@@ -5671,6 +5681,18 @@ obd_matches:
                decision = 0;
 
        /*
+        * When checking nlink, stat(2) is needed for multi-striped directories
+        * because the nlink value retrieved from the MDS above comes from
+        * the number of stripes for the dir.
+        * The posix stat call below fills in the correct number of links.
+        * Single-stripe directories and regular files already have the
+        * correct nlink value.
+        */
+       if (param->fp_nlink && S_ISDIR(lmd->lmd_stx.stx_mode) &&
+           (param->fp_lmv_md->lum_stripe_count != 0))
+               decision = 0;
+
+       /*
         * If file still fits the request, ask ost for updated info.
         * The regular stat is almost of the same speed as some new
         * 'glimpse-size-ioctl'.
@@ -5728,6 +5750,14 @@ obd_matches:
                }
        }
 
+       if (param->fp_nlink) {
+               decision = find_value_cmp(lmd->lmd_stx.stx_nlink,
+                                         param->fp_nlink, param->fp_nlink_sign,
+                                         param->fp_exclude_nlink, 1, 0);
+               if (decision == -1)
+                       goto decided;
+       }
+
        if (param->fp_check_size) {
                decision = find_value_cmp(lmd->lmd_stx.stx_size,
                                          param->fp_size,
@@ -5979,7 +6009,7 @@ int validate_printf_esc(char *c)
  */
 int validate_printf_fmt(char *c)
 {
-       char *valid_fmt_single = "abcGkmpstUwy%";
+       char *valid_fmt_single = "abcGkmnpstUwy%";
        char *valid_fmt_double = "ACTW";
        char *valid_fmt_lustre = "cFhioPpS";
        char curr = *c, next;