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] [-D] [-h] [--no-rsync|--rsync] [-q] [-R]
48 [-s] [-v] [-y] [-0] [FILE|DIR...]
49 --dry-run only print the names of files to be migrated
50 -D do not use direct I/O to copy file contents
51 -h show this usage message
52 --no-rsync do not fall back to rsync mode even if lfs migrate fails
53 -q run quietly (don't print filenames or status)
54 --rsync force rsync mode instead of using lfs migrate
55 -R restripe file using default directory striping
56 -s skip file data comparison after migrate
57 -v show verbose debug messages
58 -y answer 'y' to usage question
59 -0 input file names on stdin are separated by a null character
61 The -c <stripe_count> and -S <stripe_size> options may not be specified at
62 the same time as the -R option.
64 The --rsync and --no-rsync options may not be specified at the same time.
66 If a directory is an argument, all files in the directory are migrated.
67 If no file/directory is given, the file list is read from standard input.
69 Any arguments that are not explicitly recognized by the script are passed
70 through to the 'lfs migrate' utility.
73 lfs_migrate /mnt/lustre/dir
74 lfs_migrate -p newpool /mnt/lustre/dir
75 lfs find /test -O test-OST0004 -size +4G | lfs_migrate -y
82 [ -n "$NEWNAME" ] && rm -f "$NEWNAME"
100 # Examine any long options and arguments. getopts does not support long
101 # options, so they must be stripped out and classified as either options
102 # for the script, or passed through to "lfs migrate".
107 for f in $(seq 1 $#); do
109 if [ "${arg:0:2}" = "--" ]; then
111 if [ "$arg" = "--block" ]; then
114 elif [ "$arg" = "--non-block" ]; then
116 elif [ "$arg" = "--dry-run" ]; then
119 elif [ "$arg" = "--rsync" ]; then
120 LFS_MIGRATE_RSYNC_MODE=true
121 elif [ "$arg" = "--no-rsync" ]; then
126 OPT_PASSTHROUGH+=("$arg")
129 elif [ "${arg:0:1}" = "-" ]; then
131 if [ "$arg" == "-b" ]; then
140 # This will prevent long options from having file name
141 # arguments, but allows long options with no arguments to work.
142 if [ -f "$arg" -o -d "$arg" ]; then
145 [ "$PREV" = "--stripe-count" ] &&
147 [ "$PREV" = "--stripe-size" ] &&
149 [ "$PREV" = "--pool" ] &&
151 OPT_PASSTHROUGH+=("$arg")
153 elif $SHORT_OPT; then
154 [ "$PREV" = "-c" ] &&
156 [ "$PREV" = "-S" ] &&
158 [ "$PREV" = "-p" ] &&
167 # Reset the argument list to include only the short options and file names
170 while getopts ":DhlnqRsvy0" opt $*; do
172 D) LFS_OPT_DIRECTIO="-D";;
174 l) ;; # maintained for backward compatibility
177 echo "$(basename $0): -n deprecated, use --dry-run instead" 1>&2
178 echo "$(basename $0): to specify non-block, use --non-block instead" 1>&2;;
180 R) OPT_RESTRIPE=true;;
182 v) OPT_DEBUG=true; ECHO=echo; OPT_PASSTHROUGH+=("-v");;
185 *) # Pass through any unrecognized options to 'lfs migrate'
186 OPT_PASSTHROUGH+=("-$OPTARG")
187 if [[ ${!OPTIND:0:1} != "-" && ! -f "${!OPTIND}" &&
188 ! -d "${!OPTIND}" ]]; then
189 OPT_PASSTHROUGH+=("${!OPTIND}")
194 shift $((OPTIND - 1))
196 if $OPT_RESTRIPE && [[ "$STRIPE_COUNT" || "$STRIPE_SIZE" ]]; then
197 echo "$(basename $0): Options -c <stripe_count> and -S <stripe_size> "\
198 "may not be specified at the same time as the -R option." 1>&2
202 if $LFS_MIGRATE_RSYNC_MODE && $OPT_NO_RSYNC; then
203 echo "$(basename $0): Options --rsync and --no-rsync may not be "\
204 "specified at the same time." 1>&2
210 echo "lfs_migrate is currently NOT SAFE for moving in-use files." 1>&2
211 echo "Use it only when you are sure migrated files are unused." 1>&2
213 echo "If emptying an OST that is active on the MDS, new files may" 1>&2
214 echo "use it. To stop allocating any new objects on OSTNNNN run:" 1>&2
215 echo " lctl set_param osp.<fsname>-OSTNNNN*.max_create_count=0'" 1>&2
216 echo "on each MDS using the OST(s) being emptied." 1>&2
217 echo -n "Continue? (y/n) "
219 [ "$CHECK" != "y" -a "$CHECK" != "yes" ] && exit 1
222 # if rsync has --xattr support, then try to copy the xattrs.
223 $RSYNC --help 2>&1 | grep -q xattr && RSYNC_OPTS="$RSYNC_OPTS -X"
224 $RSYNC --help 2>&1 | grep -q acls && RSYNC_OPTS="$RSYNC_OPTS -A"
225 # If rsync copies lustre xattrs in the future, then we can skip lfs (bug 22189)
226 strings $(which $RSYNC) 2>&1 | grep -q lustre && LFS=:
228 # rsync creates its temporary files with lenient permissions, even if
229 # permissions on the original files are more strict. Tighten umask here
230 # to avoid the brief window where unprivileged users might be able to
231 # access the temporary file.
235 while IFS='' read -d '' OLDNAME; do
237 local stripe_size="$STRIPE_SIZE"
238 local stripe_count="$STRIPE_COUNT"
239 local parent_count=""
242 $ECHO -n "$OLDNAME: "
244 # avoid duplicate stat if possible
245 local nlink_type=($(LANG=C stat -c "%h %F" "$OLDNAME" \
248 # skip non-regular files, since they don't have any objects
249 # and there is no point in trying to migrate them.
250 if [ "${nlink_type[1]}" != "regular" ]; then
251 echo -e "\r\e[K$OLDNAME: not a regular file, skipped"
255 # working out write perms is hard, let the shell do it
256 if [ ! -w "$OLDNAME" ]; then
257 echo -e "\r\e[K$OLDNAME: no write permission, skipped"
261 if $OPT_DRYRUN && ! $OPT_DEBUG; then
262 $ECHO "dry run, skipped"
266 # xattrs use absolute file paths, so ensure provided path is
267 # also absolute so that the names can be compared
268 local oldname_absolute=$(readlink -f "$OLDNAME")
269 if [ -z "$oldname_absolute" ]; then
270 echo -e "\r\e[K$OLDNAME: cannot resolve full path, skipped"
273 OLDNAME=$oldname_absolute
275 # In the future, the path2fid and fid2path calls below
276 # should be replaced with a single call to
277 # "lfs path2links" once that command is available. The logic
278 # for detecting unlisted hard links could then be removed.
279 local fid=$($LFS path2fid "$OLDNAME" 2> /dev/null)
280 if [ $? -ne 0 ]; then
281 echo -n "\r\e[K$OLDNAME: cannot determine FID; skipping; "
282 echo "is this a Lustre file system?"
286 if [[ ${nlink_type[0]} -gt 1 ]] || $RSYNC_WITH_HLINKS; then
287 # don't migrate a hard link if it was already migrated
288 if path_in_set "$OLDNAME"; then
289 $ECHO -e "$OLDNAME: already migrated via another hard link"
293 # There is limited space available in the xattrs
294 # to store all of the hard links for a file, so it's
295 # possible that $OLDNAME is part of a link set but is
296 # not listed in xattrs and therefore not listed as
298 local migrated=$(old_fid_in_set "$fid")
299 if [ -n "$migrated" ]; then
300 $ECHO -e "$OLDNAME: already migrated via another hard link"
301 if $LFS_MIGRATE_RSYNC_MODE; then
302 # Only the rsync case has to relink.
303 # The lfs migrate case preserves the
304 # inode so the links are already
306 [ "$migrated" != "$OLDNAME" ] &&
307 ln -f "$migrated" "$OLDNAME"
309 add_to_set "$fid" "$OLDNAME"
314 if $OPT_RESTRIPE; then
317 # if rsync copies Lustre xattrs properly in the future
318 # (i.e. before the file data, so that it preserves striping)
319 # then we don't need to do this getstripe/mktemp stuff.
322 [ -z "$stripe_count" ] &&
323 stripe_count=$($LFS getstripe -c "$OLDNAME" 2> /dev/null)
325 [ -z "$stripe_size" ] &&
326 stripe_size=$($LFS getstripe -S "$OLDNAME" 2> /dev/null)
328 [ -z "$stripe_count" -o -z "$stripe_size" ] && UNLINK=""
332 if $OPT_RESTRIPE; then
333 parent_count=$($LFS getstripe -c \
334 $(dirname "$OLDNAME") 2> \
336 parent_size=$($LFS getstripe -S \
337 $(dirname "$OLDNAME") 2> \
342 "count=${stripe_count:-$parent_count}," \
343 "size=${stripe_size:-$parent_size}," \
344 "pool=${POOL:-not in a pool}: "
348 $ECHO "dry run, skipped"
352 if [[ "$stripe_count" && -z "$STRIPE_COUNT" ]]; then
353 stripe_count="-c $stripe_count"
357 if [[ "$stripe_size" && -z "$STRIPE_SIZE" ]]; then
358 stripe_size="-S $stripe_size"
363 # detect other hard links and store them on a global
364 # list so we don't re-migrate them
365 local mntpoint=$(df -P "$OLDNAME" |
366 awk 'NR==2 { print $NF; exit }')
367 if [ -z "$mntpoint" ]; then
368 echo -e "\r\e[K$OLDNAME: cannot determine mount point; skipped"
371 hlinks=$($LFS fid2path "$mntpoint" "$fid" 2> /dev/null)
372 if [ $? -ne 0 ]; then
373 echo -e "\r\e[K$OLDNAME: cannot determine hard link paths, skipped"
378 # first try to migrate via Lustre tools, then fall back to rsync
379 if ! $LFS_MIGRATE_RSYNC_MODE; then
380 if $LFS migrate "${OPT_PASSTHROUGH[@]}" ${BLOCK} \
381 $LFS_OPT_DIRECTIO ${stripe_count} ${stripe_size} \
382 "$OLDNAME" &> /dev/null; then
384 for link in ${hlinks[*]}; do
385 add_to_set "$fid" "$link"
388 elif $OPT_NO_RSYNC; then
389 echo -e "\r\e[K$OLDNAME: refusing to fall back to rsync, skipped" 1>&2
392 $ECHO -n "falling back to rsync: "
393 LFS_MIGRATE_RSYNC_MODE=true
397 NEWNAME=$(mktemp $UNLINK "$OLDNAME-lfs_migrate.tmp.XXXXXX")
398 if [ $? -ne 0 -o -z "$NEWNAME" ]; then
399 echo -e "\r\e[K$OLDNAME: can't make temp file, skipped" 1>&2
403 [ "$UNLINK" ] && $LFS setstripe ${OPT_PASSTHROUGH} \
404 ${stripe_count} ${stripe_size} \
405 "$NEWNAME" &> /dev/null
407 # we use --inplace, since we created our own temp file already
408 if ! $RSYNC -a --inplace $RSYNC_OPTS "$OLDNAME" "$NEWNAME";then
409 echo -e "\r\e[K$OLDNAME: copy error, exiting" 1>&2
413 if $OPT_CHECK && ! cmp -s "$OLDNAME" "$NEWNAME"; then
414 echo -e "\r\e[K$NEWNAME: compare failed, exiting" 1>&2
418 if ! mv "$NEWNAME" "$OLDNAME"; then
419 echo -e "\r\e[K$OLDNAME: rename error, exiting" 1>&2
423 $ECHO "done migrate via rsync"
424 for link in ${hlinks[*]}; do
425 if [ "$link" != "$OLDNAME" ]; then
426 ln -f "$OLDNAME" "$link"
428 add_to_set "$fid" "$link"
431 # If the number of hlinks exceeds the space in the xattrs,
432 # when the final path is statted it will have a link count
433 # of 1 (all other links will point to the new inode).
434 # This flag indicates that even paths with a link count of
435 # 1 are potentially part of a link set.
436 [ ${#hlinks[*]} -gt 1 ] && RSYNC_WITH_HLINKS=true
440 if [ "$#" -eq 0 ]; then
444 tr '\n' '\0' | lfs_migrate
449 $LFS find "$1" -type f -print0