# -*- mode: shell-script; coding: utf-8 -*-
#
# shell-tools
#
# Library of generally useful shell functions.
#
# Copyright (C) 2011,2012 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
#

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

# Enable verbose function outpug
enable_verbose ()
{
    verbose ()
    {
	local message="$@"

	echo "${message}"
    }
}

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

# Enable debug function outpug
enable_debug ()
{
    debug ()
    {
	local message="$@"

	echo >&2 "$(basename "$0"): ${message}"
    }
}

# Print warning message to standard error
warn ()
{
    local message="$@"

    echo >&2 "$(basename "$0"): Warning: ${message}"
}

# Print error message to standard error
error ()
{
    local message="$@"

    echo >&2 "$(basename "$0"): Error: ${message}"
}

# Count number of words in given arguments
words ()
{
    local words="$@"

    echo "${words}" | 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
	    echo "${words} ${word}"
	else
	    echo "${words}"
	fi
    else
	echo "${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
    echo "${output}"
}

# Return set of words in the given arguments, omitting duplicates
make_set ()
{
    local words="$@"

    local set_=''
    for word in ${words}; do
	set_=$(set_add "${set_}" "${word}")
    done
    echo "${set_}"
}

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

    if [ -n "${set_}" ]; then
	for item in ${set_}; do
	    if [ "${item}" = "${word}" ]; then
		echo "${set_}"
		return
	    fi
	done
	echo "${set_} ${word}"
    else
	echo "${word}"
    fi
}

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

    echo "${set_}" | sed \
	--expression='s/^[[:space:]]\+//;s/[[:space:]]\+$//;s/[[:space:]]\+/\n/g' \
	| head --lines="${count}"
}

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

    echo "${seta}" $(set_difference "${setb}" "${seta}")
}

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

    for itema in ${seta}; do
	for itemb in ${setb}; do
	    if [ "${itema}" = "${itemb}" ]; then
		echo "${itema}"
		break
	    fi
	done
    done
}

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

    for itema in ${seta}; do
	local found=0
	for itemb in ${setb}; do
	    if [ "${itema}" = "${itemb}" ]; then
		found=1
		break
	    fi
	done
	if [ "${found}" -eq 0 ]; then
	    echo "${itema}"
	fi
    done
}

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

    debug "Adding trap for condition ${condition}: ${action}"
    local traps=$(trap)
    if [ -n "${traps}" ]; then
	# find existing actions associated with given condition, avoiding
	# regex injection
	local old_action=$(echo "${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..."
    local traps=$(trap)
    if [ -n "${traps}" ]; then
	local conditions=$(echo "${traps}" \
	    | sed --quiet --expression="s/^trap[[:space:]]\+--[[:space:]]\+'[^']\+'[[:space:]]\+\([^[:space:]]\+\)\$/\1/p")

	debug "Removing existing traps for conditions: $(echo ${conditions})"
	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"

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

_quiet_dd_cleanup ()
{
    local dd_output="$1"

    if [ -f "${dd_output}" ]; then
	sed --expression='/^[0-9]\++[0-9]\+ records \(in\|out\)$/d' "${dd_output}" >&2
	rm -f "${dd_output}"
    fi
}

# Invoke dd(1) filtering out verbose output
quiet_dd ()
{
    local dd_output=$(mktemp -t "$(basename "$0")-dd-XXXXXX")
    debug "Using temporary file '${dd_output}' for dd(1) output"

    local saved_traps=$(trap)
    add_trap "_quiet_dd_cleanup \"${dd_output}\"" EXIT
    debug "Invoking 'dd $(print_args "$@")'"

    dd "$@" status=noxfer > "${dd_output}" 2>&1
    _quiet_dd_cleanup "${dd_output}"

    restore_traps "${saved_traps}"
}
