Viewing: lfs_migrate
#!/usr/bin/bash
# lfs_migrate: a simple tool to copy and check files.
#
# To avoid allocating objects on one or more OSTs, they should be
# deactivated on the MDS via "lctl --device {device_number} deactivate",
# where {device_number} is from the output of "lctl dl" on the MDS.
#
# To guard against corruption, the file is compared after migration
# to verify the copy is correct and the file has not been modified.
# This is not a protection against the file being open by another
# process, but it would catch the worst cases of in-use files, but
# to be 100% safe the administrator needs to ensure this is safe.
#
# WARNING: lfs_migrate is scheduled for removal after Lustre 2.20, please
# use 'lfs migrate' instead.
RSYNC=${RSYNC:-"rsync"}
RSYNC_OPTS=${RSYNC_OPTS:-"-a --inplace"}
ECHO=echo
LFS=${LFS:-lfs}
RSYNC_WITH_HLINKS=false
LFS_MIGRATE_TMP=${TMPDIR:-/tmp}
MIGRATED_SET="$(mktemp ${LFS_MIGRATE_TMP}/lfs_migrate.links.XXXXXX)"
NEWNAME=""
REMOVE_FID='s/^\[[0-9a-fx:]*\] //'
PROG=$(basename $0)
add_to_set() {
local old_fid="$1"
local path="$2"
echo "$old_fid $path" >> "$MIGRATED_SET"
}
path_in_set() {
local path="$1"
sed -e "$REMOVE_FID" $MIGRATED_SET | grep -q "^$path$"
}
old_fid_in_set() {
local old_fid="$1"
grep "^\\$old_fid" "$MIGRATED_SET" | head -n 1 |
sed -e "$REMOVE_FID"
}
usage() {
cat -- <<USAGE 1>&2
usage: lfs_migrate [--dry-run|-n] [--help|-h] [--no-rsync|--rsync|--cp]
[--auto-stripe|-A [-C <cap>]
[--fid|-F [--lustre-dir <lustre_dir>]]
[--min-free|-M <min_free>] [--max-free|-X <max_free>]]
[--pool|-p <pool>] [--stripe-count|-c <stripe_count>]
[--stripe-size|-S <stripe_size>] [--quiet|-q]
[-D] [-h] [-n] [-S]
[--restripe|-R] [--skip|-s] [--verbose|-v] [--yes|-y] [-0]
[FILE|FID|DIR...]
-A restripe file using an automatically selected stripe count,
uses stripe_count = sqrt(size_in_GB) + 1
-c <stripe_count>
restripe file using the specified <stripe_count>
-C <cap> when -A is set, limit the migrated file to use on each OST
at most 1/<cap> of the available space of the smallest OST
--cp force the use of 'cp' instead of migrate or rsync
-D do not use direct I/O to copy file contents
--fid, -F FID(s) are specified instead of file path(s)
-h show this usage message
--lustre-dir <lustre_dir>
the Lustre mount point where FID(s) are when --fid is specified.
If there is exactly one mounted Lustre file system, this option
is not necessary
-M <min_free>
when -A is set, an OST must contain more available space than
<min_free> KB in order for it to be considered available for
use in the migration
--no-rsync do not fall back to rsync even if lfs migrate fails (default)
-n only print the names of files to be migrated
-p <pool> use the specified OST pool for the destination file
-q run quietly (don't print filenames or status)
--rsync force rsync mode instead of using lfs migrate
-R restripe file using default directory striping
-s skip file data comparison after migrate
-S <stripe_size>
restripe file using the specified stripe size
-v show verbose debug messages
-X <max_free>
when -A is set, limit the amount of space on each OST that
can be considered available for the migration to
<max_free> KB
-y answer 'y' to usage question (only when --rsync/--cp used)
-0 input file names on stdin are separated by a null character
Options '-A', and '-R' are mutually exclusive with each other, and any
specific layout (e.g. any specific parameters like '-c', '-S', '-E', '-p').
Options '-C', '-M', and '-X' are ignored if '-A' is not set.
Options --rsync, --cp, --no-rsync may not be specified at the same time.
If a directory is an argument, all files in the directory are migrated.
If no file/directory is given, the file list is read from standard input.
Any arguments that are not explicitly recognized by the script are passed
through to the 'lfs migrate' utility.
If emptying an active OST, new files may continue to be allocated there.
To prevent this, on each MDS run the following for each OST being emptied:
lctl set_param osp.<fsname>-OSTxxxx*.max_create_count=0
Examples:
lfs_migrate /mnt/lustre/dir
lfs_migrate -p newpool /mnt/lustre/dir
lfs find /test -O test-OST0004 -size +4G | lfs_migrate
lfs_migrate -o 0 --lustre-dir /mnt/lustre --fid 0x200000401:0x1ae6:0x0
USAGE
exit 1
}
cleanup() {
rm -f "$MIGRATED_SET"
[ -n "$NEWNAME" ] && rm -f "$NEWNAME"
}
trap cleanup EXIT
# Print deprecation warning to stderr
echo "WARNING: lfs_migrate is scheduled for removal after Lustre 2.20, please use 'lfs migrate' instead." 1>&2
OPT_AUTOSTRIPE=false
OPT_CAP=100
OPT_CHECK=true
OPT_COMP=false
OPT_COPY=false
OPT_DEBUG=false
OPT_DRYRUN=false
OPT_FID=false
OPT_FILE=()
OPT_LAYOUT=()
OPT_LUSTRE_DIR=""
OPT_MAXFREE=""
OPT_MINFREE=262144
OPT_NO_DIRECT=false
OPT_NO_RSYNC=true
OPT_NULL=false
OPT_PASSTHROUGH=()
OPT_POOL=""
OPT_RESTRIPE=false
OPT_RSYNC=false
OPT_STRIPE_COUNT=""
OPT_STRIPE_SIZE=""
OPT_YES=false
# Examine any long options and arguments. getopts does not support long
# options, so they must be stripped out and classified as either options
# for the script, or passed through to "lfs migrate".
while [ -n "$*" ]; do
arg="$1"
case "$arg" in
--auto-stripe|-A) OPT_AUTOSTRIPE=true;;
-b|--block|--non-block|--non-direct|-D|--no-verify)
# Always pass non-layout options to 'lfs migrate'
OPT_PASSTHROUGH+=("$arg");;
--cp) $OPT_RSYNC && [[ "$RSYNC" != "cp" ]] && OPT_NO_RSYNC=true
OPT_RSYNC=true; RSYNC="cp"; RSYNC_OPTS="-p";;
--copy|--yaml|--file) OPT_COPY=true;
# these options have files as arguments, pass both through
OPT_LAYOUT+="$arg $2 "; shift;;
--dry-run) OPT_DRYRUN=true; OPT_YES=true;;
-c|--stripe-count) # if within a component, pass through stripe_count
$OPT_COMP && OPT_LAYOUT+="$arg $2 " || OPT_STRIPE_COUNT="$2"
shift;;
-C) OPT_CAP="$2"; shift;;
-D) LFS_OPT_DIRECTIO="-D";;
-E|--comp-end|--component-end) OPT_COMP=true; OPT_LAYOUT+="$arg ";;
-F|--fid) OPT_FID=true;;
-h|--help) usage;;
-l|--link) ;; # maintained backward compatibility for now
--lustre-dir) OPT_LUSTRE_DIR="$2"; shift;;
-M|--min-free) OPT_MINFREE="$2"; shift;;
-n) OPT_DRYRUN=true; OPT_YES=true
echo "$PROG: -n deprecated, use --dry-run or --non-block" 1>&2;;
-p|--pool) # if within a component, pass through pool
$OPT_COMP && OPT_LAYOUT+="$arg $2 " || OPT_POOL="-p $2";
shift;;
-q|--quiet) ECHO=:;;
-R|--restripe) OPT_RESTRIPE=true;;
--no-rsync) OPT_NO_RSYNC=true; OPT_RSYNC=false;;
--rsync) [[ "$RSYNC" != "rsync" ]] && OPT_NO_RSYNC=true || OPT_NO_RSYNC=false
OPT_RSYNC=true ;;
-s|--skip) OPT_CHECK=false;;
-S|--stripe-size) # if within a component, pass through stripe_size
$OPT_COMP && OPT_LAYOUT+="$arg $2 " || OPT_STRIPE_SIZE="$2"
shift;;
-v|--verbose) OPT_DEBUG=true; ECHO=echo;;
-X|--max-free) OPT_MAXFREE="$2"; shift;;
-y|--yes) OPT_YES=true;;
-0) OPT_NULL=true;;
*)
if [[ -e "$arg" ||
"$arg" =~ ^\[?0x[0-9a-f]+:0x[0-9a-f]+:0x[0-9a-f]+\]?$ ]]; then
# hit file path or fid, end of options
OPT_FILE+="$arg "
break
else
# Pass other non-file layout options to 'lfs migrate'
OPT_LAYOUT+="$arg "
fi
;;
esac
shift
done
if ( $OPT_RESTRIPE || $OPT_AUTOSTRIPE ) &&
[[ -n "$OPT_STRIPE_COUNT" || -n "$OPT_STRIPE_SIZE" || -n "$OPT_POOL" ]]; then
echo "$PROG error: option -R or -A cannot be used with -c, -S, or -p" 1>&2
exit 1
elif ( $OPT_RESTRIPE || $OPT_AUTOSTRIPE ) && [[ -n "$OPT_LAYOUT" ]]; then
echo "$PROG error: option -R or -A cannot be used with $OPT_LAYOUT" 1>&2
exit 1
elif $OPT_RESTRIPE && $OPT_AUTOSTRIPE; then
echo "$PROG error: option -R cannot be used with -A" 1>&2
exit 1
fi
if $OPT_RSYNC && $OPT_NO_RSYNC; then
echo "$PROG: Options --cp, --rsync, and --no-rsync may not be" \
"specified at the same time." 1>&2
exit 1
fi
if $OPT_RSYNC && ! $OPT_YES; then
echo ""
echo "'lfs_migrate --rsync' is NOT SAFE for moving in-use files." 1>&2
echo "Use it only when you are sure migrated files are unused." 1>&2
echo "" 1>&2
echo -n "Continue? (y/n) "
read CHECK
[ "$CHECK" != "y" -a "$CHECK" != "yes" ] && exit 1
fi
if $OPT_FID; then
if [[ -z "$OPT_LUSTRE_DIR" ]]; then
# guess the only mounted lustre filesystem
lustre_mounts=($(mount -t lustre | awk '/:\// { print $3 }'))
if (( ${#lustre_mounts[*]} > 1 )); then
echo "$PROG: --fid requires --lustre-dir if there are multiple lustre mountpoints" 1>&2
exit 1
elif (( ${#lustre_mounts[*]} == 0 )); then
echo "$PROG: there is no lustre mountpoint" 1>&2
exit 1
fi
OPT_LUSTRE_DIR=${lustre_mounts[0]}
elif [[ ${OPT_LUSTRE_DIR:0:1} == "-" ]]; then
# the next argument name is save as OPT_LUSTRE_DIR by mistake
echo "$PROG: Option --lustre-dir must have value" 1>&2
exit 1
fi
if ! $LFS path2fid "$OPT_LUSTRE_DIR" > /dev/null 2>&1; then
echo "$PROG: invalid --lustre-dir '$OPT_LUSTRE_DIR'" 1>&2
exit 1
fi
fi
# if rsync has --xattr support, then try to copy the xattrs.
$RSYNC --help 2>&1 | grep -q -- --xattrs && RSYNC_OPTS="$RSYNC_OPTS --xattrs"
$RSYNC --help 2>&1 | grep -q -- --acls && RSYNC_OPTS="$RSYNC_OPTS --acls"
# If rsync copies lustre xattrs in the future, then we can skip lfs (bug 22189)
strings $(which $RSYNC) 2>&1 | grep -q lustre && LFS=:
# rsync creates its temporary files with lenient permissions, even if
# permissions on the original files are more strict. Tighten umask here
# to avoid the brief window where unprivileged users might be able to
# access the temporary file.
umask 0077
# Use stripe count = sqrt(size_in_GB) + 1, but cap object size per OST.
function calc_stripe()
{
local filename=$1
local filekb=$2
local obj_max_kb=$3
local filegb=$((filekb / 1048576))
local stripe_count=1
local ost_max_count=0
# Files up to 1GB will have 1 stripe if they fit within the object max
if [[ $filegb -lt 1 && "$obj_max_kb" && $filekb -le $obj_max_kb ]]; then
echo 1 "$obj_max_kb" && return
fi
stripe_count=$(bc <<< "scale=0; 1 + sqrt($filegb)" 2> /dev/null) ||
{ echo "cannot auto calculate stripe count" >&2; return; }
if [ -z "$obj_max_kb" ]; then
local ost_min_kb=$((1 << 62))
# Calculate cap on object size at 1% of smallest OST
# but only include OSTs that have 256MB+ available space
while IFS='' read avail; do
[[ "$OPT_MAXFREE" && $avail -gt $OPT_MAXFREE ]] &&
avail=$OPT_MAXFREE
if [ $avail -ge $OPT_MINFREE ]; then
ost_max_count=$((ost_max_count + 1))
if [ $avail -lt $ost_min_kb ]; then
ost_min_kb=$avail
fi
fi
done < <($LFS df $OPT_POOL "$OLDNAME" | awk '/OST/ { print $4 }')
if [ $ost_max_count -eq 0 ]; then
# no OSTs with enough space, stripe over all of them
echo "-1" "0"
return
fi
if (( ost_min_kb == (1 << 62) )); then
echo "warning: unable to determine minimum OST size, " \
"object size not capped" >&2
echo "$stripe_count" "0"
return
fi
obj_max_kb=$((ost_min_kb / $OPT_CAP))
elif [ $obj_max_kb -eq 0 ]; then
echo "warning: unable to determine minimum OST size " \
"from previous migrate, object size not capped" >&2
echo "$stripe_count" "$obj_max_kb"
return
fi
# If disk usage would exceed the cap, increase the number of stripes.
# Round up to the nearest MB to ensure file will fit.
(( filekb > stripe_count * obj_max_kb )) &&
stripe_count=$(((filekb + obj_max_kb - 1024) / obj_max_kb))
# Limit the count to the number of eligible OSTs
if [ "$stripe_count" -gt $ost_max_count ]; then
echo "$ost_max_count" "$obj_max_kb"
else
echo "$stripe_count" "$obj_max_kb"
fi
}
lfs_migrate() {
local last_dev
local mntpoint
while IFS='' read -d '' OLDNAME; do
local hlinks=()
local layout
local fid
if $OPT_FID; then
fid=$OLDNAME
OLDNAME=$($LFS fid2path "$OPT_LUSTRE_DIR" "$fid")
if (( $? != 0 )); then
echo "$PROG: invalid FID $fid." 1>&2
exit 1
fi
fi
$ECHO -n "$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[$nlink_idx_type]}" != "regular" ]; then
echo -e "\r$OLDNAME: not a regular file, skipped" 1>&2
continue
fi
# working out write perms is hard, let the shell do it
if [ ! -w "$OLDNAME" ]; then
echo -e "\r$OLDNAME: no write permission, skipped" 1>&2
continue
fi
if $OPT_DRYRUN && ! $OPT_DEBUG; then
$ECHO "dry run, skipped"
continue
fi
# xattrs use absolute file paths, so ensure provided path is
# also absolute so that the names can be compared
local oldname_absolute=$(readlink -f "$OLDNAME")
if [ -z "$oldname_absolute" ]; then
echo -e "\r$OLDNAME: cannot resolve full path, skipped" 1>&2
continue
fi
OLDNAME=$oldname_absolute
if [[ ${nlink_type[$nlink_idx_link]} -gt 1 ]] ||
$RSYNC_WITH_HLINKS; then
$OPT_FID || 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
# don't migrate a hard link if it was already migrated
if path_in_set "$OLDNAME"; then
$ECHO "already migrated via another hard link"
continue
fi
# There is limited space available in the xattrs
# to store all of the hard links for a file, so it's
# possible that $OLDNAME is part of a link set but is
# not listed in xattrs and therefore not listed as
# being migrated.
local migrated=$(old_fid_in_set "$fid")
if [ -n "$migrated" ]; then
$ECHO "already migrated via another hard link"
# 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
fi
local olddir=$(dirname "$OLDNAME")
local stripe_size="$OPT_STRIPE_SIZE"
local stripe_count="$OPT_STRIPE_COUNT"
local getstripe_opts="-N --comp-count -c -S -p -y"
local parent_count=""
local parent_size=""
local stripe_pool="${OPT_POOL#-p }"
local mirror_count=1
local comp_count=0
layout="${OPT_PASSTHROUGH[@]} "
if $OPT_RESTRIPE; then
UNLINK=""
layout+="--copy $olddir"
OPT_COPY=true
elif ! $OPT_COMP; then
# this formatting is no longer consistent LU-15565
# avoid multiple getstripe calls
# lcm_mirror_count: 1
# lcm_entry_count: 0
# lmm_stripe_count: 1
# lmm_stripe_size: 1048576
# lmm_pool: pool_abc
local old_stripe_count
local old_stripe_size
local old_layout
old_layout=($($LFS getstripe $getstripe_opts $OLDNAME \
2>/dev/null | awk '{ print }'))
for ((i = 0; i < ${#old_layout[*]}; i++)); do
local info_this=${old_layout[$i]}
local info_next=${old_layout[$i+1]}
local idx=$((i + 1))
$OPT_DEBUG && echo "$i: $info_this"
case "${old_layout[$i]}" in
*mirror_count:)
mirror_count=${old_layout[$idx]}
$OPT_DEBUG && echo "mirror=$info_next";;
*entry_count:)
comp_count=${old_layout[$idx]}
$OPT_DEBUG && echo "comp=$info_next";;
*stripe_count:)
old_stripe_count=${old_layout[$idx]}
$OPT_DEBUG && echo "count=$info_next";;
*stripe_size:)
old_stripe_size=${old_layout[$idx]}
$OPT_DEBUG && echo "size=$info_next";;
*lmm_pool:)
stripe_pool=${stripe_pool:-${old_layout[$idx]}}
$OPT_DEBUG && echo "pool=$info_next";;
esac
done
# 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 this getstripe stuff.
UNLINK="-u"
if $OPT_AUTOSTRIPE; then
local filekb=$((${nlink_type[$nlink_idx_size]} /
1024))
read stripe_count OBJ_MAX_KB < <(calc_stripe \
"$OLDNAME" "$filekb" "$OBJ_MAX_KB")
[[ -n "$stripe_count" ]] || exit 1
(( $stripe_count >= 1 )) || stripe_count=1
else
stripe_count=${stripe_count:-$old_stripe_count}
fi
stripe_size=${stripe_size:-$old_stripe_size}
[[ -n "$stripe_count" && -n "$stripe_size" ]] || UNLINK=
fi
if $OPT_DEBUG; then
local parent_count
local parent_size
local parent_layout
if $OPT_RESTRIPE; then
parent_layout=($($LFS getstripe $getstripe_opts\
-d "$olddir" 2>/dev/null |
awk '{ print }'))
for ((i = 0; i < ${#parent_layout[*]}; i++)); do
local idx=$((i + 1))
case "${parent_layout[$i]}" in
*stripe_count:)
parent_count=${parent_layout[$idx]};;
*stripe_size:)
parent_size=${parent_layout[$idx]};;
*pool:)
stripe_pool=${parent_layout[$idx]};;
*mirror_count:)
mirror_count=${parent_layout[$idx]};;
esac
done
fi
$ECHO -n "stripe_count=${stripe_count:-$parent_count},stripe_size=${stripe_size:-$parent_size}"
[[ -z "$stripe_pool" ]] ||
$ECHO -n ",pool=${stripe_pool}"
(( $mirror_count <= 1 )) ||
$ECHO -n ",mirror_count=${mirror_count}"
$ECHO -n " "
fi
if $OPT_DRYRUN; then
$ECHO " dry run, skipped"
continue
fi
if ! $OPT_COPY && ! $OPT_COMP && ! $OPT_RSYNC &&
(( $comp_count > 0 )); then
layout+="--copy $OLDNAME"
OPT_COPY=true
fi
if ! $OPT_COPY && ! $OPT_COMP; then
[[ -z "$stripe_count" ]] || layout+="-c $stripe_count "
[[ -z "$stripe_size" ]] || layout+="-S $stripe_size "
[[ -z "$stripe_pool" ]] || layout+="-p $stripe_pool "
(( $mirror_count <= 1 )) || layout+="-N $mirror_count "
fi
layout+="$OPT_LAYOUT"
# detect other hard links and store them on a global
# list so we don't re-migrate them
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
# first try to migrate via Lustre tools, then fall back to rsync
if ! $OPT_RSYNC; then
if $OPT_DEBUG; then
echo -e "\n$LFS migrate $layout \"$OLDNAME\""
fi
if $LFS migrate $layout "$OLDNAME"; then
(( $mirror_count > 1 )) &&
$LFS mirror resync "$OLDNAME"
$ECHO "done"
# no-op if hlinks empty for 1-link files
for link in ${hlinks[*]}; do
add_to_set "$fid" "$link"
done
continue
elif $OPT_NO_RSYNC; then
echo -e "\r$OLDNAME: refusing to fall back to rsync, skipped" 1>&2
continue
else
$ECHO -n "falling back to rsync: "
OPT_RSYNC=true
fi
fi
local oldfile=$(basename "$OLDNAME")
NEWNAME=$(mktemp $UNLINK "$olddir/.$oldfile.XXXXXX")
if [ $? -ne 0 -o -z "$NEWNAME" ]; then
echo -e "\r$OLDNAME: cannot make temp file, skipped" 1>&2
continue
fi
if [ "$UNLINK" ]; then
if ! $LFS setstripe $layout "$NEWNAME"; then
echo -e "\r$NEWNAME: setstripe failed, exiting" 1>&2
exit 2
fi
fi
# we use --inplace, since we created our own temp file already
if ! $RSYNC $RSYNC_OPTS "$OLDNAME" "$NEWNAME";then
echo -e "\r$OLDNAME: copy error, exiting" 1>&2
exit 4
fi
if $OPT_CHECK && ! cmp -s "$OLDNAME" "$NEWNAME"; then
echo -e "\r$NEWNAME: compare failed, exiting" 1>&2
exit 8
fi
if ! mv "$NEWNAME" "$OLDNAME"; then
echo -e "\r$OLDNAME: rename error, exiting" 1>&2
exit 12
fi
(( $mirror_count > 1 )) && $LFS mirror resync "$OLDNAME"
$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"
fi
add_to_set "$fid" "$link"
done
# If the number of hlinks exceeds the space in the xattrs,
# when the final path is statted it will have a link count
# of 1 (all other links will point to the new inode).
# This flag indicates that even paths with a link count of
# 1 are potentially part of a link set.
(( ${#hlinks[*]} == 1 )) || RSYNC_WITH_HLINKS=true
done
}
if [ "$#" -eq 0 ]; then
if $OPT_NULL; then
lfs_migrate
else
tr '\n' '\0' | lfs_migrate
fi
else
while [ "$1" ]; do
if [ -d "$1" ]; then
$LFS find "$1" -type f -print0
else
echo -en "$1\0"
fi
shift
done | lfs_migrate
fi