#!/bin/sh -e
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: Copyright SUSE LLC

make_sure_snapshot_is_writable() {
  isRO="$(btrfs property get "/.snapshots/${snapshot}/snapshot/" ro)"
  if [ "${isRO}" = "ro=true" ]; then
    btrfs property set "/.snapshots/${snapshot}/snapshot/" ro false
  fi
}

reset_snapshot_writability() {
  if [ "${isRO}" = "ro=true" ]; then
    btrfs property set "/.snapshots/${snapshot}/snapshot/" ro true
  fi
}

create_reference_etc() {
  if [ -d "/.snapshots/${snapshot}/snapshot/etc/etc.syncpoint" ]; then
    rmdir "/.snapshots/${snapshot}/snapshot/etc/etc.syncpoint"
  fi
  # Syncing is only necessary when the snapshot is based on the currently running system.

  # If the parent snapshot was created from the running system, perform a sync now and use the current running /etc as syncpoint
  if [ -e "/.snapshots/${parent}/snapshot/etc/etc.syncpoint/transactional-update.comparewith" ] \
     && [ "$(cat "/.snapshots/${parent}/snapshot/etc/etc.syncpoint/transactional-update.comparewith")" = "${current}" ]; then
    # Snapshot first, then sync to avoid racing with changes to /etc in between.
    if btrfs subvolume list / | grep -Eq " path (@/.snapshots/${current}/snapshot/)?etc"; then
      btrfs subvolume snapshot "/etc" "/.snapshots/${snapshot}/snapshot/etc/etc.syncpoint"
    else
      # Old overlay systems still have to be supported because after first boot config management software may use --continue after a migration and before the reboot
      btrfs subvolume create "/.snapshots/${snapshot}/snapshot/etc/etc.syncpoint"
      rsync --quiet --archive --xattrs --acls "/etc/" "/.snapshots/${snapshot}/snapshot/etc/etc.syncpoint"
    fi

    /usr/libexec/transactional-update-sync-etc-state --keep-syncpoint "/.snapshots/${snapshot}/snapshot/etc/etc.syncpoint" "/.snapshots/${snapshot}/snapshot/etc" "/.snapshots/${parent}/snapshot/etc/etc.syncpoint"
    echo "${current}" > "/.snapshots/${snapshot}/snapshot/etc/etc.syncpoint/transactional-update.comparewith"
  # If it's the first descendant of the current system store a reference copy of the state before changes will be applied ...
  elif [ "${parent}" = "${current}" ]; then
    btrfs subvolume snapshot "/.snapshots/${snapshot}/snapshot/etc" "/.snapshots/${snapshot}/snapshot/etc/etc.syncpoint"
    echo "${parent}" > "/.snapshots/${snapshot}/snapshot/etc/etc.syncpoint/transactional-update.comparewith"
  # ... or if it's a consecutive snapshot, copy the already existing syncpoint
  elif [ -e "/.snapshots/${parent}/snapshot/etc/etc.syncpoint" ]; then
    btrfs subvolume snapshot "/.snapshots/${parent}/snapshot/etc/etc.syncpoint" "/.snapshots/${snapshot}/snapshot/etc/etc.syncpoint"
  else
    : # consecutive snapshot without syncpoint, skip syncing altogether
  fi
}

create_snapshot_post() {
  snapshot="${1}"

  # "Parent UUID" means which parent this snapshot was created from
  parentuuid="$(btrfs subvolume list -q / | grep -E " path @/.snapshots/${snapshot}/snapshot"$ | cut -f 9 -d ' ')"
  # "Parent" is the number of the parent snapshot (as shown by snapper)
  parent="$(btrfs subvolume list -u / | grep " uuid ${parentuuid} " | cut -f 9- -d ' ' | cut -f 3 -d '/')"
  # "Parent ID" means the parent subvolume in the file system tree
  parentid="$(btrfs subvolume list / | grep -E " path @/.snapshots/${parent}/snapshot"$ | cut -f 2 -d ' ')"
  # "Current" means the snapshot number of the currently running /usr
  current=$(findmnt --target /usr --raw --noheadings --output FSROOT --first-only --direction backward --types btrfs | sed -e 's|.*.snapshots/\(.*\)/snapshot.*|\1|g')
  if [ -z "${current}" ]; then
    current=$(findmnt --target / --raw --noheadings --output FSROOT --first-only --direction backward --types btrfs | sed -e 's|.*.snapshots/\(.*\)/snapshot.*|\1|g')
  fi

  # Abort if base snapshot has plugin, otherwise it would create a snapshot with the new mechanism which cannot be updated any more by an old t-u version.
  if [ ! -e "/.snapshots/${parent}/snapshot/${0}" ]; then
    echo "System in base snapshot does not support /etc subvolumes - aborting!"
    exit 2
  fi

  make_sure_snapshot_is_writable

  rm -rf "/.snapshots/${snapshot}/snapshot/etc"
  if btrfs subvolume list / | grep -Eq " top level ${parentid} path (@/.snapshots/${parent}/snapshot/)?etc"; then
    btrfs subvolume snapshot "/.snapshots/${parent}/snapshot/etc" "/.snapshots/${snapshot}/snapshot/etc"
    create_reference_etc
  else
    # Migration from old system
    echo "Migrating from /etc overlays to /etc subvolume..."
    btrfs subvolume create "/.snapshots/${snapshot}/snapshot/etc"

    # Syncing old /etc state
    if [ "${parent}" -eq "${current}" ]; then
      rsyncsource="/etc/"
    else
      rsyncsource="/var/lib/overlay/${parent}/rsync/"
      oldetcopts="$(findmnt --output OPTIONS --noheadings --fstab --tab-file "/.snapshots/${parent}/snapshot/etc/fstab" /etc | sed 's;/sysroot/;/;g')"
      # Mount everything as readonly, i.e. lowerdirs
      etcopts="$(echo ${oldetcopts} | gawk '{ match($0,/upperdir=([^,[:space:]]+)/,upper); match($0,/lowerdir=([^,[:space:]]+)/,lower); print "lowerdir=" upper[1] ":" lower[1] }')"
      # If the current system is part of the stack remove it and all lower layers and use it directly instead
      etcopts="$(echo ${oldetcopts} | sed "s;/var/lib/overlay/${current}/etc.*;/etc;")"
      mkdir -p "${rsyncsource}"
      mount -t overlay overlay -o"${etcopts}" "${rsyncsource}"
      trap 'umount "/var/lib/overlay/${parent}/rsync"' EXIT
    fi

    rsync --quiet --archive --xattrs --acls "${rsyncsource}" "/.snapshots/${snapshot}/snapshot/etc"

    create_reference_etc

    # Add entry for /etc
    sed -i '/^overlay[[:space:]]\+\/etc[[:space:]]/d' "/.snapshots/${snapshot}/snapshot/etc/fstab"
    echo "/etc /etc none bind,x-initrd.mount 0 0" >> "/.snapshots/${snapshot}/snapshot/etc/fstab"

    # Clean up potential conflicting overlay cruft
    rm -rf "/var/lib/overlay/${snapshot}"
  fi

  # Bugfix: Older versions didn't include the x-initrd.mount flag
  if ! awk '$2 == "/etc" && $4 !~ /x-initrd.mount/ { exit 1 }' "/.snapshots/${snapshot}/snapshot/etc/fstab"; then
    awk -i inplace '$2 == "/etc" && $4 !~ /x-initrd.mount/ { $4 = $4 ",x-initrd.mount" } { print }' "/.snapshots/${snapshot}/snapshot/etc/fstab"
  fi

  reset_snapshot_writability
}

delete_snapshot_pre() {
  snapshot="${1}"

  # Only operate on snapshots with /etc as subvolume
  if btrfs subvolume list / | grep -q " @/.snapshots/${snapshot}/snapshot/etc"; then
    make_sure_snapshot_is_writable
    if btrfs subvolume list / | grep -q " @/.snapshots/${snapshot}/snapshot/etc/etc.syncpoint"; then
      btrfs subvolume delete "/.snapshots/${snapshot}/snapshot/etc/etc.syncpoint"
    fi
    btrfs subvolume delete "/.snapshots/${snapshot}/snapshot/etc"
  fi
}

if [ "$2" != "/" ]; then
  exit 0
fi
if [ -e "/.snapshots/${4}/snapshot/etc/fstab" ] && ! findmnt --mountpoint /etc --noheadings --output TARGET --tab-file "/.snapshots/${4}/snapshot/etc/fstab" >/dev/null; then
  echo "/etc is not a mount point - skipping..."
  exit 0
fi
if [ "$3" != "btrfs" ]; then
  echo "Unsupported file system $3!"
  exit 1
fi

if [ "$1" = "create-snapshot-post" ]; then
  create_snapshot_post "${4}"
elif [ "$1" = "delete-snapshot-pre" ]; then
  delete_snapshot_pre "${4}"
fi

