#!/usr/bin/sh
#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server


# This file should implement all platform specific functions. The other components of the
# cvmfs_server script should interact with the underlying system only through the functions
# defined here. This allows simple(r) unit testing of the "cvmfs_server" script

# Returns the major.minor.patch-build kernel version string
cvmfs_sys_uname() {
    uname -r | grep -oe '^[0-9]\+\.[0-9]\+.[0-9]\+-*[0-9]*'
}


cvmfs_sys_file_is_regular() {
    [ -f $1 ]
}


cvmfs_sys_file_is_executable() {
    [ -x $1 ]
}


cvmfs_sys_file_is_empty() {
    [ ! -s $1 ]
}


cvmfs_sys_is_redhat() {
  cvmfs_sys_file_is_regular /etc/redhat-release
}


cvmfs_sys_get_fstype() {
  echo $(df -T /var/spool/cvmfs | tail -1 | awk {'print $2'})
}
set -e # ESSENTIAL! Don't remove this!
       # Stops the server script in case anything unexpected occurs, so that
       # malfunctions cause as less damage as possible.
       # For example a crashing `cvmfs_server publish` is prevented from wiping
       # the scratch area, giving us a chance to fix and retry the process.

die() {
  ( [ x"$(echo -n -e)" = x"-e" ] && echo $1 >&2 || echo -e $1 ) >&2
  exit 1
}

# set default locale
export LC_ALL=C
#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Utility functions used by the  "cvmfs_server" script
#

parse_url() {
  local input_url=$1
  local key=$2

  local proto host port path url

  local has_proto=$(echo $input_url | grep '://')
  if [ x"$has_proto" != x"" ]; then
      proto=$(echo $input_url | cut -d':' -f1)
      url=$(echo $input_url | cut -d'/' -f3-)
  else
      url=$input_url
  fi

  local has_path=$(echo $url | grep '/')
  if [ x"$has_path" != x"" ]; then
      path=$(echo $url | cut -d'/' -f2-)
      url=$(echo $url | cut -d'/' -f1)
  fi

  local has_port=$(echo $url | grep ':')
  if [ x"$has_port" != x"" ]; then
      port=$(echo $url | cut -d':' -f2)
      host=$(echo $url | cut -d':' -f1)
  else
      host=$url
  fi

  case $key in
    proto)
        echo "$proto"
        ;;
    host)
        echo "$host"
        ;;
    port)
        echo "$port"
        ;;
    path)
        echo "$path"
        ;;
    *)
        echo "proto: $proto"
        echo "host: $host"
        echo "port: $port"
        echo "path: $path"
  esac
}


mangle_local_cvmfs_url() {
  local repo_name=$1
  echo "http://localhost/cvmfs/${repo_name}"
}


mangle_s3_cvmfs_url() {
  local repo_name=$1
  local s3_url="$2"
  [ $(echo -n "$s3_url" | tail -c1) = "/" ] || s3_url="${s3_url}/"
  echo "${s3_url}${repo_name}"
}


make_upstream() {
  local type_name=$1
  local tmp_dir=$2
  local config_string=$3
  echo "$type_name,$tmp_dir,$config_string"
}


make_s3_upstream() {
  local repo_name=$1
  local s3_config=$2
  local subpath=$3
  local repo_alias
  local disable_dns_buckets=$(cat $s3_config | grep "CVMFS_S3_DNS_BUCKETS=false")
  if [ x"$disable_dns_buckets" = x"" ] && [ x"$subpath" != x"" ]; then
    repo_alias=$subpath/$repo_name
  else
    repo_alias=$repo_name
  fi
  make_upstream "S3" "/var/spool/cvmfs/${repo_name}/tmp" "${repo_alias}@${s3_config}"
}


make_local_upstream() {
  local repo_name=$1
  local repo_storage="${DEFAULT_LOCAL_STORAGE}/${repo_name}"
  make_upstream "local" "${repo_storage}/data/txn" "$repo_storage"
}


# checks if the aufs kernel module is present
# or if aufs is compiled in
# @return   0 if the aufs kernel module is loaded
check_aufs() {
  $MODPROBE_BIN -q aufs || test -d /sys/fs/aufs
}


# checks if the overlayfs kernel module is present
# or if overlayfs is compiled in
# @return   0 if the overlayfs kernel module is loaded
check_overlayfs() {
  $MODPROBE_BIN -q overlay || test -d /sys/module/overlay
}


# check if at least one of the supported union file systems is available
# currently OverlayFS get preference over AUFS if both are available
#
# @return   0 if at least one was found (name through stdout); abort otherwise
get_available_union_fs() {
  if check_overlayfs; then
    echo "overlayfs"
  elif check_aufs; then
    echo "aufs"
  else
    die "neither OverlayFS nor AUFS detected on the system!"
  fi
}


__swissknife_cmd() {
  local might_be_debugging="$1"
  if [ ! -z $might_be_debugging ]; then
    echo "$CVMFS_SERVER_SWISSKNIFE_DEBUG"
  else
    echo "$CVMFS_SERVER_SWISSKNIFE"
  fi
}


__swissknife() {
  $(__swissknife_cmd) $@
}


__publish_cmd() {
  local might_be_debugging="$1"
  if [ ! -z $might_be_debugging ]; then
    echo "$CVMFS_SERVER_PUBLISH_DEBUG"
  else
    echo "$CVMFS_SERVER_PUBLISH"
  fi
}


__publish() {
  $(__publish_cmd) $@
}


# checks if a given list of strings contains a specific item
#
# @param haystack   the list to be searched
# @param needle     the string item to be found in the haystack
# @return           0 if the item was found
contains() {
  local haystack="$1"
  local needle=$2

  for elem in $haystack; do
    if [ x"$elem" = x"$needle" ]; then
      return 0
    fi
  done

  return 1
}


# checks if autofs is disabled on /cvmfs
#
# @return  0 if autofs is not used for /cvmfs
check_autofs_on_cvmfs() {
  cat /proc/mounts | grep -q "^/etc/auto.cvmfs /cvmfs "
}


# checks if a given repository is a stratum 0 repository
#
# @param name   the repository name to be checked
# @return       0 if it is a stratum 0 repository
is_stratum0() {
  local name=$1
  ! cvmfs_sys_file_is_regular /etc/cvmfs/repositories.d/$name/replica.conf
}


# checks if a given repository is a stratum 1 repository
#
# @param name   the repository name to be checked
# @return       0 if it is a stratum 1 repository
is_stratum1() {
  local name=$1
  ! is_stratum0 $name
}


# Makes a fully qualified repository name
cvmfs_mkfqrn() {
   local repo=$1

   if [ -z "$repo" ]; then
      echo
      return 0
   fi

   echo $repo | grep \\. || echo "${repo}.${CVMFS_DEFAULT_DOMAIN}"
   return 0
}


# checks if a given path is mounted
# Note: this takes care of symlink resolving and should be
#       used exclusively for mount checks
#
# @param mountpoint  the mountpoint to be checked
# @param regexp      additional regexp to be validated on the mountpoint record
#                    found in /proc/mounts
is_mounted() {
  local mountpoint="$1"
  local regexp="$2"

  local absolute_mnt=
  # Use canonicalize-missing because the mount point can be broken and
  # inaccessible
  absolute_mnt="$(readlink --canonicalize-missing $mountpoint)" || return 2
  local mnt_record="$(cat /proc/mounts 2>/dev/null | grep -F " $absolute_mnt ")"
  if [ x"$mnt_record" = x"" ]; then
    return 1
  fi

  [ x"$regexp" = x"" ] || echo "$mnt_record" | grep -q "$regexp"
}


# only certain characters are allowed in repository names
#
# @param repo_name the name to test
is_valid_repo_name() {
  local repo_name="$1"

  [ ! -z "$1" ] || return 1
  local length=$(echo -n "$repo_name" | wc -c)
  [ $length -le 60 ] || return 1

  local repo_head="$(echo "$repo_name" | head -c 1)"
  local clean_head="$(echo "$repo_head" | tr -cd a-zA-Z0-9)"
  [ "x$clean_head" = "x$repo_head" ] || return 1

  local clean_name=$(echo "$repo_name" | tr -cd a-zA-Z0-9_.-)
  [ "x$clean_name" = "x$repo_name" ]
}


# only certain characters are allowed in branch names
#
# @param branch_name the name to test
is_valid_branch_name() {
  local branch_name="$1"

  local clean_name=$(echo "$branch_name" | tr -cd a-zA-Z0-9_@./-)
  [ "x$clean_name" = "x$branch_name" ]
}


run_suid_helper() {
  env -i /usr/bin/cvmfs_suid_helper $@
}


# gets the number of open writable file descriptors beneath a given path
#
# @param path  the path to look at for open writable fds
# @return      the number of open writable file descriptors
count_wr_fds() {
  local path=$1
  local cnt=0
  for line in $(get_fd_modes $path); do
    if echo "$line" | grep -qe '^rw'; then cnt=$(( $cnt + 1 )); fi
  done
  echo $cnt
}


### Logging functions

to_syslog() {
  logger -t cvmfs_server "$1" > /dev/null 2>&1 || return 0
}


to_syslog_for_repo() {
  local repo_name="$1"
  local message="$2"
  to_syslog "(${repo_name}) $message"
}


__hc_print_status_report() {
  local name="$1"
  local rdonly_broken=$2
  local rdonly_outdated=$3
  local rdonly_wronghash=$4
  local rw_broken=$5
  local rw_should_be_rdonly=$6
  local rw_should_be_rw=$7

  [ $rdonly_broken       -eq 0 ] || echo "${CVMFS_SPOOL_DIR}/rdonly is not mounted properly."                   >&2
  [ $rdonly_outdated     -eq 0 ] || echo "$name is not based on the newest published revision"                  >&2
  [ $rdonly_wronghash    -eq 0 ] || echo "$name is not based on the checked out revision"                       >&2
  [ $rw_broken           -eq 0 ] || echo "/cvmfs/$name is not mounted properly."                                >&2
  [ $rw_should_be_rdonly -eq 0 ] || echo "$name is not in a transaction but /cvmfs/$name is mounted read/write" >&2
  [ $rw_should_be_rw     -eq 0 ] || echo "$name is in a transaction but /cvmfs/$name is not mounted read/write" >&2
}


__hc_transition() {
  local name="$1"
  local quiet="$2"
  local transition="$3"
  local msg=""
  local stdout_msg=""
  local retcode=0

  case $transition in
    rw_umount)     msg="Trying to umount /cvmfs/${name}"             ;;
    rdonly_umount) msg="Trying to umount ${CVMFS_SPOOL_DIR}/rdonly"  ;;
    rw_mount)      msg="Trying to mount /cvmfs/${name}"              ;;
    rdonly_mount)  msg="Trying to mount ${CVMFS_SPOOL_DIR}/rdonly"   ;;
    open)          msg="Trying to remount /cvmfs/${name} read/write" ;;
    lock)          msg="Trying to remount /cvmfs/${name} read-only"  ;;
    *)             exit 2                                            ;;
  esac

  stdout_msg="Note: ${msg}... "
  [ $quiet = 0 ] && echo -n "$stdout_msg" >&2

  if run_suid_helper $transition $name > /dev/null; then
    [ $quiet = 0 ] && echo "success" >&2
    to_syslog_for_repo $name "${msg}... success"
  else
    [ $quiet = 0 ] || echo -n "${stdout_msg}" >&2 # error messages are not quiet
    echo "fail"                               >&2
    to_syslog_for_repo $name "${msg}... fail"
    exit 1
  fi
}


# Find an available file descriptor
find_available_fd()
{
  # dash only supports single-digit file descriptors so the number
  # of locks available is limited
  local fd=3
  local max=10
  while [ $fd -lt $max ]; do
    if [ ! -e /proc/$$/fd/$fd ]; then
      echo $fd
      return
    fi
    fd=$((fd + 1))
  done
  die "No file descriptor available"
}

### Locking functions

_lock_fds=""
_lock_paths=""

acquire_lock() {
  local path="$1"
  local wait_for_lock="${2:-0}"
  local lock_file="${path}.lock"
  local lock_fd
  while true; do
    lock_fd=$(find_available_fd)
    eval "exec ${lock_fd}<>${lock_file}"
    if [ $wait_for_lock -eq 0 ]; then
      if ! flock -n ${lock_fd}; then
        # didn't get it, clean up and return failure
        eval "exec ${lock_fd}<&-"
        return 1
      fi
    else
      flock ${lock_fd}
    fi
    # now have the lock
    if [ -f $lock_file ]; then
      # was not removed by the former lock holder, good
      break
    fi
    # was removed by former lock holder; close and try again
    eval "exec ${lock_fd}<&-"
  done
  # add the fd number and path to the two lists
  _lock_fds="$_lock_fds $lock_fd"
  _lock_paths="$_lock_paths $path"
}


release_lock() {
  local path="$1"
  local lock_file="${path}.lock"
  local lock_fd=-1
  # Find the index of $path in $_lock_paths and from that find $lock_fd.
  # Would be much easier with an associative array if we could rely on bash.
  local index=0
  local lock_index=0
  local lock_path
  local new_paths=""
  for lock_path in $_lock_paths; do
    index=$((index + 1))
    if [ "$path" = "$lock_path" ]; then
      lock_index=$index
    else
      new_paths="$new_paths $lock_path"
    fi
  done
  [ "$lock_index" != 0 ] || die "attempt to release $path lock when it was not acquired"
  index=0
  local fd
  local new_fds
  for fd in $_lock_fds; do
    index=$((index + 1))
    if [ "$index" = "$lock_index" ]; then
      lock_fd=$fd
    else
      new_fds="$new_fds $fd"
    fi
  done
  [ "$lock_fd" != -1 ] || die "_lock_fds and _lock_paths out of sync while releasing $path lock"
  _lock_fds="$new_fds"
  _lock_paths="$new_paths"
  rm -f $lock_file
  eval "exec ${lock_fd}<&-"
}


check_lock() {
  local path="$1"
  if ! acquire_lock "${path}" ; then
    return 0
  fi
  release_lock "${path}"
  return 1
}


set_flag() {
  local path="$1"
  local flag_file="${path}.lock"
  touch "${flag_file}"
}

clear_flag() {
  local path="$1"
  local flag_file="${path}.lock"
  rm "${flag_file}"
}

check_flag() {
  local path="$1"
  local flag_file="${path}.lock"
  [ -f "${flag_file}" ]
}


# makes sure that a version is always of the form x.y.z-b
normalize_version() {
  local version_string="$1"
  while [ $(echo "$version_string" | grep -o '\.' | wc -l) -lt 2 ]; do
    version_string="${version_string}.0"
  done
  while [ $(echo "$version_string" | grep -o '-' | wc -l) -lt 1 ]; do
    version_string="${version_string}-1"
  done
  echo "$version_string"
}


# returns the version string of the current cvmfs installation
cvmfs_version_string() {
  local version_string
  if ! __swissknife version > /dev/null 2>&1; then
    # Fallback: for CernVM-FS versions before 2.1.7
    # this is just a security measure... it should never happen, since this
    # function was introduced with CernVM-FS 2.1.7
    version_string=$(__swissknife --version | sed -n '2{p;q}' | awk '{print $2}')
  else
    version_string=$(__swissknife --version)
  fi
  echo $(normalize_version $version_string)
}

# Tracks changes to the organization of files and directories.
# Stored in CVMFS_CREATOR_VERSION.  Started with 137.
cvmfs_layout_revision() { echo "143"; }

version_major() { echo $1 | cut --delimiter=. --fields=1 | grep -oe '^[0-9]\+'; }
version_minor() { echo $1 | cut --delimiter=. --fields=2 | grep -oe '^[0-9]\+'; }
version_patch() { echo $1 | cut --delimiter=. --fields=3 | grep -oe '^[0-9]\+'; }
version_build() { echo $1 | cut --delimiter=- --fields=2 | grep -oe '^[0-9]\+'; }

prepend_zeros() { printf %03d "$1"; }


compare_versions() {
  local lhs="$(normalize_version $1)"
  local comparison_operator=$2
  local rhs="$(normalize_version $3)"

  local lhs1=$(prepend_zeros $(version_major $lhs))
  local lhs2=$(prepend_zeros $(version_minor $lhs))
  local lhs3=$(prepend_zeros $(version_patch $lhs))
  local lhs4=$(prepend_zeros $(version_build $lhs))
  local rhs1=$(prepend_zeros $(version_major $rhs))
  local rhs2=$(prepend_zeros $(version_minor $rhs))
  local rhs3=$(prepend_zeros $(version_patch $rhs))
  local rhs4=$(prepend_zeros $(version_build $rhs))

  [ $lhs1$lhs2$lhs3$lhs4 $comparison_operator $rhs1$rhs2$rhs3$rhs4 ]
}


version_equal() {
  local needle=$1
  compare_versions "$(cvmfs_version_string)" = "$needle"
}


version_greater_or_equal() {
  local needle=$1
  compare_versions $(cvmfs_version_string) -ge $needle
}


version_lower_or_equal() {
  local needle=$1
  compare_versions $(cvmfs_version_string) -le $needle
}


get_upstream_type() {
  local upstream=$1
  echo "$upstream" | cut -d, -f1
}


check_upstream_type() {
  local upstream=$1
  local needle_type=$2
  [ x$(get_upstream_type $upstream) = x"$needle_type" ]
}


is_local_upstream() {
  local upstream=$1
  check_upstream_type $upstream "local"
}


# returns 0 if the current working dir is somewhere under $path
#
# @param path  the path to look at
# @return      0 if cwd is on path or below, 1 otherwise
is_cwd_on_path() {
  local path=$1

  if [ "x$(pwd)" = "x${path}" ]; then
    return 0
  fi
  if echo "x$(pwd)" | grep -q "^x${path}/"; then
    return 0
  fi

  return 1
}


check_upstream_validity() {
  local upstream=$1
  local silent=0
  if [ $# -gt 1 ]; then
    silent=1;
  fi

  # checks if $upstream contains _exactly three_ comma separated data fields
  if echo $upstream | grep -q "^[^,]*,[^,]*,[^,]*$"; then
    return 0
  fi

  if [ $silent -ne 1 ]; then
    usage "The given upstream definition (-u) is invalid. Should look like:
      <spooler type> , <tmp directory> , <spooler configuration>"
  fi
  return 1
}


# Ensure that the installed overlayfs is viable for CernVM-FS.
# Note: More details are in CVM-835.
# @return  0 if overlayfs is installed and viable
#          1 if it is not viable, and stdout contains a reason
# This should probably now be called check_overlayfs_viability except
#   that for backward compatibility we need to keep the variable that
#   overrides it, CVMFS_DONT_CHECK_OVERLAYFS_VERSION, and changing the
#   function name would make the variable name not make sense.
check_overlayfs_version() {
  if ! check_overlayfs; then
    echo "overlayfs kernel module missing"
    return 1
  fi
  [ -z "$CVMFS_DONT_CHECK_OVERLAYFS_VERSION" ] || return 0
  local krnl_version=$(cvmfs_sys_uname)
  local required_version="4.2.0"
  if compare_versions "$krnl_version" -ge "$required_version" ; then
    return 0
  fi
  if cvmfs_sys_is_redhat; then
    # Redhat kernel with backported overlayfs supports limited filesystem types
    required_version="3.10.0-493"
    if compare_versions "$krnl_version" -ge "$required_version" ; then
      # If the mounted filesystem name is long df will split output into two
      #  lines, so use tail -n +2 to skip first line and echo to combine them
      local scratch_line="$(echo $(df -T /var/spool/cvmfs | tail -n +2))"
      local scratch_fstype=$(echo $scratch_line | awk {'print $2'})
      if [ "x$scratch_fstype" = "xext3" ] || [ "x$scratch_fstype" = "xext4" ] ; then
        return 0
      fi
      if [ "x$scratch_fstype" = "xxfs" ] ; then
        local scratch_fsmnt=$(echo $scratch_line | awk {'print $7'})
        if [ "x$(xfs_info $scratch_fsmnt 2>/dev/null | grep ftype=1)" != "x" ] ; then
          return 0
        else
          echo "XFS with ftype=0 is not supported for /var/spool/cvmfs. XFS with ftype=1 is required"
          return 1
        fi
      fi
      echo "overlayfs scratch /var/spool/cvmfs is type $scratch_fstype, but ext3, ext4 or xfs(ftype=1) required"
      return 1
    fi
  fi
  echo "Kernel version $krnl_version too old for overlayfs; at least $required_version required"
  return 1
}


# checks if cvmfs2 client is installed
#
# @return  0 if cvmfs2 client is installed
check_cvmfs2_client() {
  cvmfs_sys_file_is_executable /usr/bin/cvmfs2
}


# lowers restrictions of hardlink creation if needed
# allows AUFS to properly whiteout files without root privileges
# Note: this function requires a privileged user
lower_hardlink_restrictions() {
  if cvmfs_sys_file_is_regular /proc/sys/kernel/yama/protected_nonaccess_hardlinks && \
     [ $(cat /proc/sys/kernel/yama/protected_nonaccess_hardlinks) -ne 0 ]; then
    # disable hardlink restrictions at runtime
    sysctl -w kernel.yama.protected_nonaccess_hardlinks=0 > /dev/null 2>&1 || return 1

    # change sysctl.conf to make the change persist reboots
    cat >> /etc/sysctl.conf << EOF

# added by CVMFS to allow proper whiteout of files in AUFS
# when creating or altering repositories on this machine.
kernel.yama.protected_nonaccess_hardlinks=0
EOF
    echo "Note: permanently disabled kernel option: kernel.yama.protected_nonaccess_hardlinks"
  fi

  return 0
}


_setcap_if_needed() {
  local binary_path="$1"
  local capability="$2"
  cvmfs_sys_file_is_executable $binary_path || return 0
  $SETCAP_BIN -v "${capability}" "$binary_path" >/dev/null 2>&1 && return 0
  $SETCAP_BIN "${capability}" "$binary_path"
}


# grants CAP_SYS_ADMIN to cvmfs_swissknife and cvmfs_publish if it is necessary
# Note: OverlayFS uses trusted extended attributes that are not readable by a
#       normal unprivileged process
ensure_swissknife_suid() {
  local unionfs="$1"
  local sk_bin="/usr/bin/$CVMFS_SERVER_SWISSKNIFE"
  local sk_dbg_bin="/usr/bin/${CVMFS_SERVER_SWISSKNIFE}_debug"
  local pb_bin="/usr/bin/cvmfs_publish"
  local pb_dbg_bin="/usr/bin/cvmfs_publish_debug"
  local cap_read="cap_dac_read_search"
  local cap_overlay="cap_sys_admin"

  is_root || die "need to be root for granting capabilities to $sk_bin"

  if [ x"$unionfs" = x"overlayfs" ]; then
    _setcap_if_needed "$sk_bin"     "${cap_read},${cap_overlay}+p" || return 3
    _setcap_if_needed "$sk_dbg_bin" "${cap_read},${cap_overlay}+p" || return 4
    _setcap_if_needed "$pb_bin"     "${cap_read},${cap_overlay}+p" || return 5
    _setcap_if_needed "$pb_dbg_bin" "${cap_read},${cap_overlay}+p" || return 6
  else
    _setcap_if_needed "$sk_bin"     "${cap_read}+p" || return 1
    _setcap_if_needed "$sk_dbg_bin" "${cap_read}+p" || return 2
    _setcap_if_needed "$pb_bin"     "${cap_read}+p" || return 7
    _setcap_if_needed "$pb_dbg_bin" "${cap_read}+p" || return 8
  fi
}


find_sbin() {
  local bin_name="$1"
  local bin_path=""
  for d in /sbin /usr/sbin /usr/local/sbin /bin /usr/bin /usr/local/bin; do
    bin_path="${d}/${bin_name}"
    if cvmfs_sys_file_is_executable "$bin_path" ; then
      echo "$bin_path"
      return 0
    fi
  done
  return 1
}


# whenever you print the version string you should use this function since
# a repository created before CernVM-FS 2.1.7 cannot be fingerprinted
# correctly...
# @param version_string  the plain version string
mangle_version_string() {
  local version_string=$1
  if [ x"$version_string" = x"2.1.6" ]; then
    echo "2.1.6 or lower"
  else
    echo $version_string
  fi
}


# checks if a user exists in the system
#
# @param user   the name of the user to be checked
# @return       0 if user was found
check_user() {
  local user=$1
  id $user > /dev/null 2>&1
}


get_cvmfs_owner() {
  local name=$1
  local owner=$2
  local cvmfs_owner

  if [ "x$owner" = "x" ]; then
    read -p "Owner of $name [$(whoami)]: " cvmfs_owner
    [ x"$cvmfs_owner" = x ] && cvmfs_owner=$(whoami)
  else
    cvmfs_owner=$owner
  fi
  check_user $cvmfs_user || return 1
  echo $cvmfs_owner
}


is_s3_upstream() {
  local upstream=$1
  check_upstream_type $upstream "s3"
}

get_upstream_config() {
  local upstream=$1
  echo "$upstream" | cut -d, -f3-
}


has_selinux() {
  cvmfs_sys_file_is_executable $SESTATUS_BIN && \
  cvmfs_sys_file_is_executable $GETENFORCE_BIN && \
  $GETENFORCE_BIN | grep -qi "enforc" || return 1
}

# Functions to retrieve information about the operating system.
# Used in cvmfs_server_json.sh to populate meta.json.
# Fails gracefully if no os-release file is found (values become empty strings)
_os_set_etc_release_filename() {
  _CVMFS_OS_RELEASE_FILENAME=''
  if test -e "/etc/os-release"; then
    _CVMFS_OS_RELEASE_FILENAME="/etc/os-release";
  elif test -e "/usr/lib/os-release"; then
    _CVMFS_OS_RELEASE_FILENAME="/usr/lib/os-release";
  fi
}

# Pick a specific field from the os-release file and return it.
# 1. _os_set_etc_release_filename is called on every check, this is probably overkill,
#    but the question would be where to call this once and for all. In theory one could
#    call it right after the declaration above?
# 2. If a fieldname isn't provided, die due to wrongful usage of the (internal) function.
# 3. If we couldn't find an os-release file, return nothing.
# 4. If we grep fails to find anything, return nothing. We may want to check against known
#    keys from https://www.freedesktop.org/software/systemd/man/os-release.html?
_os_etc_release_get_field() {
  _os_set_etc_release_filename

  local fieldname=$1
  if [ "x${fieldname}" = "x" ]; then
    die "Internal error: _os_etc_release_get_field expects a field name to search for!"
  fi

  if [ "x${_CVMFS_OS_RELEASE_FILENAME}" = "x" ]; then
    # If we are unable to find a proper /etc/os-release file return nothing.
    :
  else
    sed -n "s/\"//g;s/^${fieldname}=//p" ${_CVMFS_OS_RELEASE_FILENAME}
  fi
}

_os_id() {
  _os_etc_release_get_field 'ID'
}

_os_version_id() {
  _os_etc_release_get_field 'VERSION_ID'
}

_os_pretty_name() {
  _os_etc_release_get_field 'PRETTY_NAME'
}


set_selinux_httpd_context_if_needed() {
  local directory="$1"
  if has_selinux; then
    chcon -Rv --type=httpd_sys_content_t ${directory}/ > /dev/null
  fi
}


_cleanup_tmrc() {
  local tmpdir=$1
  umount ${tmpdir}/c > /dev/null 2>&1 || umount -l > /dev/null 2>&1
  rm -fR ${tmpdir}   > /dev/null 2>&1
}


# for some reason `mount -o remount,(ro|rw) /cvmfs/$name` does not work on older
# platforms if we set the SELinux context=... parameter in /etc/fstab
# this dry-runs the whole mount, remount, unmount cycle to find out if it works
# correctly (aufs version)
# @returns  0 if the whole cycle worked as expected
try_mount_remount_cycle_aufs() {
  local tmpdir
  tmpdir=$(mktemp -d)
  mkdir ${tmpdir}/a ${tmpdir}/b ${tmpdir}/c
  mount -t aufs \
    -o br=${tmpdir}/a=ro:${tmpdir}/b=rw,ro,context=system_u:object_r:default_t:s0 \
    try_remount_aufs ${tmpdir}/c  > /dev/null 2>&1 || return 1
  mount -o remount,rw ${tmpdir}/c > /dev/null 2>&1 || { _cleanup_tmrc $tmpdir; return 2; }
  mount -o remount,ro ${tmpdir}/c > /dev/null 2>&1 || { _cleanup_tmrc $tmpdir; return 3; }
  _cleanup_tmrc $tmpdir
  return 0
}


print_new_repository_notice() {
  local name=$1
  local cvmfs_user=$2
  local skip_backup_notice=$3

  echo "\

Before you can install anything, call \`cvmfs_server transaction\`
to enable write access on your repository. Then install your
software in /cvmfs/$name as user $cvmfs_user.
Once you're happy, publish using \`cvmfs_server publish\`

For client configuration, have a look at 'cvmfs_server info'"
  if [ $skip_backup_notice -eq 0 ]; then
    echo "\

If you go for production, backup your masterkey from /etc/cvmfs/keys/!"
  fi
}


get_fd_modes() {
  local path=$1
  cvmfs_publish lsof $path
}

# gets the number of open read-only file descriptors beneath a given path
#
# @param path  the path to look at for open read-only fds
# @return      the number of open read-only file descriptors
count_rd_only_fds() {
  local path=$1
  local cnt=0
  for line in $(get_fd_modes $path); do
    if echo "$line" | grep -qe '^ro';  then cnt=$(( $cnt + 1 )); fi
  done
  echo $cnt
}

# find the partition name for a given file path
#
# @param   path  the path to the file to be checked
# @return  the name of the partition that path resides on
get_partition_for_path() {
  local path="$1"
  df --portability "$path" | tail -n1 | awk '{print $1}'
}


# checks if a given repository is replicable
#
# @param name   the repository name or URL to be checked
# @return       0 if it is a stratum0 repository and replicable
is_master_replica() {
  local name=$1
  local is_master_replica

  if [ $(echo $name | cut --bytes=1-7) = "http://" -o $(echo $name | cut --bytes=1-8) = "https://" ]; then
    is_master_replica=$(get_repo_info_from_url $name -m -L)
  else
    load_repo_config $name
    is_stratum0 $name || return 1
    is_master_replica=$(get_repo_info -m)
  fi

  [ "x$is_master_replica" = "xtrue" ]
}


# get the path of the file that contains the content hash of the reference log
#
# @param name  the name of the repository to be checked
# @return      the full path name
get_reflog_checksum() {
  local name=$1

  echo "/var/spool/cvmfs/${name}/reflog.chksum"
}


# checks if a the reflog checksum is present in the spool directory
#
# @param name  the name of the repository to be checked
# @return      0 if the reflog checksum is available
has_reflog_checksum() {
  local name=$1

  cvmfs_sys_file_is_regular $(get_reflog_checksum $name)
}


# Find the service binary (or detect systemd)
minpidof() {
  $PIDOF_BIN $1 | tr " " "\n" | sort --numeric-sort | head -n1
}


# Return true if systemd is to be used, that is, if neither the service
# command nor the supervisorctl command has been located.  Note that
# the service command is only looked for if systemctl is missing, but
# the supervisorctl command will take precedence over systemctl.
is_systemd() {
  [ x"$SERVICE_BIN" = x"false" ] && [ x"$SUPERVISOR_BIN" = x"false" ]
}


# this strips both the attached signature block and the certificate hash from
# an already signed manifest file and prints the result to stdout
strip_manifest_signature() {
  local signed_manifest="$1"
  # print lines starting with a capital letter (except X for the certificate)
  # and stop as soon as we find the signature delimiter '--'
  awk '/^[A-WY-Z]/ {print $0}; /--/ {exit}' $signed_manifest
}


_update_geodb_days_since_update() {
  local timestamp=$(date +%s)
  local dbdir=$CVMFS_UPDATEGEO_DIR
  local db_mtime=$(stat --format='%Y' ${dbdir}/${CVMFS_UPDATEGEO_DB})
  local days_since_update=$(( ( $timestamp - $db_mtime ) / 86400 ))
  echo "$days_since_update"
}

_update_geodb_lazy_install_slot() {
  [ "`date +%w`" -eq "$CVMFS_UPDATEGEO_DAY"  ] && \
  [ "`date +%k`" -ge "$CVMFS_UPDATEGEO_HOUR" ]
}

_update_geodb_lazy_attempted_today() {
  local attemptdayfile="${CVMFS_UPDATEGEO_DIR}/.last_attempt_day"
  local today="`date +%j`"
  if [ "`cat $attemptdayfile 2>/dev/null`" = "$today" ]; then
    return 0
  fi
  echo "$today" >$attemptdayfile
  return 1
}

_to_syslog_for_geoip() {
  to_syslog "(GeoIP) $1"
}

_update_geodb_install() {
  local retcode=0
  local dburl="${CVMFS_UPDATEGEO_URLBASE}${CVMFS_UPDATEGEO_URLSUFFIX}"
  local dbfile="${CVMFS_UPDATEGEO_DIR}/${CVMFS_UPDATEGEO_DB}"
  local download_target
  local untar_dir=""

  case "$CVMFS_UPDATEGEO_URLSUFFIX" in
    *tar.gz|*.tgz)
      download_target="${dbfile}.tgz"
      untar_dir="${dbfile}.untar"
      ;;
    *.gz)
      download_target="${dbfile}.gz"
      ;;
    *) echo "CVMFS_UPDATEGEO_URLSUFFIX ($CVMFS_UPDATEGEO_URLSUFFIX) ends in unrecognized suffix" >&2
      _to_syslog_for_geoip "CVMFS_UPDATEGEO_URLSUFFIX ends in unrecognized suffix"
      return 1
      ;;
  esac

  local authopts=""
  if [ "$CVMFS_UPDATEGEO_SOURCE" = "maxmind" ]; then
    if [ -z "$CVMFS_GEO_ACCOUNT_ID" ]; then
      echo "CVMFS_GEO_ACCOUNT_ID not set" >&2
      _to_syslog_for_geoip "CVMFS_GEO_ACCOUNT_ID not set"
      return 2
    fi
    if [ -z "$CVMFS_GEO_LICENSE_KEY" ]; then
      echo "CVMFS_GEO_LICENSE_KEY not set" >&2
      _to_syslog_for_geoip "CVMFS_GEO_LICENSE_KEY not set"
      return 3
    fi
    authopts="-u ${CVMFS_GEO_ACCOUNT_ID}:${CVMFS_GEO_LICENSE_KEY}"
  fi

  _to_syslog_for_geoip "started update from $dburl"

  # downloading the GeoIP database file
  curl -L -sS --connect-timeout 10 \
              --max-time 60        \
              --retry 2            \
              $authopts            \
              "$dburl" -o $download_target || true

  if [ -n "$untar_dir" ]; then
    if ! tar tzf $download_target >/dev/null 2>&1; then
      local msg
      if file $download_target|grep -q "ASCII text$"; then
        msg="`cat -v $download_target|head -1`"
      else
        msg="file not valid tarball"
      fi
      echo "failed to download geodb (see url in syslog): $msg" >&2
      _to_syslog_for_geoip "failed to download from $dburl: $msg"
      rm -f $download_target
      return 4
    fi

    # untar the GeoIP database file
    rm -rf $untar_dir
    mkdir -p $untar_dir
    if ! tar xmf $download_target -C $untar_dir --no-same-owner 2>/dev/null; then
      echo "failed to untar $download_target into $untar_dir" >&2
      _to_syslog_for_geoip "failed to untar $download_target into $untar_dir"
      rm -rf $download_target $untar_dir
      return 5
    fi

    # get rid of the tarred GeoIP database
    rm -f $download_target

    # atomically install the GeoIP database
    if ! mv -f $untar_dir/*/${CVMFS_UPDATEGEO_DB} $dbfile; then
      echo "failed to install $dbfile" >&2
      _to_syslog_for_geoip "failed to install $dbfile"
      rm -rf $untar_dir
      return 6
    fi

    # get rid of old database if present
    rm -f ${CVMFS_UPDATEGEO_DIR}/${CVMFS_UPDATEGEO_OLDDB}

    # get rid of any other files in the untar
    rm -rf $untar_dir
  else # must be .gz
    if ! zcat $download_target >$dbfile.new; then
      local msg
      if file $download_target|grep -q "ASCII text$"; then
        msg="`cat -v $download_target|head -1`"
      else
        msg="file could not be uncompressed"
      fi
      echo "failed to download geodb (see url in syslog): $msg" >&2
      _to_syslog_for_geoip "failed to download from $dburl: $msg"
      rm -f $download_target $dbfile.new
      return 7
    fi

    rm -f $download_target

    if ! mv -f $dbfile.new $dbfile; then
      echo "failed to install $dbfile" >&2
      _to_syslog_for_geoip "failed to install $dbfile"
      rm -f $dbfile.new
      return 8
    fi
  fi

  # Remove any old name .mmdb
  find $CVMFS_UPDATEGEO_DIR -name '*.mmdb' ! -name $CVMFS_UPDATEGEO_DB | xargs -r rm -f

  if [ -w "$(get_global_info_v1_path)" ]; then
    # update repositories.json for the new geodb timestamp, if possible
    update_global_repository_info || die "fail (update global repository info)"
  fi

  set_selinux_httpd_context_if_needed "$CVMFS_UPDATEGEO_DIR"

  _to_syslog_for_geoip "successfully updated from $dburl"

  return 0
}

_update_geodb() {
  local dbdir=$CVMFS_UPDATEGEO_DIR
  local dbfile=$dbdir/$CVMFS_UPDATEGEO_DB
  local lazy=false
  local retcode=0

  # parameter handling
  OPTIND=1
  while getopts "l" option; do
    case $option in
      l)
        lazy=true
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command update-geodb: Unrecognized option: $1"
      ;;
    esac
  done

  # sanity checks
  [ -w "$dbdir"  ]   || { echo "Directory '$dbdir' doesn't exist or is not writable by $(whoami)" >&2; return 1; }

  # check if an update/installation needs to be done
  if [ -z "$CVMFS_GEO_DB_FILE" ] && [ -z "$CVMFS_GEO_LICENSE_KEY" ] && \
      [ -r /usr/share/GeoIP/$CVMFS_UPDATEGEO_DB ]; then
    # Use the default location of geoipupdate
    CVMFS_GEO_DB_FILE=/usr/share/GeoIP/$CVMFS_UPDATEGEO_DB
  fi
  if [ -n "$CVMFS_GEO_DB_FILE" ]; then
    # This overrides the update/install; link to the given file instead.
    if [ ! -L "$dbfile" ] || [ "`readlink $dbfile`" != "$CVMFS_GEO_DB_FILE" ]; then
      if [ "$CVMFS_GEO_DB_FILE" != "NONE" ] && [ ! -r "$CVMFS_GEO_DB_FILE" ]; then
        echo "$CVMFS_GEO_DB_FILE doesn't exist or is not readable" >&2
        return 1
      fi
      rm -f $dbfile
      echo "Linking GeoIP Database"
      _to_syslog_for_geoip "linking db from $CVMFS_GEO_DB_FILE"
      ln -s $CVMFS_GEO_DB_FILE $dbfile
    fi
    return 0
  elif [ ! -f "$dbfile" ] || [ -L "$dbfile" ]; then
    echo -n "Installing GeoIP Database... "
  elif ! $lazy; then
    echo -n "Updating GeoIP Database... "
  else
    local days_old=$(_update_geodb_days_since_update)
    if [ $days_old -gt $CVMFS_UPDATEGEO_MAXDAYS ]; then
      if _update_geodb_lazy_attempted_today; then
        # already attempted today, wait until tomorrow
        return 0
      fi
      echo -n "GeoIP Database is very old. Updating... "
    elif [ $days_old -gt $CVMFS_UPDATEGEO_MINDAYS ]; then
      if _update_geodb_lazy_install_slot; then
        if _update_geodb_lazy_attempted_today; then
          # already attempted today, wait until next week
          return 0
        fi
        echo -n "GeoIP Database is expired. Updating... "
      else
        echo "GeoIP Database is expired, but waiting for install time slot."
        return 0
      fi
    else
      return 0
    fi
  fi

  # at this point the database needs to be installed or updated
  _update_geodb_install && echo "done" || { echo "fail"; return 3; }
}

cvmfs_server_update_geodb() {
  _update_geodb $@
}


# checks if the given command name is a supported command of cvmfs_server
#
# @param subcommand   the subcommand to be called
# @return   0 if the command was recognized
is_subcommand() {
  local subcommand="$1"
  local supported_commands="mkfs add-replica import publish rollback rmfs alterfs   \
    resign list info tag list-tags lstags check transaction enter abort snapshot    \
    skeleton migrate list-catalogs diff checkout update-geodb gc catalog-chown      \
    eliminate-hardlinks eliminate-bulk-hashes fix-stats update-info update-repoinfo \
    mount fix-permissions masterkeycard ingest merge-stats print-stats"

  for possible_command in $supported_commands; do
    if [ x"$possible_command" = x"$subcommand" ]; then
      return 0
    fi
  done

  return 1
}


# Flushes data to disk; only flush if the enforced level allowed for the
# provided level.  The order is
#  'none' (never sync)
#  'default' (sync for rare, important operations like mkfs, publish)
#  'cautious' (always sync)
syncfs() {
  local level="${1:-default}"
  local enforced_level="${CVMFS_SYNCFS_LEVEL:-default}"
  [ "x$enforced_level" = "xnone" ] && return || true
  [ "x$enforced_level" = "xdefault" -a "x$level" = "xcautious" ] && return || true

  sync
}


# prints some help information optionally followed by an error message
# afterwards it aborts the script
#
# @param errormsg   an optional error message that is printed after the
#                   actual usage text
usage() {
  errormsg=$1

  echo "\
CernVM-FS Server Tool $(cvmfs_version_string)

Usage: cvmfs_server COMMAND [options] <parameters>

Supported Commands:
  mkfs            [-w stratum0 url] [-u upstream storage] [-o owner]
                  [-m replicable] [-f union filesystem type] [-s S3 config file]
                  [-g disable auto tags] [-G Set timespan for auto tags]
                  [-a hash algorithm (default: SHA-1)]
                  [-z enable garbage collection] [-v volatile content]
                  [-Z compression algorithm (default: zlib)]
                  [-k path to existing keychain] [-p no apache config]
                  [-R require masterkeycard key ]
                  [-V VOMS authorization] [-X (external data)]
                  [-x proxy url]
                  <fully qualified repository name>
                  Creates a new repository with a given name
  add-replica     [-u stratum1 upstream storage] [-o owner] [-w stratum1 url]
                  [-a silence apache warning] [-z enable garbage collection]
                  [-n alias name] [-s S3 config file] [-p no apache config]
                  [-g snapshot group] [-P pass-through repository]
                  <stratum 0 url> <public key | keys directory>
                  Creates a Stratum 1 replica of a Stratum 0 repository
  import          [-w stratum0 url] [-o owner] [-u upstream storage]
                  [-l import legacy repo (2.0.x)] [-s show migration statistics]
                  [-f union filesystem type] [-c file ownership (UID:GID)]
                  [-k path to keys] [-g chown backend] [-r recreate whitelist]
                  [-p no apache config] [-t recreate repo key and certificate]
                  [ -R recreate whitelist and require masterkeycard ]
                  [-x proxy url]
                  <fully qualified repository name>
                  Imports an old CernVM-FS repository into a fresh repo
  publish         [-p pause for tweaks] [-n manual revision number] [-v verbose]
                  [-a tag name] [-m tag description]
                  [-X (force external data) | -N (force native data)]
                  [-Z compression algorithm] [-F authz info file]
                  [-f use force remount if necessary]
                  <fully qualified name>
                  Make a new repository snapshot
  gc              [-r number of revisions to preserve]
                  [-t time stamp after which revisions are preserved]
                  [-l print deleted objects] [-L log of deleted objects]
                  [-f force] [-d dry run]
                  {-A collect all garbage-collectable repos, log to gc.log |
                   -a collect all but replicas not collected upstream |
                    <fully qualified name>}
                  Remove unreferenced data from garbage-collectable repository
  rmfs            [-p(reserve) repo data and keys] [-f don't ask again]
                  <fully qualified name>
                  Remove the repository
  abort           [-f don't ask again]
                  <fully qualified name>
                  Abort transaction and return to the state before
  rollback        [-t tag] [-f don't ask again]
                  <fully qualified name>
                  Re-publishes the given tag as the new latest revision.
                  All snapshots between trunk and the target tag become
                  inaccessible.  Without a tag name, trunk-previous is used.
  resign          [ -w path to existing whitelist ]
                  [ -d days until expiration (default 30) ]
                  [ -f don't ask again ]
                  <fully qualified name>
                  Re-sign the whitelist.
                  Default expiration days goes down to 7 with masterkeycard.
  resign -p       <fully qualified name>
                  Re-sign .cvmfspublished
  masterkeycard   -a Checks if a smartcard is available
                  -k Checks whether a key is stored in a card
                  -r Reads pub key from a card to stdout
                  [ -f don't ask again ] -s <fully qualified name>
                     Stores masterkey and pub key of a repository into a card
                  [ -f don't ask again ] -d
                     Deletes a masterkey's certificate (pub key) from a card
                  [ -f don't ask again ] -c <fully qualified name or wildcard>
                     Converts given repositories to use card for whitelist
  list-catalogs   [-s catalog sizes] [-e catalog entry counts]
                  [-h catalog hashes] [-x machine readable]
                  <fully qualified name>
                  Print a full list of all nested catalogs of a repository
  diff            [-m(achine readable)] [-h(eader line)]
                  [-s <source tag>] [-d <destination tag>]
                  <fully qualified name>
                  Show change set between two snapshots (default: last publish)
  info            <fully qualified name>
                  Print summary about the repository
  tag             Create and manage named snapshots
                  [-a create tag <name>] [-m message] [-h hash]
                  [-r remove tag <name>] [-f don't ask again]
                  [-i inspect tag <name>] [-x machine readable]
                  [-b list branch hierarchy] [-x machine readable]
                  [-l list tags] [-x machine readable]
                  <fully qualified name>
                  Print named tags (snapshots) of the repository
  checkout        [-t <tag name>] [-b <branch name>]
                  <fully qualified name>
  check           [-c disable data chunk existence check]
                  [-i check data integrity] (may take some time)]
                  [-t tag (check given tag instead of trunk)]
                  [-s path to nested catalog subtree to check]
                  [-r repair reflog problems]
                  [-a check all active local repos, log to checks.log |
                    <fully qualified name> ]
                  <fully qualified name>
                  Checks if the repository is sane
  transaction     [-t (timeout in seconds for waiting if the repository is busy, 0=infinite)]
                  [-T /template-from=/template-to]
                  <fully qualified name>
                  Start to edit a repository
  snapshot        [-t fail if other snapshot is in progress]
                  <fully qualified name>
                  Synchronize a Stratum 1 replica with the Stratum 0 source
  snapshot -a     [-s use separate logs in /var/log/cvmfs for each repository]
                  [-n DEPRECATED (used to warn if no logrotate config, now has no effect) ]
                  [-i skip repositories that have not run initial snapshot]
                  [-g group (do only the repositories in snapshot group)]
                  Do snapshot on all active replica repositories
  mount           [-a | <fully qualified name>]
                  Mount repositories in /cvmfs, for instance after reboot
  migrate         <fully qualified name>
                  Migrates a repository to the current version of CernVM-FS
  catalog-chown   <-u uid map file> <-g gid map file> <fully qualified name>
                  Bulk change of the ownership ids in CernVM-FS catalogs
  list            List available repositories
  update-geodb    [-l update lazily based on CVMFS_UPDATEGEO* variables]
                  Updates the geo-IP database
  update-info     [-p no apache config] [-e don't edit /info/meta]
                  Open meta info JSON file for editing
  update-repoinfo [-f path to JSON file]
                  <fully qualified name>
                  Open repository meta info JSON file for editing
  ingest          -t tarfile
                  -b base directory
                  [-d <folder to delete>]
                  [-c create nested catalog in base directory]
                  <fully qualified name>
                  Extract the content of the tarfile inside the base directory,
                  in the same transaction it also delete the required folders.
                  Use '-' as -t argument to read the tarball from STDIN.
  print-stats     [-o output_file]
                  [-t table_name]
                  [-s separator] - char
                  <fully qualified name>
                  Print statistics values for a table (default publish_statistics)
                  using the separator specified. (default  '|')
  merge-stats     [-o output db file]
                  <db_file_1> <db_file_2>
                  Merge tables from two database files.
  enter           [-w stratum0 url] [-c path to cvmfs2 binary] 
                  [-C path to extra client config] [-r run as root]
                  [-k keep session after shell exits] [-t open a transaction] 
                  [-l remove session but keep logs after shell exits] 
                  [-x path to repository configuration]
                  <fully qualified repository name> [-- <command> <params>]
                  Open an ephemeral namespace to publish content
"


  if [ x"$errormsg" != x ]; then
    echo "\
________________________________________________________________________

NOTE: $errormsg
"
    exit 3
  else
    exit 2
  fi
}

#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Functionality related to SSL

# This file depends on functions implemented in the following files:
# - cvmfs_server_sys.sh
# - cvmfs_server_util.sh
# - cvmfs_server_masterkeycard.sh

create_master_key() {
  local name=$1
  local user=$2

  local master_pub="/etc/cvmfs/keys/$name.pub"
  if masterkeycard_cert_available >/dev/null; then
    masterkeycard_read_pubkey >$master_pub
  else
    local master_key="/etc/cvmfs/keys/$name.masterkey"
    openssl genrsa -out $master_key 2048 > /dev/null 2>&1
    openssl rsa -in $master_key -pubout -out $master_pub > /dev/null 2>&1
    chmod 400 $master_key
    chown $user $master_key
  fi
  chmod 444 $master_pub
  chown $user $master_pub
}


create_cert() {
  local name=$1
  local user=$2

  local key; key="/etc/cvmfs/keys/$name.key"
  local csr; csr="/etc/cvmfs/keys/$name.csr"
  local crt; crt="/etc/cvmfs/keys/$name.crt"

  # Create self-signed certificate
  local cn="$name"
  if [ $(echo -n "$cn" | wc -c) -gt 30 ]; then
    cn="$(echo -n "$cn" | head -c 30)[...]"
  fi
  cn="$cn CernVM-FS Release Managers"
  openssl genrsa -out $key 2048 > /dev/null 2>&1
  openssl req -new -subj "/CN=$cn" \
    -key $key -out $csr > /dev/null 2>&1
  openssl x509 -req -days 365 -in $csr -signkey $key -out $crt > /dev/null 2>&1
  rm -f $csr
  chmod 444 $crt
  chmod 400 $key
  chown $user $crt $key
}


create_whitelist() {
  local name=$1
  local user=$2
  local spooler_definition=$3
  local temp_dir=$4
  local expire_days=$5
  local rewrite_path=$6
  local usemasterkeycard=0
  local hash_algorithm

  local openssl_keyutil_cmd="openssl pkeyutl"

  local whitelist
  whitelist=${temp_dir}/whitelist.$name

  local masterkey=/etc/cvmfs/keys/${name}.masterkey
  if cvmfs_sys_file_is_regular $masterkey; then
    if [ -z "$expire_days" ]; then
      expire_days=30
    fi
    echo -n "Signing $expire_days day whitelist with master key... "
  elif masterkeycard_cert_available >/dev/null; then
    usemasterkeycard=1
    if [ -z "$expire_days" ]; then
      expire_days=7
    fi
    echo -n "Signing $expire_days day whitelist with masterkeycard... "
  else
    die "Neither masterkey nor masterkeycard is available to sign whitelist!"
  fi
  echo `date -u "+%Y%m%d%H%M%S"` > ${whitelist}.unsigned
  echo "E`date -u --date="+$expire_days days" "+%Y%m%d%H%M%S"`" >> ${whitelist}.unsigned
  echo "N$name" >> ${whitelist}.unsigned
  if [ -n "$rewrite_path" ]; then
    local fingerprint
    fingerprint="`cat -v $rewrite_path | awk '/^N/{getline;print;exit}'`"
    echo "$fingerprint" >> ${whitelist}.unsigned
    hash_algorithm="`echo "$fingerprint"|sed -n 's/.*-//p'|tr '[A-Z]' '[a-z]'`"
    hash_algorithm="${hash_algorithm:-sha1}"
  else
    hash_algorithm="${CVMFS_HASH_ALGORITHM:-sha1}"
    openssl x509 -in /etc/cvmfs/keys/${name}.crt -outform der | \
      __publish hash -a $hash_algorithm -f >> ${whitelist}.unsigned
  fi

  local hash;
  hash="`cat ${whitelist}.unsigned | __publish hash -a $hash_algorithm`"
  echo "--" >> ${whitelist}.unsigned
  echo $hash >> ${whitelist}.unsigned
  echo -n $hash > ${whitelist}.hash
  if [ $usemasterkeycard -eq 1 ]; then
    masterkeycard_sign ${whitelist}.hash ${whitelist}.signature
    # verify the signature because it is not 100% reliable
    local pubkey=/etc/cvmfs/keys/${name}.pub
    if [ -f $pubkey ]; then
      cp $pubkey ${whitelist}.pub
    else
      masterkeycard_read_pubkey >${whitelist}.pub
    fi
    local checkhash="`${openssl_keyutil_cmd} -verify -inkey ${whitelist}.pub -pubin -sigfile ${whitelist}.signature -in ${whitelist}.hash 2>/dev/null`"
    rm -f ${whitelist}.pub
    [ "$checkhash" = "Signature Verified Successfully" ] || die "invalid masterkeycard signature"
  else
    ${openssl_keyutil_cmd} -inkey $masterkey -sign -in ${whitelist}.hash -out ${whitelist}.signature
  fi
  cat ${whitelist}.unsigned ${whitelist}.signature > $whitelist
  chown $user $whitelist

  rm -f ${whitelist}.unsigned ${whitelist}.signature ${whitelist}.hash
  if [ -n "$rewrite_path" ]; then
    # copy first to a new name in case the filesystem is full
    cp -f $whitelist ${rewrite_path}.new
    chown $user ${rewrite_path}.new
    mv -f ${rewrite_path}.new ${rewrite_path}
  else
    __swissknife upload -i $whitelist -o .cvmfswhitelist -r $spooler_definition
  fi
  rm -f $whitelist
  syncfs
  echo "done"
}


import_keychain() {
  local name=$1
  local keys_location="$2"
  local cvmfs_user=$3
  local keys="$4"

  local global_key_dir="/etc/cvmfs/keys"
  mkdir -p $global_key_dir || return 1
  for keyfile in $keys; do
    echo -n "importing $keyfile ... "
    if [ ! -f "${global_key_dir}/${keyfile}" ]; then
      cp "${keys_location}/${keyfile}" $global_key_dir || return 2
    fi
    local key_mode=400
    if echo "$keyfile" | grep -vq '.*key$\|.gw$'; then
      key_mode=444
    fi
    chmod $key_mode "${global_key_dir}/${keyfile}"   || return 3
    chown $cvmfs_user "${global_key_dir}/${keyfile}" || return 4
    echo "done"
  done
}

#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Functionality related to the Apache web server

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh


# checks if apache is installed and running
#
# @return  0 if apache is installed and running
check_apache() {
  [ -d /etc/${APACHE_CONF} ] && request_apache_service status > /dev/null
}


request_apache_service() {
  local request_verb="$1"
  if is_systemd; then
    /bin/systemctl $request_verb ${APACHE_CONF}
  elif [ x"$SUPERVISOR_BIN" != x"false" ]; then
    if [ x"$request_verb" = x"reload" ]; then
      request_verb="restart"
    fi
    $SUPERVISOR_BIN $request_verb ${APACHE_CONF}
  else
    $SERVICE_BIN $APACHE_CONF $request_verb
  fi
}


reload_apache() {
  echo -n "Reloading Apache... "
  local verb=reload
  if [ "x$CVMFS_SERVER_APACHE_RELOAD_IS_RESTART" = "xtrue" ]; then
    # The reset-failed verb is only available with systemd
    if is_systemd; then
      request_apache_service reset-failed > /dev/null 2>&1
    fi
    verb=restart
  fi
  request_apache_service $verb > /dev/null || die "fail"
  echo "done"
}


# An Apache reload is asynchronous, the new configuration is not immediately
# accessible.  Wait up to 1 minute until a test url can be fetched
wait_for_apache() {
  local url="$1"

  local now=$(date +%s)
  local deadline=$(($now + 60))
  while [ $now -lt $deadline ]; do
    if curl -f -I --max-time 10 $(get_curl_proxy) $(get_x509_cert_settings) \
       $(get_follow_http_redirects_flag) "$url" >/dev/null 2>&1;
    then
      return 0
    fi
    sleep 1
    now=$(date +%s)
  done

  # timeout
  return 1
}


get_url() {
  local url="$1"
  local timeout="$2"
  local curlopts="$3"

  curl -f -sS --max-time $timeout \
    --retry 2 --retry-delay 5 $curlopts \
    $(get_curl_proxy) $(get_x509_cert_settings) \
    $(get_follow_http_redirects_flag) "$url"
}


check_url() {
  local url="$1"
  local timeout="$2"

  get_url "$1" "$2" -I >/dev/null 2>&1
}


check_apache_module() {
  local module_name="$1"
  ${APACHE_CTL} -M 2>&1 | grep -q "$module_name"
}


# checks if wsgi apache module is installed and enabled
check_wsgi_module() {
  if check_apache_module "wsgi_module"; then
    return 0
  fi

  echo "The apache wsgi module must be installed and enabled.
The required package is called ${APACHE_WSGI_MODPKG}."
  if cvmfs_sys_is_redhat; then
    case "`cat /etc/redhat-release`" in
      *"release 5."*)
        if cvmfs_sys_file_is_regular /etc/httpd/conf.d/wsgi.conf ; then
          # older el5 epel versions didn't automatically enable it
          echo "To enable the module, see instructions in /etc/httpd/conf.d/wsgi.conf"
        else
          echo "The package is in the epel yum repository."
        fi
        ;;
    esac
  fi
  exit 1
}

# checks if proxy apache module is installed and enabled
check_proxy_module() {
  if ! check_apache_module "proxy_module"; then
    echo "The apache proxy module must be installed and enabled."
    exit 1
  fi

  if ! check_apache_module "proxy_http_module"; then
    echo "The apache proxy_http module must be installed and enabled."
    exit 1
  fi

  return 0
}


# retrieves the apache version string
get_apache_version() {
  ${APACHE_BIN} -v | head -n1 | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+'
}


get_apache_conf_filename() {
  local name=$1
  echo "cvmfs.${name}.conf"
}


restart_apache() {
  echo -n "Restarting Apache... "
  request_apache_service restart > /dev/null || die "fail"
  echo "done"
}


# cvmfs requires a couple of apache modules to be enabled when running on
# an ubuntu machine. This enables these modules on an ubuntu installation
# Note: this function requires a privileged user
ensure_enabled_apache_modules() {
  local a2enmod_bin=
  local apache2ctl_bin=
  a2enmod_bin="$(find_sbin    a2enmod)"    || return 0
  apache2ctl_bin="$(find_sbin apache2ctl)" || return 0

  local restart=0
  local retcode=0
  local modules="headers expires proxy proxy_http"

  for module in $modules; do
    $apache2ctl_bin -M 2>/dev/null | grep -q "$module" && continue
    $a2enmod_bin $module > /dev/null 2>&1 || { echo "Warning: failed to enable apache2 module $module"; retcode=1; }
    restart=1
  done

  # restart apache if needed
  if [ $restart -ne 0 ]; then
    restart_apache 2>/dev/null | { echo "Warning: Failed to restart apache after enabling necessary modules"; retcode=2; }
  fi

  return $retcode
}


# find location of apache configuration files
#
# @return   the location of apache configuration files (stdout)
get_apache_conf_path() {
  local res_path="/etc/${APACHE_CONF}"
  if [ x"$(get_apache_conf_mode)" = x"$APACHE_CONF_MODE_CONFAVAIL" ]; then
    echo "${res_path}/conf-available"
  elif [ -d "${res_path}/modules.d" ]; then
    echo "${res_path}/modules.d"
  else
    echo "${res_path}/conf.d"
  fi
}


# returns the apache configuration string for 'allow from all'
# Note: this is necessary, since apache 2.4.x formulates that different
#
# @return   a configuration snippet to allow s'th from all hosts (stdout)
get_compatible_apache_allow_from_all_config() {
  local minor_apache_version=$(version_minor "$(get_apache_version)")
  if [ $minor_apache_version -ge 4 ]; then
    echo "Require all granted"
  else
    local nl='
'
    echo "Order allow,deny${nl}    Allow from all"
  fi
}


# writes apache configuration file
# This figures out where to put the apache configuration file depending
# on the running apache version
# Note: Configuration file content is expected to come through stdin
#
# @param   file_name  the name of the apache config file (no path!)
# @return             0 on success
create_apache_config_file() {
  local file_name=$1
  local conf_path
  conf_path="$(get_apache_conf_path)"

  # create (or append) the conf file
  cat - > ${conf_path}/${file_name} || return 1

  # the new apache requires the enable the config afterwards
  if [ x"$(get_apache_conf_mode)" = x"$APACHE_CONF_MODE_CONFAVAIL" ]; then
    a2enconf $file_name > /dev/null || return 2
  fi

  return 0
}


# removes apache config files dependent on the apache version in place
# Note: As of apache 2.4.x `a2disconf` needs to be called before removal
#
# @param   file_name  the name of the conf file to be removed (no path!)
# @return  0 on successful removal
remove_apache_config_file() {
  local file_name=$1
  local conf_path
  conf_path="$(get_apache_conf_path)/${file_name}"

  # disable configuration on newer apache versions
  if [ x"$(get_apache_conf_mode)" = x"$APACHE_CONF_MODE_CONFAVAIL" ]; then
    a2disconf $file_name > /dev/null 2>&1 || return 1
  fi

  # remove configuration file
  rm -f $conf_path
}


# check if an apache configuration file exists. This looks in the appropriate
# place, depending on the installed apache version.
has_apache_config_file() {
  local file_name=$1
  is_local_upstream $CVMFS_UPSTREAM_STORAGE || return 1
  local conf_path
  conf_path="$(get_apache_conf_path)/${file_name}"
  cvmfs_sys_file_is_regular $conf_path
}


# figure out apache config file mode
#
# @return   apache config mode (stdout) (see globals below)
get_apache_conf_mode() {
  [ -d /etc/${APACHE_CONF}/conf-available ] && echo $APACHE_CONF_MODE_CONFAVAIL \
                                            || echo $APACHE_CONF_MODE_CONFD
}


# creates a standard Apache configuration file for a repository
#
# @param name         the name of the endpoint to be served
# @param storage_dir  the storage location of the data
# @param with_wsgi    whether or not to enable WSGI api functions
create_apache_config_for_endpoint() {
  local name=$1
  local storage_dir=$2
  local with_wsgi="$3"

  create_apache_config_file "$(get_apache_conf_filename $name)" << EOF
# Created by cvmfs_server.  Don't touch.

KeepAlive On
AddType application/json .json
# Translation URL to real pathname
Alias /cvmfs/${name} ${storage_dir}
<Directory "${storage_dir}">
    Options -MultiViews
    AllowOverride Limit AuthConfig
    $(get_compatible_apache_allow_from_all_config)

    EnableMMAP Off
    EnableSendFile Off

    <FilesMatch "^\.cvmfs">
        ForceType application/x-cvmfs
    </FilesMatch>
    <FilesMatch "^[^.]*$">
        ForceType application/octet-stream
    </FilesMatch>

    # Avoid Last-Modified and If-Modified-Since because of squid bugs
    Header unset Last-Modified
    RequestHeader unset If-Modified-Since
    FileETag None

    ExpiresActive On
    ExpiresDefault "access plus 3 days"
    # 60 seconds and below is not cached at all by Squid default settings
    ExpiresByType application/x-cvmfs "access plus 61 seconds"
    ExpiresByType application/json    "access plus 61 seconds"
</Directory>
EOF

  if [ x"$with_wsgi" != x"" ]; then
    create_apache_config_for_webapi
  fi
}


# creates an Apache proxy-pass configuration file for a pass-through repository
#
# @param name         the name of the endpoint to be served
# @param storage_dir  the storage location of the data
# @param with_wsgi    whether or not to enable WSGI api functions
create_apache_proxy_config_for_endpoint() {
  local name=$1
  local pt_url=$2
  local with_wsgi="$3"

  create_apache_config_file "$(get_apache_conf_filename $name)" << EOF
# Created by cvmfs_server.  Don't touch.

KeepAlive On
AddType application/json .json

# Do not ProxyPass GeoAPI requests
ProxyPass /cvmfs/${name}/api/ !

# ProxyPass data requests
ProxyPass /cvmfs/${name} $pt_url
ProxyPassReverse /cvmfs/${name} $pt_url
EOF

  if [ x"$with_wsgi" != x"" ]; then
    create_apache_config_for_webapi
  fi
}


has_apache_config_for_global_info() {
  has_apache_config_file $(get_apache_conf_filename "info")
}


create_apache_config_for_global_info() {
  ! has_apache_config_for_global_info || return 0
  local storage_dir="${DEFAULT_LOCAL_STORAGE}/info"
  create_apache_config_for_endpoint "info" "$storage_dir"
}


create_apache_config_for_webapi() {
  # start the name with a plus sign to make sure it is read by
  #  apache before the configs for individual repositories
  ! has_apache_config_file $(get_apache_conf_filename +webapi) || return 0
  create_apache_config_file "$(get_apache_conf_filename +webapi)" << EOF
# Created by cvmfs_server.  Don't touch.
AliasMatch ^/cvmfs/([^/]+)/api/(.*)\$ /var/www/wsgi-scripts/cvmfs-server/cvmfs-api.wsgi/\$1/\$2
WSGIDaemonProcess cvmfsapi threads=64 display-name=%{GROUP} \
  python-path=/usr/share/cvmfs-server/webapi
<Directory /var/www/wsgi-scripts/cvmfs-server>
  WSGIProcessGroup cvmfsapi
  WSGIApplicationGroup cvmfsapi
  Options ExecCGI
  SetHandler wsgi-script
  $(get_compatible_apache_allow_from_all_config)
</Directory>
WSGISocketPrefix /var/run/wsgi
EOF
}

# for migrate to 2.1.20 backward compatibility
cat_wsgi_config() {
  # do nothing
  return
}


remove_config_files() {
  local name=$1
  load_repo_config $name

  rm -rf /etc/cvmfs/repositories.d/$name
  local apache_conf_file_name="$(get_apache_conf_filename $name)"
  if has_apache_config_file "$apache_conf_file_name"; then
    remove_apache_config_file "$apache_conf_file_name"
    if [ -z "$(get_or_guess_repository_name)" ]; then
      # no repositories left, remove extra config files
      for confname in +webapi info; do
        apache_conf_file_name="$(get_apache_conf_filename $confname)"
        if has_apache_config_file "$apache_conf_file_name"; then
          remove_apache_config_file "$apache_conf_file_name"
        fi
      done
    fi
    reload_apache > /dev/null
  fi
}


#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# JSON "API" related functions

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh


get_global_info_path() {
  echo "${DEFAULT_LOCAL_STORAGE}/info"
}


get_global_info_v1_path() {
  echo "$(get_global_info_path)/v${LATEST_JSON_INFO_SCHEMA}"
}


_write_info_file() {
  local info_file="${1}.json"
  local info_file_dir="$(get_global_info_v1_path)"
  local info_file_path="${info_file_dir}/${info_file}"
  local tmp_file="${info_file_dir}/${info_file}.txn.$(date +%s)"

  cat - > $tmp_file
  chmod 0644 $tmp_file
  mv -f $tmp_file $info_file_path
  set_selinux_httpd_context_if_needed $info_file_dir
}


_check_info_file() {
  local info_file="${1}.json"
  cvmfs_sys_file_is_regular "$(get_global_info_v1_path)/${info_file}"
}


_available_repos() {
  local filter="$1"
  local repo=""
  local repo_cfg_path="/etc/cvmfs/repositories.d"

  [ $(ls $repo_cfg_path | wc -l) -gt 0 ] || return 0
  for repository in ${repo_cfg_path}/*; do
    repo=$(basename $repository)
    if ( [ x"$filter" = x"" ]                              ) || \
       ( [ x"$filter" = x"stratum0" ] && is_stratum0 $repo ) || \
       ( [ x"$filter" = x"stratum1" ] && is_stratum1 $repo ); then
      echo $repo
    fi
  done
}


_render_repos() {
  local i=$#

  for repo in $@; do
    CVMFS_PASSTHROUGH=false
    load_repo_config $repo

    echo '    {'
    echo '      "name"  : "'$CVMFS_REPOSITORY_NAME'",'
    if [ x"$CVMFS_REPOSITORY_NAME" != x"$repo" ]; then
      echo '      "alias" : "'$repo'",'
    fi
    if [ x"$CVMFS_PASSTHROUGH" = x"true" ]; then
      echo '      "pass-through" : true,'
    fi
    echo '      "url"   : "/cvmfs/'$repo'"'
    echo -n '    }'

    i=$(( $i - 1 ))
    [ $i -gt 0 ] && echo "," || echo ""
  done
}


_render_info_file() {
  echo '{'
  echo '  "schema" : '$LATEST_JSON_INFO_SCHEMA','

  local dbfile="${CVMFS_UPDATEGEO_DIR}/${CVMFS_UPDATEGEO_DB}"
  if [ -f "$dbfile" ]; then
    local modtime
    modtime="$(date --utc --date @$(stat -c %Y $dbfile))"
    echo '  "last_geodb_update" : "'$modtime'",'
  fi

  if [ "x${CVMFS_PUBLISH_VERSIONS_IN_META_FILE}" = "xtrue" ]; then
    echo '  "cvmfs_version" : "'$(cvmfs_version_string)'",'
    echo '  "os_id" : "'$(_os_id)'", '
    echo '  "os_version_id" : "'$(_os_version_id)'", '
    echo '  "os_pretty_name" : "'$(_os_pretty_name)'", '
  fi

  echo '  "repositories" : ['

  _render_repos $(_available_repos "stratum0")

  echo '  ],'
  echo '  "replicas" : ['

  _render_repos $(_available_repos "stratum1")

  echo '  ]'
  echo '}'
}


has_global_info_path() {
  [ -d $(get_global_info_path) ] && [ -d $(get_global_info_v1_path) ]
}


update_global_repository_info() {
  # sanity checks
  has_global_info_path || return 1
  is_root              || return 2

  _render_info_file | _write_info_file "repositories"
}


update_global_meta_info() {
  local meta_info_file="$1"
  has_global_info_path || return 1
  is_root              || return 2

  cat "$meta_info_file" | _write_info_file "meta"
}


get_editor() {
  local editor=${EDITOR:=vi}
  if ! which $editor  > /dev/null 2>&1; then
    echo  "Didn't find editor '$editor'." 1>&2
    echo "Consider to use the \$EDITOR environment variable" 1>&2
    exit 1
  fi
  echo $editor
}


has_jq() {
  which jq > /dev/null 2>&1
}

check_jq() {
  local hasjq=1
  if ! has_jq; then
    hasjq=0
    echo 1>&2
    echo "Warning: Didn't find 'jq' on your system. It is your responsibility" 1>&2
    echo "         to produce a valid JSON file." 1>&2
    echo 1>&2
    read -p "  Press any key to continue..." nirvana
  fi
  echo $hasjq
}


validate_json() {
  local json_file="$1"

  if ! which jq > /dev/null 2>&1; then
    return 0 # no jq -> assume JSON is valid
  fi

  jq '.' $json_file 2>&1
}


edit_json_until_valid() {
  local json_file="$1"
  local editor=$(get_editor)
  local has_jq=$(check_jq)

  local retval=0
  while true; do
    $editor $json_file < $(tty) > $(tty) 2>&1
    [ $has_jq -eq 1 ] || break

    local jq_output=""
    local retry=""
    if ! jq_output=$(validate_json $json_file); then
      echo
      echo "Your JSON file is invalid, please check again:"
      echo "$jq_output"
      read -p "Edit again? [y]: " retry
      if [ x"$retry" != x"y" ] && \
         [ x"$retry" != x"Y" ] && \
         [ x"$retry" != x""  ]; then
        retval=1
        break
      fi
    else
      break
    fi
  done

  return $retval
}

# This updates a variable in the repository status file .cvmfs_status.json
# It assumes that it is running under a repository lock
# @param name      the name of the repository to update
# @param variable  the status variable to update
# @param value     the value to set the variable to
update_repo_status() {
  if ! has_jq; then
    # silently do nothing if there is no jq
    return
  fi

  local name="$1"
  local variable="$2"
  local value="$3"

  local old_status="$(read_repo_item $name .cvmfs_status.json)"
  if [ -z "$old_status" ]; then
    old_status="{}"
  fi

  load_repo_config $name
  local user_shell="$(get_user_shell $name)"

  local jq_tmp="${CVMFS_SPOOL_DIR}/tmp/status.json"
  echo "$old_status" | jq ".$variable=\"$value\"" | $user_shell "cat > $jq_tmp"
  $user_shell "$(__swissknife_cmd) upload -r ${CVMFS_UPSTREAM_STORAGE} \
    -i $jq_tmp                                                     \
    -o .cvmfs_status.json"
  $user_shell "rm -f $jq_tmp"
}


get_json_field() {
  has_jq || return ""

  local snippet="$1"
  local field="$2"
  echo "$snippet" | jq -r ".$field // empty"
}
#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of functionality common to all "cvmfs_server" commands
#


# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_json.sh


# checks the parameter count for a situation where we might be able to guess
# the repository name based on the repositories present in the system
# Note: if the parameter count does not fit or if guessing is impossible,
#       this will print the usage string with an error message and exit
# Note: this method is commonly used right before invoking
#       `get_or_guess_repository_name` to check its preconditions and report
#       error before actually doing something wrong
#
# @param provided_parameter_count  number of parameters provided by the user
# @param allow_multiple_names      switches off the usage print for too many
#                                  detected script parameters (see next fn)
check_parameter_count_with_guessing() {
  local provided_parameter_count=$1
  local allow_multiple_names=$2

  if [ $provided_parameter_count -lt 1 ]; then
    # check if we have not _exactly_ one repository present
    if [ $(ls /etc/cvmfs/repositories.d/ | wc -l) -ne 1 ]; then
      usage "Please provide a repository name"
    fi
  fi

  if [ $provided_parameter_count -gt 1 ] && \
     [ x"$allow_multiple_names" = x"" ]; then
    usage "Too many arguments provided"
  fi

  return 0
}


# checks the parameter count when we accept more than one repository for the
# command.
# Note: this method prints an error message if appropriate and exists the script
#       execution
#
# @param provided_parameter_count  number of parameters provided by the user
check_parameter_count_for_multiple_repositories() {
  local provided_parameter_count=$1
  check_parameter_count_with_guessing $provided_parameter_count allow_multiple
  return $?
}


# checks if the right number of arguments was provided
# if the wrong number was provided it will kill the script after printing the
# usage text and an error message
#
# @param expected_parameter_count   number of expected parameters
# @param provided_parameter_count   number of provided parameters
check_parameter_count() {
  local expected_parameter_count=$1
  local provided_parameter_count=$2

  if [ $provided_parameter_count -lt $expected_parameter_count ]; then
    usage "Too few arguments provided"
  fi
  if [ $provided_parameter_count -gt $expected_parameter_count ]; then
    usage "Too many arguments provided"
  fi
}


# guesses a list of repository names based on file system wildcards
#
# @param ...    all repository hints provided by the user of the script
#               Like: test.local repo.* *.cern.ch
get_or_guess_multiple_repository_names() {
  local repo_dir="/etc/cvmfs/repositories.d"
  local repo_names=""

  if [ $# -eq 0 ]; then
    repo_names=$(get_or_guess_repository_name)
    echo $repo_names
    return 0;
  fi

  for input_pattern in "$@"; do
    local names="$(ls --directory $repo_dir/$input_pattern 2>/dev/null)"
    if [ x"$names" = x"" ]; then
      repo_names="$repo_names $input_pattern"
    else
      for name in $names; do
        if ! contains "$repo_names" $(basename $name); then
          repo_names="$repo_names $(basename $name)"
        fi
      done
    fi
  done

  echo "$repo_names"
}


# checks if the given repository name already exists
#
# @param given_name   the name of the repository to be checked
# @return             0 if the repository was found
check_repository_existence() {
  local given_name="$1"
  local fqrn

  # empty name or wildcards are not allowed (and thus does not exist)
  if [ x"$given_name" = x ] || echo "$given_name" | grep -q "*"; then
    return 1
  fi

  # Check if exists
  fqrn=$(cvmfs_mkfqrn $given_name)
  [ -d /etc/cvmfs/repositories.d/$fqrn ]
}


# checks the existence of a list of repositories
# Note: the function echo's an error message and stops the execution of the
#       script by default.
#
# @param given_names   the list of repository names to be checked
# @param no_kill       (optional) skip the termination on error
# @return              0 if all listed repositories exist
check_multiple_repository_existence() {
  local given_names="$1"
  local no_kill=$2

  for name in $given_names; do
    # If "name" contains a subpath (i.e. repo.cern.ch/sub/path) only
    # the repository name should be kept
    local repo_name=$(echo $name | cut -d'/' -f1)
    if ! check_repository_existence $repo_name; then
      if [ x"$no_kill" = x"" ]; then
        die "The repository $repo_name does not exist."
      else
        return 1
      fi
    fi
  done
  return 0
}


# mangles the repository name into a fully qualified repository name
#
# @param repository_name       the repository name given by the user
# @return                      echoes the correct repository name to use
get_repository_name() {
  local repository_name=$1
  echo $(cvmfs_mkfqrn $repository_name)
}


# loads the configuration for a specific repository
load_repo_config() {
  local name=$1
  if [ ! -e /etc/cvmfs/repositories.d/${name}/server.conf ]; then
    die "Error: The repository $name does not exist."
  fi
  . /etc/cvmfs/repositories.d/${name}/server.conf
  if [ x"$CVMFS_REPOSITORY_TYPE" = x"stratum0" ]; then
    . /etc/cvmfs/repositories.d/${name}/client.conf
  else
    . /etc/cvmfs/repositories.d/${name}/replica.conf
  fi
}


# retrieves (or guesses) the version of CernVM-FS that was used to create this
# repository.
# @param name  the name of the repository to be checked
repository_creator_version() {
  local name="$1"
  load_repo_config $name
  local version="$CVMFS_CREATOR_VERSION"
  if [ x"$version" = x ]; then
    version="2.1.6" # 2.1.6 was the last version, that did not store the creator
                    # version... therefore this has to be handled as "<= 2.1.6"
                    # Note: see also `mangle_version_string()`
  elif [ x"$version" = x"2.2.0" ]; then
    version="2.2.0-0" # CernVM-FS 2.2.0-0 was a server-only pre-release which is
                      # incompatible with 2.2.0-1
                      # 2.2.0-0 marks itself as CVMFS_CREATOR_VERSION=2.2.0
                      # while 2.2.0-1 features  CVMFS_CREATOR_VERSION=2.2.0-1
  fi
  echo $version
}


# gets the catalog root hash associated to a tag name
#
# @param repository_name   the name of the repository to be checked
# @param tag               the tag name to be checked
# @return                  hash value or the empty string
get_tag_hash() {
  local repository_name="$1"
  local tag="$2"

  load_repo_config $repository_name
  __swissknife tag_info                       \
    -w $CVMFS_STRATUM0                        \
    -t ${CVMFS_SPOOL_DIR}/tmp                 \
    -p /etc/cvmfs/keys/${repository_name}.pub \
    -f $repository_name                       \
    $(get_swissknife_proxy)                   \
    $(get_follow_http_redirects_flag) -x      \
    -n "$tag" 2>/dev/null | cut -d" " -f2
}


# gets the branch associated to a tag name
#
# @param repository_name   the name of the repository to be checked
# @param tag               the tag name to be checked
# @return                  branch name
get_tag_branch() {
  local repository_name="$1"
  local tag="$2"

  load_repo_config $repository_name
  local branch=$(__swissknife tag_info        \
    -w $CVMFS_STRATUM0                        \
    -t ${CVMFS_SPOOL_DIR}/tmp                 \
    -p /etc/cvmfs/keys/${repository_name}.pub \
    -f $repository_name                       \
    $(get_swissknife_proxy)                   \
    $(get_follow_http_redirects_flag) -x      \
    -n "$tag" 2>/dev/null | cut -d" " -f6)
  if [ "x$branch" = "x(default)" ]; then
    branch=
  fi
  echo "$branch"
}


# checks if a given tag already exists in the repository's history database
#
# @param repository_name   the name of the repository to be checked
# @param tag               the tag name to be checked
# @return                  0 if tag already exists
check_tag_existence() {
  local repository_name="$1"
  local tag="$2"

  local tag_hash=$(get_tag_hash $repository_name $tag)
  [ "x$tag_hash" != "x" ]
}


# gets the youngest tag on a given branch
#
# @param repository_name   the name of the repository to be checked
# @param branch            the branch name to be checked
# @return                  '<tag name> <hash> <branch>'
get_head_of() {
  local repository_name="$1"
  local branch="$2"

  load_repo_config $repository_name
  __swissknife tag_list                       \
    -w $CVMFS_STRATUM0                        \
    -t ${CVMFS_SPOOL_DIR}/tmp                 \
    -p /etc/cvmfs/keys/${repository_name}.pub \
    -f $repository_name                       \
    $(get_swissknife_proxy)                   \
    $(get_follow_http_redirects_flag) -x      \
    | cut -d" " -f1,2,6 | grep " $branch\$" | head -n 1
}


# retrieves the currently mounted root catalog hash in the spool area
#
# @param repository_name    the name of the repository to be checked
# @return                   echoes the currently mounted root catalog hash
get_mounted_root_hash() {
  local repository_name=$1

  load_repo_config $repository_name
  get_repo_info -u ${CVMFS_SPOOL_DIR}/rdonly -C
}


# retrieves the latest published root catalog hash in the backend storage
#
# @param repository_name   the name of the repository to be checked
# @return                  echoes the last published (HEAD) root catalog hash
get_published_root_hash() {
  local repository_name=$1

  load_repo_config $repository_name
  get_repo_info -c
}

# retrieves the current manifest without signature
#
# @param repository_name   the name of the repository to be checked
# @return                  echoes the published manufest
get_raw_manifest() {
  local repository_name=$1

  load_repo_config $repository_name
  get_repo_info -R
}


set_ro_root_hash() {
  local name=$1
  local root_hash=$2
  local client_config=/var/spool/cvmfs/${name}/client.local

  load_repo_config $name

  local upstream_type=$(get_upstream_type $CVMFS_UPSTREAM_STORAGE)

  if [ x"$upstream_type" = xgw ]; then
      sed -i -e "s/CVMFS_ROOT_HASH=.*//" $client_config
  else
      if grep -q ^CVMFS_ROOT_HASH= ${client_config}; then
          sed -i -e "s/CVMFS_ROOT_HASH=.*/CVMFS_ROOT_HASH=${root_hash}/" $client_config
      else
          echo "CVMFS_ROOT_HASH=${root_hash}" >> $client_config
      fi
  fi
}


get_repo_info_from_url() {
  local url="$1"
  shift 1
  __swissknife info $(get_follow_http_redirects_flag) $(get_swissknife_proxy) -r "$url" $@ 2>/dev/null
}


# expects load_repo_config to be called with the right repository before
# possible parameters, see `cvmfs_swissknife info`
get_repo_info() {
  get_repo_info_from_url "$CVMFS_STRATUM0" $@
}


# checks if a repository is currently in a transaction
#
# @param name  the repository name to be checked
# @return      0 if in a transaction
is_in_transaction() {
  local name=$1
  load_repo_config $name
  local tx_flag="${CVMFS_SPOOL_DIR}/in_transaction"
  check_flag "${tx_flag}"
}


# checks if a repository is currently running a publish procedure
#
# @param name  the repository name to be checked
# @return      0 if a publishing procedure is running
is_publishing() {
  local name=$1
  load_repo_config $name
  check_lock ${CVMFS_SPOOL_DIR}/is_publishing
}

# checks if a repository is currently checked out
#
# @param name  the repository name to be checked
# @return      0 if checked out
is_checked_out() {
  local name=$1
  load_repo_config $name
  [ -f /var/spool/cvmfs/${name}/checkout ]
}


# parses the checkout file
#
# @param name  the repository name to be checked
# @return      0 if checked out
get_checked_out_tag() {
  local name=$1
  load_repo_config $name
  cat /var/spool/cvmfs/${name}/checkout | cut -d" " -f1
}


# parses the checkout file
#
# @param name  the repository name to be checked
# @return      0 if checked out
get_checked_out_hash() {
  local name=$1
  load_repo_config $name
  cat /var/spool/cvmfs/${name}/checkout | cut -d" " -f2
}


# parses the checkout file
#
# @param name  the repository name to be checked
# @return      0 if checked out
get_checked_out_branch() {
  local name=$1
  load_repo_config $name
  cat /var/spool/cvmfs/${name}/checkout | cut -d" " -f3
}


# parses the checkout file
#
# @param name  the repository name to be checked
# @return      0 if checked out
get_checked_out_previous_branch() {
  local name=$1
  load_repo_config $name
  cat /var/spool/cvmfs/${name}/checkout | cut -d" " -f4
}


# checks if the running user is root
#
# @return   0 if the current user is root
is_root() {
  [ $(id -u) -eq 0 ]
}


# checks if the running user is either the owner of a repository or root
#
# @param name  the name of the repository
is_owner_or_root() {
  local name="$1"
  is_root && return 0
  load_repo_config $name
  [ x"$(whoami)" = x"$CVMFS_USER" ]
}


# checks if a repository is a replica flagged as being inactive
#
# @param name  the name of the repository to be checked
# @return      0 if it is an inactive replica
is_inactive_replica() {
  local name=$1
  unset CVMFS_REPLICA_ACTIVE # remove previous setting, default is yes
  load_repo_config $name
  is_stratum1 $name && [ x"$CVMFS_REPLICA_ACTIVE" = x"no" ]
}


# checks if a repository is flagged as being garbage collectable
# (this is a safe guard to avoid mistakenly deleting data in production repos)
#
# @param name  the name of the repository to be checked
# @return      0 if it is garbage collectable
is_garbage_collectable() {
  local name=$1
  load_repo_config $name
  if is_stratum0 $name; then
    [ x"$CVMFS_GARBAGE_COLLECTION" = x"true" ]
  else
    [ x"$(get_repo_info_from_url $CVMFS_STRATUM1 -g)" = x"yes" ]
  fi
}


# checks if a repository has automatic garbage collection enabled and was
# not garbage collected for long enough to justify an automatic run
#
# @param name  the name of the repository to be checked
# @return      0 if automatic garbage collection should run
is_due_auto_garbage_collection() {
  local name=$1
  load_repo_config $name

  is_garbage_collectable $name || return 1
  [ x"$CVMFS_AUTO_GC" = x"true" ] || return 1

  CVMFS_AUTO_GC_LAPSE="${CVMFS_AUTO_GC_LAPSE:-$CVMFS_DEFAULT_AUTO_GC_LAPSE}"
  local gc_deadline=$(date --date "$CVMFS_AUTO_GC_LAPSE" +%s 2>/dev/null)
  if [ -z "$gc_deadline" ]; then
    echo "Failed to parse CVMFS_AUTO_GC_LAPSE: '$CVMFS_AUTO_GC_LAPSE'" >&2
    return 0
  fi

  local gc_status="$(read_repo_item $name .cvmfs_status.json)"
  local last_gc="$(get_json_field "$gc_status" last_gc)"
  [ ! -z "$last_gc" ] || return 0
  last_gc=$(date --date "$last_gc" +%s 2>/dev/null)
  if [ -z "$last_gc" ]; then
    echo "Failed to parse last gc timestamp: '$last_gc'" >&2
    return 0
  fi

  [ $last_gc -lt $gc_deadline ]
}


# download a given file from the backend storage
# @param name  the name of the repository to download from
# @param url   the url to download from
get_item() {
  local name="$1"
  local url="$2"

  load_repo_config $name

  curl -f -H "Cache-Control: max-age=0" $(get_curl_proxy) \
       $(get_x509_cert_settings) $(get_follow_http_redirects_flag) \
       "$url" 2>/dev/null | tr -d '\0'
}

# read an item from local or backend repository storage to stdout
# @param name  the name of the repository to download from
# @param item  the name of the item to download
# if the file does not exist, there will be no output and the
#  return will be an error code
read_repo_item() {
  local name="$1"
  local item="$2"

  load_repo_config $name

  if is_local_upstream $CVMFS_UPSTREAM_STORAGE; then
    cat $(get_upstream_config $CVMFS_UPSTREAM_STORAGE)/"$item" 2>/dev/null
  elif is_stratum0 $name; then
    get_item $name $CVMFS_STRATUM0/"$item"
  else
    get_item $name $CVMFS_STRATUM1/"$item"
  fi
}

# Parse redirects configuration into redirect flag.
# Assumes that config is loaded already, i.e. load_repo_config().
get_follow_http_redirects_flag() {
  if [ x"$CVMFS_FOLLOW_REDIRECTS" = x"yes" ]; then
    echo "-L"
  fi
}


# Parse special CA path settings for curl invocation
get_x509_cert_settings() {
  if [ x"$X509_CERT_BUNDLE" != "x" ]; then
      echo "--cacert $X509_CERT_BUNDLE"
  fi
}

# Parse proxy server for curl command
get_curl_proxy() {
  if [ x"$CVMFS_SERVER_PROXY" != x"" ]; then
    echo "-x $CVMFS_SERVER_PROXY"
  fi
}

# Parse proxy server for cvmfs_swissknife command
get_swissknife_proxy() {
  if [ x"$CVMFS_SERVER_PROXY" != x"" ]; then
    echo "-@ $CVMFS_SERVER_PROXY"
  fi
}

get_expiry_from_string() {
  local whitelist="$1"

  local expires=$(echo "$whitelist" | grep --text -e '^E[0-9]\{14\}$' | tr -d E)
  if echo "$expires" | grep -q -E --invert-match '^[0-9]{14}$'; then
    echo -1
    return 1
  fi
  local year=$(echo $expires | head -c4)
  local month=$(echo $expires | head -c6 | tail -c2)
  local day=$(echo $expires | head -c8 | tail -c2)
  local hour=$(echo $expires | head -c10 | tail -c2)
  local minute=$(echo $expires | head -c12 | tail -c2)
  local second=$(echo $expires | head -c14 | tail -c2)
  local expires_fmt="${year}-${month}-${day} ${hour}:${minute}:${second}"
  local expires_num=$(date -u -d "$expires_fmt" +%s)

  local now=$(/bin/date -u +%s)
  local valid_countdown=$(( $expires_num-$now ))
  echo $valid_countdown
}


# figures out the time to expiry of the repository's whitelist
#
# @param stratum0  path/URL to stratum0 storage
# @return          number of seconds until expiry (negative if already expired)
get_expiry() {
  local name=$1
  local stratum0=$2
  get_expiry_from_string "$(get_item $name $stratum0/.cvmfswhitelist)"
}


# checks if the repository's whitelist is valid
#
# @param stratum0  path/URL to stratum0 storage
# @return          0 if whitelist is still valid
check_expiry() {
  local name=$1
  local stratum0=$2
  local expiry="-1"

  expiry=$(get_expiry $name $stratum0)
  if [ $? -ne 0 ]; then
    echo "Failed to retrieve repository expiry date" >&2
    return 100
  fi

  [ $expiry -ge 0 ]
}


# create a shell invocation to be used by commands to impersonate the owner of
# a specific CVMFS repository.
# Note: when impersonating the non-root repository owner, root's environment is
#       kept (usually including root's `umask` - see integration test 571)
#
# @param name   the name of the repository whose owner should be impersonated
# @return       a shell invocation to impersonate $CVMFS_USER via stdout or
#               exit code 1 if user couldn't be impersonated
get_user_shell() {
  local name=$1
  local shell_cmd=""

  load_repo_config $name
  if [ x"$(whoami)" = x"$CVMFS_USER" ]; then
    shell_cmd="sh -c"
  elif is_root; then
    if [ $HAS_RUNUSER -ne 0 ]; then
      shell_cmd="$RUNUSER_BIN -m $CVMFS_USER -c"
    else
      shell_cmd="su -m $CVMFS_USER -c"
    fi
  fi

  echo "$shell_cmd"
  [ ! -z "$shell_cmd" ] # return false if no suitable shell could be constructed
}


sign_manifest() {
  local name=$1
  local unsigned_manifest=$2
  local metainfo_file=$3
  local return_early=
  if [ "x$4" = "xtrue" ]; then
    return_early="-e"
  fi

  load_repo_config $name
  local user_shell="$(get_user_shell $name)"

  local sign_command="$(__swissknife_cmd) sign \
          -c /etc/cvmfs/keys/${name}.crt       \
          -k /etc/cvmfs/keys/${name}.key       \
          -n $name                             \
          -u $CVMFS_STRATUM0                   \
          -m $unsigned_manifest                \
          -t ${CVMFS_SPOOL_DIR}/tmp            \
          $(get_swissknife_proxy)              \
          -r $CVMFS_UPSTREAM_STORAGE $return_early"

  if [ x"$metainfo_file" != x"" ]; then
    sign_command="$sign_command -M $metainfo_file"
  fi
  if [ x"$CVMFS_GARBAGE_COLLECTION" = x"true" ]; then
    sign_command="$sign_command -g"
  fi
  if [ x"$CVMFS_CATALOG_ALT_PATHS" = x"true" ]; then
    sign_command="$sign_command -A"
  fi
  if has_reflog_checksum $name; then
    sign_command="$sign_command -R $(get_reflog_checksum $name)"
  fi

  $user_shell "$sign_command" > /dev/null
}


open_transaction() {
  local name=$1
  load_repo_config $name
  local tx_flag="${CVMFS_SPOOL_DIR}/in_transaction"

  is_stratum0 $name                    || die "Cannot open transaction on Stratum1"
  set_flag "$tx_flag"                  || die "Failed to create transaction flag"
  run_suid_helper open $name           || die "Failed to make /cvmfs/$name writable"

  to_syslog_for_repo $name "opened transaction"
}


# closes a previously opened transaction
# Note: This function will perform remounts on /cvmfs/${name} and the underlying
#       read-only CVMFS branch. Hence, check for open file descriptors first!
#
# @param name             the repository whose transaction should be closed
# @param use_fd_fallback  if set != 0 this will perform a violent remount of the
#                         repository to handle potential open file descriptors
#                         on /cvmfs/${name}
close_transaction() {
  local name=$1
  local use_fd_fallback=$2

  is_in_transaction $name || return 0

  load_repo_config $name
  local tx_flag="${CVMFS_SPOOL_DIR}/in_transaction"
  local tmp_dir="${CVMFS_SPOOL_DIR}/tmp"
  local current_scratch_dir="${CVMFS_SPOOL_DIR}/scratch/current"
  local wastebin_scratch_dir="${CVMFS_SPOOL_DIR}/scratch/wastebin"
  local force_grace_time=60

  # if not explicitly asked, try if umounting works without force
  if [ $use_fd_fallback -eq 0 ]; then
    if ! run_suid_helper rw_umount     $name || \
       ! run_suid_helper rdonly_umount $name; then
      use_fd_fallback=1
    fi
  fi

  # if explicitly asked for or the normal umount failed we apply more force
  if [ $use_fd_fallback -ne 0 ]; then
    if [ x"$CVMFS_FORCE_REMOUNT_WARNING" != x"false" ]; then
      (
        echo "$name is forcefully remounted in $force_grace_time seconds."
        echo "Please close files on /cvmfs/$name"
      ) | wall 2>/dev/null
      sleep $force_grace_time
      echo "$name is forcefully remounted NOW." | wall 2>/dev/null
    fi
    run_suid_helper rw_lazy_umount     $name
    run_suid_helper kill_cvmfs         $name
    run_suid_helper rdonly_lazy_umount $name
  fi

  # continue with the remounting
  local async_msg=""
  if [ x"$CVMFS_ASYNC_SCRATCH_CLEANUP" != x"false" ]; then
    tmpdir=$(mktemp -d "${wastebin_scratch_dir}/waste.XXXXXX")
    if mv $current_scratch_dir $tmpdir; then
      mkdir -p $current_scratch_dir && chown $CVMFS_USER $current_scratch_dir
      async_msg="(asynchronous scratch cleanup)"
      run_suid_helper clear_scratch_async $name
    else
      to_syslog_for_repo $name \
        "asynchronous cleanup failed, doing synchronous cleanup"
      run_suid_helper clear_scratch $name
    fi
  else
    run_suid_helper clear_scratch $name
  fi
  # Prevent "argument too long" errors
  [ ! -z "$tmp_dir" ] && find "${tmp_dir}" -mindepth 1 -not -path '*/receiver*' | xargs rm -fR
  run_suid_helper rdonly_mount $name > /dev/null
  run_suid_helper rw_mount $name
  clear_flag "$tx_flag"

  # Remove session_token file, used for gateway transactions, if it exists
  rm -f ${CVMFS_SPOOL_DIR}/session_token

  local fallback_msg=""
  [ $use_fd_fallback -eq 0 ] || fallback_msg="(using force)"
  to_syslog_for_repo $name "closed transaction $fallback_msg $async_msg"
}


# Release an update lock
#
# @param name             the repository to release

release_update_lock() {
  local name=$1

  load_repo_config $name
  release_lock ${CVMFS_SPOOL_DIR}/is_updating || echo "Warning: failed to release updating lock"
}

# Acquire an update lock for a repository.  Always pair with a call to
#   release_update_lock if returns successful.
#
# @param name               the repository to lock
# @param update_type        update type such as snapshot or gc
# @param abort_on_conflict  0 to wait for lock, 1 to abort if already acquired.
#                           Default 0.  Always aborts if initial snapshot is
#                           in progress.
# @return                   0 if lock successfully acquired

acquire_update_lock()
{
  local name=$1
  local update_type=$2
  local abort_on_conflict=${3:-0}

  load_repo_config $name
  local update_lock=${CVMFS_SPOOL_DIR}/is_updating

  # check for other updates in progress
  if ! acquire_lock $update_lock; then
    if [ $abort_on_conflict -eq 1 ]; then
      echo "another update is in progress... aborting"
      to_syslog_for_repo $name "did not $update_type (another update in progress)"
      return 1
    fi

    local user_shell="$(get_user_shell $name)"
    local initial_snapshot=0
    if $user_shell "$(__swissknife_cmd) peek -d .cvmfs_last_snapshot -r $CVMFS_UPSTREAM_STORAGE" | grep -v -q "available"; then
      initial_snapshot=1
    fi

    if [ $initial_snapshot -eq 1 ]; then
      echo "an initial snapshot is in progress... aborting"
      to_syslog_for_repo $name "did not $update_type (initial snapshot in progress)"
      return 1
    fi

    echo "waiting for another update to finish..."
    if ! acquire_lock $update_lock 1; then
      echo "failed to acquire update lock"
      to_syslog_for_repo $name "did not $update_type (locking issues)"
      return 1
    fi
  fi

  # The lock is now acquired
}


# Release a gc lock
#
# @param name             the repository to release

release_gc_lock() {
  local name=$1

  load_repo_config $name
  release_lock ${CVMFS_SPOOL_DIR}/is_collecting || echo "Warning: failed to release gc lock"
}

# Acquire a gc lock for a repository.  Always pair with a call to
#   release_gc_lock if returns successful.
#
# @param name               the repository to lock
# @param check_type         check type, either check or gc
# @param abort_on_conflict  0 to wait for lock, 1 to abort if already acquired.
#                           Default 0.
# @return                   0 if lock successfully acquired

acquire_gc_lock()
{
  local name=$1
  local check_type=$2
  local abort_on_conflict=${3:-0}

  load_repo_config $name
  local gc_lock=${CVMFS_SPOOL_DIR}/is_collecting

  # look for another check or gc in progress
  if ! acquire_lock $gc_lock; then
    if [ $abort_on_conflict -eq 1 ]; then
      echo "A check or other gc on $name is in progress... aborting"
      to_syslog_for_repo $name "did not $check_type (check or gc in progress)"
      return 1
    fi

    echo "Waiting for gc on $name to finish..."
    if ! acquire_lock $gc_lock 1; then
      echo "failed to acquire gc lock"
      to_syslog_for_repo $name "did not $check_type (locking issues)"
      return 1
    fi
  fi

  # The lock is now acquired
}


handle_read_only_file_descriptors_on_mount_point() {
  local name=$1
  local open_fd_dialog=${2:-1}

  if [ $(count_rd_only_fds /cvmfs/$name) -eq 0 ]; then
    return 0
  fi

  if [ $open_fd_dialog -eq 1 ]; then
    file_descriptor_warning_and_question $name # might abort...
  else
    file_descriptor_warning $name
  fi

  return 1
}


file_descriptor_warning_and_question() {
  local name=$1
  echo "\

WARNING! There are open read-only file descriptors in /cvmfs/$name
  --> This is potentially harmful and might cause problems later on.
      We can anyway perform the requested operation, but this will most likely
      break other processes with open file descriptors on /cvmfs/$name!

      The following lsof report might show the processes with open file handles
      "

  generate_lsof_report_for_mountpoint "/cvmfs/${name}"

  echo -n "\

         Do you want to proceed anyway? (y/N) "

  local reply="n"
  read reply
  if [ "$reply" != "y" ] && [ "$reply" != "Y" ]; then
    echo "aborted."
    exit 1
  fi

  return 0
}


file_descriptor_warning() {
  local name=$1

  echo "WARNING: Open file descriptors on /cvmfs/$name (possible race!)"
  echo "         The following lsof report might show the culprit:"
  echo
  generate_lsof_report_for_mountpoint "/cvmfs/${name}"
  echo
}


generate_lsof_report_for_mountpoint() {
  local mountpoint="$1"
  $LSOF_BIN | awk '{print $1,$2,$3,$NF}' | column -t | grep "$mountpoint" || true
}


# puts all configuration files in place that are need for a stratum0 repository
#
# @param name        the name of the repository
# @param upstream    the upstream definition of the future repository
# @param stratum0    the URL of the stratum0 http entry point
# @param cvmfs_user  the owning user of the repository
create_config_files_for_new_repository() {
  local name=$1
  local upstream=$2
  local stratum0=$3
  local cvmfs_user=$4
  local unionfs=$5
  local hash_algo=$6
  local autotagging=$7
  local garbage_collectable=$8
  local configure_apache=$9
  local compression_alg=${10}
  local external_data=${11}
  local voms_authz=${12}
  local auto_tag_timespan="${13}"
  local proxy_url=${14}

  # other configurations
  local spool_dir="/var/spool/cvmfs/${name}"
  local scratch_dir="${spool_dir}/scratch/current"
  local rdonly_dir="${spool_dir}/rdonly"
  local temp_dir="${spool_dir}/tmp"
  local cache_dir="${spool_dir}/cache"
  local repo_cfg_dir="/etc/cvmfs/repositories.d/${name}"
  local server_conf="${repo_cfg_dir}/server.conf"
  local client_conf="${repo_cfg_dir}/client.conf"

  mkdir -p $repo_cfg_dir
  cat > $server_conf << EOF
# Created by cvmfs_server.
CVMFS_CREATOR_VERSION=$(cvmfs_layout_revision)
CVMFS_REPOSITORY_NAME=$name
CVMFS_REPOSITORY_TYPE=stratum0
CVMFS_USER=$cvmfs_user
CVMFS_UNION_DIR=/cvmfs/$name
CVMFS_SPOOL_DIR=$spool_dir
CVMFS_STRATUM0=$stratum0
CVMFS_UPSTREAM_STORAGE=$upstream
CVMFS_USE_FILE_CHUNKING=$CVMFS_DEFAULT_USE_FILE_CHUNKING
CVMFS_MIN_CHUNK_SIZE=$CVMFS_DEFAULT_MIN_CHUNK_SIZE
CVMFS_AVG_CHUNK_SIZE=$CVMFS_DEFAULT_AVG_CHUNK_SIZE
CVMFS_MAX_CHUNK_SIZE=$CVMFS_DEFAULT_MAX_CHUNK_SIZE
CVMFS_UNION_FS_TYPE=$unionfs
CVMFS_HASH_ALGORITHM=$hash_algo
CVMFS_COMPRESSION_ALGORITHM=$compression_alg
CVMFS_EXTERNAL_DATA=$external_data
CVMFS_AUTO_TAG=$autotagging
CVMFS_AUTO_TAG_TIMESPAN="$auto_tag_timespan"
CVMFS_GARBAGE_COLLECTION=$garbage_collectable
CVMFS_AUTO_REPAIR_MOUNTPOINT=true
CVMFS_AUTOCATALOGS=false
CVMFS_ASYNC_SCRATCH_CLEANUP=true
CVMFS_PRINT_STATISTICS=false
CVMFS_UPLOAD_STATS_DB=false
CVMFS_UPLOAD_STATS_PLOTS=false
CVMFS_IGNORE_XDIR_HARDLINKS=true
EOF

  if [ x"$voms_authz" != x"" ]; then
    echo "CVMFS_VOMS_AUTHZ=$voms_authz" >> $server_conf
    echo "CVMFS_CATALOG_ALT_PATHS=true" >> $server_conf
  fi

  # append GC specific configuration
  if [ x"$garbage_collectable" = x"true" ]; then
    cat >> $server_conf << EOF
CVMFS_AUTO_GC=true
EOF
  fi

  if [ x"$proxy_url" != x"" ]; then
    echo "CVMFS_SERVER_PROXY=$proxy_url" >> $server_conf
  fi

  if [ $configure_apache -eq 1 ] && is_local_upstream $upstream; then
    local repository_dir=$(get_upstream_config $upstream)
    # make sure that the config file does not exist, yet
    remove_apache_config_file "$(get_apache_conf_filename $name)" || true
    create_apache_config_for_endpoint $name $repository_dir
    create_apache_config_for_global_info
  fi

  cat > $client_conf << EOF
# Created by cvmfs_server.  Don't touch.
CVMFS_CACHE_BASE=$cache_dir
CVMFS_RELOAD_SOCKETS=$cache_dir
CVMFS_QUOTA_LIMIT=4000
CVMFS_MOUNT_DIR=/cvmfs
CVMFS_SERVER_URL=$stratum0
CVMFS_HTTP_PROXY=${proxy_url:-DIRECT}
CVMFS_PUBLIC_KEY=/etc/cvmfs/keys/${name}.pub
CVMFS_CHECK_PERMISSIONS=yes
CVMFS_IGNORE_SIGNATURE=no
CVMFS_AUTO_UPDATE=no
CVMFS_NFS_SOURCE=no
CVMFS_MAGIC_XATTRS_VISIBILITY=never
CVMFS_FOLLOW_REDIRECTS=yes
CVMFS_SERVER_CACHE_MODE=yes
CVMFS_NFILES=65536
CVMFS_TALK_SOCKET=/var/spool/cvmfs/${name}/cvmfs_io
CVMFS_TALK_OWNER=$cvmfs_user
CVMFS_USE_SSL_SYSTEM_CA=true
EOF

  if [ "x$X509_CERT_BUNDLE" != "x" ]; then
    cat >> $client_conf << EOF
X509_CERT_BUNDLE=$X509_CERT_BUNDLE
EOF
  fi
}


create_spool_area_for_new_repository() {
  local name=$1

  # gather repository information from configuration file
  load_repo_config $name
  local spool_dir=$CVMFS_SPOOL_DIR
  local current_scratch_dir="${spool_dir}/scratch/current"
  local wastebin_scratch_dir="${spool_dir}/scratch/wastebin"
  local rdonly_dir="${spool_dir}/rdonly"
  local temp_dir="${spool_dir}/tmp"
  local cache_dir="${spool_dir}/cache"
  local ofs_workdir="${spool_dir}/ofs_workdir"

  mkdir -p /cvmfs/$name          \
           $current_scratch_dir  \
           $wastebin_scratch_dir \
           $rdonly_dir           \
           $temp_dir             \
           $cache_dir || return 1
  if [ x"$CVMFS_UNION_FS_TYPE" = x"overlayfs" ]; then
    mkdir -p $ofs_workdir || return 2
  fi
  chown -R $CVMFS_USER /cvmfs/$name/ $spool_dir/
}


remove_spool_area() {
  local name=$1
  load_repo_config $name
  [ x"$CVMFS_SPOOL_DIR" != x"" ] || return 0
  rm -fR "$CVMFS_SPOOL_DIR"      || return 1
  if [ -d /cvmfs/$name ]; then
    rmdir /cvmfs/$name           || return 2
  fi
}


create_global_info_skeleton() {
  local info_path="$(get_global_info_path)"
  local info_v1_path="$(get_global_info_v1_path)"

  mkdir -p $info_path                               || return 1
  mkdir -p $info_v1_path                            || return 2
  set_selinux_httpd_context_if_needed $info_path    || return 3
  set_selinux_httpd_context_if_needed $info_v1_path || return 4

  _check_info_file "repositories" || echo "{}" | _write_info_file "repositories"
  _check_info_file "meta" || _write_info_file "meta" << EOF
{
  "administrator" : "Your Name",
  "email"         : "you@organisation.org",
  "organisation"  : "Your Organisation",

  "custom" : {
    "_comment" : "Put arbitrary structured data here"
  }
}
EOF
}


create_repository_skeleton() {
  local directory=$1
  local user=$2

  echo -n "Creating repository skeleton in ${directory}..."
  mkdir -p ${directory}/data
  local i=0
  while [ $i -lt 256 ]
  do
    mkdir -p ${directory}/data/$(printf "%02x" $i)
    i=$(($i+1))
  done
  mkdir -p ${directory}/data/txn
  if [ x$(id -un) != x$user ]; then
    chown -R $user ${directory}/
  fi
  set_selinux_httpd_context_if_needed $directory
  echo "done"
}


create_repository_storage() {
  local name=$1
  local storage_dir
  load_repo_config $name
  storage_dir=$(get_upstream_config $CVMFS_UPSTREAM_STORAGE)
  create_repository_skeleton $storage_dir $CVMFS_USER > /dev/null
}


create_repometa_skeleton() {
  local json_file="$1"
  cat > "$json_file" << EOF
{
  "administrator" : "Your Name",
  "email"         : "you@organisation.org",
  "organisation"  : "Your Organisation",
  "description"   : "Repository content",
  "url"           : "Project website",
  "recommended-stratum0":  "stratum 0 url",
  "recommended-stratum1s" : [ "stratum1 url", "stratum1 url" ],

  "custom" : {
    "_comment" : "Put arbitrary structured data here"
  }
}
EOF
}


setup_and_mount_new_repository() {
  local name=$1
  local http_timeout=15

  # get repository information
  load_repo_config $name
  local rdonly_dir="${CVMFS_SPOOL_DIR}/rdonly"
  local scratch_dir="${CVMFS_SPOOL_DIR}/scratch/current"
  local ofs_workdir="${CVMFS_SPOOL_DIR}/ofs_workdir"

  local selinux_context=""
  if [ x"$CVMFS_UNION_FS_TYPE" = x"overlayfs" ]; then
    echo -n "(overlayfs) "
    cat >> /etc/fstab << EOF
cvmfs2#$name $rdonly_dir fuse allow_other,fsname=$name,config=/etc/cvmfs/repositories.d/${name}/client.conf:${CVMFS_SPOOL_DIR}/client.local,cvmfs_suid,noauto 0 0 # added by CernVM-FS for $name
overlay_$name /cvmfs/$name overlay upperdir=${scratch_dir},lowerdir=${rdonly_dir},workdir=$ofs_workdir,noauto,nodev,ro 0 0 # added by CernVM-FS for $name
EOF
  else
    echo -n "(aufs) "
    if has_selinux && try_mount_remount_cycle_aufs; then
      selinux_context=",context=\"system_u:object_r:default_t:s0\""
    fi
    cat >> /etc/fstab << EOF
cvmfs2#$name $rdonly_dir fuse allow_other,fsname=$name,config=/etc/cvmfs/repositories.d/${name}/client.conf:${CVMFS_SPOOL_DIR}/client.local,cvmfs_suid,noauto 0 0 # added by CernVM-FS for $name
aufs_$name /cvmfs/$name aufs br=${scratch_dir}=rw:${rdonly_dir}=rr,udba=none,noauto,nodev,ro$selinux_context 0 0 # added by CernVM-FS for $name
EOF
  fi
  local user_shell="$(get_user_shell $name)"
  $user_shell "touch ${CVMFS_SPOOL_DIR}/client.local"

  # avoid racing against apache; we can safely ignore the certificate validation
  # at this step, we only want to check that the endpoint is up.
  # NB: Normally, we are anyway dealing with HTTP URLs at this point.
  local waiting=0
  while ! curl $(get_curl_proxy) --insecure -sIf ${CVMFS_STRATUM0}/.cvmfspublished > /dev/null && \
        [ $http_timeout -gt 0 ]; do
    [ $waiting -eq 1 ] || echo -n "waiting for apache... "
    waiting=1
    http_timeout=$(( $http_timeout - 1 ))
    sleep 1
  done
  [ $http_timeout -gt 0 ] || return 1

  mount $rdonly_dir > /dev/null || return 1
  mount /cvmfs/$name

  # Make sure the systemd mount unit exists
  if is_systemd; then
    /usr/lib/systemd/system-generators/systemd-fstab-generator \
      /run/systemd/generator '' '' 2>/dev/null || true
    systemctl daemon-reload
  fi
}


# checks if the (corresponding) stratum 0 is garbage collectable
#
# @param name  the name of the stratum1/stratum0 repository to be checked
# @return      0 if it is garbage collectable
is_stratum0_garbage_collectable() {
  local name=$1
  load_repo_config $name
  [ x"$(get_repo_info_from_url $CVMFS_STRATUM0 -g)" = x"yes" ]
}


# checks if a manifest is present
#
# @param url  the url of the repository to be checked
# @return      0 if it is empty
is_empty_repository_from_url() {
  local url=$1
  [ x"$(get_repo_info_from_url "$url" -e)" = x"yes" ]
}


# checks if a manifest is present
#
# @param name  the name of the repository to be checked
# @return      0 if it is empty
is_empty_repository() {
  local name=$1
  local url=""
  load_repo_config $name
  is_stratum0 $name && url="$CVMFS_STRATUM0" || url="$CVMFS_STRATUM1"
  [ x"$(get_repo_info_from_url "$url" -e)" = x"yes" ]
}

# checks if a repository contains a reference log that is necessary to run
# garbage collections
#
# @param name  the name of the repository to be checked
# @return      0 if it contains a reference log
has_reference_log() {
  local name=$1
  local url=""
  load_repo_config $name
  is_stratum0 $name && url="$CVMFS_STRATUM0" || url="$CVMFS_STRATUM1"
  [ x"$(get_repo_info_from_url "$url" -o)" = x"true" ]
}


# get the configured (or default) timespan for an automatic garbage
# collection run.
#
# @param name  the name of the repository to be checked
# @return      the configured CVMFS_AUTO_GC_TIMESPAN or default (3 days ago)
#              as a timestamp threshold (unix timestamp)
#              Note: in case of a malformed timespan it might print an error to
#                     stderr and return a non-zero code
get_auto_garbage_collection_timespan() {
  local name=$1
  local timespan="3 days ago"

  load_repo_config $name
  if [ ! -z "$CVMFS_AUTO_GC_TIMESPAN" ]; then
    timespan="$CVMFS_AUTO_GC_TIMESPAN"
  fi

  if ! date --date "$timespan" +%s 2>/dev/null; then
    echo "Failed to parse CVMFS_AUTO_GC_TIMESPAN: '$timespan'" >&2
    return 1
  fi
}


# mangles the repository name into a fully qualified repository name
# if there was no repository name given and there is only one repository present
# in the system, it automatically returns the name of this one.
#
# @param repository_name  the name of the repository to work on (might be empty)
# @return                 echoes a suitable repository name
get_or_guess_repository_name() {
  local repository_name=$1

  if [ "x$repository_name" = "x" ]; then
    echo $(get_repository_name $(ls /etc/cvmfs/repositories.d))
  else
    echo $(get_repository_name $repository_name)
  fi
}


# get the configured timespan for removing old auto-generated tags.
#
# @param name  the name of the repository to be checked
# @return      the configured CVMFS_AUTO_TAG_TIMESPAN or 0 (forever)
#              as a timestamp threshold (unix timestamp)
#              Note: in case of a malformed timespan it might print an error to
#                     stderr and return a non-zero code
get_auto_tags_timespan() {
  local repository_name=$1

  load_repo_config $repository_name
  local timespan="$CVMFS_AUTO_TAG_TIMESPAN"
  if [ -z "$timespan" ]; then
    echo "0"
    return 0
  fi

  if ! date --date "$timespan" +%s 2>/dev/null; then
    echo "Failed to parse CVMFS_AUTO_TAG_TIMESPAN: '$timespan'" >&2
    return 1
  fi
  return 0
}


unmount_and_teardown_repository() {
  local name=$1
  load_repo_config $name
  sed -i -e "/added by CernVM-FS for ${name}/d" /etc/fstab
  local rw_mnt="/cvmfs/$name"
  local rdonly_mnt="${CVMFS_SPOOL_DIR}/rdonly"
  is_mounted "$rw_mnt"     && ( umount $rw_mnt     || return 1; )
  is_mounted "$rdonly_mnt" && ( umount $rdonly_mnt || return 2; )
  return 0
}


_run_catalog_migration() {
  local name="$1"
  local migration_command="$2"

  load_repo_config $name

  # more sanity checks
  is_stratum0 $name       || die "This is not a stratum 0 repository"
  is_root                 || die "Permission denied: Only root can do that"
  is_in_transaction $name && die "Repository is already in a transaction"
  health_check -r $name

  # all following commands need an open repository transaction and are supposed
  # to commit or abort it after performing the catalog migration.
  echo "Opening repository transaction"
  trap "close_transaction $name 0" EXIT HUP INT TERM
  open_transaction $name || die "Failed to open repository transaction"

  # run the catalog migration operation (must run as root!)
  echo "Starting catalog migration"
  local tmp_dir=${CVMFS_SPOOL_DIR}/tmp
  local manifest=${tmp_dir}/manifest
  migration_command="${migration_command} -t $tmp_dir -o $manifest"
  sh -c "$migration_command" || die "Fail (executed command: $migration_command)"

  # check if the catalog migration created a new revision
  if [ ! -f $manifest ]; then
    echo "Catalog migration finished without any changes"
    return 0
  fi

  # finalizing transaction
  local trunk_hash=$(grep "^C" $manifest | tr -d C)
  echo "Flushing file system buffers"
  syncfs

  # committing newly created revision
  echo "Signing new manifest"
  chown $CVMFS_USER $manifest        || die "chmod of new manifest failed";
  sign_manifest $name $manifest      || die "Signing failed";
  set_ro_root_hash $name $trunk_hash || die "Root hash update failed";
}
#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh


# Checks for inconsistent repository states or unfavorable configs. Can repair
# inconsistent repository mount states (-r)
# Parameters:
#   -q    silence notifications to stdout/stderr (syslog messages stay in place)
#   -r    try to repair detected inconsistencies (non-zero exit on failure)
#   -t    repair even if the repository is in a transaction
#   -f    force repair (even if CVMFS_AUTO_REPAIR_MOUNTPOINT=false)
#
# @param name   the FQRN of the repository to be checked
# @return       0 if repository is healthy (or has been successfully repaired)
#               otherwise 1 (or abort when -r is given and repair fails)
health_check() {
  local name=""
  local gateway=0
  local quiet=0
  local repair=0
  local repair_in_txn=0
  local force_repair=0

  OPTIND=0
  while getopts "gqrtf" option; do
    case $option in
      g)
        gateway=1
      ;;
      q)
        quiet=1
      ;;
      r)
        repair=1
      ;;
      t)
        repair=1
        repair_in_txn=1
      ;;
      f)
        repair=1
        force_repair=1
      ;;
      ?)
        shift $(($OPTIND-2))
        die "health_check: Unrecognized option: $1"
      ;;
    esac
  done
  shift $(($OPTIND-1))

  [ $# -eq 1 ] || die "health_check: No repository name provided"
  name=$(get_repository_name $1)

  local rdonly_broken=0
  local rw_broken=0
  local rw_should_be_rdonly=0
  local rw_should_be_rw=0
  local rdonly_outdated=0
  local rdonly_wronghash=0

  load_repo_config $name

  # for stratum 1 repositories there are no health checks
  if is_stratum1 $name; then
    return 0
  fi

  # check mounted read-only cvmfs client
  local expected_hash=
  if ! is_mounted "${CVMFS_SPOOL_DIR}/rdonly"; then
    rdonly_broken=1
  elif [ x"$(get_mounted_root_hash $name)"      != \
         x"$(get_published_root_hash $name)" ]; then
    if ! is_checked_out $name; then
      expected_hash=$(get_published_root_hash $name)
      rdonly_outdated=1
    else
      expected_hash=$(get_checked_out_hash $name)
      if [ x"$(get_mounted_root_hash $name)" != x"$expected_hash" ]; then
        rdonly_wronghash=1
      fi
    fi
  fi

  # check mounted union file system
  if ! is_mounted "/cvmfs/$name"; then
    rw_broken=1
    if is_in_transaction $name; then
      rw_should_be_rw=1
      rw_should_be_ro=0
    else
      rw_should_be_rw=0
      rw_should_be_ro=1
    fi
  else
    if ! is_in_transaction $name && \
         is_mounted "/cvmfs/$name" "^.* rw[, ].*$"; then
      rw_should_be_rdonly=1
    elif is_in_transaction $name && \
         is_mounted "/cvmfs/$name" "^.* ro[, ].*$"; then
      rw_should_be_rw=1
    fi
  fi

  # did we detect any kind of problem?
  local ok=$(( $rdonly_broken       \
             + $rw_broken           \
             + $rw_should_be_rdonly \
             + $rw_should_be_rw ))
  if [ $gateway -eq 0 ]; then
    ok=$(( $ok + $rdonly_wronghash + $rdonly_outdated ))
  fi
  if [ $ok -eq 0 ]; then
    return 0
  fi

  # should we print the found status?
  if [ $quiet = 0 ]; then
    __hc_print_status_report $name $rdonly_broken       \
                                   $rdonly_outdated     \
                                   $rdonly_wronghash    \
                                   $rw_broken           \
                                   $rw_should_be_rdonly \
                                   $rw_should_be_rw
  fi

  # should we try a repair?
  if [ $repair = 0 ]; then
    return 1
  fi

  # check if we are allowed to attempt a repair
  if [ x"$CVMFS_AUTO_REPAIR_MOUNTPOINT" = x"false" ] && \
     [ $force_repair = 0 ]; then
    echo "Auto-Repair is disabled (CVMFS_AUTO_REPAIR_MOUNTPOINT = false)" >&2
    exit 1
  fi

  if is_publishing $name; then
    echo "WARNING: The repository $name is currently publishing and should not" >&2
    echo "be touched. If you are absolutely sure, that this is _not_ the case," >&2
    echo "please run the following command and retry:"                          >&2
    echo                                                                        >&2
    echo "   rm -fR ${CVMFS_SPOOL_DIR}/is_publishing.lock"                      >&2
    echo                                                                        >&2
    exit 1
  fi

  if is_in_transaction $name && [ $repair_in_txn = 0 ]; then
    echo "Repository $name is in a transaction and cannot be repaired." >&2
    echo "--> Run \`cvmfs_server abort $name\` to revert and repair."   >&2
    exit 1
  fi

  to_syslog_for_repo $name "attempting mountpoint repair ($rdonly_broken $rdonly_outdated $rw_broken $rw_should_be_rdonly $rw_should_be_rw)"

  # consecutively bring the mountpoints into a sane state by working bottom up:
  #   1. solve problems with the rdonly mountpoint
  #      Note: this might require to 'break' the rw mountpoint (rw_broken --> 1)
  #      1.1. solve outdated rdonly mountpoint (rdonly_outdated --> 0)
  #      1.2. remount rdonly mountpoint        (rdonly_broken   --> 0)
  #   2. solve problems with the rw mountpoint
  #      2.1. mount the rw mountpoint as read-only    (rw_broken       --> 0)
  #      2.2. remount the rw mountpoint as read-only  (rw_should_be_ro --> 0)
  #      2.2. remount the rw mountpoint as read-write (rw_should_be_rw --> 0)
  if [ $(($rdonly_outdated + $rdonly_wronghash)) -gt 0 ]; then
    if [ $rw_broken -eq 0 ]; then
      __hc_transition $name $quiet "rw_umount"
      rw_broken=1 # ... remount happens downstream
    fi

    if [ $rdonly_broken -eq 0 ]; then
      __hc_transition $name $quiet "rdonly_umount"
      rdonly_broken=1 # ... remount happens downstream
    fi

    set_ro_root_hash $name "$expected_hash" || die "failed to update root hash"
    rdonly_outdated=0 # ... remount will mount the latest revision
    rdonly_wronghash=0
  fi

  if [ $rdonly_broken -eq 1 ]; then
    if [ $rw_broken -eq 0 ]; then
      __hc_transition $name $quiet "rw_umount"
      rw_broken=1 # ... remount happens downstream
    fi

    __hc_transition $name $quiet "rdonly_mount"
    rdonly_broken=0 # ... rdonly is repaired
  fi

  if [ $rw_broken -eq 1 ]; then
    __hc_transition $name $quiet "rw_mount"
    rw_broken=0           # ... rw is repaired
    rw_should_be_rdonly=0 # ... and already mounted read-only by default
  fi

  if [ $rw_should_be_rw -eq 1 ]; then
    __hc_transition $name $quiet "open"
    rw_should_be_rw=0 # ... rw is repaired
  fi

  if [ $rw_should_be_rdonly -eq 1 ]; then
    __hc_transition $name $quiet "lock"
    rw_should_be_rdonly=0 # ... rw is repaired
  fi

  to_syslog_for_repo $name "finished mountpoint repair ($rdonly_broken $rdonly_outdated $rw_broken $rw_should_be_rdonly $rw_should_be_rw)"
}


#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server


# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh


# only called by check_repository_compatibility()!
# @param creator  the creator version of the (incompatible) repository
# @param nokill   (optional) see check_repository_compatibility()
_repo_is_incompatible() {
  local creator=$1
  # if 'nokill' is set, be silent and just return 1
  if [ $# -gt 1 ]; then
    return 1
  fi

  echo "\
This repository uses the previous layout revision $(mangle_version_string $creator).
This version of CernVM-FS requires layout revision $(cvmfs_layout_revision), which is
incompatible to $(mangle_version_string $creator).

Please run \`cvmfs_server migrate\` to update your repository before proceeding."
  exit 1
}


# checks if the sourced server.conf is compatible with the running version of
# this script.
# Note: this assumes that server.conf was already sourced!
# @param nokill  (optional) if not set -> `exit 1` on incompatibility
check_repository_compatibility() {
  local name="$1"
  local nokill=$2
  local creator=$(repository_creator_version $name)

  if compare_versions $(cvmfs_layout_revision) -lt "$creator"; then
    if [ $# -gt 1 ]; then
      return 1 # nokill
    fi
    echo "This repository uses layout revision $creator which is newer than the
layout used by the currently installed CernVM-FS ($(cvmfs_layout_revision)).
Please upgrade CernVM-FS to manipulate this repository."
    exit 1
  fi

  # Migration History:
  #   2.1.6 -> 2.1.7
  #     -> repository format changed
  #
  #   2.1.7+ -> 2.1.15
  #     -> config files changed (adding client.local)
  #     -> adjustments in /etc/fstab
  #     -> additional statistics counters in file catalogs
  #
  #   2.1.15+ -> 2.1.20
  #     -> replica (i.e. stratum 1) with local upstream storage has
  #        additional apache config for wsgi
  #
  #   2.1.20+ -> 2.2.0-1 (2.2.0-0 was a server pre-release and needs migration)
  #     -> new (mandatory) parameters in client.conf (Stratum 0)
  #     -> adjustments in /etc/fstab
  #     -> CVMFS_AUTO_REPAIR_MOUNTPOINT=true becomes the enforced default
  #     -> Apache configuration updated
  #
  #   2.2.0-1+ -> 2.3.0-1
  #     -> new scratch directory layout (which also effects /etc/fstab)
  #
  #   2.3.0-1+ --> 2.3.3-1
  #     -> update global JSON info if repo was migrated from 2.1.20 or before
  #        (CVM-1159)
  #
  #   2.3.3-1+ --> 137
  #     -> use an arbitrary server layout revision to decouple the creator
  #        version from the software version (CVM-1065)
  #
  #   137 --> 138
  #     -> update apache configs on relevant stratum 1s for better geo api
  #        implementation (CVM-1349)
  #
  #   138 --> 139
  #     -> use nodev mount option in /etc/fstab
  #
  #   139 --> 140
  #     -> update apache configs on stratum 1s that have them to ignore
  #        If-Modified-Since headers (CVM-1655)
  #
  #   140 --> 141
  #     -> Set CVMFS_NFILES parameter on publisher node
  #
  #   141 --> 142
  #     -> Set CVMFS_TALK_SOCKET, CVMFS_TALK_OWNER parameters on publisher node
  #
  #   142 --> 143
  #     -> Set CVMFS_USE_SSL_SYSTEM_CA client parameter on publisher node
  #
  # Note: I tried to make this code as verbose as possible
  #
  if [ "$creator" = "2.1.6" ] && version_greater_or_equal "2.1.7"; then
    _repo_is_incompatible "$creator" $nokill
    return $?
  fi

  if [ "$creator" = "2.1.7"  ] || [ "$creator" = "2.1.8"  ] || \
     [ "$creator" = "2.1.9"  ] || [ "$creator" = "2.1.10" ] || \
     [ "$creator" = "2.1.11" ] || [ "$creator" = "2.1.12" ] || \
     [ "$creator" = "2.1.13" ] || [ "$creator" = "2.1.14" ];
  then
    if version_greater_or_equal "2.1.15"; then
      _repo_is_incompatible "$creator" $nokill
      return $?
    fi
  fi

  if [ "$creator" = "2.1.15" ] || [ "$creator" = "2.1.16" ] || \
     [ "$creator" = "2.1.17" ] || [ "$creator" = "2.1.18" ] || \
     [ "$creator" = "2.1.19" ];
  then
    if version_greater_or_equal "2.1.20" && \
       is_stratum1 $name                 && \
       is_local_upstream $CVMFS_UPSTREAM_STORAGE; then
      _repo_is_incompatible "$creator" $nokill
      return $?
    fi
  fi

  if [ "$creator" = "2.1.15"  ] || [ "$creator" = "2.1.16"  ] || \
     [ "$creator" = "2.1.17"  ] || [ "$creator" = "2.1.18"  ] || \
     [ "$creator" = "2.1.19"  ] || [ "$creator" = "2.1.20"  ] || \
     [ "$creator" = "2.2.0-0" ];
  then
    if version_greater_or_equal "2.2.0"; then
      _repo_is_incompatible "$creator" $nokill
      return $?
    fi
  fi

  if [ "$creator" = "2.2.0-1" ] || [ "$creator" = "2.2.1-1" ] || \
     [ "$creator" = "2.2.2-1" ] || [ "$creator" = "2.2.3-1" ] && \
     is_stratum0 $name; then
    if version_greater_or_equal "2.3.0"; then
      _repo_is_incompatible "$creator" $nokill
      return $?
    fi
  fi

  if [ "$creator" = "2.2.0-1" ] || [ "$creator" = "2.2.1-1" ] || \
     [ "$creator" = "2.2.2-1" ] || [ "$creator" = "2.2.3-1" ] || \
     [ "$creator" = "2.3.0-1" ] || [ "$creator" = "2.3.1-1" ] || \
     [ "$creator" = "2.3.2-1" ]; then
    if version_greater_or_equal "2.3.3"; then
      _repo_is_incompatible "$creator" $nokill
      return $?
    fi
  fi

  if [ "$creator" = "2.3.3-1" ] || [ "$creator" = "2.3.4-1" ] || \
     [ "$creator" = "2.3.5-1" ] || [ "$creator" = "2.3.6-1" ] || \
     [ "$creator" = "2.4.0-1" ]; then
    _repo_is_incompatible "$creator" $nokill
    return $?
  fi

  # After this point all creator versions are numeric

  if [ "$creator" -eq 139 ] && \
      ( ! is_stratum1 $name || \
        !  has_apache_config_file $(get_apache_conf_filename $name) ); then
      # skip this migrate if not on stratum1 or no apache config
      creator=140
  fi

  if [ "$creator" -eq 140 ] && is_stratum1 $name; then
    # skip this migrate if not on stratum 0
    creator=141
  fi

  if [ "$creator" -eq 141 ] && is_stratum1 $name; then
    # skip this migrate if not on stratum 0
    creator=142
  fi

  if [ "$creator" -eq 142 ] && is_stratum1 $name; then
    # skip this migrate if not on stratum 0
    creator=143
  fi

  if [ "$creator" -lt "$(cvmfs_layout_revision)" ]; then
    _repo_is_incompatible "$creator" $nokill
    return $?
  fi

  return 0
}
#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server transaction command"
# Migrated to the new cvmfs_publish command

cvmfs_server_enter() {
  $(__publish_cmd dbg) enter $@
}
#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server transaction command"
# Migrated to the new cvmfs_publish command

cvmfs_server_transaction() {
  $(__publish_cmd dbg) transaction $@
}
#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server abort command"
# Migrated to the new cvmfs_publish command

cvmfs_server_abort() {
  $(__publish_cmd dbg) abort $@
}
cvmfs_server_publish() {
  local names
  local user
  local gw_key_file
  local spool_dir
  local stratum0
  local upstream
  local hash_algorithm
  local tweaks_option=
  local tag_name=
  local tag_description=
  local retcode=0
  local verbosity=""
  local manual_revision=""
  local gc_timespan=0
  local authz_file=""
  local force_external=0
  local force_native=0
  local force_direct_io=0
  local force_compression_algorithm=""
  local external_option=""
  local direct_io_option=""
  local open_fd_dialog=1

  # optional parameter handling
  OPTIND=1
  while getopts "F:NXZ:pa:c:m:vn:fed" option
  do
    case $option in
      p)
        tweaks_option="-d"
      ;;
      a)
        tag_name="$OPTARG"
      ;;
      m)
        tag_description="$OPTARG"
      ;;
      v)
        verbosity="-x"
      ;;
      n)
        manual_revision="$OPTARG"
      ;;
      X)
        force_external=1
      ;;
      N)
        force_native=1
      ;;
      Z)
        force_compression_algorithm="$OPTARG"
      ;;
      F)
        authz_file="-F $OPTARG"
      ;;
      d)
        force_direct_io=1
      ;;
      f)
        open_fd_dialog=0
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command publish: Unrecognized option: $1"
      ;;
    esac
  done

  if [ $(($force_external + $force_native)) -eq 2 ]; then
    usage "Command publish: -N and -X are mutually exclusive"
  fi

  shift $(($OPTIND-1))
  check_parameter_count_for_multiple_repositories $#
  # get repository names
  names=$(get_or_guess_multiple_repository_names "$@")
  check_multiple_repository_existence "$names"

  for name in $names; do
    # sanity checks
    if [ ! -z "$tag_name" ]; then
      echo $tag_name | grep -q -v " "       || die "Spaces are not allowed in tag names"
      check_tag_existence $name "$tag_name" && die "Tag name '$tag_name' is already in use."
    fi

    # Ignore any subpath appended to the repository e.g. repo.cern.ch/sub/path/for/locking
    # Providing a subpath for the "cvmfs_server publish" command is no longer needed.
    name=$(echo $name | cut -d'/' -f1)

    load_repo_config $name
    # We need the upstream type for configuring the health_check function
    upstream=$CVMFS_UPSTREAM_STORAGE
    upstream_type=$(get_upstream_type $upstream)

    # sanity checks
    is_stratum0 $name   || die "This is not a stratum 0 repository"
    is_publishing $name && die "Another publish process is active for $name"
    if [ x"$upstream_type" = xgw ]; then
        health_check -g -r $name
    else
        # TODO(jblomer): switch me back to `health_check -r $name`
        health_check -g -r $name
    fi

    # get repository information
    user=$CVMFS_USER
    gw_key_file=/etc/cvmfs/keys/${name}.gw
    spool_dir=$CVMFS_SPOOL_DIR
    scratch_dir="${spool_dir}/scratch/current"
    stratum0=$CVMFS_STRATUM0
    hash_algorithm="${CVMFS_HASH_ALGORITHM-sha1}"
    compression_alg="${CVMFS_COMPRESSION_ALGORITHM-default}"
    if [ x"$force_compression_algorithm" != "x" ]; then
      compression_alg="$force_compression_algorithm"
    fi
    if [ x"$CVMFS_EXTERNAL_DATA" = "xtrue" -o $force_external -eq 1 ]; then
      if [ $force_native -eq 0 ]; then
        external_option="-Y"
      fi
    fi
    if [ $force_direct_io -eq 1 ]; then
      direct_io_option="-W"
    fi

    # more sanity checks
    is_owner_or_root $name || { echo "Permission denied: Repository $name is owned by $user"; retcode=1; continue; }
    check_repository_compatibility $name
    check_url "${CVMFS_STRATUM0}/.cvmfspublished" 20 || { echo "Repository unavailable under $CVMFS_STRATUM0"; retcode=1; continue; }
    check_expiry $name $stratum0   || { echo "Repository whitelist for $name is expired!"; retcode=1; continue; }
    is_in_transaction $name        || { echo "Repository $name is not in a transaction"; retcode=1; continue; }
    [ $(count_wr_fds /cvmfs/$name) -eq 0 ] || { echo "Open writable file descriptors on $name"; retcode=1; continue; }
    is_cwd_on_path "/cvmfs/$name" && { echo "Current working directory is in /cvmfs/$name.  Please release, e.g. by 'cd \$HOME'."; retcode=1; continue; } || true
    gc_timespan="$(get_auto_garbage_collection_timespan $name)" || { retcode=1; continue; }
    if [ x"$manual_revision" != x"" ]; then
      if [ "x$(echo "$manual_revision" | tr -cd 0-9)" != "x$manual_revision" ]; then
        echo "Invalid revision number: $manual_revision"
        retcode=1
        continue
      fi
      local revision_number=$(attr -qg revision /var/spool/cvmfs/${name}/rdonly)
      if [ $manual_revision -le $revision_number ]; then
        echo "Current revision '$revision_number' is ahead of manual revision number '$manual_revision'."
        retcode=1
        continue
      fi
    fi

    if is_checked_out $name; then
      if [ x"$tag_name" = "x" ]; then
        echo "Publishing a checked out revision requires a tag name"
        retcode=1
        continue
      fi
    else
      if [ -z "$tag_name" ] && [ x"$CVMFS_AUTO_TAG" = x"true" ]; then
        local timestamp=$(date -u "+%Y-%m-%dT%H:%M:%SZ")
        tag_name="generic-$timestamp"
        local tag_name_number=1
        while check_tag_existence $name $tag_name; do
          tag_name="generic_$tag_name_number-$timestamp"
          tag_name_number=$(( $tag_name_number + 1 ))
        done
        echo "Using auto tag '$tag_name'"
      fi

      local auto_tag_cleanup_list=
      auto_tag_cleanup_list="$(filter_auto_tags $name)" || { echo "failed to determine outdated auto tags on $name"; retcode=1; continue; }
    fi

    # prepare the commands to be used for the publishing later
    local user_shell="$(get_user_shell $name)"

    local base_hash=$(get_mounted_root_hash $name)
    local manifest="${spool_dir}/tmp/manifest"
    local dirtab_command="$(__swissknife_cmd dbg) dirtab \
      -d /cvmfs/${name}/.cvmfsdirtab                     \
      -b $base_hash                                      \
      -w $stratum0                                       \
      $(get_swissknife_proxy)                            \
      -t ${spool_dir}/tmp                                \
      -u /cvmfs/${name}                                  \
      -s ${scratch_dir}                                  \
      $verbosity"

    local log_level=
    [ "x$CVMFS_LOG_LEVEL" != x ] && log_level="-z $CVMFS_LOG_LEVEL"

    local sync_command="$(__swissknife_cmd dbg) sync \
        -u /cvmfs/$name                                \
        -s ${scratch_dir}                              \
        -c ${spool_dir}/rdonly                         \
        -t ${spool_dir}/tmp                            \
        -b $base_hash                                  \
        -r ${upstream}                                 \
        -w $stratum0                                   \
        -o $manifest                                   \
        -e $hash_algorithm                             \
        -Z $compression_alg                            \
        -N $name                                       \
        -K $CVMFS_PUBLIC_KEY                           \
        $(get_follow_http_redirects_flag)              \
        $(get_swissknife_proxy)                        \
        $authz_file                                    \
        $log_level $tweaks_option $external_option $direct_io_option $verbosity"

    if [ ! -z "$tag_name" ]; then
      sync_command="$sync_command -D $tag_name"
    fi

    if [ x"$tag_description" != x"" ]; then
      sync_command="$sync_command -J $tag_description"
    fi

    # If the upstream type is "gw", we need to additionally pass
    # the names of the file containing the gateway key and of the
    # one containing the session token
    if [ x"$upstream_type" = xgw ]; then
      sync_command="$sync_command -H $gw_key_file -P ${spool_dir}/session_token"
    fi
    if [ "x$CVMFS_UNION_FS_TYPE" != "x" ]; then
      sync_command="$sync_command -f $CVMFS_UNION_FS_TYPE"
    fi
    if [ "x${CVMFS_GENERATE_LEGACY_BULK_CHUNKS:-$CVMFS_DEFAULT_GENERATE_LEGACY_BULK_CHUNKS}" = "xtrue" ]; then
      sync_command="$sync_command -O"
    fi
    if [ "x$CVMFS_USE_FILE_CHUNKING" = "xtrue" ]; then
      sync_command="$sync_command -p \
       -l $CVMFS_MIN_CHUNK_SIZE \
       -a $CVMFS_AVG_CHUNK_SIZE \
       -h $CVMFS_MAX_CHUNK_SIZE"
    fi
    if [ "x$CVMFS_AUTOCATALOGS" = "xtrue" ]; then
      sync_command="$sync_command -A"
    fi
    if [ "x$CVMFS_AUTOCATALOGS_MAX_WEIGHT" != "x" ]; then
      sync_command="$sync_command -X $CVMFS_AUTOCATALOGS_MAX_WEIGHT"
    fi
    if [ "x$CVMFS_AUTOCATALOGS_MIN_WEIGHT" != "x" ]; then
      sync_command="$sync_command -M $CVMFS_AUTOCATALOGS_MIN_WEIGHT"
    fi
    if [ "x$CVMFS_SERVER_USE_CATALOG_CACHE" = "xtrue" ]; then
      sync_command="$sync_command -G"
    fi
    if [ "x$CVMFS_IGNORE_XDIR_HARDLINKS" = "xtrue" ]; then
      sync_command="$sync_command -i"
    fi
    if [ "x$CVMFS_INCLUDE_XATTRS" = "xtrue" ]; then
      sync_command="$sync_command -k"
    fi
    if [ "x${CVMFS_ENFORCE_LIMITS:-$CVMFS_DEFAULT_ENFORCE_LIMITS}" = "xtrue" ]; then
      sync_command="$sync_command -E"
    fi
    if [ "x$CVMFS_ENABLE_MTIME_NS" = "xtrue" ]; then
      sync_command="$sync_command -j"
    fi
    if [ "x$CVMFS_NESTED_KCATALOG_LIMIT" != "x" ]; then
      sync_command="$sync_command -Q $CVMFS_NESTED_KCATALOG_LIMIT"
    fi
    if [ "x$CVMFS_ROOT_KCATALOG_LIMIT" != "x" ]; then
      sync_command="$sync_command -R $CVMFS_ROOT_KCATALOG_LIMIT"
    fi
    if [ "x$CVMFS_FILE_MBYTE_LIMIT" != "x" ]; then
      sync_command="$sync_command -U $CVMFS_FILE_MBYTE_LIMIT"
    fi
    if [ "x$CVMFS_NUM_UPLOAD_TASKS" != "x" ]; then
      sync_command="$sync_command -0 $CVMFS_NUM_UPLOAD_TASKS"
    fi
    if [ "x$manual_revision" != "x" ]; then
      sync_command="$sync_command -v $manual_revision"
    fi
    if [ "x$CVMFS_REPOSITORY_TTL" != "x" ]; then
      sync_command="$sync_command -T $CVMFS_REPOSITORY_TTL"
    fi
    if [ "x$CVMFS_MAXIMAL_CONCURRENT_WRITES" != "x" ]; then
      sync_command="$sync_command -q $CVMFS_MAXIMAL_CONCURRENT_WRITES"
    fi
    if [ "x${CVMFS_VOMS_AUTHZ}" != x ]; then
      sync_command="$sync_command -V"
    fi
    if [ "x$CVMFS_IGNORE_SPECIAL_FILES" = "xtrue" ]; then
      sync_command="$sync_command -g"
    fi
    if [ "x$CVMFS_UPLOAD_STATS_DB" = "xtrue" ]; then
      sync_command="$sync_command -I"
    fi
    local sync_command_virtual_dir=
    if [ "x${CVMFS_VIRTUAL_DIR}" = "xtrue" ]; then
      sync_command_virtual_dir="$sync_command -S snapshots"
    else
      if [ -d /cvmfs/$name/.cvmfs ]; then
        sync_command_virtual_dir="$sync_command -S remove"
      fi
    fi
    if [ "x$CVMFS_PRINT_STATISTICS" = "xtrue" ]; then
      sync_command="$sync_command -+stats"
    fi
    # Must be after the virtual-dir command is constructed
    if is_checked_out $name; then
      sync_command="$sync_command -B"
    fi

    local tag_command="$(__swissknife_cmd dbg) tag_edit \
      -r $upstream                                      \
      -w $stratum0                                      \
      -t ${spool_dir}/tmp                               \
      -m $manifest                                      \
      -p /etc/cvmfs/keys/${name}.pub                    \
      -f $name                                          \
      -e $hash_algorithm                                \
      $(get_swissknife_proxy)                           \
      $(get_follow_http_redirects_flag)"
    if ! is_checked_out $name; then
      # enables magic undo tag handling
      tag_command="$tag_command -x"
    else
      tag_command="$tag_command -B $(get_checked_out_branch $name)"
      if [ "x$(get_checked_out_previous_branch $name)" != "x" ]; then
        tag_command="$tag_command -P $(get_checked_out_previous_branch $name)"
      fi
    fi
    if [ ! -z "$tag_name" ]; then
      tag_command="$tag_command -a $tag_name"
    fi
    if [ ! -z "$tag_description" ]; then
      tag_command="$tag_command -D \"$tag_description\""
    fi

    local tag_command_undo_tags="$(__swissknife_cmd dbg) tag_edit \
      -r $upstream                                                \
      -w $stratum0                                                \
      -t ${spool_dir}/tmp                                         \
      -m $manifest                                                \
      -p /etc/cvmfs/keys/${name}.pub                              \
      -f $name                                                    \
      -e $hash_algorithm                                          \
      $(get_swissknife_proxy)                                     \
      $(get_follow_http_redirects_flag)                           \
      -x"

    # ---> do it! (from here on we are changing things)
    publish_before_hook $name
    $user_shell "$dirtab_command" || die "Failed to apply .cvmfsdirtab"

    # check if we have open file descriptors on /cvmfs/<name>
    local use_fd_fallback=0
    handle_read_only_file_descriptors_on_mount_point $name $open_fd_dialog || use_fd_fallback=1

    # synchronize the repository
    publish_starting $name
    $user_shell "$sync_command" || { publish_failed $name; die "Synchronization failed\n\nExecuted Command:\n$sync_command";   }
    cvmfs_sys_file_is_regular $manifest            || { publish_failed $name; die "Manifest creation failed\n\nExecuted Command:\n$sync_command"; }
    local branch_hash=
    local trunk_hash=$(grep "^C" $manifest | tr -d C)
    if is_checked_out $name; then
      local branch_hash=$trunk_hash
      trunk_hash=$(get_published_root_hash $name)
      tag_command="$tag_command -h $branch_hash"
      # write intermediate catalog hash to reflog
      sign_manifest $name $manifest "" true
      # Replace throw-away manifest with upstream copy
      get_raw_manifest $name > $manifest
      cvmfs_sys_file_is_empty $manifest && die "failed to reload manifest"
    fi

    if [ x"$upstream_type" = xgw ]; then
        # TODO(jpriessn): implement publication counters upload to gateway
        close_transaction  $name $use_fd_fallback
        publish_after_hook $name
        publish_succeeded $name
        echo "Changes submitted to repository gateway"
        return 0
    fi

    # Remove outdated automatically created tags
    local tag_remove_cmd_file=
    if [ ! -z "$auto_tag_cleanup_list" ]; then
      local tag_list_file=$(mktemp)
      echo $auto_tag_cleanup_list | xargs -n100 echo > $tag_list_file
      tag_remove_cmd_file=$(mktemp)
      cat $tag_list_file | while read REPLY; do
        local tag_cleanup_command="$(__swissknife_cmd dbg) tag_edit \
          -r $upstream                                        \
          -w $stratum0                                        \
          -t ${spool_dir}/tmp                                 \
          -m $manifest                                        \
          -p /etc/cvmfs/keys/${name}.pub                      \
          -f $name                                            \
          -b $base_hash                                       \
          -e $hash_algorithm                                  \
          $(get_swissknife_proxy)                             \
          $(get_follow_http_redirects_flag)                   \
          -d \\\"$REPLY\\\""
        echo $user_shell \"${tag_cleanup_command}\" >> $tag_remove_cmd_file
      done
      rm -f $tag_list_file
    fi

    if [ ! -z "$tag_remove_cmd_file" ]; then
      echo "Removing outdated automatically generated tags for $name..."
      /bin/sh $tag_remove_cmd_file || \
        { rm -f $tag_remove_cmd_file; publish_failed $name; \
          die "Removing tags failed\n\nExecuted Command:\n/bin/sh \
          $tag_remove_cmd_file"; }
      rm -f $tag_remove_cmd_file
      # write intermediate history hash to reflog
      sign_manifest $name $manifest "" true
    fi

    # add a tag for the new revision
    echo "Tagging $name"
    $user_shell "$tag_command" || { publish_failed $name; die "Tagging failed\n\nExecuted Command:\n$tag_command";  }

    if [ "x$sync_command_virtual_dir" != "x" ]; then
      # write intermediate catalog hash and history to reflog
      sign_manifest $name $manifest "" true
      $user_shell "$sync_command_virtual_dir" || { publish_failed $name; die "Editing .cvmfs failed\n\nExecuted Command:\n$sync_command_virtual_dir";  }
      local trunk_hash=$(grep "^C" $manifest | tr -d C)
      $user_shell "$tag_command_undo_tags" || { publish_failed $name; die "Creating undo tags\n\nExecuted Command:\n$tag_command_undo_tags";  }
    fi

    # finalizing transaction
    echo "Flushing file system buffers"
    syncfs

    # committing newly created revision
    echo "Signing new manifest"
    sign_manifest $name $manifest      || { publish_failed $name; die "Signing failed"; }
    set_ro_root_hash $name $trunk_hash || { publish_failed $name; die "Root hash update failed"; }
    if is_checked_out $name; then
      rm -f /var/spool/cvmfs/${name}/checkout
      echo "Reset to trunk on default branch"
    fi

    # run the automatic garbage collection (if configured)
    if is_due_auto_garbage_collection $name; then
      echo "Running automatic garbage collection"
      local dry_run=0
      __run_gc $name       \
               $stratum0   \
               $dry_run    \
               ""          \
               "0"         \
               -z $gc_timespan      || { local err=$?; publish_failed $name; die "Garbage collection failed ($err)"; }
    fi

    # check again for open file descriptors (potential race condition)
    if has_file_descriptors_on_mount_point $name && \
       [ $use_fd_fallback -ne 1 ]; then
      file_descriptor_warning $name
      echo "Forcing remount of already committed repository revision"
      use_fd_fallback=1
    else
      echo "Remounting newly created repository revision"
    fi

    # remount the repository
    if [ "x$CVMFS_UPLOAD_STATS_PLOTS" = "xtrue" ]; then
      /usr/share/cvmfs-server/upload_stats_plots.sh $name
    fi
    close_transaction  $name $use_fd_fallback
    publish_after_hook $name
    publish_succeeded  $name
    syncfs
  done

  return $retcode
}


has_file_descriptors_on_mount_point() {
  local name=$1
  local mountpoint="/cvmfs/${name}"

  [ $(count_rd_only_fds $mountpoint) -gt 0 ] || \
  [ $(count_wr_fds      $mountpoint) -gt 0 ]
}


# Lists all auto-generated tags
#
# @param repository_name   the name of the repository to be filtered
# @return                  list of outdated auto-generate tags, space-separated
#              Note: in case of a errors it might print an error to stderr and
#              return a non-zero code
filter_auto_tags() {
  local repository_name="$1"
  local auto_tags_timespan=
  auto_tags_timespan=$(get_auto_tags_timespan "$repository_name") || return 1
  [ $auto_tags_timespan -eq 0 ] && return 0 || true

  load_repo_config $repository_name
  local auto_tags="$(__swissknife tag_list      \
    -w $CVMFS_STRATUM0                         \
    -t ${CVMFS_SPOOL_DIR}/tmp                  \
    -p /etc/cvmfs/keys/${repository_name}.pub  \
    -f $repository_name                        \
    $(get_swissknife_proxy)                    \
    -x $(get_follow_http_redirects_flag)       | \
    grep -E \
    '^generic(_[[:digit:]]+)?-[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}T[[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}(\.[[:digit:]]{1,3})?Z' | \
    awk '{print $1 " " $5}')"
  [ "x$auto_tags" = "x" ] && return 0 || true

  local tag_name=
  local timestamp=
  local old_tags="$(echo "$auto_tags" | while read tag_name timestamp; do
    if [ "$timestamp" -lt "$auto_tags_timespan" ]; then
      echo -n "$tag_name "
    fi
  done)"
  # Trim old_tags
  echo $old_tags
}


publish_starting() {
  local name=$1
  load_repo_config $name
  local pub_lock="${CVMFS_SPOOL_DIR}/is_publishing"
  acquire_lock "$pub_lock" || die "Failed to acquire publishing lock"
  trap "publish_failed $name" EXIT HUP INT TERM
  run_suid_helper lock $name
  to_syslog_for_repo $name "started publishing"
}


publish_failed() {
  local name=$1
  load_repo_config $name
  local pub_lock="${CVMFS_SPOOL_DIR}/is_publishing"
  trap - EXIT HUP INT TERM
  release_lock $pub_lock
  run_suid_helper open $name
  to_syslog_for_repo $name "failed to publish"
}


publish_succeeded() {
  local name=$1
  load_repo_config $name
  local pub_lock="${CVMFS_SPOOL_DIR}/is_publishing"
  trap - EXIT HUP INT TERM
  release_lock $pub_lock
  to_syslog_for_repo $name "successfully published revision $(get_repo_info -v)"
}

#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server masterkeycard" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_sys.sh
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh
# - cvmfs_server_resign.sh

# Check if a masterkeycard is available to be used
# If not, the reason is sent to stdout and false is returned,
# otherwise there's nothing to stdout and true is returned
masterkeycard_available() {
  local pattern="Yubi[kK]ey.*CCID"
  local reason=""
  if ! lsusb | grep -q "$pattern"; then
    reason="USB device matching \"$pattern\" not present"
  elif ! which opensc-tool > /dev/null 2>&1; then
    reason="opensc-tool (from opensc package) not found"
  elif ! which systemctl > /dev/null 2>&1; then
    reason="masterkeycard only supported on systems with systemctl"
  elif ! systemctl is-active --quiet pcscd.socket; then
    reason="systemctl unit pcscd.socket is not active"
  elif ! systemctl is-enabled --quiet pcscd.socket; then
    reason="systemctl unit pcscd.socket is not enabled"
  elif ! opensc-tool -l | grep -q "$pattern"; then
    reason="opensc-tool -l has no device matching \"$pattern\""
  elif ! which yubico-piv-tool >/dev/null 2>&1; then
    reason="yubico-piv-tool (from yubico-piv-tool package) not found"
  elif yubico-piv-tool -a status 2>&1 | grep -q "Failed to connect"; then
    reason="yubico-piv-tool failed to connect to device"
  elif ! pkcs11-tool -I >/dev/null 2>&1; then
    reason="pkcs11-tool -I failed"
  fi
  if [ -n "$reason" ]; then
    echo "$reason"
    return 1
  fi
}

# same as above except also check if cert is present
masterkeycard_cert_available() {
  local reason
  reason="`masterkeycard_available`"
  if [ -z "$reason" ] && [ -z "`masterkeycard_read_cert`" ]; then
    reason="no certificate stored in device"
  fi
  if [ -n "$reason" ]; then
    echo "$reason"
    return 1
  fi
}

masterkeycard_read_cert() {
  yubico-piv-tool -s 9c -a read-certificate 2>/dev/null
}

masterkeycard_read_pubkey() {
  masterkeycard_read_cert | openssl x509 -pubkey -noout
}

masterkeycard_store() {
  local masterkey=$1
  # Note that these commands will fail if the management (mgm) key has been
  #   changed, but if a user is advanced enough to know that they can always
  #   run these commands by hand with the new mgm key for the rare case of
  #   storing a masterkey.
  #   Changing the management key is not required to keep a stored key from
  #   being used by an attacker who gains physical custody; changing the PIN
  #   and PUK can do that.
  yubico-piv-tool -s 9c -i $masterkey -a import-key
  openssl req -new -subj '/O=o/CN=cn' -x509 -days 36500 -key $masterkey | \
    yubico-piv-tool -s 9c -a import-cert
}

masterkeycard_delete() {
  yubico-piv-tool -s 9c -a delete-certificate
}

masterkeycard_sign() {
  local hashfile=$1
  local sigfile=$2

  local pkcsout
  # Capture output in a variable to bypass annoying "Using" messages that
  #  normally go to stderr, while still checking the exit code from pkcs11-tool.
  if ! pkcsout="`pkcs11-tool -p ${CVMFS_MASTERKEYCARD_PIN:-123456} -s -m RSA-PKCS -i $hashfile -o $sigfile 2>&1`"; then
    echo "$pkcsout" >&2
    return 1
  fi
}

cvmfs_server_masterkeycard() {
  local names
  local name
  local goodnames
  local retcode=0
  local action=""
  local force=0
  local reason
  local masterkey
  local pubkey

  # optional parameter handling
  OPTIND=1
  while getopts "aksdrcf" option
  do
    case $option in
      a|k|s|d|r|c)
        [ -z "$action" ] || die "Only one masterkeycard action option allowed"
        action=$option
      ;;
      f)
        # force skipping the prompts for dangerous actions
        force=1
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command masterkeycard: Unrecognized option: $1"
      ;;
    esac
  done
  shift $(($OPTIND-1))

  [ -n "$action" ] || usage "Command masterkeycard: no action option given"

  if [ $action = s ]; then
    is_root || die "Only root may store to the masterkeycard"
    check_parameter_count 1 $#
    name="$1"
  elif [ $action = c ]; then
    is_root || die "Only root may convert repositories to use the masterkeycard"
    check_parameter_count_for_multiple_repositories $#
    names=$(get_or_guess_multiple_repository_names "$@")
  elif [ $action = d ]; then
    is_root || die "Only root may delete from the masterkeycard"
    check_parameter_count 0 $#
  else
    check_parameter_count 0 $#
  fi

  case $action in
    a)
      # check whether a smartcard is available
      reason="`masterkeycard_available`" || die "$reason"
      echo masterkeycard is available
    ;;
    k)
      # check whether a cert (and presumably key) is stored in the card
      reason="`masterkeycard_cert_available`" || die "$reason"
      echo masterkeycard key is available
    ;;
    s)
      # Store the masterkey from the given repository into the card.
      # Does not need to be a fully created repo, the masterkey just has
      #  to exist.
      masterkey="/etc/cvmfs/keys/${name}.masterkey"

      cvmfs_sys_file_is_regular $masterkey || die "$masterkey not found"

      if [ $force -ne 1 ] && masterkeycard_read_cert >/dev/null; then
        local reply
        read -p "You are about to overwrite a stored key!  Are you sure (y/N)? " reply
        if [ "$reply" != "y" ] && [ "$reply" != "Y" ]; then
          return 1
        fi
      fi

      masterkeycard_store $masterkey

      if check_repository_existence "$name"; then
        echo
        echo "Now back up $masterkey"
        echo "to flash drives stored in safe places and use"
        echo "  cvmfs_server masterkeycard -c $name"
        echo "to remove the masterkey and convert to use the key in the card."
      fi
    ;;
    d)
      # delete a certificate from the card
      reason="`masterkeycard_available`" || die "$reason"
      masterkeycard_read_cert >/dev/null || { echo "No certificate in card to delete"; exit; }

      if [ $force -ne 1 ]; then
        local reply
        read -p "You are about to delete a card's stored certificate!  Are you sure (y/N)? " reply
        if [ "$reply" != "y" ] && [ "$reply" != "Y" ]; then
          return 1
        fi
      fi

      masterkeycard_delete
      echo
      echo "IMPORTANT NOTE: this did not delete the masterkey, only the certificate,"
      echo "  so the card still needs to be kept physically secure.  The masterkey can"
      echo "  still be made usable by storing a certificate made from the corresponding"
      echo "  pub key. To destroy the masterkey, store an unimportant key in its place."
    ;;
    r)
      # read the pub key from the card to stdout
      reason="`masterkeycard_cert_available`" || die "$reason"
      masterkeycard_read_pubkey
    ;;
    c)
      # convert given repositories to use the key in the card

      for name in $names; do

        # sanity checks
        check_repository_existence "$name" || { echo "Repository $name does not exist"; retcode=1; continue; }
        is_stratum0 $name  || { echo "Repository $name is not a stratum 0 repository"; retcode=1; continue; }
        health_check $name || { echo "Repository $name is not healthy"; retcode=1; continue; }

        # get repository information
        load_repo_config $name

        # check if repository is compatible to the installed CernVM-FS version
        check_repository_compatibility $name

        goodnames="$goodnames $name"
      done

      if [ $force -eq 1 ] && [ $retcode -ne 0 ]; then
        # If any repo had a problem, and -f is requested, do nothing.
        # If -f is not requested, the user will have a chance to decide
        # whether or not to proceed with the other repos.
        return $retcode
      fi

      if [ -n "$goodnames" ] && [ $force -ne 1 ]; then
        echo "The following repository(ies) will be converted to use the masterkeycard:"
        echo " $goodnames"
        echo "This will remove the masterkey and update the pub key and cannot be undone!"
        local reply
        read -p "Are you sure you want to proceed (y/N)? " reply
        if [ "$reply" != "y" ] && [ "$reply" != "Y" ]; then
          return 1
        fi
      fi

      pubkey="`masterkeycard_read_pubkey`" || die "Failure reading pub key from mastercard"
      for name in $goodnames; do
        masterkey="/etc/cvmfs/keys/${name}.masterkey"
        if cvmfs_sys_file_is_regular $masterkey; then
          echo "Removing $masterkey and updating pub key"
          shred -uf $masterkey
        else
          echo "$masterkey already missing, but updating pub key"
        fi
        echo "$pubkey" >/etc/cvmfs/keys/$name.pub
        cvmfs_server_resign $name
      done
    ;;
  esac


  return $retcode
}


#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server import" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh
# - cvmfs_server_ssl.sh
# - cvmfs_server_apache.sh
# - cvmfs_server_json.sh
# - cvmfs_server_transaction.sh
# - cvmfs_server_publish.sh
# - cvmfs_server_masterkeycard.sh


IMPORT_DESASTER_REPO_NAME=""
IMPORT_DESASTER_MANIFEST_BACKUP=""
IMPORT_DESASTER_MANIFEST_SIGNED=0
_import_desaster_cleanup() {
  local name="$IMPORT_DESASTER_REPO_NAME"
  if [ x"$name" = x"" ]; then
    return 0
  fi

  unmount_and_teardown_repository $name
  remove_spool_area               $name
  remove_config_files             $name

  if [ $IMPORT_DESASTER_MANIFEST_SIGNED -ne 0 ] && \
     [ x$IMPORT_DESASTER_MANIFEST_BACKUP != x"" ]; then
    echo "Manifest was overwritten. If needed here is a backup: $IMPORT_DESASTER_MANIFEST_BACKUP"
  fi
}


# This command needs transaction + publish
migrate_legacy_dirtab() {
  local name=$1
  local dirtab_path="/cvmfs/${name}/.cvmfsdirtab"
  local tmp_dirtab=$(mktemp)

  cp -f "$dirtab_path" "$tmp_dirtab"                           || return 1
  cvmfs_server_transaction $name > /dev/null                   || return 2
  cat "$tmp_dirtab" | sed -e 's/\(.*\)/\1\/\*/' > $dirtab_path || return 3
  cvmfs_server_publish $name > /dev/null                       || return 4
  rm -f "$tmp_dirtab"                                          || return 5
}


cvmfs_server_import() {
  local name
  local stratum0
  local keys_location="/etc/cvmfs/keys"
  local upstream
  local owner
  local file_ownership
  local is_legacy=0
  local show_statistics=0
  local replicable=0
  local chown_backend=0
  local unionfs=
  local recreate_whitelist=0
  local configure_apache=1
  local recreate_repo_key=0
  local require_masterkeycard=0
  local proxy_url

  # parameter handling
  OPTIND=1
  while getopts "w:o:c:u:k:lsmgf:rptRx:" option; do
    case $option in
      w)
        stratum0=$OPTARG
      ;;
      o)
        owner=$OPTARG
      ;;
      c)
        file_ownership=$OPTARG
      ;;
      u)
        upstream=$OPTARG
      ;;
      k)
        keys_location=$OPTARG
      ;;
      l)
        is_legacy=1
      ;;
      s)
        show_statistics=1
      ;;
      m)
        replicable=1
      ;;
      g)
        chown_backend=1
      ;;
      f)
        unionfs=$OPTARG
      ;;
      r)
        recreate_whitelist=1
      ;;
      p)
        configure_apache=0
      ;;
      t)
        recreate_repo_key=1
      ;;
      R)
        recreate_whitelist=1
        require_masterkeycard=1
      ;;
      x)
        proxy_url=$OPTARG
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command import: Unrecognized option: $1"
      ;;
    esac
  done

  # get repository name
  shift $(($OPTIND-1))
  check_parameter_count 1 $#
  name=$(get_repository_name $1)
  is_valid_repo_name "$name" || die "invalid repository name: $name"

  # default values
  [ x"$stratum0" = x ] && stratum0="$(mangle_local_cvmfs_url $name)"
  [ x"$upstream" = x ] && upstream=$(make_local_upstream $name)
  [ x"$unionfs"  = x ] && unionfs="$(get_available_union_fs)"

  local private_key="${name}.key"
  local master_key="${name}.masterkey"
  local certificate="${name}.crt"
  local public_key="${name}.pub"

  # sanity checks
  check_repository_existence $name  && die "The repository $name already exists"
  is_root                           || die "Only root can create a new repository"
  check_upstream_validity $upstream
  check_cvmfs2_client               || die "cvmfs client missing"
  check_autofs_on_cvmfs             && die "Autofs on /cvmfs has to be disabled"
  ensure_swissknife_suid $unionfs   || die "Need CAP_SYS_ADMIN for cvmfs_swissknife"
  lower_hardlink_restrictions
  if [ $configure_apache -eq 1 ]; then
    check_apache                      || die "Apache must be installed and running"
    ensure_enabled_apache_modules
  fi
  [ x"$keys_location" = "x" ] && die "Please provide the location of the repository security keys (-k)"

  if [ $unionfs = "aufs" ]; then
    check_aufs                      || die "aufs kernel module missing"
  fi

  # repository owner dialog
  local cvmfs_user=$(get_cvmfs_owner $name $owner)
  check_user $cvmfs_user || die "No user $cvmfs_user"
  [ x"$file_ownership" = x ] && file_ownership="$(id -u $cvmfs_user):$(id -g $cvmfs_user)"
  echo $file_ownership | grep -q "^[0-9][0-9]*:[0-9][0-9]*$" || die "Unrecognized file ownership: $file_ownership | expected: <uid>:<gid>"
  local cvmfs_uid=$(echo $file_ownership | cut -d: -f1)
  local cvmfs_gid=$(echo $file_ownership | cut -d: -f2)

  # investigate the given repository storage for sanity
  local storage_location=$(get_upstream_config $upstream)
  if is_local_upstream $upstream; then
    local needed_items="${storage_location}                 \
                        ${storage_location}/.cvmfspublished \
                        ${storage_location}/data            \
                        ${storage_location}/data/txn"
    local i=0
    while [ $i -lt 256 ]; do
      needed_items="$needed_items ${storage_location}/data/$(printf "%02x" $i)"
      i=$(($i+1))
    done
    for item in $needed_items; do
      [ -e $item ] || die "$item missing"
      [ $chown_backend -ne 0 ] || [ x"$cvmfs_user" = x"$(stat -c%U $item)" ] || die "$item not owned by $cvmfs_user"
    done
  fi

  # check availability of repository signing key and certificate
  local keys="$public_key"
  if [ $recreate_repo_key -eq 0 ]; then
    if [ ! -f ${keys_location}/${private_key} ] || \
       [ ! -f ${keys_location}/${certificate} ]; then
      die "repository signing key or certificate not found (use -t maybe?)"
    fi
    keys="$keys $private_key $certificate"
  else
    [ $recreate_whitelist -ne 0 ] || die "using -t implies whitelist recreation (use -r maybe?)"
  fi

  # check whitelist expiry date
  if [ $recreate_whitelist -eq 0 ]; then
    cvmfs_sys_file_is_regular "${storage_location}/.cvmfswhitelist" || die "didn't find ${storage_location}/.cvmfswhitelist"
    local expiry=$(get_expiry_from_string "$(cat "${storage_location}/.cvmfswhitelist")")
    [ $expiry -gt 0 ] || die "Repository whitelist expired (use -r maybe?)"
  elif [ $require_masterkeycard -eq 1 ]; then
    local reason
    reason="`masterkeycard_cert_available`" || die "masterkeycard not available to create whitelist: $reason"
  elif ! cvmfs_sys_file_is_regular ${keys_location}/${master_key}; then
    masterkeycard_cert_available >/dev/null || die "Neither masterkey nor masterkeycard found for recreating whitelist"
  fi

  # set up disaster cleanup
  IMPORT_DESASTER_REPO_NAME="$name"
  trap _import_desaster_cleanup EXIT HUP INT QUIT TERM

  # create the configuration for the new repository
  # TODO(jblomer): make a better guess for hash and compression algorithm (see
  # also reflog creation)
  echo -n "Creating configuration files... "
  create_config_files_for_new_repository "$name"             \
                                         "$upstream"         \
                                         "$stratum0"         \
                                         "$cvmfs_user"       \
                                         "$unionfs"          \
                                         "sha1"              \
                                         "true"              \
                                         "false"             \
                                         "$configure_apache" \
                                         "default"           \
                                         "false"             \
                                         ""                  \
                                         ""                  \
                                         "$proxy_url" || die "fail!"
  echo "done"

  # import the old repository security keys
  echo -n "Importing the given key files... "
  if [ $require_masterkeycard -eq 0 ] && \
      cvmfs_sys_file_is_regular ${keys_location}/${master_key} ; then
    keys="$keys $master_key"
  fi
  import_keychain $name "$keys_location" $cvmfs_user "$keys" > /dev/null || die "fail!"
  echo "done"

  # create storage
  echo -n "Creating CernVM-FS Repository Infrastructure... "
  create_spool_area_for_new_repository $name               || die "fail!"
  if [ $configure_apache -eq 1 ]; then
    reload_apache > /dev/null || die "fail!"
  fi
  echo "done"

  # create reflog checksum
  if cvmfs_sys_file_is_regular ${storage_location}/.cvmfsreflog ; then
    echo -n "Re-creating reflog content hash... "
    local reflog_hash=$(cat ${storage_location}/.cvmfsreflog | cvmfs_publish hash -a sha1)
    echo -n $reflog_hash > "${CVMFS_SPOOL_DIR}/reflog.chksum"
    chown $CVMFS_USER "${CVMFS_SPOOL_DIR}/reflog.chksum"
    echo $reflog_hash
  fi

  # load repository configuration file
  load_repo_config $name
  local temp_dir="${CVMFS_SPOOL_DIR}/tmp"

  # import storage location
  if [ $chown_backend -ne 0 ]; then
    echo -n "Importing CernVM-FS storage... "
    chown -R $cvmfs_user $storage_location || die "fail!"
    set_selinux_httpd_context_if_needed $storage_location || die "fail!"
    echo "done"
  fi

  # Let Apache finish reload (needs to happen after SElinux adjustment)
  if [ $configure_apache -eq 1 ]; then
    wait_for_apache "${stratum0}/.cvmfswhitelist" || die "fail (Apache configuration)"
  fi

  # creating a new repository signing key if requested
  if [ $recreate_repo_key -ne 0 ]; then
    echo -n "Creating new repository signing key... "
    local manifest_url="${CVMFS_STRATUM0}/.cvmfspublished"
    local unsigned_manifest="${CVMFS_SPOOL_DIR}/tmp/unsigned_manifest"
    create_cert $name $CVMFS_USER                     || die "fail (certificate creation)!"
    local old_manifest
    old_manifest="`get_item $name $manifest_url`"     || die "fail (manifest download)!"
    echo "$old_manifest" | strip_manifest_signature - > $unsigned_manifest \
                                                      || die "fail (manifest signature strip)!"
    chown $CVMFS_USER $unsigned_manifest              || die "fail (manifest chown)!"
    sign_manifest $name $unsigned_manifest            || die "fail (manifest resign)!"
    echo "done"
  fi

  # recreate whitelist if requested
  if [ $recreate_whitelist -ne 0 ]; then
    create_whitelist $name $CVMFS_USER               \
                           ${CVMFS_UPSTREAM_STORAGE} \
                           ${CVMFS_SPOOL_DIR}/tmp || die "fail!"
  fi

  # migrate old catalogs
  if [ $is_legacy -ne 0 ]; then
    echo "Migrating old catalogs (may take a while)... "
    local new_manifest="${temp_dir}/new_manifest"
    local statistics_flag
    if [ $show_statistics -ne 0 ]; then
      statistics_flag="-s"
    fi
    IMPORT_DESASTER_MANIFEST_BACKUP="${storage_location}/.cvmfspublished.bak"
    cp ${storage_location}/.cvmfspublished \
       $IMPORT_DESASTER_MANIFEST_BACKUP || die "fail! (cannot backup .cvmfspublished)"
    __swissknife migrate               \
      -v "2.0.x"                       \
      -r $storage_location             \
      $(get_swissknife_proxy)          \
      -n $name                         \
      -u $upstream                     \
      -t $temp_dir                     \
      -k "/etc/cvmfs/keys/$public_key" \
      -o $new_manifest                 \
      -p $cvmfs_uid                    \
      -g $cvmfs_gid                    \
      -f                               \
      $statistics_flag              || die "fail! (migration)"
    chown $cvmfs_user $new_manifest || die "fail! (chown manifest)"

    # sign new (migrated) repository revision
    echo -n "Signing newly imported Repository... "
    local user_shell="$(get_user_shell $name)"
    sign_manifest $name $new_manifest || die "fail! (cannot sign repo)"
    IMPORT_DESASTER_MANIFEST_SIGNED=1
    echo "done"
  fi

  # do final setup
  echo -n "Mounting CernVM-FS Storage... "
  setup_and_mount_new_repository $name || die "fail!"
  echo "done"

  # the .cvmfsdirtab semantics might need an update
  if [ $is_legacy -ne 0 ] && cvmfs_sys_file_is_regular /cvmfs/${name}/.cvmfsdirtab ; then
    echo -n "Migrating .cvmfsdirtab... "
    migrate_legacy_dirtab $name || die "fail!"
    echo "done"
  fi

  # make stratum0 repository replicable if requested
  if [ $replicable -eq 1 ]; then
    cvmfs_server_alterfs -m on $name
  fi

  echo -n "Updating global JSON information... "
  update_global_repository_info && echo "done" || echo "fail"

  # reset trap and finish
  trap - EXIT HUP INT QUIT TERM
  print_new_repository_notice $name $cvmfs_user 1

  # print warning if OverlayFS is used for repository management
  if [ x"$CVMFS_UNION_FS_TYPE" = x"overlayfs" ]; then
    echo ""
    echo "WARNING: You are using OverlayFS which cannot handle hard links."
    echo "         If the imported repository '${name}' used to be based on"
    echo "         AUFS, please run the following command NOW to remove hard"
    echo "         links from the catalogs:"
    echo ""
    echo "    cvmfs_server eliminate-hardlinks ${name}"
    echo ""
  fi
}


#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server mkfs" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh
# - cvmfs_server_ssl.sh
# - cvmfs_server_apache.sh
# - cvmfs_server_json.sh
# - cvmfs_server_transaction.sh
# - cvmfs_server_publish.sh
# - cvmfs_server_masterkeycard.sh

cvmfs_server_alterfs() {
  local master_replica=-1
  local name

  # parameter handling
  OPTIND=1
  while getopts "m:" option; do
    case $option in
      m)
        if [ x$OPTARG = "xon" ]; then
          master_replica=1
        elif [ x$OPTARG = "xoff" ]; then
          master_replica=0
        else
          usage "Command alterfs: parameter -m expects 'on' or 'off'"
        fi
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command alterfs: Unrecognized option: $1"
      ;;
    esac
  done

  # get repository name
  shift $(($OPTIND-1))
  check_parameter_count_with_guessing $#
  name=$(get_or_guess_repository_name $1)

  # sanity checks
  check_repository_existence $name || die "The repository $name does not exist"
  [ $master_replica -ne -1 ] || usage "Command alterfs: What should I change?"
  is_root || die "Only root can alter a repository"

  # gather repository information
  load_repo_config $name
  local temp_dir="${CVMFS_SPOOL_DIR}/tmp"

  # do what you've been asked for
  local success=1
  if is_master_replica $name && [ $master_replica -eq 0 ]; then
    echo -n "Disallowing Replication of this Repository... "
    __swissknife remove -o ".cvmfs_master_replica" -r $CVMFS_UPSTREAM_STORAGE > /dev/null || success=0
    if [ $success -ne 1 ]; then
      echo "fail!"
      return 1
    else
      echo "done"
    fi
  elif ! is_master_replica $name && [ $master_replica -eq 1 ]; then
    echo -n "Allowing Replication of this Repository... "
    local master_replica="${temp_dir}/.cvmfs_master_replica"
    # Azurite does not like direct empty file uploads;
    echo "This file marks the repository as replicatable to stratum 1 servers" > $master_replica
    __swissknife upload -i $master_replica -o $(basename $master_replica) -r $CVMFS_UPSTREAM_STORAGE > /dev/null || success=0
    if [ $success -ne 1 ]; then
      echo "fail!"
      return 1
    else
      echo "done"
    fi
    rm -f $master_replica
  fi
}


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


cvmfs_server_mkfs() {
  local name
  local stratum0
  local upstream
  local owner
  local replicable=1
  local volatile_content=0
  local autotagging=true
  local auto_tag_timespan=
  local unionfs
  local hash_algo
  local compression_alg
  local garbage_collectable=false
  local s3_config=""
  local keys_import_location
  local external_data=false
  local require_masterkeycard=0
  local ignore_manifest_overwrite=0

  local configure_apache=1
  local voms_authz=""
  local proxy_url

  # parameter handling
  OPTIND=1
  while getopts "Xw:u:o:mf:vgG:a:zs:k:pRV:Z:x:I" option; do
    case $option in
      X)
        external_data=true
      ;;
      w)
        stratum0=$OPTARG
      ;;
      u)
        upstream=$OPTARG
      ;;
      o)
        owner=$OPTARG
      ;;
      m)
        replicable=1
      ;;
      f)
        unionfs=$OPTARG
      ;;
      v)
        volatile_content=1
      ;;
      g)
        autotagging=false
      ;;
      G)
        auto_tag_timespan="$OPTARG"
      ;;
      a)
        hash_algo=$OPTARG
      ;;
      z)
        garbage_collectable=true
      ;;
      s)
        s3_config=$OPTARG
      ;;
      k)
        keys_import_location=$OPTARG
      ;;
      R)
        require_masterkeycard=1
      ;;
      Z)
        compression_alg=$OPTARG
      ;;
      p)
        configure_apache=0
      ;;
      V)
        voms_authz=$OPTARG
      ;;
      x)
        proxy_url=$OPTARG
      ;;
      I)
        ignore_manifest_overwrite=1
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command mkfs: Unrecognized option: $1"
      ;;
    esac
  done

  # get repository name
  shift $(($OPTIND-1))
  check_parameter_count 1 $#
  name=$(get_repository_name $1)

  is_valid_repo_name "$name" || die "invalid repository name: $name"
  is_root                    || die "Only root can create a new repository"

  # default values
  [ x"$unionfs"   = x"" ] && unionfs="$(get_available_union_fs)"
  [ x"$hash_algo" = x"" ] && hash_algo=sha1
  [ x"$compression_alg" = x"" ] && compression_alg=default

  # upstream generation (defaults to local upstream)
  if [ x"$upstream" = x"" ]; then
    if [ x"$s3_config" != x"" ]; then
      local subpath=$(parse_url $stratum0 path)
      upstream=$(make_s3_upstream $name $s3_config $subpath)
    else
      upstream=$(make_local_upstream $name)
    fi
  fi

  # stratum0 URL generation (defaults to local URL)
  if [ x"$s3_config" != x"" ]; then
    [ x"$stratum0" = x"" ] && die "Please specify the HTTP-URL for S3 (add option -w)"
    stratum0=$(mangle_s3_cvmfs_url $name "$stratum0")
  elif [ x"$stratum0" = x"" ]; then
    stratum0="$(mangle_local_cvmfs_url $name)"
  fi

  # sanity checks
  local upstream_type=$(get_upstream_type $upstream)
  check_repository_existence $name  && die "The repository $name already exists"
  # if upstream is a gateway, we expect the repository to not be empty.
  if [ x"$upstream_type" != xgw ]; then
      if [ $ignore_manifest_overwrite -eq 0 ]; then
         is_empty_repository_from_url $stratum0 ||
             die "Error: A manifest already exists at this url: $stratum0/.cvmfspublished .\n
                 Delete it manually or use cvmfs_server  mkfs -I  to force the creation of a new repository in this non-empty
                 storage. \n\n If you meant to setup a new stratum0 for this existing repository, use cvmfs_server import."
      fi
  fi
  check_upstream_validity $upstream
  if [ $unionfs = "aufs" ]; then
    check_aufs                      || die "aufs kernel module missing"
  fi
  check_cvmfs2_client               || die "cvmfs client missing"
  check_autofs_on_cvmfs             && die "Autofs on /cvmfs has to be disabled"
  lower_hardlink_restrictions
  ensure_swissknife_suid $unionfs   || die "Need CAP_SYS_ADMIN for cvmfs_swissknife"
  if is_local_upstream $upstream; then
    check_apache                    || die "Apache must be installed and running"
    ensure_enabled_apache_modules
  fi
  if [ "x$auto_tag_timespan" != "x" ]; then
    date --date "$auto_tag_timespan" +%s >/dev/null 2>&1 || die "Auto tags time span cannot be parsed"
    [ x"$autotagging" = x"false" ] &&
      echo "Warning: auto tags time span set but auto tagging turned off" || true
  fi

  # check if the keychain for the repository to create is already in place
  local keys_location="/etc/cvmfs/keys"
  mkdir -p $keys_location
  local keys="${name}.crt ${name}.pub"
  if [ x"$upstream_type" = xgw ]; then
      keys="$keys ${name}.gw"
  else
      keys="$keys ${name}.key"
  fi
  if [ $require_masterkeycard -eq 1 ]; then
      local reason
      reason="`masterkeycard_cert_available`" || die "masterkeycard not available: $reason"
  elif masterkeycard_cert_available >/dev/null; then
      require_masterkeycard=1
  else
      if [ x"$upstream_type" != xgw ]; then
          keys="${name}.masterkey $keys"
      fi
  fi
  local keys_are_there=0
  for k in $keys; do
    if cvmfs_sys_file_is_regular "${keys_location}/${k}"; then
      keys_are_there=1
      break
    fi
  done
  if [ $keys_are_there -eq 1 ]; then
    # just import the keys that are already there if they do not overwrite existing keys
    if [ x"$keys_import_location" != x""               ] && \
       [ x"$keys_import_location" != x"$keys_location" ]; then
      die "Importing keys from '$keys_import_location' would overwrite keys in '$keys_location'"
    fi
    keys_import_location=$keys_location
  fi

  # repository owner dialog
  local cvmfs_user=$(get_cvmfs_owner $name $owner)
  check_user $cvmfs_user || die "No user $cvmfs_user"

  # GC and auto-tag warning
  if [ x"$autotagging" = x"true" ] && [ x"$auto_tag_timespan" = "x" ] && [ x"$garbage_collectable" = x"true" ]; then
    echo "Note: Autotagging all revisions impedes garbage collection"
  fi

  # create system-wide configuration
  echo -n "Creating Configuration Files... "
  create_config_files_for_new_repository "$name"                \
                                         "$upstream"            \
                                         "$stratum0"            \
                                         "$cvmfs_user"          \
                                         "$unionfs"             \
                                         "$hash_algo"           \
                                         "$autotagging"         \
                                         "$garbage_collectable" \
                                         "$configure_apache"    \
                                         "$compression_alg"     \
                                         "$external_data"       \
                                         "$voms_authz"          \
                                         "$auto_tag_timespan"   \
                                         "$proxy_url" || die "fail"
  echo "done"

  # create or import security keys and certificates
  if [ x"$keys_import_location" = x"" ]; then
    echo -n "Creating CernVM-FS Master Key and Self-Signed Certificate... "
    create_master_key $name $cvmfs_user || die "fail (master key)"
    create_cert $name $cvmfs_user       || die "fail (certificate)"
    echo "done"
  else
    echo -n "Importing CernVM-FS Master Key and Certificate from '$keys_import_location'... "
    import_keychain $name "$keys_import_location" $cvmfs_user "$keys" > /dev/null || die "fail!"
    echo "done"
  fi

  # create spool area and mountpoints
  echo -n "Creating CernVM-FS Server Infrastructure... "
  create_spool_area_for_new_repository $name || die "fail"
  echo "done"

  # create storage area
  if is_local_upstream $upstream; then
    echo -n "Creating Backend Storage... "
    create_global_info_skeleton     || die "fail"
    create_repository_storage $name || die "fail"
    echo "done"
  fi

  # get information about new repository
  load_repo_config $name
  local temp_dir="${CVMFS_SPOOL_DIR}/tmp"
  local rdonly_dir="${CVMFS_SPOOL_DIR}/rdonly"
  local scratch_dir="${CVMFS_SPOOL_DIR}/scratch/current"

  # create the whitelist
  if [ x"$upstream_type" != xgw ]; then
      create_whitelist $name $cvmfs_user $upstream $temp_dir
  fi

  echo -n "Creating Initial Repository... "
  local repoinfo_file=${temp_dir}/new_repoinfo
  touch $repoinfo_file
  create_repometa_skeleton $repoinfo_file
  if is_local_upstream $upstream && [ $configure_apache -eq 1 ]; then
    reload_apache > /dev/null
    wait_for_apache "${stratum0}/.cvmfswhitelist" || die "fail (Apache configuration)"
  fi

  local volatile_opt=
  if [ $volatile_content -eq 1 ]; then
    volatile_opt="-v"
    echo -n "(repository flagged volatile)... "
  fi
  local user_shell="$(get_user_shell $name)"
  if [ x"$upstream_type" != xgw ]; then
      local create_cmd="$(__swissknife_cmd) create  \
      -t $temp_dir                                \
      -r $upstream                                \
      -n $name                                    \
      -a $hash_algo $volatile_opt                 \
      -o ${temp_dir}/new_manifest                 \
      -R $(get_reflog_checksum $name)"
      if $garbage_collectable; then
          create_cmd="$create_cmd -z"
      fi
      if [ "x$voms_authz" != "x" ]; then
          echo -n "(repository will be accessible with VOMS credentials $voms_authz)... "
          create_cmd="$create_cmd -V $voms_authz"
      fi

      $user_shell "$create_cmd" > /dev/null                       || die "fail! (cannot init repo)"
      sign_manifest $name ${temp_dir}/new_manifest $repoinfo_file || die "fail! (cannot sign repo)"
  fi
  echo "done"

  echo -n "Mounting CernVM-FS Storage... "
  setup_and_mount_new_repository $name || die "fail"
  echo "done"

  if [ $replicable -eq 1 ]; then
    cvmfs_server_alterfs -m on $name
  fi

  health_check $name || die "fail! (health check after mount)"

  if [ x"$upstream_type" != xgw -a "x$voms_authz" = "x" ]; then
      echo -n "Initial commit... "
      cvmfs_server_transaction $name > /dev/null || die "fail (transaction)"
      echo "New CernVM-FS repository for $name" > /cvmfs/${name}/new_repository
      chown $cvmfs_user /cvmfs/${name}/new_repository
      cvmfs_server_publish $name > /dev/null || die "fail (publish)"
      # When publishing an external repository, it is the user's responsibility to
      # stage the actual data files to the web server - not the publication function.
      # Hence, the following is guaranteed to not work.
      if [ $external_data = "false" ]; then
          cat $rdonly_dir/new_repository || die "fail (finish)"
      fi
  fi

  echo -n "Updating global JSON information... "
  update_global_repository_info && echo "done" || echo "fail"

  syncfs

  print_new_repository_notice $name $cvmfs_user $require_masterkeycard
}


#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server add-replica" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh

cvmfs_server_add_replica() {
  local name
  local alias_name
  local stratum0
  local stratum1_url
  local public_key
  local upstream
  local owner
  local silence_httpd_warning=0
  local configure_apache=1
  local enable_auto_gc=0
  local s3_config
  local snapshot_group
  local is_passthrough=0

  # optional parameter handling
  OPTIND=1
  while getopts "o:u:n:w:azs:pg:P" option
  do
    case $option in
      u)
        upstream=$OPTARG
      ;;
      o)
        owner=$OPTARG
      ;;
      n)
        alias_name=$OPTARG
      ;;
      w)
        stratum1_url=$OPTARG
      ;;
      a)
        silence_httpd_warning=1
      ;;
      z)
        enable_auto_gc=1
      ;;
      s)
        s3_config=$OPTARG
      ;;
      p)
        configure_apache=0
      ;;
      g)
        snapshot_group=$OPTARG
      ;;
      P)
        is_passthrough=1
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command add-replica: Unrecognized option: $1"
      ;;
    esac
  done

   # get stratum0 url and path of public key
  shift $(($OPTIND-1))

  if [ $is_passthrough -eq 0 ]; then
    check_parameter_count 2 $#
    stratum0=$1
    public_key=$2
  else
    check_parameter_count 1 $#
    stratum0=$1
  fi

  # get the name of the repository pointed to by $stratum0
  name=$(get_repo_info_from_url $stratum0 -L -n) || die "Failed to access Stratum0 repository at $stratum0"
  name=$(get_repo_info_from_url $stratum0    -n) || die "Failed to access Stratum0 repository at $stratum0"
  if [ x$alias_name = x"" ]; then
    alias_name=$name
  else
    alias_name=$(get_repository_name $alias_name)
  fi

  # sanity checks
  is_valid_repo_name "$alias_name" || die "invalid repository name: $alias_name"
  is_master_replica $stratum0 || die "The repository URL $stratum0 does not point to a replicable master copy of $name"
  if check_repository_existence $alias_name; then
    if is_stratum0 $alias_name; then
      die "Repository $alias_name already exists as a Stratum0 repository.\nUse -n to create an aliased Stratum1 replica for $name on this machine."
    else
      die "There is already a Stratum1 repository $alias_name"
    fi
  fi

  if [ $is_passthrough -eq 1 ]; then
    [ -z "$upstream" ] || die "Pass-through repository and non-default upstream storage are mutually exclusive"
    [ $enable_auto_gc -eq 0 ] || die "Pass-through repository and garbage collection are mutually exclusive"
  fi

  # upstream generation (defaults to local upstream)
  if [ x"$upstream" = x"" ]; then
    if [ x"$s3_config" != x"" ]; then
      local subpath=$(parse_url $stratum0 path)
      upstream=$(make_s3_upstream $alias_name $s3_config $subpath)
    else
      upstream=$(make_local_upstream $alias_name)
    fi
  fi

  # stratum1 URL generation (defaults to local URL)
  local stratum1=""
  if [ x"$s3_config" != x"" ]; then
    [ x"$stratum1_url" = x"" ] && die "Please specify the HTTP-URL for S3 (add option -w)"
    stratum1=$(mangle_s3_cvmfs_url $alias_name "$stratum1_url")
  elif [ x"$stratum1_url" = x"" ]; then
    stratum1="$(mangle_local_cvmfs_url $alias_name)"
  else
    stratum1="$stratum1_url"
  fi

  # additional configuration
  local cvmfs_user=$(get_cvmfs_owner $alias_name $owner)
  local spool_dir="/var/spool/cvmfs/${alias_name}"
  local temp_dir="${spool_dir}/tmp"
  local storage_dir=""
  is_local_upstream $upstream && storage_dir=$(get_upstream_config $upstream)

  # additional sanity checks
  is_root || die "Only root can create a new repository"
  check_user $cvmfs_user || die "No user $cvmfs_user"
  check_upstream_validity $upstream
  if is_local_upstream $upstream; then
    _update_geodb -l
    if [ $silence_httpd_warning -eq 0 ]; then
      check_apache || die "Apache must be installed and running"
      check_wsgi_module
      if [ x"$cvmfs_user" != x"root" ]; then
        echo "NOTE: If snapshot is not run regularly as root, the GeoIP database will not be updated."
        echo "  You have some options:"
        echo "    1. chown -R $CVMFS_UPDATEGEO_DIR accordingly"
        echo "    2. Run update-geodb from cron as root"
        echo "    3. chown -R $CVMFS_UPDATEGEO_DIR to a dedicated"
        echo "       user ID and run update-geodb monthly as that user"
        echo "    4. Use another update tool such as Maxmind's geoipupdate and"
        echo "       set CVMFS_GEO_DB_FILE to point to the downloaded file"
        echo "    5. Disable the geo api with CVMFS_GEO_DB_FILE=none"
        echo "  See 'Geo API Setup' in the cvmfs documentation for more info."
      fi
    else
      check_apache || echo "Warning: Apache is needed to access this CVMFS replication"
    fi
  fi

  echo -n "Creating Configuration Files... "
  mkdir -p /etc/cvmfs/repositories.d/${alias_name}
  cat > /etc/cvmfs/repositories.d/${alias_name}/server.conf << EOF
# Created by cvmfs_server.
CVMFS_CREATOR_VERSION=$(cvmfs_layout_revision)
CVMFS_REPOSITORY_NAME=$name
CVMFS_REPOSITORY_TYPE=stratum1
CVMFS_USER=$cvmfs_user
CVMFS_SPOOL_DIR=$spool_dir
CVMFS_STRATUM0=$stratum0
CVMFS_STRATUM1=$stratum1
CVMFS_UPSTREAM_STORAGE=$upstream
CVMFS_SNAPSHOT_GROUP=$snapshot_group
EOF
  if [ $is_passthrough -eq 1 ]; then
    cat > /etc/cvmfs/repositories.d/${alias_name}/replica.conf << EOF
# Created by cvmfs_server.
CVMFS_PASSTHROUGH=true
EOF
  else
    cat > /etc/cvmfs/repositories.d/${alias_name}/replica.conf << EOF
# Created by cvmfs_server.
CVMFS_NUM_WORKERS=16
CVMFS_PUBLIC_KEY=$public_key
CVMFS_HTTP_TIMEOUT=10
CVMFS_HTTP_RETRIES=3
EOF
  fi

  # append GC specific configuration
  if [ $enable_auto_gc != 0 ]; then
    cat >> /etc/cvmfs/repositories.d/${alias_name}/server.conf << EOF
CVMFS_AUTO_GC=true
EOF
  fi

  echo "done"

  if is_local_upstream $upstream; then
    create_global_info_skeleton

    echo -n "Create CernVM-FS Storage... "
    mkdir -p $storage_dir
    create_repository_skeleton $storage_dir $cvmfs_user > /dev/null
    echo "done"

    if [ $configure_apache -eq 1 ]; then
      echo -n "Update Apache configuration... "
      ensure_enabled_apache_modules
      if [ $is_passthrough -eq 1 ]; then
        check_proxy_module
        create_apache_proxy_config_for_endpoint $alias_name $stratum0 "with wsgi"
      else
        create_apache_config_for_endpoint $alias_name $storage_dir "with wsgi"
      fi
      create_apache_config_for_global_info
      reload_apache > /dev/null
      if [ $is_passthrough -eq 0 ]; then
        touch $storage_dir/.cvmfsempty
        wait_for_apache "${stratum1}/.cvmfsempty" || die "fail (Apache configuration)"
        rm -f $storage_dir/.cvmfsempty
      fi
      echo "done"
    fi
  fi

  echo -n "Creating CernVM-FS Server Infrastructure... "
  mkdir -p $spool_dir                       || die "fail (mkdir spool)"
  if is_local_upstream $upstream; then
    ln -s ${storage_dir}/data/txn $temp_dir || die "fail (ln -s)"
  else
    mkdir -p $temp_dir                      || die "fail (mkdir temp)"
  fi
  chown -R $cvmfs_user $spool_dir           || die "fail (chown)"
  echo "done"

  echo -n "Updating global JSON information... "
  update_global_repository_info && echo "done" || echo "fail"

  syncfs

  if [ $is_passthrough -eq 0 ]; then
    echo "\

Use 'cvmfs_server snapshot' to replicate $alias_name.
Make sure to install the repository public key in /etc/cvmfs/keys/
You might have to add the key in /etc/cvmfs/repositories.d/${alias_name}/replica.conf"
  fi
}


#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server rmfs" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh

cvmfs_server_rmfs() {
  local names
  local force=0
  local preserve_data=0
  local retcode=0

  # optional parameter handling
  OPTIND=1
  while getopts "fp" option
  do
    case $option in
      f)
        force=1
      ;;
      p)
        preserve_data=1
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command rmfs: Unrecognized option: $1"
      ;;
    esac
  done

  # sanity checks
  is_root               || die "Only root can remove a repository"
  ensure_enabled_apache_modules

  # get repository names
  shift $(($OPTIND-1))
  check_parameter_count_for_multiple_repositories $#
  names=$(get_or_guess_multiple_repository_names "$@")
  check_multiple_repository_existence "$names"

  for name in $names; do

    # better ask the user again!
    if [ $force -ne 1 ]; then
      local reply
      local question=""
      if [ $preserve_data -eq 0 ]; then
        question="You are about to WIPE OUT THE CERNVM-FS REPOSITORY ${name} INCLUDING SIGNING KEYS!"
      else
        question="You are about to REMOVE THE CERNVM-FS REPOSITORY INFRASTRUCTURE for ${name}!"
      fi

      read -p "${question}  Are you sure (y/N)? " reply
      if [ "$reply" != "y" ] && [ "$reply" != "Y" ]; then
        continue
      fi
    fi

    # get information about repository
    load_repo_config $name

    # check if repository is compatible to the installed CernVM-FS version
    check_repository_compatibility $name

    # sanity checks
    [ x"$CVMFS_SPOOL_DIR"        = x ] && { echo "Spool directory for $name is undefined";  retcode=1; continue; }
    [ x"$CVMFS_UPSTREAM_STORAGE" = x ] && { echo "Upstream storage for $name is undefined"; retcode=1; continue; }
    [ x"$CVMFS_REPOSITORY_TYPE"  = x ] && { echo "Repository type for $name is undefined";  retcode=1; continue; }

    # do it!
    if [ "$CVMFS_REPOSITORY_TYPE" = "stratum0" ]; then
      check_autofs_on_cvmfs && die "Autofs on /cvmfs has to be disabled"
      echo -n "Unmounting CernVM-FS Area... "
      unmount_and_teardown_repository $name || die "fail"
      echo "done"
    fi

    echo -n "Removing Spool Area... "
    remove_spool_area $name
    echo done

    echo -n "Removing Configuration... "
    remove_config_files $name || die "fail"
    echo "done"

    if [ $preserve_data -eq 0 ] && \
       is_local_upstream $CVMFS_UPSTREAM_STORAGE; then
      echo -n "Removing Repository Storage... "
      local storage_dir="$(get_upstream_config $CVMFS_UPSTREAM_STORAGE)"
      if [ x"$storage_dir" != x"" ]; then
        rm -fR "$storage_dir" || die "fail"
      fi
      echo "done"
    fi

    if [ $preserve_data -eq 0 ] && \
       [ "$CVMFS_REPOSITORY_TYPE" = stratum0 ]; then
      echo -n "Removing Keys and Certificate... "
      rm -f /etc/cvmfs/keys/$name.masterkey \
            /etc/cvmfs/keys/$name.pub       \
            /etc/cvmfs/keys/$name.key       \
            /etc/cvmfs/keys/$name.crt || die "fail"
      rm -f /etc/cvmfs/keys/$name.gw
      echo "done"
    fi

    echo -n "Updating global JSON information... "
    update_global_repository_info && echo "done" || echo "fail"

    echo "CernVM-FS repository $name wiped out!"

  done

  return $retcode
}


#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server resign" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh

cvmfs_server_resign() {
  local names
  local retcode=0
  local expire_days
  local force=0
  local whitelist_path
  local sign_published=0

  # parameter handling
  OPTIND=1
  while getopts "d:fpw:" option; do
    case $option in
      d)
        expire_days=$OPTARG
      ;;
      f)
        force=1
      ;;
      p)
        sign_published=1
      ;;
      w)
        whitelist_path=$OPTARG
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command resign: Unrecognized option: $1"
      ;;
    esac
  done

  # get repository names
  shift $(($OPTIND-1))
  check_parameter_count_for_multiple_repositories $#
  names=$(get_or_guess_multiple_repository_names "$@")
  [ -n "$whitelist_path" ] || check_multiple_repository_existence "$names"

  # sanity checks
  [ $sign_published -eq 0 ] || [ -z "$expire_days" ]    || die "Cannot use -d with -p"
  [ $sign_published -eq 0 ] || [ -z "$whitelist_path" ] || die "Cannot use both -w and -p"
  [ $sign_published -eq 1 ] || is_root || die "Only root can resign whitelists"

  if [ $sign_published -eq 0 ] && \
        [ -n "$expire_days" ] && [ $expire_days -gt 30 ]; then
    echo "Warning: whitelist expiration is more than 30 days."
    echo "Long expirations increase risk from repository key compromises!"
    if [ $force -ne 1 ]; then
      local reply
      read -p "Are you sure you want to do this (y/N)? " reply
      if [ "$reply" != "y" ] && [ "$reply" != "Y" ]; then
        return 1
      fi
    fi
  fi

  for name in $names; do

    if [ -z "$whitelist_path" ]; then
      # sanity checks
      is_stratum0 $name  || { echo "Repository $name is not a stratum 0 repository"; retcode=1; continue; }

      # get repository information
      load_repo_config $name

      # check if repository is compatible to the installed CernVM-FS version
      check_repository_compatibility $name

      # do it!
      if [ $sign_published -eq 1 ]; then
        # This is intended to be used when a repository key has been changed
        # It reuses everything from an old .cvmfspublished except the
        #  certificate hash, signature, and timestamp.

        echo -n "Signing .cvmfspublished... "
        local manifest="${CVMFS_SPOOL_DIR}/tmp/manifest"
        local manifest_url="${CVMFS_STRATUM0}/.cvmfspublished"
        local user_shell="$(get_user_shell $name)"
        # create the temporary manifest file with user permission first
        #  which will work whether running as the user or root
        $user_shell "> $manifest"
        local old_manifest
        old_manifest="`get_item $name $manifest_url`" || die "fail (manifest download)!"
        # overwriting will not change the owner
        echo "$old_manifest" | strip_manifest_signature - > $manifest
        sign_manifest $name $manifest
        echo "done"

      else

        create_whitelist $name $CVMFS_USER \
            ${CVMFS_UPSTREAM_STORAGE} ${CVMFS_SPOOL_DIR}/tmp "$expire_days"

      fi
    else
      # do not require repository configuration, just the whitelist file
      [ -f $whitelist_path ] || { echo "$whitelist_path does not exist!"; retcode=1; continue; }

      local user tmpdir
      user="`stat --format=%U $whitelist_path`"
      tmpdir="`mktemp -d`"
      trap "rm -rf $tmpdir" EXIT HUP INT TERM

      create_whitelist $name $user "" $tmpdir "$expire_days" $whitelist_path

      rm -rf $tmpdir
      trap - EXIT HUP INT TERM
    fi

  done

  return $retcode
}


#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server list-catalogs" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh

cvmfs_server_list_catalogs() {
  local name
  local param_list="-t"

  # optional parameter handling
  OPTIND=1
  while getopts "sehx" option
  do
    case $option in
      s)
        param_list="$param_list -s"
      ;;
      e)
        param_list="$param_list -e"
      ;;
      h)
        param_list="$param_list -d"
      ;;
      x)
        param_list=$(echo "$param_list" | sed 's/-t\s\?//')
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command list-catalogs: Unrecognized option: $1"
      ;;
    esac
  done

  # get repository name
  shift $(($OPTIND-1))
  check_parameter_count_with_guessing $#
  name=$(get_or_guess_repository_name $1)

  # sanity checks
  check_repository_existence $name || die "The repository $name does not exist"

  # get repository information
  load_repo_config $name

  # more sanity checks
  is_owner_or_root $name || die "Permission denied: Repository $name is owned by $CVMFS_USER"
  health_check     $name || die "Repository $name is not healthy"

  # check if repository is compatible to the installed CernVM-FS version
  check_repository_compatibility $name

  if is_checked_out $name; then
    param_list="$param_list -h $(get_checked_out_hash $name)"
  fi

  # do it!
  local user_shell="$(get_user_shell $name)"
  local lsrepo_cmd
  lsrepo_cmd="$(__swissknife_cmd dbg) lsrepo     \
                       -r $CVMFS_STRATUM0        \
                       $(get_swissknife_proxy)   \
                       -n $CVMFS_REPOSITORY_NAME \
                       -k $CVMFS_PUBLIC_KEY      \
                       -l ${CVMFS_SPOOL_DIR}/tmp \
                       $param_list"
  $user_shell "$lsrepo_cmd"
}


#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server diff" command
# Migrated to the new cvmfs_publish command

cvmfs_server_diff() {
  $(__publish_cmd dbg) diff $@
}
#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server info" command
# Migrated to the new cvmfs_publish command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh

cvmfs_server_info() {
  $(__publish_cmd dbg) info $@
}
#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server checkout" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh

cvmfs_server_checkout() {
  local name
  local branch_name
  local tag_name
  local tag_hash
  local branch_head

  # optional parameter handling
  OPTIND=1
  while getopts "b:t:" option
  do
    case $option in
      b)
        branch_name="$OPTARG"
      ;;
      t)
        tag_name="$OPTARG"
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command checkout: Unrecognized option: $1"
      ;;
    esac
  done

  # get repository name
  shift $(($OPTIND-1))
  check_parameter_count_with_guessing $#
  name=$(get_or_guess_repository_name $1)

  # sanity checks
  check_autofs_on_cvmfs && die "Autofs on /cvmfs has to be disabled"
  check_repository_existence $name || die "The repository $name does not exist"
  load_repo_config $name
  is_owner_or_root $name || die "Permission denied: Repository $name is owned by $CVMFS_USER"
  is_stratum0 $name      || die "This is not a stratum 0 repository"
  ! is_publishing $name  || die "Repository is currently publishing"
  health_check     $name || die "Repository $name is not healthy"

  # check if repository is compatible to the installed CernVM-FS version
  check_repository_compatibility $name

  is_in_transaction $name && die "Cannot checkout while in a transaction"
  trap "close_transaction $name 0" EXIT HUP INT TERM
  open_transaction $name || die "Failed to open transaction for checkout"

  if [ "x$branch_name" != "x" ]; then
    is_valid_branch_name "$branch_name" || die "invalid branch name: $branch_name"
    branch_head=$(get_head_of $name "$branch_name")
  fi

  if [ "x$tag_name" = "x" ]; then
    if [ "x$branch_name" = "x" ]; then
      # Reset to trunk
      set_ro_root_hash $name "$(get_published_root_hash $name)" || die "failed to update root hash"
      rm -f /var/spool/cvmfs/${name}/checkout
      echo "Reset to trunk on default branch"
      return 0
    fi
    # Checkout head of existing branch
    [ "x$branch_head" != "x" ] || die "branch $branch_name does not exist"
    tag_name=$(echo $branch_head | cut -d" " -f1)
    tag_hash=$(echo $branch_head | cut -d" " -f2)
  else
    # checkout into new branch
    [ "x$branch_name" = "x" ] && die "missing branch name"
    [ "x$branch_head" != "x" ] && die "branch $branch_name already exists"
    tag_hash=$(get_tag_hash $name "$tag_name")
    [ "x$tag_hash" != "x" ] || die "tag $tag_name does not exist"
  fi
  local tag_branch=$(get_tag_branch $name "$tag_name")

  set_ro_root_hash $name $tag_hash || die "failed to update root hash"
  echo "$tag_name $tag_hash $branch_name $tag_branch" > /var/spool/cvmfs/${name}/checkout
  if [ x"$(whoami)" = x"$CVMFS_USER" ]; then
    chown $CVMFS_USER /var/spool/cvmfs/${name}/checkout
  fi
  local report_branch="on branch"
  [ "x$branch_head" = "x" ] && report_branch="onto new branch"
  echo "Checked out tag $tag_name ($tag_hash) $report_branch $branch_name"
}
#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server tag" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh

cvmfs_server_tag() {
  local name
  local tag_name=""
  local action_add=0
  local add_tag_description
  local add_tag_root_hash
  local action_remove=0
  local tag_names=""
  local remove_tag_force=0
  local action_inspect=0
  local action_list_tags=0
  local action_list_branches=0
  local machine_readable=0
  local silence_warnings=0

  # optional parameter handling
  OPTIND=1
  while getopts "a:c:m:h:r:fblxi:" option
  do
    case $option in
      a)
        tag_name="$OPTARG"
        action_add=1
      ;;
      m)
        add_tag_description="$OPTARG"
        ;;
      h)
        add_tag_root_hash=$OPTARG
        ;;
      r)
        [ -z "$tag_names" ]      \
          && tag_names="$OPTARG" \
          || tag_names="$tag_names $OPTARG"
        action_remove=1
        ;;
      f)
        remove_tag_force=1
        ;;
      l)
        action_list_tags=1
        ;;
      b)
        action_list_branches=1
        ;;
      x)
        machine_readable=1
        silence_warnings=1
        ;;
      i)
        tag_name="$OPTARG"
        action_inspect=1
        ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command tag: Unrecognized option: $1"
      ;;
    esac
  done

  # get repository name
  shift $(($OPTIND-1))
  check_parameter_count_with_guessing $#
  name=$(get_or_guess_repository_name $1)

  # check for ambiguous action requests
  local actions=$(( $action_remove+$action_list_tags+$action_list_branches+$action_add+$action_inspect ))
  [ $actions -gt 0 ] || { action_list_tags=1; actions=$(( $actions + 1 )); } # listing is the default action
  [ $actions -eq 1 ] || die "Ambiguous parameters. Please either add, remove, inspect or list tags."

  # sanity checks
  check_repository_existence $name || die "The repository $name does not exist"
  load_repo_config $name
  is_owner_or_root $name           || die "Permission denied: Repository $name is owned by $CVMFS_USER"
  is_stratum0 $name                || die "This is not a stratum 0 repository"
  ! is_publishing $name            || die "Repository is currently publishing"
  health_check -r $name

  local base_hash="$(get_mounted_root_hash $name)"
  local user_shell="$(get_user_shell $name)"
  local hash_algorithm="${CVMFS_HASH_ALGORITHM-sha1}"

  # listing does not need an open repository transaction
  if [ $action_list_tags -eq 1 ] || [ $action_list_branches -eq 1 ] || [ $actions -eq 0 ]; then
    local tag_list_command="$(__swissknife_cmd dbg) tag_list \
      -w $CVMFS_STRATUM0                                     \
      $(get_swissknife_proxy)                                \
      -t ${CVMFS_SPOOL_DIR}/tmp                              \
      -p /etc/cvmfs/keys/${name}.pub                         \
      -f $name"
    if [ $machine_readable -ne 0 ]; then
      tag_list_command="$tag_list_command -x"
    fi
    if [ $action_list_branches -eq 1 ]; then
      tag_list_command="$tag_list_command -B"
    fi
    $user_shell "$tag_list_command"
    return $?
  fi

  # tag inspection does not need to open a repository transaction
  if [ $action_inspect -eq 1 ]; then
    local tag_inspect_command="$(__swissknife_cmd dbg) tag_info \
      -w $CVMFS_STRATUM0                                        \
      $(get_swissknife_proxy)                                   \
      -t ${CVMFS_SPOOL_DIR}/tmp                                 \
      -p /etc/cvmfs/keys/${name}.pub                            \
      -f $name                                                  \
      -n $tag_name"
    if [ $machine_readable -ne 0 ]; then
      tag_inspect_command="$tag_inspect_command -x"
    fi
    $user_shell "$tag_inspect_command"
    return $?
  fi

  # all following commands need an open repository transaction and are supposed
  # to commit or abort it after performing a tag database manipulation. Hence,
  # they also need to performed by the repository owner or root
  [ ! -z "$tag_name" -o ! -z "$tag_names" ] || die "Tag name missing"
  echo "$tag_name" | grep -q -v " "         || die "Spaces are not allowed in tag names"

  is_checked_out $name && die "Cannot modify tags when checked out on another branch"
  is_in_transaction $name && die "Cannot change repository tags while in a transaction"
  trap "close_transaction $name 0" EXIT HUP INT TERM
  open_transaction $name || die "Failed to open transaction for tag manipulation"

  local log_level=
  [ "x$CVMFS_LOG_LEVEL" != x ] && log_level="-z $CVMFS_LOG_LEVEL"
  local new_manifest="${CVMFS_SPOOL_DIR}/tmp/manifest"
  local sync_command_virtual_dir="$(__swissknife_cmd dbg) sync \
      -u /cvmfs/$name                                    \
      -s ${CVMFS_SPOOL_DIR}/scratch/current              \
      -c ${CVMFS_SPOOL_DIR}/rdonly                       \
      -t ${CVMFS_SPOOL_DIR}/tmp                          \
      -b $base_hash                                      \
      -r $CVMFS_UPSTREAM_STORAGE                         \
      -w $CVMFS_STRATUM0                                 \
      -o ${new_manifest}~                                \
      -e $hash_algorithm                                 \
      -Z ${CVMFS_COMPRESSION_ALGORITHM-default}          \
      -N $name                                           \
      -K $CVMFS_PUBLIC_KEY                               \
      $(get_swissknife_proxy)                            \
      $(get_follow_http_redirects_flag) $log_level -S snapshots"
  local tag_command_undo_tags="$(__swissknife_cmd dbg) tag_edit \
      -r $CVMFS_UPSTREAM_STORAGE                        \
      -w $CVMFS_STRATUM0                                \
      -t ${CVMFS_SPOOL_DIR}/tmp                         \
      -m ${new_manifest}~                               \
      -p /etc/cvmfs/keys/${name}.pub                    \
      -f $name                                          \
      -e $hash_algorithm                                \
      $(get_swissknife_proxy)                           \
      $(get_follow_http_redirects_flag)                 \
      -x"

  # adds (or moves) a tag in the database
  if [ $action_add -eq 1 ]; then
    local tag_create_command="$(__swissknife_cmd dbg) tag_edit   \
      -w $CVMFS_STRATUM0                                         \
      -t ${CVMFS_SPOOL_DIR}/tmp                                  \
      -p /etc/cvmfs/keys/${name}.pub                             \
      -f $name                                                   \
      -r $CVMFS_UPSTREAM_STORAGE                                 \
      -m $new_manifest                                           \
      -b $base_hash                                              \
      -e $hash_algorithm                                         \
      $(get_swissknife_proxy)                                    \
      $(get_follow_http_redirects_flag)                          \
      -a $tag_name"
    if [ ! -z "$add_tag_description" ]; then
      tag_create_command="$tag_create_command -D \"$add_tag_description\""
    fi
    if [ ! -z "$add_tag_root_hash" ]; then
      tag_create_command="$tag_create_command -h $add_tag_root_hash"
    fi
    $user_shell "$tag_create_command" || exit 1
    cp "$new_manifest" "${new_manifest}~"
    sign_manifest $name $new_manifest || die "Failed to sign repo"
  fi

  # removes one or more tags from the database
  if [ $action_remove -eq 1 ]; then
    if [ $remove_tag_force -eq 0 ]; then
      echo "You are about to remove these tags from $name:"
      for t in $tag_names; do echo "* $t"; done
      echo
      local reply
      read -p "Are you sure (y/N)? " reply
      if [ "$reply" != "y" ] && [ "$reply" != "Y" ]; then
        return 1
      fi
    fi

    $user_shell "$(__swissknife_cmd dbg) tag_edit        \
      -w $CVMFS_STRATUM0                                 \
      $(get_swissknife_proxy)                            \
      -t ${CVMFS_SPOOL_DIR}/tmp                          \
      -p /etc/cvmfs/keys/${name}.pub                     \
      -f $name                                           \
      -r $CVMFS_UPSTREAM_STORAGE                         \
      -m $new_manifest                                   \
      -b $base_hash                                      \
      -e $hash_algorithm                                 \
      -d '$tag_names'" || die "Did not remove anything"
    cp "$new_manifest" "${new_manifest}~"
    sign_manifest $name $new_manifest || die "Failed to sign repo"
  fi

  if [ "x${CVMFS_VIRTUAL_DIR}" = "xtrue" ]; then
    $user_shell "$sync_command_virtual_dir" || die "Failed to create virtual catalog"
    local trunk_hash=$(grep "^C" ${new_manifest}~ | tr -d C)
    tag_command_undo_tags="$tag_command_undo_tags -b $trunk_hash"
    $user_shell "$tag_command_undo_tags" || die "Failed to set trunk hash"
    sign_manifest $name ${new_manifest}~ || die "Failed to sign repo"
    set_ro_root_hash $name $trunk_hash   || die "Root hash update failed"
  fi
  rm -f ${new_manifest}~
}


#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of deprecated cvmfs_server commands

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh
# - cvmfs_server_ssl.sh
# - cvmfs_server_tag.sh


cvmfs_server_lstags() {
  cvmfs_server_tag -l "$@" # backward compatibility alias
  echo "NOTE: cvmfs_server lstags is deprecated! Use cvmfs_server tag instead" 1>&2
}

cvmfs_server_list_tags() {
  cvmfs_server_tag -l "$@" # backward compatibility alias
  echo "NOTE: cvmfs_server list-tags is deprecated! Use cvmfs_server tag instead" 1>&2
}
#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server check" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh


__do_check() {
  local name
  local upstream
  local storage_dir
  local url

  # get repository name
  check_parameter_count_with_guessing $#
  name=$(get_or_guess_repository_name $1)

  # sanity checks
  check_repository_existence $name || die "The repository $name does not exist"

  # get repository information
  load_repo_config $name

  # more sanity checks
  is_owner_or_root $name || die "Permission denied: Repository $name is owned by $CVMFS_USER"
  health_check -r $name

  # check if repository is compatible to the installed CernVM-FS version
  check_repository_compatibility $name

  upstream=$CVMFS_UPSTREAM_STORAGE
  if is_stratum1 $name; then
    url=$CVMFS_STRATUM1
  else
    url=$CVMFS_STRATUM0
  fi

  if [ "x$tag" = "x" ] && is_garbage_collectable $name; then
    # acquire gc lock
    # waits for gc on the same repository to finish
    # and prevents a gc from starting
    acquire_gc_lock $name check || die "Failed to acquire gc lock for $name"
    trap "release_gc_lock $name" EXIT HUP INT TERM
  fi

  # do it!
  if [ $check_integrity -ne 0 ]; then
    if ! is_local_upstream $upstream; then
      echo "Storage integrity check only works locally. skipping."
    else
      echo
      echo "Checking storage integrity of $name ... (may take a while)"
      storage_dir=$(get_upstream_config $upstream)
      __swissknife scrub -r ${storage_dir}/data || die "FAIL!"
    fi
  fi

  local log_level_param=""
  local check_chunks_param=""
  [ "x$CVMFS_LOG_LEVEL" != x ] && log_level_param="-l $CVMFS_LOG_LEVEL"
  [ $check_chunks -ne 0 ]      && check_chunks_param="-c"

  local subtree_msg=""
  local subtree_param=""
  if [ "x$subtree_path" != "x" ]; then
    subtree_param="-s '$subtree_path'"
    subtree_msg=" (starting at nested catalog '$subtree_path')"
  fi

  echo "Verifying integrity of ${name}${subtree_msg}..."
  if [ $repair_reflog -eq 1 ]; then
    __check_repair_reflog $name
  fi
  local with_reflog=
  has_reflog_checksum $name && with_reflog="-R $(get_reflog_checksum $name)"

  default_scratch_dir="${CVMFS_SPOOL_DIR}/tmp"
  if [ -z "$scratch_dir" ]; then
    scratch_dir=$default_scratch_dir
  fi

  local user_shell="$(get_user_shell $name)"
  local check_cmd
  check_cmd="$(__swissknife_cmd dbg) check $tag        \
                     $check_chunks_param               \
                     $log_level_param                  \
                     $subtree_param                    \
                     -r $url                           \
                     -t ${scratch_dir}                 \
                     -k ${CVMFS_PUBLIC_KEY}            \
                     -N ${CVMFS_REPOSITORY_NAME}       \
                     $(get_swissknife_proxy)           \
                     $(get_follow_http_redirects_flag) \
                     $with_reflog"
  $user_shell "$check_cmd"
}

# Checks for mismatch between the reflog and the checksum and tries to fix them,
# either by adjusting the checksum or by removing it.
__check_repair_reflog() {
  local name="$1"
  load_repo_config $name
  local user_shell="$(get_user_shell $name)"

  local stored_checksum=
  has_reflog_checksum $name && stored_checksum="$(cat $(get_reflog_checksum $name))"

  local repository_url=
  if is_stratum0 $name; then
    repository_url="$CVMFS_STRATUM0"
  else
    repository_url="$CVMFS_STRATUM1"
  fi

  local has_reflog=0
  local computed_checksum=
  if $user_shell "$(__swissknife_cmd) peek -d .cvmfsreflog -r $CVMFS_UPSTREAM_STORAGE" >/dev/null; then
    has_reflog=1
    local url="$repository_url/.cvmfsreflog"
    local rehash_cmd="curl -sS --fail --connect-timeout 10 --max-time 300 $(get_curl_proxy) $url \
      | cvmfs_publish hash -a ${CVMFS_HASH_ALGORITHM:-sha1}"
    computed_checksum="$($user_shell "$rehash_cmd")"
    echo "Info: found $url with content hash $computed_checksum"
  fi

  if has_reflog_checksum $name; then
    if [ $has_reflog -eq 0 ]; then
      $user_shell "rm -f $(get_reflog_checksum $name)"
      echo "Warning: removed dangling reflog checksum $(get_reflog_checksum $name)"
    else
      if [ "x$stored_checksum" != "x$computed_checksum" ]; then
        $user_shell "echo $computed_checksum > $(get_reflog_checksum $name)"
        echo "Warning: restored reflog checksum as $computed_checksum (was: $stored_checksum)"
      fi
    fi
  else
    # No checksum
    if [ $has_reflog -eq 1 ]; then
      $user_shell "echo $computed_checksum > $(get_reflog_checksum $name)"
      echo "Warning: re-created missing reflog checksum as $computed_checksum"
    fi
  fi

  # At this point we either have no .cvmfsreflog and no local reflog.chksum or
  # we have both files properly in sync.

  # Remaining case: a reflog is registered in the manifest but the
  # .cvmfsreflog file is missing.  In this case, we recreate the reflog.

  if [ $has_reflog -eq 0 ] && get_repo_info -R | grep -q ^Y; then
    echo "Warning: a reflog hash is registered in the manifest, re-creating missing reflog"
    to_syslog_for_repo $name "reference log reconstruction started"
    local repository_url

    default_scratch_dir="${CVMFS_SPOOL_DIR}/tmp"
    if [ -z "$scratch_dir" ]; then
      scratch_dir=$default_scratch_dir
    fi

    local reflog_reconstruct_command="$(__swissknife_cmd dbg) reconstruct_reflog \
                                                  -r $repository_url             \
                                                  $(get_swissknife_proxy)        \
                                                  -u $CVMFS_UPSTREAM_STORAGE     \
                                                  -n $CVMFS_REPOSITORY_NAME      \
                                                  -t ${scratch_dir}              \
                                                  -k $CVMFS_PUBLIC_KEY           \
                                                  -R $(get_reflog_checksum $name)"
    if ! $user_shell "$reflog_reconstruct_command"; then
      to_syslog_for_repo $name "failed to reconstruction reference log"
    else
      to_syslog_for_repo $name "successfully reconstructed reference log"
    fi
  fi
}

# This is a separate function because dash segfaults if it is inline :-(
__get_checks_repo_times() {
  set -- '*'
  check_parameter_count_for_multiple_repositories $#
  names=$(get_or_guess_multiple_repository_names "$@")
  check_multiple_repository_existence "$names"

  for name in $names; do
    # note that is_inactive_replica also does load_repo_config
    if is_inactive_replica $name; then
      continue
    fi

    local upstream=$CVMFS_UPSTREAM_STORAGE
    if [ x$(get_upstream_type $upstream_storage) = "xgw" ]; then
      continue
    fi

    local check_status="$(read_repo_item $name .cvmfs_status.json)"
    local last_check="$(get_json_field "$check_status" last_check)"
    local check_time=0
    if [ -n "$last_check" ]; then
      check_time="$(date --date "$last_check" +%s)"
      local min_secs num_secs
      min_secs="$((${CVMFS_CHECK_ALL_MIN_DAYS:-30}*60*60*24))"
      num_secs="$(($(date +%s)-$check_time))"
      if [ "$num_secs" -lt "$min_secs" ]; then
        # less than $CVMFS_CHECK_ALL_MIN_DAYS has elapsed since last check
        continue
      fi
    fi

    echo "${check_time}:${name}"
  done
}

__do_all_checks() {
  local log
  local repo
  local repos

  if [ ! -d /var/log/cvmfs ]; then
    if ! mkdir /var/log/cvmfs 2>/dev/null; then
      die "/var/log/cvmfs does not exist and could not create it"
    fi
  fi
  [ -w /var/log/cvmfs ] || die "cannot write to /var/log/cvmfs"

  # This check is for backward compatibility, can eventually be removed
  local old_check_all_lock=/var/spool/cvmfs/is_checking_all
  if [ -f $old_check_all_lock ]; then
    to_syslog "skipping starting cvmfs_server check -a because old $old_check_all_lock still exists"
    return 1
  fi

  # Use /dev/shm for the lock because it is world-writable and goes away
  # after reboot
  local check_all_lock=/dev/shm/cvmfs_is_checking_all
  if ! acquire_lock $check_all_lock; then
    to_syslog "skipping starting cvmfs_server check -a because $check_all_lock held by active process"
    return 1
  fi

  log=/var/log/cvmfs/checks.log

  # Sort the active repositories on local storage by last check time
  repos="$(__get_checks_repo_times|sort -n|cut -d: -f2)"

  for repo in $repos; do
    (
    to_syslog_for_repo $repo "started check"
    echo
    echo "Starting $repo at `date`"
    # Work around the errexit (that is, set -e) misfeature of being
    #  disabled whenever the exit code is to be checked.
    # See https://lists.gnu.org/archive/html/bug-bash/2012-12/msg00093.html
    set +e
    (set -e
    __do_check $repo
    )
    local ret=$?
    update_repo_status $repo last_check "`date --utc`"
    local check_status
    if [ $ret != 0 ]; then
      check_status=failed
      to_syslog_for_repo $repo "check failed"
      echo "ERROR from cvmfs_server check!" >&2
    else
      check_status=succeeded
      to_syslog_for_repo $repo "successfully completed check"
    fi
    update_repo_status $repo check_status $check_status
    echo "Finished $repo at `date`"
    ) >> $log 2>&1

  done

  release_lock $check_all_lock
}

cvmfs_server_check() {
  local retcode=0
  local do_all=0
  local check_chunks=1
  local check_integrity=0
  local subtree_path=""
  local tag=
  local repair_reflog=0
  local scratch_dir=""

  # optional parameter handling
  OPTIND=1
  while getopts "acit:s:rx:" option
  do
    case $option in
      a)
        do_all=1
      ;;
      c)
        check_chunks=0
      ;;
      i)
        check_integrity=1
      ;;
      t)
        tag="-n $OPTARG"
      ;;
      s)
        subtree_path="$OPTARG"
      ;;
      r)
        repair_reflog=1
      ;;
      x)
        scratch_dir="$OPTARG"
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command check: Unrecognized option: $1"
      ;;
    esac
  done
  shift $(($OPTIND-1))

  if [ $do_all -eq 1 ]; then
    [ $# -eq 0 ] || die "no non-option parameters expected with -a"

    __do_all_checks

    # Always return success because this is used from cron and we
    #  don't want cron sending an email every time something fails.
    # Errors will be in the log.

  else
    if [ x"$CVMFS_LOG_LEVEL" = x ]; then
      # increase log from default "Warning" to "Info" level
      CVMFS_LOG_LEVEL=2 __do_check "$@"
    else
      __do_check "$@"
    fi
    retcode=$?
    if [ $retcode = 0 ]; then
      # Intentionally do not store the status for a failure when a check
      # is individually run, but do store the status for a success if the
      # status was previously saved.
      local name=$(get_or_guess_repository_name $1)
      local check_status="$(read_repo_item "$name" .cvmfs_status.json)"
      local last_check="$(get_json_field "$check_status" last_check)"
      if [ -n "$last_check" ]; then
        update_repo_status $name last_check "`date --utc`"
        update_repo_status $name check_status succeeded
      fi
    fi
  fi

  return $retcode
}
#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server list" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh
# - cvmfs_server_ssl.sh


cvmfs_server_list() {
  for repository in /etc/cvmfs/repositories.d/*; do
    if [ "x$repository" = "x/etc/cvmfs/repositories.d/*" ]; then
      return 0
    fi
    if cvmfs_sys_file_is_regular $repository ; then
      echo "Warning: unexpected file '$repository' in directory /etc/cvmfs/repositories.d/"
      continue
    fi
    local name=$(basename $repository)
    load_repo_config $name

    # figure out the schema version of the repository
    local version_info=""
    local creator_version=$(repository_creator_version $name)
    local version_info=""
    if ! check_repository_compatibility $name "nokill"; then
      version_info="(created with INCOMPATIBLE layout revision $(mangle_version_string $creator_version))"
    fi

    # collect additional information about aliased stratum1 repos
    local stratum1_info=""
    if is_stratum1 $name; then
      if [ "$CVMFS_REPOSITORY_NAME" != "$name" ]; then
        stratum1_info="-> $CVMFS_REPOSITORY_NAME"
      fi
    fi

    # find out if the repository is currently in a transaction
    local transaction_info=""
    if is_stratum0 $name && is_in_transaction $name; then
      transaction_info=" - in transaction"
    fi

    # check if the repository whitelist is accessible and expired
    local whitelist_info=""
    if is_stratum0 $name; then
      local retval=0
      check_expiry $name $CVMFS_STRATUM0 2>/dev/null || retval=$?
      if [ $retval -eq 100 ]; then
        whitelist_info=" - whitelist unreachable"
      elif [ $retval -ne 0 ]; then
        whitelist_info=" - whitelist expired"
      fi
    fi

    # check if the repository is healthy
    local health_info=""
    if ! health_check -q $name; then
      health_info=" - unhealthy"
    fi

    # print the checked out tag and branch
    local checkout_info=""
    if is_checked_out $name; then
      local tag_name=$(get_checked_out_tag $name)
      local branch_name=$(get_checked_out_branch $name)
      checkout_info=" [checked out tag '$tag_name' on branch '$branch_name']"
    fi

    # get the storage type of the repository
    local storage_type=""
    storage_type=$(get_upstream_type $CVMFS_UPSTREAM_STORAGE)

    # print out repository information list
    echo "$name ($CVMFS_REPOSITORY_TYPE / $storage_type$transaction_info$whitelist_info$health_info$checkout_info) $stratum1_info $version_info"
    CVMFS_CREATOR_VERSION=""
  done
}


#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server rollback" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh
# - cvmfs_server_ssl.sh


cvmfs_server_rollback() {
  local name
  local user
  local spool_dir
  local stratum0
  local upstream
  local target_tag=""
  local undo_rollback=1
  local force=0

  # optional parameter handling
  OPTIND=1
  while getopts "t:f" option
  do
    case $option in
      t)
        target_tag=$OPTARG
        undo_rollback=0
      ;;
      f)
        force=1
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command rollback: Unrecognized option: $1"
      ;;
    esac
  done

  # get repository name
  shift $(($OPTIND-1))
  check_parameter_count_with_guessing $#
  name=$(get_or_guess_repository_name $1)

  # sanity checks
  check_repository_existence $name || die "The repository $name does not exist"
  is_stratum0 $name                || die "This is not a stratum 0 repository"
  is_publishing $name              && die "Repository $name is currently being published"
  is_checked_out $name             && die "Can't rollback when checked out on a branch"
  health_check -r $name

  # get repository information
  load_repo_config $name
  user=$CVMFS_USER
  spool_dir=$CVMFS_SPOOL_DIR
  stratum0=$CVMFS_STRATUM0
  upstream=$CVMFS_UPSTREAM_STORAGE

  # more sanity checks
  is_owner_or_root $name || die "Permission denied: Repository $name is owned by $user"
  check_repository_compatibility $name
  check_url "${CVMFS_STRATUM0}/.cvmfspublished" 20 || die "Repository unavailable under $CVMFS_STRATUM0"
  check_expiry $name $stratum0  || die "Repository whitelist is expired!"
  is_in_transaction $name && die "Cannot rollback a repository in a transaction"
  is_cwd_on_path "/cvmfs/$name" && die "Current working directory is in /cvmfs/$name.  Please release, e.g. by 'cd \$HOME'." || true

  if [ $undo_rollback -eq 1 ]; then
    if ! check_tag_existence $name "trunk-previous"; then
      die "More than one anonymous undo rollback is not supported. Please specify a tag name (-t)"
    fi
  elif ! check_tag_existence $name "$target_tag"; then
    die "Target tag '$target_tag' does not exist"
  fi

  if [ $force -ne 1 ]; then
    local reply
    if [ $undo_rollback -eq 1 ]; then
      read -p "You are about to UNDO your last published revision!  Are you sure (y/N)? " reply
    else
      read -p "You are about to ROLLBACK to $target_tag AS THE LATEST REVISION!  Are you sure (y/N)? " reply
    fi
    if [ "$reply" != "y" ] && [ "$reply" != "Y" ]; then
      return 1
    fi
  fi

  # prepare the shell commands
  local user_shell="$(get_user_shell $name)"
  local base_hash=$(get_mounted_root_hash $name)
  local hash_algorithm="${CVMFS_HASH_ALGORITHM-sha1}"

  local rollback_command="$(__swissknife_cmd dbg) tag_rollback \
    -w $stratum0                                               \
    $(get_swissknife_proxy)                                    \
    -t ${spool_dir}/tmp                                        \
    -p /etc/cvmfs/keys/${name}.pub                             \
    -f $name                                                   \
    -r $upstream                                               \
    -m ${spool_dir}/tmp/manifest                               \
    -b $base_hash                                              \
    -e $hash_algorithm"
  if [ ! -z "$target_tag" ]; then
    rollback_command="$rollback_command -n $target_tag"
  fi

  # do it!
  echo "Rolling back repository (leaving behind $base_hash)"
  trap "close_transaction $name 0" EXIT HUP INT TERM
  open_transaction $name || die "Failed to open transaction for rollback"

  $user_shell "$rollback_command" || die "Rollback failed\n\nExecuted Command:\n$rollback_command";

  local trunk_hash=$(grep "^C" ${spool_dir}/tmp/manifest | tr -d C)
  sign_manifest $name ${spool_dir}/tmp/manifest || die "Signing failed";
  set_ro_root_hash $name $trunk_hash

  echo "Flushing file system buffers"
  syncfs
}


#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server gc" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh


cvmfs_server_gc() {
  local names
  local list_deleted_objects=0
  local dry_run=0
  local preserve_revisions=-1
  local preserve_timestamp=0
  local timestamp_threshold=""
  local force=0
  local all_collect=0
  local all_collected=0
  local deletion_log=""
  local reconstruct_reflog="0"

  # optional parameter handling
  OPTIND=1
  while getopts "ldr:t:faAL:" option
  do
    case $option in
      l)
        list_deleted_objects=1
      ;;
      d)
        dry_run=1
      ;;
      r)
        preserve_revisions="$OPTARG"
      ;;
      t)
        timestamp_threshold="$OPTARG"
      ;;
      f)
        force=1
      ;;
      a)
        all_collect=1
        all_collected=1
      ;;
      A)
        all_collect=1
      ;;
      L)
        deletion_log="$OPTARG"
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command gc: Unrecognized option: $1"
      ;;
    esac
  done
  shift $(($OPTIND-1))

  # get repository names
  if [ $all_collect -ne 0 ] && [ -z "$@" ]; then
    set -- '*'
  fi
  check_parameter_count_for_multiple_repositories $#
  names=$(get_or_guess_multiple_repository_names "$@")
  check_multiple_repository_existence "$names"

  local gclog=/var/log/cvmfs/gc.log

  if [ $all_collect -ne 0 ]; then
    # reduce the names to those that are collectable
    local collectable_names
    for name in $names; do
      if ! is_inactive_replica $name && is_garbage_collectable $name; then
        collectable_names="$collectable_names $name"
      fi
    done
    # the echo gets rid of the leading blank
    names="`echo $collectable_names`"
    if [ -z "$names" ]; then
      die "There are no active garbage-collectable repositories"
    fi
    if [ $all_collected -ne 0 ]; then
      # further reduce the list to either stratum0s or replicas that have
      # been collected upstream after they were collected here
      collectable_names=""
      for name in $names; do
        if is_stratum0 $name || __was_garbage_collected_upstream $name; then
          collectable_names="$collectable_names $name"
        elif [ $dry_run -eq 0 ]; then
          # pretend that gc was done to keep monitors happy
          update_repo_status $name last_gc "`date --utc`"
        fi
      done
      names="`echo $collectable_names`"
      if [ -z "$names" ]; then
        echo "At `date` there are no garbage-collectable repositories that were collected upstream" >> $gclog
        exit
      fi
    fi
  fi

  # parse timestamp (if given)
  if [ ! -z "$timestamp_threshold"  ]; then
    preserve_timestamp="$(date --date "$timestamp_threshold" +%s 2>/dev/null)" || die "Cannot parse time stamp '$timestamp_threshold'"
  fi

  [ $preserve_revisions -ge 0 ] && [ $preserve_timestamp -gt 0 ] && die "Please specify either timestamp OR revision thresholds (-r and -t are mutual exclusive)"
  if [ $preserve_revisions -lt 0 ] && [ $preserve_timestamp -le 0 ]; then
    # neither revision nor timestamp threshold given... fallback to default
    preserve_timestamp="$(date --date '3 days ago' +%s 2>/dev/null)"
  fi

  for name in $names; do
    if ! has_reference_log $name; then
      reconstruct_reflog=1
    fi
  done

  # TODO: Once the gateway administration endpoint is in place (CVM-1685), it should be forbidden
  #       to run GC directly on the gateway
  # Check if the command is called on a repository gateway, and if so,
  # abort if there are any active leases
  if [ -x "/usr/libexec/cvmfs-gateway/scripts/get_leases.sh" ]; then
    for name in $names; do
      if [ x"$( /usr/libexec/cvmfs-gateway/scripts/get_leases.sh | grep $name )" != x"" ]; then
        echo "Active lease found for repository: $name. Aborting"
        return 1
      fi
    done
    # If cvmfs-gateway is running, turn it off for the duration of the GC
    if [ "x$(sudo /usr/libexec/cvmfs-gateway/scripts/run_cvmfs_gateway.sh status)" = "xpong" ]; then
      echo "Turning off cvmfs-gateway"
      if is_systemd; then
        sudo systemctl stop cvmfs-gateway
      else
        sudo service cvmfs-gateway stop
      fi
      trap __restore_cvmfs_gateway EXIT HUP INT TERM
    fi
  fi



  # sanity checks
  if [ $dry_run -ne 0 ] && [ $reconstruct_reflog -ne 0 ]; then
    die "Reflog reconstruction needed. Cannot do a dry-run."
  fi

  # safety user confirmation
  if [ $force -eq 0 ] && [ $dry_run -eq 0 ]; then
    echo "YOU ARE ABOUT TO DELETE DATA! Are you sure you want to do the following:"
  fi

  if [ $force -eq 0 ] || [ $all_collect -eq 0 ]; then
    local dry_run_msg="no"
    if [ $dry_run -eq 1 ]; then dry_run_msg="yes"; fi

    local reflog_reconstruct_msg="no"
    if [ $reconstruct_reflog -eq 1 ]; then reflog_reconstruct_msg="yes"; fi

    echo "Affected Repositories:         $names"
    echo "Dry Run (no actual deletion):  $dry_run_msg"
    echo "Needs Reflog reconstruction:   $reflog_reconstruct_msg"
    if [ $preserve_revisions -ge 0 ]; then
      echo "Preserved Legacy Revisions:    $preserve_revisions"
    fi
    if [ $preserve_timestamp -gt 0 ]; then
      echo "Preserve Revisions newer than: $(date -d@$preserve_timestamp +'%Y/%m/%d %H:%M:%S')"
    fi
    if [ $preserve_revisions -le 0 ] && [ $preserve_timestamp -le 0 ]; then
      echo "Only the latest revision will be preserved."
    fi
  fi

  if [ $force -eq 0 ]; then
    echo ""
    read -p "Please confirm this action (y/N)? " reply
    if [ "$reply" != "y" ] && [ "$reply" != "Y" ]; then
      return 1
    fi
  fi

  # Use /dev/shm for the lock file because it is world-writable and goes
  # away during reboot
  local gc_all_lock=/dev/shm/cvmfs_is_gcing_all
  if [ $all_collected -ne 0 ]; then
    if ! acquire_lock $gc_all_lock; then
      to_syslog "skipping starting cvmfs_server gc -a because $gc_all_lock held by active process"
      return 1
    fi
  fi

  for name in $names; do

    if [ $all_collect -eq 0 ]; then
      __do_gc_cmd "$name"                       \
                  "$dry_run"                    \
                  "$list_deleted_objects"       \
                  "$preserve_revisions"         \
                  "$preserve_timestamp"         \
                  "$deletion_log"
    else
      (
      echo
      echo "Starting $name at `date`"
      # Work around the errexit (that is, set -e) misfeature of being
      #  disabled whenever the exit code is to be checked.
      # See https://lists.gnu.org/archive/html/bug-bash/2012-12/msg00093.html
      set +e
      (set -e
      __do_gc_cmd "$name"                       \
                  "$dry_run"                    \
                  "$list_deleted_objects"       \
                  "$preserve_revisions"         \
                  "$preserve_timestamp"         \
                  "$deletion_log"
      )
      if [ $? != 0 ]; then
        echo "ERROR from cvmfs_server gc!" >&2
      fi
      echo "Finished $name at `date`"
      ) >> $gclog 2>&1

      # Always return success because this is used from cron and we
      #  don't want cron sending an email every time something fails.
      # Errors will be in the log.
    fi
  done

  if [ $all_collected -ne 0 ]; then
    release_lock $gc_all_lock
  fi
}

__restore_cvmfs_gateway() {
  echo "Restoring cvmfs-gateway"
  if is_systemd; then
    sudo systemctl start cvmfs-gateway
  else
    sudo service cvmfs-gateway start
  fi
}

# return true (0) if the upstream repo was garbage collected more recently
# than the local repo, otherwise return a positive number indicating which
# of the various ways it was not found to be more recent
__was_garbage_collected_upstream() {
  local name="$1"

  load_repo_config $name
  local upstreamstatus="$(get_url "${CVMFS_STRATUM0}/.cvmfs_status.json" 10 2>/dev/null)"
  if [ -z "$upstreamstatus" ]; then
    # status file not found upstream
    return 1
  fi
  local upstreamdate="$(get_json_field "$upstreamstatus" "last_gc")"
  if [ -z "$upstreamdate" ]; then
    # last_gc date not found in status file upstream
    return 2
  fi
  local localstatus="$(read_repo_item $name .cvmfs_status.json)"
  if [ -z "$localstatus" ]; then
    # status file not found locally
    return 0
  fi
  local localdate="$(get_json_field "$localstatus" "last_gc")"
  if [ -z "$localdate" ]; then
    # last_gc date not found locally
    return 0
  fi
  local upstreamepoch="$(date --date="$upstreamdate" +%s)"
  if [ -z "$upstreamepoch" ]; then
    # couldn't convert upstream last_gc date to epoch time
    return 3
  fi
  local localepoch="$(date --date="$localdate" +%s)"
  if [ -z "$localepoch" ]; then
    # couldn't convert local last_gc date to epoch time
    return 0
  fi
  if [ "$localepoch" -gt "$upstreamepoch" ]; then
    # local last_gc time is greater than upstream last_gc time
    return 4
  fi
  # local last_gc time is less than (or equal to) upstream last_gc time
  return 0
}

# this is used when gc is invoked from the cvmfs_server command line
__do_gc_cmd()
{
  local name="$1"
  local dry_run="$2"
  local list_deleted_objects="$3"
  local preserve_revisions="$4"
  local preserve_timestamp="$5"
  local deletion_log="$6"

  # leave extra layer of indent for now to better show diff with previous

    CVMFS_PASSTHROUGH=false
    load_repo_config $name

    # sanity checks
    check_repository_compatibility $name
    check_url "${CVMFS_STRATUM0}/.cvmfspublished" 20 || die "Repository unavailable under $CVMFS_STRATUM0"
    if [ x"$CVMFS_PASSTHROUGH" = x"true" ]; then
      echo "Repository $name is a pass-through repository, nothing to do"
      return 0
    fi
    if is_empty_repository $name; then
      echo "Repository $name is empty, nothing to do"
      return 0
    fi
    is_garbage_collectable $name || die "Garbage Collection is not enabled for $name"
    is_owner_or_root       $name || die "Permission denied: Repository $name is owned by $user"

    # figure out the URL of the repository
    local repository_url="$CVMFS_STRATUM0"
    if is_stratum1 $name; then
      [ ! -z $CVMFS_STRATUM1 ] || die "Missing CVMFS_STRATUM1 URL in server.conf"
      repository_url="$CVMFS_STRATUM1"
    fi

    # generate the garbage collection configuration
    local additional_switches="${CVMFS_SERVER_FLAGS}"
    [ $list_deleted_objects -ne 0 ] && additional_switches="$additional_switches -l"
    [ $dry_run              -ne 0 ] && additional_switches="$additional_switches -d"
    [ $preserve_revisions   -ge 0 ] && additional_switches="$additional_switches -h $preserve_revisions"
    [ $preserve_timestamp   -gt 0 ] && additional_switches="$additional_switches -z $preserve_timestamp"

    local trapcmd
    if [ $dry_run -eq 0 ]; then
      # if a check or other gc is in progress on this repo, abort
      acquire_gc_lock $name gc 1 || die "Failed to acquire gc lock for $name"
      trapcmd="release_gc_lock $name"
      trap "$trapcmd" EXIT HUP INT TERM
      if is_stratum0 $name; then
        is_in_transaction $name  && die "Cannot run garbage collection while in a transaction"
        trapcmd="close_transaction $name 0; $trapcmd"
        trap "$trapcmd" EXIT HUP INT TERM
        open_transaction $name   || die "Failed to open transaction for garbage collection"
      else
        # on stratum1
        # if an update is in progress, wait for it
        acquire_update_lock $name gc || die "Failed to acquire update lock for $name"
        trapcmd="release_update_lock $name; $trapcmd"
        trap "$trapcmd" EXIT HUP INT TERM
      fi
    fi

    local reconstruct_this_reflog=0
    if ! has_reference_log $name; then
      reconstruct_this_reflog=1
    fi

    # run the garbage collection
    local reflog_reconstruct_msg=""
    [ $reconstruct_this_reflog -ne 0 ] && reflog_reconstruct_msg="(reconstructing reference logs)"
    echo "Running Garbage Collection $reflog_reconstruct_msg"
    __run_gc "$name"                    \
             "$repository_url"          \
             "$dry_run"                 \
             "$deletion_log"            \
             "$reconstruct_this_reflog" \
             $additional_switches || die "Fail ($?)!"

    if [ $dry_run -eq 0 ]; then
      if is_stratum0 $name; then
        if [ "x$CVMFS_UPLOAD_STATS_PLOTS" = "xtrue" ]; then
          /usr/share/cvmfs-server/upload_stats_plots.sh $name
        fi
      fi
      # release lock(s) and close transaction
      eval $trapcmd
      trap - EXIT HUP INT TERM
    fi

    syncfs cautious
}

# this is used for both auto-gc (after publish or snapshot) and non-auto-gc
#   (when invoked from the cvmfs_server command line)
__run_gc() {
  local name="$1"
  local repository_url="$2"
  local dry_run="$3"
  local deletion_log="$4"
  local reconstruct_reflog="$5"
  shift 5
  local additional_switches="$*"

  load_repo_config $name

  # sanity checks
  is_garbage_collectable $name  || return 1
  [ x"$repository_url" != x"" ] || return 2
  if [ $dry_run -eq 0 ]; then
    is_in_transaction $name || is_stratum1 $name || return 3
  else
    [ $reconstruct_reflog -eq 0 ] || return 8
  fi

  if ! has_reference_log $name && [ $reconstruct_reflog -eq 0 ]; then
    return 9
  fi

  # handle a configured deletion log (manually passed log has precedence)
  if [ x"$deletion_log" != x"" ]; then
    additional_switches="$additional_switches -L $deletion_log"
  elif [ ! -z $CVMFS_GC_DELETION_LOG ]; then
    additional_switches="$additional_switches -L $CVMFS_GC_DELETION_LOG"
  fi

  if [ x"$CVMFS_UPLOAD_STATS_DB" = x"true" ]; then
    additional_switches="$additional_switches -I"
  fi

  # do it!
  local user_shell="$(get_user_shell $name)"

  if [ $reconstruct_reflog -ne 0 ]; then
    to_syslog_for_repo $name "reference log reconstruction started"
    local reflog_reconstruct_command="$(__swissknife_cmd dbg) reconstruct_reflog \
                                                  -r $repository_url             \
                                                  $(get_swissknife_proxy)        \
                                                  -u $CVMFS_UPSTREAM_STORAGE     \
                                                  -n $CVMFS_REPOSITORY_NAME      \
                                                  -t ${CVMFS_SPOOL_DIR}/tmp/     \
                                                  -k $CVMFS_PUBLIC_KEY           \
                                                  -R $(get_reflog_checksum $name)"
    if ! $user_shell "$reflog_reconstruct_command"; then
      to_syslog_for_repo $name "failed to reconstruction reference log"
    else
      to_syslog_for_repo $name "successfully reconstructed reference log"
    fi
  fi

  [ $dry_run -ne 0 ] || to_syslog_for_repo $name "started garbage collection"
  local gc_command="$(__swissknife_cmd dbg) gc                              \
                                            -r $repository_url              \
                                            $(get_swissknife_proxy)         \
                                            -u $CVMFS_UPSTREAM_STORAGE      \
                                            -n $CVMFS_REPOSITORY_NAME       \
                                            -k $CVMFS_PUBLIC_KEY            \
                                            -t ${CVMFS_SPOOL_DIR}/tmp/      \
                                            -R $(get_reflog_checksum $name) \
                                            $additional_switches"

  if ! $user_shell "$gc_command"; then
    [ $dry_run -ne 0 ] || to_syslog_for_repo $name "failed to garbage collect"
    return 6
  fi

  [ $dry_run -ne 0 ] || update_repo_status $name last_gc "`date --utc`"
  [ $dry_run -ne 0 ] || to_syslog_for_repo $name "successfully finished garbage collection"

  return 0
}


#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server snapshot" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh


__snapshot_cleanup() {
  local alias_name=$1

  load_repo_config $alias_name
  local user_shell="$(get_user_shell $alias_name)"
  $user_shell "$(__swissknife_cmd) remove     \
                 -r ${CVMFS_UPSTREAM_STORAGE} \
                 -o .cvmfs_is_snapshotting"       || echo "Warning: failed to remove .cvmfs_is_snapshotting"

  release_update_lock $alias_name
}

__snapshot_succeeded() {
  local alias_name=$1
  __snapshot_cleanup $alias_name
  to_syslog_for_repo $alias_name "successfully snapshotted from $CVMFS_STRATUM0"
}

__snapshot_failed() {
  local alias_name=$1
  __snapshot_cleanup $alias_name
  to_syslog_for_repo $alias_name "failed to snapshot from $CVMFS_STRATUM0"
}

__do_snapshot() {
  local alias_names="$1"
  local abort_on_conflict=$2
  local alias_name
  local name
  local user
  local spool_dir
  local stratum0
  local upstream
  local num_workers
  local public_key
  local timeout
  local retries
  local retcode=0
  local gc_timespan=0

  for alias_name in $alias_names; do

    # sanity checks
    is_stratum1 $alias_name || { echo "Repository $alias_name is not a stratum 1 repository"; retcode=1; continue; }

    # get repository information
    CVMFS_PASSTHROUGH=false
    load_repo_config $alias_name
    name=$CVMFS_REPOSITORY_NAME
    user=$CVMFS_USER
    spool_dir=$CVMFS_SPOOL_DIR
    stratum0=$CVMFS_STRATUM0
    stratum1=$CVMFS_STRATUM1
    upstream=$CVMFS_UPSTREAM_STORAGE
    num_workers=$CVMFS_NUM_WORKERS
    public_key=$CVMFS_PUBLIC_KEY
    timeout=$CVMFS_HTTP_TIMEOUT
    retries=$CVMFS_HTTP_RETRIES

    # more sanity checks
    is_owner_or_root $alias_name || { echo "Permission denied: Repository $alias_name is owned by $user"; retcode=1; continue; }
    check_repository_compatibility $alias_name
    [ ! -z $stratum1 ] || die "Missing CVMFS_STRATUM1 URL in server.conf"
    gc_timespan="$(get_auto_garbage_collection_timespan $alias_name)" || { retcode=1; continue; }
    if is_local_upstream $upstream && is_root && check_apache; then
      # this might have been missed if add-replica -a was used or
      #  if a migrate was done while apache wasn't running, but then
      #  apache was enabled later
      # unfortunately we can only check it if snapshot is run as root...
      check_wsgi_module
    fi

    # do it!

    if is_local_upstream $upstream && [ x"$CVMFS_GEO_AUTO_UPDATE" = x"true" ]; then
        # try to update the geodb, but continue if it doesn't work
        _update_geodb -l || true
    fi

    if [ x"$CVMFS_PASSTHROUGH" = x"true" ]; then
      echo "Pass-through repository, skipping snapshot"
      continue
    fi

    if ! acquire_update_lock $alias_name snapshot $abort_on_conflict; then
      retcode=1
      continue
    fi

    local user_shell="$(get_user_shell $alias_name)"

    # here the lock is acquired and needs to be cleared in case of abort
    trap "__snapshot_failed $alias_name" EXIT HUP INT TERM
    to_syslog_for_repo $alias_name "started snapshotting from $stratum0"

    local initial_snapshot=0
    local initial_snapshot_flag=""
    if $user_shell "$(__swissknife_cmd) peek -d .cvmfs_last_snapshot -r ${upstream}" | grep -v -q "available"; then
      initial_snapshot=1
      initial_snapshot_flag="-i"
    fi

    local log_level=
    [ "x$CVMFS_LOG_LEVEL" != x ] && log_level="-l $CVMFS_LOG_LEVEL"
    if [ $initial_snapshot -eq 1 ]; then
      echo "Initial snapshot"
    fi

    # put a magic file in the repository root to signal a snapshot in progress
    local snapshotting_tmp="${spool_dir}/tmp/snapshotting"
    $user_shell "date --utc > $snapshotting_tmp"
    $user_shell "$(__swissknife_cmd) upload -r ${upstream} \
      -i $snapshotting_tmp                                 \
      -o .cvmfs_is_snapshotting"
    $user_shell "rm -f $snapshotting_tmp"

    # do the actual snapshot actions
    local with_history=""
    local with_reflog=""
    local timestamp_threshold=""
    [ $initial_snapshot -ne 1 ] && with_history="-p"
    [ $initial_snapshot -eq 1 ] && \
      with_reflog="-R $(get_reflog_checksum $alias_name)"
    has_reflog_checksum $alias_name && \
      with_reflog="-R $(get_reflog_checksum $alias_name)"
    is_stratum0_garbage_collectable $alias_name &&
      timestamp_threshold="-Z $gc_timespan"
    $user_shell "$(__swissknife_cmd dbg) pull -m $name \
        -u $stratum0                                   \
        -w $stratum1                                   \
        -r ${upstream}                                 \
        -x ${spool_dir}/tmp                            \
        -k $public_key                                 \
        -n $num_workers                                \
        -t $timeout                                    \
        -a $retries $with_history $with_reflog         \
           $initial_snapshot_flag $timestamp_threshold $log_level"

    update_repo_status $alias_name last_snapshot "`date --utc`"

    # this part is deprecated but keep for now for backward compatibility
    local last_snapshot_tmp="${spool_dir}/tmp/last_snapshot"
    $user_shell "date --utc > $last_snapshot_tmp"
    $user_shell "$(__swissknife_cmd) upload -r ${upstream} \
      -i $last_snapshot_tmp                                \
      -o .cvmfs_last_snapshot"
    $user_shell "rm -f $last_snapshot_tmp"
    syncfs cautious

    # run the automatic garbage collection (if configured)
    if is_due_auto_garbage_collection $alias_name; then
      echo "Running automatic garbage collection"
      local dry_run=0
      __run_gc "$alias_name" \
               "$stratum1"   \
               "$dry_run"    \
               ""            \
               "0"           \
               -z $gc_timespan || die "Garbage collection failed ($?)"
    fi

    # all done, clear the trap and run the cleanup manually
    trap - EXIT HUP INT TERM
    __snapshot_succeeded $alias_name

  done

  return $retcode
}

__do_all_snapshots() {
  local separate_logs=0
  local logrotate_nowarn=0
  local skip_noninitial=0
  local snapshot_group
  local log
  local fullog
  local repo
  local repos

  OPTIND=1
  while getopts "snig:" option; do
    case $option in
      s)
        separate_logs=1
      ;;
      n)
        logrotate_nowarn=1
      ;;
      i)
        skip_noninitial=1
      ;;
      g)
        snapshot_group=$OPTARG
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command snapshot -a: Unrecognized option: $1"
      ;;
    esac
  done
  shift $(($OPTIND-1))

  if [ ! -d /var/log/cvmfs ]; then
    if ! mkdir /var/log/cvmfs 2>/dev/null; then
      die "/var/log/cvmfs does not exist and could not create it"
    fi
  fi
  [ -w /var/log/cvmfs ] || die "cannot write to /var/log/cvmfs"

  local maxparallel="${CVMFS_MAX_PARALLEL_SNAPSHOTS:-`nproc`}"
  local fulllog=/var/log/cvmfs/snapshots.log

  # make locks in a tmpfs directory so they will go away after system crash
  local tmpdir=/dev/shm/cvmfs_snapshot_all
  if [ ! -d $tmpdir ]; then
    # This assumes that snapshot -a will only be run by a single user id
    # on a given machine.
    mkdir $tmpdir
  fi
  local locknum=0
  local lockfile=""
  while [ "$locknum" -lt "$maxparallel" ]; do
    lockfile=$tmpdir/$locknum
    if acquire_lock $lockfile; then
      break
    fi
    let locknum+=1
  done
  if [ "$locknum" -ge "$maxparallel" ]; then
    # Note that these messages will be the only things in $fullog if 
    # separate logs are being used.
    (echo; echo "Hit limit of $maxparallel parallel 'snapshot -a's at `date`, exiting") >>$fulllog
    exit
  fi

  if [ $separate_logs -eq 0 ]; then
    # write into a temporary file in case more than one is active at the
    #  same time
    log=$tmpdir/$locknum.log
    trap "release_lock $lockfile; rm -f $log" EXIT HUP INT TERM
    (echo; echo "Logging in $log at `date`") >>$fulllog
  else
    trap "release_lock $lockfile" EXIT HUP INT TERM
  fi

  # Sort the active repositories by last snapshot time when on local storage.
  # For non-local, swissknife only supports checking whether a file exists,
  #  so only check whether non-initial snapshots are being skipped.
  repos="$(for replica in /etc/cvmfs/repositories.d/*/replica.conf; do

    # get repository information
    local repodir="${replica%/*}"
    repo="${repodir##*/}"

    if [ "$repo" = "*" ]; then
      # no replica.conf files were found
      continue
    fi

    if is_inactive_replica $repo; then
      continue
    fi

    # unset this first, for backward compatibility with versions that
    #  did not set it
    unset CVMFS_SNAPSHOT_GROUP

    load_repo_config $repo

    if [ "x$CVMFS_SNAPSHOT_GROUP" != "x$snapshot_group" ]; then
      continue
    fi

    local upstream=$CVMFS_UPSTREAM_STORAGE
    local snapshot_time=0
    if is_local_upstream $upstream; then
      local storage_dir=$(get_upstream_config $upstream)
      local snapshot_file=$storage_dir/.cvmfs_last_snapshot
      if cvmfs_sys_file_is_regular $snapshot_file ; then
        snapshot_time="$(stat --format='%Y' $snapshot_file)"
      elif [ $skip_noninitial -eq 1 ]; then
        continue
      fi
    elif [ $skip_noninitial -eq 1 ]; then
      if $user_shell "$(__swissknife_cmd) peek -d .cvmfs_last_snapshot -r ${upstream}" | grep -v -q "available"; then
        continue
      fi
    fi

    echo "${snapshot_time}:${repo}"

  done|sort -n|cut -d: -f2)"

  for repo in $repos; do
    if [ $separate_logs -eq 1 ]; then
      log=/var/log/cvmfs/$repo.log
    fi

    (
    echo
    echo "Starting $repo at `date`"
    # Work around the errexit (that is, set -e) misfeature of being
    #  disabled whenever the exit code is to be checked.
    # See https://lists.gnu.org/archive/html/bug-bash/2012-12/msg00093.html
    set +e
    (set -e
    __do_snapshot $repo 1
    )
    if [ $? != 0 ]; then
      echo "ERROR from cvmfs_server snapshot!" >&2
    fi
    echo "Finished $repo at `date`"
    ) >> $log 2>&1

    if [ $separate_logs -eq 0 ]; then
      cat $log >>$fulllog
      > $log
    fi

  done
}

cvmfs_server_snapshot() {
  local alias_names
  local retcode=0
  local abort_on_conflict=0
  local do_all=0
  local allopts=""

  OPTIND=1
  while getopts "atsnig:" option; do
    case $option in
      a)
        do_all=1
      ;;
      s|n|i)
        allopts="$allopts -$option"
      ;;
      g)
        allopts="$allopts -g $OPTARG"
      ;;
      t)
        abort_on_conflict=1
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command snapshot: Unrecognized option: $1"
      ;;
    esac
  done
  shift $(($OPTIND-1))

  if [ $do_all -eq 1 ]; then
    [ $# -eq 0 ] || die "no non-option parameters expected with -a"

    # ignore if there's a -t option, it's always implied with -a

    __do_all_snapshots $allopts

    # always return success because this is used from cron and we
    #  don't want cron sending an email every time something fails
    # errors will be in the log

  else
    if [ -n "$allopts" ]; then
      usage "Command snapshot:$allopts unrecognized without -a"
    fi

    # get repository names
    check_parameter_count_for_multiple_repositories $#
    alias_names=$(get_or_guess_multiple_repository_names "$@")
    check_multiple_repository_existence "$alias_names"

    __do_snapshot "$alias_names" $abort_on_conflict
    retcode=$?
  fi

  return $retcode
}

#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server migrate" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh


_is_generated_apache_conf() {
  local apache_conf="$1"

  grep -q '^# Created by cvmfs_server.' $apache_conf 2>/dev/null
}

_migrate_2_1_6() {
  local name=$1
  local destination_version="2.1.7"

  # get repository information
  load_repo_config $name

  echo "Migrating repository '$name' from CernVM-FS $(mangle_version_string '2.1.6') to $(mangle_version_string '2.1.7')"

  echo "--> generating new upstream descriptor"
  # before 2.1.6 there were only local backends... no need to differentiate here
  local storage_path=$(echo $CVMFS_UPSTREAM_STORAGE | cut --delimiter=: --fields=2)
  local new_upstream="local,${storage_path}/data/txn,${storage_path}"

  echo "--> removing spooler pipes"
  local pipe_pathes="${CVMFS_SPOOL_DIR}/paths"
  local pipe_digests="${CVMFS_SPOOL_DIR}/digests"
  rm -f $pipe_pathes > /dev/null 2>&1 || echo "Warning: not able to delete $pipe_pathes"
  rm -f $pipe_digests > /dev/null 2>&1 || echo "Warning: not able to delete $pipe_digests"

  if is_stratum0 $name; then
    echo "--> create temp directory in upstream storage"
    local tmp_dir=${storage_path}/data/txn
    mkdir $tmp_dir > /dev/null 2>&1 || echo "Warning: not able to create $tmp_dir"
    chown -R $CVMFS_USER $tmp_dir > /dev/null 2>&1 || echo "Warning: not able to chown $tmp_dir to $CVMFS_USER"
    set_selinux_httpd_context_if_needed $tmp_dir || echo "Warning: not able to chcon $tmp_dir to httpd_sys_content_t"

    echo "--> updating server.conf"
    mv /etc/cvmfs/repositories.d/${name}/server.conf /etc/cvmfs/repositories.d/${name}/server.conf.old
    cat > /etc/cvmfs/repositories.d/${name}/server.conf << EOF
# created by cvmfs_server.
# migrated from version $(mangle_version_string "2.1.6").
CVMFS_CREATOR_VERSION=$destination_version
CVMFS_REPOSITORY_NAME=$CVMFS_REPOSITORY_NAME
CVMFS_REPOSITORY_TYPE=$CVMFS_REPOSITORY_TYPE
CVMFS_USER=$CVMFS_USER
CVMFS_UNION_DIR=$CVMFS_UNION_DIR
CVMFS_SPOOL_DIR=$CVMFS_SPOOL_DIR
CVMFS_STRATUM0=$CVMFS_STRATUM0
CVMFS_UPSTREAM_STORAGE=$new_upstream
CVMFS_GENERATE_LEGACY_BULK_CHUNKS=$CVMFS_DEFAULT_GENERATE_LEGACY_BULK_CHUNKS
CVMFS_USE_FILE_CHUNKING=$CVMFS_DEFAULT_USE_FILE_CHUNKING
CVMFS_MIN_CHUNK_SIZE=$CVMFS_DEFAULT_MIN_CHUNK_SIZE
CVMFS_AVG_CHUNK_SIZE=$CVMFS_DEFAULT_AVG_CHUNK_SIZE
CVMFS_MAX_CHUNK_SIZE=$CVMFS_DEFAULT_MAX_CHUNK_SIZE
EOF
  fi

  if is_stratum1 $name; then
    echo "--> updating server.conf"
    mv /etc/cvmfs/repositories.d/${name}/server.conf /etc/cvmfs/repositories.d/${name}/server.conf.old
    cat > /etc/cvmfs/repositories.d/${name}/server.conf << EOF
# Created by cvmfs_server.
# migrated from version $(mangle_version_string "2.1.6").
CVMFS_CREATOR_VERSION=$destination_version
CVMFS_REPOSITORY_NAME=$CVMFS_REPOSITORY_NAME
CVMFS_REPOSITORY_TYPE=$CVMFS_REPOSITORY_TYPE
CVMFS_USER=$CVMFS_USER
CVMFS_SPOOL_DIR=$CVMFS_SPOOL_DIR
CVMFS_STRATUM0=$CVMFS_STRATUM0
CVMFS_UPSTREAM_STORAGE=$new_upstream
EOF
  fi

  # reload repository information
  load_repo_config $name
}


_migrate_2_1_7() {
  local name=$1
  local destination_version="2.1.15"

  # get repository information
  load_repo_config $name
  local user_shell="$(get_user_shell $name)"

  echo "Migrating repository '$name' from CernVM-FS $CVMFS_CREATOR_VERSION to $(mangle_version_string $destination_version)"

  if [ ! -f ${CVMFS_SPOOL_DIR}/client.local ]; then
    echo "--> creating client.local"
    $user_shell "touch ${CVMFS_SPOOL_DIR}/client.local" || die "fail!"
  fi

  local server_conf="/etc/cvmfs/repositories.d/${name}/server.conf"
  if ! cat $server_conf | grep -q CVMFS_UNION_FS_TYPE; then
    echo "--> setting AUFS as used overlay file system"
    echo "CVMFS_UNION_FS_TYPE=aufs" >> $server_conf
  fi

  if ! grep client.local /etc/fstab | grep -q ${CVMFS_REPOSITORY_NAME}; then
    echo "--> adjusting /etc/fstab"
    sed -i -e "s|cvmfs2#${CVMFS_REPOSITORY_NAME} ${CVMFS_SPOOL_DIR}/rdonly fuse allow_other,config=/etc/cvmfs/repositories.d/${CVMFS_REPOSITORY_NAME}/client.conf,cvmfs_suid 0 0 # added by CernVM-FS for ${CVMFS_REPOSITORY_NAME}|cvmfs2#${CVMFS_REPOSITORY_NAME} ${CVMFS_SPOOL_DIR}/rdonly fuse allow_other,config=/etc/cvmfs/repositories.d/${CVMFS_REPOSITORY_NAME}/client.conf:${CVMFS_SPOOL_DIR}/client.local,cvmfs_suid 0 0 # added by CernVM-FS for ${CVMFS_REPOSITORY_NAME}|" /etc/fstab
    if ! grep client.local /etc/fstab | grep -q ${CVMFS_REPOSITORY_NAME}; then
      die "fail!"
    fi
  fi

  echo "--> analyzing file catalogs for additional statistics counters"
  local temp_dir="${CVMFS_SPOOL_DIR}/tmp"
  local new_manifest="${temp_dir}/new_manifest"

  __swissknife migrate                                 \
    -v "2.1.7"                                         \
    -r ${CVMFS_STRATUM0}                               \
    $(get_swissknife_proxy)                            \
    -n $name                                           \
    -u ${CVMFS_UPSTREAM_STORAGE}                       \
    -t $temp_dir                                       \
    -o $new_manifest                                   \
    -k /etc/cvmfs/keys/$name.pub                       \
    -s || die "fail! (migrating catalogs)"
  chown ${CVMFS_USER} $new_manifest

  # sign new (migrated) repository revision
  echo -n "Signing newly imported Repository... "
  create_whitelist $name ${CVMFS_USER} ${CVMFS_UPSTREAM_STORAGE} $temp_dir > /dev/null
  sign_manifest $name $new_manifest || die "fail! (cannot sign repo)"
  echo "done"

  echo "--> updating server.conf"
  sed -i -e "s/^CVMFS_CREATOR_VERSION=.*/CVMFS_CREATOR_VERSION=$destination_version/" /etc/cvmfs/repositories.d/$name/server.conf

  # reload (updated) repository information
  load_repo_config $name

  # update repository information
  echo "--> remounting (migrated) repository"
  local remote_hash
  remote_hash=$(get_published_root_hash $name)

  run_suid_helper rw_umount $name     > /dev/null 2>&1 || die "fail! (unmounting /cvmfs/$name)"
  run_suid_helper rdonly_umount $name > /dev/null 2>&1 || die "fail! (unmounting ${CVMFS_SPOOL_DIR}/rdonly)"
  set_ro_root_hash $name $remote_hash
  run_suid_helper rdonly_mount $name  > /dev/null 2>&1 || die "fail! (mounting ${CVMFS_SPOOL_DIR}/$name)"
  run_suid_helper rw_mount $name      > /dev/null 2>&1 || die "fail! (mounting /cvmfs/$name)"
}

# note that this is only run on stratum1s that have local upstream storage
_migrate_2_1_15() {
  local name=$1
  local destination_version="2.1.20"
  local conf_file
  conf_file="$(get_apache_conf_path)/$(get_apache_conf_filename $name)"

  # get repository information
  load_repo_config $name

  echo "Migrating repository '$name' from CernVM-FS $CVMFS_CREATOR_VERSION to $(mangle_version_string $destination_version)"

  if check_apache; then
    check_wsgi_module
    _update_geodb -l
  fi
  # else apache is currently stopped, add-replica may have been run with -a

  if cvmfs_sys_file_is_regular "$conf_file" ; then
    echo "--> updating $conf_file"
    (echo "# Created by cvmfs_server.  Don't touch."
     cat_wsgi_config $name
     sed '/^# Created by cvmfs_server/d' $conf_file) > $conf_file.NEW
    cat $conf_file.NEW >$conf_file
    rm -f $conf_file.NEW
    if check_apache; then
      # Need to restart, reload doesn't work at least for the first module;
      #  that results in repeated segmentation faults on RHEL5 & 6
      restart_apache
    fi
  else
    if check_apache; then
      echo "$conf_file does not exist."
      echo "Make sure the equivalent of the following is in the apache configuration:"
      echo ----------
    else
      echo "Apache is not enabled and $conf_file does not exist."
      echo "  If you do enable Apache, make sure the equivalent of the following is"
      echo "  in the apache configuration:"
    fi
    cat_wsgi_config $name
  fi

  echo "--> updating server.conf"
  local server_conf="/etc/cvmfs/repositories.d/${name}/server.conf"
  sed -i -e "s/^CVMFS_CREATOR_VERSION=.*/CVMFS_CREATOR_VERSION=$destination_version/" $server_conf
  echo "CVMFS_STRATUM1=$(mangle_local_cvmfs_url $name)" >> $server_conf

  # reload (updated) repository information
  load_repo_config $name
}

_migrate_2_1_20() {
  local name=$1
  local destination_version="2.2.0-1"
  local creator=$(repository_creator_version $name)
  local server_conf="/etc/cvmfs/repositories.d/${name}/server.conf"
  local apache_conf="$(get_apache_conf_path)/$(get_apache_conf_filename $name)"

  # get repository information
  load_repo_config $name

  echo "Migrating repository '$name' from CernVM-FS $(mangle_version_string $CVMFS_CREATOR_VERSION) to $(mangle_version_string $destination_version)"

  if is_stratum0 $name; then
    echo "--> updating client.conf"
    local client_conf="/etc/cvmfs/repositories.d/${name}/client.conf"
    [ -z "$CVMFS_HIDE_MAGIC_XATTRS" ] && echo "CVMFS_HIDE_MAGIC_XATTRS=yes" >> $client_conf
    [ -z "$CVMFS_FOLLOW_REDIRECTS"  ] && echo "CVMFS_FOLLOW_REDIRECTS=yes"  >> $client_conf
    [ -z "$CVMFS_SERVER_CACHE_MODE" ] && echo "CVMFS_SERVER_CACHE_MODE=yes" >> $client_conf
    [ -z "$CVMFS_MOUNT_DIR"         ] && echo "CVMFS_MOUNT_DIR=/cvmfs"      >> $client_conf

    echo "--> updating /etc/fstab"
    local tmp_fstab=$(mktemp)
    awk  "/added by CernVM-FS for $name\$/"' {
            for (i = 1; i <= NF; i++) {
              if (i == 4) $i = $i",noauto";
              printf("%s ", $i);
            }
            print "";
            next;
          };
          { print $0 }' /etc/fstab > $tmp_fstab
    cat $tmp_fstab > /etc/fstab
    rm -f $tmp_fstab

    echo "--> updating server.conf"
    if ! grep -q "CVMFS_AUTO_REPAIR_MOUNTPOINT" $server_conf; then
      echo "CVMFS_AUTO_REPAIR_MOUNTPOINT=true" >> $server_conf
    else
      sed -i -e "s/^\(CVMFS_AUTO_REPAIR_MOUNTPOINT\)=.*/\1=true/" $server_conf
    fi
  fi

  if is_local_upstream $CVMFS_UPSTREAM_STORAGE && cvmfs_sys_file_is_regular "$apache_conf" ; then
    echo "--> updating apache config ($(basename $apache_conf))"
    local storage_dir=$(get_upstream_config $CVMFS_UPSTREAM_STORAGE)
    local wsgi=""
    is_stratum1 $name && wsgi="enabled"
    create_apache_config_for_endpoint $name $storage_dir $wsgi
    reload_apache > /dev/null
  fi

  sed -i -e "s/^\(CVMFS_CREATOR_VERSION\)=.*/\1=$destination_version/" $server_conf

  # update repository information
  load_repo_config $name
}

_migrate_2_2() {
  local name=$1
  local destination_version="2.3.0-1"
  local server_conf="/etc/cvmfs/repositories.d/${name}/server.conf"

  # get repository information
  load_repo_config $name
  [ ! -z $CVMFS_SPOOL_DIR     ] || die "\$CVMFS_SPOOL_DIR is not set"
  [ ! -z $CVMFS_USER          ] || die "\$CVMFS_USER is not set"
  [ ! -z $CVMFS_UNION_FS_TYPE ] || die "\$CVMFS_UNION_FS_TYPE is not set"

  echo "Migrating repository '$name' from CernVM-FS $(mangle_version_string $CVMFS_CREATOR_VERSION) to $(mangle_version_string $destination_version)"

  echo "--> umount repository"
  run_suid_helper rw_umount $name

  echo "--> updating scratch directory layout"
  local scratch_dir="${CVMFS_SPOOL_DIR}/scratch"
  rm -fR   ${scratch_dir}
  mkdir -p ${scratch_dir}/current
  mkdir -p ${scratch_dir}/wastebin
  chown -R $CVMFS_USER ${scratch_dir}

  echo "--> updating /etc/fstab"
  local comment="added by CernVM-FS for ${name}"
  sed -i -e "s~^\(.*\)\(${scratch_dir}\)\(.*${comment}\)\s*$~\1\2/current\3~" /etc/fstab

  echo "--> remount repository"
  run_suid_helper rw_mount $name

  echo "--> updating server.conf"
  sed -i -e "s/^\(CVMFS_CREATOR_VERSION\)=.*/\1=$destination_version/" $server_conf

  echo "--> ensure binary permission settings"
  ensure_swissknife_suid $CVMFS_UNION_FS_TYPE

  # update repository information
  load_repo_config $name
}

_migrate_2_3_0() {
  local name=$1
  local destination_version="2.3.3-1"
  local server_conf="/etc/cvmfs/repositories.d/${name}/server.conf"

  load_repo_config $name
  echo "Migrating repository '$name' from CernVM-FS $(mangle_version_string $CVMFS_CREATOR_VERSION) to $(mangle_version_string $destination_version)"

  if ! has_global_info_path; then
    echo "--> create info resource (please update server info with 'cvmfs_server update-info')"
    create_global_info_skeleton || die "fail"
  fi

  if ! has_apache_config_for_global_info; then
    echo "--> create Apache configuration for info resource"
    create_apache_config_for_global_info || die "fail (create apache config)"
    reload_apache > /dev/null            || die "fail (reload apache)"
  fi

  echo "--> update global JSON information"
  update_global_repository_info || die "fail"

  echo "--> updating server.conf"
  sed -i -e "s/^\(CVMFS_CREATOR_VERSION\)=.*/\1=$destination_version/" $server_conf

  # update repository information
  load_repo_config $name
}

_migrate_138() {
  local name=$1
  local destination_version="138"
  local server_conf="/etc/cvmfs/repositories.d/${name}/server.conf"
  local apache_conf_path="$(get_apache_conf_path)"
  local apache_conf_file="$(get_apache_conf_filename $name)"
  local apache_repo_conf="$apache_conf_path/$apache_conf_file"
  local apache_info_conf="$apache_conf_path/$(get_apache_conf_filename "info")"
  local do_apache_reload=0

  load_repo_config $name
  echo "Migrating repository '$name' from layout revision $(mangle_version_string $CVMFS_CREATOR_VERSION) to revision $(mangle_version_string $destination_version)"

  if has_apache_config_file "$apache_conf_file"; then
    if _is_generated_apache_conf "$apache_repo_conf"; then
      echo "--> updating apache config ($(basename $apache_repo_conf))"
      local storage_dir=$(get_upstream_config $CVMFS_UPSTREAM_STORAGE)
      local wsgi=""
      is_stratum1 $name && wsgi="enabled"
      create_apache_config_for_endpoint $name $storage_dir $wsgi
      do_apache_reload=1
    else
      echo "--> skipping foreign apache config ($(basename $apache_repo_conf))"
    fi
  fi

  if has_apache_config_for_global_info; then
    if _is_generated_apache_conf "$apache_info_conf"; then
      echo "--> updating apache info config"
      local storage_dir="${DEFAULT_LOCAL_STORAGE}/info"
      create_apache_config_for_endpoint "info" "$storage_dir"
      do_apache_reload=1
    else
      echo "--> skipping foreign apache info config"
    fi
  fi

  if [ $do_apache_reload -eq 1 ]; then
    echo "--> reloading Apache"
    reload_apache > /dev/null
  fi

  local warn_threshold="`sed -n -e 's/^CVMFS_CATALOG_ENTRY_WARN_THRESHOLD=//p' $server_conf`"
  if [ -n "$warn_threshold" ]; then
    echo "--> updating server.conf"
    sed -i -e '/^CVMFS_CATALOG_ENTRY_WARN_THRESHOLD=/d' $server_conf
    local kcatalog_limit="$(($warn_threshold / 1000))"
    # If the default was not changed, the new root catalog default of 200k
    # entries is applied.
    if [ "$kcatalog_limit" != 500 ]; then
      echo "CVMFS_ROOT_KCATALOG_LIMIT=${kcatalog_limit}" >> $server_conf
      echo "CVMFS_NESTED_KCATALOG_LIMIT=${kcatalog_limit}" >> $server_conf
    fi
  fi

  echo "--> updating server.conf"
  sed -i -e "s/^\(CVMFS_CREATOR_VERSION\)=.*/\1=$destination_version/" $server_conf

  # update repository information
  load_repo_config $name
}

_migrate_139() {
  local name=$1
  local destination_version="139"
  local server_conf="/etc/cvmfs/repositories.d/${name}/server.conf"

  load_repo_config $name
  echo "Migrating repository '$name' from layout revision $(mangle_version_string $CVMFS_CREATOR_VERSION) to revision $(mangle_version_string $destination_version)"

  if is_stratum0 $name; then
    echo "--> adjusting /etc/fstab"
    sed -i -e "s|\(.*\),noauto\(.*# added by CernVM-FS for ${CVMFS_REPOSITORY_NAME}\)|\1,noauto,nodev\2|" /etc/fstab

    # Make sure the systemd mount unit exists
    if is_systemd; then
      /usr/lib/systemd/system-generators/systemd-fstab-generator \
        /run/systemd/generator '' '' 2>/dev/null || true
      systemctl daemon-reload
    fi
  fi

  echo "--> updating server.conf"
  sed -i -e "s/^\(CVMFS_CREATOR_VERSION\)=.*/\1=$destination_version/" $server_conf

  # update repository information
  load_repo_config $name
}

_migrate_140() {
  local name=$1
  local destination_version="140"
  local server_conf="/etc/cvmfs/repositories.d/${name}/server.conf"
  local apache_repo_conf="$(get_apache_conf_path)/$(get_apache_conf_filename $name)"

  load_repo_config $name
  echo "Migrating repository '$name' from layout revision $(mangle_version_string $CVMFS_CREATOR_VERSION) to revision $(mangle_version_string $destination_version)"

  # only called when has apache config
  if _is_generated_apache_conf "$apache_repo_conf"; then
    echo "--> updating apache config ($(basename $apache_repo_conf))"
    local storage_dir=$(get_upstream_config $CVMFS_UPSTREAM_STORAGE)
    # only called on stratum1
    local wsgi="enabled"
    create_apache_config_for_endpoint $name $storage_dir $wsgi
    echo "--> reloading Apache"
    reload_apache > /dev/null
  else
    echo "--> skipping foreign apache config ($(basename $apache_repo_conf))"
  fi

  echo "--> updating server.conf"
  sed -i -e "s/^\(CVMFS_CREATOR_VERSION\)=.*/\1=$destination_version/" $server_conf

  # update repository information
  load_repo_config $name
}


_migrate_141() {
  local name=$1
  local destination_version="141"
  local server_conf="/etc/cvmfs/repositories.d/${name}/server.conf"
  local client_conf="/etc/cvmfs/repositories.d/${name}/client.conf"

  load_repo_config $name
  echo "Migrating repository '$name' from layout revision $(mangle_version_string $CVMFS_CREATOR_VERSION) to revision $(mangle_version_string $destination_version)"

  # only called when this is a stratum 0
  if [ -f $client_conf ]; then
    echo "--> updating client.conf"
    if ! grep -q "CVMFS_NFILES" $client_conf; then
      echo "CVMFS_NFILES=65536" >> $client_conf
    else
      sed -i -e "s/^\(CVMFS_NFILES\)=.*/\1=65536/" $client_conf
    fi
  else
    echo "--> skipping client configuration on stratum 1"
  fi

  echo "--> updating server.conf"
  sed -i -e "s/^\(CVMFS_CREATOR_VERSION\)=.*/\1=$destination_version/" $server_conf

  # update repository information
  load_repo_config $name
}


_migrate_142() {
  local name=$1
  local destination_version="142"
  local server_conf="/etc/cvmfs/repositories.d/${name}/server.conf"
  local client_conf="/etc/cvmfs/repositories.d/${name}/client.conf"

  load_repo_config $name
  echo "Migrating repository '$name' from layout revision $(mangle_version_string $CVMFS_CREATOR_VERSION) to revision $(mangle_version_string $destination_version)"

  # only called when this is a stratum 0
  if [ -f $client_conf ]; then
    echo "--> updating client.conf"
    if ! grep -q "CVMFS_TALK_SOCKET" $client_conf; then
      echo "CVMFS_TALK_SOCKET=/var/spool/cvmfs/${name}/cvmfs_io" >> $client_conf
    fi
    if ! grep -q "CVMFS_TALK_OWNER" $client_conf; then
      local cvmfs_user="$(grep ^CVMFS_USER= $server_conf | cut -d= -f2)"
      echo "CVMFS_TALK_OWNER=$cvmfs_user" >> $client_conf
    fi
  else
    echo "--> skipping client configuration on stratum 1"
  fi

  echo "--> updating server.conf"
  if ! grep -q "^CVMFS_IGNORE_XDIR_HARDLINKS=" $server_conf; then
      echo "CVMFS_IGNORE_XDIR_HARDLINKS=true" >> $server_conf
  fi
  sed -i -e "s/^\(CVMFS_CREATOR_VERSION\)=.*/\1=$destination_version/" $server_conf

  # update repository information
  load_repo_config $name
}


_migrate_143() {
  local name=$1
  local destination_version="143"
  local client_conf="/etc/cvmfs/repositories.d/${name}/client.conf"
  local server_conf="/etc/cvmfs/repositories.d/${name}/server.conf"

  load_repo_config $name
  echo "Migrating repository '$name' from layout revision $(mangle_version_string $CVMFS_CREATOR_VERSION) to revision $(mangle_version_string $destination_version)"

  echo "--> updating client.conf"
  if ! grep -q "CVMFS_USE_SSL_SYSTEM_CA" $client_conf; then
    echo "CVMFS_USE_SSL_SYSTEM_CA=true" >> $client_conf
  fi

  if is_stratum0 $name; then
    echo "--> adjusting /etc/fstab"
    sed -i -e "s|\(.*\)allow_other,\(.*# added by CernVM-FS for ${CVMFS_REPOSITORY_NAME}\)|\1allow_other,fsname=${CVMFS_REPOSITORY_NAME},\2|" /etc/fstab

    # Make sure the systemd mount unit exists
    if is_systemd; then
      /usr/lib/systemd/system-generators/systemd-fstab-generator \
        /run/systemd/generator '' '' 2>/dev/null || true
      systemctl daemon-reload
    fi
  fi

  echo "--> updating server.conf"
  sed -i -e "s/^\(CVMFS_CREATOR_VERSION\)=.*/\1=$destination_version/" $server_conf

  # update repository information
  load_repo_config $name
}


cvmfs_server_migrate() {
  local names
  local retcode=0

  # get repository names
  check_parameter_count_for_multiple_repositories $#
  names=$(get_or_guess_multiple_repository_names "$@")
  check_multiple_repository_existence "$names"

  # sanity checks
  is_root || die "Only root can migrate repositories"

  for name in $names; do

    check_repository_existence $name || { echo "The repository $name does not exist"; retcode=1; continue; }

    # get repository information
    load_repo_config $name
    creator="$(repository_creator_version $name)"

    # more sanity checks
    is_owner_or_root $name || { echo "Permission denied: Repository $name is owned by $user"; retcode=1; continue; }
    check_repository_compatibility $name "nokill" && { echo "Repository '$name' is already up-to-date."; continue; }
    health_check -r $name

    if is_stratum0 $name && is_in_transaction $name; then
      echo "Repository '$name' is currently in a transaction - migrating might"
      echo "result in data loss. Please abort or publish this transaction with"
      echo "the CernVM-FS version ($creator) that opened it."
      retcode=1
      continue
    fi

    # do the migrations...
    if [ x"$creator" = x"2.1.6" ]; then
      _migrate_2_1_6 $name
      creator="$(repository_creator_version $name)"
    fi

    if [ x"$creator" = x"2.1.7" -o  \
         x"$creator" = x"2.1.8" -o  \
         x"$creator" = x"2.1.9" -o  \
         x"$creator" = x"2.1.10" -o \
         x"$creator" = x"2.1.11" -o \
         x"$creator" = x"2.1.12" -o \
         x"$creator" = x"2.1.13" -o \
         x"$creator" = x"2.1.14" ];
    then
      _migrate_2_1_7 $name
      creator="$(repository_creator_version $name)"
    fi

    if [ x"$creator" = x"2.1.15" -o   \
         x"$creator" = x"2.1.16" -o   \
         x"$creator" = x"2.1.17" -o   \
         x"$creator" = x"2.1.18" -o   \
         x"$creator" = x"2.1.19" ] && \
         is_stratum1 $name         && \
         is_local_upstream $CVMFS_UPSTREAM_STORAGE;
    then
      _migrate_2_1_15 $name
      creator="$(repository_creator_version $name)"
    fi

    if [ x"$creator" = x"2.1.15" -o \
         x"$creator" = x"2.1.16" -o \
         x"$creator" = x"2.1.17" -o \
         x"$creator" = x"2.1.18" -o \
         x"$creator" = x"2.1.19" -o \
         x"$creator" = x"2.1.20" -o \
         x"$creator" = x"2.2.0-0" ];
    then
      _migrate_2_1_20 $name
      creator="$(repository_creator_version $name)"
    fi

    if [ x"$creator" = x"2.2.0-1" -o   \
         x"$creator" = x"2.2.1-1" -o   \
         x"$creator" = x"2.2.2-1" -o   \
         x"$creator" = x"2.2.3-1" ] && \
         is_stratum0 $name;
    then
      _migrate_2_2 $name
      creator="$(repository_creator_version $name)"
    fi

    if [ x"$creator" = x"2.2.0-1" -o   \
         x"$creator" = x"2.2.1-1" -o   \
         x"$creator" = x"2.2.2-1" -o   \
         x"$creator" = x"2.2.3-1" -o   \
         x"$creator" = x"2.3.0-1" -o   \
         x"$creator" = x"2.3.1-1" -o   \
         x"$creator" = x"2.3.2-1" ]; then
      _migrate_2_3_0 $name
      creator="$(repository_creator_version $name)"
    fi

    if [ x"$creator" = x"2.3.3-1" -o   \
         x"$creator" = x"2.3.4-1" -o   \
         x"$creator" = x"2.3.5-1" -o   \
         x"$creator" = x"2.3.6-1" -o   \
         x"$creator" = x"2.4.0-1" ]; then
      # initially this was version 137 but it does everything needed by
      #  138 so skip up to 138.
      _migrate_138 $name
      creator="$(repository_creator_version $name)"
    fi

    if [ "$creator" = "137" ] && \
         is_stratum1 $name && \
         has_apache_config_file $(get_apache_conf_filename $name); then
      # this does slightly more than needed but is close enough so reuse it
      _migrate_138 $name
      creator="$(repository_creator_version $name)"
    fi

    if [ $creator -lt 139 ]; then
      _migrate_139 $name
      creator="$(repository_creator_version $name)"
    fi

    if [ "$creator" -lt 140 ] && \
         is_stratum1 $name && \
         has_apache_config_file $(get_apache_conf_filename $name); then
      _migrate_140 $name
      creator="$(repository_creator_version $name)"
    fi

    if [ "$creator" -lt 141 ] && is_stratum0 $name; then
      _migrate_141 $name
      creator="$(repository_creator_version $name)"
    fi

    if [ "$creator" -lt 142 ] && is_stratum0 $name; then
      _migrate_142 $name
      creator="$(repository_creator_version $name)"
    fi

    if [ "$creator" -lt 143 ] && is_stratum0 $name; then
      _migrate_143 $name
      creator="$(repository_creator_version $name)"
    fi

  done

  syncfs

  return $retcode
}


#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server chown" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh


cvmfs_server_catalog_chown() {
  local uid_map
  local gid_map

  OPTIND=1
  while getopts "u:g:" option; do
    case $option in
      u)
        uid_map=$OPTARG
      ;;
      g)
        gid_map=$OPTARG
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command catalog-chown: Unrecognized option: $1"
      ;;
    esac
  done
  shift $(($OPTIND-1))

   # get repository names
  check_parameter_count_with_guessing $#
  name=$(get_or_guess_repository_name $@)
  check_repository_existence "$name"

  # sanity checks
  [ x"$uid_map" != x"" ] && cvmfs_sys_file_is_regular $uid_map || die "UID map file not found (-u)"
  [ x"$gid_map" != x"" ] && cvmfs_sys_file_is_regular $gid_map || die "GID map file not found (-g)"

  load_repo_config $name
  is_checked_out $name && die "command is not supported while checked out onto a branch"

  local migrate_command="$(__swissknife_cmd dbg) migrate     \
                              -v 'chown'                     \
                              -r $CVMFS_STRATUM0             \
                              $(get_swissknife_proxy)        \
                              -n $name                       \
                              -u $CVMFS_UPSTREAM_STORAGE     \
                              -k $CVMFS_PUBLIC_KEY           \
                              -i $uid_map                    \
                              -j $gid_map                    \
                              -s"

  _run_catalog_migration "$name" "$migrate_command"
}


# This file is part of the CernVM File System
# This script takes care of removing bulk hashes from files that are already
# chunked
#
# Implementation of the "cvmfs_server eliminate-bulk-hashes" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh


cvmfs_server_eliminate_bulk_hashes() {
  local name=
  local force=0

  # parameter handling
  OPTIND=1
  while getopts "f" option; do
    case $option in
      f)
        force=1
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command eliminate-bulk-hashes: Unrecognized option: $1"
      ;;
    esac
  done
  shift $(($OPTIND-1))

  # get repository name
  check_parameter_count_with_guessing $#
  name=$(get_or_guess_repository_name $@)
  check_repository_existence "$name"

  load_repo_config $name

  is_root || die "Permission denied: Only root can do that"
  is_checked_out $name && die "command is not supported while checked out onto a branch"

  if [ $force -ne 1 ]; then
    echo "This will remove bulk hashes of chunked files present in '$name'."
    echo "This process cannot be undone!"
    echo ""
    echo -n "Are you sure? (y/N): "

    local reply="n"
    read reply
    if [ "$reply" != "y" ] && [ "$reply" != "Y" ]; then
      echo "aborted."
      exit 1
    fi
  fi

  local migrate_command="$(__swissknife_cmd dbg) migrate     \
                              -v 'bulkhash'                  \
                              -r $CVMFS_STRATUM0             \
                              $(get_swissknife_proxy)        \
                              -n $name                       \
                              -u $CVMFS_UPSTREAM_STORAGE     \
                              -k $CVMFS_PUBLIC_KEY           \
                              -s"

  _run_catalog_migration "$name" "$migrate_command"
}
#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server eliminate-hardlinks" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh


cvmfs_server_eliminate_hardlinks() {
  local name=
  local force=0

  # parameter handling
  OPTIND=1
  while getopts "f" option; do
    case $option in
      f)
        force=1
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command eliminate-hardlinks: Unrecognized option: $1"
      ;;
    esac
  done
  shift $(($OPTIND-1))

  # get repository name
  check_parameter_count_with_guessing $#
  name=$(get_or_guess_repository_name $@)
  check_repository_existence "$name"

  load_repo_config $name

  is_root || die "Permission denied: Only root can do that"
  is_checked_out $name && die "command is not supported while checked out onto a branch"

  if [ $force -ne 1 ]; then
    echo "This will break up all hardlink relationships that are currently"
    echo "present in '$name'. This process cannot be undone!"
    echo ""
    echo -n "Are you sure? (y/N): "

    local reply="n"
    read reply
    if [ "$reply" != "y" ] && [ "$reply" != "Y" ]; then
      echo "aborted."
      exit 1
    fi
  fi

  local migrate_command="$(__swissknife_cmd dbg) migrate     \
                              -v 'hardlink'                  \
                              -r $CVMFS_STRATUM0             \
                              $(get_swissknife_proxy)        \
                              -n $name                       \
                              -u $CVMFS_UPSTREAM_STORAGE     \
                              -k $CVMFS_PUBLIC_KEY           \
                              -s"

  _run_catalog_migration "$name" "$migrate_command"
}


#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server update-info" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh


_update_info_cleanup() {
  local tmp_file="$1"
  rm -f $tmp_file > /dev/null 2>&1
}

cvmfs_server_update_info() {
  local configure_apache=1
  local edit_meta_info=1

  # parameter handling
  OPTIND=1
  while getopts "pe" option; do
    case $option in
      p)
        configure_apache=0
      ;;
      e)
        edit_meta_info=0
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command update-info: Unrecognized option: $1"
      ;;
    esac
  done
  shift $(($OPTIND-1))

  # sanity checks
  is_root || die "only root can update meta information"

  # create info HTTP resource if not existent yet
  if ! has_global_info_path; then
    echo -n "Creating Info Resource... "
    create_global_info_skeleton || die "fail"
    echo "done"
  fi

  if [ $configure_apache -eq 1 ] && ! has_apache_config_for_global_info; then
    echo -n "Creating Apache Configuration for Info Resource... "
    create_apache_config_for_global_info || die "fail (create apache config)"
    reload_apache > /dev/null            || die "fail (reload apache)"
    echo "done"
  fi

  # manually edit the meta information file
  local tmp_file=""
  if [ $edit_meta_info -eq 1 ]; then
    # copy the meta information file for editing
    tmp_file=$(mktemp)
    trap "_update_info_cleanup $tmp_file" EXIT HUP INT TERM
    cp -f "$(get_global_info_v1_path)/meta.json" $tmp_file

    edit_json_until_valid $tmp_file || die "Aborting..."
  fi

  # update the JSON files
  echo -n "Updating global JSON information... "
  update_global_repository_info || die "fail (update repo info)"
  if [ $edit_meta_info -eq 1 ]; then
    update_global_meta_info "$tmp_file" || die "fail (update meta info)"
  fi
  echo "done"
}


#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server update-repoinfo" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh

_update_repoinfo_cleanup() {
  local repo_name="$1"
  shift 1

  while [ $# -gt 0 ]; do
    rm -f $1 > /dev/null 2>&1
    shift 1
  done

  close_transaction $repo_name 0
}


cvmfs_server_update_repoinfo() {
  local name
  local json_file

  OPTIND=1
  while getopts "f:" option; do
    case $option in
      f)
        json_file=$OPTARG
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command update-repoinfo: Unrecognized option: $1"
      ;;
    esac
  done
  shift $(($OPTIND-1))

  # get repository name
  check_parameter_count_with_guessing $#
  name=$(get_or_guess_repository_name $@)

  # sanity checks
  check_repository_existence $name || die "The repository $name does not exist"
  load_repo_config $name
  is_owner_or_root $name           || die "Permission denied: Repository $name is owned by $CVMFS_USER"
  is_stratum0 $name                || die "This is not a stratum 0 repository"
  ! is_publishing $name            || die "Repository is currently publishing"
  health_check -r $name
  is_in_transaction $name && die "Cannot edit repository meta info while in a transaction"
  [ x"$json_file" = x"" ] || [ -f "$json_file" ] || die "Provided file '$json_file' doesn't exist"

  tmp_file_info=$(mktemp)
  tmp_file_manifest=$(mktemp)
  chown $CVMFS_USER $tmp_file_info $tmp_file_manifest || die "Cannot change ownership of temporary files"

  trap "_update_repoinfo_cleanup $name $tmp_file_info $tmp_file_manifest" EXIT HUP INT TERM
  open_transaction $name || die "Failed to open transaction for meta info editing"

  get_repo_info -M > $tmp_file_info || \
    die "Failed getting repository meta info for $name"
  get_repo_info -R > $tmp_file_manifest || \
    die "Failed getting repository manifest for $name"

  if [ x"$json_file" = x"" ]; then
    if [ -f $tmp_file_info ] && [ ! -s $tmp_file_info ]; then
      create_repometa_skeleton $tmp_file_info
    fi

    edit_json_until_valid $tmp_file_info
  else
    local jq_output
    if ! jq_output="$(validate_json $json_file)"; then
      die "The provided JSON file is invalid. See below:\n${jq_output}"
    fi

    cat $json_file > $tmp_file_info
  fi

  sign_manifest $name $tmp_file_manifest $tmp_file_info
}
#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server mount" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh


cvmfs_server_mount() {
  local names=""
  local mount_all=0
  local retval=0

  OPTIND=1
  while getopts "a" option; do
    case $option in
      a)
        mount_all=1
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command mount: Unrecognized option: $1"
      ;;
    esac
  done
  shift $(($OPTIND-1))

  if [ $mount_all -eq 1 ]; then
    # sanity checks
    is_root || die "Permission Denied: need root to mount all repositories"
    names="$(ls /etc/cvmfs/repositories.d)"
  else
    # get repository name
    check_parameter_count_for_multiple_repositories $#
    names=$(get_or_guess_multiple_repository_names "$@")
    check_multiple_repository_existence "$names"
  fi

  for name in $names; do
    is_stratum0        $name || continue
    is_owner_or_root   $name || { echo "Permission Denied: $name is owned by $CVMFS_USER" >&2; retval=1; continue; }
    health_check -rftq $name || { echo "Failed to mount $name"                            >&2; retval=1; continue; }
  done

  return $retval
}


#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server skeleton" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh


cvmfs_server_skeleton() {
  local skeleton_dir
  local skeleton_user

  # get optional parameters
  OPTIND=1
  while getopts "o:" option
  do
    case $option in
      o)
        skeleton_user=$OPTARG
        ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command skeleton: Unrecognized option: $1"
      ;;
    esac
  done

  # get skeleton destination directory
  shift $(($OPTIND-1))

  # get skeleton destination directory
  if [ $# -eq 0 ]; then
    usage "Command skeleton: Please provide a skeleton destination directory"
  fi
  if [ $# -gt 1 ]; then
    usage "Command skeleton: Too many arguments"
  fi
  skeleton_dir=$1

  # ask for the skeleton dir owern
  if [ x$skeleton_user = "x" ]; then
    read -p "Owner of $skeleton_dir [$(whoami)]: " skeleton_user
    # default value
    [ x"$skeleton_user" = x ] && skeleton_user=$(whoami)
  fi

  # sanity checks
  check_user $skeleton_user || die "No user $skeleton_user"

  # do it!
  create_repository_skeleton $skeleton_dir $skeleton_user
}


#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server fix-permissions" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh


cvmfs_server_fix_permissions() {
  local num_overlayfs=$(find /etc/cvmfs/repositories.d -name server.conf 2>/dev/null \
    -exec grep "CVMFS_UNION_FS_TYPE=overlayfs" {} \; | wc -l)
  if [ $num_overlayfs -gt 0 ]; then
    ensure_swissknife_suid overlayfs
  else
    ensure_swissknife_suid other
  fi
}


# This file is part of the CernVM File System
# This script recreates the statistics counters of the file catalogs
#
# Implementation of the "cvmfs_server fix-stats" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh


cvmfs_server_fix_stats() {
  local name=
  local force=0

  # parameter handling
  OPTIND=1
  while getopts "f" option; do
    case $option in
      f)
        force=1
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command fix-stats: Unrecognized option: $1"
      ;;
    esac
  done
  shift $(($OPTIND-1))

  # get repository name
  check_parameter_count_with_guessing $#
  name=$(get_or_guess_repository_name $@)
  check_repository_existence "$name"

  load_repo_config $name

  is_root || die "Permission denied: Only root can do that"
  is_checked_out $name && die "command is not supported while checked out onto a branch"

  if [ $force -ne 1 ]; then
    echo "This will recreate the statistics of all file catalogs in '$name'."
    echo "This process cannot be undone!"
    echo ""
    echo -n "Are you sure? (y/N): "

    local reply="n"
    read reply
    if [ "$reply" != "y" ] && [ "$reply" != "Y" ]; then
      echo "aborted."
      exit 1
    fi
  fi

  local migrate_command="$(__swissknife_cmd dbg) migrate     \
                              -v 'stats'                     \
                              -r $CVMFS_STRATUM0             \
                              $(get_swissknife_proxy)        \
                              -n $name                       \
                              -u $CVMFS_UPSTREAM_STORAGE     \
                              -k $CVMFS_PUBLIC_KEY           \
                              -s"

  _run_catalog_migration "$name" "$migrate_command"
}
#
# This file is part of the CernVM File System
# This script takes care of creating, removing, and maintaining repositories
# on a Stratum 0/1 server
#
# Implementation of the "cvmfs_server ingest-tarball" command

# This file depends on functions implemented in the following files:
# - cvmfs_server_util.sh
# - cvmfs_server_common.sh


# TODO Most of this code is replicated and shared between different scripts,
# it would be a good idea to refactor common patterns into coherent functions.

cvmfs_server_ingest() {
  local base_dir="" # where to extract the tar file
  local tar_file=""
  local to_delete="" # directories or file to delete before the extraction
  local name="" #repository name
  local user=""
  local group=""
  local uid=""
  local gid=""
  local keep_ownership=false
  local create_catalog=false

  local force_native=0
  local force_external=0

  # if we use the gateway we cannot easily accept multiple deletion
  local multiple_delete=0

  local name_from_absolute_arg=""
  local name_from_absolute_arg2=""

  while [ "$2" != "" ]; do
    case $1 in
      -b | --base_dir )
        base_dir=$2
        # remove any duplicated slashes in pathname
        # swissknife cannot handle it at the moment
        base_dir=$(echo $base_dir | tr -s / )
        ;;
      -t | --tar_file )
        tar_file=$2
        ;;
      -d | --delete )
        if [ "x$to_delete" = "x" ]
        then
          to_delete="$(echo $2 | tr -s /)"
        else
          to_delete="$to_delete///$(echo $2 | tr -s /)"
          multiple_delete=1
        fi
        ;;
      -c | --catalog )
        create_catalog=true
        ;;
      -u | --user )
        user=$2
      ;;
      -g | --group )
        group=$2
      ;;
      -k | --keep-ownership )
        keep_ownership=true
      ;;
    esac
    shift
  done

  # deal with absolute/relative paths
  case x"$base_dir" in
      x/cvmfs/*) 
        echo "Warning: interpreting the base_dir as absolute path. Remove leading slash to get a relative path to the mountpoint"
        name_from_absolute_arg=$(echo $base_dir | cut -d'/' -f3)
        base_dir=$(echo $base_dir | cut -d'/' -f 4-)
  esac
  for to_delete_path in $(echo $to_delete | sed "s;///; ;g"); do
    case x"$to_delete_path" in
        x/cvmfs/*) 
          echo "Warning: interpreting the base_dir as absolute path. Remove leading slash to get a relative path to the mountpoint"
          name_from_absolute_arg2=$(echo $to_delete_path | cut -d'/' -f3)
          to_delete=$(echo $to_delete_path  | cut -d'/' -f 4-)
          if [ ! x$name_from_absolute_arg = "x" ] ; then
            if [ ! x$name_from_absolute_arg = x$name_from_absolute_arg2 ] ; then
              die "Cannot use different repositories in same transaction: $name_from_absolute_arg2, $name_from_absolute_arg"
            fi
          fi
          name_from_absolute_arg=$name_from_absolute_arg2
    esac
  done

  name=$1
  name=$(echo $name | cut -d'/' -f1)

  if [ x"$name" = "x" ] ; then
    name=$name_from_absolute_arg
  fi
  echo "Info: transaction on repository $name"

  if [ x"$name" = "x" ] ; then

    die "Please provide a repository name, as positional argument or via an absolute path on /cvmfs given to -b"
  fi

  if [ x"$tar_file" = "x" ] && [ x"$base_dir" = "x" ] && [ x"$to_delete" = "x" ] ; then
    die "Please provide some parameters, use -t \$TAR_FILE to provide the tar to extract -b \$BASE_DIR to provide where to extract the tar and -d \$TO_DELETE to provide what to delete from the repository"
  fi

  if [ x"$tar_file" = "x" ] && [ ! x"$base_dir" = "x" ]; then
    die "Please provide the tarball to extract, use -t \$TARBALL_PATH or --tar_file \$TARBALL_PATH or don't provide the base directory to simply delete entities from the repository"
  fi

  if [ ! x"$tar_file" = "x" ] && [ x"$base_dir" = "x" ]; then
    die "Please set the base directory where to extract the tarball, use -b \$BASE_DIR or --base_dir \$BASE_DIR or don't provide the base directory to simply delete entities from the repository"
  fi

  load_repo_config $name

  #### check and set uid/gid
  # error: cannot keep ownership while also requesting other user/group
  if { [ x"$user" != "x" ] || [ x"$group" != "x" ]; } && [ $keep_ownership = true ]; then
    die "You cannot provide both: either provide user (-u)/group (-g) or keep the ownership (-k) of the tarball"
  fi


  # error: group also needs user
  if [ x"$user" = "x" ] && [ x"$group" != "x" ]; then
    die "If providing a group name, you also must provide a user (use -u) to set new owner of the ingest tarball"
  fi

  # both set
  if [ x"$user" != "x" ]; then
    uid=$(id -u "$user")

    if [ x"$uid" = xi* ]; then
      die "User set but no valid user name given"
    fi
  fi

  if [ x"$group" != "x" ]; then
    gid=$(getent group "$group" | awk -F':' '{print $3;}')

    if [ x"$gid" = "x" ]; then
      die "Group set but no valid group name given"
    fi
  fi
  # only user set: get gid from user
  if [ x"$group" != "x" ]; then
    gid=$(id -g "$user")
  fi
  # use default cvmfs repo owner
  if [ x"$user" = "x" ] && [ x"$group" = "x" ] && [ $keep_ownership = false ]; then
    uid=$(id -u "$CVMFS_USER")
    gid=$(id -g "$CVMFS_USER")

    if [ x"$uid" = xi* ]; then
      die "Default CVMFS_USER $CVMFS_USER for the repo does not exist"
    fi
  fi
  # keep tar ball ownership
  if [ $keep_ownership = true ]; then
    uid="-1"
    gid="-1"
  fi

  upstream=$CVMFS_UPSTREAM_STORAGE
  upstream_type=$(get_upstream_type $upstream)

  if [ x"$upstream_type" = xgw ]; then

    if [ $multiple_delete -eq 1 ]; then
      die "Could not delete multiple paths using a gateway in a single transaction."
    fi

    if [ ! x"$tar_file" = "x" ] && [ ! x"$to_delete" = "x" ]; then
      die "Could not delete and add a file in the same transaction while using gateway."
    fi
    # by the check above we are sure that there is only a tar_file to ingest or a directory to_delete
    # hence we just concatenate them with the name for the transaction
    cvmfs_server_transaction "$name/$base_dir$to_delete" || die "Impossible to start a transaction"
  else
    cvmfs_server_transaction $name || die "Impossible to start a transaction"
  fi

  spool_dir=$CVMFS_SPOOL_DIR
  scratch_dir="${spool_dir}/scratch/current"
  stratum0=$CVMFS_STRATUM0
  hash_algorithm="${CVMFS_HASH_ALGORITHM-sha1}"
  compression_alg="${CVMFS_COMPRESSION_ALGORITHM-default}"
  if [ x"$force_compression_algorithm" != "x" ]; then
    compression_alg="$force_compression_algorithm"
  fi
  if [ x"$CVMFS_EXTERNAL_DATA" = "xtrue" -o $force_external -eq 1 ]; then
    if [ $force_native -eq 0 ]; then
      external_option="-Y"
    fi
  fi


  [ $(count_wr_fds /cvmfs/$name) -eq 0 ] || { cvmfs_server_abort -f $name; die "Open writable file descriptors on $name"; }
  is_cwd_on_path "/cvmfs/$name" && { cvmfs_server_abort -f $name; die "Current working directory is in /cvmfs/$name.  Please release, e.g. by 'cd \$HOME'."; } || true
  gc_timespan="$(get_auto_garbage_collection_timespan $name)" || { cvmfs_server_abort -f $name; die; }
  if [ x"$manual_revision" != x"" ]; then
    if [ "x$(echo "$manual_revision" | tr -cd 0-9)" != "x$manual_revision" ]; then
      cvmfs_server_abort -f $name
      die "Invalid revision number: $manual_revision"
    fi
    local revision_number=$(attr -qg revision /var/spool/cvmfs/${name}/rdonly)
    if [ $manual_revision -le $revision_number ]; then
      cvmfs_server_abort -f $name
      die "Current revision '$revision_number' is ahead of manual revision number '$manual_revision'."
    fi
  fi

  if is_checked_out $name; then
    if [ x"$tag_name" = "x" ]; then
      cvmfs_server_abort -f $name
      die "Publishing a checked out revision requires a tag name"
    fi
  else
    if [ -z "$tag_name" ] && [ x"$CVMFS_AUTO_TAG" = x"true" ]; then
      local timestamp=$(date -u "+%Y-%m-%dT%H:%M:%SZ")
      tag_name="generic-$timestamp"
     local tag_name_number=1
      while check_tag_existence $name $tag_name; do
        tag_name="generic_$tag_name_number-$timestamp"
        tag_name_number=$(( $tag_name_number + 1 ))
      done
      echo "Using auto tag '$tag_name'"
    fi

    local auto_tag_cleanup_list=
    auto_tag_cleanup_list="$(filter_auto_tags $name)" || { cvmfs_server_abort -f $name; die "failed to determine outdated auto tags on $name"; }
  fi


  local user_shell="$(get_user_shell $name)"
  local base_hash=$(get_mounted_root_hash $name)
  local manifest="${spool_dir}/tmp/manifest"
  local dirtab_command="$(__swissknife_cmd dbg) dirtab \
    -d /cvmfs/${name}/.cvmfsdirtab                     \
    -b $base_hash                                      \
    -w $stratum0                                       \
    $(get_swissknife_proxy)                            \
    -t ${spool_dir}/tmp                                \
    -u /cvmfs/${name}                                  \
    -s ${scratch_dir}                                  \
    $verbosity"


  local log_level=
  [ "x$CVMFS_LOG_LEVEL" != x ] && log_level="-z $CVMFS_LOG_LEVEL"

  local tag_command="$(__swissknife_cmd dbg) tag_edit \
    -r $upstream                                      \
    -w $stratum0                                      \
    -t ${spool_dir}/tmp                               \
    -m $manifest                                      \
    -p /etc/cvmfs/keys/${name}.pub                    \
    -f $name                                          \
    -e $hash_algorithm                                \
    $(get_swissknife_proxy)                           \
    $(get_follow_http_redirects_flag)"
  if ! is_checked_out $name; then
    # enables magic undo tag handling
    tag_command="$tag_command -x"
  else
    tag_command="$tag_command -B $(get_checked_out_branch $name)"
    if [ "x$(get_checked_out_previous_branch $name)" != "x" ]; then
      tag_command="$tag_command -P $(get_checked_out_previous_branch $name)"
    fi
  fi
  if [ ! -z "$tag_name" ]; then
    tag_command="$tag_command -a $tag_name"
  fi
  if [ ! -z "$tag_description" ]; then
    tag_command="$tag_command -D \"$tag_description\""
  fi

  local tag_command_undo_tags="$(__swissknife_cmd dbg) tag_edit \
    -r $upstream                                                \
    -w $stratum0                                                \
    -t ${spool_dir}/tmp                                         \
    -m $manifest                                                \
    -p /etc/cvmfs/keys/${name}.pub                              \
    -f $name                                                    \
    -e $hash_algorithm                                          \
    $(get_swissknife_proxy)                                     \
    $(get_follow_http_redirects_flag)                           \
    -x"


  local ingest_command="$(__swissknife_cmd dbg) \
    ingest                                      \
    -u /cvmfs/$name                             \
    -c ${spool_dir}/rdonly                      \
    -t ${spool_dir}/tmp                         \
    -b $base_hash                               \
    -r ${upstream}                              \
    -w $stratum0                                \
    $(get_swissknife_proxy)                     \
    -o $manifest                                \
    -K $CVMFS_PUBLIC_KEY                        \
    -N $name                                    \
    -U $uid                                     \
    -G $gid                                     \
    "

  if [ ! x"$tar_file" = "x" ]; then
    ingest_command="$ingest_command -T $tar_file"
  fi

  if [ ! x"$base_dir" = "x" ]; then
    ingest_command="$ingest_command -B $base_dir"
  fi

  if [ ! x"$to_delete" = "x" ]; then
      ingest_command="$ingest_command -D ${to_delete}"
  fi

  if [ "$create_catalog" = true ]; then
    ingest_command="$ingest_command -C true"
  fi

  if [ "x$CVMFS_ENABLE_MTIME_NS" = "xtrue" ]; then
    ingest_command="$ingest_command -j"
  fi

  if [ "x$CVMFS_PRINT_STATISTICS" = "xtrue" ]; then
    ingest_command="$ingest_command -+stats"
  fi

  if [ "x$CVMFS_UPLOAD_STATS_DB" = "xtrue" ]; then
    ingest_command="$ingest_command -I"
  fi

  local upstream_storage=$CVMFS_UPSTREAM_STORAGE
  local upstream_type=$(get_upstream_type $upstream_storage)
  gw_key_file=/etc/cvmfs/keys/${name}.gw

  if [ x"$upstream_type" = xgw ]; then
    ingest_command="$ingest_command -H $gw_key_file -P ${spool_dir}/session_token"
  fi


  # ---> do it! (from here on we are changing things)
  publish_before_hook $name
  $user_shell "$dirtab_command" || { cvmfs_server_abort -f $name; die "Failed to apply .cvmfsdirtab"; }

  # check if we have open file descriptors on /cvmfs/<name>
  local use_fd_fallback=0
  handle_read_only_file_descriptors_on_mount_point $name $open_fd_dialog || use_fd_fallback=1

  publish_starting $name

  $user_shell "$ingest_command" || { publish_failed $name; cvmfs_server_abort -f $name; die "Synchronization failed\n\nExecuted Command:\n$ingest_command";   }

  cvmfs_sys_file_is_regular $manifest            || { publish_failed $name; cvmfs_server_abort -f $name; die "Manifest creation failed\n\nExecuted Command:\n$sync_command"; }

  local branch_hash=
  local trunk_hash=$(grep "^C" $manifest | tr -d C)
  if is_checked_out $name; then
    local branch_hash=$trunk_hash
    trunk_hash=$(get_published_root_hash $name)
    tag_command="$tag_command -h $branch_hash"
    # write intermediate catalog hash to reflog
    sign_manifest $name $manifest "" true
    # Replace throw-away manifest with upstream copy
    get_raw_manifest $name > $manifest
    cvmfs_sys_file_is_empty $manifest && { cvmfs_server_abort -f $name; die "failed to reload manifest"; }
  fi

  if [ x"$upstream_type" = xgw ]; then
      # TODO(jpriessn): implement publication counters upload to gateway
      close_transaction  $name $use_fd_fallback
      publish_after_hook $name
      publish_succeeded $name
      echo "Changes submitted to repository gateway"
      return 0
  fi

  # Remove outdated automatically created tags
  local tag_remove_cmd_file=
  if [ ! -z "$auto_tag_cleanup_list" ]; then
    local tag_list_file=$(mktemp)
    echo $auto_tag_cleanup_list | xargs -n100 echo > $tag_list_file
    tag_remove_cmd_file=$(mktemp)
    cat $tag_list_file | while read REPLY; do
      local tag_cleanup_command="$(__swissknife_cmd dbg) tag_edit \
        -r $upstream                                        \
        -w $stratum0                                        \
        -t ${spool_dir}/tmp                                 \
        -m $manifest                                        \
        -p /etc/cvmfs/keys/${name}.pub                      \
        -f $name                                            \
        -b $base_hash                                       \
        -e $hash_algorithm                                  \
        $(get_swissknife_proxy)                             \
        $(get_follow_http_redirects_flag)                   \
        -d \\\"$REPLY\\\""
      echo $user_shell \"${tag_cleanup_command}\" >> $tag_remove_cmd_file
    done
    rm -f $tag_list_file
  fi

  if [ ! -z "$tag_remove_cmd_file" ]; then
    echo "Removing outdated automatically generated tags for $name..."
    /bin/sh $tag_remove_cmd_file || \
      { rm -f $tag_remove_cmd_file; publish_failed $name; \
        cvmfs_server_abort -f $name; \
        die "Removing tags failed\n\nExecuted Command:\n \
        /bin/sh $tag_remove_cmd_file"; }
    rm -f $tag_remove_cmd_file
    # write intermediate history hash to reflog
    sign_manifest $name $manifest "" true
  fi

  # add a tag for the new revision
  echo "Tagging $name"
  $user_shell "$tag_command" || { publish_failed $name; cvmfs_server_abort -f $name; die "Tagging failed\n\nExecuted Command:\n$tag_command";  }

  if [ "x$sync_command_virtual_dir" != "x" ]; then
    # write intermediate catalog hash and history to reflog
    sign_manifest $name $manifest "" true
    $user_shell "$sync_command_virtual_dir" || { publish_failed $name; cvmfs_server_abort -f $name; die "Editing .cvmfs failed\n\nExecuted Command:\n$sync_command_virtual_dir";  }
    local trunk_hash=$(grep "^C" $manifest | tr -d C)
    $user_shell "$tag_command_undo_tags" || { publish_failed $name; cvmfs_server_abort -f $name; die "Creating undo tags\n\nExecuted Command:\n$tag_command_undo_tags";  }
  fi

  # finalizing transaction
  echo "Flushing file system buffers"
  sync

  # committing newly created revision
  echo "Signing new manifest"
  sign_manifest $name $manifest      || { publish_failed $name; cvmfs_server_abort -f $name; die "Signing failed"; }
  set_ro_root_hash $name $trunk_hash || { publish_failed $name; cvmfs_server_abort -f $name; die "Root hash update failed"; }
  if is_checked_out $name; then
    rm -f /var/spool/cvmfs/${name}/checkout
    echo "Reset to trunk on default branch"
  fi

  # run the automatic garbage collection (if configured)
  if is_due_auto_garbage_collection $name; then
    echo "Running automatic garbage collection"
    local dry_run=0
    __run_gc $name       \
             $stratum0   \
             $dry_run    \
             ""          \
             "0"         \
             -z $gc_timespan      || { local err=$?; publish_failed $name; cvmfs_server_abort -f $name; die "Garbage collection failed ($err)"; }
  fi

  # check again for open file descriptors (potential race condition)
  if has_file_descriptors_on_mount_point $name && \
     [ $use_fd_fallback -ne 1 ]; then
    file_descriptor_warning $name
    echo "Forcing remount of already committed repository revision"
    use_fd_fallback=1
  else
    echo "Remounting newly created repository revision"
  fi

  # remount the repository
  if [ "x$CVMFS_UPLOAD_STATS_PLOTS" = "xtrue" ]; then
    /usr/share/cvmfs-server/upload_stats_plots.sh $name
  fi
  close_transaction $name $use_fd_fallback
  publish_after_hook $name
  publish_succeeded  $name

}
#
# This file is part of the CernVM File System
#
# Implementation of the "cvmfs_server stats command"

cvmfs_server_print_stats() {
  local output_file=""
  local repo_name=""
  local repo_stats=""
  local separator='|'
  local db_table="publish_statistics"

  # optional parameter handling
  OPTIND=1
  while getopts "o:s:t:" option
  do
    case $option in
      o)
        output_file="$OPTARG"
      ;;
      s)
        separator="$OPTARG"
      ;;
      t)
        db_table="$OPTARG"
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command print-stats: Unrecognized option: $1"
      ;;
    esac
  done

  shift $(($OPTIND-1))
  check_parameter_count 1 $#
  repo_name=$(get_repository_name $1)
  check_multiple_repository_existence $repo_name
  repo_stats="/var/spool/cvmfs/$repo_name/stats.db"
  if [ -e $repo_stats ]; then
    # On an older Linux > /dev/stdout does not work
    if [ "x$output_file" = "x" ]; then
      sqlite3 -header $repo_stats "SELECT * from $db_table;" | tr \| $separator
    else
      sqlite3 -header $repo_stats "SELECT * from $db_table;" | tr \| $separator > $output_file
    fi
  else
    echo "No statistics database file for $repo_name repository."
  fi
}
#
# This file is part of the CernVM File System
#
# Implementation of the "cvmfs_server stats command"

clean_up() {
  echo "Cleaning up"
  echo "  Removing temporary files"
  rm -rvf /tmp/cvmfs_server_merge_stats/*
}

# merge publish_statistics table
cvmfs_server_merge_table() {
  local db_file_1=""
  local db_file_2=""
  local output_db=$3
  local table=$4
  local TMP_DIR=/tmp/cvmfs_server_merge_stats
  local create_table_statement=""
  local columns=""

  mkdir -p $TMP_DIR
  # Make copies for the input db files
  cp $1 $TMP_DIR/db1
  cp $2 $TMP_DIR/db2
  db_file_1=${TMP_DIR}/db1
  db_file_2=${TMP_DIR}/db2

  echo ".dump $table" | sqlite3 $db_file_1 > $TMP_DIR/${table}.txt
  # Prepare the merged table
  create_table_statement="$(cat $TMP_DIR/${table}.txt | grep CREATE)"
  sqlite3 $output_db "$create_table_statement"  # create ${table}
  # change the name of the input tables into ${table}1 and ${table}2
  sqlite3 $db_file_1 "ALTER table $table RENAME TO ${table}1;"
  sqlite3 $db_file_2 "ALTER table $table RENAME TO ${table}2;"

  echo ".dump ${table}1" | sqlite3 $db_file_1 > $TMP_DIR/${table}1.txt
  echo ".dump ${table}2" | sqlite3 $db_file_2 > $TMP_DIR/${table}2.txt

  cat $TMP_DIR/${table}1.txt > $TMP_DIR/new_db.txt
  cat $TMP_DIR/${table}2.txt >> $TMP_DIR/new_db.txt
  # The -separator option is not available on older sqlite3 utilities
  sqlite3 -header $db_file_1 "Select * from ${table}1;" | tr \| , > $TMP_DIR/data

  if [ ! -s $TMP_DIR/data ]; then
    sqlite3 -header $db_file_2 "Select * from ${table}2;" | tr \| , > $TMP_DIR/data
    if [ ! -s $TMP_DIR/data ]; then
      echo "At least one ${table} table should have data!"
      return 1
    fi
  fi

  # list with all columns separated by  ','
  cat $TMP_DIR/data | head -1 > $TMP_DIR/all_columns.txt
  # Eliminate first column (*_id) -- PRIMARY KEY
  cut -d ',' -f2- $TMP_DIR/all_columns.txt > $TMP_DIR/columns.txt
  columns="$(cat $TMP_DIR/columns.txt)"

  # Merge!
  sqlite3 $output_db < $TMP_DIR/new_db.txt      # create ${table}1 and ${table}2 (with data)
  # in $output_db should be three tables: ${table}, ${table}1 and ${table}2
  sqlite3 $output_db "insert into ${table} select * from ${table}1;"
  sqlite3 $output_db "insert into ${table} ($columns) select $columns from ${table}2;"
  # delete from $output_db ${table}1 and ${table}2 tables, keep ${table}
  sqlite3 $output_db "drop table ${table}1;"
  sqlite3 $output_db "drop table ${table}2;"

  echo "Success: $1 and $2 ${table} tables were merged in $output_db"

  clean_up
  return 0
}

cvmfs_server_merge_checks() {
  local db_file_1=$1
  local db_file_2=$2
  local output_db=$3
  local TMP_DIR=/tmp/cvmfs_server_merge_stats
  local tables1=""
  local tables2=""
  local repo_name_1=""
  local repo_name_2=""
  local schema_1=""
  local schema_2=""
  local schema_revision_1=""
  local schema_revision_2=""

  mkdir -p $TMP_DIR
  sqlite3 $db_file_1 "SELECT * from properties" > $TMP_DIR/properties_values_1
  sqlite3 $db_file_2 "SELECT * from properties" > $TMP_DIR/properties_values_2

  repo_name_1="$(cat $TMP_DIR/properties_values_1 | grep repo_name | cut -d '|' -f 2)"
  repo_name_2="$(cat $TMP_DIR/properties_values_2 | grep repo_name | cut -d '|' -f 2)"
  schema_1="$(cat $TMP_DIR/properties_values_1 | grep schema | cut -d '|' -f 2)"
  schema_2="$(cat $TMP_DIR/properties_values_2 | grep schema | cut -d '|' -f 2)"
  schema_revision_1="$(cat $TMP_DIR/properties_values_1 | grep schema_revision | cut -d '|' -f 2)"
  schema_revision_2="$(cat $TMP_DIR/properties_values_2 | grep schema_revision | cut -d '|' -f 2)"
  tables1="$(echo ".tables" | sqlite3 $db_file_1)"
  tables2="$(echo ".tables" | sqlite3 $db_file_2)"

  # Sanity checks
  if [ "x$repo_name_1" != "x$repo_name_2" ]; then
    echo "The given db files have different repo_name: $repo_name_1 vs $repo_name_2!"
    return 1
  fi
  if [ "x$schema_1" != "x$schema_2" ]; then
    echo "The given db files have different schema: $schema_1 vs $schema_2!"
    return 1
  fi
  if [ "x$schema_revision_1" != "x$schema_revision_2" ]; then
    echo "The given db files have different schema_revision: $schema_revision_1 vs $schema_revision_2!"
    return 1
  fi
  if [ "x$tables1" != "x$tables2" ]; then
    echo "The given db files have different tables!"
    return 1
  fi

  # Create properties table in the output db file and insert data into it
  echo ".dump properties" | sqlite3 $db_file_1 > $TMP_DIR/properties_table.txt
  cat $TMP_DIR/properties_table.txt > $TMP_DIR/new_db.txt
  sqlite3 $output_db < $TMP_DIR/new_db.txt
  return 0
}

cvmfs_server_merge_stats() {
  trap clean_up EXIT HUP INT TERM || return $?

  local output_db="output.db"   # default output file

  # optional parameter handling
  OPTIND=1
  while getopts "o:" option
  do
    case $option in
      o)
        output_db="$OPTARG"
      ;;
      ?)
        shift $(($OPTIND-2))
        usage "Command merge-stats: Unrecognized option: $1"
      ;;
    esac
  done
  shift $(($OPTIND-1))

  check_parameter_count 2 $#
  # Make sure the output file is empty
  echo "" > $output_db
  cvmfs_server_merge_checks $1 $2 $output_db
  cvmfs_server_merge_table $1 $2 $output_db "publish_statistics"
  cvmfs_server_merge_table $1 $2 $output_db "gc_statistics"
  return $?
}
################################################################################
#                                                                              #
#                              Environment Setup                               #
#                                                                              #
################################################################################

# Configuration variables for update-geodb -l.  May be overridden in
#   /etc/cvmfs/cvmfs_server_hooks.sh, /etc/cvmfs/server.local, or
#   per-repo in replica.conf.
# Default settings will attempt to update from cvmfs_server snapshot
#   in the 10 o'clock hour of Tuesday, every 2 weeks for openhtc or
#   every 4 weeks for maxmind.
CVMFS_GEO_AUTO_UPDATE=true # Automatically update from cvmfs_server snapshot
CVMFS_UPDATEGEO_SOURCE=openhtc # Database source: openhtc, maxmind, or none
CVMFS_UPDATEGEO_DAY=2   # Weekday of update, 0-6 where 0 is Sunday, default Tuesday
CVMFS_UPDATEGEO_HOUR=10 # First hour of day for update, 0-23, default 10am
CVMFS_UPDATEGEO_MINDAYS= # Minimum days between update attempts (set below)
CVMFS_UPDATEGEO_MAXDAYS= # Maximum days before considering it urgent (set below)

CVMFS_UPDATEGEO_DIR="/var/lib/cvmfs-server/geo" # Directory to download into
CVMFS_UPDATEGEO_DB=      # DB name (set below)
CVMFS_UPDATEGEO_OLDDB=   # DB name to remove if present (set below)
CVMFS_UPDATEGEO_URLBASE= # Base of URL for download (set below)
CVMFS_UPDATEGEO_URLSUFFIX= # Suffix to add to URLBASE for download

DEFAULT_LOCAL_STORAGE="/srv/cvmfs"

LATEST_JSON_INFO_SCHEMA=1

# Should we publish CVMFS and OS versions in meta.json?
# Set to any non-true string to turn off this feature.
CVMFS_PUBLISH_VERSIONS_IN_META_FILE=true

if [ -f /etc/cvmfs/server.local ]; then
  if [ -r /etc/cvmfs/server.local ]; then
    . /etc/cvmfs/server.local
  else
    echo "WARNING: cannot read /etc/cvmfs/server.local" >&2
  fi
fi

# setup server hooks: no-ops (overridable by /etc/cvmfs/cvmfs_server_hooks.sh)
transaction_before_hook() { :; }
transaction_after_hook() { :; }
abort_before_hook() { :; }
abort_after_hook() { :; }
publish_before_hook() { :; }
publish_after_hook() { :; }

cvmfs_sys_file_is_regular /etc/cvmfs/cvmfs_server_hooks.sh && . /etc/cvmfs/cvmfs_server_hooks.sh

if [ "$CVMFS_UPDATEGEO_SOURCE" = "openhtc" ]; then
  CVMFS_UPDATEGEO_MINDAYS=${CVMFS_UPDATEGEO_MINDAYS:-7}
  CVMFS_UPDATEGEO_MAXDAYS=${CVMFS_UPDATEGEO_MAXDAYS:-14}
  CVMFS_UPDATEGEO_OLDDB=GeoLite2-City.mmdb
  CVMFS_UPDATEGEO_DB="${CVMFS_UPDATEGEO_DB:-iplocation.mmdb}"
  CVMFS_UPDATEGEO_URLBASE="${CVMFS_UPDATEGEO_URLBASE:-https://geoipdb.openhtc.io}"
  CVMFS_UPDATEGEO_URLSUFFIX="${CVMFS_UPDATEGEO_URLSUFFIX:-/${CVMFS_UPDATEGEO_DB}.gz}"
elif [ "$CVMFS_UPDATEGEO_SOURCE" = "maxmind" ]; then
  CVMFS_UPDATEGEO_MINDAYS=${CVMFS_UPDATEGEO_MINDAYS:-14}
  CVMFS_UPDATEGEO_MAXDAYS=${CVMFS_UPDATEGEO_MAXDAYS:-28}
  CVMFS_UPDATEGEO_OLDDB=iplocation.mmdb
  CVMFS_UPDATEGEO_DB="${CVMFS_UPDATEGEO_DB:-GeoLite2-City.mmdb}"
  CVMFS_UPDATEGEO_URLBASE="${CVMFS_UPDATEGEO_URLBASE:-https://download.maxmind.com/geoip/databases/GeoLite2-City/download}"
  CVMFS_UPDATEGEO_URLSUFFIX="${CVMFS_UPDATEGEO_URLSUFFIX:-?suffix=tar.gz}"
elif [ -n "$CVMFS_UPDATEGEO_SOURCE" ] && [ "$CVMFS_UPDATEGEO_SOURCE" != "none" ]; then
  die "CVMFS_UPDATEGEO_SOURCE not openhtc, maxmind, or none"
fi

# Path to some useful sbin utilities
LSOF_BIN="$(find_sbin       lsof)"       || true
GETENFORCE_BIN="$(find_sbin getenforce)" || true
SESTATUS_BIN="$(find_sbin   sestatus)"   || true
GETCAP_BIN="$(find_sbin     getcap)"     || true
SETCAP_BIN="$(find_sbin     setcap)"     || true
MODPROBE_BIN="$(find_sbin   modprobe)"   || true
PIDOF_BIN="$(find_sbin      pidof)"      || true
RUNUSER_BIN="$(find_sbin    runuser)"    || true

# Find out how to deal with Apache
# (binary name, configuration directory, CLI, WSGI module name, ...)
if find_sbin httpd2 > /dev/null 2>&1; then # SLES/OpenSuSE
  APACHE_CONF="apache2"
  APACHE_BIN="$(find_sbin httpd2)"
  APACHE_CTL="$APACHE_BIN"
  APACHE_WSGI_MODPKG="apache2-mod_wsgi"
elif find_sbin apache2 > /dev/null 2>&1; then
  APACHE_CONF="apache2"
  APACHE_BIN="$(find_sbin apache2)"
  if find_sbin apachectl > /dev/null 2>&1; then # Debian
    APACHE_CTL="$(find_sbin apachectl)"
    APACHE_WSGI_MODPKG="libapache2-mod-wsgi"
  elif find_sbin apache2ctl > /dev/null 2>&1; then # Gentoo
    APACHE_CTL="$(find_sbin apache2ctl)"
    APACHE_WSGI_MODPKG="www-apache/mod_wsgi"
  fi
else # RedHat based
  APACHE_CONF="httpd"
  APACHE_BIN="/usr/sbin/httpd"
  APACHE_CTL="$APACHE_BIN"
  APACHE_WSGI_MODPKG="mod_wsgi"
fi

SUPERVISOR_BIN="false"
if [ -f /bin/supervisorctl ]; then
  SUPERVISOR_BIN=/bin/supervisorctl
fi
SERVICE_BIN="false"
if [ ! -f /bin/systemctl ]; then
  if cvmfs_sys_file_is_executable /sbin/service ; then
    SERVICE_BIN="/sbin/service"
  elif cvmfs_sys_file_is_executable /usr/sbin/service ; then
    SERVICE_BIN="/usr/sbin/service" # Ubuntu
  elif cvmfs_sys_file_is_executable /sbin/rc-service ; then
    SERVICE_BIN="/sbin/rc-service" # OpenRC
  else
    die "Neither systemd nor service binary detected"
  fi
fi

# Check if `runuser` is available on this system
# Note: at least Ubuntu in older versions doesn't provide this command
HAS_RUNUSER=0
if [ "x$RUNUSER_BIN" != "x" ]; then
  HAS_RUNUSER=1
fi

# standard values
CVMFS_DEFAULT_GENERATE_LEGACY_BULK_CHUNKS=false
CVMFS_DEFAULT_USE_FILE_CHUNKING=true
CVMFS_DEFAULT_MIN_CHUNK_SIZE=4194304
CVMFS_DEFAULT_AVG_CHUNK_SIZE=8388608
CVMFS_DEFAULT_MAX_CHUNK_SIZE=16777216
CVMFS_DEFAULT_ENFORCE_LIMITS=false
CVMFS_DEFAULT_AUTO_GC_LAPSE='1 day ago'

CVMFS_SERVER_DEBUG=${CVMFS_SERVER_DEBUG:=0}
CVMFS_SERVER_SWISSKNIFE="cvmfs_swissknife"
CVMFS_SERVER_SWISSKNIFE_DEBUG=$CVMFS_SERVER_SWISSKNIFE
# cvmfs_publish will eventually become cvmfs_server, removing the shell wrapper
CVMFS_SERVER_PUBLISH="/usr/bin/cvmfs_publish"
CVMFS_SERVER_PUBLISH_DEBUG=$CVMFS_SERVER_PUBLISH

# On newer Apache version, reloading is asynchonrous and not guaranteed to succeed.
# The integration test cases set this parameter to true.
CVMFS_SERVER_APACHE_RELOAD_IS_RESTART=${CVMFS_SERVER_APACHE_RELOAD_IS_RESTART:=false}

################################################################################
#                                                                              #
#                              Utility Functions                               #
#                                                                              #
################################################################################

# enable the debug mode?
if [ $CVMFS_SERVER_DEBUG -ne 0 ]; then
  if cvmfs_sys_file_is_regular /usr/bin/cvmfs_swissknife_debug ; then
    case $CVMFS_SERVER_DEBUG in
      1)
        # in case something breaks we are provided with a GDB prompt.
        CVMFS_SERVER_SWISSKNIFE_DEBUG="gdb --quiet --eval-command=run --eval-command=quit --args cvmfs_swissknife_debug"
      ;;
      2)
        # attach gdb and provide a prompt WITHOUT actual running the program
        CVMFS_SERVER_SWISSKNIFE_DEBUG="gdb --quiet --args cvmfs_swissknife_debug"
      ;;
      3)
        # do not attach gdb just run debug version
        CVMFS_SERVER_SWISSKNIFE_DEBUG="cvmfs_swissknife_debug"
      ;;
    esac
  else
    echo -e "WARNING: compile with CVMFS_SERVER_DEBUG to allow for debug mode!\nFalling back to release mode [cvmfs_swissknife]...."
  fi

  if cvmfs_sys_file_is_regular /usr/bin/cvmfs_publish_debug ; then
    case $CVMFS_SERVER_DEBUG in
      1)
        # in case something breaks we are provided with a GDB prompt.
        CVMFS_SERVER_PUBLISH_DEBUG="gdb --quiet --eval-command=run --eval-command=quit --args /usr/bin/cvmfs_publish_debug"
      ;;
      2)
        # attach gdb and provide a prompt WITHOUT actual running the program
        CVMFS_SERVER_PUBLISH_DEBUG="gdb --quiet --args /usr/bin/cvmfs_publish_debug"
      ;;
      3)
        # do not attach gdb just run debug version
        CVMFS_SERVER_PUBLISH_DEBUG="/usr/bin/cvmfs_publish_debug"
      ;;
    esac
  else
    echo -e "WARNING: compile with CVMFS_SERVER_DEBUG to allow for debug mode!\nFalling back to release mode [cvmfs_publish]...."
  fi
fi

APACHE_CONF_MODE_CONFD=1     # *.conf goes to ${APACHE_CONF}/conf.d
APACHE_CONF_MODE_CONFAVAIL=2 # *.conf goes to ${APACHE_CONF}/conf-available


################################################################################
#                                                                              #
#                                Entry Point                                   #
#                                                                              #
################################################################################

# check if there is at least a selected sub-command
if [ $# -lt 1 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
  usage
fi

## implement --version
if [ "$1" = "--version" ]; then
  echo "CernVM-FS version $(__swissknife --version)"
  exit 0
fi

# check if the given sub-command is known and, if so, call it
subcommand=$1
shift
if is_subcommand $subcommand; then
  # replace a dash (-) by an underscore (_) and call the requested sub-command
  # preserve spaces and quotes in the parameters: the eval removes the
  #   single quotes here, leaving "$@" for usual shell substitution
  eval "cvmfs_server_$(echo $subcommand | sed 's/-/_/g')" '"$@"'
else
  usage "Unrecognized command: $subcommand"
fi
