#!/bin/bash -p

set -euo pipefail

PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

trap 'echo "Press ENTER to continue."; read dummy' ERR

################################################################################
## Parameters and helper functions

INITRAMFS_DIR=/run/initramfs
SYSTEM_SLEEP_PATH=/usr/lib/systemd/system-sleep
BIND_PATHS=("/sys" "/proc" "/dev" "/run")
REMOUNT=0
# Retrieve cryptdevice name from boot cmdline
CRYPTNAME="$(sed -n 's/.*cryptdevice=[^: ]*:\([^: ]*\).*$/\1/p' /proc/cmdline)"

# run_dir DIR ARGS...
# Run all executable scripts in directory DIR with arguments ARGS
run_dir() {
  local dir
  dir="$1"
  shift
  find "$dir" -type f -executable -exec "{}" "$@" ";"
}

# Restore chroot
umount_initramfs() {
  local p
  for p in "${BIND_PATHS[@]}"; do
    ! mountpoint -q "$INITRAMFS_DIR$p" || umount -l "$INITRAMFS_DIR$p"
  done
}

cryptdevice_mount_options() {
  local mt
  mt="$(grep "^/dev/mapper/${1} " /proc/mounts | cut -d ' ' -f 3,4 | head -n 1)"
  local fs
  fs="$(cut -d ' ' -f 1 <<<"$mt")"
  local opt
  opt="$(cut -d ' ' -f 2 <<<"$mt")"
  if [[ "$fs" == "ext4" || "$fs" == "btrfs" ]]; then
    echo "$opt"
  fi
}

################################################################################
## Main script

[ -e "$INITRAMFS_DIR/ykfde-suspend" ] || exec /usr/lib/systemd/systemd-sleep suspend

# Prepare chroot
trap umount_initramfs EXIT
for p in "${BIND_PATHS[@]}"; do
  mount -o bind "$p" "$INITRAMFS_DIR$p"
done

# Run pre-suspend scripts
run_dir "$SYSTEM_SLEEP_PATH" pre suspend

# Stop udev service and prevent it to be autostarted. Otherwise, luksResume will
# hang waiting for udev, which is itself waiting or I/O on the root device.
! systemctl is-active -q systemd-udevd-control.socket || systemctl stop systemd-udevd-control.socket
! systemctl is-active -q systemd-udevd-kernel.socket || systemctl stop systemd-udevd-kernel.socket
! systemctl is-active -q systemd-udevd.service || systemctl stop systemd-udevd.service

! systemctl is-active -q systemd-journald-audit.socket || systemctl stop systemd-journald-audit.socket
! systemctl is-active -q systemd-journald-dev-log.socket || systemctl stop systemd-journald-dev-log.socket
! systemctl is-active -q systemd-journald.socket || systemctl stop systemd-journald.socket
! systemctl is-active -q systemd-journald.service || systemctl stop systemd-journald.service

# Journalled ext4 filesystems in kernel versions 3.11+ will block suspend if
# mounted with `barrier=1`, which is the default. Temporarily remount with
# `barrier=0` if this is true of the crypt fs.
# When using LVM, all the ext4 subpartitions should be remounted with the barrier=0 option to prevent hanging
if [[ "$(lsblk -drno FSTYPE /dev/mapper/"$CRYPTNAME")" == "LVM2_member" ]]; then
  for part in $(lsblk -lno NAME /dev/mapper/"$CRYPTNAME" | sed '1d'); do
    MOUNT_OPTS="$(cryptdevice_mount_options "$part")"
    if [[ "$MOUNT_OPTS" ]] && ! [[ "$MOUNT_OPTS" == *nobarrier* || "$MOUNT_OPTS" == *barrier=0* ]]; then
      REMOUNT=1
      mount -o remount,nobarrier "/dev/mapper/$part"
    fi
  done
else
  MOUNT_OPTS="$(cryptdevice_mount_options "$CRYPTNAME")"
  if [[ "$MOUNT_OPTS" ]] && ! [[ "$MOUNT_OPTS" == *nobarrier* || "$MOUNT_OPTS" == *barrier=0* ]]; then
    REMOUNT=1
    mount -o remount,nobarrier "/dev/mapper/$CRYPTNAME"
  fi
fi

# Synchronize filesystems before luksSuspend
sync

# Hand over execution to script inside initramfs
cd "$INITRAMFS_DIR"
chroot . /ykfde-suspend "$CRYPTNAME"

# Restore original mount options if necessary
if ((REMOUNT)); then
  if [[ "$(lsblk -drno FSTYPE /dev/mapper/"$CRYPTNAME")" == "LVM2_member" ]]; then
    for part in $(lsblk -lno NAME /dev/mapper/"$CRYPTNAME" | sed '1d'); do
      MOUNT_OPTS="$(cryptdevice_mount_options "$part")"
      if [[ "$MOUNT_OPTS" ]]; then
        mount -o remount,barrier "/dev/mapper/$part"
      fi
    done
  else
    mount -o remount,barrier "/dev/mapper/$CRYPTNAME"
  fi
fi

! systemctl is-enabled -q systemd-journald.socket || systemctl start systemd-journald.socket
! systemctl is-enabled -q systemd-journald-dev-log.socket || systemctl start systemd-journald-dev-log.socket
! systemctl is-enabled -q systemd-journald-audit.socket || systemctl start systemd-journald-audit.socket
! systemctl is-enabled -q systemd-journald.service || systemctl start systemd-journald.service

# Restart udev
! systemctl is-enabled -q systemd-udevd-control.socket || systemctl start systemd-udevd-control.socket
! systemctl is-enabled -q systemd-udevd-kernel.socket || systemctl start systemd-udevd-kernel.socket
! systemctl is-enabled -q systemd-udevd.service || systemctl start systemd-udevd.service

# Run post-suspend scripts
run_dir "$SYSTEM_SLEEP_PATH" post suspend

# Unlock user sessions
loginctl unlock-sessions
