#!/usr/bin/bash -e
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2015 Jason Mehring <nrgaway@gmail.com>
# Copyright (C) 2015 Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
# Copyright (C) 2022 Frédéric Pierret (fepitre) <frederic@invisiblethingslab.com>
#
# 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 <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later

# Kills any processes within the mounted location and
# unmounts any mounts active within.
#
# To keep the actual mount mounted, add a '/' to end
#
# ${1}: directory to umount
#
# Examples:
# To kill all processes and mounts within 'chroot-jessie' but keep
# 'chroot-jessie' mounted:
#
# ./umount-kill chroot-jessie/
#
# To kill all processes and mounts within 'chroot-jessie' AND also
# umount 'chroot-jessie' mount:
#
# ./umount-kill chroot-jessie
#

if [ "$DEBUG" == "1" ]; then
    set -x
fi

mountPoint() {
    local mount_point="${1}"

    # We need absolute paths here so we don't kill everything
    if ! [[ "${mount_point}" == /* ]]; then
        mount_point="$(readlink -m .)/${mount_point}"
    fi

    # Strip any extra trailing slashes ('/') from path if they exist
    # since we are doing an exact string match on the path
    # shellcheck disable=SC2001
    echo "${mount_point}" | sed s#//*#/#g
}

mountPoints() {
    local mount_point
    mount_point="$(mountPoint "${1}")"
    # shellcheck disable=SC2005
    echo "$(sudo grep "${mount_point}" /proc/mounts | cut -f2 -d" " | sort -r | grep "^${mount_point}" | uniq)"
}

# ${1} = full path to mountpoint;
# ${2} = if set will not umount; only kill processes in mount
umount_kill() {
    if [ "0${VERBOSE}" -le 2 ]; then
        # If enabled, turn off xtrace and remember its current setting.
        if test -o xtrace; then
            true "${FUNCNAME[0]}: Disabling xtrace, because variable VERBOSE (${VERBOSE}) is lower than or equal 2..."
            set +x
            XTRACE_WAS_SET=true
        fi
    fi

    local mount_point
    local kill_only="${2}"

    mount_point="$(mountPoint "${1}")"
    declare -A cache

    # Sync the disk before un-mounting to be sure everything is written
    sync

    echo "INFO: Attempting to kill any processes still running in '${mount_point}' before un-mounting"
    read -r -a mounts <<<"$(mountPoints "${mount_point}")"
    for dir in "${mounts[@]}"; do
        # Escape filename (convert spaces to '\ ', etc
        dir="$(printf "%s" "${dir}")"

        # Skip if already in cache
        [[ ${cache["${dir}"]+_} ]] && continue || cache["${dir}"]=1

        # Kill of any processes within mountpoint
        sudo lsof "${dir}" 2>/dev/null | grep "${dir}" | tail -n +2 | awk '{print $2}' | xargs --no-run-if-empty sudo kill -9

        # Umount
        if ! [ "${kill_only}" ]; then

            # Mount point found in mtab
            if sudo mountpoint -q "${dir}"; then
                echo "INFO: umount ${dir}"
                sudo umount -n "${dir}" 2>/dev/null || sudo umount -n -l "${dir}" 2>/dev/null ||
                    echo "ERROR: umount ${dir} unsuccessful!"
            # Umount entries not found within 'mountpoint'
            else
                # Look for (deleted) mountpoints
                echo "INFO: not a regular mount point: ${dir}"
                base="$(basename "${dir}")"
                dir="$(dirname "${dir}")"
                # shellcheck disable=SC2001
                base="$(echo "${base}" | sed 's/[\].*$//')"
                dir="${dir}/${base}"
                sudo umount -v -f -n "${dir}" 2>/dev/null || sudo umount -v -f -n -l "${dir}" 2>/dev/null ||
                    echo "ERROR: umount ${dir} unsuccessful!"
            fi
        fi
    done

    if [ "$XTRACE_WAS_SET" == "true" ]; then
        set -x
        true "${FUNCNAME[0]}: Restoring xtrace..."
    fi
}

kill_processes_in_mount() {
    umount_kill "${1}" "false" || :
}

if [ "$(basename "${0}")" == "umount-kill" ] && [ -n "${1}" ]; then
    umount_kill "${1}"
fi
