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