xref: /netbsd-src/external/gpl2/lvm2/dist/scripts/fsadm.sh (revision de4fa6c51a9708fc05f88b618fa6fad87c9508ec)
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
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
46CUT=cut
47READLINK=readlink
48READLINK_E="-e"
49FSCK=fsck
50XFS_CHECK=xfs_check
51
52LVRESIZE=lvresize
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
71tool_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
96verbose() {
97	test -n "$VERB" && echo "$TOOL: $@" || true
98}
99
100error() {
101	echo "$TOOL: $@" >&2
102	cleanup 1
103}
104
105dry() {
106	if [ "$DRY" -ne 0 ]; then
107		verbose "Dry execution $@"
108		return 0
109	fi
110	verbose "Executing $@"
111	$@
112}
113
114cleanup() {
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))
134decode_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
157detect_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
167detect_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
176detect_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
191round_up_block_size() {
192	echo $(( ($1 + $2 - 1) / $2 ))
193}
194
195temp_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
200temp_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
206yes_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
220try_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
225validate_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####################################
233resize_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#############################
261resize_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########################
290resize_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####################
320resize() {
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###################
343check() {
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
357test -n "$FSADM_RUNNING" && exit 0
358
359# test some prerequisities
360test -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"
367TEST64BIT=$(( 1000 * 1000000000000 ))
368test $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
372if [ "$1" = "" ] ; then
373	tool_usage
374fi
375
376while [ "$1" != "" ]
377do
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
391done
392
393if [ -n "$CHECK" ]; then
394	check "$CHECK"
395elif [ -n "$RESIZE" ]; then
396	export FSADM_RUNNING="fsadm"
397	resize "$RESIZE" "$NEWSIZE"
398else
399	error "Missing command. (see: $TOOL --help)"
400fi
401