Whamcloud - gitweb
LU-10030 utils: add lfs tool to change/list project of file 90/29190/22
authorWang Shilong <wshilong@ddn.com>
Mon, 16 Oct 2017 06:58:04 +0000 (14:58 +0800)
committerOleg Drokin <oleg.drokin@intel.com>
Thu, 4 Jan 2018 02:49:05 +0000 (02:49 +0000)
Currently, we are using chattr/lsattr for project quota
interface, this have some problems:

1)Client side need patched e2fsprogs or latest upstream e2fsprogs.
2)Project quota will be no longer osd-ldiskfs based, ZFS
too, zfs guys might dislike ldiskfs tool dependency for them.
3)customers argue chattr might be a little dangerous.

So this patch add native lfs tools for project quota.
usage: project [-p id] [-s] [-r] <file|directory..>
          set project ID and/or inherit flag for specified
          file(s) or directory.
       project [-d|-r [-0]] <file|directory...>
          list project ID and flags on file(s) or directory,
          print outliers
       project -c [-d|-r [-p id] [-0]] <file|directory..>
          check project ID and flags on file(s) or directory,
          print outliers
       project -C [-r] [-k] <file|directory..>
          clear the project inherit flag and ID on the file
          or directory

Test-Parameters: testlist=sanity-quota,sanity-quota,sanity-quota,\
    sanity-quota clientdistro=el7 serverdistro=el7 \
    ostfilesystemtype=ldiskfs mdtfilesystemtype=ldiskfs
Signed-off-by: Wang Shilong <wshilong@ddn.com>
Change-Id: I45960fb8fbd12e22a654792fba517896c0447447
Reviewed-on: https://review.whamcloud.com/29190
Tested-by: Jenkins
Reviewed-by: Andreas Dilger <andreas.dilger@intel.com>
Tested-by: Maloo <hpdd-maloo@intel.com>
Reviewed-by: Fan Yong <fan.yong@intel.com>
Reviewed-by: Oleg Drokin <oleg.drokin@intel.com>
lustre/doc/Makefile.am
lustre/doc/lfs-project.1 [new file with mode: 0644]
lustre/doc/lfs.1
lustre/include/uapi/linux/lustre/lustre_user.h
lustre/tests/sanity-quota.sh
lustre/utils/Makefile.am
lustre/utils/lfs.c
lustre/utils/lfs_project.c [new file with mode: 0644]
lustre/utils/lfs_project.h [new file with mode: 0644]

index 8d63f45..33ad4a3 100644 (file)
@@ -53,6 +53,7 @@ MANFILES =                                    \
        lfs-setdirstripe.1                      \
        lfs-setstripe.1                         \
        lfs-setquota.1                          \
+       lfs-project.1                           \
        l_getidentity.8                         \
        lgss_sk.8                               \
        lhbadm.8                                \
diff --git a/lustre/doc/lfs-project.1 b/lustre/doc/lfs-project.1
new file mode 100644 (file)
index 0000000..cabd831
--- /dev/null
@@ -0,0 +1,88 @@
+.TH LFS-PROJECT 1 2017-10-26 "Lustre" "Lustre Utilities"
+.SH NAME
+lfs project \- Change or list project attribute for specified file or directory.
+.SH SYNOPSIS
+.BR "lfs project" " [" -d | -r ] " "< \fI file | directory...\fR>
+.br
+.BR "lfs project" " {" -p " "\fIID " |" -s } " "[ -r ] " "<\fI file | directory...\fR>
+.br
+.BR "lfs project" " -c" " [" -d | -r " [" -p " "\fIID ] " [" -0 ] ] " <" file | directory...>
+.br
+.BR "lfs project" " -C" " [" -r | -k ] " <" file | directory...>
+.br
+.SH DESCRIPTION
+.TP
+.BR "lfs project" " [" -d | -r ]
+.RI < file | directory...>
+.TP
+List project ID and flags on file(s) or directories.
+.TP
+.B -d
+Show the directory's own project ID and flags, override \fB-r\fR option.
+.TP
+.B -r
+Recursively list all descendants'(of the directory) project attribute.
+.TP
+.BR "lfs project" " {" -p " "\fIID " |" -s } " "[ -r ]
+.RI < file | directory...>
+.TP
+Set project ID and/or inherit flag for specified file(s) or directories.
+.TP
+.B -p <\fIID\fR>
+Set project \fIID\fR with given value for the specified file or directory
+.TP
+.B -s
+Set the
+.B PROJID_INHERIT
+attribute on directories, so that new files and subdirectories created
+therein will inherit the project ID and attribute from the parent.
+.TP
+.B -r
+Set project \fIID\fR with the directory's project ID for all
+its descendants (with \fB-p\fR specified). For descendant directories, also set
+inherit flag (if \fI-s\fR specified).
+.TP
+.BR "lfs project" " -c" " [" -d|-r " [" -p " "\fIID ] " [" -0 ] ]
+.RI < file | directory...>
+.TP
+Check project ID and flags on file(s) or directories, print outliers.
+.TP
+.B -c
+Check project ID and inherit flag on specified file(s) or directory. If
+.B -p
+is not given, then use the project ID on the top-level directory
+, otherwise use the ID specified with
+.BR -p .
+if checking a directory and or recursively, print only files that do not match.
+.TP
+.B -0
+Print pathnames found by -c with a trailing NUL for use by
+.BR \' xargs "(1) " -0 \'.
+.TP
+.BR "lfs project" " -C [" -r | -k ]
+.RI < file | directory...>
+.TP
+Clear the project inherit flag and ID on the file(s) or directories
+.TP
+.B -C
+Clear inherit attribute, project ID will be reset to be 0 in default
+.TP
+.B -k
+keep the project ID unchanged.
+.TP
+.SH EXAMPLES
+.TP
+.B $ lfs project -srp 1000 /mnt/lustre/dir1
+set directory quota on
+.BR /mnt/lustre/dir1,
+all descendants' project ID and inherit attribute are set.
+.TP
+.B $ lfs project -cr -p 1000 /mnt/lustre/dir1
+check directory
+.BR /mnt/lustre/dir1,
+whether all files and directories ID are 1000, inherit attribute
+is properly set for all directories, print mismatch
+if any are found.
+.SH SEE ALSO
+.BR lfs (1)
+.BR xargs (1)
index cf59e78..2ef08a5 100644 (file)
@@ -364,20 +364,21 @@ Turn quotas of user and group on
 Turn quotas of user and group off
 .SH NOTES
 The usage of \fBlfs hsm_*\fR, \fBlfs setstripe\fR, \fBlfs migrate\fR, \fBlfs setdirstripe\fR,
-\fBlfs getdirstripe\fR and \fBlfs mkdir\fR are explained in separated man pages.
+\fBlfs getdirstripe\fR, \fBlfs mkdir\fR and \fBlfs project\fR are explained in separate
+man pages.
 .SH BUGS
 The \fBlfs find\fR command isn't as comprehensive as \fBfind\fR(1).
 .SH AUTHOR
 The lfs command is part of the Lustre filesystem.
 .SH SEE ALSO
+.BR lctl (8),
 .BR lfs-df (1),
-.BR lfs-hsm (1),
-.BR lfs-setdirstripe (1),
 .BR lfs-getdirstripe (1),
+.BR lfs-hsm (1),
 .BR lfs-mkdir (1),
-.BR lfs_migrate (1),
-.BR lfs-setstripe (1),
 .BR lfs-migrate (1),
+.BR lfs-project (1),
+.BR lfs-setdirstripe (1),
 .BR lfs-setquota (1),
-.BR lctl (8),
+.BR lfs-setstripe (1),
 .BR lustre (7)
index d840295..a07d74c 100644 (file)
@@ -427,6 +427,7 @@ struct fsxattr {
 #endif
 #define LL_IOC_FSGETXATTR              FS_IOC_FSGETXATTR
 #define LL_IOC_FSSETXATTR              FS_IOC_FSSETXATTR
+#define LL_PROJINHERIT_FL              0x20000000
 
 
 #define LL_STATFS_LMV          1
index 6293ad9..b27702d 100755 (executable)
@@ -72,11 +72,10 @@ export QUOTA_AUTO=0
 check_and_setup_lustre
 
 is_project_quota_supported() {
-       lsattr -dp > /dev/null 2>&1 || return 1
-
        [ "$(facet_fstype $SINGLEMDS)" == "ldiskfs" ] &&
                [ $(lustre_version_code $SINGLEMDS) -gt \
                $(version_code 2.9.55) ] &&
+               lfs --help | grep project >&/dev/null &&
                egrep -q "7." /etc/redhat-release && return 0
 
        if [ "$(facet_fstype $SINGLEMDS)" == "zfs" ]; then
@@ -117,8 +116,8 @@ lustre_fail() {
 
 change_project()
 {
-       echo "chattr $*"
-       chattr $* || error "chattr $* failed"
+       echo "lfs project $*"
+       lfs project $* || error "lfs project $* failed"
 }
 
 RUNAS="runas -u $TSTID -g $TSTID"
@@ -696,16 +695,14 @@ test_2() {
        [ $USED -ne 0 ] &&
                error "Used inodes($USED) for project $TSTPRJID isn't 0"
 
-       change_project +P $DIR/$tdir/
-       change_project -p $TSTPRJID -d $DIR/$tdir
+       change_project -sp $TSTPRJID $DIR/$tdir
        log "Create $LIMIT files ..."
        $RUNAS createmany -m ${TESTFILE} $((LIMIT-1)) || quota_error p \
                $TSTPRJID "project create fail, but expect success"
        log "Create out of file quota ..."
        $RUNAS touch ${TESTFILE}_xxx && quota_error p $TSTPRJID \
                "project create success, but expect EDQUOT"
-       change_project -P $DIR/$tdir
-       change_project -p 0 -d $DIR/$tdir
+       change_project -C $DIR/$tdir
 
        cleanup_quota_test
        USED=$(getquota -p $TSTPRJID global curinodes)
@@ -846,7 +843,7 @@ test_3() {
                # make sure the system is clean
                USED=$(getquota -p $TSTPRJID global curspace)
                [ $USED -ne 0 ] && error \
-                       "Used space($USED) for project $TSTPROJID isn't 0."
+                       "Used space($USED) for project $TSTPRJID isn't 0."
 
                $LFS setquota -t -p --block-grace $GRACE --inode-grace \
                        $MAX_IQ_TIME $DIR ||
@@ -877,8 +874,7 @@ test_file_soft() {
 
        setup_quota_test
        trap cleanup_quota_test EXIT
-       is_project_quota_supported && change_project +P $DIR/$tdir/ &&
-               change_project -p $TSTPRJID -d $DIR/$tdir
+       is_project_quota_supported && change_project -sp $TSTPRJID $DIR/$tdir
 
        echo "Create files to exceed soft limit"
        $RUNAS createmany -m ${TESTFILE}_ $((LIMIT + 1)) ||
@@ -1528,8 +1524,7 @@ test_8() {
        $LFS setquota -g $TSTUSR -b 0 -B $BLK_LIMIT -i 0 -I $FILE_LIMIT $DIR ||
                error "set group quota failed"
        if is_project_quota_supported; then
-               change_project +P $DIR/$tdir && change_project -p \
-                       $TSTPRJID -d $DIR/$tdir
+               change_project -sp $TSTPRJID $DIR/$tdir
                echo "Set enough high limit for project: $TSTPRJID"
                $LFS setquota -p $TSTPRJID -b 0 \
                        -B $BLK_LIMIT -i 0 -I $FILE_LIMIT $DIR ||
@@ -1541,8 +1536,7 @@ test_8() {
        $RUNAS bash rundbench -D $DIR/$tdir 3 $duration ||
                quota_error a $TSTUSR "dbench failed!"
 
-       is_project_quota_supported && change_project -P $DIR/$tdir &&
-       change_project -dp 0 $DIR/$tdir
+       is_project_quota_supported && change_project -C $DIR/$tdir
        cleanup_quota_test
        resetquota -u $TSTUSR
        resetquota -g $TSTUSR
@@ -2762,18 +2756,18 @@ test_39() {
        setup_quota_test || error "setup quota failed with $?"
 
        touch $TESTFILE
-       projectid=$(lsattr -p $TESTFILE | awk '{print $1}')
+       projectid=$(lfs project $TESTFILE | awk '{print $1}')
        [ $projectid -ne 0 ] &&
                error "Project id should be 0 not $projectid"
        change_project -p 1024 $TESTFILE
-       projectid=$(lsattr -p $TESTFILE | awk '{print $1}')
+       projectid=$(lfs project $TESTFILE | awk '{print $1}')
        [ $projectid -ne 1024 ] &&
                error "Project id should be 1024 not $projectid"
 
        stopall || error "failed to stopall (1)"
        mount
        setupall
-       projectid=$(lsattr -p $TESTFILE | awk '{print $1}')
+       projectid=$(lfs project $TESTFILE | awk '{print $1}')
        [ $projectid -ne 1024 ] &&
                error "Project id should be 1024 not $projectid"
 
@@ -2790,8 +2784,8 @@ test_40a() {
        setup_quota_test || error "setup quota failed with $?"
 
        mkdir -p $dir1 $dir2
-       change_project +P $dir1 && change_project -p 1 -d $dir1 && touch $dir1/1
-       change_project +P $dir2 && change_project -p 2 -d $dir2
+       change_project -sp 1 $dir1 && touch $dir1/1
+       change_project -sp 2 $dir2
 
        ln $dir1/1 $dir2/1_link &&
                error "Hard link across different project quota should fail"
@@ -2809,11 +2803,11 @@ test_40b() {
 
        setup_quota_test || error "setup quota failed with $?"
        mkdir -p $dir1 $dir2
-       change_project +P $dir1 && change_project -p 1 -d $dir1 && touch $dir1/1
-       change_project +P $dir2 && change_project -p 2 -d $dir2
+       change_project -sp 1 $dir1 && touch $dir1/1
+       change_project -sp 2 $dir2
 
        mv $dir1/1 $dir2/2 || error "mv failed $?"
-       local projid=$(lsattr -p $dir2/2 | awk '{print $1}')
+       local projid=$(lfs project $dir2/2 | awk '{print $1}')
        if [ "$projid" != "2" ]; then
                error "project id expected 2 not $projid"
        fi
@@ -2830,13 +2824,13 @@ test_40c() {
        setup_quota_test || error "setup quota failed with $?"
        local dir="$DIR/$tdir/dir"
 
-       mkdir -p $dir && change_project +P $dir && change_project -dp 1 $dir
+       mkdir -p $dir && change_project -sp 1 $dir
        $LFS mkdir -i 1 $dir/remote_dir || error "create remote dir failed"
-       local projid=$(lsattr -dp $dir/remote_dir | awk '{print $1}')
+       local projid=$(lfs project -d $dir/remote_dir | awk '{print $1}')
        [ "$projid" != "1" ] && error "projid id expected 1 not $projid"
        touch $dir/remote_dir/file
        #verify inherit works file for remote dir.
-       local projid=$(lsattr -dp $dir/remote_dir/file | awk '{print $1}')
+       local projid=$(lfs project -d $dir/remote_dir/file | awk '{print $1}')
        [ "$projid" != "1" ] &&
                error "file under remote dir expected 1 not $projid"
 
@@ -2858,7 +2852,7 @@ test_50() {
        setup_quota_test || error "setup quota failed with $?"
        local dir="$DIR/$tdir/dir"
 
-       mkdir $dir && change_project -dp 1 $dir
+       mkdir $dir && change_project -p 1 $dir
        count=$($LFS find --projid 1 $DIR | wc -l)
        [ "$count" != 1 ] && error "expected 1 but got $count"
 
@@ -2873,7 +2867,7 @@ test_51() {
        setup_quota_test || error "setup quota failed with $?"
        local dir="$DIR/$tdir/dir"
 
-       mkdir $dir && change_project -dp 1 $dir && change_project +P $dir
+       mkdir $dir && change_project -sp 1 $dir
        local used=$(getquota -p 1 global curinodes)
        [ $used != "1" ] && error "expected 1 got $used"
 
@@ -2904,7 +2898,7 @@ test_52() {
                skip "Project quota is not supported" && return 0
        setup_quota_test || error "setup quota failed with $?"
        local dir="$DIR/$tdir/dir"
-       mkdir $dir && change_project -dp 1 $dir && change_project +P $dir
+       mkdir $dir && change_project -sp 1 $dir
 
        touch $DIR/$tdir/file
        #Try renaming a file into the project.  This should fail.
@@ -2922,17 +2916,72 @@ test_53() {
                skip "Project quota is not supported" && return 0
        setup_quota_test || error "setup quota failed with $?"
        local dir="$DIR/$tdir/dir"
-       mkdir $dir && change_project +P $dir
-       lsattr -pd $dir | grep P || error "inherit attribute should be set"
+       mkdir $dir && change_project -s $dir
+       lfs project -d $dir | grep P || error "inherit attribute should be set"
 
-       change_project -Pd $dir
-       lsattr -pd $dir | grep P && error "inherit attribute should be cleared"
+       change_project -C $dir
+       lfs project -d $dir | grep P &&
+               error "inherit attribute should be cleared"
 
        rm -rf $dir
        cleanup_quota_test
 }
 run_test 53 "Project inherit attribute could be cleared"
 
+test_54() {
+       ! is_project_quota_supported &&
+               skip "Project quota is not supported" && return 0
+       setup_quota_test || error "setup quota failed with $?"
+       trap cleanup_quota_test EXIT
+       local testfile="$DIR/$tdir/$tfile-0"
+
+       #set project ID/inherit attribute
+       change_project -sp $TSTPRJID $DIR/$tdir
+       $RUNAS createmany -m ${testfile} 100 ||
+               error "create many files failed"
+
+       local proj_count=$(lfs project -r $DIR/$tdir | wc -l)
+       # one more count for directory itself */
+       ((proj_count++))
+
+       #check project
+       local proj_count1=$(lfs project -rcp $TSTPRJID $DIR/$tdir | wc -l)
+       [ $proj_count1 -eq 0 ] || error "c1: expected 0 got $proj_count1"
+
+       proj_count1=$(lfs project -rcp $((TSTPRJID+1)) $DIR/$tdir | wc -l)
+       [ $proj_count1 -eq $proj_count ] ||
+                       error "c2: expected $proj_count got $proj_count1"
+
+       #clear project but with kept projid
+       change_project -rCk $DIR/$tdir
+       proj_count1=$(lfs project -rcp $TSTPRJID $DIR/$tdir | wc -l)
+       [ $proj_count1 -eq $proj_count ] ||
+                       error "c3: expected $proj_count got $proj_count1"
+
+       #verify projid untouched.
+       proj_count1=$(lfs project -r $DIR/$tdir | grep -c $TSTPRJID)
+       ((proj_count1++))
+       [ $proj_count1 -eq $proj_count ] ||
+                       error "c4: expected $proj_count got $proj_count1"
+
+       # test -0 option
+       lfs project $DIR/$tdir -cr -0 | xargs -0 lfs project -s
+       proj_count1=$(lfs project -rcp $TSTPRJID $DIR/$tdir | wc -l)
+       [ $proj_count1 -eq 0 ] || error "c5: expected 0 got $proj_count1"
+
+       #this time clear all
+       change_project -rC $DIR/$tdir
+       proj_count1=$(lfs project -r $DIR/$tdir | grep -c $TSTPRJID)
+       [ $proj_count1 -eq 0 ] ||
+                       error "c6: expected 0 got $proj_count1"
+       #cleanup
+       unlinkmany ${testfile} 100 ||
+               error "unlink many files failed"
+
+       cleanup_quota_test
+}
+run_test 54 "basic lfs project interface test"
+
 quota_fini()
 {
        do_nodes $(comma_list $(nodes_list)) "lctl set_param debug=-quota"
index c58743b..56aba33 100644 (file)
@@ -58,7 +58,7 @@ endif
 lctl_LDADD :=  liblustreapi.a $(LIBCFS) $(LIBREADLINE) $(PTHREAD_LIBS)
 lctl_DEPENDENCIES := $(LIBCFS) liblustreapi.a
 
-lfs_SOURCES = lfs.c
+lfs_SOURCES = lfs.c lfs_project.c lfs_project.h
 lfs_LDADD := liblustreapi.a $(LIBCFS) $(LIBREADLINE)
 lfs_DEPENDENCIES := $(LIBCFS) liblustreapi.a
 
index 9d3fd32..9f168aa 100644 (file)
@@ -61,6 +61,7 @@
 #include <dirent.h>
 #include <time.h>
 #include <ctype.h>
+#include "lfs_project.h"
 
 #include <libcfs/util/string.h>
 #include <libcfs/util/ioctl.h>
@@ -87,6 +88,7 @@ static int lfs_check(int argc, char **argv);
 #ifdef HAVE_SYS_QUOTA_H
 static int lfs_setquota(int argc, char **argv);
 static int lfs_quota(int argc, char **argv);
+static int lfs_project(int argc, char **argv);
 #endif
 static int lfs_flushctx(int argc, char **argv);
 static int lfs_cp(int argc, char **argv);
@@ -226,8 +228,6 @@ static inline int lfs_mirror_extend(int argc, char **argv)
        "\tdefault_stripe: set default dirstripe of the directory\n"    \
        "\tmode: the mode of the directory\n"
 
-static const char      *progname;
-
 /**
  * command_t mirror_cmdlist - lfs mirror commands.
  */
@@ -390,6 +390,17 @@ command_t cmdlist[] = {
                       "<ost_idx>]\n"
         "             [<-u|-g|-p> <uname>|<uid>|<gname>|<gid>|<projid>] <filesystem>\n"
         "       quota [-o <obd_uuid>|-i <mdt_idx>|-I <ost_idx>] -t <-u|-g|-p> <filesystem>"},
+       {"project", lfs_project, 0,
+        "Change or list project attribute for specified file or directory.\n"
+        "usage: project [-d|-r] <file|directory...>\n"
+        "         list project ID and flags on file(s) or directories\n"
+        "       project [-p id] [-s] [-r] <file|directory...>\n"
+        "         set project ID and/or inherit flag for specified file(s) or directories\n"
+        "       project -c [-d|-r [-p id] [-0]] <file|directory...>\n"
+        "         check project ID and flags on file(s) or directories, print outliers\n"
+        "       project -C [-r] [-k] <file|directory...>\n"
+        "         clear the project inherit flag and ID on the file or directory\n"
+       },
 #endif
         {"flushctx", lfs_flushctx, 0, "Flush security context for current user.\n"
          "usage: flushctx [-k] [mountpoint...]"},
@@ -4916,6 +4927,160 @@ out:
 
 }
 
+static int lfs_project(int argc, char **argv)
+{
+       int ret = 0, err = 0, c, i;
+       struct project_handle_control phc = { 0 };
+       enum lfs_project_ops_t op;
+
+       phc.newline = true;
+       phc.assign_projid = false;
+       /* default action */
+       op = LFS_PROJECT_LIST;
+
+       while ((c = getopt(argc, argv, "p:cCsdkr0")) != -1) {
+               switch (c) {
+               case 'c':
+                       if (op != LFS_PROJECT_LIST) {
+                               fprintf(stderr,
+                                       "%s: cannot specify '-c' '-C' '-s' together\n",
+                                       progname);
+                               return CMD_HELP;
+                       }
+
+                       op = LFS_PROJECT_CHECK;
+                       break;
+               case 'C':
+                       if (op != LFS_PROJECT_LIST) {
+                               fprintf(stderr,
+                                       "%s: cannot specify '-c' '-C' '-s' together\n",
+                                       progname);
+                               return CMD_HELP;
+                       }
+
+                       op = LFS_PROJECT_CLEAR;
+                       break;
+               case 's':
+                       if (op != LFS_PROJECT_LIST) {
+                               fprintf(stderr,
+                                       "%s: cannot specify '-c' '-C' '-s' together\n",
+                                       progname);
+                               return CMD_HELP;
+                       }
+
+                       phc.set_inherit = true;
+                       op = LFS_PROJECT_SET;
+                       break;
+               case 'd':
+                       phc.dironly = true;
+                       break;
+               case 'k':
+                       phc.keep_projid = true;
+                       break;
+               case 'r':
+                       phc.recursive = true;
+                       break;
+               case 'p':
+                       phc.projid = strtoul(optarg, NULL, 0);
+                       phc.assign_projid = true;
+
+                       break;
+               case '0':
+                       phc.newline = false;
+                       break;
+               default:
+                       fprintf(stderr, "%s: invalid option '%c'\n",
+                               progname, optopt);
+                       return CMD_HELP;
+               }
+       }
+
+       if (phc.assign_projid && op == LFS_PROJECT_LIST) {
+               op = LFS_PROJECT_SET;
+               phc.set_projid = true;
+       } else if (phc.assign_projid && op == LFS_PROJECT_SET) {
+               phc.set_projid = true;
+       }
+
+       switch (op) {
+       case LFS_PROJECT_CHECK:
+               if (phc.keep_projid) {
+                       fprintf(stderr,
+                               "%s: '-k' is useless together with '-c'\n",
+                               progname);
+                       return CMD_HELP;
+               }
+               break;
+       case LFS_PROJECT_CLEAR:
+               if (!phc.newline) {
+                       fprintf(stderr,
+                               "%s: '-0' is useless together with '-C'\n",
+                               progname);
+                       return CMD_HELP;
+               }
+               if (phc.assign_projid) {
+                       fprintf(stderr,
+                               "%s: '-p' is useless together with '-C'\n",
+                               progname);
+                       return CMD_HELP;
+               }
+               break;
+       case LFS_PROJECT_SET:
+               if (!phc.newline) {
+                       fprintf(stderr,
+                               "%s: '-0' is useless together with '-s'\n",
+                               progname);
+                       return CMD_HELP;
+               }
+               if (phc.keep_projid) {
+                       fprintf(stderr,
+                               "%s: '-k' is useless together with '-s'\n",
+                               progname);
+                       return CMD_HELP;
+               }
+               break;
+       default:
+               if (!phc.newline) {
+                       fprintf(stderr,
+                               "%s: '-0' is useless for list operations\n",
+                               progname);
+                       return CMD_HELP;
+               }
+               break;
+       }
+
+       argv += optind;
+       argc -= optind;
+       if (argc == 0) {
+               fprintf(stderr, "%s: missing file or directory target(s)\n",
+                       progname);
+               return CMD_HELP;
+       }
+
+       for (i = 0; i < argc; i++) {
+               switch (op) {
+               case LFS_PROJECT_CHECK:
+                       err = lfs_project_check(argv[i], &phc);
+                       break;
+               case LFS_PROJECT_LIST:
+                       err = lfs_project_list(argv[i], &phc);
+                       break;
+               case LFS_PROJECT_CLEAR:
+                       err = lfs_project_clear(argv[i], &phc);
+                       break;
+               case LFS_PROJECT_SET:
+                       err = lfs_project_set(argv[i], &phc);
+                       break;
+               default:
+                       break;
+               }
+               if (err && !ret)
+                       ret = err;
+       }
+
+       return ret;
+}
+
 static int lfs_quota(int argc, char **argv)
 {
        int c;
diff --git a/lustre/utils/lfs_project.c b/lustre/utils/lfs_project.c
new file mode 100644 (file)
index 0000000..180447a
--- /dev/null
@@ -0,0 +1,337 @@
+/*
+ * GPL HEADER START
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License version 2 for more details (a copy is included
+ * in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; If not, see
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * GPL HEADER END
+ */
+/*
+ * Copyright (c) 2017, DataDirect Networks Storage.
+ * Copyright (c) 2017, Intel Corporation.
+ */
+/*
+ * This file is part of Lustre, http://www.lustre.org/
+ *
+ * lustre/utils/lfs_project.c
+ *
+ * Author: Wang Shilong <wshilong@ddn.com>
+ * Author: Fan Yong <fan.yong@intel.com>
+ */
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <stddef.h>
+#include <libintl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <string.h>
+#include <libcfs/util/list.h>
+#include <libcfs/util/ioctl.h>
+#include <sys/ioctl.h>
+
+#include "lfs_project.h"
+#include <lustre/lustreapi.h>
+
+struct lfs_project_item {
+       struct list_head lpi_list;
+       char lpi_pathname[PATH_MAX];
+};
+
+static int
+lfs_project_item_alloc(struct list_head *head, const char *pathname)
+{
+       struct lfs_project_item *lpi;
+
+       lpi = malloc(sizeof(struct lfs_project_item));
+       if (lpi == NULL) {
+               fprintf(stderr,
+                       "%s: cannot allocate project item for '%s': %s\n",
+                       progname, pathname, strerror(ENOMEM));
+               return -ENOMEM;
+       }
+
+       strncpy(lpi->lpi_pathname, pathname, sizeof(lpi->lpi_pathname));
+       list_add_tail(&lpi->lpi_list, head);
+
+       return 0;
+}
+
+static int project_get_xattr(const char *pathname, struct fsxattr *fsx)
+{
+       int ret, fd;
+
+       fd = open(pathname, O_RDONLY | O_NOCTTY);
+       if (fd < 0) {
+               fprintf(stderr, "%s: failed to open '%s': %s\n",
+                       progname, pathname, strerror(errno));
+               return -errno;
+       }
+
+       ret = ioctl(fd, LL_IOC_FSGETXATTR, fsx);
+       if (ret) {
+               fprintf(stderr, "%s: failed to get xattr for '%s': %s\n",
+                       progname, pathname, strerror(errno));
+               return -errno;
+       }
+       return fd;
+}
+
+static int
+project_check_one(const char *pathname, struct project_handle_control *phc)
+{
+       struct fsxattr fsx;
+       struct stat st;
+       int ret;
+
+       ret = stat(pathname, &st);
+       if (ret) {
+               fprintf(stderr, "%s: failed to stat '%s': %s\n",
+                       progname, pathname, strerror(errno));
+               return -errno;
+       }
+
+       ret = project_get_xattr(pathname, &fsx);
+       if (ret < 0)
+               return ret;
+
+       /* use top directory's project ID if not specified */
+       if (!phc->assign_projid) {
+               phc->assign_projid = true;
+               phc->projid = fsx.fsx_projid;
+       }
+
+       if (!(fsx.fsx_xflags & LL_PROJINHERIT_FL)) {
+               if (!phc->newline) {
+                       printf("%s%c", pathname, '\0');
+                       goto out;
+               }
+                printf("%s - project inheritance flag is not set\n",
+                       pathname);
+       }
+
+       if (fsx.fsx_projid != phc->projid) {
+               if (!phc->newline) {
+                       printf("%s%c", pathname, '\0');
+                       goto out;
+               }
+               printf("%s - project identifier is not set (inode=%u, tree=%u)\n",
+                      pathname, fsx.fsx_projid, phc->projid);
+       }
+out:
+       close(ret);
+       return 0;
+}
+
+static int
+project_list_one(const char *pathname, struct project_handle_control *phc)
+{
+       struct fsxattr fsx;
+       int ret;
+
+       ret = project_get_xattr(pathname, &fsx);
+       if (ret < 0)
+               return ret;
+
+       printf("%5u %c %s\n", fsx.fsx_projid,
+              (fsx.fsx_xflags & LL_PROJINHERIT_FL) ?
+               'P' : '-', pathname);
+
+       close(ret);
+       return 0;
+}
+
+static int
+project_set_one(const char *pathname, struct project_handle_control *phc)
+{
+       struct fsxattr fsx;
+       int fd, ret = 0;
+
+       fd = project_get_xattr(pathname, &fsx);
+       if (fd < 0)
+               return fd;
+
+       if ((!phc->set_projid || fsx.fsx_projid == phc->projid) &&
+           (!phc->set_inherit || (fsx.fsx_xflags & LL_PROJINHERIT_FL)))
+               goto out;
+
+       if (phc->set_inherit)
+               fsx.fsx_xflags |= LL_PROJINHERIT_FL;
+       if (phc->set_projid)
+               fsx.fsx_projid = phc->projid;
+
+       ret = ioctl(fd, LL_IOC_FSSETXATTR, &fsx);
+       if (ret)
+               fprintf(stderr, "%s: failed to set xattr for '%s': %s\n",
+                       progname, pathname, strerror(errno));
+out:
+       close(fd);
+       return ret;
+}
+
+static int
+project_clear_one(const char *pathname, struct project_handle_control *phc)
+{
+       struct fsxattr fsx;
+       int ret = 0, fd;
+
+       fd = project_get_xattr(pathname, &fsx);
+       if (fd < 0)
+               return fd;
+
+       if ((!(fsx.fsx_xflags & LL_PROJINHERIT_FL)) &&
+            (fsx.fsx_projid == 0 || phc->keep_projid))
+               goto out;
+
+       fsx.fsx_xflags &= ~LL_PROJINHERIT_FL;
+       if (!phc->keep_projid)
+               fsx.fsx_projid = 0;
+
+       ret = ioctl(fd, LL_IOC_FSSETXATTR, &fsx);
+       if (ret)
+               fprintf(stderr, "%s: failed to set xattr for '%s': %s\n",
+                       progname, pathname, strerror(errno));
+out:
+       close(fd);
+       return ret;
+}
+
+static int
+lfs_project_handle_dir(struct list_head *head, const char *pathname,
+                      struct project_handle_control *phc,
+                      int (*func)(const char *,
+                                  struct project_handle_control *))
+{
+       char fullname[PATH_MAX];
+       struct dirent *ent;
+       DIR *dir;
+       int ret = 0;
+
+       dir = opendir(pathname);
+       if (dir == NULL) {
+               ret = -errno;
+               fprintf(stderr, "%s: failed to opendir '%s': %s\n",
+                       progname, pathname, strerror(-ret));
+               return ret;
+       }
+
+       while (ret == 0 && (ent = readdir(dir)) != NULL) {
+               /* skip "." and ".." */
+               if (strcmp(ent->d_name, ".") == 0 ||
+                   strcmp(ent->d_name, "..") == 0)
+                       continue;
+
+               if (strlen(ent->d_name) + strlen(pathname) >=
+                   sizeof(fullname) + 1) {
+                       ret = -ENAMETOOLONG;
+                       errno = ENAMETOOLONG;
+                       break;
+               }
+               snprintf(fullname, PATH_MAX, "%s/%s", pathname,
+                        ent->d_name);
+
+               ret = func(fullname, phc);
+               if (phc->recursive && ret == 0 && ent->d_type == DT_DIR)
+                       ret = lfs_project_item_alloc(head, fullname);
+       }
+
+       if (ret)
+               fprintf(stderr, "%s: failed to handle dir '%s': %s\n",
+                       progname, pathname, strerror(errno));
+
+       closedir(dir);
+       return ret;
+}
+
+static int lfs_project_iterate(const char *pathname,
+                              struct project_handle_control *phc,
+                              int (*func)(const char *,
+                                          struct project_handle_control *))
+{
+       struct lfs_project_item *lpi;
+       struct list_head head;
+       struct stat st;
+       int ret = 0;
+       bool top_dir = true;
+
+       ret = stat(pathname, &st);
+       if (ret) {
+               fprintf(stderr, "%s: failed to stat '%s': %s\n",
+                       progname, pathname, strerror(errno));
+               return ret;
+       }
+
+       /* list opeation will skip top directory in default */
+       if (!S_ISDIR(st.st_mode) || phc->dironly ||
+           project_list_one != func)
+               ret = func(pathname, phc);
+
+       /* dironly first, recursive will be ignored */
+       if (!S_ISDIR(st.st_mode) || phc->dironly || ret)
+               return ret;
+
+       INIT_LIST_HEAD(&head);
+       ret = lfs_project_item_alloc(&head, pathname);
+       if (ret)
+               return ret;
+
+       while (!list_empty(&head)) {
+               lpi = list_entry(head.next, struct lfs_project_item, lpi_list);
+               list_del(&lpi->lpi_list);
+               if (ret == 0) {
+                       ret = lfs_project_handle_dir(&head, lpi->lpi_pathname,
+                                                    phc, func);
+                       /* only ignore ENOENT error if this is
+                        * not top directory. */
+                       if (ret == -ENOENT && !top_dir)
+                               ret = 0;
+               }
+               free(lpi);
+               top_dir = false;
+       }
+
+       return ret;
+}
+
+
+inline int lfs_project_check(const char *pathname,
+                            struct project_handle_control *phc)
+{
+       return lfs_project_iterate(pathname, phc, project_check_one);
+}
+
+inline int lfs_project_clear(const char *pathname,
+                            struct project_handle_control *phc)
+{
+       return lfs_project_iterate(pathname, phc, project_clear_one);
+}
+
+inline int lfs_project_set(const char *pathname,
+                          struct project_handle_control *phc)
+{
+       return lfs_project_iterate(pathname, phc, project_set_one);
+}
+
+inline int lfs_project_list(const char *pathname,
+                           struct project_handle_control *phc)
+{
+       return lfs_project_iterate(pathname, phc, project_list_one);
+}
diff --git a/lustre/utils/lfs_project.h b/lustre/utils/lfs_project.h
new file mode 100644 (file)
index 0000000..66404ca
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * GPL HEADER START
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License version 2 for more details (a copy is included
+ * in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; If not, see
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * GPL HEADER END
+ */
+/*
+ * Copyright (c) 2017, DataDirect Networks Storage.
+ * Copyright (c) 2017, Intel Corporation.
+ */
+/*
+ * This file is part of Lustre, http://www.lustre.org/
+ *
+ * lustre/utils/lfs_project.h
+ *
+ * Author: Wang Shilong <wshilong@ddn.com>
+ * Author: Fan Yong <fan.yong@intel.com>
+ */
+#ifndef        _LFS_PROJECT_H
+#define        _LFS_PROJECT_H
+#include <stdbool.h>
+#include <linux/types.h>
+
+const char     *progname;
+
+enum lfs_project_ops_t {
+       LFS_PROJECT_CHECK       = 0,
+       LFS_PROJECT_CLEAR       = 1,
+       LFS_PROJECT_SET         = 2,
+       LFS_PROJECT_LIST        = 3,
+       LFS_PROJECT_MAX         = 4,
+};
+
+struct project_handle_control {
+       __u32   projid;
+       bool    assign_projid;
+       bool    set_inherit;
+       bool    set_projid;
+       bool    newline;
+       bool    keep_projid;
+       bool    recursive;
+       bool    dironly;
+};
+
+int lfs_project_list(const char *pathname,
+                    struct project_handle_control *phc);
+int lfs_project_check(const char *pathname,
+                     struct project_handle_control *phc);
+int lfs_project_clear(const char *pathname,
+                     struct project_handle_control *phc);
+int lfs_project_set(const char *pathname,
+                   struct project_handle_control *phc);
+#endif /* _LFS_PROJECT_H */