Re: ramdisk-zfsroot: pool detection and configurable boot fs

jornane <[email protected]>
Newsgroups gmane.os.netbsd.current
Message-ID <[email protected]>
I improved upon the rc script, building on the work on the existing rc
script by roy.  I'm currently using this in my setup.  It's backwards
compatible with the existing script (attempts to boot from rpool/ROOT).

It mimics how FreeBSD picks it's boot filesystem, using the bootfs
property, which FreeBSD uses to support boot environments [2].  It's not
exactly the same, mainly because we are not able to read the kernel from
ZFS which is why we still need a separate boot partition.

The behaviour differs from FreeBSD in that FreeBSDs gptzfsboot picks the
first ZFS pool on the same disk from which gptzfsboot itself was loaded.
In our case we don't know which disk that was, so we rely on the order 
that zpool import returns pools, which is not deterministic.  Instead,
we require the pool to have set a bootfs, so that we can skip over 
non-bootable pools.
This behaviour could be improved upon by detecting if the contents of
the filesystem are bootable for a NetBSD kernel.  I'm not certain what
would be a good way of doing that.

Changes from 1.5 [3]:
- Uses the zpool bootfs property to determine where to boot from
- Falls back to rpool/ROOT if no bootfs property found anywhere
- Prints diagnostic information during boot, so if you're dropped to 
shell you know where to start searching for the issue
- Graceful handling of pools already imported by the operator after a 
session in single user mode

[2] https://man.freebsd.org/cgi/man.cgi?query=gptzfsboot
[3] 
https://cvsweb.netbsd.org/bsdweb.cgi/src/distrib/common/zfsroot.rc?rev=1.5

Attached file to src/distrib/common/zfsroot.rc
zfsroot.rc (text/plain, 3.9 KB)
#!/bin/sh
#
#       $NetBSD: zfsroot.rc,v 1.6 2026/08/24 19:42:03 jordej Exp $

# - boot.cfg loads ramdisk, this script is /etc/rc in the ramdisk.
# - Required kernel modules either on this ramdisk OR loaded by boot.cfg
#   - solaris
#   - zfs
# - One of the available pools has a bootfs property set, OR,
#   backwards compatible with version 1.5 of this script,
#   is named rpool/ROOT.
# - First matching pool is chosen, operator should take care to only
#   have one pool available that has a bootfs set.
# - A future version of this script should require setting bootfs, and
#   drop support for the filesystem named ROOT, as this may conflict
#   with FreeBSD's practice of making an empty ROOT filesystem with the
#   actual bootable filesystems underneath.
#
# This script iterates over all available pools.  Every candidate pool
# is imported read-only (-o readonly) without mounting (-N), then
# queried for a root filesystem (bootfs property).  The first matching
# pool is imported read/write, the bootfs is mounted as /altroot and the
# init.root sysctl is set to /altroot.  After that, the NetBSD rc in the
# altroot is responsible for mounting the rest of the ZFS pool, through
# zfs=YES in /etc/rc.conf.

# List both imported and non-imported pools, imported ones first.
# Normally at this point, no pools are imported yet.  But if single user
# mode has been used for any reason, the correct pool might have been
# imported by the operator already, and "zpool import" only lists
# importable pools, not pools that are imported.

pools="$(/sbin/zpool list -Honame)
$(/sbin/zpool import | /usr/bin/sed -ne'/^ *pool: / s/^.*: //p')"

bootfs=
fbbootfs=
fbpool=
echo "zfsroot: Pools found:" $pools
for pool in $pools
do
	if ! /sbin/zpool list "$pool" >/dev/null 2>&1
	then
		echo "zfsroot: $pool: Importing readonly"
		/sbin/zpool import \
			-o readonly \
			-o cachefile=none \
			-f -N "$pool" \
		|| continue
	fi

	# "{zpool,zfs} get" returns "-" if the bootfs value is not set
	bootfs="$(/sbin/zpool get -Hovalue bootfs "$pool")"
	[ - = "$bootfs" ] && bootfs=

	# If a bootfs is returned, zfs guarantees it's a filesystem,
	# but we still have to figure out if we can mount it.
	# "mount -t zfs" doesn't actually care about canmount (we do!)
	# "mount -t zfs" refuses if mountpoint is not "legacy"
	[ -n "$bootfs" ] && \
	[ legacy = "$(/sbin/zfs get -Hovalue mountpoint "$bootfs" )" ] \
	&& [ on = "$(/sbin/zfs get -Hovalue canmount "$bootfs" )" ] \
	&& break

	# Print diagnostics in case bootfs exists but is not viable
	[ -n "$bootfs" ] \
	&& /sbin/zfs list -o name,refer,mountpoint,canmount "$bootfs"

	# Fallback to rpool/ROOT?
	[ rpool = "$pool" ] && [ \
	legacy = "$(/sbin/zfs get -Hovalue mountpoint "$pool/ROOT" )" \
	] && fbpool="$pool" && fbbootfs="$pool/ROOT"

	defer=
	case "$fbbootfs" in "$pool/"*)
		defer=", deferring $fbbootfs"
	esac
	[ -n "$bootfs" ] \
	&& echo "zfsroot: $pool: invalid bootfs $bootfs$defer" \
	|| echo "zfsroot: $pool: bootfs not set$defer"

	/sbin/zpool export "$pool"
	bootfs=
done

# Fallback to rpool/ROOT?
[ -z "$bootfs" ] && bootfs="$fbbootfs" && pool="$fbpool"

if [ -z "$bootfs" ]
then
	echo "zfsroot: No suitable ZFS pool found"
	exit 2
fi

echo "zfsroot: $pool: Found bootfs: $bootfs"
if [ off != "$(/sbin/zpool get -Hovalue readonly "$pool" 2>/dev/null)" ]
then
	echo "zfsroot: $pool: Re-importing read/write"
	/sbin/zpool list "$pool" >/dev/null 2>&1 \
	&& /sbin/zpool export "$pool"
	/sbin/zpool import -f -N -o cachefile=none "$pool" || exit $?
fi
/sbin/zfs list -o name,refer,mountpoint,canmount "$bootfs"

# Anything still mounted would become unavailable once we set init.root
/sbin/zfs unmount -f -a

echo "zfsroot: $pool: Mounting $bootfs to /altroot"
/bin/mkdir -p /altroot
/sbin/mount -t zfs "$bootfs" /altroot

/sbin/sysctl -w init.root=/altroot

# Setup some stuff in case things go south and we drop to the shell
export HOME=/
export PATH=/sbin:/bin:/usr/sbin:/usr/bin
umask 022
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.