Whamcloud - gitweb
50fdea73b50c7abbe345a082cddd3009bf74f7cf
[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 -e "$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] [--help|-h] [--no-rsync|--rsync] [--quiet|-q]
49                    [--restripe|-R] [--skip|-s] [--verbose|-v] [--yes|-y] [-0]
50                    [FILE|DIR...]
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
61
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.
66
67 The --rsync and --no-rsync options may not be specified at the same time.
68
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.
71
72 Any arguments that are not explicitly recognized by the script are passed
73 through to the 'lfs migrate' utility.
74
75 Examples:
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
79 USAGE
80     exit 1
81 }
82
83 cleanup() {
84         rm -f "$MIGRATED_SET"
85         [ -n "$NEWNAME" ] && rm -f "$NEWNAME"
86 }
87
88 trap cleanup EXIT
89
90 OPT_CHECK=true
91 OPT_DEBUG=false
92 OPT_DRYRUN=false
93 OPT_FILE=()
94 OPT_LAYOUT=()
95 OPT_NO_RSYNC=false
96 OPT_NO_DIRECT=false
97 OPT_NULL=false
98 OPT_PASSTHROUGH=()
99 OPT_RESTRIPE=false
100 OPT_YES=false
101
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
106         arg="$1"
107         case "$arg" in
108         -h|--help) usage;;
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;;
112         -q|--quiet) ECHO=:;;
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;;
117         -0) OPT_NULL=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 "
128         esac
129         shift
130 done
131
132 if $OPT_RESTRIPE && [ -n "$OPT_LAYOUT" ]; then
133         echo "$PROG: Options $OPT_LAYOUT cannot be used with the -R option" 1>&2
134         exit 1
135 fi
136
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
140         exit 1
141 fi
142
143 if ! $OPT_YES; then
144         echo ""
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
147         echo "" 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) "
153         read CHECK
154         [ "$CHECK" != "y" -a "$CHECK" != "yes" ] && exit 1
155 fi
156
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=:
162
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.
167 umask 0077
168
169 lfs_migrate() {
170         while IFS='' read -d '' OLDNAME; do
171                 local hlinks=()
172                 local stripe_size
173                 local stripe_count
174                 local stripe_pool
175                 local mirror_count
176                 local layout
177
178                 $ECHO -n "$OLDNAME: "
179
180                 # avoid duplicate stat if possible
181                 local nlink_type=($(LANG=C stat -c "%h %F" "$OLDNAME"   \
182                                  2> /dev/null))
183
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"
188                         continue
189                 fi
190
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"
194                         continue
195                 fi
196
197                 if $OPT_DRYRUN && ! $OPT_DEBUG; then
198                         $ECHO "dry run, skipped"
199                         continue
200                 fi
201
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"
207                         continue
208                 fi
209                 OLDNAME=$oldname_absolute
210
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?"
219                         continue
220                 fi
221
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"
226                                 continue
227                         fi
228
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
233                         # being migrated.
234                         local migrated=$(old_fid_in_set "$fid")
235                         if [ -n "$migrated" ]; then
236                                 $ECHO -e "$OLDNAME: already migrated via another hard link"
237                                 if $OPT_RSYNC; then
238                                         # Only the rsync case has to relink.
239                                         # The lfs migrate case preserves the
240                                         # inode so the links are already
241                                         # correct.
242                                         [ "$migrated" != "$OLDNAME" ] &&
243                                                 ln -f "$migrated" "$OLDNAME"
244                                 fi
245                                 add_to_set "$fid" "$OLDNAME"
246                                 continue;
247                         fi
248                 fi
249
250                 if $OPT_RESTRIPE; then
251                         UNLINK=""
252                 else
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.
256                         UNLINK="-u"
257
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)
262
263                         [ -z "$stripe_count" -o -z "$stripe_size" ] && UNLINK=""
264                 fi
265
266                 if $OPT_DEBUG; then
267                         local parent_count
268                         local parent_size
269
270                         if $OPT_RESTRIPE; then
271                                 parent_count=$($LFS getstripe -c \
272                                                $(dirname "$OLDNAME") 2> \
273                                                /dev/null)
274                                 parent_size=$($LFS getstripe -S \
275                                               $(dirname "$OLDNAME") 2> \
276                                               /dev/null)
277                                 stripe_pool=$($LFS getstripe --pool \
278                                               $(dirname "$OLDNAME") 2> \
279                                               /dev/null)
280                                 mirror_count=$($LFS getstripe -N \
281                                                $(dirname "$OLDFILE") 2> \
282                                                /dev/null)
283                         fi
284
285                         $ECHO -n "stripe" \
286                                 "count=${stripe_count:-$parent_count}," \
287                                 "size=${stripe_size:-$parent_size}," \
288                                 "pool=${stripe_pool}," \
289                                 "mirror_count=${mirror_count}"
290                 fi
291
292                 if $OPT_DRYRUN; then
293                         $ECHO "dry run, skipped"
294                         continue
295                 fi
296
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 \
302                         $OPT_LAYOUT"
303
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"
310                         continue
311                 fi
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"
315                         continue
316                 fi
317                 hlinks+=("$OLDNAME")
318
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 \
322                            "$OLDNAME"; then
323                                 $ECHO "done migrate"
324                                 for link in ${hlinks[*]}; do
325                                         add_to_set "$fid" "$link"
326                                 done
327                                 continue
328                         elif $OPT_NO_RSYNC; then
329                                 echo -e "\r\e[K$OLDNAME: refusing to fall back to rsync, skipped" 1>&2
330                                 continue
331                         else
332                                 $ECHO -n "falling back to rsync: "
333                                 OPT_RSYNC=true
334                         fi
335                 fi
336
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
340                         continue
341                 fi
342
343                 if [ "$UNLINK" ]; then
344                         if ! $LFS setstripe "${OPT_PASSTHROUGH}" $layout \
345                              "$NEWNAME"; then
346                                 echo -e "\r\e[K$NEWNAME: setstripe failed, exiting" 1>&2
347                                 exit 2
348                         fi
349                 fi
350
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
354                         exit 4
355                 fi
356
357                 if $OPT_CHECK && ! cmp -s "$OLDNAME" "$NEWNAME"; then
358                         echo -e "\r\e[K$NEWNAME: compare failed, exiting" 1>&2
359                         exit 8
360                 fi
361
362                 if ! mv "$NEWNAME" "$OLDNAME"; then
363                         echo -e "\r\e[K$OLDNAME: rename error, exiting" 1>&2
364                         exit 12
365                 fi
366
367                 $ECHO "done migrate via rsync"
368                 for link in ${hlinks[*]}; do
369                         if [ "$link" != "$OLDNAME" ]; then
370                                 ln -f "$OLDNAME" "$link"
371                         fi
372                         add_to_set "$fid" "$link"
373                 done
374
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
381         done
382 }
383
384 if [ "$#" -eq 0 ]; then
385         if $OPT_NULL; then
386                 lfs_migrate
387         else
388                 tr '\n' '\0' | lfs_migrate
389         fi
390 else
391         while [ "$1" ]; do
392                 if [ -d "$1" ]; then
393                         $LFS find "$1" -type f -print0
394                 else
395                         echo -en "$1\0"
396                 fi
397                 shift
398         done | lfs_migrate
399 fi
400