git: 5a31987d4c39 - main - rc+devd: Add growfs_postboot

From: Colin Percival <cperciva_at_FreeBSD.org>
Date: Mon, 24 Aug 2026 05:02:49 UTC
The branch main has been updated by cperciva:

URL: https://cgit.FreeBSD.org/src/commit/?id=5a31987d4c395ec4d3ed303cbc2e1d12ea54a039

commit 5a31987d4c395ec4d3ed303cbc2e1d12ea54a039
Author:     Colin Percival <cperciva@FreeBSD.org>
AuthorDate: 2026-07-30 20:20:51 +0000
Commit:     Colin Percival <cperciva@FreeBSD.org>
CommitDate: 2026-08-24 05:02:16 +0000

    rc+devd: Add growfs_postboot
    
    In VM and cloud environments it is often possible to enlarge virtual
    disks; this can be useful, for example, if a system is launched with a
    small root disk and it later becomes clear that more space is needed.
    
    On kernels which support run-time resizing of disks (for NVMe, this was
    added in November 2025; some other disk types have supported this for
    longer) a SIZECHANGE notification is sent to userland via devd.
    
    Add a "nostart" rc.d script (runnable manually but not automatically at
    boot time) and a devd script which invokes it when a notification
    arrives.  The rc.d script enlarges the "final partition" on partitioned
    geoms, or the UFS filesystem or zpool device when triggered on a disk
    containing either of those.
    
    Reviewed by:    imp, ziaee
    MFC after:      2 weeks
    Relnotes:       Disk partitions and filesystems can be enlarged
                    automatically when disks grow by setting
                    growfs_postboot_enable=YES in /etc/rc.conf.
    Sponsored by:   Amazon
    Differential Revision:  https://reviews.freebsd.org/D58582
---
 libexec/rc/rc.conf               |   3 +
 libexec/rc/rc.d/Makefile         |   1 +
 libexec/rc/rc.d/growfs_postboot  | 116 +++++++++++++++++++++++++++++++++++++++
 sbin/devd/Makefile               |   1 +
 sbin/devd/growfs_postboot.conf   |   8 +++
 share/man/man5/rc.conf.5         |   1 +
 share/man/man7/Makefile          |   1 +
 share/man/man7/growfs.7          |   1 +
 share/man/man7/growfs_postboot.7 |  69 +++++++++++++++++++++++
 9 files changed, 201 insertions(+)

diff --git a/libexec/rc/rc.conf b/libexec/rc/rc.conf
index d5d1a198b143..e7ce22822515 100644
--- a/libexec/rc/rc.conf
+++ b/libexec/rc/rc.conf
@@ -122,6 +122,9 @@ background_fsck_delay="60" # Time to wait (seconds) before starting the fsck.
 growfs_enable="NO"	# Set to YES to attempt to grow the root filesystem on boot
 growfs_swap_size=""	# Set to 0 to disable growfs swap, "" to default size,
 			# size in bytes to specify swap size.
+growfs_postboot_enable="NO" # Set to YES to attempt to grow partitions and
+			# filesystems when devd notifies about disks growing
+			# after the system boots.
 netfs_types="nfs:NFS smbfs:SMB" # Net filesystems.
 extra_netfs_types="NO"	# List of network extra filesystem types for delayed
 			# mount at startup (or NO).
diff --git a/libexec/rc/rc.d/Makefile b/libexec/rc/rc.d/Makefile
index cb94380aff32..f6646208dc03 100644
--- a/libexec/rc/rc.d/Makefile
+++ b/libexec/rc/rc.d/Makefile
@@ -24,6 +24,7 @@ CONFS=	DAEMON \
 	fsck \
 	growfs \
 	growfs_fstab \
+	growfs_postboot \
 	hostid \
 	hostid_save \
 	hostname \
diff --git a/libexec/rc/rc.d/growfs_postboot b/libexec/rc/rc.d/growfs_postboot
new file mode 100755
index 000000000000..346c8a0d7162
--- /dev/null
+++ b/libexec/rc/rc.d/growfs_postboot
@@ -0,0 +1,116 @@
+#!/bin/sh
+#
+# Copyright 2026 Colin Percival
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# PROVIDE: growfs_postboot
+# KEYWORD: nostart
+
+# Triggered by /etc/devd/growfs_postboot.conf: When a disk grows, enlarge the
+# final partition to fill the available space (if the disk is partitioned) or
+# enlarge the UFS filesystem or ZFS zpool (if in use for one of those).  Note
+# that in the common case of "a disk is partitioned and one of the partitions
+# contains a filesystem" this script is invoked twice -- the repartitioning
+# done on the first call generates a new devd event which triggers the second
+# invocation.
+#
+# Enable by setting
+# growfs_postboot_enable=YES
+# in /etc/rc.conf.  Note that on systems with growfs_enable=YES, it will often
+# be necessary to set growfs_swap_size=0 in order to disable swap creation;
+# otherwise the "final partition" is the swap space, not the filesystem and
+# growing the disk will not cause the most likely desired results.
+
+. /etc/rc.subr
+
+name="growfs_postboot"
+desc="automatically grow file systems during runtime"
+rcvar="growfs_postboot_enable"
+start_cmd="growfs_postboot_start"
+
+disktype() {
+	sysctl -n kern.geom.conftxt | awk -v dev="$1" '
+	BEGIN {
+		partitioned=0
+		indev=0
+	}
+	{
+		if (dev == $3) {
+			indev=1
+			devlevel=$1
+		} else if (devlevel >= $1) {
+			indev=0
+		} else if (indev == 1) {
+			if ($2 == "PART") {
+				partitioned=1
+			}
+		}
+	}
+	END {
+		if (partitioned == 1) {
+			print "partitioned"
+		} else {
+			print "unpartitioned"
+		}
+	}'
+}
+
+growpart() {
+	# Run 'gpart recover' if GPT
+	if gpart list "$1" | grep -qi 'scheme: GPT'; then
+		gpart recover "$1" || true
+	fi
+
+	# Find the index of the last partition; note that we need to compare
+	# the starting sectors in case partitions are not numbered sensibly.
+	idx=$(gpart backup "$1" | awk '
+		NR == 1 { next }
+		$3 + 0 > start { start = $3 + 0; idx = $1 }
+		END { print idx }')
+	if [ -z "$idx" ]; then
+		echo "Error finding final partition of $1"
+		return 1
+	fi
+
+	# Expand that partition
+	gpart resize -i "$idx" "$1"
+}
+
+growfilesystem() {
+	case $(fstyp -u "/dev/$1" 2>/dev/null) in
+	ufs)	growfs -y "/dev/$1"
+		;;
+	zfs)	zpool list -H -o name |
+		    while read -r pool; do
+			zpool list -vH -o name "$pool" |
+			    grep "^[[:space:]]" |
+			    while read -r dev; do
+				if [ "$dev" = "$1" ]; then
+					zpool online -e "$pool" "$1"
+				fi
+			    done
+		    done
+		;;
+	"")	# Nothing to grow here
+		;;
+	*)	echo "Don't know how to grow filesystem on /dev/$1"
+		;;
+	esac
+}
+
+growfs_postboot_start() {
+	# Run either growpart or growfilesystem depending on whether this is
+	# something which is partitioned or not.
+	case $(disktype "$1") in
+	partitioned)
+		growpart "$1"
+		;;
+	unpartitioned)
+		growfilesystem "$1"
+		;;
+	esac
+}
+
+load_rc_config $name
+run_rc_command "$@"
diff --git a/sbin/devd/Makefile b/sbin/devd/Makefile
index 78b2b039ac6e..864df3604d64 100644
--- a/sbin/devd/Makefile
+++ b/sbin/devd/Makefile
@@ -5,6 +5,7 @@ PACKAGE=devd
 CONFGROUPS=	CONFS DEVD
 CONFS=	devd.conf
 DEVD=	devmatch.conf
+DEVD+=	growfs_postboot.conf
 DEVDDIR=	/etc/devd
 .if ${MK_ACPI} != "no"
 DEVD+=	asus.conf
diff --git a/sbin/devd/growfs_postboot.conf b/sbin/devd/growfs_postboot.conf
new file mode 100644
index 000000000000..0c6b817755ec
--- /dev/null
+++ b/sbin/devd/growfs_postboot.conf
@@ -0,0 +1,8 @@
+# Trigger growfs on GEOM size changes.
+
+notify 200 {
+	match "system" "GEOM";
+	match "subsystem" "DEV";
+	match "type" "SIZECHANGE";
+	action "lockf -k /var/run/growfs_postboot.lock /etc/rc.d/growfs_postboot quietstart $cdev 2>&1 | logger -t growfs_postboot &";
+};
diff --git a/share/man/man5/rc.conf.5 b/share/man/man5/rc.conf.5
index b666345def48..f8a3f4f87ce6 100644
--- a/share/man/man5/rc.conf.5
+++ b/share/man/man5/rc.conf.5
@@ -5159,6 +5159,7 @@ to
 .Xr pf.conf 5 ,
 .Xr firewall 7 ,
 .Xr growfs 7 ,
+.Xr growfs_postboot 7 ,
 .Xr security 7 ,
 .Xr tuning 7 ,
 .Xr accton 8 ,
diff --git a/share/man/man7/Makefile b/share/man/man7/Makefile
index 1462ad1da2a5..54aa60c9067a 100644
--- a/share/man/man7/Makefile
+++ b/share/man/man7/Makefile
@@ -16,6 +16,7 @@ MAN=	arch.7 \
 	firewall.7 \
 	groups.7 \
 	growfs.7 \
+	growfs_postboot.7 \
 	hier.7 \
 	hostname.7 \
 	intro.7 \
diff --git a/share/man/man7/growfs.7 b/share/man/man7/growfs.7
index 43648d8d9f2b..6b026570ceee 100644
--- a/share/man/man7/growfs.7
+++ b/share/man/man7/growfs.7
@@ -124,6 +124,7 @@ must be available prior to running the script.
 .Sh SEE ALSO
 .Xr fstab 5 ,
 .Xr rc.conf 5 ,
+.Xr growfs_postboot 7 ,
 .Xr growfs 8 ,
 .Xr zpool 8
 .Sh HISTORY
diff --git a/share/man/man7/growfs_postboot.7 b/share/man/man7/growfs_postboot.7
new file mode 100644
index 000000000000..147934744edf
--- /dev/null
+++ b/share/man/man7/growfs_postboot.7
@@ -0,0 +1,69 @@
+.\"
+.\" Copyright 2026 Colin Percival
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd July 31, 2026
+.Dt GROWFS_POSTBOOT 7
+.Os
+.Sh NAME
+.Nm growfs_postboot
+.Nd automatically grow file systems during runtime
+.Sh DESCRIPTION
+The
+.Nm
+script expands filesystems automatically.
+Typically it is run via
+.Xr devd 8
+in response to a SIZECHANGE notification.
+It will enlarge the final partition of a partitioned disk; expand a UFS
+file system; or expand a zpool device, as appropriate.
+.Pp
+The following option in
+.Pa /etc/rc.conf
+controls the behavior of
+.Nm :
+.Bl -tag -width "growfs_postboot_enable" -offset indent
+.It Va growfs_postboot_enable
+.Pq Dq Li NO
+If set to
+.Dq Li YES ,
+the script will enlarge partitions and file systems when disks expand.
+.El
+.Pp
+Note that the
+.Xr growfs 7
+startup script will normally place a swap partition at the end of the disk
+containing the root file system, which will prevent the file system growing
+further; as such, in most cases it will be advisable to set
+.Va growfs_swap_size
+to
+.Dq Li 0
+in virtual machine images if
+.Nm
+is being enabled.
+If the system has already booted without that setting, a swap partition may
+have already been created, in which case it must be removed before this script
+can grow the root file system.
+.Sh FILES
+.Bl -tag -compact -width Pa
+.It Pa /etc/rc.conf
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr rc.conf 5 ,
+.Xr growfs 7 ,
+.Xr devd 8 ,
+.Xr growfs 8 ,
+.Xr zpool 8
+.Sh HISTORY
+The
+.Nm
+script first appeared in
+.Fx 16.0 .
+.Sh AUTHORS
+.An Colin Percival Aq Mt cperciva@FreeBSD.org
+.Sh CAVEATS
+I/O may be paused while file systems grow, so this functionality should be used
+with care on systems with strict latency requirements.