Whamcloud - gitweb
LU-15098 tests: sanity-sec 27a exec commands on right node
[fs/lustre-release.git] / lustre / scripts / lfs_migrate
1 #!/bin/bash
2
3 # lfs_migrate: a simple tool to copy and check files.
4 #
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.
8 #
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.
14
15 RSYNC=${RSYNC:-rsync}
16 OPT_RSYNC=${LFS_MIGRATE_RSYNC_MODE:-false}
17 ECHO=echo
18 LFS=${LFS:-lfs}
19 RSYNC_WITH_HLINKS=false
20 LFS_MIGRATE_TMP=${TMPDIR:-/tmp}
21 MIGRATED_SET="$(mktemp ${LFS_MIGRATE_TMP}/lfs_migrate.links.XXXXXX)"
22 NEWNAME=""
23 REMOVE_FID='s/^\[[0-9a-fx:]*\] //'
24 PROG=$(basename $0)
25
26 add_to_set() {
27         local old_fid="$1"
28         local path="$2"
29
30         echo "$old_fid $path" >> "$MIGRATED_SET"
31 }
32
33 path_in_set() {
34         local path="$1"
35
36         sed -e "$REMOVE_FID" $MIGRATED_SET | grep -q "^$path$"
37 }
38
39 old_fid_in_set() {
40         local old_fid="$1"
41
42         grep "^\\$old_fid" "$MIGRATED_SET" | head -n 1 |
43                 sed -e "$REMOVE_FID"
44 }
45
46 usage() {
47     cat -- <<USAGE 1>&2
48 usage: lfs_migrate [--dry-run|-n] [--help|-h] [--no-rsync|--rsync] [--quiet|-q]
49                    [--auto-stripe|-A [-C <cap>]
50                    [--min-free|-M <min_free>] [--max-free|-X <max_free>]]
51                    [--pool|-p <pool>] [--stripe-count|-c <stripe_count>]
52                    [--stripe-size|-S <stripe_size>]
53                    [-D] [-h] [-n] [-S]
54                    [--restripe|-R] [--skip|-s] [--verbose|-v] [--yes|-y] [-0]
55                    [FILE|DIR...]
56         -A         restripe file using an automatically selected stripe count,
57                    uses stripe_count = sqrt(size_in_GB) + 1
58         -c <stripe_count>
59                    restripe file using the specified <stripe_count>
60         -C <cap>   when -A is set, limit the migrated file to use on each OST
61                    at most 1/<cap> of the available space of the smallest OST
62         -D         do not use direct I/O to copy file contents
63         -h         show this usage message
64         -M <min_free>
65                    when -A is set, an OST must contain more available space than
66                    <min_free> KB in order for it to be considered available for
67                    use in the migration
68         --no-rsync do not fall back to rsync mode even if lfs migrate fails
69         -n         only print the names of files to be migrated
70         -p <pool>  use the specified OST pool for the destination file
71         -q         run quietly (don't print filenames or status)
72         --rsync    force rsync mode instead of using lfs migrate
73         -R         restripe file using default directory striping
74         -s         skip file data comparison after migrate
75         -S <stripe_size>
76                    restripe file using the specified stripe size
77         -v         show verbose debug messages
78         -X <max_free>
79                    when -A is set, limit the amount of space on each OST that
80                    can be considered available for the migration to
81                    <max_free> KB
82         -y         answer 'y' to usage question
83         -0         input file names on stdin are separated by a null character
84
85 Options '-A', '-c', and '-R' are mutually exclusive.
86 Options '-C', '-M', and '-X' are ignored if '-A' is not set.
87
88 The --rsync and --no-rsync options may not be specified at the same time.
89
90 If a directory is an argument, all files in the directory are migrated.
91 If no file/directory is given, the file list is read from standard input.
92
93 Any arguments that are not explicitly recognized by the script are passed
94 through to the 'lfs migrate' utility.
95
96 Examples:
97       lfs_migrate /mnt/lustre/dir
98       lfs_migrate -p newpool /mnt/lustre/dir
99       lfs find /test -O test-OST0004 -size +4G | lfs_migrate -y
100 USAGE
101     exit 1
102 }
103
104 cleanup() {
105         rm -f "$MIGRATED_SET"
106         [ -n "$NEWNAME" ] && rm -f "$NEWNAME"
107 }
108
109 trap cleanup EXIT
110
111 OPT_CHECK=true
112 OPT_DEBUG=false
113 OPT_DRYRUN=false
114 OPT_FILE=()
115 OPT_LAYOUT=()
116 OPT_COMP=false
117 OPT_NO_RSYNC=false
118 OPT_NO_DIRECT=false
119 OPT_NULL=false
120 OPT_PASSTHROUGH=()
121 OPT_POOL=""
122 OPT_RESTRIPE=false
123 OPT_YES=false
124 OPT_AUTOSTRIPE=false
125 OPT_STRIPE_COUNT=""
126 OPT_STRIPE_SIZE=""
127 OPT_MINFREE=262144
128 OPT_MAXFREE=""
129 OPT_CAP=100
130
131 # Examine any long options and arguments.  getopts does not support long
132 # options, so they must be stripped out and classified as either options
133 # for the script, or passed through to "lfs migrate".
134 while [ -n "$*" ]; do
135         arg="$1"
136         case "$arg" in
137         -h|--help) usage;;
138         -l|--link) ;; # maintained backward compatibility for now
139         -n) OPT_DRYRUN=true; OPT_YES=true
140            echo "$PROG: -n deprecated, use --dry-run or --non-block" 1>&2;;
141         --dry-run) OPT_DRYRUN=true; OPT_YES=true;;
142         -p|--pool) OPT_POOL="$arg $2"; OPT_LAYOUT+="$OPT_POOL "; shift;;
143         -q|--quiet) ECHO=:;;
144         -R|--restripe) OPT_RESTRIPE=true;;
145         -s|--skip) OPT_CHECK=false;;
146         -v|--verbose) OPT_DEBUG=true; ECHO=echo;;
147         -y|--yes) OPT_YES=true;;
148         -0) OPT_NULL=true;;
149         -b|--block|--non-block|--non-direct|-D|--no-verify)
150            # Always pass non-layout options to 'lfs migrate'
151            OPT_PASSTHROUGH+=("$arg");;
152         --rsync) OPT_RSYNC=true;;
153         --no-rsync) OPT_NO_RSYNC=true;;
154         --copy|--yaml|--file) OPT_COMP=true;
155            # these options have files as arguments, pass both through
156            OPT_LAYOUT+="$arg $2 "; shift;;
157         --auto-stripe|-A) OPT_AUTOSTRIPE=true;;
158         -C) OPT_CAP="$2"; shift;;
159         -M|--min-free) OPT_MINFREE="$2"; shift;;
160         -X|--max-free) OPT_MAXFREE="$2"; shift;;
161         -c|--stripe-count) OPT_STRIPE_COUNT="$2"; shift;;
162         -S|--stripe-size) OPT_STRIPE_SIZE="$2"; shift;;
163         *) # Pass other non-file layout options to 'lfs migrate'
164            [ -e "$arg" ] && OPT_FILE+="$arg " && break || OPT_LAYOUT+="$arg "
165         esac
166         shift
167 done
168
169 if $OPT_RESTRIPE || $OPT_AUTOSTRIPE && [ -n "$OPT_LAYOUT" ]; then
170         echo "$PROG error: Options '$OPT_LAYOUT' can't be used with -R or -A" \
171                 1>&2
172         exit 1
173 elif $OPT_RESTRIPE && [[ "$OPT_STRIPE_COUNT" || "$OPT_STRIPE_SIZE" ]]; then
174         echo "$PROG error: Option -R can't be used with -c or -S" 1>&2
175         exit 1
176 elif $OPT_AUTOSTRIPE && [ -n "$OPT_STRIPE_COUNT" ]; then
177         echo "$PROG error: Option -A can't be used with -c" 1>&2
178         exit 1
179 elif $OPT_AUTOSTRIPE && $OPT_RESTRIPE; then
180         echo "$PROG error: Option -A can't be used with -R" 1>&2
181         exit 1
182 fi
183
184 if $OPT_RSYNC && $OPT_NO_RSYNC; then
185         echo "$PROG: Options --rsync and --no-rsync may not be" \
186                 "specified at the same time." 1>&2
187         exit 1
188 fi
189
190 if ! $OPT_YES; then
191         echo ""
192         echo "lfs_migrate is currently NOT SAFE for moving in-use files." 1>&2
193         echo "Use it only when you are sure migrated files are unused." 1>&2
194         echo "" 1>&2
195         echo "If emptying an OST that is active on the MDS, new files may" 1>&2
196         echo "use it.  To stop allocating any new objects on OSTNNNN run:" 1>&2
197         echo "  lctl set_param osp.<fsname>-OSTNNNN*.max_create_count=0'" 1>&2
198         echo "on each MDS using the OST(s) being emptied." 1>&2
199         echo -n "Continue? (y/n) "
200         read CHECK
201         [ "$CHECK" != "y" -a "$CHECK" != "yes" ] && exit 1
202 fi
203
204 # if rsync has --xattr support, then try to copy the xattrs.
205 $RSYNC --help 2>&1 | grep -q xattr && RSYNC_OPTS="$RSYNC_OPTS -X"
206 $RSYNC --help 2>&1 | grep -q acls && RSYNC_OPTS="$RSYNC_OPTS -A"
207 # If rsync copies lustre xattrs in the future, then we can skip lfs (bug 22189)
208 strings $(which $RSYNC) 2>&1 | grep -q lustre && LFS=:
209
210 # rsync creates its temporary files with lenient permissions, even if
211 # permissions on the original files are more strict. Tighten umask here
212 # to avoid the brief window where unprivileged users might be able to
213 # access the temporary file.
214 umask 0077
215
216 # Use stripe count = sqrt(size_in_GB) + 1, but cap object size per OST.
217 function calc_stripe()
218 {
219         local filename=$1
220         local filekb=$2
221         local obj_max_kb=$3
222         local filegb=$((filekb / 1048576))
223         local stripe_count=1
224         local ost_max_count=0
225
226         # Files up to 1GB will have 1 stripe if they fit within the object max
227         if [[ $filegb -lt 1 && "$obj_max_kb" && $filekb -le $obj_max_kb ]]; then
228                 echo 1 "$obj_max_kb" && return
229         fi
230
231         stripe_count=$(bc <<< "scale=0; 1 + sqrt($filegb)" 2> /dev/null) ||
232                 { echo "cannot auto calculate stripe count" >&2; return; }
233
234         if [ -z "$obj_max_kb" ]; then
235                 local ost_min_kb=$((1 << 62))
236
237                 # Calculate cap on object size at 1% of smallest OST
238                 # but only include OSTs that have 256MB+ available space
239                 while IFS='' read avail; do
240                         [[ "$OPT_MAXFREE" && $avail -gt $OPT_MAXFREE ]] &&
241                                 avail=$OPT_MAXFREE
242                         if [ $avail -ge $OPT_MINFREE ]; then
243                                 ost_max_count=$((ost_max_count + 1))
244                                 if [ $avail -lt $ost_min_kb ]; then
245                                         ost_min_kb=$avail
246                                 fi
247                         fi
248                 done < <($LFS df $OPT_POOL "$OLDNAME" | awk '/OST/ { print $4 }')
249
250                 if [ $ost_max_count -eq 0 ]; then
251                         # no OSTs with enough space, stripe over all of them
252                         echo "-1" "0"
253                         return
254                 fi
255
256                 if (( ost_min_kb == (1 << 62) )); then
257                         echo "warning: unable to determine minimum OST size, " \
258                              "object size not capped" >&2
259                         echo "$stripe_count" "0"
260                         return
261                 fi
262
263                 obj_max_kb=$((ost_min_kb / $OPT_CAP))
264         elif [ $obj_max_kb -eq 0 ]; then
265                 echo "warning: unable to determine minimum OST size " \
266                      "from previous migrate, object size not capped" >&2
267                 echo "$stripe_count" "$obj_max_kb"
268                 return
269         fi
270
271         # If disk usage would exceed the cap, increase the number of stripes.
272         # Round up to the nearest MB to ensure file will fit.
273         (( filekb > stripe_count * obj_max_kb )) &&
274                 stripe_count=$(((filekb + obj_max_kb - 1024) / obj_max_kb))
275
276         # Limit the count to the number of eligible OSTs
277         if [ "$stripe_count" -gt $ost_max_count ]; then
278                 echo "$ost_max_count" "$obj_max_kb"
279         else
280                 echo "$stripe_count" "$obj_max_kb"
281         fi
282 }
283
284 lfs_migrate() {
285         local last_dev
286         local mntpoint
287
288         while IFS='' read -d '' OLDNAME; do
289                 local hlinks=()
290                 local layout
291                 local fid
292
293                 $ECHO -n "$OLDNAME: "
294
295                 # avoid duplicate stat call by fetching all attrs at once
296                 local nlink_idx_link=0 # %h is the hard link count
297                 local nlink_idx_type=1 # %F is "regular file", ignore others
298                 local nlink_idx_file=2 #       "file" is here
299                 local nlink_idx_size=3 # %s is file size in bytes
300                 local nlink_idx_dev=4  # %D is the underlying device number
301                 # nlink_type=(1 regular file 1234 0x810)
302                 local nlink_type=($(LANG=C stat -c "%h %F %s %D" "$OLDNAME" \
303                                  2> /dev/null))
304
305                 # skip non-regular files, since they don't have any objects
306                 # and there is no point in trying to migrate them.
307                 if [ "${nlink_type[$nlink_idx_type]}" != "regular" ]; then
308                         echo -e "\r$OLDNAME: not a regular file, skipped" 1>&2
309                         continue
310                 fi
311
312                 # working out write perms is hard, let the shell do it
313                 if [ ! -w "$OLDNAME" ]; then
314                         echo -e "\r$OLDNAME: no write permission, skipped" 1>&2
315                         continue
316                 fi
317
318                 if $OPT_DRYRUN && ! $OPT_DEBUG; then
319                         $ECHO "dry run, skipped"
320                         continue
321                 fi
322
323                 # xattrs use absolute file paths, so ensure provided path is
324                 # also absolute so that the names can be compared
325                 local oldname_absolute=$(readlink -f "$OLDNAME")
326                 if [ -z "$oldname_absolute" ]; then
327                         echo -e "\r$OLDNAME: cannot resolve full path, skipped" 1>&2
328                         continue
329                 fi
330                 OLDNAME=$oldname_absolute
331
332                 if [[ ${nlink_type[$nlink_idx_link]} -gt 1 ]] ||
333                    $RSYNC_WITH_HLINKS; then
334                         fid=$($LFS path2fid "$OLDNAME" 2> /dev/null)
335                         if [ $? -ne 0 ]; then
336                                 echo -e "\r$OLDNAME: cannot get FID, skipping; is this a Lustre file system?" 1>&2
337                                 continue
338                         fi
339
340                         # don't migrate a hard link if it was already migrated
341                         if path_in_set "$OLDNAME"; then
342                                 $ECHO "already migrated via another hard link"
343                                 continue
344                         fi
345
346                         # There is limited space available in the xattrs
347                         # to store all of the hard links for a file, so it's
348                         # possible that $OLDNAME is part of a link set but is
349                         # not listed in xattrs and therefore not listed as
350                         # being migrated.
351                         local migrated=$(old_fid_in_set "$fid")
352                         if [ -n "$migrated" ]; then
353                                 $ECHO "already migrated via another hard link"
354                                 # Only the rsync case has to relink.  The
355                                 # "lfs migrate" case keeps the same inode so
356                                 # all of the links are already correct.
357                                 $OPT_RSYNC && [ "$migrated" != "$OLDNAME" ] &&
358                                         ln -f "$migrated" "$OLDNAME"
359
360                                 add_to_set "$fid" "$OLDNAME"
361                                 continue;
362                         fi
363                 fi
364
365                 local olddir=$(dirname "$OLDNAME")
366                 local stripe_size="$OPT_STRIPE_SIZE"
367                 local stripe_count="$OPT_STRIPE_COUNT"
368                 local stripe_opts="-N --comp-count -c -S -p -y"
369                 local parent_count=""
370                 local parent_size=""
371                 local stripe_pool="${OPT_POOL#-p }"
372                 local mirror_count=1
373                 local comp_count=0
374                 # avoid multiple getstripe calls
375                 #   lcm_mirror_count:  1
376                 #   lcm_entry_count:   0
377                 #      lmm_stripe_count:  1
378                 #      lmm_stripe_size:   1048576
379                 #      lmm_pool:          pool_abc
380                 local l_mirror_count=0
381                 local l_comp_count=1
382                 local l_stripe_count=2
383                 local l_stripe_size=3
384                 local l_stripe_pool=4
385                 local layout_info=($($LFS getstripe $stripe_opts "$OLDNAME" \
386                         2>/dev/null | awk '{ print $2 }'))
387
388                 layout="${OPT_PASSTHROUGH[@]} "
389
390                 if $OPT_RESTRIPE; then
391                         UNLINK=""
392                         layout+="--copy $olddir"
393                         OPT_COMP=true
394                 else
395                         # If rsync copies Lustre xattrs properly in the future
396                         # (i.e. before the file data, so that it preserves
397                         # striping) then we don't need this getstripe stuff.
398                         UNLINK="-u"
399
400                         [ -n "$OPT_POOL" ] ||
401                                 stripe_pool=${layout_info[$l_stripe_pool]}
402                         mirror_count=${layout_info[$l_mirror_count]}
403
404                         if $OPT_AUTOSTRIPE; then
405                                 local filekb=$((${nlink_type[$nlink_idx_size]} /
406                                                 1024))
407
408                                 read stripe_count OBJ_MAX_KB < <(calc_stripe \
409                                         "$OLDNAME" "$filekb" "$OBJ_MAX_KB")
410                                 [ -z "$stripe_count" ] && exit 1
411                                 [ $stripe_count -lt 1 ] && stripe_count=1
412                         else
413                                 [ -n "$stripe_count" ] ||
414                                         stripe_count=${layout_info[$l_stripe_count]}
415                         fi
416                         [ -n "$stripe_size" ] ||
417                                 stripe_size=${layout_info[$l_stripe_size]}
418
419                         [ -z "$stripe_count" -o -z "$stripe_size" ] && UNLINK=""
420                 fi
421
422                 if $OPT_DEBUG; then
423                         local parent_count
424                         local parent_size
425                         local parent_layout
426
427                         if $OPT_RESTRIPE; then
428                                 parent_layout=($($LFS getstripe $stripe_opts \
429                                                 -d "$olddir" 2>/dev/null |
430                                                 awk '{print $2 }'))
431                                 parent_count=${parent_layout[$l_stripe_count]}
432                                 parent_size=${parent_layout[$l_stripe_size]}
433                                 stripe_pool=${parent_layout[$l_stripe_pool]}
434                                 mirror_count=${parent_layout[$l_mirror_count]}
435                         fi
436
437                         $ECHO -n "stripe_count=${stripe_count:-$parent_count},stripe_size=${stripe_size:-$parent_size}"
438                         [ -n "$stripe_pool" ] &&
439                                 $ECHO -n ",pool=${stripe_pool}"
440                         [[ $mirror_count -gt 1 ]] &&
441                                 $ECHO -n ",mirror_count=${mirror_count}"
442                         $ECHO -n " "
443                 fi
444
445                 if $OPT_DRYRUN; then
446                         $ECHO " dry run, skipped"
447                         continue
448                 fi
449
450                 if ! $OPT_COMP && [ ${layout_info[$l_comp_count]} -gt 0 ]; then
451                         layout+="--copy $OLDNAME"
452                         OPT_COMP=true
453                 fi
454                 if ! $OPT_COMP; then
455                         [ -n "$stripe_count" ] && layout+="-c $stripe_count "
456                         [ -n "$stripe_size" ] && layout+="-S $stripe_size "
457                         [ -n "$OPT_POOL" -a -n "$stripe_pool" ] &&
458                                                 layout+="-p $stripe_pool "
459                         [[ $mirror_count -gt 1 ]] && layout+="-N $mirror_count "
460                 fi
461                 layout+="$OPT_LAYOUT"
462
463                 # detect other hard links and store them on a global
464                 # list so we don't re-migrate them
465                 if [[ ${nlink_type[$nlink_idx_link]} -gt 1 ]]; then
466                         [ "${nlink_type[$nlink_idx_dev]}" == "$last_dev" ] ||
467                                 mntpoint=$(df -P "$OLDNAME" |
468                                            awk 'NR==2 { print $NF }')
469                         if [ -z "$mntpoint" ]; then
470                                 echo -e "\r$OLDNAME: cannot determine mount point; skipped" 1>&2
471                                 continue
472                         fi
473                         hlinks=$($LFS fid2path "$mntpoint" "$fid" 2> /dev/null)
474                         if $OPT_RSYNC && [ $? -ne 0 ]; then
475                                 echo -e "\r$OLDNAME: cannot determine hard link paths, skipped" 1>&2
476                                 continue
477                         fi
478                         hlinks+=("$OLDNAME")
479                 else
480                         hlinks=
481                 fi
482
483                 # first try to migrate via Lustre tools, then fall back to rsync
484                 if ! $OPT_RSYNC; then
485                         if $OPT_DEBUG; then
486                                 echo -e "\n$LFS migrate $layout \"$OLDNAME\""
487                         fi
488                         if $LFS migrate $layout "$OLDNAME"; then
489                                 $ECHO "done"
490                                 # no-op if hlinks empty for 1-link files
491                                 for link in ${hlinks[*]}; do
492                                         add_to_set "$fid" "$link"
493                                 done
494                                 continue
495                         elif $OPT_NO_RSYNC; then
496                                 echo -e "\r$OLDNAME: refusing to fall back to rsync, skipped" 1>&2
497                                 continue
498                         else
499                                 $ECHO -n "falling back to rsync: "
500                                 OPT_RSYNC=true
501                         fi
502                 fi
503
504                 local oldfile=$(basename "$OLDNAME")
505                 NEWNAME=$(mktemp $UNLINK "$olddir/.$oldfile.XXXXXX")
506                 if [ $? -ne 0 -o -z "$NEWNAME" ]; then
507                         echo -e "\r$OLDNAME: cannot make temp file, skipped" 1>&2
508                         continue
509                 fi
510
511                 if [ "$UNLINK" ]; then
512                         if ! $LFS setstripe $layout "$NEWNAME"; then
513                                 echo -e "\r$NEWNAME: setstripe failed, exiting" 1>&2
514                                 exit 2
515                         fi
516                 fi
517
518                 # we use --inplace, since we created our own temp file already
519                 if ! $RSYNC -a --inplace $RSYNC_OPTS "$OLDNAME" "$NEWNAME";then
520                         echo -e "\r$OLDNAME: copy error, exiting" 1>&2
521                         exit 4
522                 fi
523
524                 if $OPT_CHECK && ! cmp -s "$OLDNAME" "$NEWNAME"; then
525                         echo -e "\r$NEWNAME: compare failed, exiting" 1>&2
526                         exit 8
527                 fi
528
529                 if ! mv "$NEWNAME" "$OLDNAME"; then
530                         echo -e "\r$OLDNAME: rename error, exiting" 1>&2
531                         exit 12
532                 fi
533
534                 $ECHO "done rsync"
535                 # no-op if hlinks empty for 1-link files
536                 for link in ${hlinks[*]}; do
537                         if [ "$link" != "$OLDNAME" ]; then
538                                 ln -f "$OLDNAME" "$link"
539                         fi
540                         add_to_set "$fid" "$link"
541                 done
542
543                 # If the number of hlinks exceeds the space in the xattrs,
544                 # when the final path is statted it will have a link count
545                 # of 1 (all other links will point to the new inode).
546                 # This flag indicates that even paths with a link count of
547                 # 1 are potentially part of a link set.
548                 (( ${#hlinks[*]} == 1 )) || RSYNC_WITH_HLINKS=true
549         done
550 }
551
552 if [ "$#" -eq 0 ]; then
553         if $OPT_NULL; then
554                 lfs_migrate
555         else
556                 tr '\n' '\0' | lfs_migrate
557         fi
558 else
559         while [ "$1" ]; do
560                 if [ -d "$1" ]; then
561                         $LFS find "$1" -type f -print0
562                 else
563                         echo -en "$1\0"
564                 fi
565                 shift
566         done | lfs_migrate
567 fi
568