#!/usr/bin/bash
#
#   Script name: ubmload
#   Description: Load modules and directory to 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.18
[[ -f /usr/bin/bash ]] && /usr/bin/bash --version | grep -q ^"GNU bash" || exit 1

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
 -u, --upper            Mount filesystem to upper layer AUFS
 -l, --lower            Mount filesystem to lower layer AUFS
 -n, --no-update        Do not update cache
 -t, --no-skriplets     Do not execute scriplets
     --ro, --read-only  Mount read-only
 -r, --toram            Copy module to RAM disk (tmpfs) before loading"
 -V                     Version script

OBJECTS LIST:
  module, directory, iso, img, squashfs, etc
  Use a [space] or [,] or [;] for the separator

=====================================================
Examples:
$(basename $0) module1.ubm,module2.ubm module3.ubm
$(basename $0) -n 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 ;;
	    -u | --upper)	 mntmode="upper" ;;
	    -l | --lower)	 mntmode="lower" ;;
	    -n | --no-update)	 NO_UPDATE_CACHE="yes" ;;
	    -t | --no-skriplets) NO_SCRIPLETS="yes" ;;
	    -r | --toram)	 TORAM="yes" ;;
	    -q | --quiet)     	 QUIET=1 ;;
	    -V | --version)	 echo "Version: ${VERSION_SCRIPT}"; exit 0 ;;
	    --ro | --read-only)  romode="yes" ;;
	    --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
    sourcelist="${POSITIONAL_ARGS[@]}"
    [[ -z ${mntmode} ]] && mntmode="upper"
    [[ -n "${sourcelist}" ]] || usage
}

copy_toram() {
    # Дополнитеьное свободное место в ОЗУ в KB
    TMPFSLIMIT=4096
    [[ -d ${copyramdir} ]] || mkdir -p ${copyramdir}
    df ${copyramdir} |grep -Eq '^tmpfs|^/dev/zram'  || mount -t tmpfs tmpfs "${copyramdir}"
    freeram=$(checkramfreeb)
    filesize="$(du -k "${fsfile}" | cut -f 1)"
    minram="$(( ${filesize} + ${TMPFSLIMIT} ))"
    [[ "${minram}" -gt "${freeram}" ]] && exitmsg "ERROR: Not enough free RAM space" 3
    if [[ -f "${copyramdir}/${fsname}" ]]; then
	ubmunload -r ${fsname}
	exitmsg "File already in ram and mounted" $?
    fi
#   echo "\"${fsname}\" copy to RAM..."
    cp -afL "${fsfile}" "${copyramdir}" || return 2
    echo  "${copyramdir}/$(basename ${fsfile})" | sed 's://:/:g'
}


###################
###   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}
    
    init

    [[ -f $(dirname $0)/ubm ]] && . $(dirname $0)/ubm || . $(which ubm) || exit 1

    arguments $@
    allow_only_root
    
    for infile in $(echo ${sourcelist} | tr ',; ' '\n'); do
	[ -z "${infile}" ] && continue
	if [ -f "${infile}" ]; then
	    fsfile="$(realpath "${infile}")"
	    [[ -n ${TORAM} ]] && fsfile=$(copy_toram)
	    devtype="file"
	elif [ -b "${infile}" ]; then
	    fsfile="${infile}"
	    devtype="dev"
	elif [ -d "${infile}" ]; then
	    fsfile="$(realpath "${infile}")"
	    devtype="dir"
	else
	    exitmsg "WARNING: Object '${infile}' not found!"  2
	fi

	if [ "${devtype}" != "dir" ]; then
	    fstype=$(fs_type "${fsfile}")
	    [ "${fstype}" = "" ] && exitmsg "ERROR: Unknown file system -- '$fsfile'" 3
	    [ check_ubm 2>/dev/null ] || exitmsg "ERROR: Kernel not support module squashfs/aufs!" 4
	else
	    fsinfo="$(df -aT "${fsfile}" | tail -n 1 | tr -s ' ' '_' | cut -d'_' -f 2)"
	    case "${fsinfo}" in
		"ntfs"|"fat32"|"aufs")
		    exitmsg "ERROR: '$fsinfo' -- isn't supported FS" 5
		;;
	    esac
	    fstype="${fsinfo}"
	fi
  
	fsname="$(basename "${fsfile}")"
	MOUNTPOINT="${prefixmp}${fsname}"
	[ -d "${MOUNTPOINT}" ] && rmdir "${MOUNTPOINT}" 2>/dev/null
	[ -d "${MOUNTPOINT}" ] && exitmsg "INFO: Object '${fsfile}' is mounted!" 6

	if [ "${devtype}:${fstype}" = "file:squashfs" ]; then
	    sync
	    freeloop=0
	    looplist="$(losetup -a)"
	    while true ; do
		echo "${looplist}" | grep -q -F "/dev/loop${freeloop}" && freeloop="$(expr ${freeloop} + 1)" || break
	    done
	    if [ ! -b /dev/loop${freeloop} ]; then
		mknod -m660 /dev/loop${freeloop} b 7 ${freeloop}
		chown root:root /dev/loop${freeloop}
		sync
	    fi
	    [ -b /dev/loop${freeloop} ] || exitmsg "ERROR: Free loop devices is not found!" 7
	    losetup /dev/loop${freeloop} "${fsfile}"
	    status=$?
	else
	    status=0
	fi

	if [ ${status} -eq 0 ]; then
	    mkdir -p "${MOUNTPOINT}"
	    sync
	    case "${devtype}" in
		"file")
		    if [ "${fstype}" = "squashfs" ]; then
            		mount -t squashfs -o ro /dev/loop${freeloop} "${MOUNTPOINT}" >/dev/null #2>&1
            	    else
            		[ "${romode}" ] && optmnt="loop,ro" || optmnt="loop"
            		mount -t ${fstype} -o ${optmnt} "${fsfile}" "${MOUNTPOINT}" >/dev/null #2>&1
            	    fi
        	;;
		"dev")
		    [ "${romode}" ] && optmnt="ro" || optmnt="rw"
            	    mount -t ${fstype} -o ${optmnt} "${fsfile}" "${MOUNTPOINT}" >/dev/null
        	;;
	        #"dir")
#		    mount -t ${fstype} --bind "${fsfile}" "${MOUNTPOINT}" >/dev/null
#	        ;;
		"dir")
		    mount --bind "${fsfile}" "${MOUNTPOINT}" >/dev/null
		;;
	    esac
	    status=$?
	    if [ ${status} -eq 0 ]; then
		sync
		[[ ${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" ]] && source "${MOUNTPOINT}/var/lib/ubm/${PKG_NAME}-${PKG_VER}/install"
		[[ -f "${MOUNTPOINT}/var/lib/ubm/${PKG_NAME}/install" ]] && source "${MOUNTPOINT}/var/lib/ubm/${PKG_NAME}/install"
		# Если присутствует файл скриптлетов пакета pacman, то подключаем как ресурс
		[[ -f "${MOUNTPOINT}/var/lib/pacman/local/ubm-${PKG_NAME}-${PKG_VER}/install" ]] && source "${MOUNTPOINT}/var/lib/pacman/local/ubm-${PKG_NAME}-${PKG_VER}/install"
		[[ -f "${MOUNTPOINT}/var/lib/pacman/local/${PKG_NAME}/install" ]] && source "${MOUNTPOINT}/var/lib/pacman/local/${PKG_NAME}/install"
		# Выполнить фнкцию ubm_pre_load из ресурсов ubm.install и pacman.install
	        [[ -z ${NO_SCRIPLETS} ]] && declare -f "ubm_pre_load" &>/dev/null && echo "EXECUTE: ubm_pre_load" && ubm_pre_load "${PKG_VER}" && unset -f ubm_pre_load
		if [ "${mntmode}" = "upper" ]; then
		    nlayer=1
		    prefix=$(grep ' / aufs' /proc/mounts | cut -f2 -d= | tr ',' ' ' | cut -f1 -d' ')
		    [ -w $(cat /sys/fs/aufs/si_${prefix}/br1 | head -n1 | sed 's/=.*//') ] && nlayer=2
		    [ -w "${MOUNTPOINT}" ] && nlayer=1
		    mount -o remount,add=${nlayer}:"${MOUNTPOINT}"=ro+wh / >/dev/null #2>&1
		else
    		    mount -o remount,append="${MOUNTPOINT}"=ro+wh / >/dev/null #2>&1
		fi
		status=$?
		sync
		if [[ ${status} -eq 0 ]]; then
		    [[ -z ${NO_UPDATE_CACHE} ]] && ubm_update_caches "${sourcelist[@]}" 
		    # Выполнить фнкцию ubm_post_load из ресурсов ubm.install и pacman.install
		    [[ -z ${NO_SCRIPLETS} ]] && declare -f "ubm_post_load" &>/dev/null && echo "EXECUTE: ubm_post_load" && ubm_post_load "${PKG_VER}" && unset -f ubm_post_load
		else
    		    umount -d "${MOUNTPOINT}" >/dev/null
		    rmdir "${MOUNTPOINT}" >/dev/null
		    exitmsg "ERROR: AUFS error!" ${status}
		fi
	    else
		losetup -d /dev/loop${freeloop}
		rmdir "${MOUNTPOINT}" >/dev/null
		exitmsg "ERROR: Unable to mount '${fsfile}' in '${MOUNTPOINT}'." ${status}
	    fi
	else
	    rmdir "${MOUNTPOINT}" 2>/dev/null
	    exitmsg "ERROR: Mount error!" ${status}
	fi
    done
    sync
    echo 3 > /proc/sys/vm/drop_caches
