Skip to content

Commit

Permalink
zfsbootmenu: manage efivarfs when needed
Browse files Browse the repository at this point in the history
initcpio mounts efivarfs as read-write, dracut doesn't mount it at all.
Inside zfsbootmenu-init, we now check if the system is in EFI mode and
then mount or remount efivarfs as read-only.

When chrooting into a boot environment, efivarfs is mounted in the
chroot as read-only if the zpool is read-only, and read-write if the
zpool is read-write.

When entering a recovery shell, efivarfs is re-mounted as read-write if
efibootmgr is present in the initramfs. Exiting the shell remounts it as
read-only.
  • Loading branch information
zdykstra committed Jan 17, 2023
1 parent c0cee74 commit f78f723
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 10 deletions.
4 changes: 4 additions & 0 deletions docs/online/recovery-shell.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Common Commands

Mount the filesystem at a unique location and print the mount point.

**mount_efivarfs** *mode*

Mount or remount *efivarfs* as read-write or read-only.

**help**

View the online help system.
Expand Down
36 changes: 26 additions & 10 deletions zfsbootmenu/bin/zfs-chroot
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ cleanup() {
umount "${_fs}" || zerror "unable to unmount ${_fs}"
done

mount_efivarfs

_mnt=()
trap - HUP INT QUIT ABRT EXIT
}
Expand Down Expand Up @@ -37,6 +39,19 @@ if ! mountpoint="$( allow_rw=yes mount_zfs "${selected}" )"; then
exit 1
fi

pool="${selected%%/*}"

# Snapshots and read-only pools always produce read-only mounts
if is_snapshot "${selected}" || ! is_writable "${pool}"; then
writemode="$( colorize green "read-only")"
efivarmode="ro"
else
writemode="$( colorize red "read/write")"
efivarmode="rw"
fi

notices+=( "* $( colorize orange "${selected}" ) is mounted ${writemode}" )

# Track submounts so we know how to clean up on exit
trap cleanup HUP INT QUIT ABRT EXIT
_mnt=( "${mountpoint}" )
Expand All @@ -55,20 +70,20 @@ mount -t sysfs sys "${mountpoint}/sys" \
mount -B /dev "${mountpoint}/dev" \
&& _mnt=( "${mountpoint}/dev" "${_mnt[@]}" )


if mount_efivarfs "${efivarmode}" ; then
efivarfs="${mountpoint}/sys/firmware/efi/efivars"
mount_efivarfs "${efivarmode}" "${efivarfs}" \
&& _mnt=( "${efivarfs}" "${_mnt[@]}" )

notices+=( "\n* $( colorize orange "efivarfs" ) is mounted ${writemode}" )
fi

# Not all /dev filesystems have /dev/pts
[ -d "${mountpoint}/dev/pts" ] \
&& mount -t devpts pts "${mountpoint}/dev/pts" \
&& _mnt=( "${mountpoint}/dev/pts" "${_mnt[@]}" )

pool="${selected%%/*}"

# Snapshots and read-only pools always produce read-only mounts
if is_snapshot "${selected}" || ! is_writable "${pool}"; then
writemode="$( colorize green "read-only")"
else
writemode="$( colorize red "read/write")"
fi

_SHELL=
if [ -x "${mountpoint}/bin/bash" ] \
&& chroot "${mountpoint}" /bin/bash -c "exit 0" >/dev/null 2>&1 ; then
Expand All @@ -88,7 +103,8 @@ if [ -z "${_SHELL}" ]; then
exit 1
fi

echo -e "$( colorize orange "${selected}") is mounted ${writemode}, /tmp is shared and read/write\n"
notices+=( "\n* $( colorize orange "/tmp" ) is mounted $( colorize red "read/write")" )
echo -e "${notices[*]}\n"

# regardless of shell, set PS1
# shellcheck disable=SC2086
Expand Down
4 changes: 4 additions & 0 deletions zfsbootmenu/help-files/132/recovery-shell.ansi
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@

Mount the filesystem at a unique location and print the mount point.

mount_efivarfs mode

Mount or remount efivarfs as read-write or read-only.

help

View the online help system.
Expand Down
5 changes: 5 additions & 0 deletions zfsbootmenu/help-files/52/recovery-shell.ansi
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
Mount the filesystem at a unique location and
print the mount point.

mount_efivarfs mode

Mount or remount efivarfs as read-write or
read-only.

help

View the online help system.
Expand Down
4 changes: 4 additions & 0 deletions zfsbootmenu/help-files/92/recovery-shell.ansi
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@

Mount the filesystem at a unique location and print the mount point.

mount_efivarfs mode

Mount or remount efivarfs as read-write or read-only.

help

View the online help system.
Expand Down
11 changes: 11 additions & 0 deletions zfsbootmenu/lib/zfsbootmenu-completions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,14 @@ _zkexec() {

}
complete -F _zkexec zkexec

_mount_efivarfs() {
local STATE
COMPREPLY=()

[ "${#COMP_WORDS[@]}" != "2" ] && return

STATE=("ro" "rw")
COMPREPLY=( $( compgen -W "${STATE[*]}" -- "${COMP_WORDS[1]}" ) )
}
complete -F _mount_efivarfs mount_efivarfs
71 changes: 71 additions & 0 deletions zfsbootmenu/lib/zfsbootmenu-core.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1795,6 +1795,9 @@ emergency_shell() {
type '$( colorize red "exit")' to return to ZFSBootMenu
EOF

command -v efibootmgr >/dev/null 2>&1 && mount_efivarfs "rw"

# -i (interactive) mode will source $HOME/.bashrc
/bin/bash -i

Expand All @@ -1805,6 +1808,9 @@ emergency_shell() {
zdebug "unmounting: ${mp}"
fi
done < /proc/self/mounts

# always remount as read-only
mount_efivarfs
}

# prints: zpool list and zfs property list
Expand Down Expand Up @@ -1876,3 +1882,68 @@ import_zbm_hooks() {

return 0
}

# arg1: directory path
# prints: nothing
# returns: 0 if the directory is a mountpoint, 1 if directory is not a mountpoint

is_mountpoint() {
local mount_path dev path opts

if command -v mountpoint >/dev/null 2>&1; then
mountpoint -q "${1}"
return
fi

if ! mount_path="$(readlink -f "${1}")"; then
zerror "parent of ${1} does not exist"
return 1
fi

if [ ! -r /proc/self/mounts ]; then
zerror "unable to read mount database"
return 1
fi

# shellcheck disable=SC2034
while read -r dev path opts; do
path="$(readlink -f "${path}")" || continue
[ "${path}" = "${mount_path}" ] && return 0
done < /proc/self/mounts

return 1
}

# args: none
# prints: nothing
# returns: 0 if EFI is detected, 1 if not

is_efi_system() {
[ -d /sys/firmware/efi ] && return 0
return 1
}


# arg1: 'ro' or 'rw' to mount or remount efivarfs in that desired mode
# arg2: optional mountpoint
# prints: nothing
# returns: 0 on success, 1 on failure, 2 if unsupported

mount_efivarfs() {
local efivar_state efivar_location

efivar_state="${1:-ro}"
efivar_location="${2:-/sys/firmware/efi/efivars}"

if ! is_efi_system ; then
zdebug "efivarfs unsupported"
return 2
elif is_mountpoint "${efivar_location}" >/dev/null 2>&1 ; then
zdebug "remounting '${efivar_location}' '${efivar_state}'"
# remounting is cheap enough that it's not worth detecting the current state
mount -t efivarfs efivarfs "${efivar_location}" -o "remount,${efivar_state}"
else
zdebug "mounting '${efivar_location}' '${efivar_state}'"
mount -t efivarfs efivarfs "${efivar_location}" -o "${efivar_state}"
fi
}
3 changes: 3 additions & 0 deletions zfsbootmenu/libexec/zfsbootmenu-init
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ unset src sources

mkdir -p "${BASE:=/zfsbootmenu}"

# explicitly mount efivarfs as read-only
mount_efivarfs "ro"

# Attempt to load spl normally
if ! _modload="$( modprobe spl 2>&1 )" ; then
zdebug "${_modload}"
Expand Down

0 comments on commit f78f723

Please sign in to comment.