diff options
Diffstat (limited to 'modules.d/95nfs')
-rwxr-xr-x | modules.d/95nfs/module-setup.sh | 138 | ||||
-rwxr-xr-x | modules.d/95nfs/nfs-lib.sh | 157 | ||||
-rwxr-xr-x | modules.d/95nfs/nfs-start-rpc.sh | 23 | ||||
-rwxr-xr-x | modules.d/95nfs/nfsroot-cleanup.sh | 29 | ||||
-rwxr-xr-x | modules.d/95nfs/nfsroot.sh | 30 | ||||
-rwxr-xr-x | modules.d/95nfs/parse-nfsroot.sh | 128 |
6 files changed, 505 insertions, 0 deletions
diff --git a/modules.d/95nfs/module-setup.sh b/modules.d/95nfs/module-setup.sh new file mode 100755 index 0000000..16bafe3 --- /dev/null +++ b/modules.d/95nfs/module-setup.sh @@ -0,0 +1,138 @@ +#!/bin/bash + +# return value: +# 'nfs4': Only nfs4 founded +# 'nfs': nfs with version < 4 founded +# '': No nfs founded +get_nfs_type() { + local _nfs _nfs4 + + for fs in "${host_fs_types[@]}"; do + [[ $fs == "nfs" ]] && _nfs=1 + [[ $fs == "nfs3" ]] && _nfs=1 + [[ $fs == "nfs4" ]] && _nfs4=1 + done + + [[ "$_nfs" ]] && echo "nfs" && return + [[ "$_nfs4" ]] && echo "nfs4" && return +} + +# called by dracut +check() { + # If our prerequisites are not met, fail anyways. + require_any_binary rpcbind portmap || return 1 + require_binaries rpc.statd mount.nfs mount.nfs4 umount sed chmod chown || return 1 + + [[ $hostonly ]] || [[ $mount_needs ]] && { + [[ "$(get_nfs_type)" ]] && return 0 + return 255 + } + return 0 +} + +# called by dracut +depends() { + # We depend on network modules being loaded + echo network +} + +# called by dracut +installkernel() { + hostonly=$(optional_hostonly) instmods '=net/sunrpc' '=fs/nfs' ipv6 nfs_acl nfs_layout_nfsv41_files +} + +cmdline() { + local nfs_device + local nfs_options + local nfs_root + local nfs_address + local lookup + + ### nfsroot= ### + nfs_device=$(findmnt -t nfs4 -n -o SOURCE /) + if [ -n "$nfs_device" ]; then + nfs_root="root=nfs4:$nfs_device" + else + nfs_device=$(findmnt -t nfs -n -o SOURCE /) + [ -z "$nfs_device" ] && return + nfs_root="root=nfs:$nfs_device" + fi + nfs_options=$(findmnt -t nfs4,nfs -n -o OPTIONS /) + [ -n "$nfs_options" ] && nfs_root="$nfs_root:$nfs_options" + echo "$nfs_root" + + ### ip= ### + if [[ $nfs_device =~ [0-9]*\.[0-9]*\.[0-9]*.[0-9]* ]] || [[ $nfs_device =~ \[[^]]*\] ]]; then + nfs_address="${nfs_device%%:*}" + else + lookup=$(host "${nfs_device%%:*}" | grep " address " | head -n1) + nfs_address=${lookup##* } + fi + + [[ $nfs_address ]] || return + ip_params_for_remote_addr "$nfs_address" +} + +# called by dracut +install() { + local _nsslibs + inst_multiple -o rpc.idmapd mount.nfs mount.nfs4 umount sed /etc/netconfig chmod chown "$tmpfilesdir/rpcbind.conf" + inst_multiple -o /etc/idmapd.conf + inst_multiple -o /etc/services /etc/nsswitch.conf /etc/rpc /etc/protocols + inst_multiple -o /usr/etc/services /usr/etc/nsswitch.conf /usr/etc/rpc /usr/etc/protocols + + if [[ $hostonly_cmdline == "yes" ]]; then + local _netconf + _netconf="$(cmdline)" + [[ $_netconf ]] && printf "%s\n" "$_netconf" >> "${initdir}/etc/cmdline.d/95nfs.conf" + fi + + if [[ -f $dracutsysrootdir/lib/modprobe.d/nfs.conf ]]; then + inst_multiple /lib/modprobe.d/nfs.conf + else + [[ -d $initdir/etc/modprobe.d ]] || mkdir -p "$initdir"/etc/modprobe.d + echo "alias nfs4 nfs" > "$initdir"/etc/modprobe.d/nfs.conf + fi + + inst_libdir_file 'libnfsidmap_nsswitch.so*' 'libnfsidmap/*.so' 'libnfsidmap*.so*' + + _nsslibs=$( + cat "$dracutsysrootdir"/{,usr/}etc/nsswitch.conf 2> /dev/null \ + | sed -e '/^#/d' -e 's/^.*://' -e 's/\[NOTFOUND=return\]//' \ + | tr -s '[:space:]' '\n' | sort -u | tr -s '[:space:]' '|' + ) + _nsslibs=${_nsslibs#|} + _nsslibs=${_nsslibs%|} + + inst_libdir_file -n "$_nsslibs" 'libnss_*.so*' + + inst_hook cmdline 90 "$moddir/parse-nfsroot.sh" + inst_hook pre-udev 99 "$moddir/nfs-start-rpc.sh" + inst_hook cleanup 99 "$moddir/nfsroot-cleanup.sh" + inst "$moddir/nfsroot.sh" "/sbin/nfsroot" + + # For strict hostonly, only install rpcbind for NFS < 4 + if [[ $hostonly_mode != "strict" ]] || [[ "$(get_nfs_type)" != "nfs4" ]]; then + inst_multiple -o portmap rpcbind rpc.statd + fi + + inst "$moddir/nfs-lib.sh" "/lib/nfs-lib.sh" + mkdir -m 0755 -p "$initdir/var/lib/nfs" + mkdir -m 0755 -p "$initdir/var/lib/nfs/rpc_pipefs" + mkdir -m 0770 -p "$initdir/var/lib/rpcbind" + [ -d "/var/lib/nfs/statd/sm" ] && mkdir -m 0755 -p "$initdir/var/lib/nfs/statd/sm" + [ -d "/var/lib/nfs/sm" ] && mkdir -m 0755 -p "$initdir/var/lib/nfs/sm" + + # Rather than copy the passwd file in, just set a user for rpcbind + # We'll save the state and restart the daemon from the root anyway + grep -E '^nfsnobody:|^rpc:|^rpcuser:' "$dracutsysrootdir"/etc/passwd >> "$initdir/etc/passwd" + grep -E '^nogroup:|^rpc:|^nobody:' "$dracutsysrootdir"/etc/group >> "$initdir/etc/group" + + # rpc user needs to be able to write to this directory to save the warmstart + # file + chmod 770 "$initdir/var/lib/rpcbind" + grep -q '^rpc:' "$dracutsysrootdir"/etc/passwd \ + && grep -q '^rpc:' "$dracutsysrootdir"/etc/group + + dracut_need_initqueue +} diff --git a/modules.d/95nfs/nfs-lib.sh b/modules.d/95nfs/nfs-lib.sh new file mode 100755 index 0000000..f000671 --- /dev/null +++ b/modules.d/95nfs/nfs-lib.sh @@ -0,0 +1,157 @@ +#!/bin/sh + +type getarg > /dev/null 2>&1 || . /lib/dracut-lib.sh +. /lib/net-lib.sh + +# TODO: make these things not pollute the calling namespace + +# nfs_to_var NFSROOT [NETIF] +# use NFSROOT to set $nfs, $server, $path, and $options. +# NFSROOT is something like: nfs[4]:<server>:/<path>[:<options>|,<options>] +# NETIF is used to get information from DHCP options, if needed. +nfs_to_var() { + # Unfortunately, there's multiple styles of nfs "URL" in use, so we need + # extra functions to parse them into $nfs, $server, $path, and $options. + # FIXME: local netif=${2:-$netif}? + case "$1" in + nfs://*) rfc2224_nfs_to_var "$1" ;; + nfs:*[*) anaconda_nfsv6_to_var "$1" ;; + nfs:*:*:/*) anaconda_nfs_to_var "$1" ;; + *) nfsroot_to_var "$1" ;; + esac + # if anything's missing, try to fill it in from DHCP options + if [ -z "$server" ] || [ -z "$path" ]; then nfsroot_from_dhcp "$2"; fi + # if there's a "%s" in the path, replace it with the hostname/IP + if strstr "$path" "%s"; then + local node="" + read -r node < /proc/sys/kernel/hostname + [ "$node" = "(none)" ] && node=$(get_ip "$2") + path=${path%%%s*}$node${path#*%s} # replace only the first %s + fi +} + +# root=nfs:[<server-ip>:]<root-dir>[:<nfs-options>] +# root=nfs4:[<server-ip>:]<root-dir>[:<nfs-options>] +nfsroot_to_var() { + # strip nfs[4]: + local arg="$*:" + nfs="${arg%%:*}" + arg="${arg##"$nfs":}" + + # check if we have a server + if strstr "$arg" ':/'; then + server="${arg%%:/*}" + arg="/${arg##*:/}" + fi + + path="${arg%%:*}" + + # rest are options + options="${arg##"$path"}" + # strip leading ":" + options="${options##:}" + # strip ":" + options="${options%%:}" + + # Does it really start with '/'? + [ -n "${path%%/*}" ] && path="error" + + #Fix kernel legacy style separating path and options with ',' + if [ "$path" != "${path#*,}" ]; then + options=${path#*,} + path=${path%%,*} + fi +} + +# RFC2224: nfs://<server>[:<port>]/<path> +rfc2224_nfs_to_var() { + nfs="nfs" + server="${1#nfs://}" + path="/${server#*/}" + server="${server%%/*}" + server="${server%%:}" # anaconda compat (nfs://<server>:/<path>) + local port="${server##*:}" + [ "$port" != "$server" ] && options="port=$port" +} + +# Anaconda-style path with options: nfs:<options>:<server>:/<path> +# (without mount options, anaconda is the same as dracut) +anaconda_nfs_to_var() { + nfs="nfs" + options="${1#nfs:}" + server="${options#*:}" + server="${server%:/*}" + options="${options%%:*}" + path="/${1##*:/}" +} + +# IPv6 nfs path will be treated separately +anaconda_nfsv6_to_var() { + nfs="nfs" + path="$1" + options="${path#*:/}" + path="/${options%%:*}" + server="${1#*nfs:}" + if str_starts "$server" '['; then + server="${server%:/*}" + options="${options#*:*}" + else + server="${server%:/*}" + options="${server%%:*}" + server="${server#*:}" + fi +} + +# nfsroot_from_dhcp NETIF +# fill in missing server/path from DHCP options. +nfsroot_from_dhcp() { + local f + for f in /tmp/net.$1.override /tmp/dhclient.$1.dhcpopts; do + # shellcheck disable=SC1090 + [ -f "$f" ] && . "$f" + done + [ -n "$new_root_path" ] && nfsroot_to_var "$nfs:$new_root_path" + [ -z "$path" ] && [ "$(getarg root=)" = "/dev/nfs" ] && path=/tftpboot/%s + [ -z "$server" ] && server=$srv + [ -z "$server" ] && server=$new_next_server + [ -z "$server" ] && server=$new_dhcp_server_identifier + [ -z "$server" ] && server=${new_root_path%%:*} +} + +# Look through $options, fix "rw"/"ro", move "lock"/"nolock" to $nfslock +munge_nfs_options() { + local f="" flags="" nfsrw="ro" OLDIFS="$IFS" + IFS=, + for f in $options; do + case $f in + ro | rw) nfsrw=$f ;; + lock | nolock) nfslock=$f ;; + *) flags=${flags:+$flags,}$f ;; + esac + done + IFS="$OLDIFS" + + # Override rw/ro if set on cmdline + getarg ro > /dev/null && nfsrw=ro + getarg rw > /dev/null && nfsrw=rw + + options=$nfsrw${flags:+,$flags} +} + +# mount_nfs NFSROOT MNTDIR [NETIF] +mount_nfs() { + local nfsroot="$1" mntdir="$2" netif="$3" + local nfs="" server="" path="" options="" + nfs_to_var "$nfsroot" "$netif" + munge_nfs_options + if [ "$nfs" = "nfs4" ]; then + options=$options${nfslock:+,$nfslock} + else + # NFSv{2,3} doesn't support using locks as it requires a helper to + # transfer the rpcbind state to the new root + [ "$nfslock" = "lock" ] \ + && warn "Locks unsupported on NFSv{2,3}, using nolock" 1>&2 + options=$options,nolock + fi + mount -t "$nfs" -o"$options" "$server:$path" "$mntdir" +} diff --git a/modules.d/95nfs/nfs-start-rpc.sh b/modules.d/95nfs/nfs-start-rpc.sh new file mode 100755 index 0000000..52f6a4d --- /dev/null +++ b/modules.d/95nfs/nfs-start-rpc.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +if load_fstype sunrpc rpc_pipefs; then + [ ! -d /var/lib/nfs/rpc_pipefs/nfs ] \ + && mount -t rpc_pipefs rpc_pipefs /var/lib/nfs/rpc_pipefs + + # Start rpcbind or rpcbind + # FIXME occasionally saw 'rpcbind: fork failed: No such device' -- why? + command -v portmap > /dev/null && [ -z "$(pidof portmap)" ] && portmap + if command -v rpcbind > /dev/null && [ -z "$(pidof rpcbind)" ]; then + mkdir -p /run/rpcbind + chown rpc:rpc /run/rpcbind + rpcbind + fi + + # Start rpc.statd as mount won't let us use locks on a NFSv4 + # filesystem without talking to it. NFSv4 does locks internally, + # rpc.lockd isn't needed + command -v rpc.statd > /dev/null && [ -z "$(pidof rpc.statd)" ] && rpc.statd + command -v rpc.idmapd > /dev/null && [ -z "$(pidof rpc.idmapd)" ] && rpc.idmapd +else + warn 'Kernel module "sunrpc" not in the initramfs, or support for filesystem "rpc_pipefs" missing!' +fi diff --git a/modules.d/95nfs/nfsroot-cleanup.sh b/modules.d/95nfs/nfsroot-cleanup.sh new file mode 100755 index 0000000..d99519b --- /dev/null +++ b/modules.d/95nfs/nfsroot-cleanup.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +type incol2 > /dev/null 2>&1 || . /lib/dracut-lib.sh + +[ -f /tmp/nfs.rpc_pipefs_path ] && read -r rpcpipefspath < /tmp/nfs.rpc_pipefs_path +[ -z "$rpcpipefspath" ] && rpcpipefspath=var/lib/nfs/rpc_pipefs + +pid=$(pidof rpc.statd) +[ -n "$pid" ] && kill "$pid" + +pid=$(pidof rpc.idmapd) +[ -n "$pid" ] && kill "$pid" + +pid=$(pidof rpcbind) +[ -n "$pid" ] && kill "$pid" + +if incol2 /proc/mounts /var/lib/nfs/rpc_pipefs; then + # try to create the destination directory + [ -d "$NEWROOT"/$rpcpipefspath ] \ + || mkdir -m 0755 -p "$NEWROOT"/$rpcpipefspath 2> /dev/null + + if [ -d "$NEWROOT"/$rpcpipefspath ]; then + # mount --move does not seem to work??? + mount --bind /var/lib/nfs/rpc_pipefs "$NEWROOT"/$rpcpipefspath + umount /var/lib/nfs/rpc_pipefs 2> /dev/null + else + umount /var/lib/nfs/rpc_pipefs 2> /dev/null + fi +fi diff --git a/modules.d/95nfs/nfsroot.sh b/modules.d/95nfs/nfsroot.sh new file mode 100755 index 0000000..794e0d8 --- /dev/null +++ b/modules.d/95nfs/nfsroot.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +type getarg > /dev/null 2>&1 || . /lib/dracut-lib.sh +. /lib/nfs-lib.sh + +[ "$#" = 3 ] || exit 1 + +# root is in the form root=nfs[4]:[server:]path[:options], either from +# cmdline or dhcp root-path +netif="$1" +root="$2" +NEWROOT="$3" + +nfs_to_var "$root" "$netif" +[ -z "$server" ] && die "Required parameter 'server' is missing" + +mount_nfs "$root" "$NEWROOT" "$netif" && { + [ -e /dev/root ] || ln -s null /dev/root + [ -e /dev/nfs ] || ln -s null /dev/nfs +} + +[ -f "$NEWROOT"/etc/fstab ] && cat "$NEWROOT"/etc/fstab > /dev/null + +# inject new exit_if_exists +# shellcheck disable=SC2016 +echo 'settle_exit_if_exists="--exit-if-exists=/dev/root"; rm -- "$job"' > "$hookdir"/initqueue/nfs.sh +# force udevsettle to break +: > "$hookdir"/initqueue/work + +need_shutdown diff --git a/modules.d/95nfs/parse-nfsroot.sh b/modules.d/95nfs/parse-nfsroot.sh new file mode 100755 index 0000000..0c8dbbb --- /dev/null +++ b/modules.d/95nfs/parse-nfsroot.sh @@ -0,0 +1,128 @@ +#!/bin/sh +# +# Preferred format: +# root=nfs[4]:[server:]path[:options] +# +# This syntax can come from DHCP root-path as well. +# +# Legacy format: +# root=/dev/nfs nfsroot=[server:]path[,options] +# +# In Legacy root=/dev/nfs mode, if the 'nfsroot' parameter is not given +# on the command line or is empty, the dhcp root-path is used as +# [server:]path[:options] or the default "/tftpboot/%s" will be used. +# +# If server is unspecified it will be pulled from one of the following +# sources, in order: +# static ip= option on kernel command line +# DHCP next-server option +# DHCP server-id option +# DHCP root-path option +# +# NFSv4 is only used if explicitly requested with nfs4: prefix, otherwise +# NFSv3 is used. +# + +type getarg > /dev/null 2>&1 || . /lib/dracut-lib.sh +. /lib/nfs-lib.sh + +# This script is sourced, so root should be set. But let's be paranoid +[ -z "$root" ] && root=$(getarg root=) +[ -z "$nfsroot" ] && nfsroot=$(getarg nfsroot=) + +[ -n "$netroot" ] && oldnetroot="$netroot" + +# netroot= cmdline argument must be ignored, but must be used if +# we're inside netroot to parse dhcp root-path +if [ -n "$netroot" ]; then + for n in $(getargs netroot=); do + [ "$n" = "$netroot" ] && break + done + if [ "$n" = "$netroot" ]; then + #warn "Ignoring netroot argument for NFS" + netroot=$root + fi +else + netroot=$root +fi + +# LEGACY: nfsroot= is valid only if root=/dev/nfs +if [ -n "$nfsroot" ]; then + # @deprecated + warn "Argument nfsroot is deprecated and might be removed in a future release. See 'man dracut.kernel' for more information." + if [ "$(getarg root=)" != "/dev/nfs" ]; then + die "Argument nfsroot only accepted for legacy root=/dev/nfs" + fi + netroot=nfs:$nfsroot +fi + +case "$netroot" in + /dev/nfs) netroot=nfs ;; + /dev/*) + if [ -n "$oldnetroot" ]; then + netroot="$oldnetroot" + else + unset netroot + fi + return + ;; + # LEGACY: root=<server-ip>:/<path + [0-9]*:/* | [0-9]*\.[0-9]*\.[0-9]*[!:] | /*) + netroot=nfs:$netroot + ;; +esac + +# Continue if nfs +case "${netroot%%:*}" in + nfs | nfs4 | /dev/nfs) ;; + *) + if [ -n "$oldnetroot" ]; then + netroot="$oldnetroot" + else + unset netroot + fi + return + ;; +esac + +# Check required arguments + +if nfsdomain=$(getarg rd.nfs.domain -d rd_NFS_DOMAIN); then + if [ -f /etc/idmapd.conf ]; then + sed -i -e \ + "s/^[[:space:]#]*Domain[[:space:]]*=.*/Domain = $nfsdomain/g" \ + /etc/idmapd.conf + fi + # and even again after the sed, in case it was not yet specified + echo "Domain = $nfsdomain" >> /etc/idmapd.conf +fi + +nfsroot_to_var "$netroot" +[ "$path" = "error" ] && die "Argument nfsroot must contain a valid path!" + +# Set fstype, might help somewhere +fstype=${nfs#/dev/} + +# Rewrite root so we don't have to parse this ugliness later on again +netroot="$fstype:$server:$path:$options" + +# If we don't have a server, we need dhcp +if [ -z "$server" ]; then + # shellcheck disable=SC2034 + DHCPORSERVER="1" +fi + +# Done, all good! +# shellcheck disable=SC2034 +rootok=1 + +# Shut up init error check or make sure that block parser won't get +# confused by having /dev/nfs[4] +root="$fstype" + +# shellcheck disable=SC2016 +echo '[ -e $NEWROOT/proc ]' > "$hookdir"/initqueue/finished/nfsroot.sh + +mkdir -p /var/lib/rpcbind +chown rpc:rpc /var/lib/rpcbind +chmod 770 /var/lib/rpcbind |