Home | History | Annotate | Line # | Download | only in scripts
      1 #!/bin/sh
      2 #
      3 # Copyright (C) 2007-2009 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 
     27 TOOL=fsadm
     28 
     29 PATH=/sbin:/usr/sbin:/bin:/usr/sbin:$PATH
     30 
     31 # utilities
     32 TUNE_EXT=tune2fs
     33 RESIZE_EXT=resize2fs
     34 TUNE_REISER=reiserfstune
     35 RESIZE_REISER=resize_reiserfs
     36 TUNE_XFS=xfs_info
     37 RESIZE_XFS=xfs_growfs
     38 
     39 MOUNT=mount
     40 UMOUNT=umount
     41 MKDIR=mkdir
     42 RMDIR=rmdir
     43 BLOCKDEV=blockdev
     44 BLKID=blkid
     45 GREP=grep
     46 READLINK=readlink
     47 READLINK_E="-e"
     48 FSCK=fsck
     49 XFS_CHECK=xfs_check
     50 
     51 # user may override lvm location by setting LVM_BINARY
     52 LVM=${LVM_BINARY-lvm}
     53 
     54 YES=
     55 DRY=0
     56 VERB=
     57 FORCE=
     58 EXTOFF=0
     59 DO_LVRESIZE=0
     60 FSTYPE=unknown
     61 VOLUME=unknown
     62 TEMPDIR="${TMPDIR:-/tmp}/${TOOL}_${RANDOM}$$/m"
     63 BLOCKSIZE=
     64 BLOCKCOUNT=
     65 MOUNTPOINT=
     66 MOUNTED=
     67 REMOUNT=
     68 
     69 IFS_OLD=$IFS
     70 # without bash $'\n'
     71 NL='
     72 '
     73 
     74 tool_usage() {
     75 	echo "${TOOL}: Utility to resize or check the filesystem on a device"
     76 	echo
     77 	echo "  ${TOOL} [options] check device"
     78 	echo "    - Check the filesystem on device using fsck"
     79 	echo
     80 	echo "  ${TOOL} [options] resize device [new_size[BKMGTPE]]"
     81 	echo "    - Change the size of the filesystem on device to new_size"
     82 	echo
     83 	echo "  Options:"
     84 	echo "    -h | --help         Show this help message"
     85 	echo "    -v | --verbose      Be verbose"
     86 	echo "    -e | --ext-offline  unmount filesystem before ext2/ext3/ext4 resize"
     87 	echo "    -f | --force        Bypass sanity checks"
     88 	echo "    -n | --dry-run      Print commands without running them"
     89 	echo "    -l | --lvresize     Resize given device (if it is LVM device)"
     90 	echo "    -y | --yes          Answer \"yes\" at any prompts"
     91 	echo
     92 	echo "  new_size - Absolute number of filesystem blocks to be in the filesystem,"
     93 	echo "             or an absolute size using a suffix (in powers of 1024)."
     94 	echo "             If new_size is not supplied, the whole device is used."
     95 
     96 	exit
     97 }
     98 
     99 verbose() {
    100 	test -n "$VERB" && echo "$TOOL: $@" || true
    101 }
    102 
    103 error() {
    104 	echo "$TOOL: $@" >&2
    105 	cleanup 1
    106 }
    107 
    108 dry() {
    109 	if [ "$DRY" -ne 0 ]; then
    110 		verbose "Dry execution $@"
    111 		return 0
    112 	fi
    113 	verbose "Executing $@"
    114 	$@
    115 }
    116 
    117 cleanup() {
    118 	trap '' 2
    119 	# reset MOUNTPOINT - avoid recursion
    120 	test "$MOUNTPOINT" = "$TEMPDIR" && MOUNTPOINT="" temp_umount
    121 	if [ -n "$REMOUNT" ]; then
    122 		verbose "Remounting unmounted filesystem back"
    123 		dry $MOUNT "$VOLUME" "$MOUNTED"
    124 	fi
    125 	IFS=$IFS_OLD
    126 	trap 2
    127 
    128 	# start LVRESIZE with the filesystem modification flag
    129 	# and allow recursive call of fsadm
    130 	unset FSADM_RUNNING
    131 	test "$DO_LVRESIZE" -eq 2 && exec $LVM lvresize $VERB -r -L$(( $NEWSIZE / 1048576 )) $VOLUME
    132 	exit ${1:-0}
    133 }
    134 
    135 # convert parameter from Exa/Peta/Tera/Giga/Mega/Kilo/Bytes and blocks
    136 # (2^(60/50/40/30/20/10/0))
    137 decode_size() {
    138 	case "$1" in
    139 	 *[eE]) NEWSIZE=$(( ${1%[eE]} * 1152921504606846976 )) ;;
    140 	 *[pP]) NEWSIZE=$(( ${1%[pP]} * 1125899906842624 )) ;;
    141 	 *[tT]) NEWSIZE=$(( ${1%[tT]} * 1099511627776 )) ;;
    142 	 *[gG]) NEWSIZE=$(( ${1%[gG]} * 1073741824 )) ;;
    143 	 *[mM]) NEWSIZE=$(( ${1%[mM]} * 1048576 )) ;;
    144 	 *[kK]) NEWSIZE=$(( ${1%[kK]} * 1024 )) ;;
    145 	 *[bB]) NEWSIZE=${1%[bB]} ;;
    146 	     *) NEWSIZE=$(( $1 * $2 )) ;;
    147 	esac
    148 	#NEWBLOCKCOUNT=$(round_block_size $NEWSIZE $2)
    149 	NEWBLOCKCOUNT=$(( $NEWSIZE / $2 ))
    150 
    151 	if [ $DO_LVRESIZE -eq 1 ]; then
    152 		# start lvresize, but first cleanup mounted dirs
    153 		DO_LVRESIZE=2
    154 		cleanup 0
    155 	fi
    156 }
    157 
    158 # detect filesystem on the given device
    159 # dereference device name if it is symbolic link
    160 detect_fs() {
    161         VOLUME=${1#/dev/}
    162 	VOLUME=$($READLINK $READLINK_E "/dev/$VOLUME") || error "Cannot get readlink $1"
    163 	# strip newline from volume name
    164 	VOLUME=${VOLUME%%$NL}
    165 	# use /dev/null as cache file to be sure about the result
    166 	# not using option '-o value' to be compatible with older version of blkid
    167 	FSTYPE=$($BLKID -c /dev/null -s TYPE "$VOLUME") || error "Cannot get FSTYPE of \"$VOLUME\""
    168 	FSTYPE=${FSTYPE##*TYPE=\"} # cut quotation marks
    169 	FSTYPE=${FSTYPE%%\"*}
    170 	verbose "\"$FSTYPE\" filesystem found on \"$VOLUME\""
    171 }
    172 
    173 # check if the given device is already mounted and where
    174 detect_mounted()  {
    175 	$MOUNT >/dev/null || error "Cannot detect mounted device $VOLUME"
    176 	MOUNTED=$($MOUNT | $GREP "$VOLUME")
    177 	MOUNTED=${MOUNTED##* on }
    178 	MOUNTED=${MOUNTED% type *} # allow type in the mount name
    179 	test -n "$MOUNTED"
    180 }
    181 
    182 # get the full size of device in bytes
    183 detect_device_size() {
    184 	# check if blockdev supports getsize64
    185 	$BLOCKDEV 2>&1 | $GREP getsize64 >/dev/null
    186 	if test $? -eq 0; then
    187 		DEVSIZE=$($BLOCKDEV --getsize64 "$VOLUME") || error "Cannot read size of device \"$VOLUME\""
    188 	else
    189 		DEVSIZE=$($BLOCKDEV --getsize "$VOLUME") || error "Cannot read size of device \"$VOLUME\""
    190 		SSSIZE=$($BLOCKDEV --getss "$VOLUME") || error "Cannot block size read device \"$VOLUME\""
    191 		DEVSIZE=$(($DEVSIZE * $SSSIZE))
    192 	fi
    193 }
    194 
    195 # round up $1 / $2
    196 # could be needed to gaurantee 'at least given size'
    197 # but it makes many troubles
    198 round_up_block_size() {
    199 	echo $(( ($1 + $2 - 1) / $2 ))
    200 }
    201 
    202 temp_mount() {
    203 	dry $MKDIR -p -m 0000 "$TEMPDIR" || error "Failed to create $TEMPDIR"
    204 	dry $MOUNT "$VOLUME" "$TEMPDIR" || error "Failed to mount $TEMPDIR"
    205 }
    206 
    207 temp_umount() {
    208 	dry $UMOUNT "$TEMPDIR" || error "Failed to umount $TEMPDIR"
    209 	dry $RMDIR "${TEMPDIR}" || error "Failed to remove $TEMPDIR"
    210 	dry $RMDIR "${TEMPDIR%%m}" || error "Failed to remove ${TEMPDIR%%m}"
    211 }
    212 
    213 yes_no() {
    214 	echo -n "$@? [Y|n] "
    215 
    216 	if [ -n "$YES" ]; then
    217 		echo y ; return 0
    218 	fi
    219 
    220 	while read -r -s -n 1 ANS ; do
    221 		case "$ANS" in
    222 		 "y" | "Y" | "") echo y ; return 0 ;;
    223 		 "n" | "N") echo n ; return 1 ;;
    224 		esac
    225 	done
    226 }
    227 
    228 try_umount() {
    229 	yes_no "Do you want to unmount \"$MOUNTED\"" && dry $UMOUNT "$MOUNTED" && return 0
    230 	error "Can not proceed with mounted filesystem \"$MOUNTED\""
    231 }
    232 
    233 validate_parsing() {
    234 	test -n "$BLOCKSIZE" -a -n "$BLOCKCOUNT" || error "Cannot parse $1 output"
    235 }
    236 ####################################
    237 # Resize ext2/ext3/ext4 filesystem
    238 # - unmounted or mounted for upsize
    239 # - unmounted for downsize
    240 ####################################
    241 resize_ext() {
    242 	verbose "Parsing $TUNE_EXT -l \"$VOLUME\""
    243 	for i in $($TUNE_EXT -l "$VOLUME"); do
    244 		case "$i" in
    245 		  "Block size"*) BLOCKSIZE=${i##*  } ;;
    246 		  "Block count"*) BLOCKCOUNT=${i##*  } ;;
    247 		esac
    248 	done
    249 	validate_parsing $TUNE_EXT
    250 	decode_size $1 $BLOCKSIZE
    251 	FSFORCE=$FORCE
    252 
    253 	if [ "$NEWBLOCKCOUNT" -lt "$BLOCKCOUNT" -o "$EXTOFF" -eq 1 ]; then
    254 		detect_mounted && verbose "$RESIZE_EXT needs unmounted filesystem" && try_umount
    255 		REMOUNT=$MOUNTED
    256 		# CHECKME: after umount resize2fs requires fsck or -f flag.
    257 		FSFORCE="-f"
    258 	fi
    259 
    260 	verbose "Resizing filesystem on device \"$VOLUME\" to $NEWSIZE bytes ($BLOCKCOUNT -> $NEWBLOCKCOUNT blocks of $BLOCKSIZE bytes)"
    261 	dry $RESIZE_EXT $FSFORCE "$VOLUME" $NEWBLOCKCOUNT
    262 }
    263 
    264 #############################
    265 # Resize reiserfs filesystem
    266 # - unmounted for upsize
    267 # - unmounted for downsize
    268 #############################
    269 resize_reiser() {
    270 	detect_mounted && verbose "ReiserFS resizes only unmounted filesystem" && try_umount
    271 	REMOUNT=$MOUNTED
    272 	verbose "Parsing $TUNE_REISER \"$VOLUME\""
    273 	for i in $($TUNE_REISER "$VOLUME"); do
    274 		case "$i" in
    275 		  "Blocksize"*) BLOCKSIZE=${i##*: } ;;
    276 		  "Count of blocks"*) BLOCKCOUNT=${i##*: } ;;
    277 		esac
    278 	done
    279 	validate_parsing $TUNE_REISER
    280 	decode_size $1 $BLOCKSIZE
    281 	verbose "Resizing \"$VOLUME\" $BLOCKCOUNT -> $NEWBLOCKCOUNT blocks ($NEWSIZE bytes, bs: $NEWBLOCKCOUNT)"
    282 	if [ -n "$YES" ]; then
    283 		dry echo y | $RESIZE_REISER -s $NEWSIZE "$VOLUME"
    284 	else
    285 		dry $RESIZE_REISER -s $NEWSIZE "$VOLUME"
    286 	fi
    287 }
    288 
    289 ########################
    290 # Resize XFS filesystem
    291 # - mounted for upsize
    292 # - can not downsize
    293 ########################
    294 resize_xfs() {
    295 	detect_mounted
    296 	MOUNTPOINT=$MOUNTED
    297 	if [ -z "$MOUNTED" ]; then
    298 		MOUNTPOINT=$TEMPDIR
    299 		temp_mount || error "Cannot mount Xfs filesystem"
    300 	fi
    301 	verbose "Parsing $TUNE_XFS \"$MOUNTPOINT\""
    302 	for i in $($TUNE_XFS "$MOUNTPOINT"); do
    303 		case "$i" in
    304 		  "data"*) BLOCKSIZE=${i##*bsize=} ; BLOCKCOUNT=${i##*blocks=} ;;
    305 		esac
    306 	done
    307 	BLOCKSIZE=${BLOCKSIZE%%[^0-9]*}
    308 	BLOCKCOUNT=${BLOCKCOUNT%%[^0-9]*}
    309 	validate_parsing $TUNE_XFS
    310 	decode_size $1 $BLOCKSIZE
    311 	if [ $NEWBLOCKCOUNT -gt $BLOCKCOUNT ]; then
    312 		verbose "Resizing Xfs mounted on \"$MOUNTPOINT\" to fill device \"$VOLUME\""
    313 		dry $RESIZE_XFS $MOUNTPOINT
    314 	elif [ $NEWBLOCKCOUNT -eq $BLOCKCOUNT ]; then
    315 		verbose "Xfs filesystem already has the right size"
    316 	else
    317 		error "Xfs filesystem shrinking is unsupported"
    318 	fi
    319 }
    320 
    321 ####################
    322 # Resize filesystem
    323 ####################
    324 resize() {
    325 	NEWSIZE=$2
    326 	detect_fs "$1"
    327 	detect_device_size
    328 	verbose "Device \"$VOLUME\" size is $DEVSIZE bytes"
    329 	# if the size parameter is missing use device size
    330 	#if [ -n "$NEWSIZE" -a $NEWSIZE <
    331 	test -z "$NEWSIZE" && NEWSIZE=${DEVSIZE}b
    332 	trap cleanup 2
    333 	IFS=$NL
    334 	case "$FSTYPE" in
    335 	  "ext3"|"ext2"|"ext4") resize_ext $NEWSIZE ;;
    336 	  "reiserfs") resize_reiser $NEWSIZE ;;
    337 	  "xfs") resize_xfs $NEWSIZE ;;
    338 	  *) error "Filesystem \"$FSTYPE\" on device \"$VOLUME\" is not supported by this tool" ;;
    339 	esac || error "Resize $FSTYPE failed"
    340 	cleanup 0
    341 }
    342 
    343 ###################
    344 # Check filesystem
    345 ###################
    346 check() {
    347 	detect_fs "$1"
    348 	detect_mounted && error "Can not fsck device \"$VOLUME\", filesystem mounted on $MOUNTED"
    349 	case "$FSTYPE" in
    350 	  "xfs") dry $XFS_CHECK "$VOLUME" ;;
    351 	  *) dry $FSCK $YES "$VOLUME" ;;
    352 	esac
    353 }
    354 
    355 #############################
    356 # start point of this script
    357 # - parsing parameters
    358 #############################
    359 
    360 # test if we are not invoked recursively
    361 test -n "$FSADM_RUNNING" && exit 0
    362 
    363 # test some prerequisities
    364 test -n "$TUNE_EXT" -a -n "$RESIZE_EXT" -a -n "$TUNE_REISER" -a -n "$RESIZE_REISER" \
    365   -a -n "$TUNE_XFS" -a -n "$RESIZE_XFS" -a -n "$MOUNT" -a -n "$UMOUNT" -a -n "$MKDIR" \
    366   -a -n "$RMDIR" -a -n "$BLOCKDEV" -a -n "$BLKID" -a -n "$GREP" -a -n "$READLINK" \
    367   -a -n "$FSCK" -a -n "$XFS_CHECK" -a -n "LVM" \
    368   || error "Required command definitions in the script are missing!"
    369 
    370 $LVM version >/dev/null 2>&1 || error "Could not run lvm binary '$LVM'"
    371 $($READLINK -e / >/dev/null 2>&1) || READLINK_E="-f"
    372 TEST64BIT=$(( 1000 * 1000000000000 ))
    373 test $TEST64BIT -eq 1000000000000000 || error "Shell does not handle 64bit arithmetic"
    374 $(echo Y | $GREP Y >/dev/null) || error "Grep does not work properly"
    375 
    376 
    377 if [ "$#" -eq 0 ] ; then
    378 	tool_usage
    379 fi
    380 
    381 while [ "$#" -ne 0 ]
    382 do
    383 	 case "$1" in
    384 	  "") ;;
    385 	  "-h"|"--help") tool_usage ;;
    386 	  "-v"|"--verbose") VERB="-v" ;;
    387 	  "-n"|"--dry-run") DRY=1 ;;
    388 	  "-f"|"--force") FORCE="-f" ;;
    389 	  "-e"|"--ext-offline") EXTOFF=1 ;;
    390 	  "-y"|"--yes") YES="-y" ;;
    391 	  "-l"|"--lvresize") DO_LVRESIZE=1 ;;
    392 	  "check") CHECK="$2" ; shift ;;
    393 	  "resize") RESIZE="$2"; NEWSIZE="$3" ; shift 2 ;;
    394 	  *) error "Wrong argument \"$1\". (see: $TOOL --help)"
    395 	esac
    396 	shift
    397 done
    398 
    399 if [ -n "$CHECK" ]; then
    400 	check "$CHECK"
    401 elif [ -n "$RESIZE" ]; then
    402 	export FSADM_RUNNING="fsadm"
    403 	resize "$RESIZE" "$NEWSIZE"
    404 else
    405 	error "Missing command. (see: $TOOL --help)"
    406 fi
    407