From: Thomas Bertschinger Date: Sun, 7 May 2023 17:34:42 +0000 (-0400) Subject: LU-7495 utils: add --links option for lfs find X-Git-Tag: 2.15.56~55 X-Git-Url: https://git.whamcloud.com/gitweb?a=commitdiff_plain;h=f759d6386d5d0edb95d683d97ca8d84c80080c1c;p=fs%2Flustre-release.git LU-7495 utils: add --links option for lfs find 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 Change-Id: I5d15bc290df8e8a08402f8d5cfa0a7139791b0a4 Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/50886 Tested-by: jenkins Tested-by: Maloo Reviewed-by: Andreas Dilger Reviewed-by: Anjus George Reviewed-by: Oleg Drokin --- diff --git a/lustre/doc/lfs-find.1 b/lustre/doc/lfs-find.1 index a021dcb..d78e6af 100644 --- a/lustre/doc/lfs-find.1 +++ b/lustre/doc/lfs-find.1 @@ -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 diff --git a/lustre/include/lustre/lustreapi.h b/lustre/include/lustre/lustreapi.h index 1e486fd..0f0a23f 100644 --- a/lustre/include/lustre/lustreapi.h +++ b/lustre/include/lustre/lustreapi.h @@ -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); diff --git a/lustre/tests/sanity.sh b/lustre/tests/sanity.sh index 915af6d..83383de 100755 --- a/lustre/tests/sanity.sh +++ b/lustre/tests/sanity.sh @@ -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 diff --git a/lustre/utils/lfs.c b/lustre/utils/lfs.c index 2152d74..37d2e36 100644 --- a/lustre/utils/lfs.c +++ b/lustre/utils/lfs.c @@ -426,7 +426,7 @@ command_t cmdlist[] = { " [[!] --extension-size|--ext-size|-z [+-]N[kMGT]]\n" " [[!] --foreign[=]]\n" " [[!] --gid|-g|--group|-G |] [--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 [+-]]\n" " [[!] --mdt-hash|-H <[^][blm],[^]fnv_1a_64,all_char,crush,...>\n" " [[!] --mdt-index|--mdt|-m ]\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(¶m.fp_uid, optarg); diff --git a/lustre/utils/liblustreapi.c b/lustre/utils/liblustreapi.c index 7a3666b..8cea04e 100644 --- a/lustre/utils/liblustreapi.c +++ b/lustre/utils/liblustreapi.c @@ -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;