Whamcloud - gitweb
LU-9629 utils: fix lfs_migrate for non-root users 83/36383/5
authorAndreas Dilger <adilger@whamcloud.com>
Sat, 5 Oct 2019 06:03:51 +0000 (00:03 -0600)
committerOleg Drokin <green@whamcloud.com>
Tue, 22 Oct 2019 23:57:18 +0000 (23:57 +0000)
Allow lfs_migrate to work with non-root users even when there are
hard-linked files.  The use of "lfs fid2path" is only strictly
needed if "lfs migrate" is not working and the script falls back
to using "rsync" to migrate the hard-linked files.  In the common
case, "lfs migrate" will preserve the links to the file and all
that is needed is "path2fid" to record which FIDs have already
been migrated so that they are not migrated again.

There is no need to track files with only one link, so none of
this FID-handling infrastructure is needed in the common case.

Don't get the mountpoint (via "df") for each hard-linked file within
a single filesystem (which is normally all files).  This is only
needed if files are on different mountpoints, which can be detected
by the device number returned by stat(1) on the file.  Cache the
device number across stat calls, and if it doesn't change then use
the same mountpoint for the fid2path call.

Add named variables to index the fields in the "nlink_type" array to
make it easier to see what is being accessed and avoid bugs.

Fixes: 80a2ff7137d3 ("LU-6051 utils: allow lfs_migrate to handle hard links")
Test-Parameters: trivial
Signed-off-by: Andreas Dilger <adilger@whamcloud.com>
Change-Id: If37d9f73bd1e2ff261fdcfb5248b9e51ae42bd13
Reviewed-on: https://review.whamcloud.com/36383
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Ben Evans <bevans@cray.com>
Reviewed-by: Jian Yu <yujian@whamcloud.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
lustre/doc/lfs_migrate.1
lustre/scripts/lfs_migrate
lustre/tests/sanity.sh

index 6c04e76..bb5e604 100644 (file)
@@ -4,18 +4,18 @@
 \- migrate files between Lustre OSTs
 .SH SYNOPSIS
 .B lfs_migrate
-.RB [ --dry-run ]
-.RB [ --help|-h ]
+.RB [ "-A " [ -C \fI<cap> \fR] [ -M \fI<min_free> \fR] [ -X \fI<max_free> \fR]]
+.RB [ --dry-run | -n ]
+.RB [ --help | -h ]
 .RB [ --no-rsync | --rsync ]
-.RB [ --quiet|-q ]
-.RB [ --restripe|-R ]
-.RB [ --skip|-s ]
-.RB [ --verbose|-v ]
-.RB [ --yes|-y ]
+.RB [ --quiet | -q ]
+.RB [ --restripe | -R ]
+.RB [ --stripe-count | -c \fI<stripe_count> \fR]
+.RB [ --stripe-size | -S \fI<stripe_size> \fR]
+.RB [ --skip | -s ]
+.RB [ --verbose | -v ]
+.RB [ --yes | -y ]
 .RB [ -D ]
-.RB [ -A [ -C \fI<cap> \fR] [ -M \fI<min_free> \fR] [ -X \fI<max_free> \fR]]
-.RB [ --stripe-count|-c \fI<stripe_count> \fR]
-.RB [ --stripe-size|-S \fI<stripe_size> \fR]
 .RB [ -0 ]
 .RI [ FILE | DIR ] ...
 .br
index eb0799d..eec337d 100755 (executable)
@@ -286,6 +286,9 @@ function calc_stripe()
 }
 
 lfs_migrate() {
+       local last_dev
+       local mntpoint
+
        while IFS='' read -d '' OLDNAME; do
                local hlinks=()
                local stripe_size="$OPT_STRIPE_SIZE"
@@ -295,16 +298,23 @@ lfs_migrate() {
                local stripe_pool
                local mirror_count
                local layout
+               local fid
 
                $ECHO -n "$OLDNAME: "
 
-               # avoid duplicate stat if possible
-               local nlink_type=($(LANG=C stat -c "%h %F %s" "$OLDNAME" \
+               # avoid duplicate stat call by fetching all attrs at once
+               local nlink_idx_link=0 # %h is the hard link count
+               local nlink_idx_type=1 # %F is "regular file", ignore others
+               local nlink_idx_file=2 #       "file" is here
+               local nlink_idx_size=3 # %s is file size in bytes
+               local nlink_idx_dev=4  # %D is the underlying device number
+               # nlink_type=(1 regular file 1234 0x810)
+               local nlink_type=($(LANG=C stat -c "%h %F %s %D" "$OLDNAME" \
                                 2> /dev/null))
 
                # skip non-regular files, since they don't have any objects
                # and there is no point in trying to migrate them.
-               if [ "${nlink_type[1]}" != "regular" ]; then
+               if [ "${nlink_type[$nlink_idx_type]}" != "regular" ]; then
                        echo -e "\r$OLDNAME: not a regular file, skipped" 1>&2
                        continue
                fi
@@ -329,18 +339,14 @@ lfs_migrate() {
                fi
                OLDNAME=$oldname_absolute
 
-               # In the future, the path2fid and fid2path calls below
-               # should be replaced with a single call to
-               # "lfs path2links" once that command is available.  The logic
-               # for detecting unlisted hard links could then be removed.
-               local fid=$($LFS path2fid "$OLDNAME" 2> /dev/null)
-               if [ $? -ne 0 ]; then
-                       echo -e "\r$OLDNAME: cannot determine FID; skipping; " \
-                               "is this a Lustre file system?" 1>&2
-                       continue
-               fi
+               if [[ ${nlink_type[$nlink_idx_link]} -gt 1 ]] ||
+                  $RSYNC_WITH_HLINKS; then
+                       fid=$($LFS path2fid "$OLDNAME" 2> /dev/null)
+                       if [ $? -ne 0 ]; then
+                               echo -e "\r$OLDNAME: cannot get FID, skipping; is this a Lustre file system?" 1>&2
+                               continue
+                       fi
 
-               if [[ ${nlink_type[0]} -gt 1 ]] || $RSYNC_WITH_HLINKS; then
                        # don't migrate a hard link if it was already migrated
                        if path_in_set "$OLDNAME"; then
                                $ECHO "already migrated via another hard link"
@@ -355,14 +361,12 @@ lfs_migrate() {
                        local migrated=$(old_fid_in_set "$fid")
                        if [ -n "$migrated" ]; then
                                $ECHO "already migrated via another hard link"
-                               if $OPT_RSYNC; then
-                                       # Only the rsync case has to relink.
-                                       # The lfs migrate case preserves the
-                                       # inode so the links are already
-                                       # correct.
-                                       [ "$migrated" != "$OLDNAME" ] &&
-                                               ln -f "$migrated" "$OLDNAME"
-                               fi
+                               # Only the rsync case has to relink.  The
+                               # "lfs migrate" case keeps the same inode so
+                               # all of the links are already correct.
+                               $OPT_RSYNC && [ "$migrated" != "$OLDNAME" ] &&
+                                       ln -f "$migrated" "$OLDNAME"
+
                                add_to_set "$fid" "$OLDNAME"
                                continue;
                        fi
@@ -371,17 +375,17 @@ lfs_migrate() {
                if $OPT_RESTRIPE; then
                        UNLINK=""
                else
-                       # if rsync copies Lustre xattrs properly in the future
+                       # If rsync copies Lustre xattrs properly in the future
                        # (i.e. before the file data, so that it preserves
-                       # striping) then we don't need to do this getstripe
-                       # stuff.
+                       # striping) then we don't need this getstripe stuff.
                        UNLINK="-u"
 
                        stripe_pool=$($LFS getstripe -p "$OLDNAME" 2> /dev/null)
                        mirror_count=$($LFS getstripe -N "$OLDFILE" 2> /dev/null)
 
                        if $OPT_AUTOSTRIPE; then
-                               local filekb=$((${nlink_type[3]} / 1024))
+                               local filekb=$((${nlink_type[$nlink_idx_size]} /
+                                               1024))
 
                                read stripe_count OBJ_MAX_KB < <(calc_stripe \
                                        "$OLDNAME" "$filekb" "$OBJ_MAX_KB")
@@ -439,24 +443,30 @@ lfs_migrate() {
 
                # detect other hard links and store them on a global
                # list so we don't re-migrate them
-               local mntpoint=$(df -P "$OLDNAME" |
-                               awk 'NR==2 { print $NF; exit }')
-               if [ -z "$mntpoint" ]; then
-                       echo -e "\r$OLDNAME: cannot determine mount point; skipped" 1>&2
-                       continue
-               fi
-               hlinks=$($LFS fid2path "$mntpoint" "$fid" 2> /dev/null)
-               if [ $? -ne 0 ]; then
-                       echo -e "\r$OLDNAME: cannot determine hard link paths, skipped" 1>&2
-                       continue
+               if [[ ${nlink_type[$nlink_idx_link]} -gt 1 ]]; then
+                       [ "${nlink_type[$nlink_idx_dev]}" == "$last_dev" ] ||
+                               mntpoint=$(df -P "$OLDNAME" |
+                                          awk 'NR==2 { print $NF }')
+                       if [ -z "$mntpoint" ]; then
+                               echo -e "\r$OLDNAME: cannot determine mount point; skipped" 1>&2
+                               continue
+                       fi
+                       hlinks=$($LFS fid2path "$mntpoint" "$fid" 2> /dev/null)
+                       if $OPT_RSYNC && [ $? -ne 0 ]; then
+                               echo -e "\r$OLDNAME: cannot determine hard link paths, skipped" 1>&2
+                               continue
+                       fi
+                       hlinks+=("$OLDNAME")
+               else
+                       hlinks=
                fi
-               hlinks+=("$OLDNAME")
 
                # first try to migrate via Lustre tools, then fall back to rsync
                if ! $OPT_RSYNC; then
                        if $LFS migrate "${OPT_PASSTHROUGH[@]}" $layout \
                           "$OLDNAME"; then
                                $ECHO "done"
+                               # no-op if hlinks empty for 1-link files
                                for link in ${hlinks[*]}; do
                                        add_to_set "$fid" "$link"
                                done
@@ -501,6 +511,7 @@ lfs_migrate() {
                fi
 
                $ECHO "done rsync"
+               # no-op if hlinks empty for 1-link files
                for link in ${hlinks[*]}; do
                        if [ "$link" != "$OLDNAME" ]; then
                                ln -f "$OLDNAME" "$link"
index 1e7d1e1..2e8cd41 100644 (file)
@@ -6231,7 +6231,7 @@ test_56w() {
        done
 
        # $LFS_MIGRATE will fail if hard link migration is unsupported
-       if [[ $(lustre_version_code mds1) -gt $(version_code 2.5.55) ]]; then
+       if [[ $MDS1_VERSION -gt $(version_code 2.5.55) ]]; then
                createmany -l$dir/dir1/file1 $dir/dir1/link 200 ||
                        error "creating links to $dir/dir1/file1 failed"
        fi
@@ -6569,6 +6569,7 @@ check_migrate_links() {
        local file1="$dir/file1"
        local begin="$2"
        local count="$3"
+       local runas="$4"
        local total_count=$(($begin + $count - 1))
        local symlink_count=10
        local uniq_count=10
@@ -6613,7 +6614,7 @@ check_migrate_links() {
        fi
 
        echo -n "migrating files..."
-       local migrate_out=$($LFS_MIGRATE -y -S '1m' $dir)
+       local migrate_out=$($runas $LFS_MIGRATE -y -S '1m' $dir)
        local rc=$?
        [ $rc -eq 0 ] || error "migrate failed rc = $rc"
        echo "done"
@@ -6668,6 +6669,9 @@ test_56xb() {
        echo "testing rsync mode when all links do not fit within xattrs"
        LFS_MIGRATE_RSYNC_MODE=true check_migrate_links "$dir" 101 100
 
+       chown -R $RUNAS_ID $dir
+       echo "testing non-root lfs migrate mode when not all links are in xattr"
+       LFS_MIGRATE_RSYNC_MODE=false check_migrate_links "$dir" 101 100 "$RUNAS"
 
        # clean up
        rm -rf $dir