summaryrefslogtreecommitdiffstats
path: root/debian/scripts/suspend/cryptsetup-suspend-wrapper
diff options
context:
space:
mode:
Diffstat (limited to 'debian/scripts/suspend/cryptsetup-suspend-wrapper')
-rw-r--r--debian/scripts/suspend/cryptsetup-suspend-wrapper320
1 files changed, 320 insertions, 0 deletions
diff --git a/debian/scripts/suspend/cryptsetup-suspend-wrapper b/debian/scripts/suspend/cryptsetup-suspend-wrapper
new file mode 100644
index 0000000..953196c
--- /dev/null
+++ b/debian/scripts/suspend/cryptsetup-suspend-wrapper
@@ -0,0 +1,320 @@
+#!/bin/sh
+
+# Wrapper for cryptsetup-suspend(7)
+#
+# Copyright © 2019-2020 Tim <tim@systemli.org>
+# © 2019-2020 Jonas Meurer <jonas@freesources.org>
+# © 2020-2022 Guilhem Moulin <guilhem@debian.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+set -ue
+PATH="/usr/sbin:/usr/bin:/sbin:/bin"
+export PATH
+
+# import cryptsetup shell functions
+[ -f /lib/cryptsetup/functions ] || return 0
+. /lib/cryptsetup/functions
+
+INITRAMFS_MNT="/run/cryptsetup/cryptsetup-suspend-initramfs"
+SYSTEM_SLEEP_PATH="/lib/systemd/system-sleep"
+CONFIG_FILE="/etc/cryptsetup/suspend.conf"
+unset -v INITRAMFS_DIR
+
+read_config() {
+ # define defaults
+ export UNLOCK_SESSIONS="false"
+ export KEEP_INITRAMFS="false"
+
+ # read config file if it exists
+ # shellcheck source=/etc/cryptsetup/suspend.conf
+ [ -f "$CONFIG_FILE" ] && . "$CONFIG_FILE" || true
+}
+
+# run_dir ARGS...
+# Run all executable scripts in directory SYSTEM_SLEEP_PATH with arguments ARGS
+# mimic systemd behavior
+run_dir() {
+ [ -d "$SYSTEM_SLEEP_PATH" ] || return 0
+ find "$SYSTEM_SLEEP_PATH" -type f -executable -execdir {} "$@" \;
+}
+
+log_error() {
+ # arg1 should be message
+ echo "Error: $1" | systemd-cat -t cryptsetup-suspend -p err
+ echo "Error: $1" >&2
+}
+
+mount_initramfs() {
+ local k v u IFS MemAvailable=0 SwapFree=0 new="n"
+ # update-initramfs(8) hardcodes /boot also: there is a `-b bootdir`
+ # option but no config file to put it to
+ local INITRAMFS="/boot/initrd.img-$(uname -r)" p
+ if [ ! -f "$INITRAMFS" ]; then
+ log_error "No initramfs found at $INITRAMFS"
+ exit 1
+ fi
+
+ if [ -d "$INITRAMFS_MNT" ] && [ ! "$INITRAMFS" -ot "$INITRAMFS_MNT" ]; then
+ # need to unpack again: initramfs is newer than what we unpacked earlier
+ if mountpoint -q "$INITRAMFS_MNT"; then
+ umount "$INITRAMFS_MNT"
+ fi
+ rmdir "$INITRAMFS_MNT" || exit 1
+ fi
+
+ if [ ! -d "$INITRAMFS_MNT" ]; then
+ # we need at about 300 MiB on ubuntu, 200 on debian
+ # https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
+ while IFS=" " read -r k v u; do
+ # /proc/meminfo format is documented in proc(5)
+ case "$u" in
+ MB) u=1048576;;
+ kB) u=1024;;
+ *) u=1;;
+ esac
+ case "$k" in
+ "MemAvailable:") MemAvailable=$((v*u));;
+ "SwapFree:") SwapFree=$((v*u));;
+ esac
+ done </proc/meminfo
+ if [ $((MemAvailable+SwapFree)) -lt $((300*1024*1024)) ]; then
+ log_error "Not enough memory available. Please close some programs or add swap space to suspend successfully."
+ exit 1
+ fi
+
+ mkdir -m0700 "$INITRAMFS_MNT"
+ mount -t ramfs -o nodev,mode=0700 ramfs "$INITRAMFS_MNT"
+
+ # extract initrd.img to initramfs dir
+ unmkinitramfs "$INITRAMFS" "$INITRAMFS_MNT"
+ new="y"
+ fi
+
+ # unmkinitramfs(8) extracts microcode into folders "early*" and the actual initramfs into "main"
+ if [ -f "$INITRAMFS_MNT/sbin/cryptsetup" ]; then
+ INITRAMFS_DIR="$INITRAMFS_MNT"
+ elif [ -f "$INITRAMFS_MNT/main/sbin/cryptsetup" ]; then
+ INITRAMFS_DIR="$INITRAMFS_MNT/main"
+ else
+ log_error "Directory $INITRAMFS_MNT has unpected content" >&2
+ exit 1
+ fi
+
+ if [ "$new" = "y" ]; then
+ for p in /dev /proc /run /sys; do
+ if [ ! -d "$INITRAMFS_DIR$p" ]; then
+ mkdir -m0755 "$INITRAMFS_DIR$p"
+ fi
+ done
+
+ # copy our binary to ramdisk
+ install -m0755 -t "$INITRAMFS_DIR/bin" /lib/cryptsetup/scripts/suspend/cryptsetup-suspend
+
+ # copy all firmware files to ramdisk to prevent dead-lock
+ # see https://salsa.debian.org/mejo/cryptsetup-suspend/issues/38)
+ # TODO we should try to identify which firmwares need to be loaded
+ # and only copy those
+ if [ -d /lib/firmware ] && [ ! -d "$INITRAMFS_DIR/lib/firmware" ]; then
+ cp -dR -T -- /lib/firmware "$INITRAMFS_DIR/lib/firmware"
+ fi
+ fi
+
+ # from initramfs-tools-core's /usr/share/initramfs-tools/init
+ mount -t devtmpfs -o noexec,nosuid,mode=0755 udev "$INITRAMFS_DIR/dev"
+ mount -t proc -o nodev,noexec,nosuid proc "$INITRAMFS_DIR/proc"
+ mount -t ramfs -o nodev,noexec,nosuid,mode=0755 ramfs "$INITRAMFS_DIR/run"
+ mount -t sysfs -o nodev,noexec,nosuid sysfs "$INITRAMFS_DIR/sys"
+
+ [ -d "$INITRAMFS_DIR/dev/pts" ] || mkdir -m0755 "$INITRAMFS_DIR/dev/pts"
+ mount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts "$INITRAMFS_DIR/dev/pts" || true
+
+ # remount read-only, private and unbindable
+ mount -oremount,ro --make-rprivate --make-runbindable "$INITRAMFS_MNT"
+}
+
+umount_initramfs() {
+ if [ -d "${INITRAMFS_DIR-}" ]; then
+ umount -- "$INITRAMFS_DIR/dev/pts"
+ umount -- "$INITRAMFS_DIR/dev"
+ umount -- "$INITRAMFS_DIR/proc"
+ umount -- "$INITRAMFS_DIR/run"
+ umount -- "$INITRAMFS_DIR/sys"
+ fi
+ if [ "$KEEP_INITRAMFS" != "true" ] || [ -z "${INITRAMFS_DIR+x}" ]; then
+ # always unmount if we error out before setting INITRAMFS_DIR
+ umount -- "$INITRAMFS_MNT"
+ rmdir -- "$INITRAMFS_MNT"
+ fi
+}
+
+CGROUP_FREEZER=
+freeze_cgroup() {
+ local c="$1" v
+ # freeze cgroup if non-frozen
+ if [ -f "$c" ] && v="$(cat <"$c")" && [ $v -eq 0 ]; then
+ echo 1 >"$c"
+ CGROUP_FREEZER="$c${CGROUP_FREEZER:+" $CGROUP_FREEZER"}"
+ fi
+}
+freeze_cgroups() {
+ local mycgroup c
+
+ # freeze all machines/containers and user cgroups
+ freeze_cgroup "$hierarchy/machine.slice/cgroup.freeze"
+ freeze_cgroup "$hierarchy/user.slice/cgroup.freeze"
+
+ # get my second level cgroup
+ mycgroup="$(grep -m1 "^0::" /proc/self/cgroup | cut -sd/ -f3)"
+
+ # freeze all system cgroups except ours and systemd-suspend
+ for c in "$hierarchy"/system.slice/*/cgroup.freeze; do
+ if [ "$c" != "$hierarchy/system.slice/$mycgroup/cgroup.freeze" ] && \
+ [ "${c#"$hierarchy/system.slice/systemd-suspend."}" = "$c" ]; then
+ freeze_cgroup "$c"
+ fi
+ done
+
+ # freeze systemd itself
+ freeze_cgroup "$hierarchy/init.scope/cgroup.freeze"
+}
+
+thaw_cgroups() {
+ local c
+ for c in $CGROUP_FREEZER; do
+ echo 0 >"$c"
+ done
+}
+
+populate_ACTIVE_DEVICES() {
+ local DEV MAJ MIN
+ if ! dm_blkdevname "$CRYPTTAB_NAME" >/dev/null; then
+ # silently ignore unmapped devices
+ return 0
+ elif [ "$(dmsetup info --noheadings -c -o subsystem -- "$CRYPTTAB_NAME")" != "CRYPT" ]; then
+ cryptsetup_message "ERROR: $CRYPTTAB_NAME: Subsystem mismatch"
+ return 1
+ elif ! _resolve_device "$CRYPTTAB_SOURCE"; then
+ cryptsetup_message "ERROR: $CRYPTTAB_NAME: Missing source $CRYPTTAB_SOURCE"
+ return 1
+ elif [ "$(dmsetup info -c --noheadings -o devnos_used -- "$CRYPTTAB_NAME" 2>/dev/null)" != "$MAJ:$MIN" ]; then
+ cryptsetup_message "ERROR: $CRYPTTAB_NAME: Source mismatch"
+ return 1
+ fi
+
+ if ! crypttab_parse_options --quiet; then
+ cryptsetup_message "ERROR: $CRYPTTAB_NAME: Unable to parse options field"
+ return 1
+ elif [ "$CRYPTTAB_TYPE" != "luks" ]; then
+ # XXX does it even work with detached headers?
+ cryptsetup_message "WARNING: $CRYPTTAB_NAME: unable to suspend non-LUKS device"
+ return 0
+ fi
+
+ # XXX that's not robust since $CRYPTTAB_NAME might contain spaces or
+ # special characters; we need to create a NUL-delimited list in a
+ # file instead
+ ACTIVE_DEVICES="${ACTIVE_DEVICES:+"$ACTIVE_DEVICES "}$CRYPTTAB_NAME"
+}
+
+clean_up() {
+ # we always want to run through the whole cleanup
+ set +e
+
+ # thaw all frozen cgroups
+ thaw_cgroups
+
+ # Run post-suspend scripts
+ run_dir post suspend
+
+ umount_initramfs
+
+ # unlock sessions
+ if [ "$UNLOCK_SESSIONS" = "true" ]; then
+ loginctl unlock-sessions
+ fi
+}
+
+## Main script
+
+# check unified cgroups hierarchy
+# https://github.com/systemd/systemd/blob/master/docs/CGROUP_DELEGATION.md
+if [ -d /sys/fs/cgroup/system.slice ]; then
+ hierarchy="/sys/fs/cgroup"
+elif [ -d /sys/fs/cgroup/unified/system.slice ]; then
+ # hybrid cgroup hierarchy
+ hierarchy="/sys/fs/cgroup/unified"
+else
+ log_error "No unified cgroups hierarchy"
+ exit 1
+fi
+
+# check that not run as user
+# XXX: We should catch also cases where libpam-systemd is not installed
+if grep -Eq '^[0-9]+:[^:]*:/user\.slice/' /proc/self/cgroup; then
+ log_error "Don't run this script as user"
+ exit 1
+fi
+
+# always thaw cgroups, re-mount filesystems and remove initramfs at the end of the script
+trap clean_up EXIT
+
+read_config
+
+# extract temporary filesystem to switch to
+mount_initramfs
+
+# Run pre-suspend scripts
+run_dir pre suspend
+
+# populate list of active crypt devices
+ACTIVE_DEVICES=""
+crypttab_foreach_entry populate_ACTIVE_DEVICES
+
+# freeze all cgroups but us
+freeze_cgroups
+
+# No longer fail in case of errors
+set +e
+
+# change into ramdisk
+devices_remaining="$(chroot "$INITRAMFS_DIR" /bin/sh -c "
+ # suspend active luks devices (in reverse order) and system
+ /bin/cryptsetup-suspend --reverse $ACTIVE_DEVICES
+
+ TABFILE=\"/cryptroot/crypttab\"
+ . /lib/cryptsetup/functions
+
+ # resume active luks devices (only initramfs devices)
+ for dev in $ACTIVE_DEVICES; do
+ if crypttab_find_entry --quiet \"\$dev\"; then
+ DM_DISABLE_UDEV=y resume_device \"\$dev\" || sleep 5
+ else
+ # write remaining devices to FD3
+ printf \"%s \" \"\$dev\" >&3
+ fi
+ done
+" 3>&- 3>&1 >&2)"
+
+# resume remaining active luks devices (non-initramfs devices)
+for dev in $devices_remaining; do
+ if crypttab_find_entry --quiet "$dev"; then
+ # explicitely disable udev support, cf. #1020553
+ # XXX this is not ideal since udev might be required in some situations
+ # (detached header or key material on removable device comes to mind)
+ DM_DISABLE_UDEV=y resume_device "$dev" || true
+ else
+ log_error "'$dev' not found in /etc/crypttab"
+ fi
+done