xref: /dragonfly/contrib/lvm2/dist/scripts/fsadm.sh (revision 5fb3968e)
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
27TOOL=fsadm
28
29PATH=/sbin:/usr/sbin:/bin:/usr/sbin:$PATH
30
31# utilities
32TUNE_EXT=tune2fs
33RESIZE_EXT=resize2fs
34TUNE_REISER=reiserfstune
35RESIZE_REISER=resize_reiserfs
36TUNE_XFS=xfs_info
37RESIZE_XFS=xfs_growfs
38
39MOUNT=mount
40UMOUNT=umount
41MKDIR=mkdir
42RMDIR=rmdir
43BLOCKDEV=blockdev
44BLKID=blkid
45GREP=grep
46READLINK=readlink
47READLINK_E="-e"
48FSCK=fsck
49XFS_CHECK=xfs_check
50
51# user may override lvm location by setting LVM_BINARY
52LVM=${LVM_BINARY-lvm}
53
54YES=
55DRY=0
56VERB=
57FORCE=
58EXTOFF=0
59DO_LVRESIZE=0
60FSTYPE=unknown
61VOLUME=unknown
62TEMPDIR="${TMPDIR:-/tmp}/${TOOL}_${RANDOM}$$/m"
63BLOCKSIZE=
64BLOCKCOUNT=
65MOUNTPOINT=
66MOUNTED=
67REMOUNT=
68
69IFS_OLD=$IFS
70# without bash $'\n'
71NL='
72'
73
74tool_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
99verbose() {
100	test -n "$VERB" && echo "$TOOL: $@" || true
101}
102
103error() {
104	echo "$TOOL: $@" >&2
105	cleanup 1
106}
107
108dry() {
109	if [ "$DRY" -ne 0 ]; then
110		verbose "Dry execution $@"
111		return 0
112	fi
113	verbose "Executing $@"
114	$@
115}
116
117cleanup() {
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))
137decode_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
160detect_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
174detect_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
183detect_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
198round_up_block_size() {
199	echo $(( ($1 + $2 - 1) / $2 ))
200}
201
202temp_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
207temp_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
213yes_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
228try_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
233validate_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####################################
241resize_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#############################
269resize_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########################
294resize_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####################
324resize() {
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###################
346check() {
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
361test -n "$FSADM_RUNNING" && exit 0
362
363# test some prerequisities
364test -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"
372TEST64BIT=$(( 1000 * 1000000000000 ))
373test $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
377if [ "$#" -eq 0 ] ; then
378	tool_usage
379fi
380
381while [ "$#" -ne 0 ]
382do
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
397done
398
399if [ -n "$CHECK" ]; then
400	check "$CHECK"
401elif [ -n "$RESIZE" ]; then
402	export FSADM_RUNNING="fsadm"
403	resize "$RESIZE" "$NEWSIZE"
404else
405	error "Missing command. (see: $TOOL --help)"
406fi
407