From 06588e4a22b0ff037eafa1eee5e22521b1626904 Mon Sep 17 00:00:00 2001 From: Courrier Guillaume Date: Thu, 29 Apr 2021 11:35:01 +0200 Subject: [PATCH] LU-11188 lfs: add "--perm" option to "lfs find" Add support for "--perm" option to "lfs find". The option supports both octal and symbolic representation and follows the POSIX standard. As for GNU find, it supports '-' and '/' modifiers before the permission. Signed-off-by: Guillaume Courrier Change-Id: I8e1292421986c3a4bde686f3c7dc7bfcb679cabc Reviewed-on: https://review.whamcloud.com/43715 Tested-by: jenkins Reviewed-by: Andreas Dilger Tested-by: Maloo Reviewed-by: Olaf Faaland-LLNL --- lustre/doc/lfs-find.1 | 11 ++ lustre/include/lustre/lustreapi.h | 21 ++-- lustre/tests/sanity.sh | 88 +++++++++++++ lustre/utils/lfs.c | 256 +++++++++++++++++++++++++++++++++++++- lustre/utils/liblustreapi.c | 40 +++++- 5 files changed, 405 insertions(+), 11 deletions(-) diff --git a/lustre/doc/lfs-find.1 b/lustre/doc/lfs-find.1 index 7598f26..4360dd7 100644 --- a/lustre/doc/lfs-find.1 +++ b/lustre/doc/lfs-find.1 @@ -26,6 +26,7 @@ lfs-find \- Lustre client utility to list files with specific attributes [[\fB!\fR] \fB--name\fR|\fB-n <\fIpattern\fR>] [[\fB!\fR] \fB--newer\fR[\fBXY\fR] <\fIreference\fR>] [[\fB!\fR] \fB--ost\fR|\fB-O\fR <\fIindex\fR,...>] + [[\fB!\fR] \fB--perm\fR [\fB/-\fR]<\fImode\fR> ] [[\fB!\fR] \fB--pool\fR <\fIpool\fR>] [\fB--print\fR|\fB-P\fR] [\fB--print0\fR|\fB-0\fR] @@ -254,6 +255,16 @@ filesystem only once when migrating objects off multiple OSTs for evacuation and replacement using .BR lfs-migrate (1). .TP +.BR "--perm \fImode\fR" +File's permission are exactly \fImode\fR (octal or symbolic). +.TP +.BR "--perm /\fImode\fR" +All of the permission bits \fImode\fR are set for the file. +.TP +.BR "--perm -\fImode\fR" +Any of the permission bits \fImode\fR are set for the file. If no permission +bits in \fImode\fR are set, this test matches any file. +.TP .BR --pool Layout was created with the specified .I pool diff --git a/lustre/include/lustre/lustreapi.h b/lustre/include/lustre/lustreapi.h index af0587f..abd42bd 100644 --- a/lustre/include/lustre/lustreapi.h +++ b/lustre/include/lustre/lustreapi.h @@ -221,12 +221,20 @@ enum { NEWERXY_MAX, }; +enum lfs_find_perm { + LFS_FIND_PERM_EXACT = -2, + LFS_FIND_PERM_ANY = -1, + LFS_FIND_PERM_OFF = 0, + LFS_FIND_PERM_ALL = 1, +}; + struct find_param { unsigned int fp_max_depth; dev_t fp_dev; mode_t fp_type; /* S_IFIFO,... */ uid_t fp_uid; gid_t fp_gid; + mode_t fp_perm; time_t fp_atime; time_t fp_mtime; time_t fp_ctime; @@ -248,7 +256,7 @@ struct find_param { fp_mdt_count_sign:2, fp_blocks_sign:2, fp_ext_size_sign:2, - fp_unused1_sign:2, /* Fields available to use*/ + fp_perm_sign:2, fp_unused2_sign:2, /* Once used we must add */ fp_unused3_sign:2, /* a separate flag field */ fp_unused4_sign:2; /* at end of the struct. */ @@ -314,12 +322,11 @@ struct find_param { fp_lazy:1, fp_newerxy:1, fp_exclude_btime:1, - fp_unused_bit3:1, /* All of these unused bit */ - fp_unused_bit4:1, /* fields available to use.*/ - fp_unused_bit5:1, /* Once all unused fields */ - fp_unused_bit6:1, /* are used we need to add */ - fp_unused_bit7:1; /* a separate flag field at*/ - /* the end of the struct. */ + fp_exclude_perm:1, + fp_unused_bit4:1, /* Once all unused fields */ + fp_unused_bit5:1, /* are used we need to add */ + fp_unused_bit6:1, /* a separate flag field at*/ + fp_unused_bit7:1; /* the end of the struct. */ enum llapi_layout_verbose fp_verbose; int fp_quiet; diff --git a/lustre/tests/sanity.sh b/lustre/tests/sanity.sh index f1732e5..8306005 100755 --- a/lustre/tests/sanity.sh +++ b/lustre/tests/sanity.sh @@ -7644,6 +7644,94 @@ test_56ab() { # LU-10705 } run_test 56ab "lfs find --blocks" +# LU-11188 +test_56aca() { + local dir="$DIR/$tdir" + local perms=(001 002 003 004 005 006 007 + 010 020 030 040 050 060 070 + 100 200 300 400 500 600 700 + 111 222 333 444 555 666 777) + local perm_minus=(8 8 4 8 4 4 2 + 8 8 4 8 4 4 2 + 8 8 4 8 4 4 2 + 4 4 2 4 2 2 1) + local perm_slash=(8 8 12 8 12 12 14 + 8 8 12 8 12 12 14 + 8 8 12 8 12 12 14 + 16 16 24 16 24 24 28) + + test_mkdir "$dir" + for perm in ${perms[*]}; do + touch "$dir/$tfile.$perm" + chmod $perm "$dir/$tfile.$perm" + done + + for ((i = 0; i < ${#perms[*]}; i++)); do + local num=$($LFS find $dir -perm ${perms[i]} | wc -l) + (( $num == 1 )) || + error "lfs find -perm ${perms[i]}:"\ + "$num != 1" + + num=$($LFS find $dir -perm -${perms[i]} -type f| wc -l) + (( $num == ${perm_minus[i]} )) || + error "lfs find -perm -${perms[i]}:"\ + "$num != ${perm_minus[i]}" + + num=$($LFS find $dir -perm /${perms[i]} -type f| wc -l) + (( $num == ${perm_slash[i]} )) || + error "lfs find -perm /${perms[i]}:"\ + "$num != ${perm_slash[i]}" + done +} +run_test 56aca "check lfs find -perm with octal representation" + +test_56acb() { + local dir=$DIR/$tdir + # p is the permission of write and execute for user, group and other + # without the umask. It is used to test +wx. + local p=$(printf "%o" "$((0333 & ~$(umask)))") + local perms=(1000 000 2000 4000 $p 644 111 110 100 004) + local symbolic=(+t a+t u+t g+t o+t + g+s u+s o+s +s o+sr + o=r,ug+o,u+w + u+ g+ o+ a+ ugo+ + u- g- o- a- ugo- + u= g= o= a= ugo= + o=r,ug+o,u+w u=r,a+u,u+w + g=r,ugo=g,u+w u+x,+X +X + u+x,u+X u+X u+x,g+X o+r,+X + u+x,go+X +wx +rwx) + + test_mkdir $dir + for perm in ${perms[*]}; do + touch "$dir/$tfile.$perm" + chmod $perm "$dir/$tfile.$perm" + done + + for (( i = 0; i < ${#symbolic[*]}; i++ )); do + local num=$($LFS find $dir -perm ${symbolic[i]} | wc -l) + + (( $num == 1 )) || + error "lfs find $dir -perm ${symbolic[i]}: $num != 1" + done +} +run_test 56acb "check lfs find -perm with symbolic representation" + +test_56acc() { + local dir=$DIR/$tdir + local tests="17777 787 789 abcd + ug=uu ug=a ug=gu uo=ou urw + u+xg+x a=r,u+x," + + test_mkdir $dir + for err in $tests; do + if $LFS find $dir -perm $err 2>/dev/null; then + error "lfs find -perm $err: parsing should have failed" + fi + done +} +run_test 56acc "check parsing error for lfs find -perm" + test_56ba() { [ $MDS1_VERSION -lt $(version_code 2.10.50) ] && skip "Need MDS version at least 2.10.50" diff --git a/lustre/utils/lfs.c b/lustre/utils/lfs.c index 479c2be..3216a09 100644 --- a/lustre/utils/lfs.c +++ b/lustre/utils/lfs.c @@ -494,14 +494,15 @@ command_t cmdlist[] = { " [[!] --newer[XY] ] [[!] --blocks|-b N]\n" " [--maxdepth|-D N] [[!] --mdt-index|--mdt|-m ]\n" " [[!] --name|-n ] [[!] --ost|-O ]\n" - " [--print|-P] [--print0|-0] [[!] --size|-s [+-]N[bkMGTPE]]\n" + " [[!] --perm [/-]mode] [[!] --pool ] [--print|-P]\n" + " [--print0|-0] [[!] --projid ]\n" + " [[!] --size|-s [+-]N[bkMGTPE]]\n" " [[!] --stripe-count|-c [+-]]\n" " [[!] --stripe-index|-i ]\n" " [[!] --stripe-size|-S [+-]N[kMGT]] [[!] --type|-t ]\n" " [[!] --extension-size|--ext-size|-z [+-]N[kMGT]]\n" " [[!] --gid|-g|--group|-G |]\n" - " [[!] --uid|-u|--user|-U |] [[!] --pool ]\n" - " [[!] --projid ]\n" + " [[!] --uid|-u|--user|-U |]\n" " [[!] --layout|-L released,raid0,mdt]\n" " [[!] --foreign[=]]\n" " [[!] --component-count [+-]]\n" @@ -3388,6 +3389,7 @@ enum { LFS_MODE_OPT, LFS_NEWERXY_OPT, LFS_INHERIT_RR_OPT, + LFS_FIND_PERM, }; /* functions */ @@ -4593,6 +4595,234 @@ static int name2layout(__u32 *layout, char *name) return 0; } +static int parse_symbolic(const char *input, mode_t *outmode, const char **end) +{ + int loop; + int user, group, other; + int who, all; + char c, op; + mode_t perm; + mode_t usermask; + mode_t previous_flags; + + user = group = other = 0; + all = 0; + loop = 1; + perm = 0; + previous_flags = 0; + *end = input; + usermask = 0; + + while (loop) { + switch (*input) { + case 'u': + user = 1; + break; + case 'g': + group = 1; + break; + case 'o': + other = 1; + break; + case 'a': + user = group = other = 1; + all = 1; + break; + default: + loop = 0; + } + + if (loop) + input++; + } + + who = user || group || other; + if (!who) { + /* get the umask */ + usermask = umask(0022); + umask(usermask); + usermask &= 07777; + } + + if (*input == '-' || *input == '+' || *input == '=') + op = *input++; + else + /* operation is required */ + return -1; + + /* get the flags in *outmode */ + switch (*input) { + case 'u': + previous_flags = (*outmode & 0700); + perm |= user ? previous_flags : 0; + perm |= group ? (previous_flags >> 3) : 0; + perm |= other ? (previous_flags >> 6) : 0; + input++; + goto write_perm; + case 'g': + previous_flags = (*outmode & 0070); + perm |= user ? (previous_flags << 3) : 0; + perm |= group ? previous_flags : 0; + perm |= other ? (previous_flags >> 3) : 0; + input++; + goto write_perm; + case 'o': + previous_flags = (*outmode & 0007); + perm |= user ? (previous_flags << 6) : 0; + perm |= group ? (previous_flags << 3) : 0; + perm |= other ? previous_flags : 0; + input++; + goto write_perm; + default: + break; + } + + /* this part is optional, + * if empty perm = 0 and *outmode is not modified + */ + loop = 1; + while (loop) { + c = *input; + switch (c) { + case 'r': + perm |= user ? 0400 : 0; + perm |= group ? 0040 : 0; + perm |= other ? 0004 : 0; + /* set read permission for uog except for umask's + * permissions + */ + perm |= who ? 0 : (0444 & ~usermask); + break; + case 'w': + perm |= user ? 0200 : 0; + perm |= group ? 0020 : 0; + perm |= other ? 0002 : 0; + /* set write permission for uog except for umask' + * permissions + */ + perm |= who ? 0 : (0222 & ~usermask); + break; + case 'x': + perm |= user ? 0100 : 0; + perm |= group ? 0010 : 0; + perm |= other ? 0001 : 0; + /* set execute permission for uog except for umask' + * permissions + */ + perm |= who ? 0 : (0111 & ~usermask); + break; + case 'X': + /* + * Adds execute permission to 'u', 'g' and/or 'g' if + * specified and either 'u', 'g' or 'o' already has + * execute permissions. + */ + if ((*outmode & 0111) != 0) { + perm |= user ? 0100 : 0; + perm |= group ? 0010 : 0; + perm |= other ? 0001 : 0; + perm |= !who ? 0111 : 0; + } + break; + case 's': + /* s is ignored if o is given, but it's not an error */ + if (other && !group && !user) + break; + perm |= user ? S_ISUID : 0; + perm |= group ? S_ISGID : 0; + break; + case 't': + /* 't' should be used when 'a' is given + * or who is empty + */ + perm |= (!who || all) ? S_ISVTX : 0; + /* using ugo with t is not an error */ + break; + default: + loop = 0; + break; + } + if (loop) + input++; + } + +write_perm: + /* uog flags should be only one character long */ + if (previous_flags && (*input != '\0' && *input != ',')) + return -1; + + switch (op) { + case '-': + /* remove the flags from outmode */ + *outmode &= ~perm; + break; + case '+': + /* add the flags to outmode */ + *outmode |= perm; + break; + case '=': + /* set the flags of outmode to perm */ + if (perm != 0) + *outmode = perm; + break; + } + + *end = input; + return 0; +} + +static int str2mode_t(const char *input, mode_t *outmode) +{ + int ret; + const char *iter; + + ret = 0; + + if (*input >= '0' && *input <= '7') { + /* parse octal representation */ + char *end; + + iter = input; + + /* look for invalid digits in octal representation */ + while (isdigit(*iter)) + if (*iter++ > '7') + return -1; + + errno = 0; + *outmode = strtoul(input, &end, 8); + + if (errno != 0 || *outmode > 07777) { + *outmode = 0; + ret = -1; + } + + } else if (*input == '8' || *input == '9') { + /* error: invalid octal number */ + ret = -1; + } else { + /* parse coma seperated list of symbolic representation */ + int rc; + const char *end; + + *outmode = 0; + rc = 0; + end = NULL; + + do { + rc = parse_symbolic(input, outmode, &end); + if (rc) + return -1; + + input = end+1; + } while (*end == ','); + + if (*end != '\0') + ret = -1; + } + return ret; +} + static int lfs_find(int argc, char **argv) { int c, rc; @@ -4719,6 +4949,8 @@ static int lfs_find(int argc, char **argv) { .val = 'S', .name = "stripe-size", .has_arg = required_argument }, { .val = 'S', .name = "stripe_size", .has_arg = required_argument }, { .val = 't', .name = "type", .has_arg = required_argument }, + { .val = LFS_FIND_PERM, + .name = "perm", .has_arg = required_argument }, { .val = 'T', .name = "mdt-count", .has_arg = required_argument }, { .val = 'u', .name = "uid", .has_arg = required_argument }, { .val = 'U', .name = "user", .has_arg = required_argument }, @@ -5418,6 +5650,24 @@ err_free: goto err; }; break; + case LFS_FIND_PERM: + param.fp_exclude_perm = !!neg_opt; + param.fp_perm_sign = LFS_FIND_PERM_EXACT; + if (*optarg == '/') { + param.fp_perm_sign = LFS_FIND_PERM_ANY; + optarg++; + } else if (*optarg == '-') { + param.fp_perm_sign = LFS_FIND_PERM_ALL; + optarg++; + } + + if (str2mode_t(optarg, ¶m.fp_perm)) { + fprintf(stderr, "error: invalid mode '%s'\n", + optarg); + ret = -1; + goto err; + } + break; case 'T': if (optarg[0] == '+') { param.fp_mdt_count_sign = -1; diff --git a/lustre/utils/liblustreapi.c b/lustre/utils/liblustreapi.c index 03dbfad..52a9b1c 100644 --- a/lustre/utils/liblustreapi.c +++ b/lustre/utils/liblustreapi.c @@ -4831,6 +4831,34 @@ static int fget_projid(int fd, int *projid) return 0; } +/* + * Check that the file's permissions in *st matches the one in find_param + */ +static int check_file_permissions(const struct find_param *param, + const lstat_t *st) +{ + const mode_t st_mode = st->st_mode & 07777; + int decision = 0; + + switch (param->fp_perm_sign) { + case LFS_FIND_PERM_EXACT: + decision = (st_mode == param->fp_perm); + break; + case LFS_FIND_PERM_ALL: + decision = ((st_mode & param->fp_perm) == param->fp_perm); + break; + case LFS_FIND_PERM_ANY: + decision = ((st_mode & param->fp_perm) != 0); + break; + } + + if ((param->fp_exclude_perm && decision) + || (!param->fp_exclude_perm && !decision)) + return -1; + else + return 1; +} + static int cb_find_init(char *path, int p, int *dp, void *data, struct dirent64 *de) { @@ -4885,7 +4913,7 @@ static int cb_find_init(char *path, int p, int *dp, param->fp_check_size || param->fp_check_blocks || find_check_lmm_info(param) || param->fp_check_mdt_count || param->fp_hash_type || - param->fp_check_hash_flag) + param->fp_check_hash_flag || param->fp_perm_sign) decision = 0; if (param->fp_type != 0 && checked_type == 0) @@ -5240,6 +5268,9 @@ obd_matches: (param->fp_lazy && flags & OBD_MD_FLLAZYBLOCKS))) decision = 0; + if (param->fp_perm_sign) + 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 @@ -5296,6 +5327,13 @@ obd_matches: goto out; } } + + /* Check the file permissions from the stat info */ + if (param->fp_perm_sign) { + decision = check_file_permissions(param, &st); + if (decision == -1) + goto decided; + } } if (param->fp_check_size) { -- 1.8.3.1