#!/bin/bash

# Overlord management of snapd for package manager actions.
# Implements actions that would be invoked in %pre(un) actions for snapd.
# Derived from the snapd.postrm scriptlet used in the Ubuntu packaging for
# snapd.

set -e

SNAP_MOUNT_DIR="/snap"

show_help() {
    exec cat <<'EOF'
Usage: snap-mgmt.sh [OPTIONS]

A simple script to cleanup snap installations.

optional arguments:
  --help                           Show this help message and exit
  --snap-mount-dir=<path>          Provide a path to be used as $SNAP_MOUNT_DIR
  --purge                          Purge all data from $SNAP_MOUNT_DIR
EOF
}

SNAP_UNIT_PREFIX="$(systemd-escape -p ${SNAP_MOUNT_DIR})"

systemctl_stop() {
    unit="$1"
    if systemctl is-active -q "$unit"; then
        echo "Stopping $unit"
        systemctl stop -q "$unit" || true
    fi
}

purge() {
    # shellcheck disable=SC1091
    distribution=$(. /etc/os-release; echo "${ID}-${VERSION_ID}")

    if [ "$distribution" = "ubuntu-14.04" ]; then
        # snap.mount.service is a trusty thing
        systemctl_stop snap.mount.service
    else
        # undo any bind mount to ${SNAP_MOUNT_DIR} that resulted from LP:#1668659
        # (that bug can't happen in trusty -- and doing this would mess up snap.mount.service there)
        if grep -q "${SNAP_MOUNT_DIR} ${SNAP_MOUNT_DIR}" /proc/self/mountinfo; then
            umount -l "${SNAP_MOUNT_DIR}" || true
        fi
    fi

    units=$(systemctl list-unit-files --no-legend --full | grep -vF snap.mount.service || true)
    # *.snap mount points
    mounts=$(echo "$units" | grep "^${SNAP_UNIT_PREFIX}[-.].*\\.mount" | cut -f1 -d ' ')
    # services from snaps
    services=$(echo "$units" | grep '^snap\..*\.service' | cut -f1 -d ' ')
    for unit in $services $mounts; do
        # ensure its really a snap mount unit or systemd unit
        if ! grep -q 'What=/var/lib/snapd/snaps/' "/etc/systemd/system/$unit" && ! grep -q 'X-Snappy=yes' "/etc/systemd/system/$unit"; then
            echo "Skipping non-snapd systemd unit $unit"
            continue
        fi

        echo "Stopping $unit"
        systemctl_stop "$unit"

        if echo "$unit" | grep -q '.*\.mount' ; then
            # Transform ${SNAP_MOUNT_DIR}/core/3440 -> core/3440 removing any
            # extra / preceding snap name, eg:
            #  /var/lib/snapd/snap/core/3440  -> core/3440
            #  /snap/core/3440                -> core/3440
            #  /snap/core//3440               -> core/3440
            # NOTE: we could have used `systemctl show $unit -p Where --value`
            # but systemd 204 shipped with Ubuntu 14.04 does not support this
            snap_rev=$(systemctl show "$unit" -p Where | sed -e 's#Where=##' -e "s#$SNAP_MOUNT_DIR##" -e 's#^/*##')
            snap=$(echo "$snap_rev" |cut -f1 -d/)
            rev=$(echo "$snap_rev" |cut -f2 -d/)
            if [ -n "$snap" ]; then
                echo "Removing snap $snap"
                # aliases
                if [ -d "${SNAP_MOUNT_DIR}/bin" ]; then
                    find "${SNAP_MOUNT_DIR}/bin" -maxdepth 1 -lname "$snap" -delete
                    find "${SNAP_MOUNT_DIR}/bin" -maxdepth 1 -lname "$snap.*" -delete
                fi
                # generated binaries
                rm -f "${SNAP_MOUNT_DIR}/bin/$snap"
                rm -f "${SNAP_MOUNT_DIR}/bin/$snap".*
                # snap mount dir
                umount -l "${SNAP_MOUNT_DIR}/$snap/$rev" 2> /dev/null || true
                rm -rf "${SNAP_MOUNT_DIR:?}/$snap/$rev"
                rm -f "${SNAP_MOUNT_DIR}/$snap/current"
                # snap data dir
                rm -rf "/var/snap/$snap/$rev"
                rm -rf "/var/snap/$snap/common"
                rm -f "/var/snap/$snap/current"
                # opportunistic remove (may fail if there are still revisions left)
                for d in "${SNAP_MOUNT_DIR}/$snap" "/var/snap/$snap"; do
                    if [ -d "$d" ]; then
                        rmdir --ignore-fail-on-non-empty "$d"
                    fi
                done
                # udev rules
                find /etc/udev/rules.d -name "*-snap.${snap}.rules" -execdir rm -f "{}" \;
                # dbus policy files
                if [ -d /etc/dbus-1/system.d ]; then
                    find /etc/dbus-1/system.d -name "snap.${snap}.*.conf" -execdir rm -f "{}" \;
                fi
                # timer files
                find /etc/systemd/system -name "snap.${snap}.*.timer" | while read -r f; do
                    systemctl_stop "$(basename "$f")"
                    rm -f "$f"
                done
            fi
        fi

        echo "Removing $unit"
        rm -f "/etc/systemd/system/$unit"
        rm -f "/etc/systemd/system/multi-user.target.wants/$unit"
    done

    echo "Discarding preserved snap namespaces"
    # opportunistic as those might not be actually mounted
    if [ -d /run/snapd/ns ]; then
        if [ "$(find /run/snapd/ns/ -name "*.mnt" | wc -l)" -gt 0 ]; then
            for mnt in /run/snapd/ns/*.mnt; do
                umount -l "$mnt" || true
                rm -f "$mnt"
            done
        fi
        find /run/snapd/ns/ \( -name '*.fstab' -o -name '*.user-fstab' \) -delete
        umount -l /run/snapd/ns/ || true
    fi

    echo "Removing downloaded snaps"
    rm -rf /var/lib/snapd/snaps/*

    echo "Removing features exported from snapd to helper tools"
    rm -rf /var/lib/snapd/features

    echo "Final directory cleanup"
    rm -rf "${SNAP_MOUNT_DIR}"
    rm -rf /var/snap

    echo "Removing leftover snap shared state data"
    rm -rf /var/lib/snapd/desktop/applications/*
    rm -rf /var/lib/snapd/seccomp/bpf/*
    rm -rf /var/lib/snapd/device/*
    rm -rf /var/lib/snapd/assertions/*
    rm -rf /var/lib/snapd/cookie/*
    rm -rf /var/lib/snapd/cache/*
    rm -rf /var/lib/snapd/mount/*
    rm -rf /var/lib/snapd/sequence/*
    rm -rf /var/lib/snapd/apparmor/*
    rm -f /var/lib/snapd/state.json
    rm -f /var/lib/snapd/system-key

    echo "Removing snapd catalog cache"
    rm -rf /var/cache/snapd/*

    if test -d /etc/apparmor.d; then
        # Remove auto-generated rules for snap-confine from the 'core' snap
        echo "Removing extra snap-confine apparmor rules"
        # shellcheck disable=SC2046
        rm -f /etc/apparmor.d/$(echo "$SNAP_UNIT_PREFIX" | tr '-' '.').core.*.usr.lib.snapd.snap-confine
    fi
}

while [ -n "$1" ]; do
    case "$1" in
        --help)
            show_help
            exit
            ;;
        --snap-mount-dir=*)
            SNAP_MOUNT_DIR=${1#*=}
            SNAP_UNIT_PREFIX=$(systemd-escape -p "$SNAP_MOUNT_DIR")
            shift
            ;;
        --purge)
            purge
            shift
            ;;
        *)
            echo "Unknown command: $1"
            exit 1
            ;;
    esac
done
