From 57b253239043c849ea097dfc40e34c6e45ddf75e Mon Sep 17 00:00:00 2001 From: poscat Date: Sun, 15 Dec 2024 12:12:57 +0800 Subject: [PATCH] add mkinitcpio tools for zfs Signed-off-by: poscat --- contrib/mkinitcpio/parse-cmdline | 61 +++++++++++++ contrib/mkinitcpio/sd-zfs | 72 +++++++++++++++ contrib/mkinitcpio/zfs-root-generator | 125 ++++++++++++++++++++++++++ contrib/mkinitcpio/zfs-set-env | 43 +++++++++ 4 files changed, 301 insertions(+) create mode 100755 contrib/mkinitcpio/parse-cmdline create mode 100755 contrib/mkinitcpio/sd-zfs create mode 100755 contrib/mkinitcpio/zfs-root-generator create mode 100644 contrib/mkinitcpio/zfs-set-env diff --git a/contrib/mkinitcpio/parse-cmdline b/contrib/mkinitcpio/parse-cmdline new file mode 100755 index 000000000000..56026b99cb50 --- /dev/null +++ b/contrib/mkinitcpio/parse-cmdline @@ -0,0 +1,61 @@ +#!/usr/bin/awk -f +# input variable: +# command: "mode" or "pool" or "dataset" (default to "mode" when non is given) +# +# mode checks the boot mode, returns "dataset", "import", "import_all" or "none" +# dataset: use a particular dataset +# import: import a specific pool, use bootfs to determine the dataset +# import_all: import all pools, use the first one with a bootfs property +# none: not using zfs for root +# pool checks the root pool, returns the pool name (used with mode "dataset" and "import") +# dataset checks the root dataset, returns the dataset name (used with mode "dataset") +{ + split($0, args, " ") + + for (i in args) { + if (match(args[i], /^root=/)) { + root = substr(args[i], 6) + } + } + + if (root == "zfs") { + # import all pools + if (command == "pool" || command == "dataset") { + print "using auto detection for pool and dataset" > "/dev/stderr" + exit 1 + } else { + printf "import_all" + } + } else if (match(root, /^zfs:[^\/]+$/)) { + # import a specific pool + if (command == "dataset") { + print "using auto detection for dataset" > "/dev/stderr" + exit 1 + } else if (command == "pool") { + printf "%s", substr(root, 5) + } else { + printf "import" + } + } else if (match(root, /^zfs:[^\/]+(\/[^\/]+)+$/)) { + # use a particular dataset + dSet = substr(root, 5) + paths = split(dSet, p, "/") + pool = p[1] + if (command == "pool") { + printf "%s", pool + } else if (command == "dataset") { + printf "%s", dSet + } else { + printf "dataset" + } + } else { + if (command == "pool" || command == "dataset") { + print "not using zfs for root" > "/dev/stderr" + exit 1 + } else { + printf "none" + } + } + + exit 0 +} diff --git a/contrib/mkinitcpio/sd-zfs b/contrib/mkinitcpio/sd-zfs new file mode 100755 index 000000000000..5e4aa48e8287 --- /dev/null +++ b/contrib/mkinitcpio/sd-zfs @@ -0,0 +1,72 @@ +#!/usr/bin/bash + +true || source /usr/lib/initcpio/functions +true || source /usr/lib/initcpio/install/systemd + +build() { + map add_module \ + zfs \ + spl + + map add_file \ + /usr/lib/udev/rules.d/60-zvol.rules \ + /usr/lib/udev/rules.d/69-vdev.rules \ + /usr/lib/udev/rules.d/90-zfs.rules + + map add_binary \ + mount.zfs \ + zfs \ + zpool + + add_systemd_unit "systemd-udev-settle.service" + + { + copied_files=( + /etc/hostid + ) + + for f in "${copied_files[@]}"; do + add_file "$f" + done + + # Include hostid when it's not written to file + if [[ ! -f /etc/hostid ]]; then + AA="$(hostid | cut -b 1,2)" + BB="$(hostid | cut -b 3,4)" + CC="$(hostid | cut -b 5,6)" + DD="$(hostid | cut -b 7,8)" + printf "\x${DD}\x${CC}\x${BB}\x${AA}" > "${BUILDROOT}/etc/hostid" + fi + } + + add_binary /usr/lib/systemd/system-generators/systemd-debug-generator + + # generator and its dependencies + add_binary /usr/lib/zfs/initcpio/zfs-root-generator /usr/lib/systemd/system-generators/ + add_binary /usr/lib/zfs/initcpio/zfs-set-env /usr/bin/ + add_binary /usr/lib/zfs/initcpio/parse-cmdline /usr/bin/ + + add_binary /usr/lib/initcpio/busybox /usr/bin/ + for applet in $(/usr/lib/initcpio/busybox --list); do + add_symlink "/usr/bin/$applet" busybox + done +} + +help() { + cat << EOF +This hook adds ZFS support for systemd-based initrd. +It has a hard dependency on the systemd hook. Since it's implemented +using shell scripts, it still requires busybox and will copy it to +the initrd if you don't have the base hook. + +To use this hook, simply add it to your HOOKS array in mkinitcpio.conf. +You'll also need to change the kernel command line. The supported cmdline +formats are: + 1. root=zfs, which imports all pools in initrd, searches for the + first pool with the bootfs property set, and then mounts bootfs as root. + 2. root=zfs:poolname, which imports only the specified pool and then mounts + the pool's bootfs as root. + 3. root=zfs:poolname/dataset, which imports only the specified pool and then + mounts the specified dataset as root. +EOF +} diff --git a/contrib/mkinitcpio/zfs-root-generator b/contrib/mkinitcpio/zfs-root-generator new file mode 100755 index 000000000000..d12d1a4b5d0b --- /dev/null +++ b/contrib/mkinitcpio/zfs-root-generator @@ -0,0 +1,125 @@ +#!/usr/bin/sh +# shellcheck shell=ash +# cmdline format: +# +# use a particular dataset: +# root=zfs:pool/dataset +# use the bootfs property of a pool: +# root=zfs:pool +# import all pools and use the first one with a bootfs property: +# root=zfs +set -e + +# usage: klog "message" [level] +klog() { + if [ -n "$DEBUG_CMDLINE" ]; then + return + fi + echo "<${2:-6}>zfs-root-generator: $1" | tee /dev/kmsg +} + +write_sysroot_override_unit() { + klog "writing sysroot.mount override" + mkdir -p "$GENERATOR_DIR"/sysroot.mount.d + { + echo "[Unit]" + echo "After=zfs-import.target" + echo + echo "[Mount]" + echo "PassEnvironment=ROOT_DATASET" + echo "Type=zfs" + echo 'What=${ROOT_DATASET}' + echo 'Options=zfsutil' + } > "$GENERATOR_DIR"/sysroot.mount.d/zfs-override.conf +} + +write_set_env_unit() { + klog "generating zfs-set-env.service" + { + echo "[Unit]" + echo "Description=Set systemd environment variables for ZFS sysroot.mount unit" + echo "DefaultDependencies=no" + echo "Before=sysroot.mount" + echo + echo "[Service]" + echo "Type=oneshot" + echo "RemainAfterExit=yes" + echo "ExecStart=/usr/bin/zfs-set-env" + } > "$GENERATOR_DIR"/zfs-set-env.service +} + +enable_unit() { + local unit="$1" + klog "enabling $unit" + mkdir -p "$GENERATOR_DIR"/initrd-root-device.target.wants/ + ln -fs "$GENERATOR_DIR"/"$unit" "$GENERATOR_DIR"/initrd-root-device.target.wants/ +} + +GENERATOR_DIR="$1" +[ -n "$GENERATOR_DIR" ] || { + klog "impossible: no generator directory specified" 2 + exit 1 +} +CMDLINE=${DEBUG_CMDLINE:-/proc/cmdline} + +mode=$(parse-cmdline "$CMDLINE") +klog "mode: $mode" + +case $mode in + import_all) + write_sysroot_override_unit + + write_set_env_unit + enable_unit zfs-set-env.service + + klog "generating zfs-import-all.service" + { + echo "[Unit]" + echo "Description=Import all ZFS pools" + echo "DefaultDependencies=no" + echo "Wants=systemd-udev-settle.service" + echo "After=systemd-udev-settle.service cryptsetup.target" + echo "Before=sysroot.mount zfs-set-env.service" + echo + echo "[Service]" + echo "Type=oneshot" + echo "RemainAfterExit=yes" + echo "ExecStart=/usr/bin/zpool import -o cachefile=none -aN" + } > "$GENERATOR_DIR"/zfs-import-all.service + enable_unit zfs-import-all.service + ;; + import|dataset) + write_sysroot_override_unit + + write_set_env_unit + enable_unit zfs-set-env.service + + pool=$(parse-cmdline -v command=pool "$CMDLINE") + klog "generating zfs-import-root-pool.service for pool $pool" + { + echo "[Unit]" + echo "Description=Import ZFS pool $pool for rootfs" + echo "DefaultDependencies=no" + echo "Requires=systemd-udev-settle.service" + echo "After=systemd-udev-settle.service cryptsetup.target" + echo "Before=sysroot.mount zfs-set-env.service" + echo + echo "[Service]" + echo "Type=oneshot" + echo "RemainAfterExit=yes" + echo "ExecStart=/usr/bin/zpool import -o cachefile=none -N \"$pool\"" + } > "$GENERATOR_DIR"/zfs-import-root-pool.service + enable_unit zfs-import-root-pool.service + ;; + none) + klog "not using zfs for root: stopping" + exit 0 + ;; + *) + klog "impossible: unknown mode $mode" 2 + exit 1 + ;; +esac + +klog "finished generating" +exit 0 diff --git a/contrib/mkinitcpio/zfs-set-env b/contrib/mkinitcpio/zfs-set-env new file mode 100644 index 000000000000..d0c184bc4a9b --- /dev/null +++ b/contrib/mkinitcpio/zfs-set-env @@ -0,0 +1,43 @@ +#!/usr/bin/sh +# shellcheck shell=ash +set -e + +CMDLINE=${DEBUG_CMDLINE:-/proc/cmdline} + +# usage: log "message" [level] +log() { + echo "<${2:-6}>$1" +} + +mode=$(parse-cmdline "$CMDLINE") +log "mode: $mode" + +case $mode in + import_all) + if dSet=$(zpool list -Ho bootfs | grep -m1 -vFx -); then + : + else + log "no pool with bootfs property found" 2 + exit 1 + fi + ;; + import) + pool=$(parse-cmdline -v command=pool "$CMDLINE") + if dSet=$(zpool list -Ho bootfs "$pool" | grep -m1 -vFx -); then + : + else + log "no bootfs property found for pool $pool" 2 + exit 1 + fi + ;; + dataset) + dSet=$(parse-cmdline -v command=dataset "$CMDLINE") + ;; + none) + log "no zfs root specified" 2 + exit 1 + ;; +esac + +log "setting ROOT_DATASET to $dSet" +exec systemctl set-environment ROOT_DATASET="$dSet"