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 OPT_RSYNC=${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:]*\] //'
30 echo -e "$old_fid $path" >> "$MIGRATED_SET"
36 sed -e "$REMOVE_FID" $MIGRATED_SET | grep -q "^$path$"
42 grep "^\\$old_fid" "$MIGRATED_SET" | head -n 1 |
48 usage: lfs_migrate [--dry-run] [--help|-h] [--no-rsync|--rsync] [--quiet|-q]
49 [--restripe|-R] [--skip|-s] [--verbose|-v] [--yes|-y] [-0]
51 --dry-run only print the names of files to be migrated
52 -h show this usage message
53 --no-rsync do not fall back to rsync mode even if lfs migrate fails
54 -q run quietly (don't print filenames or status)
55 --rsync force rsync mode instead of using lfs migrate
56 -R restripe file using default directory striping
57 -s skip file data comparison after migrate
58 -v show verbose debug messages
59 -y answer 'y' to usage question
60 -0 input file names on stdin are separated by a null character
62 If the --restripe|-R option is used, other "lfs setstripe" layout options
63 such as -E, -c, -S, --copy, and --yaml may not be specified at the same time.
64 Only the --block, --non-block, --non-direct, and --verbose non-layout setstripe
65 options may be used in that case.
67 The --rsync and --no-rsync options may not be specified at the same time.
69 If a directory is an argument, all files in the directory are migrated.
70 If no file/directory is given, the file list is read from standard input.
72 Any arguments that are not explicitly recognized by the script are passed
73 through to the 'lfs migrate' utility.
76 lfs_migrate /mnt/lustre/dir
77 lfs_migrate -p newpool /mnt/lustre/dir
78 lfs find /test -O test-OST0004 -size +4G | lfs_migrate -y
85 [ -n "$NEWNAME" ] && rm -f "$NEWNAME"
102 # Examine any long options and arguments. getopts does not support long
103 # options, so they must be stripped out and classified as either options
104 # for the script, or passed through to "lfs migrate".
105 while [ -n "$*" ]; do
109 -l|--link) ;; # maintained backward compatibility for now
110 -n|--dry-run) OPT_DRYRUN=true; OPT_YES=true
111 echo "$PROG: -n deprecated, use --dry-run or --non-block" 1>&2;;
113 -R|--restripe) OPT_RESTRIPE=true;;
114 -s|--skip) OPT_CHECK=false;;
115 -v|--verbose) OPT_DEBUG=true; ECHO=echo; OPT_PASSTHROUGH+=("$arg");;
116 -y|--yes) OPT_YES=true;;
118 -b|--block|--non-block|--non-direct|--no-verify)
119 # Always pass non-layout options to 'lfs migrate'
120 OPT_PASSTHROUGH+=("$arg");;
121 --rsync) OPT_RSYNC=true;;
122 --no-rsync) OPT_NO_RSYNC=true;;
123 --copy|--yaml|--file)
124 # these options have files as arguments, pass both through
125 OPT_LAYOUT+="$arg $2"; shift;;
126 *) # Pass other non-file layout options to 'lfs migrate'
127 [ -e "$arg" ] && OPT_FILE+="$arg " && break || OPT_LAYOUT+="$arg "
132 if $OPT_RESTRIPE && [ -n "$OPT_LAYOUT" ]; then
133 echo "$PROG: Options $OPT_LAYOUT cannot be used with the -R option" 1>&2
137 if $OPT_RSYNC && $OPT_NO_RSYNC; then
138 echo "$PROG: Options --rsync and --no-rsync may not be" \
139 "specified at the same time." 1>&2
145 echo "lfs_migrate is currently NOT SAFE for moving in-use files." 1>&2
146 echo "Use it only when you are sure migrated files are unused." 1>&2
148 echo "If emptying an OST that is active on the MDS, new files may" 1>&2
149 echo "use it. To stop allocating any new objects on OSTNNNN run:" 1>&2
150 echo " lctl set_param osp.<fsname>-OSTNNNN*.max_create_count=0'" 1>&2
151 echo "on each MDS using the OST(s) being emptied." 1>&2
152 echo -n "Continue? (y/n) "
154 [ "$CHECK" != "y" -a "$CHECK" != "yes" ] && exit 1
157 # if rsync has --xattr support, then try to copy the xattrs.
158 $RSYNC --help 2>&1 | grep -q xattr && RSYNC_OPTS="$RSYNC_OPTS -X"
159 $RSYNC --help 2>&1 | grep -q acls && RSYNC_OPTS="$RSYNC_OPTS -A"
160 # If rsync copies lustre xattrs in the future, then we can skip lfs (bug 22189)
161 strings $(which $RSYNC) 2>&1 | grep -q lustre && LFS=:
163 # rsync creates its temporary files with lenient permissions, even if
164 # permissions on the original files are more strict. Tighten umask here
165 # to avoid the brief window where unprivileged users might be able to
166 # access the temporary file.
170 while IFS='' read -d '' OLDNAME; do
178 $ECHO -n "$OLDNAME: "
180 # avoid duplicate stat if possible
181 local nlink_type=($(LANG=C stat -c "%h %F" "$OLDNAME" \
184 # skip non-regular files, since they don't have any objects
185 # and there is no point in trying to migrate them.
186 if [ "${nlink_type[1]}" != "regular" ]; then
187 echo -e "\r\e[K$OLDNAME: not a regular file, skipped"
191 # working out write perms is hard, let the shell do it
192 if [ ! -w "$OLDNAME" ]; then
193 echo -e "\r\e[K$OLDNAME: no write permission, skipped"
197 if $OPT_DRYRUN && ! $OPT_DEBUG; then
198 $ECHO "dry run, skipped"
202 # xattrs use absolute file paths, so ensure provided path is
203 # also absolute so that the names can be compared
204 local oldname_absolute=$(readlink -f "$OLDNAME")
205 if [ -z "$oldname_absolute" ]; then
206 echo -e "\r\e[K$OLDNAME: cannot resolve full path, skipped"
209 OLDNAME=$oldname_absolute
211 # In the future, the path2fid and fid2path calls below
212 # should be replaced with a single call to
213 # "lfs path2links" once that command is available. The logic
214 # for detecting unlisted hard links could then be removed.
215 local fid=$($LFS path2fid "$OLDNAME" 2> /dev/null)
216 if [ $? -ne 0 ]; then
217 echo -n "\r\e[K$OLDNAME: cannot determine FID; skipping; "
218 echo "is this a Lustre file system?"
222 if [[ ${nlink_type[0]} -gt 1 ]] || $RSYNC_WITH_HLINKS; then
223 # don't migrate a hard link if it was already migrated
224 if path_in_set "$OLDNAME"; then
225 $ECHO -e "$OLDNAME: already migrated via another hard link"
229 # There is limited space available in the xattrs
230 # to store all of the hard links for a file, so it's
231 # possible that $OLDNAME is part of a link set but is
232 # not listed in xattrs and therefore not listed as
234 local migrated=$(old_fid_in_set "$fid")
235 if [ -n "$migrated" ]; then
236 $ECHO -e "$OLDNAME: already migrated via another hard link"
238 # Only the rsync case has to relink.
239 # The lfs migrate case preserves the
240 # inode so the links are already
242 [ "$migrated" != "$OLDNAME" ] &&
243 ln -f "$migrated" "$OLDNAME"
245 add_to_set "$fid" "$OLDNAME"
250 if $OPT_RESTRIPE; then
253 # if rsync copies Lustre xattrs properly in the future
254 # (i.e. before the file data, so that it preserves striping)
255 # then we don't need to do this getstripe/mktemp stuff.
258 stripe_count=$($LFS getstripe -c "$OLDNAME" 2> /dev/null)
259 stripe_size=$($LFS getstripe -S "$OLDNAME" 2> /dev/null)
260 stripe_pool=$($LFS getstripe -p "$OLDNAME" 2> /dev/null)
261 mirror_count=$($LFS getstripe -N "$OLDFILE" 2> /dev/null)
263 [ -z "$stripe_count" -o -z "$stripe_size" ] && UNLINK=""
270 if $OPT_RESTRIPE; then
271 parent_count=$($LFS getstripe -c \
272 $(dirname "$OLDNAME") 2> \
274 parent_size=$($LFS getstripe -S \
275 $(dirname "$OLDNAME") 2> \
277 stripe_pool=$($LFS getstripe --pool \
278 $(dirname "$OLDNAME") 2> \
280 mirror_count=$($LFS getstripe -N \
281 $(dirname "$OLDFILE") 2> \
286 "count=${stripe_count:-$parent_count}," \
287 "size=${stripe_size:-$parent_size}," \
288 "pool=${stripe_pool}," \
289 "mirror_count=${mirror_count}"
293 $ECHO "dry run, skipped"
297 stripe_count="-c $stripe_count"
298 stripe_size="-S $stripe_size"
299 [ -n "$stripe_pool" ] && stripe_pool="-p $stripe_pool"
300 [ -n "$mirror_count" ] && mirror_count="-N $mirror_count"
301 layout="$stripe_count $stripe_size $stripe_pool $mirror_count \
304 # detect other hard links and store them on a global
305 # list so we don't re-migrate them
306 local mntpoint=$(df -P "$OLDNAME" |
307 awk 'NR==2 { print $NF; exit }')
308 if [ -z "$mntpoint" ]; then
309 echo -e "\r\e[K$OLDNAME: cannot determine mount point; skipped"
312 hlinks=$($LFS fid2path "$mntpoint" "$fid" 2> /dev/null)
313 if [ $? -ne 0 ]; then
314 echo -e "\r\e[K$OLDNAME: cannot determine hard link paths, skipped"
319 # first try to migrate via Lustre tools, then fall back to rsync
320 if ! $OPT_RSYNC; then
321 if $LFS migrate "${OPT_PASSTHROUGH[@]}" $layout \
324 for link in ${hlinks[*]}; do
325 add_to_set "$fid" "$link"
328 elif $OPT_NO_RSYNC; then
329 echo -e "\r\e[K$OLDNAME: refusing to fall back to rsync, skipped" 1>&2
332 $ECHO -n "falling back to rsync: "
337 NEWNAME=$(mktemp $UNLINK "$OLDNAME-lfs_migrate.tmp.XXXXXX")
338 if [ $? -ne 0 -o -z "$NEWNAME" ]; then
339 echo -e "\r\e[K$OLDNAME: can't make temp file, skipped" 1>&2
343 if [ "$UNLINK" ]; then
344 if ! $LFS setstripe "${OPT_PASSTHROUGH}" $layout \
346 echo -e "\r\e[K$NEWNAME: setstripe failed, exiting" 1>&2
351 # we use --inplace, since we created our own temp file already
352 if ! $RSYNC -a --inplace $RSYNC_OPTS "$OLDNAME" "$NEWNAME";then
353 echo -e "\r\e[K$OLDNAME: copy error, exiting" 1>&2
357 if $OPT_CHECK && ! cmp -s "$OLDNAME" "$NEWNAME"; then
358 echo -e "\r\e[K$NEWNAME: compare failed, exiting" 1>&2
362 if ! mv "$NEWNAME" "$OLDNAME"; then
363 echo -e "\r\e[K$OLDNAME: rename error, exiting" 1>&2
367 $ECHO "done migrate via rsync"
368 for link in ${hlinks[*]}; do
369 if [ "$link" != "$OLDNAME" ]; then
370 ln -f "$OLDNAME" "$link"
372 add_to_set "$fid" "$link"
375 # If the number of hlinks exceeds the space in the xattrs,
376 # when the final path is statted it will have a link count
377 # of 1 (all other links will point to the new inode).
378 # This flag indicates that even paths with a link count of
379 # 1 are potentially part of a link set.
380 [ ${#hlinks[*]} -gt 1 ] && RSYNC_WITH_HLINKS=true
384 if [ "$#" -eq 0 ]; then
388 tr '\n' '\0' | lfs_migrate
393 $LFS find "$1" -type f -print0