From d7838d329316f641e3e0bb401c8415c605caac26 Mon Sep 17 00:00:00 2001 From: Thomas Bertschinger Date: Sun, 7 May 2023 13:34:42 -0400 Subject: [PATCH] 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. Lustre-change: https://review.whamcloud.com/50886 Lustre-commit: f759d6386d5d0edb95d683d97ca8d84c80080c1c Signed-off-by: Thomas Bertschinger Change-Id: I5d15bc290df8e8a08402f8d5cfa0a7139791b0a4 Reviewed-by: Andreas Dilger Reviewed-by: Anjus George Reviewed-on: https://review.whamcloud.com/c/ex/lustre-release/+/51327 Tested-by: jenkins Tested-by: Maloo --- lustre/doc/lfs-find.1 | 14 ++++++++--- lustre/include/lustre/lustreapi.h | 9 +++++-- lustre/tests/sanity.sh | 51 +++++++++++++++++++++++++++++++++++++-- lustre/utils/lfs.c | 27 ++++++++++++++++++--- lustre/utils/liblustreapi.c | 34 ++++++++++++++++++++++++-- 5 files changed, 122 insertions(+), 13 deletions(-) diff --git a/lustre/doc/lfs-find.1 b/lustre/doc/lfs-find.1 index 9ad022e..6a23616c 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!\fR] \fB--layout\fR|\fB-L mdt\fR,\fBraid0\fR,\fBcompress\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] @@ -148,9 +148,12 @@ Files that have the first data component on an MDT. Files that contains compress component. .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 @@ -325,6 +328,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 8f8ddd6..51e0124 100644 --- a/lustre/include/lustre/lustreapi.h +++ b/lustre/include/lustre/lustreapi.h @@ -234,6 +234,10 @@ enum { NEWERXY_MAX, }; +/* + * 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; @@ -262,7 +266,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; @@ -329,7 +333,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. */ @@ -404,6 +408,7 @@ struct find_param { unsigned int fp_hash_exflags; /* Print all information (lfs find only) */ char *fp_format_printf_str; + nlink_t fp_nlink; unsigned int fp_compr_type; unsigned int fp_compr_lvl; diff --git a/lustre/tests/sanity.sh b/lustre/tests/sanity.sh index 32d9924..b851e2c 100755 --- a/lustre/tests/sanity.sh +++ b/lustre/tests/sanity.sh @@ -7967,8 +7967,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" @@ -7992,6 +7992,53 @@ test_56ea() { #LU-10378 } run_test 56ea "test lfs find -printf option" +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 efd9421..f6a9f35 100644 --- a/lustre/utils/lfs.c +++ b/lustre/utils/lfs.c @@ -545,14 +545,14 @@ command_t cmdlist[] = { " [[!] --extension-size|--ext-size|-z [+-]N[kMGT]]\n" " [[!] --foreign[=]]\n" " [[!] --gid|-g|--group|-G |]\n" - " [[!] --layout|-L released,raid0,mdt,compress]\n" + " [[!] --layout|-L released,raid0,mdt,compress] [--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|-m ]\n" " [[!] --mirror-count|-N [+-]]\n" " [[!] --mirror-state <[^]state>]\n" " [[!] --name|-n ] [[!] --newer[XY] ]\n" - " [[!] --ost|-O ] [[!] --perm [/-]mode]\n" + " [[!] --ost|-O ]\n" " [[!] --pool ] [--print|-P] [--print0|-0] [--printf ]\n" " [[!] --projid ] [[!] --size|-s [+-]N[bkMGTPE]]\n" " [[!] --stripe-count|-c [+-]]\n" @@ -3683,6 +3683,7 @@ enum { LFS_PRINTF_OPT, LFS_STATS_OPT, LFS_STATS_INTERVAL_OPT, + LFS_LINKS_OPT, LFS_COMPRESS_TYPE_OPT, LFS_COMPRESS_LEVEL_OPT, LFS_COMPRESS_CHUNK_OPT, @@ -5108,6 +5109,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 }, @@ -5156,7 +5159,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:H:i:L:m:M:n:N:O:Ppqrs:S:t:T:u:U:z:", + "-0A:b:B:c:C:D:E:g:G:H:i:lL:m:M:n:N:O:Ppqrs:S:t:T:u:U:z:", long_opts, &optidx)) >= 0) { xtime = NULL; xsign = NULL; @@ -5638,6 +5641,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 9118a28..8dc5dbb 100644 --- a/lustre/utils/liblustreapi.c +++ b/lustre/utils/liblustreapi.c @@ -5150,6 +5150,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; @@ -5341,6 +5345,7 @@ static int cb_find_init(char *path, DIR *parent, DIR **dirp, find_check_lmm_info(param) || param->fp_check_mdt_count || param->fp_hash_type || param->fp_check_hash_flag || + param->fp_nlink || gather_all) decision = 0; @@ -5351,7 +5356,12 @@ static int cb_find_init(char *path, DIR *parent, DIR **dirp, if (dir && (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, dir, param); if (ret != 0) { @@ -5711,6 +5721,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'. @@ -5768,6 +5790,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, @@ -6021,7 +6051,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; -- 1.8.3.1