growfs script: add swap partition as well as growing root

Add the ability to create a swap partition in the course of growing
the root file system on first boot, enabling by default.  The default
rules are: add swap if the disk is at least 15 GB (decimal), and the
existing root is less than 40% of the disk.  The default size is 10%
of the disk, but is limited by the memory size.  The limit is twice
memory size up to 4 GB, 8 GB up to 8 GB memory, and memory size over
8 GB memory. Swap size is clamped at vm.swap_maxpages/2 as well.
The new swap partition is labeled as "growfs_swap".

The default behavior can be overridden by setting growfs_swap_size in
/etc/rc.conf or in the kernel environment, with kenv taking priority.
A value of 0 inhibits the addition of swap, an empty value specifies
the default, and other values indicate a swap size in bytes.

By default, addition of swap is inhibited if a swap partition is found
in the output of the sysctl kern.geom.conftxt before the current root
partition, usually meaning that there is another disk present.
Swap space is not added if one is already present in /etc/fstab.

The root partition is read-only when growfs runs, so /etc/fstab can
not be modified.  That step is handled by a new growfs_fstab script,
added in a separate commit.  Set the value "growfs_swap_pdev" in kenv
to indicate that this should be done, as well as for internal use.

There is optional verbose output meant for debugging; it can only be
enabled by modifying the script (in two places, for sh and awk).
This should be removed before release, after testing on -current.

Discussed with:	cperciva
Reviewed by:	imp (previous version)
Differential Revision:	https://reviews.freebsd.org/D37462
This commit is contained in:
Mike Karels 2022-12-10 13:38:36 -06:00
parent 08b05de1e2
commit 4a30d7bb37

View file

@ -1,5 +1,6 @@
#!/bin/sh
#
# Copyright 2022 Michael J. Karels
# Copyright 2014 John-Mark Gurney
# All rights reserved.
#
@ -32,8 +33,9 @@
# BEFORE: root
# KEYWORD: firstboot
# This allows us to distribute an image
# and have it work on essentially any size drive.
# Grow root partition to fill available space, optionally adding a swap
# partition at the end. This allows us to distribute an image and
# have it work on essentially any size drive.
# Note that this uses awk(1), and thus will not work if /usr is on a separate
# filesystem. We need to run early, because there might be not enough free
@ -48,7 +50,7 @@ start_cmd="growfs_start"
stop_cmd=":"
rcvar="growfs_enable"
growfs_get_diskdev ()
growfs_get_diskdev()
{
local _search=${1}
sysctl -b kern.geom.conftxt |
@ -61,8 +63,51 @@ growfs_get_diskdev ()
done
}
growfs_start ()
# Compute upper bound on swap partition size (if added), based on physmem
# and vm.swap_maxpages / 2 (the limit that elicits a warning).
# Rule for swap size based on memory size:
# up to 4 GB twice memory size
# 4 GB - 8 GB 8 GB
# over 8 GB memory size
growfs_swap_max()
{
memsize=$(sysctl -n hw.physmem)
memsizeMB=$(($memsize / (1024 * 1024)))
if [ $memsizeMB -lt 4096 ]
then
swapmax=$(($memsize * 2))
elif [ $memsizeMB -lt 8192 ]
then
swapmax=$((8192 * 1024 * 1024))
else
swapmax=$memsize
fi
pagesize=$(sysctl -n hw.pagesize)
vm_swap_max=$(($(sysctl -n vm.swap_maxpages) / 2 * $pagesize))
if [ $swapmax -gt $vm_swap_max ]
then
$swapmax=$vm_swap_max
fi
echo -n "$swapmax"
}
# Find newly-added swap partition on parent device ($1).
growfs_last_swap()
{
swapdev=$(gpart list $1 | awk '
$2 == "Name:" { dev = $3 }
$1 == "type:" && $2 == "freebsd-swap" { swapdev = dev }
END { print swapdev }
')
echo -n $swapdev
}
growfs_start()
{
verbose=0
echo "Growing root partition to fill device"
FSTYPE=$(mount -p | awk '{ if ( $2 == "/") { print $3 }}')
FSDEV=$(mount -p | awk '{ if ( $2 == "/") { print $1 }}')
@ -100,19 +145,126 @@ growfs_start ()
diskdev=${rootdev}
fi
# Check kenv for growfs_swap_size; if not present,
# check $growfs_swap_size from /etc/rc.conf.
# A value of 0 suppresses swap addition,
# "" (or unset) specifies the default;
# other values indicate the size in bytes.
# If default, check whether swap is already in fstab;
# if so, don't add another.
addswap=1
swapsize="$(kenv -q growfs_swap_size 2>/dev/null)"
case "$swapsize" in
"0") addswap=0
;;
"") case "$growfs_swap_size" in
"0") addswap=0
;;
"")
if ! awk '
/^#/ { next }
$3 == "swap" { exit 1 }
' < /etc/fstab
then
addswap=0
fi
;;
*) swapsize="$growfs_swap_size"
;;
esac
;;
*) ;;
esac
swaplim=$(growfs_swap_max)
[ $verbose -eq 1 ] && {
echo "diskdev is $diskdev"
echo "search is $search"
echo "swapsize is $swapsize"
echo "swaplim is $swaplim"
}
sysctl -b kern.geom.conftxt | awk '
{
verbose = 0
lvl=$1
device[lvl] = $3
type[lvl] = $2
idx[lvl] = $7
offset[lvl] = $9
parttype[lvl] = $13
size[lvl] = $4
if (verbose) print lvl, type[lvl], $3
if (type[lvl] == "DISK") {
disksize = size[lvl]
if (verbose)
print "disksize ", disksize
# Don't add swap on disks under 15 GB (decimal) by default.
if (addswap == 1 && (size[lvl] > 15000000000 || swapsize > 0))
doing_swap = 1
else
doing_swap = 0
} else if (type[lvl] == "PART" && $11 == "freebsd-swap" && \
int(swapsize) == 0) {
# This finds swap only if it precedes root, e.g. preceding disk.
addswap = 0
doing_swap = 0
print "swap device exists, not adding swap"
}
if (dev == $3) {
for (i = 1; i <= lvl; i++) {
# resize
if (type[i] == "PART") {
pdev = device[i - 1]
cmd[i] = "gpart resize -i " idx[i] " " pdev
if (verbose)
print i, pdev, addswap, disksize, \
doing_swap
swapcmd = ""
# Allow swap if current root is < 40% of disk.
if (parttype[i] != "MBR" && doing_swap == 1 && \
(size[i] / disksize < 0.4 || \
swapsize > 0)) {
print "Adding swap partition"
if (int(swapsize) == 0) {
swapsize = int(disksize / 10)
if (swapsize > swaplim)
swapsize = swaplim
}
sector = $5
swapsize /= sector
if (verbose)
print "swapsize sectors",
swapsize
align = 4 * 1024 * 1024 / sector
# Estimate offset for swap; let
# gpart compute actual start and size.
# Assume expansion all goes into this
# partition for MBR case.
if (parttype[i - 1] == "MBR") {
if (verbose)
print "sz ", size[i - 1], \
" off ", offset[i - 1]
expand = size[0] - \
(size[i - 1] + offset[i - 1])
} else {
if (verbose)
print "sz ", size[i], \
" off ", offset[i]
expand = size[0] - \
(size[i] + offset[i])
}
if (verbose)
print "expand ", expand, \
" sz ", size[i]
swapbase = (expand + size[i]) / sector
swapbase -= swapsize + align
swapcmd = "gpart add -t freebsd-swap -a " align " -b " swapbase " " pdev "; kenv growfs_swap_pdev=" pdev " >/dev/null; "
if (verbose)
swapcmd = "set -x; " swapcmd
}
cmd[i] = swapcmd "gpart resize -i " idx[i] " " pdev
if (parttype[i] == "GPT")
cmd[i] = "gpart recover " pdev " ; " cmd[i]
} else if (type[i] == "LABEL") {
@ -128,7 +280,7 @@ growfs_start ()
}
exit 0
}
}' dev="$search"
}' dev="$search" addswap="$addswap" swapsize="$swapsize" swaplim="$swaplim"
gpart commit "$diskdev" 2> /dev/null
case "$FSTYPE" in
ufs)
@ -138,6 +290,20 @@ growfs_start ()
zpool online -e $pool $rootdev
;;
esac
# Get parent device of swap partition if one was added;
# if so, find swap device and label it.
pdev=$(kenv -q growfs_swap_pdev)
if [ -n "$pdev" ]
then
dev=$(growfs_last_swap "$pdev")
if [ -z "$dev" ]
then
echo "Swap partition not found on $pdev"
exit 0
fi
glabel label -v growfs_swap $dev
fi
}
load_rc_config $name