3 # lfs_migrate: a simple tool to copy and check files.
5 # To avoid allocating objects on one or more OSTs, they should be
6 # deactivated on the MDS via "lctl --device {device_number} deactivate",
7 # where {device_number} is from the output of "lctl dl" on the MDS.
9 # To guard against corruption, the file is compared after migration
10 # to verify the copy is correct and the file has not been modified.
11 # This is not a protection against the file being open by another
12 # process, but it would catch the worst cases of in-use files, but
13 # to be 100% safe the administrator needs to ensure this is safe.
16 LFS_MIGRATE_RSYNC_MODE=${LFS_MIGRATE_RSYNC_MODE:-false}
19 RSYNC_WITH_HLINKS=false
20 LFS_MIGRATE_TMP=${TMPDIR:-/tmp}
21 MIGRATED_SET="$(mktemp ${LFS_MIGRATE_TMP}/lfs_migrate.links.XXXXXX)"
23 REMOVE_FID='s/^\[[0-9a-fx:]*\] //'
29 echo -e "$old_fid $path" >> "$MIGRATED_SET"
35 sed -e "$REMOVE_FID" $MIGRATED_SET | grep -q "^$path$"
41 grep "^\\$old_fid" "$MIGRATED_SET" | head -n 1 |
47 usage: lfs_migrate [--dry-run] [-h] [--no-rsync|--rsync] [-q] [-R] [-s]
48 [-v] [-y] [-0] [FILE|DIR...]
49 --dry-run only print the names of files to be migrated
50 -h show this usage message
51 --no-rsync do not fall back to rsync mode even if lfs migrate fails
52 -q run quietly (don't print filenames or status)
53 --rsync force rsync mode instead of using lfs migrate
54 -R restripe file using default directory striping
55 -s skip file data comparison after migrate
56 -v show verbose debug messages
57 -y answer 'y' to usage question
58 -0 input file names on stdin are separated by a null character
60 The -c <stripe_count> and -S <stripe_size> options may not be specified at
61 the same time as the -R option.
63 The --rsync and --no-rsync options may not be specified at the same time.
65 If a directory is an argument, all files in the directory are migrated.
66 If no file/directory is given, the file list is read from standard input.
68 Any arguments that are not explicitly recognized by the script are passed
69 through to the 'lfs migrate' utility.
72 lfs_migrate /mnt/lustre/dir
73 lfs_migrate -p newpool /mnt/lustre/dir
74 lfs find /test -O test-OST0004 -size +4G | lfs_migrate -y
81 [ -n "$NEWNAME" ] && rm -f "$NEWNAME"
98 # Examine any long options and arguments. getopts does not support long
99 # options, so they must be stripped out and classified as either options
100 # for the script, or passed through to "lfs migrate".
105 for f in $(seq 1 $#); do
107 if [ "${arg:0:2}" = "--" ]; then
109 if [ "$arg" = "--block" ]; then
112 elif [ "$arg" = "--non-block" ]; then
114 elif [ "$arg" = "--dry-run" ]; then
117 elif [ "$arg" = "--rsync" ]; then
118 LFS_MIGRATE_RSYNC_MODE=true
119 elif [ "$arg" = "--no-rsync" ]; then
124 OPT_PASSTHROUGH+=("$arg")
127 elif [ "${arg:0:1}" = "-" ]; then
129 if [ "$arg" == "-b" ]; then
138 # This will prevent long options from having file name
139 # arguments, but allows long options with no arguments to work.
140 if [ -f "$arg" -o -d "$arg" ]; then
143 [ "$PREV" = "--stripe-count" ] &&
145 [ "$PREV" = "--stripe-size" ] &&
147 [ "$PREV" = "--pool" ] &&
149 OPT_PASSTHROUGH+=("$arg")
151 elif $SHORT_OPT; then
152 [ "$PREV" = "-c" ] &&
154 [ "$PREV" = "-S" ] &&
156 [ "$PREV" = "-p" ] &&
165 # Reset the argument list to include only the short options and file names
168 while getopts ":hlnqRsvy0" opt $*; do
171 l) ;; # maintained for backward compatibility
174 echo "$(basename $0): -n deprecated, use --dry-run instead" 1>&2
175 echo "$(basename $0): to specify non-block, use --non-block instead" 1>&2;;
177 R) OPT_RESTRIPE=true;;
179 v) OPT_DEBUG=true; ECHO=echo; OPT_PASSTHROUGH+=("-v");;
182 *) # Pass through any unrecognized options to 'lfs migrate'
183 OPT_PASSTHROUGH+=("-$OPTARG")
184 if [[ ${!OPTIND:0:1} != "-" && ! -f "${!OPTIND}" &&
185 ! -d "${!OPTIND}" ]]; then
186 OPT_PASSTHROUGH+=("${!OPTIND}")
191 shift $((OPTIND - 1))
193 if $OPT_RESTRIPE && [[ "$STRIPE_COUNT" || "$STRIPE_SIZE" ]]; then
194 echo "$(basename $0): Options -c <stripe_count> and -S <stripe_size> "\
195 "may not be specified at the same time as the -R option." 1>&2
199 if $LFS_MIGRATE_RSYNC_MODE && $OPT_NO_RSYNC; then
200 echo "$(basename $0): Options --rsync and --no-rsync may not be "\
201 "specified at the same time." 1>&2
207 echo "lfs_migrate is currently NOT SAFE for moving in-use files." 1>&2
208 echo "Use it only when you are sure migrated files are unused." 1>&2
210 echo "If emptying an OST that is active on the MDS, new files may" 1>&2
211 echo "use it. To stop allocating any new objects on OSTNNNN run:" 1>&2
212 echo " lctl set_param osp.<fsname>-OSTNNNN*.max_create_count=0'" 1>&2
213 echo "on each MDS using the OST(s) being emptied." 1>&2
214 echo -n "Continue? (y/n) "
216 [ "$CHECK" != "y" -a "$CHECK" != "yes" ] && exit 1
219 # if rsync has --xattr support, then try to copy the xattrs.
220 $RSYNC --help 2>&1 | grep -q xattr && RSYNC_OPTS="$RSYNC_OPTS -X"
221 $RSYNC --help 2>&1 | grep -q acls && RSYNC_OPTS="$RSYNC_OPTS -A"
222 # If rsync copies lustre xattrs in the future, then we can skip lfs (bug 22189)
223 strings $(which $RSYNC) 2>&1 | grep -q lustre && LFS=:
225 # rsync creates its temporary files with lenient permissions, even if
226 # permissions on the original files are more strict. Tighten umask here
227 # to avoid the brief window where unprivileged users might be able to
228 # access the temporary file.
232 while IFS='' read -d '' OLDNAME; do
234 local stripe_size="$STRIPE_SIZE"
235 local stripe_count="$STRIPE_COUNT"
236 local parent_count=""
239 $ECHO -n "$OLDNAME: "
241 # avoid duplicate stat if possible
242 local nlink_type=($(LANG=C stat -c "%h %F" "$OLDNAME" \
245 # skip non-regular files, since they don't have any objects
246 # and there is no point in trying to migrate them.
247 if [ "${nlink_type[1]}" != "regular" ]; then
248 echo -e "\r\e[K$OLDNAME: not a regular file, skipped"
252 # working out write perms is hard, let the shell do it
253 if [ ! -w "$OLDNAME" ]; then
254 echo -e "\r\e[K$OLDNAME: no write permission, skipped"
258 if $OPT_DRYRUN && ! $OPT_DEBUG; then
259 $ECHO "dry run, skipped"
263 # xattrs use absolute file paths, so ensure provided path is
264 # also absolute so that the names can be compared
265 local oldname_absolute=$(readlink -f "$OLDNAME")
266 if [ -z "$oldname_absolute" ]; then
267 echo -e "\r\e[K$OLDNAME: cannot resolve full path, skipped"
270 OLDNAME=$oldname_absolute
272 # In the future, the path2fid and fid2path calls below
273 # should be replaced with a single call to
274 # "lfs path2links" once that command is available. The logic
275 # for detecting unlisted hard links could then be removed.
276 local fid=$($LFS path2fid "$OLDNAME" 2> /dev/null)
277 if [ $? -ne 0 ]; then
278 echo -n "\r\e[K$OLDNAME: cannot determine FID; skipping; "
279 echo "is this a Lustre file system?"
283 if [[ ${nlink_type[0]} -gt 1 ]] || $RSYNC_WITH_HLINKS; then
284 # don't migrate a hard link if it was already migrated
285 if path_in_set "$OLDNAME"; then
286 $ECHO -e "$OLDNAME: already migrated via another hard link"
290 # There is limited space available in the xattrs
291 # to store all of the hard links for a file, so it's
292 # possible that $OLDNAME is part of a link set but is
293 # not listed in xattrs and therefore not listed as
295 local migrated=$(old_fid_in_set "$fid")
296 if [ -n "$migrated" ]; then
297 $ECHO -e "$OLDNAME: already migrated via another hard link"
298 if $LFS_MIGRATE_RSYNC_MODE; then
299 # Only the rsync case has to relink.
300 # The lfs migrate case preserves the
301 # inode so the links are already
303 [ "$migrated" != "$OLDNAME" ] &&
304 ln -f "$migrated" "$OLDNAME"
306 add_to_set "$fid" "$OLDNAME"
311 if $OPT_RESTRIPE; then
314 # if rsync copies Lustre xattrs properly in the future
315 # (i.e. before the file data, so that it preserves striping)
316 # then we don't need to do this getstripe/mktemp stuff.
319 [ -z "$stripe_count" ] &&
320 stripe_count=$($LFS getstripe -c "$OLDNAME" 2> /dev/null)
322 [ -z "$stripe_size" ] &&
323 stripe_size=$($LFS getstripe -S "$OLDNAME" 2> /dev/null)
325 [ -z "$stripe_count" -o -z "$stripe_size" ] && UNLINK=""
329 if $OPT_RESTRIPE; then
330 parent_count=$($LFS getstripe -c \
331 $(dirname "$OLDNAME") 2> \
333 parent_size=$($LFS getstripe -S \
334 $(dirname "$OLDNAME") 2> \
339 "count=${stripe_count:-$parent_count}," \
340 "size=${stripe_size:-$parent_size}," \
341 "pool=${POOL:-not in a pool}: "
345 $ECHO "dry run, skipped"
349 if [[ "$stripe_count" && -z "$STRIPE_COUNT" ]]; then
350 stripe_count="-c $stripe_count"
354 if [[ "$stripe_size" && -z "$STRIPE_SIZE" ]]; then
355 stripe_size="-S $stripe_size"
360 # detect other hard links and store them on a global
361 # list so we don't re-migrate them
362 local mntpoint=$(df -P "$OLDNAME" |
363 awk 'NR==2 { print $NF; exit }')
364 if [ -z "$mntpoint" ]; then
365 echo -e "\r\e[K$OLDNAME: cannot determine mount point; skipped"
368 hlinks=$($LFS fid2path "$mntpoint" "$fid" 2> /dev/null)
369 if [ $? -ne 0 ]; then
370 echo -e "\r\e[K$OLDNAME: cannot determine hard link paths, skipped"
375 # first try to migrate via Lustre tools, then fall back to rsync
376 if ! $LFS_MIGRATE_RSYNC_MODE; then
377 if $LFS migrate "${OPT_PASSTHROUGH[@]}" ${BLOCK} \
378 ${stripe_count} ${stripe_size} "$OLDNAME" &> \
381 for link in ${hlinks[*]}; do
382 add_to_set "$fid" "$link"
385 elif $OPT_NO_RSYNC; then
386 echo -e "\r\e[K$OLDNAME: refusing to fall back to rsync, skipped" 1>&2
389 $ECHO -n "falling back to rsync: "
390 LFS_MIGRATE_RSYNC_MODE=true
394 NEWNAME=$(mktemp $UNLINK "$OLDNAME-lfs_migrate.tmp.XXXXXX")
395 if [ $? -ne 0 -o -z "$NEWNAME" ]; then
396 echo -e "\r\e[K$OLDNAME: can't make temp file, skipped" 1>&2
400 [ "$UNLINK" ] && $LFS setstripe ${OPT_PASSTHROUGH} \
401 ${stripe_count} ${stripe_size} \
402 "$NEWNAME" &> /dev/null
404 # we use --inplace, since we created our own temp file already
405 if ! $RSYNC -a --inplace $RSYNC_OPTS "$OLDNAME" "$NEWNAME";then
406 echo -e "\r\e[K$OLDNAME: copy error, exiting" 1>&2
410 if $OPT_CHECK && ! cmp -s "$OLDNAME" "$NEWNAME"; then
411 echo -e "\r\e[K$NEWNAME: compare failed, exiting" 1>&2
415 if ! mv "$NEWNAME" "$OLDNAME"; then
416 echo -e "\r\e[K$OLDNAME: rename error, exiting" 1>&2
420 $ECHO "done migrate via rsync"
421 for link in ${hlinks[*]}; do
422 if [ "$link" != "$OLDNAME" ]; then
423 ln -f "$OLDNAME" "$link"
425 add_to_set "$fid" "$link"
428 # If the number of hlinks exceeds the space in the xattrs,
429 # when the final path is statted it will have a link count
430 # of 1 (all other links will point to the new inode).
431 # This flag indicates that even paths with a link count of
432 # 1 are potentially part of a link set.
433 [ ${#hlinks[*]} -gt 1 ] && RSYNC_WITH_HLINKS=true
437 if [ "$#" -eq 0 ]; then
441 tr '\n' '\0' | lfs_migrate
446 $LFS find "$1" -type f -print0