Tizen 2.1 base
[external/device-mapper.git] / scripts / fsadm.sh
1 #!/bin/bash
2 #
3 # Copyright (C) 2007-2010 Red Hat, Inc. All rights reserved.
4 #
5 # This file is part of LVM2.
6 #
7 # This copyrighted material is made available to anyone wishing to use,
8 # modify, copy, or redistribute it subject to the terms and conditions
9 # of the GNU General Public License v.2.
10 #
11 # You should have received a copy of the GNU General Public License
12 # along with this program; if not, write to the Free Software Foundation,
13 # Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
14 #
15 # Author: Zdenek Kabelac <zkabelac at redhat.com>
16 #
17 # Script for resizing devices (usable for LVM resize)
18 #
19 # Needed utilities:
20 #   mount, umount, grep, readlink, blockdev, blkid, fsck, xfs_check
21 #
22 # ext2/ext3/ext4: resize2fs, tune2fs
23 # reiserfs: resize_reiserfs, reiserfstune
24 # xfs: xfs_growfs, xfs_info
25 #
26 # Return values:
27 #   0 success
28 #   1 error
29 #   2 break detected
30 #   3 unsupported online filesystem check for given mounted fs
31
32 TOOL=fsadm
33
34 PATH=/sbin:/usr/sbin:/bin:/usr/sbin:$PATH
35
36 # utilities
37 TUNE_EXT=tune2fs
38 RESIZE_EXT=resize2fs
39 TUNE_REISER=reiserfstune
40 RESIZE_REISER=resize_reiserfs
41 TUNE_XFS=xfs_info
42 RESIZE_XFS=xfs_growfs
43
44 MOUNT=mount
45 UMOUNT=umount
46 MKDIR=mkdir
47 RMDIR=rmdir
48 BLOCKDEV=blockdev
49 BLKID=blkid
50 DATE=date
51 GREP=grep
52 READLINK=readlink
53 READLINK_E="-e"
54 FSCK=fsck
55 XFS_CHECK=xfs_check
56
57 # user may override lvm location by setting LVM_BINARY
58 LVM=${LVM_BINARY:-lvm}
59
60 YES=${_FSADM_YES}
61 DRY=0
62 VERB=
63 FORCE=
64 EXTOFF=0
65 DO_LVRESIZE=0
66 FSTYPE=unknown
67 VOLUME=unknown
68 TEMPDIR="${TMPDIR:-/tmp}/${TOOL}_${RANDOM}$$/m"
69 BLOCKSIZE=
70 BLOCKCOUNT=
71 MOUNTPOINT=
72 MOUNTED=
73 REMOUNT=
74 PROCMOUNTS="/proc/mounts"
75
76 IFS_OLD=$IFS
77 # without bash $'\n'
78 NL='
79 '
80
81 tool_usage() {
82         echo "${TOOL}: Utility to resize or check the filesystem on a device"
83         echo
84         echo "  ${TOOL} [options] check device"
85         echo "    - Check the filesystem on device using fsck"
86         echo
87         echo "  ${TOOL} [options] resize device [new_size[BKMGTPE]]"
88         echo "    - Change the size of the filesystem on device to new_size"
89         echo
90         echo "  Options:"
91         echo "    -h | --help         Show this help message"
92         echo "    -v | --verbose      Be verbose"
93         echo "    -e | --ext-offline  unmount filesystem before ext2/ext3/ext4 resize"
94         echo "    -f | --force        Bypass sanity checks"
95         echo "    -n | --dry-run      Print commands without running them"
96         echo "    -l | --lvresize     Resize given device (if it is LVM device)"
97         echo "    -y | --yes          Answer \"yes\" at any prompts"
98         echo
99         echo "  new_size - Absolute number of filesystem blocks to be in the filesystem,"
100         echo "             or an absolute size using a suffix (in powers of 1024)."
101         echo "             If new_size is not supplied, the whole device is used."
102
103         exit
104 }
105
106 verbose() {
107         test -n "$VERB" && echo "$TOOL: $@" || true
108 }
109
110 error() {
111         echo "$TOOL: $@" >&2
112         cleanup 1
113 }
114
115 dry() {
116         if [ "$DRY" -ne 0 ]; then
117                 verbose "Dry execution $@"
118                 return 0
119         fi
120         verbose "Executing $@"
121         $@
122 }
123
124 cleanup() {
125         trap '' 2
126         # reset MOUNTPOINT - avoid recursion
127         test "$MOUNTPOINT" = "$TEMPDIR" && MOUNTPOINT="" temp_umount
128         if [ -n "$REMOUNT" ]; then
129                 verbose "Remounting unmounted filesystem back"
130                 dry $MOUNT "$VOLUME" "$MOUNTED"
131         fi
132         IFS=$IFS_OLD
133         trap 2
134
135         test "$1" -eq 2 && verbose "Break detected"
136
137         if [ "$DO_LVRESIZE" -eq 2 ]; then
138                 # start LVRESIZE with the filesystem modification flag
139                 # and allow recursive call of fsadm
140                 _FSADM_YES=$YES
141                 export _FSADM_YES
142                 unset FSADM_RUNNING
143                 dry exec $LVM lvresize $VERB $FORCE -r -L${NEWSIZE}b $VOLUME_ORIG
144         fi
145         
146         # error exit status for break
147         exit ${1:-1}
148 }
149
150 # convert parameter from Exa/Peta/Tera/Giga/Mega/Kilo/Bytes and blocks
151 # (2^(60/50/40/30/20/10/0))
152 decode_size() {
153         case "$1" in
154          *[eE]) NEWSIZE=$(( ${1%[eE]} * 1152921504606846976 )) ;;
155          *[pP]) NEWSIZE=$(( ${1%[pP]} * 1125899906842624 )) ;;
156          *[tT]) NEWSIZE=$(( ${1%[tT]} * 1099511627776 )) ;;
157          *[gG]) NEWSIZE=$(( ${1%[gG]} * 1073741824 )) ;;
158          *[mM]) NEWSIZE=$(( ${1%[mM]} * 1048576 )) ;;
159          *[kK]) NEWSIZE=$(( ${1%[kK]} * 1024 )) ;;
160          *[bB]) NEWSIZE=${1%[bB]} ;;
161              *) NEWSIZE=$(( $1 * $2 )) ;;
162         esac
163         #NEWBLOCKCOUNT=$(round_block_size $NEWSIZE $2)
164         NEWBLOCKCOUNT=$(( $NEWSIZE / $2 ))
165
166         if [ $DO_LVRESIZE -eq 1 ]; then
167                 # start lvresize, but first cleanup mounted dirs
168                 DO_LVRESIZE=2
169                 cleanup 0
170         fi
171 }
172
173 # detect filesystem on the given device
174 # dereference device name if it is symbolic link
175 detect_fs() {
176         VOLUME_ORIG=$1
177         VOLUME=${1#/dev/}
178         VOLUME=$($READLINK $READLINK_E "/dev/$VOLUME") || error "Cannot get readlink $1"
179         RVOLUME=$VOLUME
180         case "$RVOLUME" in
181           /dev/dm-[0-9]*)
182                 read </sys/block/${RVOLUME#/dev/}/dm/name SYSVOLUME 2>&1 && VOLUME="/dev/mapper/$SYSVOLUME"
183                 ;;
184         esac
185         # use /dev/null as cache file to be sure about the result
186         # not using option '-o value' to be compatible with older version of blkid
187         FSTYPE=$($BLKID -c /dev/null -s TYPE "$VOLUME") || error "Cannot get FSTYPE of \"$VOLUME\""
188         FSTYPE=${FSTYPE##*TYPE=\"} # cut quotation marks
189         FSTYPE=${FSTYPE%%\"*}
190         verbose "\"$FSTYPE\" filesystem found on \"$VOLUME\""
191 }
192
193 # check if the given device is already mounted and where
194 # FIXME: resolve swap usage and device stacking
195 detect_mounted()  {
196         test -e $PROCMOUNTS || error "Cannot detect mounted device $VOLUME"
197
198         MOUNTED=$($GREP ^"$VOLUME" $PROCMOUNTS)
199
200         # for empty string try again with real volume name
201         test -z "$MOUNTED" && MOUNTED=$($GREP ^"$RVOLUME" $PROCMOUNTS)
202
203         # cut device name prefix and trim everything past mountpoint
204         # echo translates \040 to spaces
205         MOUNTED=${MOUNTED#* }
206         MOUNTED=$(echo -n -e ${MOUNTED%% *})
207
208         # for systems with different device names - check also mount output
209         if test -z "$MOUNTED" ; then
210                 MOUNTED=$(LANG=C $MOUNT | $GREP ^"$VOLUME")
211                 test -z "$MOUNTED" && MOUNTED=$(LANG=C $MOUNT | $GREP ^"$RVOLUME")
212                 MOUNTED=${MOUNTED##* on }
213                 MOUNTED=${MOUNTED% type *} # allow type in the mount name
214         fi
215
216         test -n "$MOUNTED"
217 }
218
219 # get the full size of device in bytes
220 detect_device_size() {
221         # check if blockdev supports getsize64
222         $BLOCKDEV 2>&1 | $GREP getsize64 >/dev/null
223         if test $? -eq 0; then
224                 DEVSIZE=$($BLOCKDEV --getsize64 "$VOLUME") || error "Cannot read size of device \"$VOLUME\""
225         else
226                 DEVSIZE=$($BLOCKDEV --getsize "$VOLUME") || error "Cannot read size of device \"$VOLUME\""
227                 SSSIZE=$($BLOCKDEV --getss "$VOLUME") || error "Cannot block size read device \"$VOLUME\""
228                 DEVSIZE=$(($DEVSIZE * $SSSIZE))
229         fi
230 }
231
232 # round up $1 / $2
233 # could be needed to gaurantee 'at least given size'
234 # but it makes many troubles
235 round_up_block_size() {
236         echo $(( ($1 + $2 - 1) / $2 ))
237 }
238
239 temp_mount() {
240         dry $MKDIR -p -m 0000 "$TEMPDIR" || error "Failed to create $TEMPDIR"
241         dry $MOUNT "$VOLUME" "$TEMPDIR" || error "Failed to mount $TEMPDIR"
242 }
243
244 temp_umount() {
245         dry $UMOUNT "$TEMPDIR" || error "Failed to umount $TEMPDIR"
246         dry $RMDIR "${TEMPDIR}" || error "Failed to remove $TEMPDIR"
247         dry $RMDIR "${TEMPDIR%%m}" || error "Failed to remove ${TEMPDIR%%m}"
248 }
249
250 yes_no() {
251         echo -n "$@? [Y|n] "
252
253         if [ -n "$YES" ]; then
254                 echo y ; return 0
255         fi
256
257         while read -r -s -n 1 ANS ; do
258                 case "$ANS" in
259                  "y" | "Y" | "") echo y ; return 0 ;;
260                  "n" | "N") echo n ; return 1 ;;
261                 esac
262         done
263 }
264
265 try_umount() {
266         yes_no "Do you want to unmount \"$MOUNTED\"" && dry $UMOUNT "$MOUNTED" && return 0
267         error "Cannot proceed with mounted filesystem \"$MOUNTED\""
268 }
269
270 validate_parsing() {
271         test -n "$BLOCKSIZE" -a -n "$BLOCKCOUNT" || error "Cannot parse $1 output"
272 }
273 ####################################
274 # Resize ext2/ext3/ext4 filesystem
275 # - unmounted or mounted for upsize
276 # - unmounted for downsize
277 ####################################
278 resize_ext() {
279         verbose "Parsing $TUNE_EXT -l \"$VOLUME\""
280         for i in $(LANG=C $TUNE_EXT -l "$VOLUME"); do
281                 case "$i" in
282                   "Block size"*) BLOCKSIZE=${i##*  } ;;
283                   "Block count"*) BLOCKCOUNT=${i##*  } ;;
284                 esac
285         done
286         validate_parsing $TUNE_EXT
287         decode_size $1 $BLOCKSIZE
288         FSFORCE=$FORCE
289
290         if [ "$NEWBLOCKCOUNT" -lt "$BLOCKCOUNT" -o "$EXTOFF" -eq 1 ]; then
291                 detect_mounted && verbose "$RESIZE_EXT needs unmounted filesystem" && try_umount
292                 REMOUNT=$MOUNTED
293                 if test -n "$MOUNTED" ; then
294                         # Forced fsck -f for umounted extX filesystem.
295                         case "$-" in
296                           *i*) dry $FSCK $YES -f "$VOLUME" ;;
297                           *) dry $FSCK -f -p "$VOLUME" ;;
298                         esac
299                 fi
300         fi
301
302         verbose "Resizing filesystem on device \"$VOLUME\" to $NEWSIZE bytes ($BLOCKCOUNT -> $NEWBLOCKCOUNT blocks of $BLOCKSIZE bytes)"
303         dry $RESIZE_EXT $FSFORCE "$VOLUME" $NEWBLOCKCOUNT
304 }
305
306 #############################
307 # Resize reiserfs filesystem
308 # - unmounted for upsize
309 # - unmounted for downsize
310 #############################
311 resize_reiser() {
312         detect_mounted && verbose "ReiserFS resizes only unmounted filesystem" && try_umount
313         REMOUNT=$MOUNTED
314         verbose "Parsing $TUNE_REISER \"$VOLUME\""
315         for i in $(LANG=C $TUNE_REISER "$VOLUME"); do
316                 case "$i" in
317                   "Blocksize"*) BLOCKSIZE=${i##*: } ;;
318                   "Count of blocks"*) BLOCKCOUNT=${i##*: } ;;
319                 esac
320         done
321         validate_parsing $TUNE_REISER
322         decode_size $1 $BLOCKSIZE
323         verbose "Resizing \"$VOLUME\" $BLOCKCOUNT -> $NEWBLOCKCOUNT blocks ($NEWSIZE bytes, bs: $NEWBLOCKCOUNT)"
324         if [ -n "$YES" ]; then
325                 echo y | dry $RESIZE_REISER -s $NEWSIZE "$VOLUME"
326         else
327                 dry $RESIZE_REISER -s $NEWSIZE "$VOLUME"
328         fi
329 }
330
331 ########################
332 # Resize XFS filesystem
333 # - mounted for upsize
334 # - cannot downsize
335 ########################
336 resize_xfs() {
337         detect_mounted
338         MOUNTPOINT=$MOUNTED
339         if [ -z "$MOUNTED" ]; then
340                 MOUNTPOINT=$TEMPDIR
341                 temp_mount || error "Cannot mount Xfs filesystem"
342         fi
343         verbose "Parsing $TUNE_XFS \"$MOUNTPOINT\""
344         for i in $(LANG=C $TUNE_XFS "$MOUNTPOINT"); do
345                 case "$i" in
346                   "data"*) BLOCKSIZE=${i##*bsize=} ; BLOCKCOUNT=${i##*blocks=} ;;
347                 esac
348         done
349         BLOCKSIZE=${BLOCKSIZE%%[^0-9]*}
350         BLOCKCOUNT=${BLOCKCOUNT%%[^0-9]*}
351         validate_parsing $TUNE_XFS
352         decode_size $1 $BLOCKSIZE
353         if [ $NEWBLOCKCOUNT -gt $BLOCKCOUNT ]; then
354                 verbose "Resizing Xfs mounted on \"$MOUNTPOINT\" to fill device \"$VOLUME\""
355                 dry $RESIZE_XFS $MOUNTPOINT
356         elif [ $NEWBLOCKCOUNT -eq $BLOCKCOUNT ]; then
357                 verbose "Xfs filesystem already has the right size"
358         else
359                 error "Xfs filesystem shrinking is unsupported"
360         fi
361 }
362
363 ####################
364 # Resize filesystem
365 ####################
366 resize() {
367         NEWSIZE=$2
368         detect_fs "$1"
369         detect_device_size
370         verbose "Device \"$VOLUME\" size is $DEVSIZE bytes"
371         # if the size parameter is missing use device size
372         #if [ -n "$NEWSIZE" -a $NEWSIZE <
373         test -z "$NEWSIZE" && NEWSIZE=${DEVSIZE}b
374         IFS=$NL
375         case "$FSTYPE" in
376           "ext3"|"ext2"|"ext4") resize_ext $NEWSIZE ;;
377           "reiserfs") resize_reiser $NEWSIZE ;;
378           "xfs") resize_xfs $NEWSIZE ;;
379           *) error "Filesystem \"$FSTYPE\" on device \"$VOLUME\" is not supported by this tool" ;;
380         esac || error "Resize $FSTYPE failed"
381         cleanup 0
382 }
383
384 ####################################
385 # Calclulate diff between two dates
386 #  LANG=C input is expected the
387 #  only one supported
388 ####################################
389 diff_dates() {
390          echo $(( $($DATE -u -d"$1" +%s 2>/dev/null) - $($DATE -u -d"$2" +%s 2>/dev/null) ))
391 }
392
393 ###################
394 # Check filesystem
395 ###################
396 check() {
397         detect_fs "$1"
398         if detect_mounted ; then
399                 verbose "Skipping filesystem check for device \"$VOLUME\" as the filesystem is mounted on $MOUNTED";
400                 cleanup 3
401         fi
402
403         case "$FSTYPE" in
404           "ext2"|"ext3"|"ext4")
405                 IFS_CHECK=$IFS
406                 IFS=$NL
407                 for i in $(LANG=C $TUNE_EXT -l "$VOLUME"); do
408                         case "$i" in
409                           "Last mount"*) LASTMOUNT=${i##*: } ;;
410                           "Last checked"*) LASTCHECKED=${i##*: } ;;
411                         esac
412                 done
413                 case "$LASTMOUNT" in
414                   *"n/a") ;; # nothing to do - system was not mounted yet
415                   *)
416                         LASTDIFF=$(diff_dates $LASTMOUNT $LASTCHECKED)
417                         if test "$LASTDIFF" -gt 0 ; then
418                                 verbose "Filesystem has not been checked after the last mount, using fsck -f"
419                                 FORCE="-f"
420                         fi
421                         ;;
422                 esac
423                 IFS=$IFS_CHECK
424         esac
425
426         case "$FSTYPE" in
427           "xfs") dry $XFS_CHECK "$VOLUME" ;;
428           *)    # check if executed from interactive shell environment
429                 case "$-" in
430                   *i*) dry $FSCK $YES $FORCE "$VOLUME" ;;
431                   *) dry $FSCK $FORCE -p "$VOLUME" ;;
432                 esac
433         esac
434 }
435
436 #############################
437 # start point of this script
438 # - parsing parameters
439 #############################
440 trap "cleanup 2" 2
441
442 # test if we are not invoked recursively
443 test -n "$FSADM_RUNNING" && exit 0
444
445 # test some prerequisities
446 test -n "$TUNE_EXT" -a -n "$RESIZE_EXT" -a -n "$TUNE_REISER" -a -n "$RESIZE_REISER" \
447   -a -n "$TUNE_XFS" -a -n "$RESIZE_XFS" -a -n "$MOUNT" -a -n "$UMOUNT" -a -n "$MKDIR" \
448   -a -n "$RMDIR" -a -n "$BLOCKDEV" -a -n "$BLKID" -a -n "$GREP" -a -n "$READLINK" \
449   -a -n "$DATE" -a -n "$FSCK" -a -n "$XFS_CHECK" -a -n "LVM" \
450   || error "Required command definitions in the script are missing!"
451
452 $LVM version >/dev/null 2>&1 || error "Could not run lvm binary '$LVM'"
453 $($READLINK -e / >/dev/null 2>&1) || READLINK_E="-f"
454 TEST64BIT=$(( 1000 * 1000000000000 ))
455 test $TEST64BIT -eq 1000000000000000 || error "Shell does not handle 64bit arithmetic"
456 $(echo Y | $GREP Y >/dev/null) || error "Grep does not work properly"
457 test $($DATE -u -d"Jan 01 00:00:01 1970" +%s) -eq 1 || error "Date translation does not work"
458
459
460 if [ "$#" -eq 0 ] ; then
461         tool_usage
462 fi
463
464 while [ "$#" -ne 0 ]
465 do
466          case "$1" in
467           "") ;;
468           "-h"|"--help") tool_usage ;;
469           "-v"|"--verbose") VERB="-v" ;;
470           "-n"|"--dry-run") DRY=1 ;;
471           "-f"|"--force") FORCE="-f" ;;
472           "-e"|"--ext-offline") EXTOFF=1 ;;
473           "-y"|"--yes") YES="-y" ;;
474           "-l"|"--lvresize") DO_LVRESIZE=1 ;;
475           "check") CHECK="$2" ; shift ;;
476           "resize") RESIZE="$2"; NEWSIZE="$3" ; shift 2 ;;
477           *) error "Wrong argument \"$1\". (see: $TOOL --help)"
478         esac
479         shift
480 done
481
482 if [ -n "$CHECK" ]; then
483         check "$CHECK"
484 elif [ -n "$RESIZE" ]; then
485         export FSADM_RUNNING="fsadm"
486         resize "$RESIZE" "$NEWSIZE"
487 else
488         error "Missing command. (see: $TOOL --help)"
489 fi