#!/bin/bash
# barium helper scripts library
# author: rosalinux.ru: betcher_
# email: a.betkher@rosalinux.ru
# version: 0.1

# to use losetup by user
PATH="/sbin/:/usr/sbin:$PATH"

. /etc/initvars
COPY2RAMDIR=${SYSMNT}/copy2ram/
BUNDLESDIR=${SYSMNT}/bundles/
CHANGESDIR=${SYSMNT}/changes/
METAINFODIR=/var/lib/packages
DEFMASK="${SYSMNT}/bundles/[0-9][0-9]-"
EXT=xzm

color_red='\033[0;31m'
color_green='\033[0;32m'
color_yellow='\033[0;33m'
color_blue='\033[0;34m'
color_default='\033[0m'

SQFSOPT="-b 512K -comp xz"
SQFSOPT_fast="-comp lz4"

if echo $SQFSOPT |grep -q xz; then
    if uname -m | grep -q -e "86" -e "32" -e "64" ;then
        SQFSOPT="$SQFSOPT -Xbcj x86"
    else
        SQFSOPT="$SQFSOPT -Xbcj x86,arm"
    fi
fi

CFG_DIR="/etc/barium"           # папка с конфигами для getmod
PUBKEYS="$CFG_DIR/keys"         # папка для доверенных публичных ключей
mirror_list="$CFG_DIR/mirror.lst"   # список зеркал
ALIASCFG="$CFG_DIR/aliases.cfg"     # файл с алиасами для нечеткого поиска
WORK_DIR="/var/tmp/barium"
TMPUNION=${SYSMNT}/tmp_union
TMPSQFS=${SYSMNT}/tmp_sqfs

# fix for long mount cmdline
export LIBMOUNT_FORCE_MOUNT2=always

#user config
[ -f ${CFG_DIR}/barium.cfg ] && . ${CFG_DIR}/barium.cfg

HLP() {
    # this help page
    #
echo "$(basename $0) - library for barium utils"
echo "Usage:
    source /path/$(basename $0) func_name args
or
    barium $(basename $0) func_name args

#############################################################

"
cat $0 |grep -E -A2 '^[a-z,A-Z,_]+\(\) {$' | \
sed -e 's/(.*$//' -e 's/^--$//' -e 's/#/*/'
exit
}

action() { # hide from --help
    action=$@
    echo "==> $action"
}

echo_exit() { # hide from --help
    echo "$@"
    [ "$2" -a "$2" -ne 0 ] && exit $2
}
getPin () {
    # $1 - lehth of pincode
    # return - random pin
    lenth=$1
    a=''
    while [ $(echo $a |wc -c) -le $1 ] ; do
        a=${a}"$(( $RANDOM % 10 ))"
    done
    echo $a
}

getPass () {
    # $1 - lehth of password
    # return - random pass
    lenth=$1
    cat /dev/urandom | head -n 10 |md5sum | cut -c 1-$lenth
}

getHash() {
    # $1 - password
    # return - password hash and salt in /etc/shadow format
    python3 -c "import random,string,crypt;
randomsalt = ''.join(random.sample(string.ascii_letters,8));
print(crypt.crypt('"$1"', f'\$6\${randomsalt}\$'))"
}


check_union() {
    # return type of union fs
    #
    local UNION
    df / |grep -q ^aufs[[:space:]] >/dev/null 2>&1 && UNION='aufs'
    df / |grep -q ^overlay[[:space:]] >/dev/null 2>&1 && UNION='ovl'
    [ "$UNION" ]  || exitmsg "Can't determine the type of unionfs" 111
    echo "$UNION"
    }

make_union() { # hide from --help
    # Принимает параметры в формате ключ=значение
    # Любые из local, обязательный только LOWERDIRS
    local LOWERDIRS='' SPACE='' IMGFILE='' IMGMPOINT='' WORKDIR='' CHANGES='' UNION='' LINDEX=''
    local TMPDIR="$TMPUNION"
    local UNIONFS="$(check_union)"
    for a in $@ ; do
        local |grep -q "$(echo $a |cut  -f1 -d=)=" &&  eval "$a"
    done
    mkdir -p ${TMPDIR}/0
    lastN=$(ls -1 ${TMPDIR} |sort -n |tail -n1)
    N=$(( $lastN + 1 ))
    [ -z "$UNION" ] && UNION=${TMPDIR}/$N/UNION
    [ -z "$IMGFILE" ] && IMGFILE=${TMPDIR}/$N/upper.img
    [ -z "$IMGMPOINT" ] && IMGMPOINT=${TMPDIR}/$N/upperMP
    [ -z "$WORKDIR" ] && WORKDIR=${TMPDIR}/$N/upperMP/workdir
    [ -z "$CHANGES" ] && CHANGES=${TMPDIR}/$N/upperMP/changes
    mkdir -p "$IMGMPOINT" "$UNION"
    [ -z "$SPACE" ] && SIZE=$(LC_ALL=C df -BM "${TMPDIR}" --output=avail | tail -1 | tr -d M)
    [ -z "$SPACE" ] && SPACE=$(( "$SIZE" * 80 / 100 ))
    dd if=/dev/zero of="$IMGFILE" bs=1 count=0 seek="$SPACE"M 1>&2 || return ${LINENO}
    mkfs.ext2 "$IMGFILE" 1>&2 || return ${LINENO}
    mount -o loop "$IMGFILE" "$IMGMPOINT" 1>&2 || return ${LINENO}
    mkdir -p "$WORKDIR" "$CHANGES"
    if [ "$UNIONFS" = 'ovl' ] ; then
        mount -t overlay overlay -o lowerdir="$LOWERDIRS",upperdir="$CHANGES",workdir="$WORKDIR" "$UNION" || return ${LINENO}
    elif [ "$UNIONFS" = 'aufs' ] ; then
        LOWERDIRS=$(echo $LOWERDIRS |sed 's#:#=ro+wh:#g')=ro+wh
        mount -t aufs -o nowarn_perm,br=${CHANGES}:${LOWERDIRS} aufs "$UNION"
    fi
    echo "UNION FS:" 1>&2
    for a in INDEX=$N UNION=$UNION CHANGES=$CHANGES IMGMPOINT=$IMGMPOINT SPACE=$SPACE LINDEX=$LINDEX ; do
        echo $a
        echo ":::$a" 1>&2
    done
}

destroy_union() {  # hide from --help
    # UNION="точка монтирования overlayfs", IMGMPOINT=upperdir (если upperdir тоже точка монтирования)
    # или $1 $2 диапазон номеров overlayfs созданных make-ovl
    local UNION=''  IMGMPOINT='' STARTINDEX='' ENDINDEX=''
    for a in $@ ; do
        if local |grep -q "$(echo $a |cut  -f1 -d=)=" ; then
            eval $a
        else
            if [ -z $STARTINDEX ] ; then
                STARTINDEX=$a
                continue
            fi
            [ -z $ENDINDEX ] && ENDINDEX=$a
        fi
    done

    destroy_by_index(){
        [ -z $ENDINDEX ] && ENDINDEX="$STARTINDEX"
        for a in $(seq $STARTINDEX $ENDINDEX) ; do
            destroy_by_path ${TMPUNION}/$a/UNION ${TMPUNION}/$a/upperMP
        done
    }
    destroy_by_path(){
        [ $3 ] && int=$3 || int=3
        [ "$int" -le  '0' ] && return 111
        [ ! -d $1 -a ! -d $2 ] && return 0
        key=''
        echo -n .
        [ "$int -eq 1" ] && key="-l"
        umount $key $1 2>/dev/null
        [ $2 ] && umount $key "$2" 2>/dev/nullrrr
        [ $1 != '/' ] && rm -rf $(dirname "$1")
        int=$(("$int" - 1))
        sleep 1
        destroy_by_path $1 $2 $int
    }
    expr $ENDINDEX + 1 >/dev/null 2>&1 && destroy_by_index
    [ -d "$UNION" ] && destroy_by_path "$UNION" "$IMGMPOINT"
    echo ""
}

# $1 - точка монтирования aufs
# или номер aufs созданного make-ovl
# $2 - спрятать rw слой (любое значение)
ls_aufs() { # hide from --help
    if [ "$1" ] ; then
        expr "$1" + 1 >/dev/null 2>&1 && AUFS=${TMPUNION}/$1/UNION
        [ -d $1 ] && AUFS=$1
    else
        AUFS='/'
    fi
    # получаем значение для переменной si, присваивает eval
    eval $(grep -E '^aufs[[:space:]]+'${AUFS}'[[:space:]]+' /proc/mounts |sed 's/,/\n/g' |grep si=)
    if [ $2 ] ; then
        cat  $(ls /sys/fs/aufs/si_$si/br* |sort -V) |grep ${SYSMNT} |sed 's/=.*$//' |sed 1d
    else
        cat  $(ls /sys/fs/aufs/si_$si/br* |sort -V) |grep ${SYSMNT} |sed 's/=.*$//'
    fi
}

ls_ovl() {  # hide from --help
    local OVL
    if [[ "$1" ]]; then
        [[ "$1" =~ ^[0-9]+$ ]] && OVL="${TMPUNION}/$1/UNION"
        [[ -d "$1" ]] && OVL="$1"
    else
        OVL="$SYSMNT"
    fi

    getLayers() {
        awk -v ovl="$OVL" -v layer="$1" '
            $1 == "overlay" && index($0, ovl) {
                if (match($0, layer "=([^,]+)", arr)) {
                    gsub(":", "\n", arr[1])
                    print arr[1]
                }
            }
        ' /proc/mounts
    }

    local UPPERDIR LOWERDIRS
    UPPERDIR=$(getLayers upperdir)
    LOWERDIRS=$(getLayers lowerdir)

    [[ $2 ]] || echo "${UPPERDIR}"
    echo "${LOWERDIRS}"
}

ls_union() {
    # $1 - aufs/overlayfs  mount point {default "/"}
    # return - list of layers
    UNIONFS=$(check_union)
    ls_$UNIONFS $@
    }

exitmsg() { # hide from --help
    # $1 - сообщение
    # $2 - exitcode
    # $3 - если есть $3 не завершать скрипт
    if [ "$2" -ne 0 ] ; then
        echo -ne "$color_red" ; echo -e "$1"
        echo "exitcode: $2"
        echo -ne "$color_default"
        [ "$3" ] || exit $2
    fi
}

prepare_layers() { # hide from --help
    # MASK="шаблон в формате grep -E, для поиска слоев в основной overlayfs"
    # INDEX="номер unionfs, если создана make_union. По умолчанию - пусто"
    # LIST="список папок и файлов (squashfs, img c ext2/3/4)
    local a layer MASK='' LIST='' UNION='/' INDEX='' LINDEX='none'
    local TMPDIR=${TMPSQFS}
    for a in $@ ; do
        local |grep -q "$(echo $a |cut  -f1 -d=)=" &&  eval $a
    done
    mkdir -p ${TMPDIR}/0
    lastN=$(ls -1 ${TMPDIR} |sort -n |tail -n1)
    N=$(( $lastN + 1 ))
    if [ -z "$INDEX" ] ; then
        LSOVLPAR="$UNION"
    else
        LSOVLPAR="$INDEX"
    fi
    [ -z "$MASK" ] || layers=$(ls_union "$LSOVLPAR" |grep -E "$MASK")
    if ! [ -z $LIST ] ; then
        for layer in $(echo "$LIST" |sed 's#,# #g') ; do
        mpoint="$TMPDIR/$N/$(basename $layer)"
        mkdir -p $mpoint
        case $(file -b "$layer") in
            *directory)
                mount --bind "$layer" "$mpoint" 2>/dev/null ;;
            Squashfs*4.0*|Linux*ext*)
                mount -o loop "$layer" "$mpoint" 2>/dev/null ;;
            *) return ${LINENO} ;;
        esac
        layers="$layers $mpoint"
    done
    LINDEX=$N
    fi
    [ -z "$MASK" -a -z "$LIST" ] && layers=$(ls_union "$LSOVLPAR")
    [ -z "$layers" ] && return ${LINENO}
    echo "LINDEX=$LINDEX LOWERDIRS=$(echo $layers |sed  -e 's# #:#g')"
    echo LAYERS: 1>&2
    for a in $layers ;do
       echo ":::$a" 1>&2
    done
}

destroy_layers() {  # hide from --help
    # $1 или STARTINDEX=N начальный или единственный номер для уничтожения слоев
    # $2 или ENDINDEX=M конечный индекс
    local STARTINDEX='' ENDINDEX='' a n
    for a in $@ ; do
        if local |grep -q "$(echo $a |cut  -f1 -d=)=" ; then
            eval $a
        else
            if [ -z $STARTINDEX ] ; then
                STARTINDEX=$a
                continue
            fi
            [ -z $ENDINDEX ] && ENDINDEX=$a
        fi
    done
    [ -z $ENDINDEX ] && ENDINDEX="$STARTINDEX"
    expr $1 + 1 >/dev/null 2>&1 || return ${LINENO}
    for n in $(seq $STARTINDEX $ENDINDEX) ; do
        [ -d ${TMPSQFS}/$n ]  || continue
        echo -n .
        for a in $(ls -1 ${TMPSQFS}/$n) ; do
            umount ${TMPSQFS}/$n/$a 2>/dev/null
        done
        [ -z "$(find ${TMPSQFS}/$n -type f)" ] && rm -rf ${TMPSQFS}/$n || return ${LINENO}
    done
    echo ''
}

clear() {
    # remove lib mount artifacts
    #
    local INDEX LINDEX
    LINDEX=$(ls -1 ${TMPSQFS}/ | wc -l)
    INDEX=$(ls -1 ${TMPUNION}/ | wc -l)
    destroy_union 1 $INDEX
    destroy_layers 1 $LINDEX
}

make_list() { # hide from --help
    local SOURCE='' DEST='' NAME='' SPEC='' DEPS=''
    for a in $@ ; do
        local |grep -q "$(echo $a |cut  -f1 -d=)=" &&  eval "$a"
    done
    [ "$SOURCE" ] || return ${LINENO}
    source_root=$(realpath $SOURCE)
    dest_root="$source_root"
    [ "$DEST" ] && dest_root="$(realpath "$DEST")"
    pack_name="$(basename "${source_root%.$EXT}")"
    [ $NAME ] && pack_name=$NAME
    dest_dir="${dest_root}${METAINFODIR}/mount/${pack_name}/"
    mkdir -p $dest_dir
if [ -d "${source_root}${METAINFODIR}/mount" -a $(ls -1 "${source_root}${METAINFODIR}/mount" 2>/dev/null |wc -l) -gt 1 ] ; then
    mkdir -p "${dest_dir}submod"
    cp  -fax "${source_root}${METAINFODIR}/mount"   /tmp/
    mv  -f /tmp/mount "${dest_dir}submod/" &&  rm -rf /tmp/mount
    for submod in $(ls -1 ${source_root}${METAINFODIR}/mount/ 2>/dev/null); do
        rm -rf ${dest_root}${METAINFODIR}/mount/$submod
    done
fi
[ ! -z $SPEC -a -f $SPEC ] && cp "$SPEC" ${dest_dir}/
find "${source_root}" ! -type d        | sed 's#'"$source_root"'##' |grep -E -v "$METAINFODIR" > "${dest_dir}/pfs.files"
find "${source_root}" -type d -empty   | sed 's#'"$source_root"'##' |grep -E -v "$METAINFODIR" > "${dest_dir}/pfs.dirs.empty"
[ "$DEPS" ] && echo "$DEPS" > "${dest_dir}/pfs.depends"
return 0
}

root_only() { # hide from --help
    if [ $(id -un) != "root" ] ; then
        echo  "You must be root !!!"
        exit 1
    fi
}

update_caches() {
    # update caches, icons, menus, etc after load/unload modules
    #
    [ "$(find ${1}/usr{,/local,/X11*}/{lib64,lib} -type f -name "*.so" 2>/dev/null)" ] && ldconfig #&
    [ -d "$1/usr/share/mime/" ] && update-mime-database /usr/share/mime &
    [ -d "$1/usr/lib/gdk-pixbuf-2.0/" ]  && gdk-pixbuf-query-loaders --update-cache &
    [ -d "$1/usr/share/icons/hicolor/" ] && gtk-update-icon-cache -f -i -q /usr/share/icons/hicolor &
    [ -d "$1/usr/share/glib-2.0/schemas/" ]  && glib-compile-schemas /usr/share/glib-2.0/schemas/ &
    if [ -d "$1/usr/share/applications/" -o -d "$1/usr/local/share/applications/" ] ; then
        update-desktop-database -q &
        touch /usr/share/applications/screensavers &
    fi
    for font_dir in /usr/share/fonts{,/default}/TTF  /usr/X11R6/lib/X11/fonts/TTF ; do
        if [ -d "$1/${font_dir}" ]; then
            fcneed=on
            mkfontscale ${font_dir} &
            mkfontdir ${font_dir} &
        fi
    done
    [ $fcneed ] && fc-cache -f -s &
}

clear_CHANGES() { # hide from --help
    rm -rf "$CHANGES/{dev,proc,sys,tmp,run}" >/dev/null
    rm -rf "$CHANGES"/var/tmp "$CHANGES"/var/cache/{dnf,ldconfig} "$CHANGES"/etc/ld.so.cache 2>/dev/null
    rm -rf "$CHANGES"/var/lib/menu "$CHANGES"/var/log "$CHANGES"/usr/share/applications/mimeinfo.cache 2>/dev/null
    find "$CHANGES"/usr/share/icons -type f -name icon-theme.cache 2>/dev/null | xargs rm -f
    rmdir "$CHANGES"/{dev,proc,sys,lost+found} "$CHANGES/var/cache" "$CHANGES/var/lib" "$CHANGES/var" "$CHANGES/etc" 2>/dev/null
    if [ "${erase:-no}" != "yes" ]; then
        find "$CHANGES"  -name .wh..* | xargs rm -rf
        find "$CHANGES"  -xtype c -perm 0000 -size 0c | xargs rm -rf
    fi
}

# $1 файл (локальный, http, ftp, rsync)
# $2 файл для сохранения
# вернет имя файла с путем
getfile() {
    # $1 - file {local, ftp, http, rsync}
    # $2 - file name to save
    local file='/'
    geturl() {
        if  echo "$1" | grep -E -q "^rsync://[a-z0-9./-]*" ; then
            eval rsync -avzz --progress "$1" "$2" 1>&2 $DEVNULL   || rm -f $2
        else
            eval wget -t 2 -O "$2"  "$1"  1>&2  $DEVNULL || rm -f $2
        fi
    }
    mkdir -p "$(dirname $2)"
    if echo "$1" | grep -E -q "^file://[a-z0-9./-]*" ; then
        file="$(echo $1 |sed 's#^file:/##')"
    elif echo "$1" | grep -E -q "^[a-z]{3,6}://[a-z0-9./-]*"; then
        geturl "$1" "$2"
    else
        file="$1"
    fi
    [ -f "$file" -a "$(realpath "$file")" != "$(realpath "$2")" ] && cp -a "$file" "$2"
    [ -f "$2" ] && echo "$2"
}

# $1 корень фс где искать файлы с метаинформацией от модулей
# $2,3,4,5... список файлов которые вывести
builders_info() {
    # info about dnf2mod, chroot2mod current builders
    #
    for builder in dnf2mod chroot2mod ; do
        if [ -d ${TMP}/var/lib/$builder ] ; then
            for package in $(ls -1 $1/var/lib/${builder}/) ; do
                echo ''
                for f in  $(ls -1 $1/var/lib/$builder/$package/) ; do
                    echo "$@" |grep -qw "$f" || continue
                    echo -en "$builder ${f} $package: "
                    echo $(cat  $1/var/lib/$builder/${package}/$f)
                done
            done
        fi
    done
}

find_syspart() { # hide from --help
    grep -m1 " $SYSMNT/layer-base/0 " /proc/self/mountinfo | awk '{ print $10 }'
}

find_sysdir() { # hide from --help
    DISTRDEV=$(find_syspart)
    [ -e "$DISTRDEV" ] || return 1
    DISTRMPOINT=$(findmnt -lnf $DISTRDEV -o TARGET)
    DISTRPATH=${DISTRMPOINT}$(grep -m1 " $SYSMNT/layer-base/0 " /proc/self/mountinfo | awk '{ print $4 }')
    [ -f "$DISTRPATH/vmlinuz" -a -d "$DISTRPATH/base" ] || return  1
    echo "$DISTRPATH"
}

find_distrpath() {
    DISTRDEV=$(grep -m1 " $SYSMNT/layer-base/0 " /proc/self/mountinfo | awk '{ print $10 }')
    DISTRMPOINT=$(findmnt -lnf $DISTRDEV -o TARGET)
    DISTRPATH=${DISTRMPOINT}$(grep -m1 " $SYSMNT/layer-base/0 " /proc/self/mountinfo | awk '{ print $4 }')
    [ -f "$DISTRPATH/vmlinuz" -a -d "$DISTRPATH/base" ] || return  1
    echo "$DISTRPATH"
}

remount() { # hide from --help
    # $1 - mount parameter
    # remount system dir {see find_sys} with $1 parameter
    par="$1"
    mount -o remount,$par $(find_syspart)
    mount -o remount,$par ${SYSMNT}/layer-base/0
}

set_order(){
    local union="$1"
    local changes="$2"
    local util="$3"
    local modname="$4"
    local deps_file cur_order
    local deps_file="/var/lib/${util}/${modname}/stack.order"
    mkdir -p $(dirname ${changes}/${deps_file})
    cur_order=$(cat ${union}/var/lib/{chroot2mod,dnf2mod}/*/stack.order 2>/dev/null | awk '!seen[$0]++')
    echo -e "${cur_order}\n${modname}" |sed '/^$/d' > "${changes}/${deps_file}"
}

parents2mask() {
    local layer=$1
    local MASK
    if ! [ -d $layer ] ; then
        layer=$(find "$BUNDLESDIR" -maxdepth 1 -mindepth 1 -type d |grep "$layer" |head -n 1)
    fi
    [ -d $layer ] && MASK=$(cat ${layer}/var/lib/{chroot2mod,dnf2mod}/*/stack.order 2>/dev/null | \
                            awk '!seen[$0]++' |tr -s '\n' '|' | \
                            sed -e "s/^|*/'/" -e "s/|*$/'/")
    if ! [ -z $MASK ] ; then
		echo "$MASK"
		return
    fi
    echo "Modules order not found using default mask" 1>&2
    echo "$DEFMASK"
}

if [ "$(basename $0)" = "lib" ] ;then
[ "$1" ] || HLP
[ "$1" == '--help' -o "$1" == '-h' ] && HLP
command="$1"
shift
$command $@
exitmsg "$command  ERROR!!!" "$?"
fi

