# -*- mode: shell-script; coding: utf-8 -*-
#
# shell-tools
#
# Library of generally useful shell functions.
#
# Copyright (C) 2011-2023 Elmar Hoffmann
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#

. /lib/elho/sysexits.sh

# Print verbose message to standard output
# Has to be enabled by enable_verbose
verbose ()
{
    :
}

# Print verbose message to syslog
# Has to be enabled by enable_verbose
log_verbose ()
{
    :
}

# Enable verbose function outpug
enable_verbose ()
{
    verbose ()
    {
	(
	    IFS=' '
	    printf '%s\n' "$*"
	)
    }

    log_verbose ()
    {
	logger -p user.info -t "$(basename -- "$0")" -i -- "$@"
    }
}

# Print debugging message to standard error
# Has to be enabled using enable_debug function
debug ()
{
    :
}

# Print debugging message to syslog
# Has to be enabled using enable_debug function
log_debug ()
{
    :
}

# Enable debug function outpug
enable_debug ()
{
    debug ()
    {
	(
	    IFS=' '
	    printf >&2 '%s: %s\n' "$(basename -- "$0")" "$*"
	)
    }

    log_debug ()
    {
	logger -p user.debug -t "$(basename -- "$0")" -i -- "$@"
    }
}

# Print message to syslog
log_notice ()
{
    logger -p user.notice -t "$(basename -- "$0")" -i -- "$@"
}

# Print warning message to standard error
warn ()
{
    (
	IFS=' '
	printf >&2 '%s: Warning: %s\n' "$(basename -- "$0")" "$*"
    )
}

# Print warning message to syslog
log_warn ()
{
    logger -p user.warning -t "$(basename -- "$0")" -i -- 'Warning:' "$@"
}

# Print error message to standard error
error ()
{
    (
	IFS=' '
	printf >&2 '%s: Error: %s\n' "$(basename -- "$0")" "$*"
    )
}

# Print error message to syslog
log_error ()
{
    logger -p user.err -t "$(basename -- "$0")" -i -- 'Error:' "$@"
}

# check that given dot-separted vension number is at least the reference one
check_version ()
{
    local version="$1"
    local reference="$2"

    printf '%s\n' "${reference}" "${version}" \
	| sort --version-sort --check=quiet
}

# Count number of words in given arguments
words ()
{
    (
	IFS=' '
	printf '%s' "$*" | wc --words
    )
}

# Append word to given words, separated by a space character
append_word ()
{
    local words="$1"
    local word="$2"

    if [ -n "${words}" ]; then
	if [ -n "${word}" ]; then
	    printf '%s %s\n' "${words}" "${word}"
	else
	    printf '%s\n' "${words}"
	fi
    else
	printf '%s\n' "${word}"
    fi
}

# Print given arguments, quoting the ones with embedded whitespace
print_args ()
{
    # return nothing for single empty argument and no arguments
    if [ $# -le 1 -a -z "$1" ]; then
	return
    fi

    local output=''
    for arg in "$@"; do
	if printf '%s' "${arg}" | grep --quiet --null-data '[[:space:]]'; then
	    output=$(append_word "${output}" "\"${arg}\"")
	else
	    output=$(append_word "${output}" "${arg}")
	fi
    done
    printf '%s\n' "${output}"
}

# Return set of items based on the given arguments, omitting duplicates
make_set ()
{
    local set_=''
    for item in "$@"; do
	set_=$(set_add "${set_}" "${item}")
    done
    set_print "${set_}"
}

# Return given set of items with the given item added, if not already present
set_add ()
{
    local set_="$1"
    local item="$2"

    if [ -n "${set_}" ]; then
	if printf '%s\n' "${set_}" | \
    	    grep --quiet --fixed-strings --line-regexp -- "${item}"; then
	    printf '%s\n' "${set_}"
	    return
	fi
	printf '%s\n%s\n' "${set_}" "${item}"
    else
	printf '%s\n' "${item}"
    fi
}

# Return the number of items in the given set
set_items ()
{
    local set_="$1"

    set_print "${set_}" | wc --lines
}

# Print the given set of items to standard output, one item per line
set_print ()
{
    local set_="$1"

    if [ -n "${set_}" ]; then
	printf '%s\n' "${set_}"
    fi
}

# Return the given number of items of the given set of items
set_head ()
{
    local set_="$1"
    local count="$2"

    set_print "${set_}" \
	| head --lines="${count}"
}

# Return the union of the two given sets of items
set_union ()
{
    local seta="$1"
    local setb="$2"

    set_print "${seta}"
    set_print "${setb}" | while read -r itemb; do
    	if set_print "${seta}" | \
    	    grep --quiet --fixed-strings --line-regexp -- "${itemb}"; then
	    continue
	fi
	printf '%s\n' "${itemb}"
    done
}

# Return the intersection of the two given sets of items
set_intersect ()
{
    local seta="$1"
    local setb="$2"

    set_print "${setb}" | while read -r itemb; do
    	set_print "${seta}" | \
    	    grep --fixed-strings --line-regexp -- "${itemb}"
    done
}

# Return the difference of the two given sets of items
set_difference ()
{
    local seta="$1"
    local setb="$2"

    set_print "${seta}" | while read -r itema; do
    	if set_print "${setb}" | \
    	    grep --quiet --fixed-strings --line-regexp -- "${itema}"; then
	    continue
	fi
	printf '%s\n' "${itema}"	
    done
}

# Append trap action to given condition
add_trap ()
{
    local action="$1"
    local condition="$2"

    debug "Adding trap for condition ${condition}: ${action}"

    # workaround POSIX violation of shells that execute $(trap)
    # in a subshell, leading to always empty result
    local traps
    local traps_file="$(mktemp -t -- "$(basename -- "$0")-traps-XXXXXX")"
    {
	rm -f -- "${traps_file}"
	trap
	traps=$(cat)
    } > "${traps_file}" < "${traps_file}"

    if [ -n "${traps}" ]; then
	# find existing actions associated with given condition, avoiding
	# regex injection
	local old_action="$(printf '%s\n' "${traps}" \
	    | sed --quiet --expression="s/^trap[[:space:]]\+--[[:space:]]\+'\([^']\+\)'[[:space:]]\+\([^[:space:]]\+\)\$/C:\2\nA:\1/p" \
	    | grep --after-context=1 --fixed-strings --line-regexp "C:${condition}" \
	    | sed --quiet --expression='s/^A://p')"

	if [ -n "${old_action}" ]; then
	    debug "Prepending to existing trap action: ${old_action}"
	    action="${action}; ${old_action}"
	fi
    fi
    trap "${action}" "${condition}"
}

# Restore saved trap actions
restore_traps ()
{
    local saved_traps="$1"

    debug "Restoring trap actions..."

    # workaround POSIX violation of shells that execute $(trap)
    # in a subshell, leading to always empty result
    local traps
    local traps_file="$(mktemp -t -- "$(basename -- "$0")-traps-XXXXXX")"
    {
	rm -f -- "${traps_file}"
	trap
	traps=$(cat)
    } > "${traps_file}" < "${traps_file}"

    if [ -n "${traps}" ]; then
	local conditions=$(printf '%s\n' "${traps}" \
	    | sed --quiet --expression="s/^trap[[:space:]]\+--[[:space:]]\+'[^']\+'[[:space:]]\+\([^[:space:]]\+\)\$/\1/p")

	debug "Removing existing traps for conditions: $(printf '%s' "${conditions}" | tr '\n' ' ')"
	for condition in ${conditions}; do
	    trap - "${condition}"
	done
    fi

    if [ -n "${saved_traps}" ]; then
	debug "Setting saved traps:" "$(printf '\n%s\n' "${saved_traps}")"
	eval "${saved_traps}"
    fi
}

# Terminate with an error message if not running with root privileges
require_root ()
{
    if [ $(id --user) -ne 0 ]; then
	error "This program needs to be run with root privileges."
	exit 1
    fi
}

# Test whether given path is a mounted mountpoint
is_mounted ()
{
    local path="$1"

    debug "Testing whether '${path}' is currently mounted"
    if [ -z "${path}" ]; then
	return 1
    fi
    mount \
	| sed --quiet --expression='s/^\([^[:space:]]\+\)[[:space:]]\+on[[:space:]]\+\([^[:space:]]\+\)[[:space:]]\+type[[:space:]]\+.*$/\1\n\2/gp' \
	| grep --quiet --fixed-strings --line-regexp "${path}"
}

# Test whether given name would be a valid LVM LV or VG name
is_valid_lvmname ()
{
    local name="$1"

    printf '%s\n' "${name}" | grep --quiet \
        --extended-regexp '^[0-9A-Za-z._+][0-9A-Za-z._+-]*$'
}

# Invoke dd(1) filtering out verbose output
quiet_dd ()
{
    debug "Invoking 'dd $(print_args "$@")'"

    dd "$@" status=none
}
