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")
126 elif [ "${arg:0:1}" = "-" ]; then
128 if [ "$arg" == "-b" ]; then
136 # This will prevent long options from having file name
137 # arguments, but allows long options with no arguments to work.
138 if [ -f "$arg" -o -d "$arg" ]; then
141 [ "${OPT_PASSTHROUGH[-1]}" = "--stripe-count" ] &&
143 [ "${OPT_PASSTHROUGH[-1]}" = "--stripe-size" ] &&
145 [ "${OPT_PASSTHROUGH[-1]}" = "--pool" ] &&
147 OPT_PASSTHROUGH+=("$arg")
149 elif $SHORT_OPT; then
150 [ "${OPTS[-1]}" = "-c" ] &&
152 [ "${OPTS[-1]}" = "-S" ] &&
154 [ "${OPTS[-1]}" = "-p" ] &&
163 # Reset the argument list to include only the short options and file names
166 while getopts ":hlnqRsvy0" opt $*; do
169 l) ;; # maintained for backward compatibility
172 echo "$(basename $0): -n deprecated, use --dry-run instead" 1>&2
173 echo "$(basename $0): to specify non-block, use --non-block instead" 1>&2;;
175 R) OPT_RESTRIPE=true;;
177 v) OPT_DEBUG=true; ECHO=echo; OPT_PASSTHROUGH+=("-v");;
180 *) # Pass through any unrecognized options to 'lfs migrate'
181 OPT_PASSTHROUGH+=("-$OPTARG")
182 if [[ ${!OPTIND:0:1} != "-" && ! -f "${!OPTIND}" &&
183 ! -d "${!OPTIND}" ]]; then
184 OPT_PASSTHROUGH+=("${!OPTIND}")
189 shift $((OPTIND - 1))
191 if $OPT_RESTRIPE && [[ "$STRIPE_COUNT" || "$STRIPE_SIZE" ]]; then
192 echo "$(basename $0): Options -c <stripe_count> and -S <stripe_size> "\
193 "may not be specified at the same time as the -R option." 1>&2
197 if $LFS_MIGRATE_RSYNC_MODE && $OPT_NO_RSYNC; then
198 echo "$(basename $0): Options --rsync and --no-rsync may not be "\
199 "specified at the same time." 1>&2
205 echo "lfs_migrate is currently NOT SAFE for moving in-use files." 1>&2
206 echo "Use it only when you are sure migrated files are unused." 1>&2
208 echo "If emptying an OST that is active on the MDS, new files may" 1>&2
209 echo "use it. To stop allocating any new objects on OSTNNNN run:" 1>&2
210 echo " lctl set_param osp.<fsname>-OSTNNNN*.max_create_count=0'" 1>&2
211 echo "on each MDS using the OST(s) being emptied." 1>&2
212 echo -n "Continue? (y/n) "
214 [ "$CHECK" != "y" -a "$CHECK" != "yes" ] && exit 1
217 # if rsync has --xattr support, then try to copy the xattrs.
218 $RSYNC --help 2>&1 | grep -q xattr && RSYNC_OPTS="$RSYNC_OPTS -X"
219 $RSYNC --help 2>&1 | grep -q acls && RSYNC_OPTS="$RSYNC_OPTS -A"
220 # If rsync copies lustre xattrs in the future, then we can skip lfs (bug 22189)
221 strings $(which $RSYNC) 2>&1 | grep -q lustre && LFS=:
223 # rsync creates its temporary files with lenient permissions, even if
224 # permissions on the original files are more strict. Tighten umask here
225 # to avoid the brief window where unprivileged users might be able to
226 # access the temporary file.
230 while IFS='' read -d '' OLDNAME; do
232 local stripe_size="$STRIPE_SIZE"
233 local stripe_count="$STRIPE_COUNT"
234 local parent_count=""
237 $ECHO -n "$OLDNAME: "
239 # avoid duplicate stat if possible
240 local nlink_type=($(LANG=C stat -c "%h %F" "$OLDNAME" \
243 # skip non-regular files, since they don't have any objects
244 # and there is no point in trying to migrate them.
245 if [ "${nlink_type[1]}" != "regular" ]; then
246 echo -e "\r\e[K$OLDNAME: not a regular file, skipped"
250 # working out write perms is hard, let the shell do it
251 if [ ! -w "$OLDNAME" ]; then
252 echo -e "\r\e[K$OLDNAME: no write permission, skipped"
256 if $OPT_DRYRUN && ! $OPT_DEBUG; then
257 $ECHO "dry run, skipped"
261 # xattrs use absolute file paths, so ensure provided path is
262 # also absolute so that the names can be compared
263 local oldname_absolute=$(readlink -f "$OLDNAME")
264 if [ -z "$oldname_absolute" ]; then
265 echo -e "\r\e[K$OLDNAME: cannot resolve full path, skipped"
268 OLDNAME=$oldname_absolute
270 # In the future, the path2fid and fid2path calls below
271 # should be replaced with a single call to
272 # "lfs path2links" once that command is available. The logic
273 # for detecting unlisted hard links could then be removed.
274 local fid=$($LFS path2fid "$OLDNAME" 2> /dev/null)
275 if [ $? -ne 0 ]; then
276 echo -n "\r\e[K$OLDNAME: cannot determine FID; skipping; "
277 echo "is this a Lustre file system?"
281 if [[ ${nlink_type[0]} -gt 1 ]] || $RSYNC_WITH_HLINKS; then
282 # don't migrate a hard link if it was already migrated
283 if path_in_set "$OLDNAME"; then
284 $ECHO -e "$OLDNAME: already migrated via another hard link"
288 # There is limited space available in the xattrs
289 # to store all of the hard links for a file, so it's
290 # possible that $OLDNAME is part of a link set but is
291 # not listed in xattrs and therefore not listed as
293 local migrated=$(old_fid_in_set "$fid")
294 if [ -n "$migrated" ]; then
295 $ECHO -e "$OLDNAME: already migrated via another hard link"
296 if $LFS_MIGRATE_RSYNC_MODE; then
297 # Only the rsync case has to relink.
298 # The lfs migrate case preserves the
299 # inode so the links are already
301 [ "$migrated" != "$OLDNAME" ] &&
302 ln -f "$migrated" "$OLDNAME"
304 add_to_set "$fid" "$OLDNAME"
309 if $OPT_RESTRIPE; then
312 # if rsync copies Lustre xattrs properly in the future
313 # (i.e. before the file data, so that it preserves striping)
314 # then we don't need to do this getstripe/mktemp stuff.
317 [ -z "$stripe_count" ] &&
318 stripe_count=$($LFS getstripe -c "$OLDNAME" 2> /dev/null)
320 [ -z "$stripe_size" ] &&
321 stripe_size=$($LFS getstripe -S "$OLDNAME" 2> /dev/null)
323 [ -z "$stripe_count" -o -z "$stripe_size" ] && UNLINK=""
327 if $OPT_RESTRIPE; then
328 parent_count=$($LFS getstripe -c \
329 $(dirname "$OLDNAME") 2> \
331 parent_size=$($LFS getstripe -S \
332 $(dirname "$OLDNAME") 2> \
337 "count=${stripe_count:-$parent_count}," \
338 "size=${stripe_size:-$parent_size}," \
339 "pool=${POOL:-not in a pool}: "
343 $ECHO "dry run, skipped"
347 if [[ "$stripe_count" && -z "$STRIPE_COUNT" ]]; then
348 stripe_count="-c $stripe_count"
352 if [[ "$stripe_size" && -z "$STRIPE_SIZE" ]]; then
353 stripe_size="-S $stripe_size"
358 # detect other hard links and store them on a global
359 # list so we don't re-migrate them
360 local mntpoint=$(df -P "$OLDNAME" |
361 awk 'NR==2 { print $NF; exit }')
362 if [ -z "$mntpoint" ]; then
363 echo -e "\r\e[K$OLDNAME: cannot determine mount point; skipped"
366 hlinks=$($LFS fid2path "$mntpoint" "$fid" 2> /dev/null)
367 if [ $? -ne 0 ]; then
368 echo -e "\r\e[K$OLDNAME: cannot determine hard link paths, skipped"
373 # first try to migrate via Lustre tools, then fall back to rsync
374 if ! $LFS_MIGRATE_RSYNC_MODE; then
375 if $LFS migrate "${OPT_PASSTHROUGH[@]}" ${BLOCK} \
376 ${stripe_count} ${stripe_size} "$OLDNAME" &> \
379 for link in ${hlinks[*]}; do
380 add_to_set "$fid" "$link"
383 elif $OPT_NO_RSYNC; then
384 echo -e "\r\e[K$OLDNAME: refusing to fall back to rsync, skipped" 1>&2
387 $ECHO -n "falling back to rsync: "
388 LFS_MIGRATE_RSYNC_MODE=true
392 NEWNAME=$(mktemp $UNLINK "$OLDNAME-lfs_migrate.tmp.XXXXXX")
393 if [ $? -ne 0 -o -z "$NEWNAME" ]; then
394 echo -e "\r\e[K$OLDNAME: can't make temp file, skipped" 1>&2
398 [ "$UNLINK" ] && $LFS setstripe ${OPT_PASSTHROUGH} \
399 ${stripe_count} ${stripe_size} \
400 "$NEWNAME" &> /dev/null
402 # we use --inplace, since we created our own temp file already
403 if ! $RSYNC -a --inplace $RSYNC_OPTS "$OLDNAME" "$NEWNAME";then
404 echo -e "\r\e[K$OLDNAME: copy error, exiting" 1>&2
408 if $OPT_CHECK && ! cmp -s "$OLDNAME" "$NEWNAME"; then
409 echo -e "\r\e[K$NEWNAME: compare failed, exiting" 1>&2
413 if ! mv "$NEWNAME" "$OLDNAME"; then
414 echo -e "\r\e[K$OLDNAME: rename error, exiting" 1>&2
418 $ECHO "done migrate via rsync"
419 for link in ${hlinks[*]}; do
420 if [ "$link" != "$OLDNAME" ]; then
421 ln -f "$OLDNAME" "$link"
423 add_to_set "$fid" "$link"
426 # If the number of hlinks exceeds the space in the xattrs,
427 # when the final path is statted it will have a link count
428 # of 1 (all other links will point to the new inode).
429 # This flag indicates that even paths with a link count of
430 # 1 are potentially part of a link set.
431 [ ${#hlinks[*]} -gt 1 ] && RSYNC_WITH_HLINKS=true
435 if [ "$#" -eq 0 ]; then
439 tr '\n' '\0' | lfs_migrate
444 $LFS find "$1" -type f -print0