#!/usr/bin/bash
#
#   Script name: ubmunload
#   Description: Unload modules and directory from AUFS
#   Git: https://gitea.ublinux.com/
#   Author: Dmitry Razumov asmeron@ublinux.com
#   Contributors: support@ublinux.com
#
#   Copyright (c) 2021-2025 UBLinux <support@ublinux.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 <http://www.gnu.org/licenses/>.

VERSION_SCRIPT=2.19
[[ -f /usr/bin/bash ]] && /usr/bin/bash --version | grep -q ^"GNU bash" || exit 1

exec_exit(){
    rm -rdf "${TMP_PATH}"
}

init(){
    export TEXTDOMAIN="${PKGNAME}"
    export TEXTDOMAINDIR="${PATH_ROOT}/usr/share/locale"
}

i18n(){
    local key="${1}"; shift
    printf "$(gettext -s "${key}")" "$@"
}

usage(){
cat <<EOF
Usage: $(basename $0) <keys> [OBJECTS LIST]

$(basename $0) keys:
 -h, --help	     Help
 -k           	     Kill open branch files
 -n, --no-update     Do not update caches
 -t, --no-skriplets  Do not execute scriplets
 -s, --saveram       Do not remove module from RAM after unmounting
 -v                  Verbose output
 -V                  Version script

OBJECTS LIST:
 module, directory, iso, img, squashfs, etc, layer number(see aufs-n)
 Use a [space] or [,] or [;] for the separator

=====================================================
Examples:
$(basename $0) module1.ubm,module2.ubm module3.ubm
$(basename $0) -nvk module1.ubm
EOF
    exit 0
}
arguments() {
# Pre-process options to:
# - expand -xyz into -x -y -z
# - expand --longopt=arg into --longopt arg
    local ARGV=()
    local END_OF_OPT=
    while [[ $# -gt 0 ]]; do
	arg="$1"; shift
	case "${END_OF_OPT}${arg}" in
	    --) ARGV+=("$arg"); END_OF_OPT=1 ;;
	    --*=*)ARGV+=("${arg%%=*}" "${arg#*=}") ;;
	    --*) ARGV+=("$arg") ;;
	    -*) for i in $(seq 2 ${#arg}); do ARGV+=("-${arg:i-1:1}"); done ;;
	    *) ARGV+=("$arg") ;;
	esac
    done
# Apply pre-processed options
    set -- "${ARGV[@]}"
# Parse options
    local END_OF_OPT=
    local POSITIONAL_ARGS=()
    [[ -z $@ ]] && usage && exit 0
    while [[ $# -gt 0 ]]; do
	case "${END_OF_OPT}${1}" in
	    -h | --help	| help)	 usage && exit 0 ;;
	    -k)			 KILL_BRANCH=1 ;;
	    -n | --no-update) 	 NO_UPDATE_CACHE="yes" ;;
	    -t | --no-skriplets) NO_SCRIPLETS="yes" ;;
	    -s | --saveram) 	 SAVERAM="yes" ;;
	    -v) 		 VERBOSE="yes" ;;
	    -q | --quiet)     	 QUIET=1 ;;
	    -V | --version)	 echo "Version: ${VERSION_SCRIPT}"; exit 0 ;;
	    --stdin)	    	 READ_STDIN=1 ;;
	    --)             	 END_OF_OPT=1 ;;
	    -*  | --*)         	 echo "WARNING: Unrecognized argument, skiped: $1" >&2  ;;
	    *)              	 POSITIONAL_ARGS+=("$1"); OPT_END="$1" ;;
	esac
	shift
    done
# Restore positional parameters
    set -- "${POSITIONAL_ARGS[@]}"
# Default options
    SOURCE_LIST="${POSITIONAL_ARGS[@]}"
    [[ -n "${SOURCE_LIST}" ]] || usage
}
files_open_lsof() {
# Показать файлы которые загружены mmem из точки монтирования ветки aufs
    FILES_OPEN=$(lsof / 2>/dev/null | awk '{print $9}' | grep "/..*" | grep -Ev "/proc|/sys|/run|/tmp|/dev|/home" | sort -u)
    for SELECT_FILES_OPEN in ${FILES_OPEN}; do
	[[ -d ${SELECT_FILES_OPEN} ]] && continue
	[[ -f ${SELECT_FILES_OPEN} ]] || continue
	[[ -f "${MOUNTPOINT}${SELECT_FILES_OPEN}" ]] && BRANCH_OPENFILES+=" ${SELECT_FILES_OPEN}"
    done
    if [[ -n ${BRANCH_OPENFILES} ]]; then
	if [[ -n ${VERBOSE} ]]; then
	    echo "Files opened from \"${SELECT_SOURCE_LIST}\":"
	    for SELECT_BRANCH_OPENFILE in ${BRANCH_OPENFILES}; do
		echo -n ${SELECT_BRANCH_OPENFILE}
		# VAR from /bin/ubm : changesDir
		[[ -f "${changesDir}${SELECT_BRANCH_OPENFILE}" ]] && echo " - changed" || echo ''
	    done
	fi
	# Убить все открытые файлы
	[[ -n ${KILL_BRANCH} ]] && fuser -uk ${BRANCH_OPENFILES} && echo
    fi
}

###################
###   M A I N   ###
###################

    [[ -d ${SSC_EXTRACT_DIR} ]] && rm -rdf "${SSC_EXTRACT_DIR}"
    [[ -n ${SSC_ARGV0} ]] && PKGNAME=${SSC_ARGV0##*/} && PKGPATH=${SSC_ARGV0%/*} || { PKGNAME=${0##*/} && PKGPATH=${0%/*}; }
    PATH_WORK=${PWD}
    
    trap "exec_exit" EXIT
    init

    [[ -f $(dirname $0)/ubm ]] && . $(dirname $0)/ubm || . $(which ubm) || exit 1
    TMP_PATH="$(mktemp --directory --tmpdir ${PKGNAME}.XXXX)"

    arguments $@
    allow_only_root

    for SELECT_SOURCE_LIST in $(echo ${SOURCE_LIST} | tr ',; ' '\n'); do
	prefix=$(grep ' / aufs' /proc/mounts | cut -f2 -d= | tr ',' ' ' | cut -f1 -d' ')
	if [[ -f ${SELECT_SOURCE_LIST} ]]; then
	# Найти по указанному имени файла бандл для отключения
	    FINDIT=$(realpath ${SELECT_SOURCE_LIST})
	    MOUNTPOINT=$(ls /sys/fs/aufs/si_${prefix}/br{[0-9],[0-9][0-9],[0-9][0-9][0-9]} 2>/dev/null | xargs cat | sed "s/=.*//" | grep -E "/${FINDIT##*/}$" )
	elif [[ ${SELECT_SOURCE_LIST} =~ ^[0-9]+$ ]]; then
	# Найти по указанному номеру бандл для отключения
	    MOUNTPOINT=$(ls /sys/fs/aufs/si_${prefix}/br${SELECT_SOURCE_LIST} 2>/dev/null | xargs cat | head -1 |sed "s/=.*//")
	else
	# Найти по указанному имени бандл для отключения, может быть каталог или любое имя найденное
	    MOUNTPOINT=$(ls /sys/fs/aufs/si_${prefix}/br{[0-9],[0-9][0-9],[0-9][0-9][0-9]} 2>/dev/null | xargs cat | sed "s/=.*//" | grep "/${SELECT_SOURCE_LIST##*/}$")
	fi
	if [[ -d ${MOUNTPOINT} ]]; then
	    files_open_lsof
	    [[ ${MOUNTPOINT##*/} =~ ^(.*)'-'([0-9]+':'{0,1}[^-]+'-'[0-9]+)'-'(any|x86_64|pentium4|i686|arm|armv6h|armv7h|aarch64)'.ubm' ]] && PKG_NAME=${BASH_REMATCH[1]} && PKG_VER=${BASH_REMATCH[2]}
	    # Если присутствует файл скриптлетов ubm, то подключаем как ресурс
	    [[ -f "${MOUNTPOINT}/var/lib/ubm/${PKG_NAME}-${PKG_VER}/install" ]] && cp -a "${MOUNTPOINT}/var/lib/ubm/${PKG_NAME}-${PKG_VER}/install" "${TMP_PATH}/ubm.install" 2>/dev/null \
	    && source ${TMP_PATH}/ubm.install
	    [[ -f "${MOUNTPOINT}/var/lib/ubm/${PKG_NAME}/install" ]] && cp -a "${MOUNTPOINT}/var/lib/ubm/${PKG_NAME}/install" "${TMP_PATH}/ubm.install" 2>/dev/null \
	    && source ${TMP_PATH}/ubm.install
	    # Если присутствует файл скриптлетов пакета pacman, то подключаем как ресурс
	    [[ -f "${MOUNTPOINT}/var/lib/pacman/local/ubm-${PKG_NAME}-${PKG_VER}/install" ]] && cp -a "${MOUNTPOINT}/var/lib/pacman/local/ubm-${PKG_NAME}-${PKG_VER}/install" "${TMP_PATH}/pacman.install" 2>/dev/null \
	    && source ${TMP_PATH}/pacman.install
	    [[ -f "${MOUNTPOINT}/var/lib/pacman/local/${PKG_NAME}-${PKG_VER}/install" ]] && cp -a "${MOUNTPOINT}/var/lib/pacman/local/${PKG_NAME}-${PKG_VER}/install" "${TMP_PATH}/pacman.install" 2>/dev/null \
	    && source ${TMP_PATH}/pacman.install
	    sync; echo 3 >/proc/sys/vm/drop_caches; sync
	    # Выполнить фнкцию ubm_pre_unload из ресурсов ubm.install и pacman.install
	    [[ -z ${NO_SCRIPLETS} ]] && declare -f "ubm_pre_unload" &>/dev/null && echo "==> EXECUTE: ubm_pre_unload" && ubm_pre_unload "${PKG_VER}"; unset -f ubm_pre_unload &>/dev/null 
	    mount -t aufs -o remount,del="${MOUNTPOINT}"/ aufs /  2>/dev/null
	    STATUS_AUFS=$?
	    sync
	    # Выполнить фнкцию ubm_post_unload из ресурсов ubm.install и pacman.install
	    [[ -z ${NO_SCRIPLETS} ]] && declare -f "ubm_post_unload" &>/dev/null && echo "==> EXECUTE: ubm_post_unload" && ubm_post_unload "${PKG_VER}"; unset -f ubm_post_unload &>/dev/null 
	    # Если модуль находится полностью загруженным в ОЗУ, удалить его из ОЗУ
	    # VAR from /bin/ubm : copyramdir
	    losetup | grep -q "${copyramdir}${MOUNTPOINT##*/}" && RAMFREE=yes
	    [[ ${STATUS_AUFS} -eq 0 && -z ${NO_UPDATE_CACHE} ]] && ubm_update_caches "${MOUNTPOINT}"
	    umount -d "${MOUNTPOINT}" 2>/dev/null
	    STATUS=$?
	    [[ ${STATUS} -eq 0 ]] && rmdir "${MOUNTPOINT}" 2>/dev/null && sync
	    [[ ${STATUS} -eq 0 ]] && STATUS=${STATUS_AUFS}
	    [[ ${STATUS} -gt 0 ]] && echo "ERROR: Unmount '${MOUNTPOINT}' failed!" >&2
	    if [[ -n ${RAMFREE} ]]; then
		# Function from /bin/ubm : ubmramfree
		[[ -n ${SAVERAM} ]] || ubmramfree "${MOUNTPOINT}"
	    fi
	else
	    echo "WARNING: Object '${SELECT_SOURCE_LIST}' not mounted" >&2
	fi
    done
    sync
    echo 3 > /proc/sys/vm/drop_caches
    