#!/usr/bin/sh
# Copyright (C) 2011 Canonical Ltd.
# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
#
# Authors: Scott Moser <smoser@canonical.com>
# Juerg Haefliger <juerg.haefliger@hp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# the fudge factor. if within this many bytes dont bother
FUDGE=${GROWPART_FUDGE:-$((1024*1024))}
TEMP_D=""
RESTORE_FUNC=""
RESTORE_HUMAN=""
VERBOSITY=0
DISK=""
PART=""
PT_UPDATE=false
DRY_RUN=0
FLOCK_DISK_FD=""
RESIZE_RESULT=""
SFDISK_VERSION=""
SFDISK_2_26="22600"
SFDISK_V_WORKING_GPT="22603"
MBR_BACKUP=""
GPT_BACKUP=""
_capture=""
error() {
echo "$@" 1>&2
}
fail() {
[ $# -eq 0 ] || echo "FAILED:" "$@"
exit 2
}
nochange() {
RESIZE_RESULT="NOCHANGE"
echo "NOCHANGE:" "$@"
return 1
}
changed() {
RESIZE_RESULT="CHANGED"
echo "CHANGED:" "$@"
return 0
}
change() {
RESIZE_RESULT="CHANGE"
echo "CHANGE:" "$@"
return 0
}
cleanup() {
if [ -n "${RESTORE_FUNC}" ]; then
error "***** WARNING: Resize failed, attempting to revert ******"
if ${RESTORE_FUNC} ; then
error "***** Restore appears to have gone OK ****"
else
error "***** Restore FAILED! ******"
if [ -n "${RESTORE_HUMAN}" -a -f "${RESTORE_HUMAN}" ]; then
error "**** original table looked like: ****"
cat "${RESTORE_HUMAN}" 1>&2
else
error "We seem to have not saved the partition table!"
fi
fi
unlock_disk_and_settle $DISK
fi
[ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
}
debug() {
local level=${1}
shift
[ "${level}" -gt "${VERBOSITY}" ] && return
if [ "${DEBUG_LOG}" ]; then
echo "$@" >>"${DEBUG_LOG}"
else
error "$@"
fi
}
debugcat() {
local level="$1"
shift;
[ "${level}" -gt "$VERBOSITY" ] && return
if [ "${DEBUG_LOG}" ]; then
cat "$@" >>"${DEBUG_LOG}"
else
cat "$@" 1>&2
fi
}
mktemp_d() {
# just a mktemp -d that doens't need mktemp if its not there.
_RET=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX" 2>/dev/null) &&
return
_RET=$(umask 077 && t="${TMPDIR:-/tmp}/${0##*/}.$$" &&
mkdir "${t}" && echo "${t}")
return
}
Usage() {
cat <<EOF
${0##*/} disk partition
rewrite partition table so that partition takes up all the space it can
options:
-h | --help print Usage and exit
--free-percent F resize so that specified percentage F of the disk is
not used in total (not just by this partition). This
is useful for consumer SSD or SD cards where a small
percentage unallocated can improve device lifetime.
--fudge F if part could be resized, but change would be less
than 'F' bytes, do not resize (default: ${FUDGE})
-N | --dry-run only report what would be done, show new 'sfdisk -d'
-v | --verbose increase verbosity / debug
-u | --update R update the the kernel partition table info after
growing this requires kernel support and
'partx --update'
R is one of:
- 'auto' : [default] update partition if possible
- 'force' : try despite sanity checks (fail on
failure)
- 'off' : do not attempt
- 'on' : fail if sanity checks indicate no
support
Example:
- ${0##*/} /dev/sda 1
Resize partition 1 on /dev/sda
- ${0##*/} --free-percent=10 /dev/sda 1
Resize partition 1 on /dev/sda so that 10% of the disk is unallocated
EOF
}
bad_Usage() {
Usage 1>&2
error "$@"
exit 2
}
lock_disk() {
local disk="$1"
# flock the target disk to protect against udev actions while modifying.
# https://systemd.io/BLOCK_DEVICE_LOCKING/
[ "${DRY_RUN}" = 0 ] || return
# only lock block devices, files do not need a lock
[ -b "${disk}" ] || return
# The FD values are hard-coded per /bin/sh requirement for using exec
# to open paths with specific fd values; man (1) sh on "Redirections"
FLOCK_DISK_FD=9
debug 1 "FLOCK: try exec open fd 9, on failure exec exits this program"
exec 9<>$disk
# Do not use --nonblock or --timeout as udev may be already processing
# the disk and we must wait until it has released the disk to
# proceed. Failure to obtain exclusive lock is fatal to growpart.
rq flock flock -x $FLOCK_DISK_FD ||
fail "Error while obtaining exclusive lock on $DISK"
debug 1 "FLOCK: $disk: obtained exclusive lock"
}
unlock_disk_and_settle() {
# unlock_disk(disk, settle)
local disk="$1"
local settle=${2-"1"}
# release the lock on a disk if locked. When a disk is locked,
# FLOCK_DISK_FD is set to the hard-coded value of 9.
# After unlocking run udevadm settle (if installed) as the disk has
# likely been changed.
[ "${DRY_RUN}" = 0 ] || return
[ -n "${FLOCK_DISK_FD}" ] || return
debug 1 "FLOCK: ${disk}: releasing exclusive lock"
exec 9>&-
[ "${settle}" = 1 ] && has_cmd udevadm && udevadm settle
FLOCK_DISK_FD=""
}
sfdisk_restore_legacy() {
sfdisk --no-reread "${DISK}" -I "${MBR_BACKUP}"
}
sfdisk_restore() {
# files are named: sfdisk-<device>-<offset>.bak
local f="" offset="" fails=0
for f in "${MBR_BACKUP}"*.bak; do
[ -f "$f" ] || continue
offset=${f##*-}
offset=${offset%.bak}
[ "$offset" = "$f" ] && {
error "WARN: confused by file $f";
continue;
}
dd "if=$f" "of=${DISK}" seek=$(($offset)) bs=1 conv=notrunc ||
{ error "WARN: failed restore from $f"; fails=$(($fails+1)); }
done
return $fails
}
sfdisk_worked_but_blkrrpart_failed() {
local ret="$1" output="$2"
# exit code found was just 1, but dont insist on that
#[ $ret -eq 1 ] || return 1
# Successfully wrote the new partition table
if grep -qi "Success.* wrote.* new.* partition" "$output"; then
grep -qi "BLKRRPART: Device or resource busy" "$output"
return
# The partition table has been altered.
elif grep -qi "The.* part.* table.* has.* been.* altered" "$output"; then
# Re-reading the partition table failed
grep -qi "Re-reading.* partition.* table.* failed" "$output"
return
fi
return $ret
}
get_sfdisk_version() {
# set SFDISK_VERSION to MAJOR*10000+MINOR*100+MICRO
local out oifs="$IFS" ver=""
[ -n "$SFDISK_VERSION" ] && return 0
[ -n "$SFDISK" ] || {
SFDISK_VERSION=0
return 0
}
# expected output: sfdisk from util-linux 2.25.2
out=$(LANG=C sfdisk --version) ||
{ error "failed to get sfdisk version"; return 1; }
set -- $out
ver=$4
case "$ver" in
[0-9]*.[0-9]*.[0-9]|[0-9].[0-9]*)
IFS="."; set -- $ver; IFS="$oifs"
SFDISK_VERSION=$(($1*10000+$2*100+${3:-0}))
return 0;;
*) error "unexpected output in sfdisk --version [$out]"
return 1;;
esac
}
get_diskpart_path() {
# get_diskpart_path(disk, part_number)
# return the path to the partition device on disk
#
# Convert inputs disk and part into a path to a partition device path
# handle both block devices or files.
# e.g. get_diskpart_path /dev/loop0 7 returns /dev/loop0p7
local disk="$1"
local part="$2"
local dpart=""
dpart="${disk}${part}" # disk and partition number
if [ -b "$disk" ]; then
if [ -b "${disk}p${part}" -a "${disk%[0-9]}" != "${disk}" ]; then
# for block devices that end in a number (/dev/nbd0)
# the partition is "<name>p<partition_number>" (/dev/nbd0p1)
dpart="${disk}p${part}"
elif [ "${disk#/dev/loop[0-9]}" != "${disk}" ]; then
# for /dev/loop devices, sfdisk output will be <name>p<number>
# format also, even though there is not a device there.
dpart="${disk}p${part}"
fi
else
case "$disk" in
# sfdisk for files ending in digit to <disk>p<num>.
*[0-9]) dpart="${disk}p${part}";;
esac
fi
_RET="$dpart"
}
resize_sfdisk() {
local humanpt="${TEMP_D}/recovery"
local mbr_backup="${TEMP_D}/orig.save"
local restore_func=""
local format="$1"
local change_out=${TEMP_D}/change.out
local dump_out=${TEMP_D}/dump.out
local new_out=${TEMP_D}/new.out
local dump_mod=${TEMP_D}/dump.mod
local tmp="${TEMP_D}/tmp.out"
local err="${TEMP_D}/err.out"
local mbr_max_512="4294967296"
local pt_start pt_size pt_end max_end new_size change_info dpart
local sector_num sector_size disk_size tot out
local excess_sectors free_percent_sectors remaining_free_sectors
LANG=C rqe sfd_list sfdisk --list --unit=S "$DISK" >"$tmp" ||
fail "failed: sfdisk --list $DISK"
if [ "${SFDISK_VERSION}" -lt ${SFDISK_2_26} ]; then
# exected output contains: Units: sectors of 512 bytes, ...
out=$(awk '$1 == "Units:" && $5 ~ /bytes/ { print $4 }' "$tmp") ||
fail "failed to read sfdisk output"
if [ -z "$out" ]; then
error "WARN: sector size not found in sfdisk output, assuming 512"
sector_size=512
else
sector_size="$out"
fi
local _w _cyl _w1 _heads _w2 sectors _w3 t s
# show-size is in units of 1024 bytes (same as /proc/partitions)
t=$(sfdisk --show-size "${DISK}") ||
fail "failed: sfdisk --show-size $DISK"
disk_size=$((t*1024))
sector_num=$(($disk_size/$sector_size))
msg="disk size '$disk_size' not evenly div by sector size '$sector_size'"
[ "$((${disk_size}%${sector_size}))" -eq 0 ] ||
error "WARN: $msg"
restore_func=sfdisk_restore_legacy
else
# --list first line output:
# Disk /dev/vda: 20 GiB, 21474836480 bytes, 41943040 sectors
local _x
read _x _x _x _x disk_size _x sector_num _x < "$tmp"
sector_size=$((disk_size/$sector_num))
restore_func=sfdisk_restore
fi
debug 1 "$sector_num sectors of $sector_size. total size=${disk_size} bytes"
rqe sfd_dump sfdisk --unit=S --dump "${DISK}" >"${dump_out}" ||
fail "failed to dump sfdisk info for ${DISK}"
RESTORE_HUMAN="$dump_out"
{
echo "## sfdisk --unit=S --dump ${DISK}"
cat "${dump_out}"
} >"$humanpt"
[ $? -eq 0 ] || fail "failed to save sfdisk -d output"
RESTORE_HUMAN="$humanpt"
debugcat 1 "$humanpt"
sed -e 's/,//g; s/start=/start /; s/size=/size /' "${dump_out}" \
>"${dump_mod}" ||
fail "sed failed on dump output"
get_diskpart_path $DISK $PART
dpart="$_RET"
pt_start=$(awk '$1 == pt { print $4 }' "pt=${dpart}" <"${dump_mod}") &&
pt_size=$(awk '$1 == pt { print $6 }' "pt=${dpart}" <"${dump_mod}") &&
[ -n "${pt_start}" -a -n "${pt_size}" ] &&
pt_end=$((${pt_size} + ${pt_start} - 1)) ||
fail "failed to get start and end for ${dpart} in ${DISK}"
# find the minimal starting location that is >= pt_end
max_end=$(awk '$3 == "start" { if($4 >= pt_end && $4 < min)
{ min = $4 } } END { printf("%s\n",min); }' \
min=${sector_num} pt_end=${pt_end} "${dump_mod}") &&
[ -n "${max_end}" ] ||
fail "failed to get max_end for partition ${PART}"
# As sector numbering starts from 0 need to reduce value by 1.
max_end=$((max_end - 1))
if [ "$format" = "gpt" ]; then
# sfdisk respects 'last-lba' in input, and complains about
# partitions that go past that. without it, it does the right thing.
sed -i '/^last-lba:/d' "$dump_out" ||
fail "failed to remove last-lba from output"
fi
if [ "$format" = "dos" ]; then
mbr_max_sectors=$((mbr_max_512*$((sector_size/512))))
if [ "$max_end" -gt "$mbr_max_sectors" ]; then
max_end=$mbr_max_sectors
fi
[ $(($disk_size/512)) -gt $mbr_max_512 ] &&
debug 0 "WARNING: MBR/dos partitioned disk is larger than 2TB." \
"Additional space will go unused."
fi
local gpt_second_size="33"
if [ "${max_end}" -gt "$((${sector_num}-${gpt_second_size}))" ]; then
# if MBR, allow subsequent conversion to GPT without shrinking
# the partition and safety net at cost of 33 sectors seems
# reasonable. If GPT, we can't write there anyway.
debug 1 "padding ${gpt_second_size} sectors for gpt secondary header"
max_end=$((${sector_num} - ${gpt_second_size} - 1))
fi
if [ -n "${free_percent}" ]; then
free_percent_sectors=$((sector_num/100*free_percent))
if [ "$format" = "dos" ]; then
if [ $(($disk_size/512)) -ge $((mbr_max_512+free_percent_sectors)) ]; then
# If MBR partitioned disk larger than 2TB and
# remaining space over 2TB boundary is greater
# than the requested overprovisioning sectors
# then do not change max_end.
debug 1 "WARNING: Additional unused space on MBR/dos partitioned disk" \
"is larger than requested percent of overprovisioning."
elif [ $sector_num -gt $mbr_max_512 ]; then
# If only some of the overprovisioning sectors
# are over the 2TB boundary then reduce max_end
# by the remaining number of overprovisioning
# sectors.
excess_sectors=$((sector_num-mbr_max_512))
remaining_free_sectors=$((free_percent_sectors - excess_sectors))
debug 1 "reserving ${remaining_free_sectors} sectors from MBR maximum for overprovisioning"
max_end=$((max_end - remaining_free_sectors))
else
# Shrink max_end to keep X% of whole disk unused
# (for overprovisioning).
debug 1 "reserving ${free_percent_sectors} sectors (${free_percent}%) for overprovisioning"
max_end=$((max_end-free_percent_sectors))
fi
if [ ${max_end} -lt ${pt_end} ]; then
nochange "partition ${PART} could not be grown while leaving" \
"${free_percent}% (${free_percent_sectors} sectors) free on device"
return
fi
else
# Shrink max_end to keep X% of whole disk unused
# (for overprovisioning).
debug 1 "reserving ${free_percent_sectors} sectors (${free_percent}%) for overprovisioning"
max_end=$((max_end-free_percent_sectors))
if [ ${max_end} -lt ${pt_end} ]; then
nochange "partition ${PART} could not be grown while leaving" \
"${free_percent}% (${free_percent_sectors} sectors) free on device"
return
fi
fi
fi
debug 1 "max_end=${max_end} tot=${sector_num} pt_end=${pt_end}" \
"pt_start=${pt_start} pt_size=${pt_size}"
[ $((${pt_end})) -eq ${max_end} ] && {
nochange "partition ${PART} is size ${pt_size}. it cannot be grown"
return
}
[ $((${pt_end}+(${FUDGE}/$sector_size))) -gt ${max_end} ] && {
nochange "partition ${PART} could only be grown by" \
"$((${max_end}-${pt_end})) [fudge=$((${FUDGE}/$sector_size))]"
return
}
# Now, change the size for this partition in ${dump_out} to be the
# new size.
new_size=$((${max_end} - ${pt_start} + 1))
sed "\|^\s*${dpart} |s/\(.*\)${pt_size},/\1${new_size},/" "${dump_out}" \
>"${new_out}" ||
fail "failed to change size in output"
change_info="partition=${PART} start=${pt_start}"
change_info="${change_info} old: size=${pt_size} end=${pt_end}"
change_info="${change_info} new: size=${new_size} end=${max_end}"
if [ ${DRY_RUN} -ne 0 ]; then
echo "CHANGE: ${change_info}"
{
echo "# === old sfdisk -d ==="
cat "${dump_out}"
echo "# === new sfdisk -d ==="
cat "${new_out}"
} 1>&2
exit 0
fi
MBR_BACKUP="${mbr_backup}"
LANG=C sfdisk --no-reread "${DISK}" --force \
-O "${mbr_backup}" <"${new_out}" >"${change_out}" 2>&1
ret=$?
[ $ret -eq 0 ] || RESTORE_FUNC="${restore_func}"
if [ $ret -eq 0 ]; then
debug 1 "resize of ${DISK} returned 0."
if [ $VERBOSITY -gt 2 ]; then
sed 's,^,| ,' "${change_out}" 1>&2
fi
elif $PT_UPDATE &&
sfdisk_worked_but_blkrrpart_failed "$ret" "${change_out}"; then
# if the command failed, but it looks like only because
# the device was busy and we have pt_update, then go on
debug 1 "sfdisk failed, but likely only because of blkrrpart"
else
error "attempt to resize ${DISK} failed. sfdisk output below:"
sed 's,^,| ,' "${change_out}" 1>&2
fail "failed to resize"
fi
rq pt_update pt_update "$DISK" "$PART" ||
fail "pt_resize failed"
RESTORE_FUNC=""
changed "${change_info}"
return
# dump_out looks something like:
## partition table of /tmp/out.img
#unit: sectors
#
#/tmp/out.img1 : start= 1, size= 48194, Id=83
#/tmp/out.img2 : start= 48195, size= 963900, Id=83
#/tmp/out.img3 : start= 1012095, size= 305235, Id=82
#/tmp/out.img4 : start= 1317330, size= 771120, Id= 5
#/tmp/out.img5 : start= 1317331, size= 642599, Id=83
#/tmp/out.img6 : start= 1959931, size= 48194, Id=83
#/tmp/out.img7 : start= 2008126, size= 80324, Id=83
}
gpt_restore() {
sgdisk -l "${GPT_BACKUP}" "${DISK}"
}
resize_sgdisk() {
GPT_BACKUP="${TEMP_D}/pt.backup"
local pt_info="${TEMP_D}/pt.info"
local pt_pretend="${TEMP_D}/pt.pretend"
local pt_data="${TEMP_D}/pt.data"
local out="${TEMP_D}/out"
local dev="disk=${DISK} partition=${PART}"
local pt_start pt_end pt_size last pt_max code guid name new_size
local old new change_info sector_size
# Dump the original partition information and details to disk. This is
# used in case something goes wrong and human interaction is required
# to revert any changes.
rqe sgd_info sgdisk "--info=${PART}" --print "${DISK}" >"${pt_info}" ||
fail "${dev}: failed to dump original sgdisk info"
RESTORE_HUMAN="${pt_info}"
# support reading old format (<1.0.2)
# Logical sector size: <VAL> bytes
# and newer format >= 1.0.2
# Sector size (logical): <VAL> bytes
# Sector size (logical/physical): <VAL>/<PVAL> bytes
sector_size=$(awk '
$0 ~ /^Logical sector size:.*bytes/ { print $4; exit(0); }
$0 ~ /^Sector size \(logical\):/ { print $4; exit(0); }
$0 ~ /^Sector size \(logical\/physical\):/ {
sub(/\/.*/, "", $4); print $4; exit(0); }' \
"$pt_info") && [ -n "$sector_size" ] || {
sector_size=512
error "WARN: did not find sector size, assuming 512"
}
debug 1 "$dev: original sgdisk info:"
debugcat 1 "${pt_info}"
# Pretend to move the backup GPT header to the end of the disk and dump
# the resulting partition information. We use this info to determine if
# we have to resize the partition.
rqe sgd_pretend sgdisk --pretend --move-second-header \
--print "${DISK}" >"${pt_pretend}" ||
fail "${dev}: failed to dump pretend sgdisk info"
debug 1 "$dev: pretend sgdisk info"
debugcat 1 "${pt_pretend}"
# Extract the partition data from the pretend dump
awk 'found { print } ; $1 == "Number" { found = 1 }' \
"${pt_pretend}" >"${pt_data}" ||
fail "${dev}: failed to parse pretend sgdisk info"
# Get the start and end sectors of the partition to be grown
pt_start=$(awk '$1 == '"${PART}"' { print $2 }' "${pt_data}") &&
[ -n "${pt_start}" ] ||
fail "${dev}: failed to get start sector"
pt_end=$(awk '$1 == '"${PART}"' { print $3 }' "${pt_data}") &&
[ -n "${pt_end}" ] ||
fail "${dev}: failed to get end sector"
# Start and end are inclusive, start 2048 end 2057 is length 10.
pt_size="$((${pt_end} - ${pt_start} + 1))"
# Get the last usable sector
last=$(awk '/last usable sector is/ { print $NF }' \
"${pt_pretend}") && [ -n "${last}" ] ||
fail "${dev}: failed to get last usable sector"
# Find the maximal end sector that is >= pt_end
pt_max=$(awk '{ if ($2 >= pt_end && $2 < min) { min = $2 } } END \
{ print min-1 }' min="${last}" pt_end="${pt_end}" \
"${pt_data}") && [ -n "${pt_max}" ] ||
fail "${dev}: failed to find max end sector"
debug 1 "${dev}: pt_start=${pt_start} pt_end=${pt_end}" \
"pt_size=${pt_size} pt_max=${pt_max} last=${last}"
# Check if the partition can be grown
[ "${pt_end}" -eq "${pt_max}" ] && {
nochange "${dev}: size=${pt_size}, it cannot be grown"
return
}
[ "$((${pt_end} + ${FUDGE}/${sector_size}))" -gt "${pt_max}" ] && {
nochange "${dev}: could only be grown by" \
"$((${pt_max} - ${pt_end})) [fudge=$((${FUDGE}/$sector_size))]"
return
}
# The partition can be grown if we made it here. Get some more info
# about it so we can do it properly.
# FIXME: Do we care about the attribute flags?
code=$(awk '/^Partition GUID code:/ { print $4 }' "${pt_info}")
guid=$(awk '/^Partition unique GUID:/ { print $4 }' "${pt_info}")
name=$(awk '/^Partition name:/ { gsub(/'"'"'/, "") ; \
if (NF >= 3) print substr($0, index($0, $3)) }' "${pt_info}")
[ -n "${code}" -a -n "${guid}" ] ||
fail "${dev}: failed to parse sgdisk details"
debug 1 "${dev}: code=${code} guid=${guid} name='${name}'"
local wouldrun=""
[ "$DRY_RUN" -ne 0 ] && wouldrun="would-run"
# Calculate the new size of the partition
new_size=$((${pt_max} - ${pt_start} + 1))
change_info="partition=${PART} start=${pt_start}"
change_info="${change_info} old: size=${pt_size} end=${pt_end}"
change_info="${change_info} new: size=${new_size} end=${pt_max}"
# Backup the current partition table, we're about to modify it
rq sgd_backup $wouldrun sgdisk "--backup=${GPT_BACKUP}" "${DISK}" ||
fail "${dev}: failed to backup the partition table"
# Modify the partition table. We do it all in one go (the order is
# important!):
# - move the GPT backup header to the end of the disk
# - delete the partition
# - recreate the partition with the new size
# - set the partition code
# - set the partition GUID
# - set the partition name
rq sgdisk_mod $wouldrun sgdisk --move-second-header "--delete=${PART}" \
"--new=${PART}:${pt_start}:$((pt_max-1))" \
"--typecode=${PART}:${code}" \
"--partition-guid=${PART}:${guid}" \
"--change-name=${PART}:${name}" "${DISK}" &&
rq pt_update $wouldrun pt_update "$DISK" "$PART" || {
RESTORE_FUNC=gpt_restore
fail "${dev}: failed to repartition"
}
# Dry run
[ "${DRY_RUN}" -ne 0 ] && {
change "${change_info}"
return
}
changed "${change_info}"
return
}
kver_to_num() {
local kver="$1" maj min mic
# Canonicalize the kernel version
kver=${kver%%[!0-9.]*}.0.0
maj=${kver%%[!0-9]*}
kver=${kver#*.}
min=${kver%%[!0-9]*}
kver=${kver#*.}
mic=${kver%%[!0-9]*}
_RET=$((maj*1000*1000+min*1000+mic))
}
kver_cmp() {
local op="$2" n1="" n2=""
kver_to_num "$1"
n1="$_RET"
kver_to_num "$3"
n2="$_RET"
test $n1 $op $n2
}
rq() {
# runquieterror(label, command)
# gobble stderr of a command unless it errors
local label="$1" ret="" efile=""
efile="$TEMP_D/$label.err"
shift;
local rlabel="running"
[ "$1" = "would-run" ] && rlabel="would-run" && shift
local cmd="" x=""
for x in "$@"; do
[ "${x#* }" != "$x" -o "${x#* \"}" != "$x" ] && x="'$x'"
cmd="$cmd $x"
done
cmd=${cmd# }
debug 2 "${rlabel}[$label][$_capture]" "$cmd"
[ "$rlabel" = "would-run" ] && return 0
if [ "${_capture}" = "erronly" ]; then
"$@" 2>"$TEMP_D/$label.err"
ret=$?
else
"$@" >"$TEMP_D/$label.err" 2>&1
ret=$?
fi
if [ $ret -ne 0 ]; then
error "failed [$label:$ret]" "$@"
cat "$efile" 1>&2
fi
return $ret
}
rqe() {
local _capture="erronly"
rq "$@"
}
verify_ptupdate() {
local input="$1" found="" reason="" kver=""
# we can always satisfy 'off'
if [ "$input" = "off" ]; then
_RET="false";
return 0;
fi
if command -v partx >/dev/null 2>&1; then
local out="" ret=0
out=$(partx --help 2>&1)
ret=$?
if [ $ret -eq 0 ]; then
echo "$out" | grep -q -- --update || {
reason="partx has no '--update' flag in usage."
found="off"
}
else
reason="'partx --help' returned $ret. assuming it is old."
found="off"
fi
else
reason="no 'partx' command"
found="off"
fi
if [ -z "$found" ]; then
if [ "$(uname)" != "Linux" ]; then
reason="Kernel is not Linux per uname."
found="off"
fi
fi
if [ -z "$found" ]; then
kver=$(uname -r) || debug 1 "uname -r failed!"
if ! kver_cmp "${kver-0.0.0}" -ge 3.8.0; then
reason="Kernel '$kver' < 3.8.0."
found="off"
fi
fi
if [ -z "$found" ]; then
_RET="true"
return 0
fi
case "$input" in
on) error "$reason"; return 1;;
auto)
_RET="false";
debug 1 "partition update disabled: $reason"
return 0;;
force)
_RET="true"
error "WARNING: ptupdate forced on even though: $reason"
return 0;;
esac
error "unknown input '$input'";
return 1;
}
pt_update() {
local dev="$1" part="$2" update="${3:-$PT_UPDATE}"
if ! $update; then
return 0
fi
# partx only works on block devices (do not run on file)
[ -b "$dev" ] || return 0
partx --update --nr "$part" "$dev"
}
has_cmd() {
command -v "${1}" >/dev/null 2>&1
}
resize_sgdisk_gpt() {
resize_sgdisk gpt
}
resize_sgdisk_dos() {
fail "unable to resize dos label with sgdisk"
}
resize_sfdisk_gpt() {
resize_sfdisk gpt
}
resize_sfdisk_dos() {
resize_sfdisk dos
}
get_table_format() {
local out="" disk="$1"
if has_cmd blkid && blkid --version | grep -q util-linux &&
out=$(blkid -o value -s PTTYPE "$disk") &&
[ "$out" = "dos" -o "$out" = "gpt" ]; then
_RET="$out"
return
fi
_RET="dos"
if [ -z "$SFDISK" ]; then
out=$(LANG=C sgdisk --print "$disk") || {
error "Could not determine partition table format of $disk" \
"with 'sgdisk --print $disk'"
return 1
}
# looking for a message like:
# Found invalid GPT and valid MBR; converting MBR to GPT format
case "$out" in
*\ valid\ MBR\ *) _RET="dos";;
*) _RET="gpt";;
esac
return
elif [ ${SFDISK_VERSION} -lt ${SFDISK_2_26} ] &&
out=$(sfdisk --id --force "$disk" 1 2>/dev/null); then
if [ "$out" = "ee" ]; then
_RET="gpt"
else
_RET="dos"
fi
return
elif out=$(LANG=C sfdisk --list "$disk"); then
out=$(echo "$out" | sed -e '/Disklabel type/!d' -e 's/.*: //')
case "$out" in
gpt|dos) _RET="$out";;
*) error "WARN: unknown label $out";;
esac
fi
}
get_resizer() {
local format="$1" user=${2:-"auto"}
case "$user" in
sgdisk) _RET="resize_sgdisk_$format"; return;;
sfdisk) _RET="resize_sfdisk_$format"; return;;
auto) :;;
*) error "unexpected value '$user' for growpart resizer"; return 1;;
esac
if [ "$format" = "dos" ]; then
[ -n "$SFDISK" ] || {
error "sfdisk is required for resizing dos/MBR partition table."
return 1
}
_RET="resize_sfdisk_dos"
return 0
fi
if [ "${SFDISK_VERSION}" -ge ${SFDISK_V_WORKING_GPT} ]; then
# sfdisk 2.26.2 works for resize but loses type (LP: #1474090)
_RET="resize_sfdisk_gpt"
elif has_cmd sgdisk; then
_RET="resize_sgdisk_$format"
else
error "no tools available to resize disk with '$format'"
return 1
fi
return 0
}
maybe_lvm_resize() {
local disk="$1" part="$2" partpath="" ret="" out="" wouldrun=""
[ "$DRY_RUN" -ne 0 ] && wouldrun="would-run"
has_cmd lvm || {
debug 2 "No lvm command, cannot attempt lvm resize of disk '$disk' part '$part'"
return 0
}
get_diskpart_path "$1" "$2" || {
error "could not determine partition path for disk '$DISK' part '$part'"
return 1
}
partpath="$_RET"
# can't use rq or rqe here because of "not an lvm" exit code 5.
set -- lvm pvs --nolocking --readonly -o pvname "$partpath"
debug 2 "executing: $*"
out=$("$@" 2>&1)
ret=$?
case "$ret" in
5) debug 1 "$partpath is not an lvm pv"; return 0;;
0) :;;
*) error "failed to execute [$ret] '$*'"
error "$out"
return 1;;
esac
rq lvm_resize $wouldrun lvm pvresize "$partpath" || {
error "Failed to resize lvm pv $partpath"
return 1
}
return 0
}
pt_update="auto"
resizer=${GROWPART_RESIZER:-"auto"}
while [ $# -ne 0 ]; do
cur=${1}
next=${2}
case "$cur" in
-h|--help)
Usage
exit 0
;;
--free-percent|--free-percent=*)
if [ "${cur#--free-percent=}" != "$cur" ]; then
next="${cur#--free-percent=}"
else
shift
fi
if [ "$next" -gt 0 ] 2>/dev/null &&
[ "$next" -lt 100 ] 2>/dev/null; then
free_percent=$next
else
fail "unknown/invalid --free-percent option: $next"
fi
;;
--fudge)
FUDGE=${next}
shift
;;
-N|--dry-run)
DRY_RUN=1
;;
-u|--update|--update=*)
if [ "${cur#--update=}" != "$cur" ]; then
next="${cur#--update=}"
else
shift
fi
case "$next" in
off|auto|force|on) pt_update=$next;;
*) fail "unknown --update option: $next";;
esac
;;
-v|--verbose)
VERBOSITY=$(($VERBOSITY+1))
;;
--)
shift
break
;;
-*)
fail "unknown option ${cur}"
;;
*)
if [ -z "${DISK}" ]; then
DISK=${cur}
else
[ -z "${PART}" ] || fail "confused by arg ${cur}"
PART=${cur}
fi
;;
esac
shift
done
[ -n "${DISK}" ] || bad_Usage "must supply disk and partition-number"
[ -n "${PART}" ] || bad_Usage "must supply partition-number"
[ -e "${DISK}" ] || fail "${DISK}: does not exist"
# cache the has_cmd return value.
has_cmd sfdisk && SFDISK=sfdisk || SFDISK=""
has_cmd sgdisk && SGDISK=sgdisk || SGDISK=""
[ -n "$SGDISK" -o -n "$SFDISK" ] ||
fail "Did not have sfdisk or sgdisk in PATH."
get_sfdisk_version || fail
# If $DISK is a symlink, resolve it.
# This avoids problems due to varying partition device name formats
# (e.g. "1" for /dev/sda vs "-part1" for /dev/disk/by-id/name)
if [ -L "${DISK}" ]; then
has_cmd readlink ||
fail "${DISK} is a symlink, but 'readlink' command not available."
real_disk=$(readlink -f "${DISK}") || fail "unable to resolve ${DISK}"
debug 1 "${DISK} resolved to ${real_disk}"
DISK=${real_disk}
fi
[ "${PART#*[!0-9]}" = "${PART}" ] || fail "partition-number must be a number"
verify_ptupdate "$pt_update" || fail
PT_UPDATE=$_RET
debug 1 "update-partition set to $PT_UPDATE"
mktemp_d && TEMP_D="${_RET}" || fail "failed to make temp dir"
trap cleanup 0 # EXIT - some shells may not like 'EXIT' but are ok with 0
# get the ID of the first partition to determine if it's MBR or GPT
get_table_format "$DISK" || fail
format=$_RET
get_resizer "$format" "$resizer" ||
fail "failed to get a resizer for format '$format'"
resizer=$_RET
lock_disk $DISK
debug 1 "resizing $PART on $DISK using $resizer"
"$resizer"
ret=$?
unlock_disk_and_settle $DISK
if [ "$RESIZE_RESULT" = "CHANGED" -o "$RESIZE_RESULT" = "CHANGE" ]; then
maybe_lvm_resize "$DISK" "$PART" || fail "lvm resize failed."
fi
exit $ret
# vi: ts=4 noexpandtab
Mr. DellatioNx196 GaLers xh3LL Backd00r 1.0, Coded By Mr. DellatioNx196 - Bogor BlackHat