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-setdirstripe.1                      \
        lfs-setstripe.1                         \
        lfs-setquota.1                          \
+       lfs-project.1                           \
        l_getidentity.8                         \
        lgss_sk.8                               \
        lhbadm.8                                \
        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,
 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
 .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-df (1),
-.BR lfs-hsm (1),
-.BR lfs-setdirstripe (1),
 .BR lfs-getdirstripe (1),
 .BR lfs-getdirstripe (1),
+.BR lfs-hsm (1),
 .BR lfs-mkdir (1),
 .BR lfs-mkdir (1),
-.BR lfs_migrate (1),
-.BR lfs-setstripe (1),
 .BR lfs-migrate (1),
 .BR lfs-migrate (1),
+.BR lfs-project (1),
+.BR lfs-setdirstripe (1),
 .BR lfs-setquota (1),
 .BR lfs-setquota (1),
-.BR lctl (8),
+.BR lfs-setstripe (1),
 .BR lustre (7)
 .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
 #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
 
 
 #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() {
 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) ] &&
        [ "$(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
                egrep -q "7." /etc/redhat-release && return 0
 
        if [ "$(facet_fstype $SINGLEMDS)" == "zfs" ]; then
@@ -117,8 +116,8 @@ lustre_fail() {
 
 change_project()
 {
 
 change_project()
 {
-       echo "chattr $*"
-       chattr $* || error "chattr $* failed"
+       echo "lfs project $*"
+       lfs project $* || error "lfs project $* failed"
 }
 
 RUNAS="runas -u $TSTID -g $TSTID"
 }
 
 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"
 
        [ $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"
        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)
 
        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 \
                # 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 ||
 
                $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
 
        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)) ||
 
        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
        $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 ||
                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!"
 
        $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
        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
        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 -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 -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"
 
        [ $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
        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"
 
        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
 
        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 $?"
 
        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
        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"
 
        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"
        $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.
        [ "$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"
 
        [ "$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"
 
        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"
 
        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"
 
        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"
 
        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"
                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.
 
        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"
                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"
 
 
        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"
 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
 
 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
 
 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 <dirent.h>
 #include <time.h>
 #include <ctype.h>
+#include "lfs_project.h"
 
 #include <libcfs/util/string.h>
 #include <libcfs/util/ioctl.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);
 #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);
 #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"
 
        "\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.
  */
 /**
  * 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>"},
                       "<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...]"},
 #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;
 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 */