From dd009a221d14feb463ef1179559e5bbab22efa08 Mon Sep 17 00:00:00 2001 From: Andreas Dilger Date: Sat, 5 Oct 2019 00:03:51 -0600 Subject: [PATCH] LU-9629 utils: fix lfs_migrate for non-root users 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 Change-Id: If37d9f73bd1e2ff261fdcfb5248b9e51ae42bd13 Reviewed-on: https://review.whamcloud.com/36383 Tested-by: jenkins Tested-by: Maloo Reviewed-by: Ben Evans Reviewed-by: Jian Yu Reviewed-by: Oleg Drokin --- lustre/doc/lfs_migrate.1 | 20 +++++------ lustre/scripts/lfs_migrate | 85 ++++++++++++++++++++++++++-------------------- lustre/tests/sanity.sh | 8 +++-- 3 files changed, 64 insertions(+), 49 deletions(-) diff --git a/lustre/doc/lfs_migrate.1 b/lustre/doc/lfs_migrate.1 index 6c04e76..bb5e604 100644 --- a/lustre/doc/lfs_migrate.1 +++ b/lustre/doc/lfs_migrate.1 @@ -4,18 +4,18 @@ \- migrate files between Lustre OSTs .SH SYNOPSIS .B lfs_migrate -.RB [ --dry-run ] -.RB [ --help|-h ] +.RB [ "-A " [ -C \fI \fR] [ -M \fI \fR] [ -X \fI \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 \fR] +.RB [ --stripe-size | -S \fI \fR] +.RB [ --skip | -s ] +.RB [ --verbose | -v ] +.RB [ --yes | -y ] .RB [ -D ] -.RB [ -A [ -C \fI \fR] [ -M \fI \fR] [ -X \fI \fR]] -.RB [ --stripe-count|-c \fI \fR] -.RB [ --stripe-size|-S \fI \fR] .RB [ -0 ] .RI [ FILE | DIR ] ... .br diff --git a/lustre/scripts/lfs_migrate b/lustre/scripts/lfs_migrate index eb0799d..eec337d 100755 --- a/lustre/scripts/lfs_migrate +++ b/lustre/scripts/lfs_migrate @@ -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" diff --git a/lustre/tests/sanity.sh b/lustre/tests/sanity.sh index 1e7d1e1..2e8cd41 100644 --- a/lustre/tests/sanity.sh +++ b/lustre/tests/sanity.sh @@ -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 -- 1.8.3.1