diff options
Diffstat (limited to 'scripts/fsadm.sh')
-rwxr-xr-x | scripts/fsadm.sh | 576 |
1 files changed, 457 insertions, 119 deletions
diff --git a/scripts/fsadm.sh b/scripts/fsadm.sh index 4624a1c..d22c7d0 100755 --- a/scripts/fsadm.sh +++ b/scripts/fsadm.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright (C) 2007-2012 Red Hat, Inc. All rights reserved. +# Copyright (C) 2007-2020 Red Hat, Inc. All rights reserved. # # This file is part of LVM2. # @@ -10,14 +10,14 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, -# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # # Author: Zdenek Kabelac <zkabelac at redhat.com> # # Script for resizing devices (usable for LVM resize) # # Needed utilities: -# mount, umount, grep, readlink, blockdev, blkid, fsck, xfs_check +# mount, umount, grep, readlink, blockdev, blkid, fsck, xfs_check, cryptsetup # # ext2/ext3/ext4: resize2fs, tune2fs # reiserfs: resize_reiserfs, reiserfstune @@ -29,43 +29,48 @@ # 2 break detected # 3 unsupported online filesystem check for given mounted fs -TOOL=fsadm +set -euE -o pipefail + +TOOL="fsadm" _SAVEPATH=$PATH -PATH=/sbin:/usr/sbin:/bin:/usr/sbin:$PATH +PATH="/sbin:/usr/sbin:/bin:/usr/sbin:$PATH" # utilities -TUNE_EXT=tune2fs -RESIZE_EXT=resize2fs -TUNE_REISER=reiserfstune -RESIZE_REISER=resize_reiserfs -TUNE_XFS=xfs_info -RESIZE_XFS=xfs_growfs - -MOUNT=mount -UMOUNT=umount -MKDIR=mkdir -RMDIR=rmdir -BLOCKDEV=blockdev -BLKID=blkid -DATE=date -GREP=grep -READLINK=readlink +TUNE_EXT="tune2fs" +RESIZE_EXT="resize2fs" +TUNE_REISER="reiserfstune" +RESIZE_REISER="resize_reiserfs" +TUNE_XFS="xfs_info" +RESIZE_XFS="xfs_growfs" + +MOUNT="mount" +UMOUNT="umount" +MKDIR="mkdir" +RMDIR="rmdir" +BLOCKDEV="blockdev" +BLKID="blkid" +DATE="date" +GREP="grep" +READLINK="readlink" READLINK_E="-e" -FSCK=fsck -XFS_CHECK=xfs_check +FSCK="fsck" +XFS_CHECK="xfs_check" +# XFS_REPAIR -n is used when XFS_CHECK is not found +XFS_REPAIR="xfs_repair" +CRYPTSETUP="cryptsetup" # user may override lvm location by setting LVM_BINARY LVM=${LVM_BINARY:-lvm} -YES=${_FSADM_YES} +YES="${_FSADM_YES-}" DRY=0 VERB= FORCE= EXTOFF=${_FSADM_EXTOFF:-0} DO_LVRESIZE=0 -FSTYPE=unknown -VOLUME=unknown +FSTYPE="unknown" +VOLUME="unknown" TEMPDIR="${TMPDIR:-/tmp}/${TOOL}_${RANDOM}$$/m" DM_DEV_DIR="${DM_DEV_DIR:-/dev}" BLOCKSIZE= @@ -73,7 +78,9 @@ BLOCKCOUNT= MOUNTPOINT= MOUNTED= REMOUNT= -PROCMOUNTS="/proc/mounts" +PROCDIR="/proc" +PROCMOUNTS="$PROCDIR/mounts" +PROCSELFMOUNTINFO="$PROCDIR/self/mountinfo" NULL="$DM_DEV_DIR/null" IFS_OLD=$IFS @@ -84,10 +91,10 @@ NL=' tool_usage() { echo "${TOOL}: Utility to resize or check the filesystem on a device" echo - echo " ${TOOL} [options] check device" + echo " ${TOOL} [options] check <device>" echo " - Check the filesystem on device using fsck" echo - echo " ${TOOL} [options] resize device [new_size[BKMGTPE]]" + echo " ${TOOL} [options] resize <device> [<new_size>[BKMGTPE]]" echo " - Change the size of the filesystem on device to new_size" echo echo " Options:" @@ -97,6 +104,7 @@ tool_usage() { echo " -f | --force Bypass sanity checks" echo " -n | --dry-run Print commands without running them" echo " -l | --lvresize Resize given device (if it is LVM device)" + echo " -c | --cryptresize Resize given crypt device" echo " -y | --yes Answer \"yes\" at any prompts" echo echo " new_size - Absolute number of filesystem blocks to be in the filesystem," @@ -107,21 +115,32 @@ tool_usage() { } verbose() { - test -n "$VERB" && echo "$TOOL: $@" || true + test -z "$VERB" || echo "$TOOL:" "$@" } +# Support multi-line error messages error() { - echo "$TOOL: $@" >&2 + for i in "$@" ; do + echo "$TOOL: $i" >&2 + done cleanup 1 } dry() { if [ "$DRY" -ne 0 ]; then - verbose "Dry execution $@" + verbose "Dry execution" "$@" return 0 fi - verbose "Executing $@" - "$@" + verbose "Executing" "$@" + $@ +} + +# Accept as succss also return code 1 with fsck +accept_0_1() { + $@ + local ret="$?" + test "$ret" -eq 1 || return "$ret" + # Filesystem was corrected } cleanup() { @@ -144,12 +163,12 @@ cleanup() { _FSADM_EXTOFF=$EXTOFF export _FSADM_YES _FSADM_EXTOFF unset FSADM_RUNNING - test -n "$LVM_BINARY" && PATH=$_SAVEPATH - dry exec "$LVM" lvresize $VERB $FORCE -r -L${NEWSIZE}b "$VOLUME_ORIG" + test -n "${LVM_BINARY-}" && PATH=$_SAVEPATH + dry exec "$LVM" lvresize $VERB $FORCE $YES --fs resize_fsadm -L"${NEWSIZE_ORIG}b" "$VOLUME_ORIG" fi # error exit status for break - exit ${1:-1} + exit "${1:-1}" } # convert parameter from Exa/Peta/Tera/Giga/Mega/Kilo/Bytes and blocks @@ -166,72 +185,205 @@ decode_size() { *) NEWSIZE=$(( $1 * $2 )) ;; esac #NEWBLOCKCOUNT=$(round_block_size $NEWSIZE $2) - NEWBLOCKCOUNT=$(( $NEWSIZE / $2 )) + NEWBLOCKCOUNT=$(( NEWSIZE / $2 )) - if [ $DO_LVRESIZE -eq 1 ]; then + if [ "$DO_LVRESIZE" -eq 1 ]; then # start lvresize, but first cleanup mounted dirs DO_LVRESIZE=2 cleanup 0 fi } +decode_major_minor() { + # 0x00000fff00 mask MAJOR + # 0xfffff000ff mask MINOR + + #MINOR=$(( $1 / 1048576 )) + #MAJOR=$(( ($1 - ${MINOR} * 1048576) / 256 )) + #MINOR=$(( $1 - ${MINOR} * 1048576 - ${MAJOR} * 256 + ${MINOR} * 256)) + + echo "$(( ( $1 >> 8 ) & 4095 )):$(( ( ( $1 >> 12 ) & 268435200 ) | ( $1 & 255 ) ))" +} + # detect filesystem on the given device # dereference device name if it is symbolic link detect_fs() { - VOLUME_ORIG=$1 + test -n "${VOLUME_ORIG-}" || VOLUME_ORIG=$1 VOLUME=${1/#"${DM_DEV_DIR}/"/} - VOLUME=$("$READLINK" $READLINK_E "$DM_DEV_DIR/$VOLUME") || error "Cannot get readlink \"$1\"" + VOLUME=$("$READLINK" $READLINK_E "$DM_DEV_DIR/$VOLUME") + test -n "$VOLUME" || error "Cannot get readlink \"$1\"." RVOLUME=$VOLUME case "$RVOLUME" in - # hardcoded /dev since udev does not create these entries elsewhere + # hardcoded /dev since udev does not create these entries elsewhere /dev/dm-[0-9]*) - read </sys/block/${RVOLUME#/dev/}/dm/name SYSVOLUME 2>&1 && VOLUME="$DM_DEV_DIR/mapper/$SYSVOLUME" + read -r <"/sys/block/${RVOLUME#/dev/}/dm/name" SYSVOLUME 2>&1 && VOLUME="$DM_DEV_DIR/mapper/$SYSVOLUME" + read -r <"/sys/block/${RVOLUME#/dev/}/dev" MAJORMINOR 2>&1 || error "Cannot get major:minor for \"$VOLUME\"." + MAJOR=${MAJORMINOR%%:*} + MINOR=${MAJORMINOR##*:} + ;; + *) + STAT=$(stat --format "MAJOR=\$((0x%t)) MINOR=\$((0x%T))" "$RVOLUME") + test -n "$STAT" || error "Cannot get major:minor for \"$VOLUME\"." + eval "$STAT" + MAJORMINOR="${MAJOR}:${MINOR}" ;; esac # use null device as cache file to be sure about the result # not using option '-o value' to be compatible with older version of blkid - FSTYPE=$("$BLKID" -c "$NULL" -s TYPE "$VOLUME") || error "Cannot get FSTYPE of \"$VOLUME\"" + FSTYPE=$("$BLKID" -c "$NULL" -s TYPE "$VOLUME" || true) + test -n "$FSTYPE" || error "Cannot get FSTYPE of \"$VOLUME\"." FSTYPE=${FSTYPE##*TYPE=\"} # cut quotation marks FSTYPE=${FSTYPE%%\"*} - verbose "\"$FSTYPE\" filesystem found on \"$VOLUME\"" + verbose "\"$FSTYPE\" filesystem found on \"$VOLUME\"." } -# check if the given device is already mounted and where -# FIXME: resolve swap usage and device stacking -detect_mounted() { - test -e "$PROCMOUNTS" || error "Cannot detect mounted device \"$VOLUME\"" - MOUNTED=$("$GREP" "^$VOLUME[ \t]" "$PROCMOUNTS") +# Check that passed mounted MAJOR:MINOR is not matching $MAJOR:MINOR of resized $VOLUME +validate_mounted_major_minor() { + test "$1" = "$MAJORMINOR" || { + local REFNAME + local CURNAME + REFNAME=$(dmsetup info -c -j "${1%%:*}" -m "${1##*:}" -o name --noheadings 2>"$NULL") + CURNAME=$(dmsetup info -c -j "$MAJOR" -m "$MINOR" -o name --noheadings 2>"$NULL") + error "Cannot ${CHECK+CHECK}${RESIZE+RESIZE} device \"$VOLUME\" without umounting filesystem $MOUNTED first." \ + "Mounted filesystem is using device $CURNAME, but referenced device is $REFNAME." \ + "Filesystem utilities currently do not support renamed devices." + } +} + +# ATM fsresize & fsck tools are not able to work properly +# when mounted device has changed its name. +# So whenever such device no longer exists with original name +# abort further command processing +check_valid_mounted_device() { + local MOUNTEDMAJORMINOR + local VOL + local CURNAME + + VOL=$("$READLINK" $READLINK_E "$1") + CURNAME=$(dmsetup info -c -j "$MAJOR" -m "$MINOR" -o name --noheadings) + # more confused, device is not DM.... + local SUGGEST="Possibly device \"$1\" has been renamed to \"$CURNAME\"?" + test -n "$CURNAME" || SUGGEST="Mounted volume is not a device mapper device???" + + test -n "$VOL" || + error "Cannot access device \"$1\" referenced by mounted filesystem \"$MOUNTED\"." \ + "$SUGGEST" \ + "Filesystem utilities currently do not support renamed devices." + + case "$VOL" in + # hardcoded /dev since kernel does not create these entries elsewhere + /dev/dm-[0-9]*) + read -r <"/sys/block/${VOL#/dev/}/dev" MOUNTEDMAJORMINOR 2>&1 || error "Cannot get major:minor for \"$VOLUME\"." + ;; + *) + STAT=$(stat --format "MOUNTEDMAJORMINOR=\$((0x%t)):\$((0x%T))" "$VOL") + test -n "$STAT" || error "Cannot get major:minor for \"$VOLUME\"." + eval "$STAT" + ;; + esac + + validate_mounted_major_minor "$MOUNTEDMAJORMINOR" +} + +detect_mounted_with_proc_self_mountinfo() { + # Check self mountinfo + # grab major:minor mounted_device mount_point + MOUNTED=$("$GREP" "^[0-9]* [0-9]* $MAJORMINOR " "$PROCSELFMOUNTINFO" 2>"$NULL" | head -1) + + # If device is opened and not yet found as self mounted + # check all other mountinfos (since it can be mounted in cgroups) + # Use 'find' to not fail on to long list of args with too many pids + # only 1st. line is needed + test -z "$MOUNTED" && + test "$(dmsetup info -c --noheading -o open -j "$MAJOR" -m "$MINOR")" -gt 0 && + MOUNTED=$(find "$PROCDIR" -maxdepth 2 -name mountinfo -print0 | xargs -0 "$GREP" "^[0-9]* [0-9]* $MAJORMINOR " 2>"$NULL" | head -1 2>"$NULL") + + # TODO: for performance compare with sed and stop with 1st. match: + # sed -n "/$MAJORMINOR/ {;p;q;}" + + # extract 2nd field after ' - ' separator as mouted device + MOUNTDEV=$(echo "${MOUNTED##* - }" | cut -d ' ' -f 2) + MOUNTDEV=$(echo -n -e "$MOUNTDEV") + + # extract 5th field as mount point + # echo -e translates \040 to spaces + MOUNTED=$(echo "$MOUNTED" | cut -d ' ' -f 5) + MOUNTED=$(echo -n -e "$MOUNTED") + + test -n "$MOUNTED" || return 1 # Not seen mounted anywhere + + check_valid_mounted_device "$MOUNTDEV" +} + +# With older systems without /proc/*/mountinfo we may need to check +# every mount point as cannot easily depend on the name of mounted +# device (which could have been renamed). +# We need to visit every mount point and check it's major minor +detect_mounted_with_proc_mounts() { + MOUNTED=$("$GREP" "^${VOLUME}[ \\t]" "$PROCMOUNTS") # for empty string try again with real volume name - test -z "$MOUNTED" && MOUNTED=$("$GREP" "^$RVOLUME[ \t]" "$PROCMOUNTS") + test -z "$MOUNTED" && MOUNTED=$("$GREP" "^${RVOLUME}[ \\t]" "$PROCMOUNTS") + MOUNTDEV=$(echo -n -e "${MOUNTED%% *}") # cut device name prefix and trim everything past mountpoint # echo translates \040 to spaces MOUNTED=${MOUNTED#* } - MOUNTED=$(echo -n -e ${MOUNTED%% *}) + MOUNTED=$(echo -n -e "${MOUNTED%% *}") # for systems with different device names - check also mount output if test -z "$MOUNTED" ; then - MOUNTED=$(LANG=C "$MOUNT" | "$GREP" "^$VOLUME[ \t]") - test -z "$MOUNTED" && MOUNTED=$(LANG=C "$MOUNT" | "$GREP" "^$RVOLUME[ \t]") + # will not work with spaces in paths + MOUNTED=$(LC_ALL=C "$MOUNT" | "$GREP" "^${VOLUME}[ \\t]") + test -z "$MOUNTED" && MOUNTED=$(LC_ALL=C "$MOUNT" | "$GREP" "^${RVOLUME}[ \\t]") + MOUNTDEV=${MOUNTED%% on *} MOUNTED=${MOUNTED##* on } MOUNTED=${MOUNTED% type *} # allow type in the mount name fi - test -n "$MOUNTED" + if test -n "$MOUNTED" ; then + check_valid_mounted_device "$MOUNTDEV" + return 0 # mounted + fi + + # If still nothing found and volume is in use + # check every known mount point against MAJOR:MINOR + if test "$(dmsetup info -c --noheading -o open -j "$MAJOR" -m "$MINOR")" -gt 0 ; then + while IFS=$'\n' read -r i ; do + MOUNTDEV=$(echo -n -e "${i%% *}") + MOUNTED=${i#* } + MOUNTED=$(echo -n -e "${MOUNTED%% *}") + STAT=$(stat --format "%d" "$MOUNTED") + validate_mounted_major_minor "$(decode_major_minor "$STAT")" + done < "$PROCMOUNTS" + fi + + return 1 # nothing is mounted +} + +# check if the given device is already mounted and where +# FIXME: resolve swap usage and device stacking +detect_mounted() { + if test -e "$PROCSELFMOUNTINFO"; then + detect_mounted_with_proc_self_mountinfo + elif test -e "$PROCMOUNTS"; then + detect_mounted_with_proc_mounts + else + error "Cannot detect mounted device \"$VOLUME\"." + fi } # get the full size of device in bytes detect_device_size() { # check if blockdev supports getsize64 - "$BLOCKDEV" 2>&1 | "$GREP" getsize64 >"$NULL" - if test $? -eq 0; then - DEVSIZE=$("$BLOCKDEV" --getsize64 "$VOLUME") || error "Cannot read size of device \"$VOLUME\"" - else - DEVSIZE=$("$BLOCKDEV" --getsize "$VOLUME") || error "Cannot read size of device \"$VOLUME\"" - SSSIZE=$("$BLOCKDEV" --getss "$VOLUME") || error "Cannot block size read device \"$VOLUME\"" - DEVSIZE=$(($DEVSIZE * $SSSIZE)) + DEVSIZE=$("$BLOCKDEV" --getsize64 "$VOLUME" 2>"$NULL" || true) + if test -z "$DEVSIZE" ; then + DEVSIZE=$("$BLOCKDEV" --getsize "$VOLUME" || true) + test -n "$DEVSIZE" || error "Cannot read size of device \"$VOLUME\"." + SSSIZE=$("$BLOCKDEV" --getss "$VOLUME" || true) + test -n "$SSSIZE" || error "Cannot read sector size of device \"$VOLUME\"." + DEVSIZE=$(( DEVSIZE * SSSIZE )) fi } @@ -243,18 +395,18 @@ round_up_block_size() { } temp_mount() { - dry "$MKDIR" -p -m 0000 "$TEMPDIR" || error "Failed to create $TEMPDIR" - dry "$MOUNT" "$VOLUME" "$TEMPDIR" || error "Failed to mount $TEMPDIR" + dry "$MKDIR" -p -m 0000 "$TEMPDIR" || error "Failed to create $TEMPDIR." + dry "$MOUNT" "$VOLUME" "$TEMPDIR" || error "Failed to mount $TEMPDIR." } temp_umount() { - dry "$UMOUNT" "$TEMPDIR" || error "Failed to umount \"$TEMPDIR\"" - dry "$RMDIR" "${TEMPDIR}" || error "Failed to remove \"$TEMPDIR\"" - dry "$RMDIR" "${TEMPDIR%%m}" || error "Failed to remove \"${TEMPDIR%%m}\"" + dry "$UMOUNT" "$TEMPDIR" || error "Failed to umount \"$TEMPDIR\"." + dry "$RMDIR" "${TEMPDIR}" || error "Failed to remove \"$TEMPDIR\"," + dry "$RMDIR" "${TEMPDIR%%m}" || error "Failed to remove \"${TEMPDIR%%m}\"." } yes_no() { - echo -n "$@? [Y|n] " + echo -n "$@" "? [Y|n] " if [ -n "$YES" ]; then echo y ; return 0 @@ -262,19 +414,27 @@ yes_no() { while read -r -s -n 1 ANS ; do case "$ANS" in - "y" | "Y" | "") echo y ; return 0 ;; - "n" | "N") echo n ; return 1 ;; + "y" | "Y" ) echo y ; return 0 ;; + "n" | "N") break ;; + "" ) if [ -t 1 ] ; then + echo y ; return 0 + fi ;; esac done + + echo n + return 1 } try_umount() { yes_no "Do you want to unmount \"$MOUNTED\"" && dry "$UMOUNT" "$MOUNTED" && return 0 - error "Cannot proceed with mounted filesystem \"$MOUNTED\"" + error "Cannot proceed with mounted filesystem \"$MOUNTED\"." } validate_parsing() { - test -n "$BLOCKSIZE" -a -n "$BLOCKCOUNT" || error "Cannot parse $1 output" + if test -z "$BLOCKSIZE" || test -z "$BLOCKCOUNT" ; then + error "Cannot parse $1 output." + fi } #################################### # Resize ext2/ext3/ext4 filesystem @@ -282,31 +442,35 @@ validate_parsing() { # - unmounted for downsize #################################### resize_ext() { + local IS_MOUNTED=0 + detect_mounted && IS_MOUNTED=1 + verbose "Parsing $TUNE_EXT -l \"$VOLUME\"" - for i in $(LANG=C "$TUNE_EXT" -l "$VOLUME"); do + for i in $(LC_ALL=C "$TUNE_EXT" -l "$VOLUME"); do case "$i" in "Block size"*) BLOCKSIZE=${i##* } ;; "Block count"*) BLOCKCOUNT=${i##* } ;; esac done validate_parsing "$TUNE_EXT" - decode_size $1 $BLOCKSIZE + decode_size "$1" "$BLOCKSIZE" FSFORCE=$FORCE - if [ "$NEWBLOCKCOUNT" -lt "$BLOCKCOUNT" -o "$EXTOFF" -eq 1 ]; then - detect_mounted && verbose "$RESIZE_EXT needs unmounted filesystem" && try_umount + if test "$NEWBLOCKCOUNT" -lt "$BLOCKCOUNT" || test "$EXTOFF" -eq 1 ; then + test "$IS_MOUNTED" -eq 1 && verbose "$RESIZE_EXT needs unmounted filesystem" && try_umount REMOUNT=$MOUNTED if test -n "$MOUNTED" ; then # Forced fsck -f for umounted extX filesystem. case "$-" in - *i*) dry "$FSCK" $YES -f "$VOLUME" ;; - *) dry "$FSCK" -f -p "$VOLUME" ;; + *i*) FLAG=$YES ;; + *) FLAG="-p" ;; esac + accept_0_1 dry "$FSCK" -f $FLAG "$VOLUME" || error "Failed to fsck $VOLUME" fi fi verbose "Resizing filesystem on device \"$VOLUME\" to $NEWSIZE bytes ($BLOCKCOUNT -> $NEWBLOCKCOUNT blocks of $BLOCKSIZE bytes)" - dry "$RESIZE_EXT" $FSFORCE "$VOLUME" $NEWBLOCKCOUNT + dry "$RESIZE_EXT" $FSFORCE "$VOLUME" "$NEWBLOCKCOUNT" } ############################# @@ -318,19 +482,19 @@ resize_reiser() { detect_mounted && verbose "ReiserFS resizes only unmounted filesystem" && try_umount REMOUNT=$MOUNTED verbose "Parsing $TUNE_REISER \"$VOLUME\"" - for i in $(LANG=C "$TUNE_REISER" "$VOLUME"); do + for i in $(LC_ALL=C "$TUNE_REISER" "$VOLUME"); do case "$i" in "Blocksize"*) BLOCKSIZE=${i##*: } ;; "Count of blocks"*) BLOCKCOUNT=${i##*: } ;; esac done validate_parsing "$TUNE_REISER" - decode_size $1 $BLOCKSIZE + decode_size "$1" "$BLOCKSIZE" verbose "Resizing \"$VOLUME\" $BLOCKCOUNT -> $NEWBLOCKCOUNT blocks ($NEWSIZE bytes, bs: $NEWBLOCKCOUNT)" if [ -n "$YES" ]; then - echo y | dry "$RESIZE_REISER" -s $NEWSIZE "$VOLUME" + echo y | dry "$RESIZE_REISER" -s "$NEWSIZE" "$VOLUME" else - dry "$RESIZE_REISER" -s $NEWSIZE "$VOLUME" + dry "$RESIZE_REISER" -s "$NEWSIZE" "$VOLUME" fi } @@ -344,10 +508,10 @@ resize_xfs() { MOUNTPOINT=$MOUNTED if [ -z "$MOUNTED" ]; then MOUNTPOINT=$TEMPDIR - temp_mount || error "Cannot mount Xfs filesystem" + temp_mount || error "Cannot mount Xfs filesystem." fi verbose "Parsing $TUNE_XFS \"$MOUNTPOINT\"" - for i in $(LANG=C "$TUNE_XFS" "$MOUNTPOINT"); do + for i in $(LC_ALL=C "$TUNE_XFS" "$MOUNTPOINT"); do case "$i" in "data"*) BLOCKSIZE=${i##*bsize=} ; BLOCKCOUNT=${i##*blocks=} ;; esac @@ -355,17 +519,149 @@ resize_xfs() { BLOCKSIZE=${BLOCKSIZE%%[^0-9]*} BLOCKCOUNT=${BLOCKCOUNT%%[^0-9]*} validate_parsing "$TUNE_XFS" - decode_size $1 $BLOCKSIZE - if [ $NEWBLOCKCOUNT -gt $BLOCKCOUNT ]; then + decode_size "$1" "$BLOCKSIZE" + if [ "$NEWBLOCKCOUNT" -gt "$BLOCKCOUNT" ]; then verbose "Resizing Xfs mounted on \"$MOUNTPOINT\" to fill device \"$VOLUME\"" - dry "$RESIZE_XFS" $MOUNTPOINT - elif [ $NEWBLOCKCOUNT -eq $BLOCKCOUNT ]; then + dry "$RESIZE_XFS" "$MOUNTPOINT" + elif [ "$NEWBLOCKCOUNT" -eq "$BLOCKCOUNT" ]; then verbose "Xfs filesystem already has the right size" else - error "Xfs filesystem shrinking is unsupported" + error "Xfs filesystem shrinking is unsupported." + fi +} + +# Find active LUKS device on original volume +# 1) look for LUKS device with well-known UUID format (CRYPT-LUKS[12]-<uuid>-<dmname>) +# 2) the dm-crypt device has to be on top of original device (dont't support detached LUKS headers) +detect_luks_device() { + local _LUKS_VERSION + local _LUKS_UUID + + CRYPT_NAME="" + CRYPT_DATA_OFFSET="" + + _LUKS_VERSION=$("$CRYPTSETUP" luksDump "$VOLUME" 2>"$NULL" | "$GREP" "Version:") + + if [ -z "$_LUKS_VERSION" ]; then + verbose "Failed to parse LUKS version on volume \"$VOLUME\"" + return + fi + + _LUKS_VERSION=${_LUKS_VERSION//[Version:[:space:]]/} + + _LUKS_UUID=$("$CRYPTSETUP" luksDump "$VOLUME" 2>"$NULL" | "$GREP" "UUID:") + + if [ -z "$_LUKS_UUID" ]; then + verbose "Failed to parse LUKS UUID on volume \"$VOLUME\"" + return + fi + + _LUKS_UUID="CRYPT-LUKS$_LUKS_VERSION-${_LUKS_UUID//[UID:[:space:]-]/}-" + + CRYPT_NAME=$(dmsetup info -c --noheadings -S "UUID=~^$_LUKS_UUID&&segments=1&&devnos_used='$MAJOR:$MINOR'" -o name) + test -z "$CRYPT_NAME" || CRYPT_DATA_OFFSET=$(dmsetup table "$CRYPT_NAME" | cut -d ' ' -f 8) + + # LUKS device must be active and mapped over volume where detected + if [ -z "$CRYPT_NAME" ] || [ -z "$CRYPT_DATA_OFFSET" ]; then + error "Can not find active LUKS device. Unlock \"$VOLUME\" volume first." + fi +} + +###################################### +# Resize active LUKS device +# - LUKS must be active for fs resize +###################################### +resize_luks() { + local L_NEWSIZE + local L_NEWBLOCKCOUNT + local NAME + local SHRINK=0 + + detect_luks_device + + NAME=$CRYPT_NAME + + verbose "Found active LUKS device \"$NAME\" for volume \"$VOLUME\"" + + decode_size "$1" 512 + + if [ $((NEWSIZE % 512)) -gt 0 ]; then + error "New size is not sector alligned" + fi + + if [ $((NEWBLOCKCOUNT - CRYPT_DATA_OFFSET)) -lt 1 ]; then + error "New size is smaller than minimum ($(((CRYPT_DATA_OFFSET + 1) * 512)) bytes) for LUKS device $VOLUME" + fi + + L_NEWBLOCKCOUNT=$((NEWBLOCKCOUNT - CRYPT_DATA_OFFSET)) + L_NEWSIZE=$(( L_NEWBLOCKCOUNT * 512)) + + VOLUME="$DM_DEV_DIR/mapper/$NAME" + detect_device_size + + test "$DEVSIZE" -le "$L_NEWSIZE" || SHRINK=1 + + if [ $SHRINK -eq 1 ]; then + # shrink fs on LUKS device first + resize "$DM_DEV_DIR/mapper/$NAME" "$L_NEWSIZE"b + fi + + # resize LUKS device + dry "$CRYPTSETUP" resize "$NAME" --size $L_NEWBLOCKCOUNT || error "Failed to resize active LUKS device" + + if [ $SHRINK -eq 0 ]; then + # grow fs on top of LUKS device + resize "$DM_DEV_DIR/mapper/$NAME" "$L_NEWSIZE"b + fi +} + +detect_crypt_device() { + local CRYPT_TYPE + local L_NEWSIZE + local TMP + + which "$CRYPTSETUP" >"$NULL" 2>&1 || error "$CRYPTSETUP utility required to resize crypt device" + + CRYPT_TYPE=$("$CRYPTSETUP" status "$1" 2>"$NULL" | "$GREP" "type:") + + test -n "$CRYPT_TYPE" || error "$CRYPTSETUP failed to detect device type on $1." + + CRYPT_TYPE=${CRYPT_TYPE##*[[:space:]]} + + case "$CRYPT_TYPE" in + LUKS[12]|PLAIN) + verbose "\"$1\" crypt device is type $CRYPT_TYPE" + ;; + *) + error "Unsupported crypt type \"$CRYPT_TYPE\"" + esac + + TMP=$NEWSIZE + decode_size "$2" 512 + L_NEWSIZE=$NEWSIZE + NEWSIZE=$TMP + + if [ $((L_NEWSIZE % 512)) -ne 0 ]; then + error "New size is not sector alligned" + fi + + CRYPT_RESIZE_BLOCKS=$NEWBLOCKCOUNT + + if [ "$DEVSIZE" -ge "$L_NEWSIZE" ]; then + CRYPT_SHRINK=1 + else + CRYPT_GROW=1 fi } +################################# +# Resize active crypt device +# (on direct user request only) +################################# +resize_crypt() { + dry "$CRYPTSETUP" resize "$1" --size $CRYPT_RESIZE_BLOCKS || error "$CRYPTSETUP failed to resize device $1" +} + #################### # Resize filesystem #################### @@ -377,25 +673,40 @@ resize() { # if the size parameter is missing use device size #if [ -n "$NEWSIZE" -a $NEWSIZE < test -z "$NEWSIZE" && NEWSIZE=${DEVSIZE}b + NEWSIZE_ORIG=${NEWSIZE_ORIG:-$NEWSIZE} IFS=$NL + test -z "${DO_CRYPTRESIZE-}" || detect_crypt_device "$VOLUME_ORIG" "$NEWSIZE_ORIG" + test -z "${CRYPT_GROW-}" || resize_crypt "$VOLUME_ORIG" + case "$FSTYPE" in - "ext3"|"ext2"|"ext4") resize_ext $NEWSIZE ;; - "reiserfs") resize_reiser $NEWSIZE ;; - "xfs") resize_xfs $NEWSIZE ;; - *) error "Filesystem \"$FSTYPE\" on device \"$VOLUME\" is not supported by this tool" ;; - esac || error "Resize $FSTYPE failed" - cleanup 0 + ext[234]) CMD=resize_ext ;; + "reiserfs") CMD=resize_reiser ;; + "xfs") CMD=resize_xfs ;; + "crypto_LUKS") + which "$CRYPTSETUP" >"$NULL" 2>&1 || error "$CRYPTSETUP utility required to resize LUKS volume" + CMD=resize_luks ;; + *) error "Filesystem \"$FSTYPE\" on device \"$VOLUME\" is not supported by this tool." ;; + esac + + $CMD $NEWSIZE || error "$FSTYPE resize failed." + test -z "${CRYPT_SHRINK-}" || resize_crypt "$VOLUME_ORIG" } #################################### # Calclulate diff between two dates -# LANG=C input is expected the +# LC_ALL=C input is expected the # only one supported #################################### diff_dates() { echo $(( $("$DATE" -u -d"$1" +%s 2>"$NULL") - $("$DATE" -u -d"$2" +%s 2>"$NULL") )) } +check_luks() { + detect_luks_device + + check "$DM_DEV_DIR/mapper/$CRYPT_NAME" +} + ################### # Check filesystem ################### @@ -407,10 +718,10 @@ check() { fi case "$FSTYPE" in - "ext2"|"ext3"|"ext4") + ext[234]) IFS_CHECK=$IFS IFS=$NL - for i in $(LANG=C "$TUNE_EXT" -l "$VOLUME"); do + for i in $(LC_ALL=C "$TUNE_EXT" -l "$VOLUME"); do case "$i" in "Last mount"*) LASTMOUNT=${i##*: } ;; "Last checked"*) LASTCHECKED=${i##*: } ;; @@ -419,7 +730,7 @@ check() { case "$LASTMOUNT" in *"n/a") ;; # nothing to do - system was not mounted yet *) - LASTDIFF=$(diff_dates $LASTMOUNT $LASTCHECKED) + LASTDIFF=$(diff_dates "$LASTMOUNT" "$LASTCHECKED") if test "$LASTDIFF" -gt 0 ; then verbose "Filesystem has not been checked after the last mount, using fsck -f" FORCE="-f" @@ -430,12 +741,29 @@ check() { esac case "$FSTYPE" in - "xfs") dry "$XFS_CHECK" "$VOLUME" ;; - *) # check if executed from interactive shell environment + "xfs") if which "$XFS_CHECK" >"$NULL" 2>&1 ; then + dry "$XFS_CHECK" "$VOLUME" || error "Xfs check failed." + else + # Replacement for outdated xfs_check + # FIXME: for small devices we need to force_geometry, + # since we run in '-n' mode, it shouldn't be problem. + # Think about better way.... + dry "$XFS_REPAIR" -n -o force_geometry "$VOLUME" || error "Xfs repair failed." + fi ;; + ext[234]|"reiserfs") + # check if executed from interactive shell environment case "$-" in - *i*) dry "$FSCK" $YES $FORCE "$VOLUME" ;; - *) dry "$FSCK" $FORCE -p "$VOLUME" ;; + *i*) FLAG=$YES ;; + *) FLAG="-p" ;; esac + accept_0_1 dry "$FSCK" $FORCE $FLAG "$VOLUME" || error "Fsck $FSTYPE failed." + ;; + "crypto_LUKS") + which "$CRYPTSETUP" >"$NULL" 2>&1 || error "$CRYPTSETUP utility required." + check_luks || error "Crypto luks check failed." + ;; + *) + error "Filesystem \"$FSTYPE\" on device \"$VOLUME\" is not supported by this tool." ;; esac } @@ -446,27 +774,32 @@ check() { trap "cleanup 2" 2 # test if we are not invoked recursively -test -n "$FSADM_RUNNING" && exit 0 +test -n "${FSADM_RUNNING-}" && exit 0 # test some prerequisities -test -n "$TUNE_EXT" -a -n "$RESIZE_EXT" -a -n "$TUNE_REISER" -a -n "$RESIZE_REISER" \ - -a -n "$TUNE_XFS" -a -n "$RESIZE_XFS" -a -n "$MOUNT" -a -n "$UMOUNT" -a -n "$MKDIR" \ - -a -n "$RMDIR" -a -n "$BLOCKDEV" -a -n "$BLKID" -a -n "$GREP" -a -n "$READLINK" \ - -a -n "$DATE" -a -n "$FSCK" -a -n "$XFS_CHECK" -a -n "$LVM" \ - || error "Required command definitions in the script are missing!" - -"$LVM" version >"$NULL" 2>&1 || error "Could not run lvm binary \"$LVM\"" -$("$READLINK" -e / >"$NULL" 2>&1) || READLINK_E="-f" +for i in "$TUNE_EXT" "$RESIZE_EXT" "$TUNE_REISER" "$RESIZE_REISER" \ + "$TUNE_XFS" "$RESIZE_XFS" "$MOUNT" "$UMOUNT" "$MKDIR" \ + "$RMDIR" "$BLOCKDEV" "$BLKID" "$GREP" "$READLINK" \ + "$DATE" "$FSCK" "$XFS_CHECK" "$XFS_REPAIR" "$LVM" ; do + test -n "$i" || error "Required command definitions in the script are missing!" +done + +"$LVM" version >"$NULL" 2>&1 || error "Could not run lvm binary \"$LVM\"." +"$READLINK" -e / >"$NULL" 2>&1 || READLINK_E="-f" TEST64BIT=$(( 1000 * 1000000000000 )) -test "$TEST64BIT" -eq 1000000000000000 || error "Shell does not handle 64bit arithmetic" -$(echo Y | "$GREP" Y >"$NULL") || error "Grep does not work properly" -test $("$DATE" -u -d"Jan 01 00:00:01 1970" +%s) -eq 1 || error "Date translation does not work" +test "$TEST64BIT" -eq 1000000000000000 || error "Shell does not handle 64bit arithmetic." +echo Y | "$GREP" Y >"$NULL" || error "Grep does not work properly." +test "$("$DATE" -u -d"Jan 01 00:00:01 1970" +%s)" -eq 1 || error "Date translation does not work." if [ "$#" -eq 0 ] ; then tool_usage fi +CHECK="" +RESIZE="" +NEWSIZE="" + while [ "$#" -ne 0 ] do case "$1" in @@ -478,14 +811,18 @@ do "-e"|"--ext-offline") EXTOFF=1 ;; "-y"|"--yes") YES="-y" ;; "-l"|"--lvresize") DO_LVRESIZE=1 ;; - "check") CHECK="$2" ; shift ;; - "resize") RESIZE="$2"; NEWSIZE="$3" ; shift 2 ;; + "-c"|"--cryptresize") DO_CRYPTRESIZE=1 ;; + "check") test -z "${2-}" && error "Missing <device>. (see: $TOOL --help)" + CHECK=$2 ; shift ;; + "resize") test -z "${2-}" && error "Missing <device>. (see: $TOOL --help)" + RESIZE=$2 ; shift + if test -n "${2-}" ; then NEWSIZE="${2-}" ; shift ; fi ;; *) error "Wrong argument \"$1\". (see: $TOOL --help)" esac shift done -test "$YES" = "-y" || YES= +test "$YES" = "-y" || YES="" test "$EXTOFF" -eq 1 || EXTOFF=0 if [ -n "$CHECK" ]; then @@ -493,6 +830,7 @@ if [ -n "$CHECK" ]; then elif [ -n "$RESIZE" ]; then export FSADM_RUNNING="fsadm" resize "$RESIZE" "$NEWSIZE" + cleanup 0 else error "Missing command. (see: $TOOL --help)" fi |