Skip to content

Commit

Permalink
growpart: Add support for overprovisioning
Browse files Browse the repository at this point in the history
Add option to 'growpart' to specify percentage of device that should be
left unallocated when growing partition. This is intended for consumer
SSDs and SD cards where the performance and/or lifetime of these devices
can be improved if some disk space (in addition to any the device "hides"
from users) is left unallocated.

Overprovisioning code caters for several distinct scenarios:

(1) MSDOS/MBR partitioned disk where the disk is >2TB and so MBR
    partitions cannot extend beyond 2TB - if disk is larger than
    (2TB + overprovisioning requirement) then nothing needs to be
    done.

(2) MSDOS/MBR partitioned disk where the disk is >2TB and so MBR
    partitions cannot extend beyond 2TB - if disk is not larger
    than (2TB + overprovisioning requirement) then *some*
    overprovisioning space still needs to be reserved.

(3) MSDOS/MBR partitioned disk <=2TB where overprovisioning space
    needs to be reserved.

(4) GPT partitioned disk where overprovisioning space needs to be
    reserved.

Also added a testcase script, test-growpart-overprovision.
  • Loading branch information
dermotbradley committed Mar 23, 2022
1 parent 4cc4950 commit 17cb8d3
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 12 deletions.
94 changes: 82 additions & 12 deletions bin/growpart
Original file line number Diff line number Diff line change
Expand Up @@ -119,22 +119,32 @@ Usage() {
${0##*/} disk partition
rewrite partition table so that partition takes up all the space it can
options:
-h | --help print Usage and exit
--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
-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
}

Expand Down Expand Up @@ -291,6 +301,7 @@ resize_sfdisk() {

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"
Expand Down Expand Up @@ -384,6 +395,52 @@ resize_sfdisk() {
max_end=$((${sector_num}-${gpt_second_size}))
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-renaming_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)
if [ $max_end -lt $((sector_num-free_percent_sectors)) ]; then
debug 1 "reserving ${free_percent_sectors} sectors (${free_percent}%) for overprovisioning"
max_end=$((max_end-free_percent_sectors))
else
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} ] && {
Expand Down Expand Up @@ -873,6 +930,19 @@ while [ $# -ne 0 ]; do
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
Expand Down
112 changes: 112 additions & 0 deletions test/test-growpart-overprovision
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/bin/bash
# NEED_ROOT

set -e

[ "$(id -u)" = "0" ] ||
{ echo "sorry, must be root"; exit 1; }

PT_TYPE="${PT_TYPE:-dos}" # dos or gpt
size=${DISK_SIZE_NEW:-100M}
osize=${DISK_SIZE_ORIG:-50M}
freepercent=${OVER_PROVISION_PERCENT:-10}

LODEV=""
TEMP_D=""

clearparts() {
# read /proc/partitions, clearing any partitions on dev (/dev/loopX)
local dev="$1"
local short=${dev##*/} parts="" part=""
parts=$(awk '$4 ~ m { sub(m,"",$4); print $4 }' \
"m=${short}p" /proc/partitions)
[ -z "$parts" ] && return
echo "clearing parts [$parts] from $dev"
for part in $parts; do
echo "delpart $LODEV $part"
delpart $LODEV $part
done
udevadm settle
}
cleanup() {
if [ -n "$LODEV" ]; then
clearparts "$LODEV"
echo "losetup --detach $LODEV";
losetup --detach "$LODEV";
udevadm settle
fi
[ ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
}
rq() {
local out="${TEMP_D}/out"
"$@" > "$out" 2>&1 || { echo "FAILED:" "$@"; cat "$out"; return 1; }
}

TEMP_D=$(mktemp -d ${TMPDIR:-/tmp}/${0##*/}.XXXXXX)
trap cleanup EXIT

img="${TEMP_D}/disk.img"

echo "Partitioning $PT_TYPE orig_size=$osize grow_size=$size, overprovisioning=$freepercent%."
echo "growpart is $(which growpart)"
rm -f $img

truncate --size $osize "$img"

label_flag="--label=${PT_TYPE}"
echo "2048," | rq sfdisk $label_flag --force --unit=S "$img"

truncate --size "$size" "$img"

lodev=$(losetup --show --find "$img")
LODEV=$lodev
echo "set up $lodev"

# clear any old ones that might be around (LP: #1136781)
clearparts "$lodev"
partx --add $lodev
lodevpart="${lodev}p1"

echo "==== before ===="
grep "${lodev##*/}" /proc/partitions
sfdisk --list --unit=S "$lodev"

errfile="${TEMP_D}/growpart.err"
growpart -v -v --free-percent "$freepercent" "$lodev" 1 2>"$errfile" || {
rc=$?
echo "failed [$rc]: growpart -v -v --free-percent $freepercent $lodev 1"
cat "$errfile" 1>&2
exit $rc
}

out=$(grep "FLOCK:.*releasing exclusive lock" "$errfile") || :
if [ -z "$out" ]; then
echo "ERROR: growpart stderr did not mention releasing lock"
exit 1
fi

echo === growpart stderr ===
cat "$errfile"

echo "==== after ===="
grep "${lodev##*/}" /proc/partitions
sfdisk --list --unit=S "$lodev"

enddevice=$(grep "${lodev##*/}" /proc/partitions|grep -v "${lodevpart##*/}"|awk '{print $3}')
endpart=$(grep "${lodevpart##*/}" /proc/partitions|awk '{print $3}')
# Subtract the following from disk image end:
# - required percentage of overprovisioning
# - 1024 MiB (the partition start, 2048 sectors of 512 bytes)
# - 17 MiB (rounded up value of MBR padding of 33 sectors for GPT conversion)
# to calculate the expected end of resized partition.
expectedendpart=$((enddevice-(enddevice/100*freepercent)-1024-17))
echo
if [ $endpart = $expectedendpart ]; then
echo "Final partition size matches expected partition size"
echo
else
echo "ERROR: final partition size of $endpart is different than expected size of $expectedendpart"
exit 1
fi

# vi: ts=4 noexpandtab
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ shfiles =
test/test-growpart
test/test-growpart-fsimage
test/test-growpart-fsimage-middle
test/test-growpart-overprovision
test/test-growpart-lvm
test/test-growpart-start-matches-size
test/test-mic
Expand Down

0 comments on commit 17cb8d3

Please sign in to comment.