Whamcloud - gitweb
LU-11188 lfs: add "--perm" option to "lfs find" 15/43715/4
authorCourrier Guillaume <guillaume.courrier@cea.fr>
Thu, 29 Apr 2021 09:35:01 +0000 (11:35 +0200)
committerOleg Drokin <green@whamcloud.com>
Wed, 2 Jun 2021 17:49:11 +0000 (17:49 +0000)
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 <guillaume.courrier@cea.fr>
Change-Id: I8e1292421986c3a4bde686f3c7dc7bfcb679cabc
Reviewed-on: https://review.whamcloud.com/43715
Tested-by: jenkins <devops@whamcloud.com>
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Olaf Faaland-LLNL <faaland1@llnl.gov>
lustre/doc/lfs-find.1
lustre/include/lustre/lustreapi.h
lustre/tests/sanity.sh
lustre/utils/lfs.c
lustre/utils/liblustreapi.c

index 7598f26..4360dd7 100644 (file)
@@ -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
index af0587f..abd42bd 100644 (file)
@@ -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;
index f1732e5..8306005 100755 (executable)
@@ -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"
index 479c2be..3216a09 100644 (file)
@@ -494,14 +494,15 @@ command_t cmdlist[] = {
         "     [[!] --newer[XY] <reference>] [[!] --blocks|-b N]\n"
         "     [--maxdepth|-D N] [[!] --mdt-index|--mdt|-m <uuid|index,...>]\n"
         "     [[!] --name|-n <pattern>] [[!] --ost|-O <uuid|index,...>]\n"
-        "     [--print|-P] [--print0|-0] [[!] --size|-s [+-]N[bkMGTPE]]\n"
+        "     [[!] --perm [/-]mode] [[!] --pool <pool>] [--print|-P]\n"
+        "     [--print0|-0] [[!] --projid <projid>]\n"
+        "     [[!] --size|-s [+-]N[bkMGTPE]]\n"
         "     [[!] --stripe-count|-c [+-]<stripes>]\n"
         "     [[!] --stripe-index|-i <index,...>]\n"
         "     [[!] --stripe-size|-S [+-]N[kMGT]] [[!] --type|-t <filetype>]\n"
         "     [[!] --extension-size|--ext-size|-z [+-]N[kMGT]]\n"
         "     [[!] --gid|-g|--group|-G <gid>|<gname>]\n"
-        "     [[!] --uid|-u|--user|-U <uid>|<uname>] [[!] --pool <pool>]\n"
-        "     [[!] --projid <projid>]\n"
+        "     [[!] --uid|-u|--user|-U <uid>|<uname>]\n"
         "     [[!] --layout|-L released,raid0,mdt]\n"
         "     [[!] --foreign[=<foreign_type>]]\n"
         "     [[!] --component-count [+-]<comp_cnt>]\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, &param.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;
index 03dbfad..52a9b1c 100644 (file)
@@ -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) {