# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
#   Name: infrastructure.sh - part of the BeakerLib project
#   Description: Mounting, backup & service helpers
#
#   Author: Petr Muller <pmuller@redhat.com>
#   Author: Jan Hutar <jhutar@redhat.com>
#   Author: Petr Splichal <psplicha@redhat.com>
#   Author: Ales Zelinka <azelinka@redhat.com>
#   Author: Karel Srot <ksrot@redhat.com>
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
#   Copyright (c) 2008-2013 Red Hat, Inc. All rights reserved.
#
#   This copyrighted material is made available to anyone wishing
#   to use, modify, copy, or redistribute it subject to the terms
#   and conditions of the GNU General Public License version 2.
#
#   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 Street, Fifth Floor,
#   Boston, MA 02110-1301, USA.
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

: <<'=cut'
=pod

=head1 NAME

BeakerLib - infrastructure - mounting, backup and service helpers

=head1 DESCRIPTION

BeakerLib functions providing checking and mounting NFS shares, backing up and
restoring files and controlling running services.

=head1 FUNCTIONS

=cut

. "$BEAKERLIB"/logging.sh
. "$BEAKERLIB"/testing.sh
. "$BEAKERLIB"/storage.sh

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#   Internal Stuff
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

__INTERNAL_rlRunsInBeaker() {
	if [ -n "$JOBID" ] && [ -n "$TESTID" ]
	then
	  return 0
	else
	  return 1
	fi
}

__INTERNAL_CheckMount(){
    local MNTPATH="$1"
    local MNTHOST="$2"
    local OPTIONS="$3"
    local RETCODE=0

    # if a host was specified, the semantics is "is PATH mounted on HOST?"
    # if a host is not specified, the semantics is "is PATH mounted somewhere?"
    if [ -z "$MNTHOST" ]
    then
      mount | grep "on ${MNTPATH%/} type"
      RETCODE=$?
    else
      mount | grep "on ${MNTPATH%/} type" | grep -E "^$MNTHOST[ :/]"
      RETCODE=$?
    fi

    # check if the mountpoint is mounted with given options
    if [ $RETCODE -eq 0 ] && [ -n "$OPTIONS" ]
    then
      IFS=',' read -ra OPTS <<< "$OPTIONS"
      for option in "${OPTS[@]}"; do
        mount | grep "on ${MNTPATH%/} type" | grep "[(,]$option[),]"
        [ $? -eq 0 ] || RETCODE=2
      done
    fi

    return $RETCODE
}

__INTERNAL_Mount(){
    local SERVER=$1
    local MNTPATH=$2
    local WHO=$3
    local OPTIONS=$4

    if __INTERNAL_CheckMount "$MNTPATH"
    then
      if [[ -z "$OPTIONS" ]]; then
        rlLogInfo "$WHO already mounted: success"
        return 0
      else
        [[ "$OPTIONS" =~ remount ]] || OPTIONS="remount,$OPTIONS"
      fi
    elif [ ! -d "$MNTPATH" ]
    then
        rlLogInfo "$WHO creating directory $MNTPATH"
        mkdir -p "$MNTPATH"
    fi
    if [ -z "$OPTIONS" ] ; then
        rlLogInfo "$WHO mounting $SERVER on $MNTPATH with default mount options"
        mount "$SERVER" "$MNTPATH"
    else
        rlLogInfo "$WHO mounting $SERVER on $MNTPATH with custom mount options: $OPTIONS"
        mount -o "$OPTIONS" "$SERVER" "$MNTPATH"
    fi
    if [ $? -eq 0 ]
    then
        rlLogInfo "$WHO success"
        return 0
    else
        rlLogWarning "$WHO failure"
        return 1
    fi
}

__INTERNAL_rlCleanupGenFinal()
{
    local __varname=
    local __newfinal="$__INTERNAL_CLEANUP_FINAL".tmp
    if [ -e "$__newfinal" ]; then
        rm -f "$__newfinal" || return 1
    fi
    touch "$__newfinal" || return 1

    # head
    cat > "$__newfinal" <<EOF
#!/bin/bash
EOF

    # environment
    # - variables (local, global and env)
    for __varname in $(compgen -v); do
        __varname=$(declare -p "$__varname")
        # declaration of a readonly variable may fail if a variable with
        # the same name is already declared - silently ignore it
        if expr + "$__varname" : "declare -[^r]*r[^r]* " >/dev/null; then
            echo "$__varname" "2>/dev/null" >> "$__newfinal"
        else
            echo "$__varname" >> "$__newfinal"
        fi
    done
    # - functions
    declare -f >> "$__newfinal"

    # journal/phase start
    cat >> "$__newfinal" <<EOF
rlJournalStart
rlPhaseStartCleanup
EOF

    # body
    cat "$__INTERNAL_CLEANUP_BUFF" >> "$__newfinal"

    # tail
    cat >> "$__newfinal" <<EOF
rlPhaseEnd
rlJournalEnd
EOF

    chmod +x "$__newfinal" || return 1
    # atomic move
    mv "$__newfinal" "$__INTERNAL_CLEANUP_FINAL" || return 1
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlMount
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head2 Mounting

=head3 rlMount

Create mount point (if neccessary) and mount a NFS share.

    rlMount [-o MOUNT_OPTS] server share mountpoint

=over

=item server

NFS server hostname.

=item share

Shared directory name.

=item mountpoint

Local mount point.

=item MOUNT_OPTS

Mount options.

=back

Returns 0 if mounting the share was successful.

=cut

rlMount() {
    local OPTIONS=''
    local GETOPT=$(getopt -q -o o: -- "$@"); eval set -- "$GETOPT"
    while true; do
      case $1 in
        --) shift; break; ;;
        -o) shift; OPTIONS="$1"; ;;
      esac ; shift;
    done
    local SERVER=$1
    local REMDIR=$2
    local LOCDIR=$3
    __INTERNAL_Mount "$SERVER:$REMDIR" "$LOCDIR" "[MOUNT $LOCDIR]" "$OPTIONS"
    return $?
}

# backward compatibility
rlMountAny() {
    rlLogWarning "rlMountAny is deprecated and will be removed in the future. Use 'rlMount' instead"
    rlMount "$1" "$2" "$2";
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlCheckMount
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head3 rlCheckMount

Check either if a directory is a mountpoint, if it is a mountpoint to a
specific server, or if it is a mountpoint to a specific export on a specific
server and if it uses specific mount options.

    rlCheckMount [-o MOUNT_OPTS] mountpoint
    rlCheckMount [-o MOUNT_OPTS] server mountpoint
    rlCheckMount [-o MOUNT_OPTS] server share mountpoint

=over

=item mountpoint

Local mount point.

=item server

NFS server hostname

=item share

Shared directory name

=item MOUNT_OPTS

Mount options to check (comma separated list)

=back

With one parameter, returns 0 when specified directory exists and is a
mountpoint. With two parameters, returns 0 when specific directory exists, is a
mountpoint and an export from specific server is mounted there. With three
parameters, returns 0 if a specific shared directory is mounted on a given
server on a given mountpoint

If the -o option is provided, returns 0 if the mountpoint uses all the given
options, 2 otherwise.

=cut

rlCheckMount() {
    local MNTOPTS=''
    local GETOPT=$(getopt -q -o o: -- "$@"); eval set -- "$GETOPT"
    while true; do
      case $1 in
        --) shift; break; ;;
        -o) shift; MNTOPTS="$1"; ;;
      esac ; shift;
    done

    local LOCPATH=""
    local REMPATH=""
    local SERVER=""
    local MESSAGE="a mount point"

    case $# in
      1)  LOCPATH="$1" ;;
      2)  LOCPATH="$2"
          SERVER="$1"
          MESSAGE="$MESSAGE to $SERVER" ;;
      3)  LOCPATH="$3"
          REMPATH=":$2"
          SERVER="$1"
          MESSAGE="$MESSAGE to ${SERVER}${REMPATH}" ;;
      *)  rlLogError "rlCheckMount: Bad parameter count"
          return 1 ;;
    esac

    __INTERNAL_CheckMount "${LOCPATH}" "${SERVER}${REMPATH}" "$MNTOPTS"

    local RETCODE=$?
    if [ $RETCODE -eq 0  ] ; then
        rlLogDebug "rlCheckMount: Directory $LOCPATH is $MESSAGE"
    elif [ $RETCODE -eq 2  ] ; then
        local USEDMNTOPTS=$(mount | grep "on ${LOCPATH%/} type" | awk '{print $6}')
        rlLogDebug "rlCheckMount: Directory $LOCPATH mounted with $USEDMNTOPTS, some of $MNTOPTS missing"
    else
        rlLogDebug "rlCheckMount: Directory $LOCPATH is not $MESSAGE"
    fi
    return $RETCODE
}

# backward compatibility
rlAnyMounted() {
    rlLogWarning "rlAnyMounted is deprecated and will be removed in the future. Use 'rlCheckMount' instead"
    rlCheckMount "$1" "$2" "$2"
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlAssertMount
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head3 rlAssertMount

Assertion making sure that given directory is a mount point, if it is a
mountpoint to a specific server, or if it is a mountpoint to a specific export
on a specific server and if it uses specific mount options.

    rlAssertMount [-o MOUNT_OPTS]  mountpoint
    rlAssertMount [-o MOUNT_OPTS]  server mountpoint
    rlAssertMount [-o MOUNT_OPTS]  server share mountpoint

=over

=item mountpoint

Local mount point.

=item server

NFS server hostname

=item share

Shared directory name

=item MOUNT_OPTS

Mount options to check (comma separated list)

=back

With one parameter, returns 0 when specified directory exists and is a
mountpoint. With two parameters, returns 0 when specific directory exists, is a
mountpoint and an export from specific server is mounted there. With three
parameters, returns 0 if a specific shared directory is mounted on a given
server on a given mountpoint. Asserts PASS when the condition is true.

If the -o option is provided, asserts PASS if the above conditions are met and
the mountpoint uses all the given options.

=cut

rlAssertMount() {
    local MNTOPTS=''
    local GETOPT=$(getopt -q -o o: -- "$@"); eval set -- "$GETOPT"
    while true; do
      case $1 in
        --) shift; break; ;;
        -o) shift; MNTOPTS="$1"; ;;
      esac ; shift;
    done

    if [ -n "MNTOPTS" ] ; then
        local OPTSMESSAGE=" using ($MNTOPTS)"
    fi

    local LOCPATH=""
    local REMPATH=""
    local SERVER=""
    local MESSAGE="a mount point"

    case $# in
      1)  LOCPATH="$1" ;;
      2)  LOCPATH="$2"
          SERVER="$1"
          MESSAGE="$MESSAGE to $SERVER" ;;
      3)  LOCPATH="$3"
          REMPATH=":$2"
          SERVER="$1"
          MESSAGE="$MESSAGE to ${SERVER}${REMPATH}" ;;
      *)  rlLogError "rlAssertMount: Bad parameter count"
          return 1 ;;
    esac

    __INTERNAL_CheckMount "${LOCPATH}" "${SERVER}${REMPATH}" "$MNTOPTS"
    __INTERNAL_ConditionalAssert "Mount assert: Directory $LOCPATH is ${MESSAGE}${OPTSMESSAGE}" $?
    return $?
}


: <<'=cut'
=pod

=head3 rlHash, rlUnhash

Hashes/Unhashes given string and prints the result to stdout.

    rlHash [--decode] [--algorithm HASH_ALG] --stdin|STRING
    rlUnhash [--algorithm HASH_ALG] --stdin|STRING

=over

=item --decode

Unhash given string.

=item --algorithm

Use given hash algorithm.
Currently supported algorithms:
  base64
  base64_ - this is standard base64 where '=' is replaced by '_'
  hex

Defaults to hex.
Default algorithm can be override using global variable rlHashAlgorithm.

=item --stdin

Get the string from stdin.

=item STRING

String to be hashed/unhashed.

=back

Returns 0 if success.

=cut

rlHash() {
  local GETOPT=$(getopt -q -o a: -l decode,algorithm:,stdin -- "$@"); eval set -- "$GETOPT"
  local decode=0 alg="$rlHashAlgorithm" stdin=0
  while true; do
    case $1 in
      --)          shift; break ;;
      --decode)    decode=1 ;;
      -a|--algorithm) shift; alg="$1" ;;
      --stdin)     stdin=1 ;;
    esac
    shift
  done
  [[ "$alg" =~ ^(base64|base64_|hex)$ ]] || alg='hex'
  local text="$1" command

  case $alg in
    base64)
      if [[ $decode -eq 0 ]]; then
        command="base64 --wrap 0"
      else
        command="base64 --decode"
      fi
      ;;
    base64_)
      if [[ $decode -eq 0 ]]; then
        command="base64 --wrap 0 | tr '=' '_'"
      else
        command="tr '_' '=' | base64 --decode"
      fi
      ;;
    hex)
      if [[ $decode -eq 0 ]]; then
        command="od -A n -t x1 -v | tr -d ' \n\t'"
      else
        command="sed 's/\([0-9a-zA-Z]\{2\}\)/\\\x\1/g' | echo -en \"\$(cat -)\""
      fi
      ;;
  esac

  eval "( [[ \$stdin -eq 1 ]] && cat - || echo -n \"\$text\" ) | $command"
}

rlUnhash() {
  rlHash --decode "$@"
}


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlFileBackup
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head2 Backup and Restore

=head3 rlFileBackup

Create a backup of files or directories (recursive). Can be used
multiple times to add more files to backup. Backing up an already
backed up file overwrites the original backup.

    rlFileBackup [--clean] [--namespace name] [--missing-ok|--no-missing-ok] file [file...]

You can use C<rlRun> for asserting the result but keep in mind meaning of exit codes,
especialy exit code 8, if using without --clean option.

=over

=item --clean

If this option is provided (have to be first option of the command),
then file/dir backuped using this command (provided in next
options) will be (recursively) removed before we will restore it.
This option implies --missing-ok, this can be overridden by --no-missing-ok.

=item --namespace name

Specifies the namespace to use.
Namespaces can be used to separate backups and their restoration.

=item --missing-ok

Do not raise an error in case of missing file to backup.

=item --no-missing-ok

Do raise an error in case of missing file to backup.
This is useful with --clean. This behaviour can be globally
set by global variable BEAKERLIB_FILEBACKUP_MISSING_OK=false.

=item file

Files and/or directories to be backed up.

=back

Returns 0 if the backup was successful.
Returns 1 if parsing of parameters was not successful.
Returns 2 if no files specification was provided.
Returns 3 if BEAKERLIB_DIR variable is not set, e.g. rlJournalStart was not executed.
Returns 4 if creating of main backup destination directories was not successful.
Returns 5 if creating of file specific backup destination directories was not successful.
Returns 6 if the copy of backed up files was not successful.
Returns 7 if attributes of backedup files were not successfuly copied.
Returns 8 if backed up files does not exist. This can be suppressed based on other options.


=head4 Example with --clean:

    touch cleandir/aaa
    rlRun "rlFileBackup --clean cleandir/"
    touch cleandir/bbb
    ls cleandir/
    aaa   bbb
    rlRun "rlFileRestore"
    ls cleandir/
    aaa

=cut

__INTERNAL_FILEBACKUP_NAMESPACE="rlFileBackupNamespace"

__INTERNAL_FILEBACKUP_SET_PATH_CLEAN() {
  local path="$1"
  local path_encoded="$( rlHash -a hex "$path" )"

  local namespace="_$2"
  local namespace_encoded="$( rlHash -a hex "$namespace" )"

  rlLogDebug "rlFileBackup: Setting up the cleaning lists"
  rlLogDebug "rlFileBackup: Path [$path] Encoded [$path_encoded]"
  rlLogDebug "rlFileBackup: Namespace [$namespace] Encoded [$namespace_encoded]"

  local CURRENT="$( __INTERNAL_ST_GET --namespace="$__INTERNAL_FILEBACKUP_NAMESPACE" $namespace_encoded )"
  if [ -z "$CURRENT" ]
  then
    __INTERNAL_ST_PUT --namespace="$__INTERNAL_FILEBACKUP_NAMESPACE" $namespace_encoded $path_encoded
  else
    __INTERNAL_ST_PUT --namespace="$__INTERNAL_FILEBACKUP_NAMESPACE" $namespace_encoded "$CURRENT $path_encoded"
  fi
}

__INTERNAL_FILEBACKUP_CLEAN_PATHS() {
  local namespace="_$1"
  local namespace_encoded="$( rlHash -a hex "$namespace" )"
  local res=0

  rlLogDebug "rlFileRestore: Fetching clean-up lists for namespace: [$namespace] (encoded as [$namespace_encoded])"

  local PATHS="$( __INTERNAL_ST_GET --namespace="$__INTERNAL_FILEBACKUP_NAMESPACE" $namespace_encoded )"

  rlLogDebug "rlFileRestore: Fetched: [$PATHS]"

  local path
  for path in $PATHS
  do
    local path_decoded="$( rlUnhash -a hex "$path" )"
    echo "$path_decoded"
    if rm -rf "$path_decoded";
    then
      rlLogDebug "rlFileRestore: Cleaning $path_decoded successful"
    else
      rlLogError "rlFileRestore: Failed to clean $path_decoded"
      let res++
    fi
  done
  return $res
}

rlFileBackup() {
    local backup status file path dir failed selinux acl missing_ok="$BEAKERLIB_FILEBACKUP_MISSING_OK"

    local OPTS clean="" namespace=""

    # getopt will cut off first long opt when no short are defined
    OPTS=$(getopt -o "." -l "clean,namespace:,no-missing-ok,missing-ok" -- "$@")
    [ $? -ne 0 ] && return 1

    eval set -- "$OPTS"
    while true; do
        case "$1" in
            '--clean') clean=1; missing_ok="${missing_ok:-true}"; ;;
            '--missing-ok') missing_ok="true"; ;;
            '--no-missing-ok') missing_ok="false"; ;;
            '--namespace') shift; namespace="$1"; ;;
            --) shift; break ;;
        esac
        shift
    done;
    missing_ok="${missing_ok:-false}"

    # check parameter sanity
    if [ -z "$1" ]; then
        rlLogError "rlFileBackup: Nothing to backup... supply a file or dir"
        return 2
    fi

    # check if we have '--clean' option and save items if we have
    if [ "$clean" ]; then
        local file
        for file in "$@"; do
          rlLogDebug "rlFileBackup: Adding '$file' to the clean list"
          __INTERNAL_FILEBACKUP_SET_PATH_CLEAN "$file" "$namespace"
        done
    fi

    # set the backup dir
    if [ -z "$BEAKERLIB_DIR" ]; then
        rlLogError "rlFileBackup: BEAKERLIB_DIR not set, run rlJournalStart first"
        return 3
    fi

    # backup dir to use, append namespace if defined
    backup="$BEAKERLIB_DIR/backup${namespace:+-$namespace}"
    rlLogInfo "using '$backup' as backup destination"

    # create backup dir (unless it already exists)
    if [ -d "$backup" ]; then
        rlLogDebug "rlFileBackup: Backup dir ready: $backup"
    else
        if mkdir "$backup"; then
            rlLogDebug "rlFileBackup: Backup dir created: $backup"
        else
            rlLogError "rlFileBackup: Creating backup dir failed"
            return 4
        fi
    fi

    # do the actual backup
    status=0
    # detect selinux & acl support
    if selinuxenabled
    then
      selinux=true
    else
      selinux=false
    fi

    if setfacl -m u:root:rwx "$BEAKERLIB_DIR" &>/dev/null
    then
      acl=true
    else
      acl=false
    fi

    local file
    for file in "$@"; do
        # convert relative path to absolute, remove trailing slash
        file="$(echo "$file" | sed "s|^\([^/]\)|$PWD/\1|" | sed 's|/$||')"
        # follow symlinks in parent dir
        path="$(dirname "$file")"
        path="$(readlink -n -m "$path")"
        file="$path/$(basename "$file")"

        # bail out if the file does not exist
        if ! [ -e "$file" ]; then
            $missing_ok || {
              rlLogError "rlFileBackup: File $file does not exist."
              status=8
            }
            continue
        fi

        if [ -h "$file" ]; then
          rlLogWarning "rlFileBackup: Backing up symlink (not its target): $file"
        fi

        # create path
        if ! mkdir -p "${backup}${path}"; then
            rlLogError "rlFileBackup: Cannot create ${backup}${path} directory."
            status=5
            continue
        fi

        # copy files
        if ! cp -fa "$file" "${backup}${path}"; then
            rlLogError "rlFileBackup: Failed to copy $file to ${backup}${path}."
            status=6
            continue
        fi

        # preserve path attributes
        dir="$path"
        failed=false
        while true; do
            $acl && { getfacl --absolute-names "$dir" | setfacl --set-file=- "${backup}${dir}" || failed=true; }
            $selinux && { chcon --reference "$dir" "${backup}${dir}" || failed=true; }
            chown --reference "$dir" "${backup}${dir}" || failed=true
            chmod --reference "$dir" "${backup}${dir}" || failed=true
            touch --reference "$dir" "${backup}${dir}" || failed=true
            [ "$dir" == "/" ] && break
            dir=$(dirname "$dir")
        done
        if $failed; then
            rlLogError "rlFileBackup: Failed to preserve all attributes for backup path ${backup}${path}."
            status=7
            continue
        fi

        # everything ok
        rlLogDebug "rlFileBackup: $file successfully backed up to $backup"
    done

    return $status
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlFileRestore
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head3 rlFileRestore

Restore backed up files to their original location.
C<rlFileRestore> does not remove new files appearing after backup
has been made.  If you don't want to leave anything behind just
remove the whole original tree before running C<rlFileRestore>,
or see C<--clean> option of C<rlFileBackup>.

    rlFileRestore [--namespace name]

You can use C<rlRun> for asserting the result.

=over

=item --namespace name

Specifies the namespace to use.
Namespaces can be used to separate backups and their restoration.

=back

Returns 0 if backup dir is found and files are restored successfully.

Return code bits meaning XXXXX
                         |||||
                         ||||\_ error parsing parameters
                         |||\__ could not find backup directory
                         ||\___ files cleanup failed
                         |\____ files restore failed
                         \_____ no files were restored nor cleaned

=cut
#'

rlFileRestore() {
    local OPTS namespace="" backup res=0

    # getopt will cut off first long opt when no short are defined
    OPTS=$(getopt -o "n:" -l "namespace:" -- "$@")
    [ $? -ne 0 ] && return 1

    eval set -- "$OPTS"
    while true; do
        case "$1" in
            '--namespace') shift; namespace="$1"; shift ;;
            --) shift; break ;;
        esac
    done;

    # check for backup dir first, append namespace if defined
    backup="$BEAKERLIB_DIR/backup${namespace:+-$namespace}"
    if [ -d "$backup" ]; then
        rlLogDebug "rlFileRestore: Backup dir ready: $backup"
    else
        rlLogError "rlFileRestore: Cannot find backup in $backup"
        return 2
    fi

    # clean up if required
    local cleaned_files
    cleaned_files=$(__INTERNAL_FILEBACKUP_CLEAN_PATHS "$namespace") || (( res |= 4 ))

    # if destination is a symlink, remove the file first
    local filecheck
    for filecheck in $( find "$backup" | cut --complement -b 1-"${#backup}")
    do
      if [ -L "/$filecheck" ]
      then
        rm -f "/$filecheck"
      fi
    done

    # restore the files
    if [[ -n "$(ls -A "$backup")" ]]; then
      if cp -fa "$backup"/* /
      then
        rlLogDebug "rlFileRestore: restoring files from $backup successful"
      else
        rlLogError "rlFileRestore: failed to restore files from $backup"
        (( res |= 8 ))
      fi
    else
      if [[ -n "$cleaned_files" && $(( res & 4)) -eq 0 ]]; then
        rlLogDebug "rlFileRestore: there were just some files cleaned up"
      else
        rlLogError "rlFileRestore: no files were actually restored as there were no files backed up to $backup"
        (( res |= 16 ))
      fi
    fi

    return $res
}



# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlServiceStart
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head2 Services

Following routines implement comfortable way how to start/stop
system services with the possibility to restore them to their
original state after testing.

=head3 rlServiceStart

Make sure the given C<service> is running with fresh
configuration. (Start it if it is stopped and restart if it is
already running.) In addition, when called for the first time, the
current state is saved so that the C<service> can be restored to
its original state when testing is finished, see
C<rlServiceRestore>.

    rlServiceStart service [service...]

=over

=item service

Name of the service(s) to start.

=back

Returns number of services which failed to start/restart; thus
zero is returned when everything is OK.

=cut

__INTERNAL_SERVICE_NS="rlService"
__INTERNAL_SERVICE_STATE_SECTION="savedStates"
__INTERNAL_SERVICE_STATE_SECTION_PERSISTENCE="savedPersistentStates"

__INTERNAL_SERVICE_STATE_SAVE(){
  local serviceId=$1
  local serviceState=$2
  local persistence=${3:-"false"}

  if [[ "$persistence" == "false" ]]; then
    local section=$__INTERNAL_SERVICE_STATE_SECTION
  else
    local section=$__INTERNAL_SERVICE_STATE_SECTION_PERSISTENCE
  fi

  __INTERNAL_ST_PUT --namespace="$__INTERNAL_SERVICE_NS" --section="$section" $serviceId $serviceState
}

__INTERNAL_SERVICE_STATE_LOAD(){
  local serviceId=$1
  local persistence=${2:-"false"}

  if [[ "$persistence" == "false" ]]; then
    local section=$__INTERNAL_SERVICE_STATE_SECTION
  else
    local section=$__INTERNAL_SERVICE_STATE_SECTION_PERSISTENCE
  fi
  __INTERNAL_ST_GET --namespace="$__INTERNAL_SERVICE_NS" --section="$section" $serviceId
}

__INTERNAL_SERVICE_initial_state_save() {
  local service=$1
  local status=$2
  local persistence=${3:-"false"}

  # if the original state hasn't been saved yet, do it now!
  local serviceId="$(echo $service | sed 's/[^a-zA-Z0-9]//g')"

  local wasRunning="$( __INTERNAL_SERVICE_STATE_LOAD $serviceId "$persistence")"
  rlLogDebug "Previous state of service $service: $wasRunning"
  if [ -z "$wasRunning" ]; then
      # was running
      if [ $status == 0 ]; then
          rlLogDebug "rlServiceStart: Original state of $service saved (running)"
          __INTERNAL_SERVICE_STATE_SAVE $serviceId true "$persistence"
      # was stopped
      elif [ $status == 3 ]; then
          rlLogDebug "rlServiceStart: Original state of $service saved (stopped)"
          __INTERNAL_SERVICE_STATE_SAVE $serviceId false "$persistence"
      # weird exit status (warn and suppose stopped)
      else
          rlLogWarning "rlServiceStart: service $service status returned $status"
          rlLogWarning "rlServiceStart: Guessing that original state of $service is stopped"
          __INTERNAL_SERVICE_STATE_SAVE $serviceId false "$persistence"
      fi
  fi
}

# __INTERNAL_SERVICE operation service..
# returns last failure
__INTERNAL_SERVICE() {
  local res=0
  local op=$1
  shift
  while [[ -n "$1" ]]; do
    PAGER= service $1 $op || res=$?
    shift
  done
  return $res
}

# __INTERNAL_SERVICE_systemctl operation systemctl..
# returns last failure
__INTERNAL_SYSTEMCTL() {
  systemctl --no-pager "$@"
}

__INTERNAL_SERVICES_LIST="$BEAKERLIB_DIR/services_list"

rlServiceStart() {
    # at least one service has to be supplied
    if [ $# -lt 1 ]; then
        rlLogError "rlServiceStart: You have to supply at least one service name"
        return 99
    fi

    local failed=0

    # create file to store list of services, if it doesn't already exist
    touch $__INTERNAL_SERVICES_LIST

    local service
    for service in "$@"; do
        __INTERNAL_SERVICE status "$service"
        local status=$?

        # if the original state hasn't been saved yet, do it now!
        local serviceId="$(echo $service | sed 's/[^a-zA-Z0-9]//g')"
        local wasRunning="$( __INTERNAL_SERVICE_STATE_LOAD $serviceId)"
        if [ -z "$wasRunning" ]; then
            echo "$service" >> $__INTERNAL_SERVICES_LIST
            # was running
            if [ $status == 0 ]; then
                rlLogDebug "rlServiceStart: Original state of $service saved (running)"
                __INTERNAL_SERVICE_STATE_SAVE $serviceId true
            # was stopped
            elif [ $status == 3 ]; then
                rlLogDebug "rlServiceStart: Original state of $service saved (stopped)"
                __INTERNAL_SERVICE_STATE_SAVE $serviceId false
            # weird exit status (warn and suppose stopped)
            else
                rlLogWarning "rlServiceStart: service $service status returned $status"
                rlLogWarning "rlServiceStart: Guessing that original state of $service is stopped"
                __INTERNAL_SERVICE_STATE_SAVE $serviceId false
            fi
        fi

        # if the service is running, stop it first
        if [ $status == 0 ]; then
            rlLog "rlServiceStart: Service $service already running, stopping first."
            if ! __INTERNAL_SERVICE stop "$service"; then
                # if service stop failed, inform the user and provide info about service status
                rlLogWarning "rlServiceStart: Stopping service $service failed."
                rlLogWarning "Status of the failed service:"
                __INTERNAL_SERVICE status "$service" 2>&1 | while read line; do rlLog "  $line"; done
                ((failed++))
            fi
        fi

        # finally let's start the service!
        if __INTERNAL_SERVICE start "$service"; then
            rlLog "rlServiceStart: Service $service started successfully"
        else
            # if service start failed, inform the user and provide info about service status
            rlLogError "rlServiceStart: Starting service $service failed"
            rlLogError "Status of the failed service:"
            __INTERNAL_SERVICE status "$service" 2>&1 | while read line; do rlLog "  $line"; done
            ((failed++))
        fi
    done

    return $failed
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlServiceStop
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head3 rlServiceStop

Make sure the given C<service> is stopped. Stop it if it is
running and do nothing when it is already stopped. In addition,
when called for the first time, the current state is saved so that
the C<service> can be restored to its original state when testing
is finished, see C<rlServiceRestore>.

    rlServiceStop service [service...]

=over

=item service

Name of the service(s) to stop.

=back

Returns number of services which failed to become stopped; thus
zero is returned when everything is OK.

=cut

rlServiceStop() {
    # at least one service has to be supplied
    if [ $# -lt 1 ]; then
        rlLogError "rlServiceStop: You have to supply at least one service name"
        return 99
    fi

    local failed=0

    # create file to store list of services, if it doesn't already exist
    touch $__INTERNAL_SERVICES_LIST

    local service
    for service in "$@"; do
        __INTERNAL_SERVICE status "$service"
        local status=$?

        # if the original state hasn't been saved yet, do it now!

        local serviceId="$(echo $service | sed 's/[^a-zA-Z0-9]//g')"
        local wasRunning="$(__INTERNAL_SERVICE_STATE_LOAD $serviceId)"
        if [ -z "$wasRunning" ]; then
            echo "$service" >> $__INTERNAL_SERVICES_LIST
            # was running
            if [ $status == 0 ]; then
                rlLogDebug "rlServiceStop: Original state of $service saved (running)"
                __INTERNAL_SERVICE_STATE_SAVE $serviceId true
            # was stopped
            elif [ $status == 3 ]; then
                rlLogDebug "rlServiceStop: Original state of $service saved (stopped)"
                __INTERNAL_SERVICE_STATE_SAVE $serviceId false
            # weird exit status (warn and suppose stopped)
            else
                rlLogWarning "rlServiceStop: service $service status returned $status"
                rlLogWarning "rlServiceStop: Guessing that original state of $service is stopped"
                __INTERNAL_SERVICE_STATE_SAVE $serviceId false
            fi
        fi

        # if the service is stopped, do nothing
        if [ $status == 3 ]; then
            rlLogDebug "rlServiceStop: Service $service already stopped, doing nothing."
            continue
        fi

        # finally let's stop the service!
        if __INTERNAL_SERVICE stop "$service"; then
            rlLogDebug "rlServiceStop: Service $service stopped successfully"
        else
            # if service stop failed, inform the user and provide info about service status
            rlLogError "rlServiceStop: Stopping service $service failed"
            rlLogError "Status of the failed service:"
            __INTERNAL_SERVICE status "$service" 2>&1 | while read line; do rlLog "  $line"; done
            ((failed++))
        fi
    done

    return $failed
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlServiceRestore
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head3 rlServiceRestore

Restore given C<service> into its original state (before the first
C<rlServiceStart> or C<rlServiceStop> was called).

    rlServiceRestore [service...]

=over

=item service

Name of the service(s) to restore to original state.
All services will be restored in reverse order if no service name is given.

=back

Returns number of services which failed to get back to their
original state; thus zero is returned when everything is OK.

=cut

rlServiceRestore() {
    # create file to store list of services, if it doesn't already exist
    touch $__INTERNAL_SERVICES_LIST

    if [ $# -lt 1 ]; then
        local services=`tac $__INTERNAL_SERVICES_LIST`
        if [ -z "$services" ]; then
            rlLogWarning "rlServiceRestore: There are no services to restore"
        else
            rlLogDebug "rlServiceRestore: No service supplied, restoring all"
        fi
    else
        local services=$@
    fi

    local failed=0

    local service
    for service in $services; do
        # if the original state hasn't been saved, then something's wrong
        local serviceId="$(echo $service | sed 's/[^a-zA-Z0-9]//g')"
        local wasRunning="$( __INTERNAL_SERVICE_STATE_LOAD $serviceId )"
        local wasEnabled="$( __INTERNAL_SERVICE_STATE_LOAD ${serviceId} "true")"
        if [ -z "$wasRunning" ] && [ -z "$wasEnabled" ]; then
            rlLogError "rlServiceRestore: Original state of $service was not saved, nothing to do"
            ((failed++))
            continue
        fi

        ####
        # part handling start/stop actions
        if [ -n "$wasRunning" ]; then

            $wasRunning && wasStopped=false || wasStopped=true
            rlLogDebug "rlServiceRestore: Restoring $service to original state ($(
                $wasStopped && echo "stopped" || echo "running"))"

            # find out current state
            __INTERNAL_SERVICE status "$service"
            local status=$?
            if [ $status == 0 ]; then
                isStopped=false
            elif [ $status == 3 ]; then
                isStopped=true
            # weird exit status (warn and suppose stopped)
            else
                rlLogWarning "rlServiceRestore: service $service status returned $status"
                rlLogWarning "rlServiceRestore: Guessing that current state of $service is stopped"
                isStopped=true
            fi

            # if stopped and was stopped -> nothing to do
            if $isStopped; then
                if $wasStopped; then
                    rlLogDebug "rlServiceRestore: Service $service is already stopped, doing nothing"
                    continue
                fi
            # if running, we have to stop regardless original state
            else
                if __INTERNAL_SERVICE stop "$service"; then
                    rlLogDebug "rlServiceRestore: Service $service stopped successfully"
                else
                    # if service stop failed, inform the user and provide info about service status
                    rlLogError "rlServiceRestore: Stopping service $service failed"
                    rlLogError "Status of the failed service:"
                    __INTERNAL_SERVICE "$service" 2>&1 | while read line; do rlLog "  $line"; done
                    ((failed++))
                    continue
                fi
            fi

            # if was running then start again
            if ! $wasStopped; then
                if __INTERNAL_SERVICE start "$service"; then
                    rlLogDebug "rlServiceRestore: Service $service started successfully"
                else
                    # if service start failed, inform the user and provide info about service status
                    rlLogError "rlServiceRestore: Starting service $service failed"
                    rlLogError "Status of the failed service:"
                    __INTERNAL_SERVICE status "$service" 2>&1 | while read line; do rlLog "  $line"; done
                    ((failed++))
                    continue
                fi
            fi
        fi

        ####
        # part handling enable/disable
        if [ -n "$wasEnabled" ]; then
            local error="false"
            # as enablement and disablement of service does not change actual
            # state, we can use simpler approach than in start/stop case
            if [ "$wasEnabled" == "true" ];then
                if rlServiceEnable ${service}; then
                    rlLogDebug "rlServiceRestore: Enablement of service $service successful."
                else
                    rlLogError "rlServiceRestore: Enablement of service $service failed."
                    error="true"
                fi
            else
                if rlServiceDisable ${service};then
                    rlLogDebug "rlServiceRestore: Disablement of service $service successful."
                else
                    rlLogError "rlServiceRestore: Disablement of service $service failed."
                    error="true"
                fi
            fi
            if [ "$error" == "true" ]; then
                rlLogError "Status of the failed service:"
                __INTERNAL_SERVICE status "$service" 2>&1 | while read line; do rlLog "  $line"; done
                ((failed++))
                continue
            fi
        fi
    done

    return $failed
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlServiceEnable
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head3 rlServiceEnable

Enables selected services if needed, or does nothing if already enabled.
In addition, when called for the first time, the current state is saved
so that the C<service> can be restored to its original state when testing
is finished, see C<rlServiceRestore>.

    rlServiceEnable service [service...]

=over

=item service

Name of the service(s) to enable.

=back

Returns number of services which failed enablement; thus
zero is returned when everything is OK.

=cut


rlServiceEnable() {
  # at least one service has to be supplied
  if [ $# -lt 1 ]; then
      rlLogError "rlServiceEnable: You have to supply at least one service name"
      return 99
  fi

  local failed=0

  local service
  for service in "$@"; do
      chkconfig $service > /dev/null
      local status=$?

      # if the original state hasn't been saved yet, do it now!
      __INTERNAL_SERVICE_initial_state_save $service $status "true"

      # if the service is enabled, nothing more is needed
      if [ $status == 0 ]; then
          rlLog "rlServiceEnable: Service $service is already enabled."
          continue
      fi

      # enable service
      if chkconfig "$service" on; then
          rlLog "rlServiceEnable: Service $service enabled successfully"
      else
          # if service start failed, inform the user and provide info about service status
          rlLogError "rlServiceEnable: Enablement of service $service failed"
          rlLogError "Status of the failed service:"
          __INTERNAL_SERVICE status "$service" 2>&1 | while read line; do rlLog "  $line"; done
          ((failed++))
      fi
    done

    return $failed
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlServiceDisable
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head3 rlServiceDisable

Disables selected services if needed, or does nothing if already disabled.
In addition, when called for the first time, the current state is saved
so that the C<service> can be restored to its original state when testing
is finished, see C<rlServiceRestore>.

    rlServiceDisable service [service...]

=over

=item service

Name of the service(s) to disable.

=back

Returns number of services which failed disablement; thus
zero is returned when everything is OK.

=cut

rlServiceDisable() {
    # at least one service has to be supplied
    if [ $# -lt 1 ]; then
        rlLogError "rlServiceDisable: You have to supply at least one service name"
        return 99
    fi

    local failed=0

    local service
    for service in "$@"; do
        chkconfig $service > /dev/null
        local status=$?

        # if the original state hasn't been saved yet, do it now!
        __INTERNAL_SERVICE_initial_state_save $service $status "true"

        # if the service is disabled, do nothing
        if [ $status == 1 ]; then
            rlLogDebug "rlServiceDisable: Service $service already disabled, doing nothing."
            continue
        fi

        # disable it
        if chkconfig "$service" off; then
            rlLogDebug "rlServiceDisable: Service $service disabled successfully"
        else
            # if disable failed, inform the user and provide info about service status
            rlLogError "rlServiceDisable: Disablement service $service failed"
            rlLogError "Status of the failed service:"
            __INTERNAL_SERVICE status "$service" 2>&1 | while read line; do rlLog "  $line"; done
            ((failed++))
        fi
    done

    return $failed
}


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlSocketStart
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head2 Sockets

Following routines implement comfortable way how to start/stop
system sockets with the possibility to restore them to their
original state after testing.

=head3 rlSocketStart

Make sure the given C<socket> is running. (Start it if stopped, leave
it if already running.) In addition, when called for the first time, the
current state is saved so that the C<socket> can be restored to
its original state when testing is finished, see
C<rlSocketRestore>.

    rlSocketStart socket [socket...]

=over

=item socket

Name of the socket(s) to start.

=back

Returns number of sockets which failed to start/restart; thus
zero is returned when everything is OK.

=cut

__INTERNAL_SOCKET_NS="rlSocket"
__INTERNAL_SOCKET_STATE_SECTION="savedStates"

__INTERNAL_SOCKET_STATE_SAVE(){
  local socketId=$1
  local socketState=$2

  __INTERNAL_ST_PUT --namespace="$__INTERNAL_SOCKET_NS" --section="$__INTERNAL_SOCKET_STATE_SECTION" $socketId $socketState
}

__INTERNAL_SOCKET_STATE_LOAD(){
  local socketId=$1

  __INTERNAL_ST_GET --namespace="$__INTERNAL_SOCKET_NS" --section="$__INTERNAL_SOCKET_STATE_SECTION" $socketId
}

__INTERNAL_SOCKET_get_handler() {
  local socketName="$1"
  local handler="xinetd"

  # detection whether it is xinetd service, or systemd socket
  if ! rlIsRHEL "<7"; then
    # need to check if socket exists, in case it does not, it is xinetd
    __INTERNAL_SYSTEMCTL list-sockets --all | grep -q "${socketName}.socket" && handler="systemd"
  fi
  # return correct handler:
  [ "$handler" == "systemd" ] && return 0
  [ "$handler" == "xinetd" ] && return 1
}

__INTERNAL_SOCKET_service() {
  local serviceName="$1"
  local serviceTask="$2"
  __INTERNAL_SOCKET_get_handler ${serviceName}; local handler=$?

  if [[ "$handler" -eq "0" ]];then
  ######## systemd #########
  rlLogDebug "rlSocket: Handling $serviceName socket via systemd"
  case $serviceTask in
    "start")
      __INTERNAL_SYSTEMCTL start ${serviceName}.socket
      return
    ;;
    "stop")
      __INTERNAL_SYSTEMCTL stop ${serviceName}.socket
      return
    ;;
    "status")
      local outcome
      outcome=$(__INTERNAL_SYSTEMCTL is-active ${serviceName}.socket)
      local outcomeExit=$?
      rlLogDebug "rlSocket: status of ${serviceName} is \"${outcome}\", exit code: ${outcomeExit}."
      return $outcomeExit
    ;;
  esac

  else
  ######## legacy (xinetd) ########
  # also needs to handle (non)existence of the socket
  rlLogDebug "rlSocket: Handling $serviceName via xinetd"
  case $serviceTask in
    "start")
      rlServiceStart xinetd && chkconfig ${serviceName} on
      outcome=$?
      if [[ $outcome == "0" ]]; then
        return 0
      else
        chkconfig ${serviceName} > /dev/null;   local outcome=$?
        __INTERNAL_SERVICE status xinetd 2>&1 > /dev/null; local outcomeXinetd=$?
        rlLogDebug "xinetd status code: $outcomeXinetd"
        rlLogDebug "socket $serviceName status: $outcome"
        return 1
      fi
    ;;
    "stop")
      chkconfig ${serviceName} off
      return
    ;;
    "status")
      chkconfig ${serviceName} > /dev/null;   local outcome=$?
      __INTERNAL_SERVICE status xinetd 2>&1 > /dev/null; local outcomeXinetd=$?

      if [[ "$outcome" == 0 && "$outcomeXinetd" == 0 ]]; then
        rlLogDebug "rlSocket: Socket $serviceName is started"
        return 0
      else
        rlLogDebug "xinetd status code: $outcomeXinetd"
        rlLogDebug "socket $serviceName status: $outcome"
        return 3
      fi
    ;;
  esac

  fi
}

__INTERNAL_SOCKET_initial_state_save() {
  local socket=$1
  __INTERNAL_SOCKET_service "$socket" status
  local status=$?

  # if the original state hasn't been saved yet, do it now!

  local socketId="$(echo $socket | sed 's/[^a-zA-Z0-9]//g')"
  local wasRunning="$( __INTERNAL_SOCKET_STATE_LOAD $socketId)"
  if [ -z "$wasRunning" ]; then
      # was running
      if [ $status == 0 ]; then
          rlLogDebug "rlSocketStart: Original state of $socket saved (running)"
          __INTERNAL_SOCKET_STATE_SAVE $socketId true
      # was stopped
      elif [ $status == 3 ]; then
          rlLogDebug "rlSocketStart: Original state of $socket saved (stopped)"
          __INTERNAL_SOCKET_STATE_SAVE $socketId false
      # weird exit status (warn and suppose stopped)
      else
          rlLogWarning "rlSocketStart: socket $socket status returned $status"
          rlLogWarning "rlSocketStart: Guessing that original state of $socket is stopped"
          __INTERNAL_SOCKET_STATE_SAVE $socketId false
      fi
  fi
}


rlSocketStart() {
    # at least one socket has to be supplied
    if [ $# -lt 1 ]; then
        rlLogError "rlSocketStart: You have to supply at least one socket name"
        return 99
    fi

    local failed=0

    local socket
    for socket in "$@"; do
        __INTERNAL_SOCKET_service "$socket" status
        local status=$?

        # if the original state hasn't been saved yet, do it now!
        __INTERNAL_SOCKET_initial_state_save $socket

        if [ $status == 0 ]; then
            rlLog "rlSocketStart: Socket $socket already running, doing nothing."
            continue
        fi

        # finally let's start the socket!
        if __INTERNAL_SOCKET_service "$socket" start; then
            rlLog "rlSocketStart: Socket $socket started successfully"
        else
            # if socket start failed, inform the user and provide info about socket status
            rlLogError "rlSocketStart: Starting socket $socket failed"
            rlLogError "Status of the failed socket:"
            __INTERNAL_SOCKET_service "$socket" status 2>&1 | while read line; do rlLog "  $line"; done
            ((failed++))
        fi
    done

    return $failed
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlSocketStop
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head3 rlSocketStop

Make sure the given C<socket> is stopped. Stop it if it is
running and do nothing when it is already stopped. In addition,
when called for the first time, the current state is saved so that
the C<socket> can be restored to its original state when testing
is finished, see C<rlSocketRestore>.

    rlSocketStop socket [socket...]

=over

=item socket

Name of the socket(s) to stop.

=back

Returns number of sockets which failed to become stopped; thus
zero is returned when everything is OK.

=cut

rlSocketStop() {
    # at least one socket has to be supplied
    if [ $# -lt 1 ]; then
        rlLogError "rlSocketStop: You have to supply at least one socket name"
        return 99
    fi

    local failed=0

    local socket
    for socket in "$@"; do
        __INTERNAL_SOCKET_service "$socket" status
        local status=$?
        # if the original state hasn't been saved yet, do it now!
        __INTERNAL_SOCKET_initial_state_save $socket

        # if the socket is stopped, do nothing
        if [ $status != 0 ]; then
            rlLogDebug "rlSocketStop: Socket $socket already stopped, doing nothing."
            continue
        fi

        # finally let's stop the socket!
        if __INTERNAL_SOCKET_service "$socket" stop; then
            rlLogDebug "rlSocketStop: Socket $socket stopped successfully"
        else
            # if socket stop failed, inform the user and provide info about socket status
            rlLogError "rlSocketStop: Stopping socket $socket failed"
            rlLogError "Status of the failed socket:"
            __INTERNAL_SOCKET_service "$socket" status 2>&1 | while read line; do rlLog "  $line"; done
            ((failed++))
        fi
    done

    return $failed
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlSocketRestore
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head3 rlSocketRestore

Restore given C<socket> into its original state (before the first
C<rlSocketStart> or C<rlSocketStop> was called).

Warning !!!
Xinetd process [even though it might have been used] is NOT restored. It is
recommended to call rlServiceRestore xinetd as well.

    rlSocketRestore socket [socket...]

=over

=item socket

Name of the socket(s) to restore to original state.

=back

Returns number of sockets which failed to get back to their
original state; thus zero is returned when everything is OK.

=cut

rlSocketRestore() {
    # at least one socket has to be supplied
    if [ $# -lt 1 ]; then
        rlLogError "rlSocketRestore: You have to supply at least one socket name"
        return 99
    fi

    local failed=0

    local socket
    for socket in "$@"; do
        # if the original state hasn't been saved, then something's wrong
        local socketId="$(echo $socket | sed 's/[^a-zA-Z0-9]//g')"
        local wasRunning="$( __INTERNAL_SOCKET_STATE_LOAD $socketId )"
        if [ -z "$wasRunning" ]; then
            rlLogError "rlSocketRestore: Original state of $socket was not saved, nothing to do"
            ((failed++))
            continue
        fi

        $wasRunning && wasStopped=false || wasStopped=true
        rlLogDebug "rlSocketRestore: Restoring $socket to original state ($(
            $wasStopped && echo "stopped" || echo "running"))"

        # find out current state
        __INTERNAL_SOCKET_service "$socket" status
        local status=$?
        if [ $status == 0 ]; then
            isStopped=false
        elif [ $status == 3 ]; then
            isStopped=true
        # weird exit status (warn and suppose stopped)
        else
            rlLogWarning "rlSocketRestore: socket $socket status returned $status"
            rlLogWarning "rlSocketRestore: Guessing that current state of $socket is stopped"
            isStopped=true
        fi

        if [[ $isStopped == $wasStopped ]]; then
            rlLogDebug "rlSocketRestore: Socket $socket is already in original state, doing nothing."
            continue
        fi
        # we actually have to do something
        local action=$($wasStopped && echo "stop" || echo "start")
        if __INTERNAL_SOCKET_service "$socket" $action; then
            rlLogDebug "rlSocketRestore: Socket $socket ${action}ed successfully"
        else
            rlLogError "rlSocketRestore: Socket $socket failed to ${action}"
            rlLogError "Status of the failed socket:"
            __INTERNAL_SOCKET_service "$socket" status 2>&1 | while read line; do rlLog "  $line"; done
            ((failed++))
            continue
        fi
    done

    return $failed
}


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlSEBooleanOn
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

rlSEBooleanOn() {
  rlLogError "this function was dropped as its development is completely moved to the beaker library"
  rlLogInfo "if you realy on this function and you really need to have it present in core beakerlib, file a RFE, please"
  return 1
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlSEBooleanOff
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rlSEBooleanOff() {
  rlLogError "this function was dropped as its development is completely moved to the beaker library"
  rlLogInfo "if you realy on this function and you really need to have it present in core beakerlib, file a RFE, please"
  return 1
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlSEBooleanRestore
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rlSEBooleanRestore() {
  rlLogError "this function was dropped as its development is completely moved to the beaker library"
  rlLogInfo "if you realy on this function and you really need to have it present in core beakerlib, file a RFE, please"
  return 1
}

: <<'=cut'
=pod

=head2 Cleanup management

Cleanup management works with a so-called cleanup buffer, which is a temporary
representation of what should be run at cleanup time, and a final cleanup
script (executable), which is generated from this buffer and wraps it using
BeakerLib essentials (journal initialization, cleanup phase, ...).
The cleanup script must always be updated on an atomic basis (filesystem-wise)
to allow an asynchronous execution by a third party (ie. test watcher).

The test watcher usage is mandatory for the cleanup management system to work
properly as it is the test watcher that executes the actual cleanup script.
Limited, catastrophe-avoiding mechanism is in place even when the test is not
run in test watcher, but that should be seen as a backup and such situation
is to be avoided whenever possible.

The cleanup script shares all environment (variables, exported or not, and
functions) with the test itself - the cleanup append/prepend functions "sample"
or "snapshot" the environment at the time of their call, IOW any changes to the
test environment are synchronized to the cleanup script only upon calling
append/prepend.
When the append/prepend functions are called within a function which has local
variables, these will appear as global in the cleanup.

While the cleanup script receives $PWD from the test, its working dir is set
to the initial test execution dir even if $PWD contains something else. It is
impossible to use relative paths inside cleanup reliably - certain parts of
the cleanup might have been added under different current directories (CWDs).
Therefore always use absolute paths in append/prepend cleanup or make sure
you never 'cd' elsewhere (ie. to a TmpDir).
=cut

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlCleanupAppend
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head3 rlCleanupAppend

Appends a string to the cleanup buffer and recreates the cleanup script.

    rlCleanupAppend string

=over

=back

Returns 0 if the operation was successful, 1 otherwise.

=cut

rlCleanupAppend() {
    if [ ! -f "$__INTERNAL_CLEANUP_BUFF" ]; then
        rlLogError "rlCleanupAppend: cleanup metadata not initialized"
        return 1
    elif [ $# -lt 1 ]; then
        rlLogError "rlCleanupAppend: not enough arguments"
        return 1
    elif [ -z "$__INTERNAL_TESTWATCHER_ACTIVE" ]; then
        rlLogWarning "rlCleanupAppend: Running outside of the test watcher"
        rlLogWarning "rlCleanupAppend: Check your 'run' target in the test Makefile"
        rlLogWarning "rlCleanupAppend: Cleanup will be executed only if rlJournalEnd is called properly"
    fi

    echo "$1" >> "$__INTERNAL_CLEANUP_BUFF" || return 1

    __INTERNAL_rlCleanupGenFinal || return 1
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rlCleanupPrepend
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head3 rlCleanupPrepend

Prepends a string to the cleanup buffer and recreates the cleanup script.

    rlCleanupPrepend string

=over

=back

Returns 0 if the operation was successful, 1 otherwise.

=cut

rlCleanupPrepend() {
    if [ ! -f "$__INTERNAL_CLEANUP_BUFF" ]; then
        rlLogError "rlCleanupPrepend: cleanup metadata not initialized"
        return 1
    elif [ $# -lt 1 ]; then
        rlLogError "rlCleanupPrepend: not enough arguments"
        return 1
    elif [ -z "$__INTERNAL_TESTWATCHER_ACTIVE" ]; then
        rlLogWarning "rlCleanupPrepend: Running outside of the test watcher"
        rlLogWarning "rlCleanupPrepend: Check your 'run' target in the test Makefile"
        rlLogWarning "rlCleanupPrepend: Cleanup will be executed only if rlJournalEnd is called properly"
    fi

    local tmpbuff="$__INTERNAL_CLEANUP_BUFF".tmp
    echo "$1" > "$tmpbuff" || return 1
    cat "$__INTERNAL_CLEANUP_BUFF" >> "$tmpbuff" || return 1
    mv -f "$tmpbuff" "$__INTERNAL_CLEANUP_BUFF" || return 1

    __INTERNAL_rlCleanupGenFinal || return 1
}



# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AUTHORS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod

=head1 AUTHORS

=over

=item *

Petr Muller <pmuller@redhat.com>

=item *

Jan Hutar <jhutar@redhat.com>

=item *

Petr Splichal <psplicha@redhat.com>

=item *

Ales Zelinka <azelinka@redhat.com>

=item *

Karel Srot <ksrot@redhat.com>

=back

=cut
