#!/bin/bash
# Common configuration tasks for CernVM-FS

SCRIPT_LOCATION=$(cd "$(dirname "$0")"; pwd)
INSTALL_BASE=$(echo "$SCRIPT_LOCATION" | sed -e 's/^\(.*\)\/bin$/\1/')
# workaround for fedora 42
INSTALL_BASE=$(echo "$INSTALL_BASE" | sed -e 's/^\(.*\)\/sbin$/\1/')
SBIN_BASE="$( [ x"$INSTALL_BASE" = x"/usr" ] && echo "/sbin" || echo "${INSTALL_BASE}/sbin" )"

if [ -f /etc/cvmfs/config.sh ]; then
  . /etc/cvmfs/config.sh
elif [ -f ${INSTALL_BASE}/etc/cvmfs/config.sh ]; then
  . ${INSTALL_BASE}/etc/cvmfs/config.sh
else
  echo "/etc/cvmfs/config.sh missing"
  exit 1
fi

sys_arch=$(uname)

case $sys_arch in
  Linux )
    if [ -x /sbin/service ]; then
      service="/sbin/service"
    elif [ -x /usr/sbin/service ]; then
      # Ubuntu
      service="/usr/sbin/service"
    elif [ -x /sbin/rc-service ] ; then
      # OpenRC
      service="/sbin/rc-service"
    elif [ -x /usr/bin/systemctl ] ; then
      # archlinux
      service="/usr/bin/systemctl"
    fi
    if [ -x /sbin/pidof ]; then
      pidof="/sbin/pidof"
    elif [ -x /bin/pidof ]; then
      # Ubuntu
      pidof="/bin/pidof"
    elif [ -x /usr/bin/pidof ]; then
      # Gentoo
      pidof="/usr/bin/pidof"
    fi
    if [ -x /sbin/fuser ]; then
      fuser="/sbin/fuser"  # RHEL
    else
      fuser="/bin/fuser"  # Ubuntu, SuSE
    fi
    if [ -x /usr/bin/fusermount ]; then
      fusermount="/usr/bin/fusermount"  # RHEL, SuSE
    else
      fusermount="/bin/fusermount"  # Ubuntu
    fi
    if [ ! -x /usr/bin/attr ]; then
      # Fedora
      if [ ! -x /bin/attr ]; then
        echo "attr utility required"
        exit 1
      fi
    fi
    fuse_fs_type="fuse"
    ;;

  Darwin )
    fuser="/usr/bin/fuser"
    fuse_fs_type="macfuse"
    ;;

  * )
    echo "Architecture $sys_arch is not supported"
    exit 1 ;;
esac

# This list of known parameters will be merged with all CVMFS_... environment
# variable for cvmfs_config showconfig.  It is useful to keep this list to show
# in cvmfs_config showconfig which known parameters are _not_ set.
parm_list="CVMFS_USER CVMFS_NFILES CVMFS_CACHE_BASE CVMFS_CACHE_DIR CVMFS_MOUNT_DIR CVMFS_QUOTA_LIMIT \
          CVMFS_SERVER_URL CVMFS_DEBUGLOG CVMFS_HTTP_PROXY \
          CERNVM_GRID_UI_VERSION CVMFS_SYSLOG_LEVEL CVMFS_SYSLOG_FACILITY CVMFS_TRACEFILE \
          CVMFS_TIMEOUT CVMFS_TIMEOUT_DIRECT CVMFS_PUBLIC_KEY CVMFS_KEYS_DIR \
          CVMFS_MAX_TTL CVMFS_RELOAD_SOCKETS CVMFS_DEFAULT_DOMAIN CVMFS_OOM_SCORE_ADJ \
          CVMFS_MEMCACHE_SIZE CVMFS_KCACHE_TIMEOUT CVMFS_ROOT_HASH CVMFS_REPOSITORY_TAG CVMFS_REPOSITORY_DATE CVMFS_REPOSITORIES \
          CVMFS_PROXY_RESET_AFTER CVMFS_MAX_RETRIES CVMFS_BACKOFF_INIT CVMFS_BACKOFF_MAX \
          CVMFS_ALIEN_CACHE CVMFS_TRUSTED_CERTS CVMFS_INITIAL_GENERATION \
          CVMFS_CONFIG_REPOSITORY CVMFS_LOW_SPEED_LIMIT CVMFS_FALLBACK_PROXY CVMFS_PROXY_TEMPLATE \
          CVMFS_FOLLOW_REDIRECTS CVMFS_MAX_IPADDR_PER_PROXY CVMFS_ALT_ROOT_PATH \
          CVMFS_IPFAMILY_PREFER CVMFS_DNS_RETRIES CVMFS_DNS_TIMEOUT \
          CVMFS_AUTHZ_HELPER CVMFS_AUTHZ_SEARCH_PATH CVMFS_WORKSPACE \
          CVMFS_EXTERNAL_SERVER_URL CVMFS_EXTERNAL_TIMEOUT CVMFS_EXTERNAL_TIMEOUT_DIRECT \
          CVMFS_EXTERNAL_HTTP_PROXY CVMFS_EXTERNAL_FALLBACK_PROXY CVMFS_CACHE_PRIMARY \
          CVMFS_CLIENT_PROFILE CVMFS_USE_CDN"
switch_list="CVMFS_IGNORE_SIGNATURE CVMFS_STRICT_MOUNT CVMFS_SHARED_CACHE \
          CVMFS_NFS_SOURCE CVMFS_NFS_SHARED CVMFS_CHECK_PERMISSIONS CVMFS_AUTO_UPDATE \
          CVMFS_MOUNT_RW CVMFS_SEND_INFO_HEADER CVMFS_USE_GEOAPI CVMFS_CLAIM_OWNERSHIP \
          CVMFS_HIDE_MAGIC_XATTRS CVMFS_SYSTEMD_NOKILL CVMFS_SERVER_CACHE_MODE \
          CVMFS_CONFIG_REPO_REQUIRED"
required_list="CVMFS_USER CVMFS_NFILES CVMFS_MOUNT_DIR CVMFS_STRICT_MOUNT CVMFS_RELOAD_SOCKETS \
               CVMFS_QUOTA_LIMIT CVMFS_CACHE_BASE CVMFS_SERVER_URL CVMFS_HTTP_PROXY \
               CVMFS_TIMEOUT CVMFS_TIMEOUT_DIRECT CVMFS_SHARED_CACHE CVMFS_CHECK_PERMISSIONS"
var_list="$parm_list $switch_list"

cvmfs_fs_bundle="/Library/Filesystems/cvmfs.fs/Contents/Resources" # Mac OS X

# Check if killall has extra arguments
# -f(orce): abort all cvmfs associated fuse devices
# -s(tuck): abort all cvmfs associated fuse devices in waiting state
parse_killall_opts(){
  for arg in "$@"; do
    [[ "$arg" == "killall"  ]] && shift && break
    shift
  done
  while [[ $# -gt 0  ]]; do
    case "$1" in
      "-r")
        abort_all_devices $(get_cvmfs_devices)
        ;;
      "-s")
        abort_stuck_devices $(get_devices_map)
        ;;
      "--help")
        cvmfs_config_usage
        ;;
      *)
        echo "Ignoring unknown option $1"
        break
        ;;
    esac
    shift
  done
}

# Given a device id in the form <Major>:<minor>
# we need only the <minor> part to identify the fuse device
get_minor_device_id()
{
  local device_id=$1
  echo $device_id | cut -d':' -f2
}

abort_fuse_device(){
  local fuse_device=$1
  local device_path="/sys/fs/fuse/connections/$fuse_device"
  if [ -d "$device_path" ]; then
    echo -n "Aborting fuse device $fuse_device.. "
    echo 1 > $device_path/abort
    echo "OK"
  fi
}

# Collect every device associated to a cvmfs mount point,
# based on /proc/<cvmfs2_pid>/mountinfo
get_cvmfs_devices()
{
  local cvmfs_devices=""
  for pid in $(pidof cvmfs2)
  do
    while read -r line; do
      # Get the interesting fields from every line of mountinfo;
      # those are the mountpoint and the device id
      device_id=$(echo "$line" | cut -d' ' -f 3)
      current_mountpoint=$(echo "$line" | cut -d' ' -f 5)

      if [[ "$current_mountpoint" == "/cvmfs/"*  ]]; then
        if ! grep -q "$device_id" <<< "$cvmfs_devices"; then
          cvmfs_devices=$cvmfs_devices" $device_id"
        fi
      fi
    done < /proc/$pid/mountinfo
  done
  echo $cvmfs_devices
}

get_devices_map()
{
  local devices_map=""
  for pid in $(pidof cvmfs2)
  do
    while read -r line; do
      # Get the interesting fields from every line of mountinfo;
      # those are the mountpoint and the device id
      device_id=$(echo "$line" | cut -d' ' -f 3)
      current_mountpoint=$(echo "$line" | cut -d' ' -f 5)

      if [[ "$current_mountpoint" == "/cvmfs/"*  ]]; then
        if ! grep -q "$device_id" <<< "$devices_map"; then
          devices_map="$current_mountpoint>$device_id "$devices_map
        fi
      fi
    done < /proc/$pid/mountinfo
  done
  echo $devices_map
}

abort_all_devices()
{
  echo "Aborting all cvmfs fuse devices.."
  local devices="$@"
  for device in $devices
  do
    abort_fuse_device $(get_minor_device_id $device)
  done
}

abort_stuck_devices()
{
  echo "Aborting stuck cvmfs fuse devices.."
  local device_map="$@"
  for map_entry in $device_map
  do
    local mountpoint=$(echo $map_entry | cut -d'>' -f 1)
    local device=$(echo $map_entry | cut -d'>' -f 2)

    fuse_dev=$(get_minor_device_id $device)
    local fuse_dev_path="/sys/fs/fuse/connections/$fuse_dev"
    # Abort waiting devices only
    if [ "$(cat $fuse_dev_path/waiting)" != "0" ] ;
    then
      echo "Aborting $mountpoint"
      abort_fuse_device $fuse_dev
    fi
  done
}

# 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"
}
version_major() { echo $1 | cut -d. -f1 | grep -oe '^[0-9]\+'; }
version_minor() { echo $1 | cut -d. -f2 | grep -oe '^[0-9]\+'; }
version_patch() { echo $1 | cut -d. -f3 | grep -oe '^[0-9]\+'; }
version_build() { echo $1 | cut -d- -f2 | 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 ]
}

cvmfs_config_usage() {
  echo "Common configuration tasks for CernVM-FS"
  echo "Usage: $0 <command>"
  echo "Commands:"
  echo "  setup [nouser] [nocfgmod] [nostart] [noautofs]"
  echo ""
  if is_wsl2; then
    echo "  wsl2_start"
    echo ""
  fi
  echo "  chksetup"
  echo ""
  echo "  showconfig [-s(hort output, only non-empty parameters)] [<repository>]"
  echo ""
  echo "  stat [-v | <repository>]"
  echo ""
  echo "  status [<repository list>]"
  echo ""
  echo "  probe [<repository list>]"
  echo ""
  echo "  fsck [-q(uick check for zero byte files only) | <cvmfs_fsck options>]"
  echo ""
  echo "  fuser <repository>"
  echo ""
  echo "  reload [-c | <repository>]"
  echo ""
  echo "  umount"
  echo ""
  echo "  wipecache"
  echo ""
  echo "  killall [-r(reset fuse) ] [-s(reset stuck fuse)]"
  echo ""
  echo "  bugreport [timeout_in_sec_per_command (default = 90)]"
}

has_selinux() {
  which getenforce > /dev/null 2>&1 && \
  which semodule   > /dev/null 2>&1 && \
  getenforce | grep -qi "enforc" || return 1
}

is_wsl2() {
  # Another possibility is looking for the /run/WSL directory
  grep -q microsoft-standard /proc/version 2>/dev/null
}

cvmfs_wsl2_start() {
  is_wsl2 || die "WSL2 not detected, exiting"
  [ $(id -u) -eq 0 ] || die "root privileges required"
  if $pidof automount >/dev/null; then
    echo "[WSL2] automounter already running"
    exit 0
  fi

  local automounter="/usr/sbin/automount"
  local automount_config=
  if [ -f /etc/sysconfig/autofs ]; then
    automount_config=/etc/sysconfig/autofs
  elif [ -f /etc/default/autofs ]; then
    automount_config=/etc/default/autofs
  fi

  local automounter=/usr/sbin/automount
  echo "[WSL2] starting automounter"
  $(
    if [ -n $automount_config ]; then
      . $automount_config
    fi
    $automounter $OPTIONS --pid-file /var/run/autofs.pid
  )

  # return value: process found
  $pidof automount >/dev/null
}

cvmfs_setup() {
  local nouser
  local nocfgmod
  local nostart
  nouser=0
  nocfgmod=0
  nostart=0

  cvmfs_readconfig || die "Failed to read CernVM-FS configuration"

  while [ $# -ne 0 ]
  do
    case "$1" in
      nouser)
         shift 1
         nouser=1
      ;;
      nocfgmod)
         shift 1
         nocfgmod=1
      ;;
      nostart)
         shift 1
         nostart=1
      ;;
      noautofs)
         shift 1
         nocfgmod=1
         nostart=1
      ;;
      *)
        echo "Bad option: $1"
        return 1
      ;;
    esac
  done

  mkdir -p /cvmfs
  # set permissions for home dir
  mkdir -p /var/lib/cvmfs

  if [ $nouser -eq 0 ]; then
    if ! check_group "cvmfs"; then
      if ! add_cvmfs_group; then
        echo "Cannot create a cvmfs group"
        exit 1
      fi
    fi

    if ! check_cvmfs_user; then
      if ! add_cvmfs_user; then
        echo "Cannot create a cvmfs user"
        exit 1
      fi
    fi
    chown cvmfs:cvmfs /var/lib/cvmfs
  fi


  # put workarounds for OS X in place (CernVM-FS poor-man's relocatability)
  if [ x"$sys_arch" = x"Darwin" ]; then
    # sweep legacy CernVM-FS installation that was not installed into /usr/local
    _usrlocal_migrate

    # symlink configuration files into /etc
    if [ ! -e /etc/cvmfs           ] && \
       [   -e /usr/local/etc/cvmfs ]; then
      ln -s /usr/local/etc/cvmfs /etc/cvmfs
    fi

    if [ ! -e ${cvmfs_fs_bundle}/mount_cvmfs ] && \
       [ ! -e /sbin/mount_cvmfs              ] && \
       [   -e /usr/local/sbin/mount_cvmfs ]; then
      if compare_versions $(sw_vers -productVersion) -ge "10.11"; then
        # create a (minimal) file system bundle for `mount -t cvmfs` support
        # Note: the `mount` utility in OS X 10.11.1 doesn't find the installed
        #       mount helper in `/usr/local/sbin`. When disabling SIP and sym-
        #       linking the mount helper into `/sbin` it would work. Not sure if
        #       that is to be considered a bug or a feature...
        #       Currently the bundle creation below is considered a work around!
        mkdir -p $cvmfs_fs_bundle
        ln -s /usr/local/sbin/mount_cvmfs ${cvmfs_fs_bundle}/mount_cvmfs
      else
        ln -s /usr/local/sbin/mount_cvmfs /sbin/mount_cvmfs
      fi
    fi
  fi

  # if group fuse exists, add user cvmfs
  if check_group "fuse"; then
    if ! add_user_to_group_fuse; then
      echo "Cannot add user cvmfs to group fuse"
      exit 1
    fi
  fi

  if [ $nocfgmod -eq 0 ]; then
    if ! configure_autofs; then
      echo "Cannot configure autofs!"
      exit 1
    fi

    # configure sudoers on Mac OS
    if ! configure_sudoers; then
      echo "Cannot configure sudoers"
      exit 1
    fi
  fi

  if [ $nostart -eq 0 ]; then
    activate_service autofs
  fi
}


cvmfs_chksetup() {
  local num_warnings
  local num_errors
  num_warnings=0
  num_errors=0

  cvmfs_readconfig || die "Failed to read CernVM-FS configuration"

  # Check binaries
  local binary
  for binary in cvmfs2 cvmfs_fsck cvmfs_talk
  do
    if ! test -f ${INSTALL_BASE}/bin/$binary; then
      echo "Error: $binary not found"
      num_errors=$(($num_errors+1))
    fi
  done

  if [ "$sys_arch" = "Darwin" ]; then
    cvmfs_libs="libcvmfs_fuse.dylib libcvmfs_fuse_debug.dylib"
  else
    cvmfs_libs="libcvmfs_fuse3.so libcvmfs_fuse3_debug.so"
  fi
  for library in $cvmfs_libs
  do
    foundlib=0
    for libdir in lib lib64 lib/x86_64-linux-gnu lib/i386-linux-gnu; do
      if test -f ${INSTALL_BASE}/${libdir}/${library}; then
        foundlib=1
      fi
    done
    if [ $foundlib -eq 0 ]; then
      echo "Error: $library not found"
      num_errors=$(($num_errors+1))
    fi
  done

  # Check mount helper
  local tools
  if [ "$sys_arch" = "Darwin" ]; then
    if compare_versions $(sw_vers -productVersion) -ge "10.11"; then
      tools="${cvmfs_fs_bundle}/mount_cvmfs"
    else
      tools="${SBIN_BASE}/mount_cvmfs"
    fi
  elif [ "$sys_arch" = "Linux" ]; then
    if [ -d ${SBIN_BASE} ]; then
      tools="${SBIN_BASE}/mount.cvmfs"
    else
      tools="${INSTALL_BASE}/bin/mount.cvmfs"
    fi
  fi

  local tool
  for tool in $tools $service
  do
    if [ ! -x $tool ]; then
      echo "Warning: failed to locate $tool"
      num_errors=$(($num_errors+1))
    fi
  done

  # Check that CVMFS_USER is set
  if [ x"$CVMFS_USER" = x"" ]; then
    echo "CVMFS_USER variable is empty"
    num_errors=$(($num_errors+1))
  fi

  # saving global mount directory configuration
  local global_mount_dir
  global_mount_dir=$CVMFS_MOUNT_DIR

  # No autofs integration on OS X
  if [ "$sys_arch" = "Linux" ]; then
    for path in /etc/auto.master.d/cvmfs.autofs \
                /etc/autofs/auto.master.d/cvmfs.autofs \
                /etc/auto.master \
                /etc/autofs/auto.master
    do
      if [ -f $path ]; then
        AUTOFSMAP=$path
        break;
      fi
    done
    if [ -z "$AUTOFSMAP" ]; then
      echo "Error: unable to find cvmfs.auto or auto.master in any of the supplied paths!"
      num_errors=$(($num_errors+1))
      AUTOFSMAP=/dev/null
    fi
    # Check that /etc/auto.cvmfs is referenced in autofs maps
    if ! grep -q "^$CVMFS_MOUNT_DIR[[:space:]]\+\(program:\|\)/etc/auto.cvmfs" $AUTOFSMAP 2> /dev/null; then
      if ! check_is_on CVMFS_NFS_SOURCE; then
        echo "Warning: CernVM-FS map is not referenced from autofs maps ($AUTOFSMAP)"
        num_warnings=$(($num_warnings+1))
      fi
    fi

    # Check that a drop-in autofs map is referenced by the auto master map
    for path in /etc/auto.master.d/cvmfs.autofs,/etc/auto.master \
      /etc/autofs/auto.master.d/cvmfs.autofs,/etc/autofs/auto.master
    do
      local drop_in=$(echo $path | cut -d, -f1)
      local auto_master=$(echo $path | cut -d, -f2)
      if [ -f $drop_in ]; then
        if ! grep -q "^+dir:$(dirname $drop_in)" $auto_master 2>/dev/null; then
          if ! check_is_on CVMFS_NFS_SOURCE; then
            echo "Warning: CernVM-FS drop-in map $drop_in is not referenced from autofs master map $auto_master"
            num_warnings=$(($num_warnings+1))
          fi
        fi
        break
      fi
    done

    # Check that /etc/auto.cvmfs is executable
    if [ ! -x /etc/auto.cvmfs ]; then
      echo "Error: /etc/auto.cvmfs is not executable"
      num_errors=$(($num_errors+1))
    fi

    # Check autofs' kill mode when systemd managed
    if [ -e /usr/bin/systemctl -a ! is_wsl2 ]; then
      if ! /usr/bin/systemctl show -p KillMode autofs.service | grep -q process;
      then
        echo "Warning: autofs kill mode is not 'process', busy CernVM-FS mount points are likely to be forcefully killed when autofs is restarted"
        num_warnings=$(($num_warnings+1))
      fi
    fi
  fi

  # Check that cvmfs user exists
  if ! check_cvmfs_user; then
    echo "Error, user $CVMFS_USER does not exist"
    num_errors=$(($num_errors+1))
  else
    # Check that cvmfs user is in fuse group (if the group exists)
    if check_group "fuse"; then
      if ! check_user_in_group $CVMFS_USER "fuse"; then
        echo "Warning: user $CVMFS_USER is not member of fuse group"
        num_warnings=$(($num_warnings+1))
      fi
    fi
  fi

  # Check that /dev/fuse is read/writable from cvmfs user
  if [ x"$sys_arch" = x"Darwin" ] && [ ! is_macfuse_enabled ]; then
    if ! check_fuse_t_installation; then
      echo "Error: FUSE-T is not installed."
      num_errors=$(($num_errors+1))
    fi
  elif [ x"$sys_arch" = x"Darwin" ]; then
    if [ ! -d /Library/Filesystems/macfuse.fs ]; then
      echo "Error: macFUSE is missing."
      num_errors=$(($num_errors+1))
    fi
  fi
  check_dev_fuse
  exit_code=$?
  if [ $exit_code -ne 0 ]; then
    num_errors=$(($num_errors+$exit_code))
  fi

  # Check that /etc/nsswitch.conf is readable from cvmfs user
  if [ -f /etc/nsswitch.conf ]; then
    if ! runascvmfsuser "test -r /etc/nsswitch.conf"; then
      echo "Error: /etc/nsswitch not readable by cvmfs user"
      num_errors=$(($num_errors+1))
    fi
  fi

  # Check that automount is running
  if ! check_auto_mounter; then
    if ! check_is_on CVMFS_NFS_SOURCE; then
      echo "Warning: autofs service is not running"
      num_warnings=$(($num_warnings+1))
    fi
  fi

  # Check SELinux label of cache directory
  if has_selinux && semodule --list-modules 2> /dev/null | grep -q cvmfs; then
    local expected_label="cvmfs_cache_t"
    local cache_ctx="$(stat --format='%C' $CVMFS_CACHE_BASE)"
    if [ $? -ne 0 ]; then
      echo "Warning: cannot retrieve SELinux context from cache directory"
      num_warnings=$(($num_warnings+1))
    else
      local cache_label="$(echo \"$cache_ctx\" | cut --delimiter=':' --field=3)"
      if [ x"$cache_label" != x"$expected_label" ]; then
        echo "Warning: SELinux enabled, but cache directory ($CVMFS_CACHE_BASE) labeled '$cache_label' instead of '$expected_label'"
        num_warnings=$(($num_warnings+1))
      fi
    fi
  fi

  # Check CVMFS_CONFIG_REPOSITORY not set in late config files
  for conf_file in /etc/cvmfs/default.local \
    $(find /etc/cvmfs/domain.d -name '*.conf') \
    $(find /etc/cvmfs/domain.d -name '*.local') \
    $(find /etc/cvmfs/config.d -name '*.conf') \
    $(find /etc/cvmfs/config.d -name '*.local'); do
    if [ -f $conf_file ]; then
      if grep -q ^CVMFS_CONFIG_REPOSITORY= $conf_file; then
        echo "Error: CVMFS_CONFIG_REPOSITORY can only be set in /etc/cvmfs/default.conf and /etc/cvmfs/default.d/*.conf (not in $conf_file)"
        num_errors=$(($num_errors+1))
      fi
    fi
  done

  # Check repository specific settings
  local repo_list="$(list_repos)"
  local repo
  for repo in $repo_list
  do
    cvmfs_readconfig
    local fqrn; fqrn=`cvmfs_mkfqrn $repo`
    for var in $var_list
    do
      unset $var 2>/dev/null || true
    done

    if ! cvmfs_readconfig $fqrn; then
      echo "Error: failed to read configuration for $fqrn"
      num_errors=$(($num_errors+1))
    else
      # Check that cache directories are read-writable by cvmfs user
      local cache_dir
      if check_is_on CVMFS_SHARED_CACHE; then
        cache_dir="$CVMFS_CACHE_BASE/shared"
      else
        cache_dir="$CVMFS_CACHE_BASE/$fqrn"
      fi
      if [ -d $cache_dir ]; then
        if ! runascvmfsuser "test -O $cache_dir"; then
          echo "Error: $cache_dir is not owned by cvmfs user"
          num_errors=$(($num_errors+1))
        fi
        if ! runascvmfsuser "test -r $cache_dir"; then
          echo "Error: $cache_dir is not readable by cvmfs user"
          num_errors=$(($num_errors+1))
        fi
        if ! runascvmfsuser "test -w $cache_dir"; then
          echo "Error: $cache_dir is not writable by cvmfs user"
          num_errors=$(($num_errors+1))
        fi
        if  [ "x$CVMFS_CACHE_PRIMARY" = "x" -o  "x$CVMFS_CACHE_PRIMARY" = "xposix" ]; then
          if [ "x$CVMFS_ALIEN_CACHE" != "x" ]; then
            for i in {0..255}
              do
                local chunk_dir; chunk_dir="${CVMFS_ALIEN_CACHE}/`printf   \"%.2x\n\" $i`"
                  if ! runascvmfsuser "test -d $chunk_dir "; then
                    echo "Error: chunk directory $chunk_dir missing in alien cache."
                    num_errors=$(($num_errors+1))
                  fi
              done
          else
            if [ "$(ls -A $cache_dir)" ]; then
              for i in {0..255}
                do
                  local chunk_dir; chunk_dir="$cache_dir/`printf \"%.2x\n\" $i`"
                    if [ ! -d $chunk_dir ]; then
                      echo "Error: chunk directory $chunk_dir missing.  Is   tmpwatch interfering?"
                      num_errors=$(($num_errors+1))
                    fi
                done
            fi
          fi
        fi  # POSIX cache
      fi

      # Check readability of config files
      for config_file in /etc/cvmfs/default.conf /etc/cvmfs/default.local \
                         /etc/cvmfs/default.d/*.conf \
                         /etc/cvmfs/domain.d/*.conf /etc/cvmfs/domain.d/*.local \
                         /etc/cvmfs/config.d/*.conf /etc/cvmfs/config.d/*.local
      do
        if [ -f $config_file ]; then
          if ! runascvmfsuser "test -r $config_file"; then
            echo "Error: $config_file is not readable by cvmfs user"
            num_errors=$(($num_errors+1))
          fi
        fi
      done

      # Check readability of hosts file
      local host_file=/etc/hosts
      if [ ! -z $HOST_ALIASES ]; then
        host_file="$HOST_ALIASES"
      fi
      if ! runascvmfsuser "test -r $host_file"; then
        echo "Error: $host_file is not readable by cvmfs user"
        num_errors=$(($num_errors+1))
      fi

      # Check that number of open files is reasonably high
      if [ ${CVMFS_NFILES:-0} -lt 8192 ]; then
        echo "Warning: maximum number of open files is low ($CVMFS_NFILES) for $fqrn"
        num_warnings=$(($num_warnings+1))
      fi

      # Check for tracer or debuglog
      if [ ! -z $CVMFS_DEBUGLOG ]; then
        echo "Warning: debug mode is on for $fqrn"
        num_warnings=$(($num_warnings+1))
      fi

      if [ ! -z $CVMFS_TRACEFILE ]; then
        echo "Warning: trace mode is on for $fqrn"
        num_warnings=$(($num_warnings+1))
      fi

      # Check that log file directories are writable
      for parameter in CVMFS_USYSLOG CVMFS_DEBUGLOG CVMFS_TRACEFILE; do
        eval value=\$$parameter
        if [ ! -z $value ]; then
          if ! runascvmfsuser "test -w $(dirname $value)"; then
            echo "Error: log file directory $parameter=$value not writable for $fqrn"
            num_errors=$(($num_errors+1))
          fi
        fi
      done

      # Check syslog level / facility
      if [ ! -z $CVMFS_SYSLOG_LEVEL ]; then
        if [ $CVMFS_SYSLOG_LEVEL -lt 1 ] || [ $CVMFS_SYSLOG_LEVEL -gt 3 ]; then
          echo "Error: invalid value for CVMFS_SYSLOG_LEVEL ($CVMFS_SYSLOG_LEVEL) for $fqrn"
          num_errors=$(($num_errors+1))
        fi
      fi
      if [ ! -z $CVMFS_SYSLOG_FACILITY ]; then
        if ! expr "$CVMFS_SYSLOG_FACILITY" : '[0,1,2,3,4,5,6,7]' > /dev/null; then
          echo -n "Error: invalid value for CVMFS_SYSLOG_FACILITY ($CVMFS_SYSLOG_FACILITY) for $fqrn. "
          echo "Valid entries: 0..7"
          num_errors=$(($num_errors+1))
        fi
      fi

      # Check quota limit
      if [ ${CVMFS_QUOTA_LIMIT:-0} -ne -1 ]; then
        if [ ${CVMFS_QUOTA_LIMIT:-0} -lt 1000 ]; then
          echo "Warning: cache limit for $fqrn is very low (below 1GB)"
          num_warnings=$(($num_warnings+1))
        fi
      else
        if [ -z "$CVMFS_ALIEN_CACHE" ]; then
          echo "Warning: no cache quota set for $fqrn.  Cvmfs will abort if the cache partition is full."
          num_warnings=$(($num_warnings+1))
        fi
      fi

      # Alien cache and quota / shared cache mutually exclusive
      if [ "x$CVMFS_ALIEN_CACHE" != "x" ]; then
        if check_is_on CVMFS_SHARED_CACHE; then
          echo "Error: alien cache and shared cache are mutually exclusive ($fqrn)"
          num_errors=$(($num_errors+1))
        fi
        if [ $CVMFS_QUOTA_LIMIT -ge 0 ]; then
          echo "Error: alien cache and managed cache quota are mutually exclusive ($fqrn)"
          num_errors=$(($num_errors+1))
        fi
      fi

      # Compare quota limit with available space on the file system hosting the cache
      if [ ${CVMFS_QUOTA_LIMIT:-0} -ge 0 ]; then
        local cache_dir_parent="$cache_dir"
        while [ ! -d "$cache_dir_parent" ]; do
          cache_dir_parent=$(dirname "$cache_dir_parent")
        done
        local total_kb=$(df -Pk "$cache_dir_parent" | tail -n 1 | awk '{print $2}')
        local total_mb=$(($total_kb/1024))
        if [ ${CVMFS_QUOTA_LIMIT:-0} -gt $total_mb ]; then
          echo "Error: file system hosting the cache for $fqrn too small (quota: ${CVMFS_QUOTA_LIMIT}M, total space: ${total_mb}M)."
          num_errors=$(($num_errors+1))
        fi
        if [ $((${CVMFS_QUOTA_LIMIT:-0}*115/100)) -gt $total_mb ]; then
          echo "Warning: less than 15% safety margin on file system hosting the cache for $fqrn (quota: ${CVMFS_QUOTA_LIMIT}M, total space: ${total_mb}M)."
          num_warnings=$(($num_warnings+1))
        fi
      fi

      # Syntax check for switches
      for parameter in $switch_list; do
        eval value=\$$parameter
        if [ "x$value" != "x" ]; then
          local valid=0
          for literal in "yes" "YES" "no" "NO" "on" "ON" "off" "OFF" "1" "0"
          do
            if [ "$value" = $literal ]; then
              valid=1
            fi
          done
          if [ $valid -eq 0 ]; then
            echo "Error: invalid value for $parameter (valid values: yes/no) for $fqrn"
            num_errors=$(($num_errors+1))
          fi
        fi
      done

      if [ ! -z "$CVMFS_NFS_SHARED" ]; then
        if ! check_is_on CVMFS_NFS_SOURCE; then
          echo "Warning: Setting CVMFS_NFS_SHARED has no effect without CVMFS_NFS_SOURCE."
          num_warnings=$(($num_warnings+1))
        fi
      fi

      # Check CVMFS_MOUNT_DIR
      if [ -z $CVMFS_MOUNT_DIR ] || [ ! -d $CVMFS_MOUNT_DIR ] || [ "$global_mount_dir" != "$CVMFS_MOUNT_DIR" ]; then
        echo "Error: invalid CVMFS_MOUNT_DIR for $fqrn"
        num_errors=$(($num_errors+1))
      fi

      # Check Keys
      if [ -z "$CVMFS_KEYS_DIR" -a -z "$CVMFS_PUBLIC_KEY" ]; then
        echo "Warning: no public key (CVMFS_KEYS_DIR or CVMFS_PUBLIC_KEY) defined for $fqrn"
        num_warnings=$(($num_warnings+1))
      else
        if [ -z "$CVMFS_PUBLIC_KEY" ]; then
          if [ ! -d "$CVMFS_KEYS_DIR" ]; then
            echo "Error: public key location $CVMFS_KEYS_DIR is not a directory"
            num_errors=$(($num_errors+1))
          else
            if [ $(find "${CVMFS_KEYS_DIR}" -maxdepth 1 -name '*.pub' | wc -l) -eq 0 ]; then
              echo "Error: no public keys in $CVMFS_KEYS_DIR"
              num_errors=$(($num_errors+1))
            fi
          fi
        else
          for k in `echo $CVMFS_PUBLIC_KEY | tr ':' " "`
            do
              if [ ! -f "$k" ]; then
                echo "Error: public key $k for $fqrn not accessible"
                num_errors=$(($num_errors+1))
              fi
            done
        fi
      fi

      # Check for required variables
      for reqvar in $required_list
      do
        eval value=\$$reqvar
        if [ -z "$value" ]; then
          echo "Error: required parameter $reqvar undefined for $fqrn"
          num_errors=$(($num_errors+1))
        fi
      done

      # Check for network
      local uuid=$(uuidgen 2>/dev/null)
      if [ $? -ne 0 ]; then
        # fallback: use following on Debian based systems
        uuid=$(uuid -v4)
      fi
      if [ ! -z "$CVMFS_HTTP_PROXY" -a ! -z "$CVMFS_SERVER_URL"  ]; then
        server_list=`echo "$CVMFS_SERVER_URL" | sed -e 's/,/ /g' -e 's/;/ /g'`
        for server in $server_list
        do
          local proxy_list
          proxy_list=`echo "$CVMFS_HTTP_PROXY $CVMFS_FALLBACK_PROXY" | sed -e 's/;/ /g' -e 's/|/ /g'`
          local first_proxy=
          for proxy in $proxy_list
          do
            org=`cvmfs_getorg $fqrn`
            url=`echo $server | sed s/@org@/$org/g | sed s/@fqrn@/$fqrn/g`
            url="${url}/.cvmfspublished"
            if [ "x$proxy" == "xauto" ]; then
              auto_proxy=$(cvmfs2 __wpad__ auto "$CVMFS_SERVER_URL" 2>/dev/null)
              if [ "x$auto_proxy" == "x" ]; then
                echo "Warning: failed to resolve auto proxy for $url"
                num_warnings=$(($num_warnings+1))
                continue
              fi
              proxy="$auto_proxy"
            fi
            if [ "x$proxy" != "xDIRECT" ]; then
              if echo "$proxy" | grep -q -v ^http; then
                echo "Warning: $proxy does not start with http(s)://"
                num_warnings=$(($num_warnings+1))
                continue
              fi
              proxy_param="env http_proxy=$proxy"
              timeout=$CVMFS_TIMEOUT
            else
              proxy_param=
              timeout=$CVMFS_TIMEOUT_DIRECT
            fi
            $proxy_param curl -H "Pragma:" -f --max-time  $timeout $url > /dev/null 2>&1
            if [ $? -ne 0 ]; then
              echo "Warning: failed to access $url through proxy $proxy"
              num_warnings=$(($num_warnings+1))
            fi

            # Check Geo-API
            if [ "x$first_proxy" = "x" ]; then
              if [ ! -z "$CVMFS_USE_GEOAPI" ]; then
                if check_is_on CVMFS_USE_GEOAPI; then
                  local server_name=$(echo $server | sed s,^http://,, | cut -d/ -f1 | sed 's/:[0-9]*$//')
                  url=$(echo $server | sed s/@org@/$org/g | sed s/@fqrn@/$fqrn/g)
                  url="${url}/api/v1.0/geo/${uuid}/$server_name"
                  $proxy_param curl -H "Pragma:" -f --max-time $timeout $url > /dev/null 2>&1
                  if [ $? -ne 0 ]; then
                    echo "Warning: failed to use Geo-API with ${server_name}"
                    num_warnings=$(($num_warnings+1))
                  fi
                fi
              fi
            fi
            first_proxy="taken"
          done
        done
      fi
    fi
  done

  if [ $(($num_warnings+$num_errors)) -eq 0 ]; then
    echo "OK"
  fi

  return $num_errors
}


cvmfs_showconfig() {
  local fqrn
  local org
  local retval
  local short_output=0
  local short_opt=
  org=$1
  if [ "x$org" = "x-s" ]; then
    short_output=1
    short_opt="-s "
    shift 1
    org=$1
  fi

  cvmfs_readconfig || die "Failed to read CernVM-FS configuration"
  if [ -z "$org" ]; then
    list="$(list_repos)"
    for entry in $list
    do
      echo
      echo "Running $0 $short_opt$entry:"
      cvmfs_showconfig $short_opt$entry
    done
    return 0
  fi

  fqrn=`cvmfs_mkfqrn $org`
  org=`cvmfs_getorg $fqrn`
  cvmfs_readconfig $fqrn || die "Failed to read CernVM-FS configuration"
  retval=$?
  if [ $retval -ne 0 ]; then
    return $retval
  fi

  local setting=
  local all_vars=
  while read setting; do
    [ "x${setting:0:6}" != "xCVMFS_" ] && continue
    var=$(echo "$setting" | cut -d= -f1)
    all_vars="$all_vars $var"
  done <<< "$CVMFS_PARMS"
  all_vars="$(echo $all_vars $var_list | tr ' ' '\n' | sort -u)"

  echo "CVMFS_REPOSITORY_NAME=$fqrn"
  local var=
  while read var; do
    if [ "x$var" = "xCVMFS_CACHE_DIR" ]; then
      if [ ! -z "$CVMFS_CACHE_DIR" -o $short_output -eq 0 ]; then
        echo "CVMFS_CACHE_DIR=$CVMFS_CACHE_DIR"
      fi
      continue
    fi
    line=$(echo "$CVMFS_PARMS" | grep "^${var}=")
    if [ "x$line" != "x" ]; then
      if [ "x$org" != "x" ]; then
        line=`echo "$line" | sed s/@org@/$org/g`
      fi
      if [ "x$fqrn" != "x" ]; then
        line=`echo "$line" | sed s/@fqrn@/$fqrn/g`
      fi
      echo "$line"
    else
      [ $short_output -eq 0 ] && echo "${var}="
    fi
  done <<< "$all_vars"
}


cvmfs_stat() {
  local fqrn
  local org
  local retval
  local verbose; verbose=0
  if [ "x$1" = "x-v" ]; then
    verbose=1
    shift
  fi
  local mounted_repos
  org=$1

  mounted_repos=`list_mounts | awk '{print $3}'`

  if [ -z "$org" ]; then
    for mountpoint in $mounted_repos
    do
      cd $mountpoint || exit 32
      local this_fqrn
      get_attr fqrn; this_fqrn=$attr_value
      echo
      echo "Running $0 stat $this_fqrn:"
      if [ $verbose -eq 1 ]; then
        cvmfs_stat -v $this_fqrn
      else
        cvmfs_stat $this_fqrn
      fi
    done
    return 0
  fi

  cvmfs_readconfig || die "Failed to read CernVM-FS configuration"
  fqrn=`cvmfs_mkfqrn $org`
  org=`cvmfs_getorg $fqrn`
  local active=0
  for mountpoint in $mounted_repos
  do
    cd $mountpoint 2>/dev/null || continue
    local this_fqrn
    get_attr fqrn; this_fqrn=$attr_value
    if [ "$this_fqrn" = "$fqrn" ]; then
      active=1
      break
    fi
  done
  if [ $active -eq 0 ]; then
    echo "$fqrn not mounted"
    return 1
  fi

  get_attr version; version=$attr_value
  get_attr pid; pid=$attr_value
  get_attr uptime; uptime=$attr_value
  memory=`ps -p $pid -o rss= | sed 's/ //g'` || exit 3
  get_attr revision; revision=$attr_value
  get_attr root_hash; root_hash=$attr_value
  get_attr expires; expires=$attr_value
  get_attr nclg; nclg=$attr_value
  cache_use=`df -P . | tail -n 1 | awk '{print int($3)}'` || exit 34
  cache_avail=`df -P . | tail -n 1 | awk '{print int($4)}'` || exit 34
  cache_max=$(($cache_use+$cache_avail))
  get_attr usedfd; usedfd=$attr_value
  get_attr useddirp; useddirp=$attr_value
  get_attr maxfd; maxfd=$attr_value
  get_attr nioerr; nioerr=$attr_value
  get_attr host; host=$attr_value
  get_attr proxy; proxy=$attr_value
  get_attr timeout; timeout=$attr_value
  get_attr timeout_direct; timeout_direct=$attr_value
  timeout_effective=$timeout
  proxy_effective=$proxy
  if [ "$proxy" == "DIRECT" ]; then
    proxy_effective=
    timeout_effective=$timeout_direct
  fi
  env http_proxy=$proxy_effective curl -H "Pragma:" -f --max-time $timeout_effective ${host}/.cvmfspublished >/dev/null 2>&1
  if [ $? -eq 0 ]; then
    online=1
  else
    online=0
  fi
  get_attr nopen; nopen=$attr_value
  get_attr ndiropen; ndiropen=$attr_value
  get_attr ndownload; ndownload=$attr_value
  get_attr hitrate; hitrate=$attr_value
  get_attr rx; rx=$attr_value
  get_attr speed; speed=$attr_value

  if [ $verbose -eq 1 ]; then
    echo "Version: $version"
    echo "PID: $pid"
    echo "Uptime: $uptime minutes"
    echo "Memory Usage: ${memory}k"
    echo "File Catalog Revision: $revision (expires in ${expires} minutes)"
    echo "File Catalog ID: $root_hash"
    echo "No. Active File Catalogs: $nclg"
    echo "Cache Usage: ${cache_use}k / ${cache_max}k"
    echo "File Descriptor Usage: $usedfd / $maxfd"
    echo "No. Open Directories: $useddirp"
    echo "No. IO Errors: $nioerr"
    echo -n "Connection: $host through proxy $proxy"
    if [ $online -eq 1 ]; then
      echo " (online)"
    else
      echo " (offline)"
    fi
    echo "Usage: $nopen open() calls (hitrate ${hitrate}%), $ndiropen opendir() calls"
    echo "Transfer Statistics: ${rx}k read, avg. speed: ${speed}k/s"
  else
    echo "VERSION PID UPTIME(M) MEM(K) REVISION EXPIRES(M) NOCATALOGS CACHEUSE(K) CACHEMAX(K) NOFDUSE NOFDMAX NOIOERR NOOPEN HITRATE(%) RX(K) SPEED(K/S) HOST PROXY ONLINE"
    echo "$version $pid $uptime $memory $revision $expires $nclg $cache_use $cache_max $usedfd $maxfd $nioerr $nopen $hitrate $rx $speed $host $proxy $online"
  fi
}


cvmfs_status() {
  local list
  local repos="$@"
  list=""

  RETVAL=0

  local mounted_repos
  local mountpoints
  local processed
  mounted_repos="$(echo $(list_mounts | awk '{print $3}'))"
  if [ -n "$repos" ]; then
    cvmfs_readconfig || die "Failed to read CernVM-FS configuration"
    for repo in $repos; do
      mountpoint="$CVMFS_MOUNT_DIR/$repo"
      if [[ " $mounted_repos " != *" $mountpoint "* ]]; then
        echo $mountpoint is not mounted
        RETVAL=1
      else
        mountpoints="$mountpoints $mountpoint"
      fi
    done
  else
    mountpoints="$mounted_repos"
  fi
  for mountpoint in $mountpoints
  do
    local fqrn=
    local pid=
    cd $mountpoint
    if [ $? -ne 0 ]; then
      echo "mountpoint $mountpoint inaccessible"
      RETVAL=1
      continue
    fi
    get_attr fqrn; fqrn=$attr_value
    get_attr pid; pid=$attr_value
    echo -n "$fqrn mounted on $mountpoint with pid $pid"
    if [ -z "$fqrn" -o -z "$pid" ]; then
      echo " - mountpoint unhealthy!"
      RETVAL=1
    else
      echo
    fi
    if [ -z "$repos" ]; then
      continue
    fi
    if [ "$sys_arch" = "Darwin" ]; then
      # no more detail is available on macOS
      continue
    fi
    # provide more detail if specific repo(s) requested
    local s1="$(attr -qg host $mountpoint)"
    if [ -z "$s1" ]; then
      echo "- could not read host from $mountpoint"
      RETVAL=1
      continue
    fi
    local last_snapshot="$(curl -s -m 5 "$s1/.cvmfs_status.json" | sed -n 's/"//g;s/,$//;s/.*last_snapshot[^ ]* //p')"
    if [ -z "$last_snapshot" ]; then
      echo "- could not read last_snapshot from $s1/.cvmfs_status.json"
      RETVAL=1
      continue
    fi
    local age="$(($(date +%s) - $(date --date "$last_snapshot" +%s)))"
    echo "- mounted for $(attr -qg uptime $mountpoint) minutes"
    echo "- host ${s1%/*/*}"
    local external_host="$(attr -qg external_host $mountpoint)"
    if [ -n "$external_host" ]; then
      echo "- external host $external_host"
    fi
    echo "- proxy $(attr -qg proxy $mountpoint)"
    echo "- host updated $(($age / 60)) minutes and $(($age % 60)) seconds ago"
    local host_list="$(attr -qg host_list $mountpoint)"
    local max_revision=0
    local max_rev_host=""
    for host in ${host_list//;/ }; do
      local rev="$(curl -s -m 3 "$host/.cvmfspublished" | cat -v | sed -n 's/^S//p')"
      # Note that later hosts are queried later so they get more time to
      # catch up. That might give them a little advantage.
      if [ -n "$rev" ] && [ "$rev" -gt $max_revision ]; then
        max_revision=$rev
        max_rev_host=$host
      fi
    done
    local revision=$(attr -qg revision $mountpoint)
    if [ "$revision" = "$max_revision" ]; then
      echo "- revision $revision matches most current host"
    else
      echo "- current revision is $(attr -qg revision $mountpoint) and most current host is revision $max_revision"
      echo "- most current host ${max_rev_host%/*/*}"
    fi
    echo "- i/o errors since mount: $(attr -qg nioerr $mountpoint)"
    echo "- cache hitrate since mount: $(attr -qg hitrate $mountpoint)"
    echo "- cache cleanups in last 24 hours: $(attr -qg ncleanup24 $mountpoint)"
    echo "- end of log (for all do attr -g logbuffer $mountpoint):"
    attr -qg logbuffer $mountpoint|tail -10
  done

  return $RETVAL
}



_cvmfs_reload_pause() {
  # Helper function to put a particular repository in sleep mode
  local mountpoint="$1"
  local debug_flag=""
 
  if [ "x${CVMFS_DEBUGLOG}" != "x" ]; then
    debug_flag="--debug"
  fi

  cd "$mountpoint" 2>/dev/null || return
  get_attr fqrn ${mountpoint}; fqrn=$attr_value
  get_attr pid ${mountpoint}; pid=$attr_value
  if [ -z "$fqrn" ] || [ -z "$pid" ]; then
    # This repository must have been unmounted just now
    cd /
    return 1
  fi
  echo "Pausing ${fqrn} on ${mountpoint}"
  cd "$mountpoint" &&
    rm -f ${CVMFS_RELOAD_SOCKETS}/cvmfs.pause/$(echo -n "$mountpoint" | base64_nowrap) && \
    cvmfs2 __RELOAD__ ${CVMFS_RELOAD_SOCKETS}/cvmfs.$fqrn stop_and_go ${debug_flag} | \
    awk -v PREFIX="$fqrn: " '{print PREFIX $0}' &
  cd /
  echo $pid > ${CVMFS_RELOAD_SOCKETS}/pid.cvmfs.${fqrn}.paused
  echo ${CVMFS_RELOAD_SOCKETS}/cvmfs.${fqrn}.paused >> "${CVMFS_RELOAD_SOCKETS}/guard_files"
  return 0
}

_cvmfs_reload_waitfor() {
  local guard_file="$1"
  # Helper function to wait for a particular repository being put into pause mode
  while [ ! -f ${guard_file}* ]; do
    sleep 1
  done
  if [ -f ${guard_file}.crashed ]; then
    echo "RELOADING FAILED (${guard_file}.crashed)" 1>&2
    rm -f "$(dirname $guard_file)/pid.$(basename $guard_file)" ${guard_file}.crashed
  fi
}


cvmfs_reload() {
  # Run the reload command with clean environment to remove potential cvmfs
  # dependencies
  if [ "x$__CVMFS_CLEAN_ENV__" = "x" ]; then
    exec env -i __CVMFS_CLEAN_ENV__=1 $(get_minimal_path) $0 reload $@
  fi

  cvmfs_readconfig || die "Failed to read CernVM-FS configuration"
  local clean_cache; clean_cache=0
  local cache_directories=
  local debug_flag=""
  if [ "x$1" = "x-c" ]; then
    clean_cache=1
    shift
    cache_directories=$(list_cache_directories)
  elif [ "x$1" != "x" ]; then
    if [ "x${CVMFS_DEBUGLOG}" != "x" ]; then
      debug_flag="--debug"
    fi

    org=$1
    fqrn=$(cvmfs_mkfqrn $org)
    cvmfs2 __RELOAD__ ${CVMFS_RELOAD_SOCKETS}/cvmfs.$fqrn ${debug_flag}
    return $?
  fi

  # Organized reload of all repositories
  if [ ! -d "$CVMFS_RELOAD_SOCKETS" ]; then
    local mount_list
    mount_list=`list_mounts | awk '{print $3}'`
    if [ x"$mount_list" != "x" ]; then
      echo "$CVMFS_RELOAD_SOCKETS is not a directory.  No hotpatch performed."
      echo "Nevertheless, there are mounted cvmfs repositories."
      return 1
    fi
    return 0
  fi

  if [ "x$CVMFS_CONFIG_REPOSITORY" != "x" ]; then
    # Ensure that we capture the config repository as well
    cd "${CVMFS_MOUNT_DIR}/${CVMFS_CONFIG_REPOSITORY}" 2>/dev/null
  fi
  # TODO(jblomer): here is a pretty big race: fuse clients could have passed
  # the check for this directory in their mount helper but are not yet fully
  # mounted.  This in result can lead to old quota manager staying alive during
  # a reload.  Need to be fixed with the overhaul of the fuse client's
  # initialization code.
  mkdir "$CVMFS_RELOAD_SOCKETS/cvmfs.pause" 2>/dev/null
  if [ $? -ne 0 ]; then
    echo "Another reload process has not finished yet"
    return 1
  fi
  trap "rm -f $CVMFS_RELOAD_SOCKETS/cvmfs.pause/*; rmdir $CVMFS_RELOAD_SOCKETS/cvmfs.pause" EXIT

  RETVAL=0
  local mount_list
  mount_list=`list_mounts | awk '{print $3}'`
  # Make sure that if autofs unmounts any of these guys, they (but only they)
  # can be remounted
  for m in $mount_list
  do
    touch ${CVMFS_RELOAD_SOCKETS}/cvmfs.pause/$(echo -n "$m" | base64_nowrap)
  done

  # Avoid deadlock if cwd is on cvmfs
  cd /

  rm -f "${CVMFS_RELOAD_SOCKETS}/pid.*" "${CVMFS_RELOAD_SOCKETS}/guard_files*"
  trap "echo ' *** reloading CernVM-FS repositories must not be interrupted at this point *** '" HUP INT QUIT TERM
  local m
  for m in $mount_list
  do
    if [ x"$m" = x"$(canonicalize_path ${CVMFS_MOUNT_DIR}/${CVMFS_CONFIG_REPOSITORY})" ]; then
      # We need to finish pausing the other repositories first because they
      # might try to load the blacklist from the config repository as part of
      # their reload
      continue
    fi
    _cvmfs_reload_pause "$m"
  done
  if [ -f "${CVMFS_RELOAD_SOCKETS}/guard_files" ]; then
    # Wait for the repository to be hybernated
    sort -u "${CVMFS_RELOAD_SOCKETS}/guard_files" > "${CVMFS_RELOAD_SOCKETS}/guard_files.tmp"
    mv "${CVMFS_RELOAD_SOCKETS}/guard_files.tmp" "${CVMFS_RELOAD_SOCKETS}/guard_files"
    for guard_file in $(cat "${CVMFS_RELOAD_SOCKETS}/guard_files"); do
      _cvmfs_reload_waitfor "$guard_file"
    done
  fi

  if [ "x${CVMFS_CONFIG_REPOSITORY}" != "x" ]; then
    # If we have a config repository, now is the time to pause it at the very end
    _cvmfs_reload_pause "${CVMFS_MOUNT_DIR}/${CVMFS_CONFIG_REPOSITORY}" &&
      _cvmfs_reload_waitfor "${CVMFS_RELOAD_SOCKETS}/cvmfs.${CVMFS_CONFIG_REPOSITORY}.paused"
  fi
  rm -f "${CVMFS_RELOAD_SOCKETS}/guard_files"

  if [ $clean_cache -eq 1 ]; then
    for dir in $cache_directories; do
      echo -n "Wiping out $dir ... "
      if [ -d "$dir" ]; then
        local num_nfs_maps=$(find $dir -maxdepth 1 -type d -name 'nfs_maps.*' | wc -l)
        if [ $num_nfs_maps -ne 0 ]; then
          echo "not supported for NFS exported client, leaving directory untouched!"
        else
          rm -rf "$dir" && \
            mkdir -p "$dir" && \
            chown $CVMFS_USER "$dir"
            if [ "$sys_arch" = "Darwin" ]; then
              sleep 15
            fi
          if [ $? -ne 0 ]; then
            echo "Failed!"
            RETVAL=1
          else
            echo "OK"
          fi
        fi
      else
        echo "OK"
      fi
    done
  fi

  if stat ${CVMFS_RELOAD_SOCKETS}/pid.* >/dev/null 2>&1; then
    if [ "x$CVMFS_CONFIG_REPOSITORY" != "x" ]; then
      touch ${CVMFS_RELOAD_SOCKETS}/cvmfs.pause/$(echo -n "${CVMFS_MOUNT_DIR}/${CVMFS_CONFIG_REPOSITORY}" | base64_nowrap)
    fi
    for pidfile in ${CVMFS_RELOAD_SOCKETS}/pid.*; do
      kill -s USR1 $(cat $pidfile)
      rm -f $pidfile
    done
  fi
  wait

  return $RETVAL
}

cvmfs_probe() {
  RETVAL=0

  cvmfs_readconfig || die "Failed to read CernVM-FS configuration"
  local list
  if [ $# -gt 0 ]; then
    list="$@"
  else
    list="$(list_repos)"
  fi

  local org
  for org in $list
  do
    case $org in
      none)
      ;;
      *)
        . /etc/cvmfs/config.sh # start with fresh repository_... functions
        cvmfs_readconfig || die "Failed to read CernVM-FS configuration"
        fqrn=`cvmfs_mkfqrn $org`
        cvmfs_readconfig $fqrn || die "Failed to read CernVM-FS configuration"
        echo -n "Probing $CVMFS_MOUNT_DIR/$fqrn... "
        ls "$CVMFS_MOUNT_DIR/$fqrn" > /dev/null 2>&1  # workaround for FC28+
        if [ "$sys_arch" = "Darwin" ] && ! is_macfuse_enabled; then
          df -P "$CVMFS_MOUNT_DIR/$fqrn" 2>&1 | grep -q "$fqrn"
        else 
          df -P "$CVMFS_MOUNT_DIR/$fqrn" 2>&1 | grep -q ^cvmfs2
        fi
        if [ $? -ne 0 ]; then
          echo "Failed!"
          RETVAL=1
        else
          echo "OK"
        fi
      ;;
    esac
  done

  return $RETVAL
}

cvmfs_config_fsck() {
  local retval=0
  cvmfs_readconfig || die "Failed to read CernVM-FS configuration"
  local cache_directories=$(list_cache_directories)

  local dir
  for dir in $cache_directories; do
    if [ ! -d $dir ]; then
      continue
    fi

    if [ "x$1" = "x-q" ]; then
      # hardcode hashes for the (legitimately) empty file:
      # SHA1: full hash of empty file: e8ec3d88b62ebf526e4e5a4ff6162a3aa48a6b78
      ignore_empty_hashes=" ! -name ec3d88b62ebf526e4e5a4ff6162a3aa48a6b78 "
      # RMD160: full hash of empty file: fcdb20b894f31f1306d56294c049ca7293dde0fc
      ignore_empty_hashes="$ignore_empty_hashes ! -name db20b894f31f1306d56294c049ca7293dde0fc-rmd160 "
      # SHAKE128: full hash of empty file: 28d94b83b7229b331fc601362b8e272e02665122
      ignore_empty_hashes="$ignore_empty_hashes ! -name d94b83b7229b331fc601362b8e272e02665122-shake128 "
      for zeroed_file in $(find $dir -empty -wholename "$dir/[a-z0-9][a-z0-9]/*" ${ignore_empty_hashes}); do
        echo "WARNING: unexpected zero-byte file $zeroed_file -  Moving it to quarantaine."
        mv $zeroed_file $dir/quarantaine
        retval=1
      done
    else
      echo "CernVM-FS fsck on $dir"
      cvmfs_fsck $@ $dir
      retval=$(($? + $retval))
    fi
  done

  return $retval
}


cvmfs_config_fuser() {
  if [ "$sys_arch" = "Darwin" ]; then
    echo "The fuser command is unsupported on macOS"
    return 1
  fi

  local retval=0
  [ $# -eq 1 ] || die "one repository name required"
  local fqrn="$1"

  local deviceid
  deviceid="$(cvmfs_talk -i $fqrn device id)"
  local retval=$?
  if [ $retval != 0 ]; then
    return $retval
  fi
  [ -n "$deviceid" ] || die "Could not obtain device id for $fqrn"

  local skip_afs_opt=
  if mountpoint /afs >/dev/null 2>&1; then
    skip_afs_opt="-e /afs"
  fi
  lsof $skip_afs_opt | awk '{if ($6 == "'"${deviceid/:/,}"'") print $2}' | sort -un | paste -sd ' '
}

cvmfs_umount() {
  local silent=$1
  local force=$2
  local umount_options=
  if [ "x$force" != "x" ]; then
    umount_options="-l"
  fi

  local mount_list
  mount_list=`list_mounts | awk '{print $3}'`

  RETVAL=0
  local m
  for m in $mount_list
  do
    [ "x$silent" != "x" ] || echo -n "Unmounting ${m}: "
    if [ "$sys_arch" = "Darwin" ] && ! is_macfuse_enabled; then
      umount -f $umount_options $m 2>/dev/null
    else
      umount $umount_options $m 2>/dev/null
    fi
    if [ $? -ne 0 ]; then
      RETVAL=1
      if [ "x$silent" = "x" ]; then
        echo "Failed!"
        if [ "$sys_arch" = "Linux" ]; then
          $fuser -m -a -v $m
        else
          $fuser -u -c $m
        fi
      fi
    else
      [ "x$silent" != "x" ] || echo "OK"
    fi
  done

  return $RETVAL
}


cvmfs_killall() {
  local pid=$$

  parse_killall_opts "$@"

  echo -n "Terminating cvmfs_config processes... "
  # The exact match (pgrep -x) works only on Linux.  On macOS, one needs to
  # use the more coarse grained match against the full command line.
  local pgrep_opt=
  if [ "$sys_arch" = "Linux" ]; then
    pgrep_opt="-x"
  else
    pgrep_opt="-f"
  fi
  local config_pids=$(pgrep $pgrep_opt cvmfs_config)
  other_config_pids=
  for p in $config_pids; do
    if [ $p -ne $pid ]; then
      other_config_pids="$p $other_config_pids"
    fi
  done
  if [ "x$other_config_pids" != "x" ]; then
    kill -s KILL $other_config_pids
    waitfor cvmfs_config 10
    if [ $? -ne 0 ]; then
      echo "Failed!"
      return 1
    fi
  fi
  echo "OK"

  echo -n "Terminating cvmfs2 processes... "
  # killall doesn't work because the cvmfs2 binary might have been replaced
  pkill -SIGKILL -f ${INSTALL_BASE}/bin/cvmfs2 >/dev/null 2>&1
  waitfor cvmfs2 10 ${INSTALL_BASE}/bin/cvmfs2
  if [ $? -ne 0 ]; then
    echo "Failed!"
    return 1
  fi
  echo "OK"

  echo -n "Unmounting stale mount points... "
  cvmfs_umount silent
  if [ $? -ne 0 ]; then
    cvmfs_umount silent force
    if [ $? -ne 0 ]; then
      echo "Failed!"
      return 1
    fi
  fi
  echo "OK"

  echo -n "Cleaning up run-time variable data... "
  rm -rf /var/run/cvmfs/*
  echo "OK"

  $pidof automount > /dev/null
  if [ $? -eq 0 ]; then
    echo -n "Reloading autofs... "
    reload_service autofs
    echo "OK"
  fi

  return 0
}


cvmfs_bugreport() {
  tmpdir=`mktemp -d -t cvmfs-bugreport.XXXXXX` || exit 1
  cd $tmpdir
  mkdir bugreport_$(date -I)_$(hostname)
  cd bugreport_$(date -I)_$(hostname)

  local timeout_sec
  if [ -z "$1" ]; then
    timeout_sec=90  # default: 90sec per command until it is killed
  else
    timeout_sec=$1
  fi

  dist_default="/etc/cvmfs/default.d/*.conf"
  for file in /etc/cvmfs/default.conf $dist_default /etc/cvmfs/default.local
  do
    if [ -f $file ]; then
      . $file
    fi
  done

  echo "Gathering /etc/cvmfs"
  mkdir etc
  cp -r /etc/cvmfs etc/

  echo "Gathering /var/run/cvmfs"
  mkdir -p var/run/cvmfs
  # omit to copy sockets
  find /var/run/cvmfs -maxdepth 1 -type f -exec cp "{}" var/run/cvmfs  \;
  # keep correct directory hierarchy
  if [ -d /var/run/cvmfs/cvmfs.pause ]; then
    mkdir -p var/run/cvmfs/cvmfs.pause
    cp /var/run/cvmfs/cvmfs.pause/* var/run/cvmfs/cvmfs.pause
  fi


  echo "Gathering files in quarantaine"
  for repo in `ls "$CVMFS_CACHE_BASE"`
  do
    qdir="${CVMFS_CACHE_BASE}/${repo}/quarantaine"
      if [ -d "$qdir" ]; then
        tar cfz quarantaine-${repo}.tar.gz "$qdir" 2>/dev/null
      fi
  done

  echo "Gathering stack traces"
  for repo in `ls "$CVMFS_CACHE_BASE"`
  do
    mkdir -p stacktraces-$repo
    find ${CVMFS_CACHE_BASE}/${repo} -maxdepth 1 -name 'stacktrace*' \
      -exec cp {} stacktraces-$repo/ \;
  done

  local commands

  cvmfs_cmds=('cvmfs2 --version' 'cvmfs_config probe' 'cvmfs_config status' \
              'cvmfs_config showconfig' 'cvmfs_config chksetup' \
              'cvmfs_config stat -v' 'cvmfs_talk internal affairs' \
              "find ${CVMFS_CACHE_BASE} -maxdepth 1 -exec ls -lah  \{\} \;")

  # always run this last as "df" might hang
  common_cmds=('uname -a' 'hostname -f' 'ifconfig -a' 'ls -lR /var/run/cvmfs' \
               'cat /etc/fuse.conf' 'cat /etc/exports' 'cat /etc/hosts' 'id cvmfs' \
               'mount' 'ps -ef' 'df -h')

  case $sys_arch in
    Linux )
      commands=('find /usr/lib /usr/lib64 /lib /lib64 -name libfuse*' \
                'cat /etc/issue' \
                'grep cvmfs /var/log/messages' 'grep cvmfs /var/log/syslog' \
                'journalctl -alm /usr/bin/cvmfs2' \
                'journalctl -alm /usr/libexec/cvmfs/cache/cvmfs_cache_ram' \
                'ls -la /usr/bin/fusermount' 'ls -la /bin/fusermount' \
                'cat /etc/auto.master' 'cat /etc/autofs/auto.master' 'cat /etc/sysconfig/autofs' \
                'cat /etc/default/autofs' 'cat /etc/conf.d/autofs' 'journalctl -almu autofs.service' \
                'cat /etc/fstab' 'cat /proc/mounts' 'cat /proc/cpuinfo' \
                'free -m' 'systemctl show autofs.service' \
                'ls -la /etc/auto.master.d/*' 'cat /etc/auto.master.d/*' \
                "${cvmfs_cmds[@]}" "${common_cmds[@]}")
      ;;

    Darwin )
      commands=('sw_vers' 'syslog | grep cvmfs' \
                'grep cvmfs /var/log/system.log' 'grep cvmfs /var/log/kernel.log' \
                'cat /Library/Filesystems/macfuse.fs/Contents/version.plist' \
                'cat /etc/auto_master' 'sysctl vfs.autofs' \
                "${cvmfs_cmds[@]}" "${common_cmds[@]}" )
      ;;

    * )
      echo "Architecture $sys_arch is not supported"
      exit 1 ;;
  esac

  for cmd in "${commands[@]}"
   do
      out="`echo $cmd | tr -Cd [:alnum:]`.stdout"
      err="`echo $cmd | tr -Cd [:alnum:]`.stderr"
      echo "Gathering $cmd"
      echo "$cmd" > $out

      case $sys_arch in
        Linux )
          timeout -s 9 ${timeout_sec} sh -c "$cmd >> $out 2>$err & wait"
          ret="$?"
          if [ "$ret" -eq "124" ] || [ "$ret" -eq "137" ]; then
            # write to stdout and into the files $out and $err
            # add some new lines so that the error stands in a new line
            echo -e "\nERROR! Command '$cmd' killed by timeout of length: ${timeout_sec} seconds\n" \
              | tee -a "$out" "$err"
          fi
        ;;
        Darwin )
          # base installation on mac does not have any "timeout" cmd
          $cmd >> $out 2>$err
        ;;
      esac
   done

   cd ..
   tar cfz cvmfs-bugreport_$(date -I)_$(hostname).tar.gz *
   rm -rf bugreport_$(date -I)_$(hostname)

   echo
   echo "System information has been collected in ${tmpdir}/cvmfs-bugreport_$(date -I)_$(hostname).tar.gz"
   echo "Please attach this file to your problem description and send it as a"
   echo "bug report to https://github.com/cvmfs/cvmfs/issues"
}


die() {
  echo -e $1 >&2
  exit 1
}


waitfor() {
  local process=$1
  local timeout=$2
  local fullpath="$3"
  local pid=$$

  local seconds=0
  while [ $seconds -lt $timeout ]; do
    local procs=$(pgrep -x $process)
    local num_other_procs=0
    for p in $procs; do
      # Not myself
      if [ $p -eq $pid ]; then
        continue;
      fi
      # No zombies
      local pstate=$(ps -p "$p" -o state=)
      if echo "$pstate" | grep -q "Z"; then
        continue
      fi
      # Only the processes started from the right location
      if [ "x$fullpath" != "x" ]; then
        local cmdline=$(ps -p "$p" -o command=)
        if echo "$cmdline" | grep -v -q "^$fullpath"; then
          continue
        fi
      fi
      # Not the CernVM instance of cvmfs
      case $sys_arch in
        Linux )
          if [ "x$(cat /proc/$p/cmdline 2>/dev/null | head -c1)" = "x@" ]; then
            continue
          fi
        ;;
      esac
      num_other_procs=$(($num_other_procs + 1))
    done
    if [ $num_other_procs -eq 0 ]; then
      return 0
    fi
    sleep 1
    seconds=$(($seconds + 1))
  done

  return 1
}


list_mounts() {
  # Matches lines that begin with cvmfs2 or /usr/bin/cvmfs2 (/bin/cvmfs2,
  # /usr/local/bin/cvmfs2) on Linux or lines that contain fuse-t on macOS
  if [ x"$sys_arch" = x"Darwin" ] && ! is_macfuse_enabled; then
    mount -t "nfs" | grep "fuse-t"
  else
    mount -t $fuse_fs_type | grep "^\(/[^[:space:]]*/\)\?cvmfs2[[:space:]]"
  fi
}


# cvmfs_readconfig must have been called before
list_repos() {
  if [ "x$CVMFS_REPOSITORIES" != "x" ]; then
    echo "$(echo $CVMFS_REPOSITORIES | sed 's/,/ /g')"
  fi
}


# Some of the cache directories might not exist yet.  It's necessary to list
# them nevertheless to avoid a race in cvmfs_config reload.
list_cache_directories() {
  local repos="$(list_repos)"
  local org
  for org in $repos shared
  do
    . /etc/cvmfs/config.sh # start with fresh repository_... functions
    cvmfs_readconfig || die "Failed to read CernVM-FS configuration"
    if [ "$org" != "shared" ]; then
      fqrn=`cvmfs_mkfqrn $org`
      cvmfs_readconfig $fqrn || die "Failed to read CernVM-FS configuration"
    else
      fqrn=shared
    fi
    cache_directories="${CVMFS_CACHE_BASE}/${fqrn} $cache_directories"
  done
  echo "$cache_directories" | tr ' ' '\n' | sort -u | tr '\n' ' '

  . /etc/cvmfs/config.sh # start with fresh repository_... functions
  cvmfs_readconfig || die "Failed to read CernVM-FS configuration"
}


add_cvmfs_group() {
  case $sys_arch in
    Linux )
     if ! check_group "cvmfs"; then
       /usr/sbin/groupadd -r cvmfs
     fi ;;

    Darwin )
      if ! check_group "cvmfs"; then
        #find un-used group id starting from 10000
        local gid=10000
        while true; do
          if [ -z $(sudo dscl . search /groups PrimaryGroupID $gid | cut -f1 -s) ]; then
            break
          fi
          gid=$((${gid}+1))
        done

        #create cvmfs group
        sudo dscl . -create /Groups/cvmfs
        sudo dscl . -create /Groups/cvmfs gid $gid
        sudo dscl . -create /Groups/cvmfs RealName "CVMFS Group"
        sudo dscl . -create /Groups/cvmfs Password "*"
      fi ;;

   * )
     echo "Architecture $sys_arch is not supported"
     exit 1 ;;
  esac
}


add_cvmfs_user() {
  case $sys_arch in
    Linux )
      if ! check_cvmfs_user; then
        /usr/sbin/useradd -r -g cvmfs -d /var/lib/cvmfs -s /sbin/nologin -c "CernVM-FS service account" cvmfs
      fi
    ;;
    Darwin )
      if ! check_cvmfs_user; then
        #find un-used user id starting from 10000
        local uid=10000
        local gid
        while true; do
          if [ -z $(sudo dscl . search /users UniqueID $uid | cut -f1 -s) ]; then
            break
          fi
          uid=$((${uid}+1))
        done

        if check_group "cvmfs"; then
          gid=$(dscl . read /groups/cvmfs PrimaryGroupID | awk {'print $NF'})
        else
          echo "CVMFS group does not exists."
          exit 1
        fi

        #create cvmfs user
        sudo dscl . -create /Users/cvmfs
        sudo dscl . -create /Users/cvmfs UserShell /sbin/nologin
        sudo dscl . -create /Users/cvmfs RealName "CVMFS User"
        sudo dscl . -create /Users/cvmfs UniqueID $uid
        sudo dscl . -create /Users/cvmfs PrimaryGroupID $gid
        sudo dscl . -create /Users/cvmfs NFSHomeDirectory "/var/lib/cvmfs"
        sudo dscl . -passwd /Users/cvmfs "*"

        #Hide user from login screen
        sudo defaults write /Library/Preferences/com.apple.loginwindow HiddenUsersList -array-add cvmfs
      fi
    ;;
    * )
      echo "Architecture $sys_arch is not supported"
      exit 1 ;;
  esac
}


add_user_to_group_fuse() {
  case $sys_arch in
    Linux )
      if ! id -Gn cvmfs | grep -q fuse; then
        groups=$(id -Gn cvmfs | sed 's/ /,/')
        groups="${groups},fuse"
         /usr/sbin/usermod -G $groups cvmfs
      fi ;;

    Darwin )
      if ! dscl . read /Groups/fuse GroupMembership 2>&1 | grep -q cvmfs; then
        dscl . -append /Groups/fuse GroupMembership cvmfs
      fi ;;

    * )
      echo "Architecture $sys_arch is not supported"
      exit 1 ;;
  esac
}


check_auto_mounter() {
  case $sys_arch in
    Linux )
        if [[ $service == *systemctl ]]; then
            $service status autofs >/dev/null 2>&1
            return $?
        else
            $service autofs status > /dev/null 2>&1
            return $?
        fi ;;
    Darwin )
      ps aux | grep autofsd | grep -v grep &> /dev/null
      return $? ;;

    * )
     echo "Architecture $sys_arch is not supported"
     exit 1 ;;
  esac
}


check_group() {
  local group=$1
  case $sys_arch in
    Linux )
      /usr/bin/getent group $group &> /dev/null
      return $? ;;

    Darwin )
      dscl . read /groups/$group &> /dev/null
      return $? ;;

    * )
      echo "Architecture $sys_arch is not supported"
      exit 1 ;;
  esac
}


check_cvmfs_user() {
  case $sys_arch in
    Linux )
      /usr/bin/getent passwd cvmfs &> /dev/null
      return $? ;;

    Darwin )
      dscl . read /users/cvmfs &> /dev/null
      return $? ;;

    * )
      echo "Architecture $sys_arch is not supported"
      exit 1 ;;
  esac
}

# Expected to be ran only on macOS
check_fuse_t_installation() {
  local product="fuse-t"
  local nfsV4ServerLink="/usr/local/bin/go-nfsv4"
  local dylibPath="/usr/local/lib/lib${product}.dylib"
  local staticLibPath="/usr/local/lib/lib${product}.a"

  # Check for existence of required files and links
  local paths=("$nfsV4ServerLink" "$dylibPath" "$staticLibPath")
  for path in "${paths[@]}"; do
    if [ ! -e "$path" ]; then
      echo "Error: Missing required file or directory: $path"
      return 1
    fi
  done
  return 0
}

is_macfuse_enabled() {
  if [ x"$CVMFS_USE_MACFUSE_KEXT" = x ]; then
    return 1
  fi
  local value=$(echo "$CVMFS_USE_MACFUSE_KEXT" | tr '[:upper:]' '[:lower:]')
  case "$value" in
    yes|on|1|true)
      return 0 ;;
    no|off|0|false)
      return 1 ;;
    *)
      echo "Error: invalid value of CVMFS_USE_MACFUSE_KEXT: $CVMFS_USE_MACFUSE_KEXT"
      echo "Valid values are: yes, on, 1, true, no, off, 0, false"
      return 1 ;;
  esac
}

check_dev_fuse() {
  local charDevice
  local return_code

  case $sys_arch in
    Linux )
      charDevice=/dev/fuse ;;

    Darwin )
      if ! is_macfuse_enabled; then
      # Since we rely on user-space FUSE-T now the check for /dev/<device_name> isn't necessary
        exit 0
      fi
      charDevice=/dev/macfuse ;;
    * )
     echo "Architecture $sys_arch is not supported"
     exit 1 ;;
  esac
  return_code=0
  if [ ! -c $charDevice ]; then
    if [ x"$sys_arch" = x"Linux" ]; then
      echo "Error: character device $charDevice does not exist"
      return_code=$(($return_code+1))
    elif [ x"$sys_arch" = x"Darwin" ]; then
      echo -n "Warning: character device $charDevice does not exist. "
      echo "Please make sure that macFUSE is installed."
    fi
  else
    if ! runascvmfsuser "test -r $charDevice"; then
      echo "Error: $charDevice is not readable by cvmfs user"
      return_code=$(($return_code+1))
    fi
    if ! runascvmfsuser "test -w $charDevice"; then
      echo "Error: $charDevice is not writable by cvmfs user"
      return_code=$(($return_code+1))
    fi
  fi

  return $return_code
}


check_user_in_group() {
  local user=$1
  local group=$2

  case $sys_arch in
    Linux )
      getent group $group | grep -q "[^A-Za-z0-9]$user\([^A-Za-z0-9]\|$\)"
      return $? ;;

    Darwin )
      dscl . read /groups/$group GroupMembership | grep -q $user
      return $? ;;

    * )
      echo "Architecture $sys_arch is not supported"
      exit 1 ;;
  esac
}

activate_service() {
  local svc=$1

  case $sys_arch in
    Linux )
      if is_wsl2; then
        echo "[WSL2] skipping activation of $1"
	echo "[WSL2] start CernVM-FS with 'sudo cvmfs_config wsl2_start'"
        exit 0
      fi

      if [ -e /usr/bin/systemctl ]; then
        /usr/bin/systemctl start -q ${svc}.service
        /usr/bin/systemctl enable -q ${svc}.service
      elif [ -e /sbin/service -o -e /usr/sbin/service ]; then
        local servicebin=/sbin/service
        if [ ! -e $servicebin ]; then
          servicebin=/usr/sbin/service
        fi
        $servicebin $svc status >/dev/null 2>&1
        if [ $? -ne 0 ]; then
          $servicebin $svc start >/dev/null
        fi

        if [ -e /sbin/chkconfig ]; then
          # Prevent on upstart managed systems
          if [ -f /etc/init.d/$svc -a ! -L /etc/init.d/$svc ]; then
            /sbin/chkconfig $svc on
          fi
        fi
      else
        echo "Utility to manage services missing"
        exit 1
      fi
    ;;
    Darwin )
      # Not supported at this point
      :
    ;;
    * )
      echo "Architecture $sys_arch is not supported"
      exit 1
    ;;
  esac
}


reload_service() {
  local svc=$1

  case $sys_arch in
    Linux )
      if is_wsl2; then
        [ "$svc" == "autofs" ] || die "[WSL2] internal error, expected autofs service ('$svc')"
	echo -n "WSL2 automounter "
	local autofs_pid=$(cat /var/run/autofs.pid)
	/usr/bin/kill -HUP $autofs_pid
	return 0
      fi

      if [ -e /usr/bin/systemctl ]; then
        /usr/bin/systemctl reload -q ${svc}.service
      elif [ -e /sbin/service -o -e /usr/sbin/service ]; then
        local servicebin=/sbin/service
        if [ ! -e $servicebin ]; then
          servicebin=/usr/sbin/service
        fi
        $servicebin $svc reload >/dev/null 2>&1
      else
        echo "Utility to manage services missing"
        exit 1
      fi
    ;;
    Darwin )
      # Not supported at this point
      :
    ;;
    * )
      echo "Architecture $sys_arch is not supported"
      exit 1
    ;;
  esac
}


configure_autofs() {
  case $sys_arch in
    Linux )
      for path in /etc/auto.master /etc/autofs/auto.master; do
        if [ -f $path ]; then
          AUTOMASTER=$path
          break
        fi
      done
      if [ -z "$AUTOMASTER" ]; then
        echo "Error: unable to find auto.master in any of the supplied paths!"
        num_errors=$(($num_errors+1))
        AUTOMASTER=/dev/null
      fi
      for path in /etc/auto.master.d /etc/autofs/auto.master.d; do
        if [ -d $path ]; then
          AUTOFSMAPSDIR=$path
          break
        fi
      done
      # Fix 'systemctl restart autofs' on CentOS 7 (CVM-1200)
      if [ -e /usr/bin/systemctl -a ! is_wsl2 ]; then
        if /usr/bin/systemctl show -p KillMode autofs.service | \
           grep -q control-group;
        then
          mkdir -p /etc/systemd/system/autofs.service.d
          cat > /etc/systemd/system/autofs.service.d/cvmfs-autosetup.conf << EOF
[Service]
KillMode=process
EOF
          /usr/bin/systemctl daemon-reload
        fi
      fi

      local cvmfs_map="$CVMFS_MOUNT_DIR /etc/auto.cvmfs"
      if [ -z "$AUTOFSMAPSDIR" ]; then
        if ! grep -q "^$cvmfs_map" $AUTOMASTER; then
          echo "$cvmfs_map" >> $AUTOMASTER
          $pidof automount > /dev/null && reload_service autofs || true
        fi
      else
        AUTOFSMAP="$AUTOFSMAPSDIR/cvmfs.autofs"
        if ! grep -q "^$cvmfs_map" $AUTOFSMAP 2>/dev/null; then
          echo -e "# automatically generated by CernVM-FS\n${cvmfs_map}" >> $AUTOFSMAP
          # Remove the entry in auto.master from previous cvmfs versions
          sed -i "\\,^${cvmfs_map},d" $AUTOMASTER
          $pidof automount > /dev/null && reload_service autofs || true
        fi
      fi
    ;;

   Darwin )
     # Since MacOS 10.15 Catalina it is not possible anymore (without disabling
     # SIP) create directory in root `/`.
     # We decide to move the standard installation directory on
     # `/Users/shared/cvmfs` instead of `/cvmfs`
     # In order to keep the standard CVMFS mountpoint, we decide to implement
     # firmlinks from `/cvmfs` to `/Users/shared/cvmfs`
     if compare_versions $(sw_vers -productVersion) -ge "10.15"; then
        mkdir -p /Users/shared/cvmfs
        chown cvmfs:cvmfs /Users/shared/cvmfs
        chmod 777 /Users/shared/cvmfs
        if ! grep -q "^cvmfs\t" /etc/synthetic.conf 2> /dev/null ; then
          echo -e "# The line below was autogenerated by CernVM-FS, do not delete nor edit" >> /etc/synthetic.conf
          echo -e "cvmfs\t/Users/Shared/cvmfs" >> /etc/synthetic.conf
        fi
        # reload synthetic.conf - seems to return nonzero status code even on success
        /System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util -t || true
     else
        echo "Fuse4x and autofs bug" > /dev/null
        mkdir -p /cvmfs
        chown cvmfs:cvmfs /cvmfs
        chmod 777 /cvmfs
     fi

     ;;

   * )
     echo "Architecture $sys_arch is not supported"
     exit 1 ;;

  esac
}


configure_sudoers() {
  if [ "$sys_arch" = "Darwin" ]; then
    sudo sed -i .cvmfs_backup -e "/# added by CernVM-FS/d" /etc/sudoers
    sudo cat >> /etc/sudoers << EOF
%everyone ALL=(cvmfs:cvmfs) NOPASSWD: /usr/bin/sudo # added by CernVM-FS
cvmfs ALL= NOPASSWD: ${INSTALL_BASE}/bin/cvmfs2 # added by CernVM-FS
cvmfs ALL= NOPASSWD: ${INSTALL_BASE}/bin/cvmfs2_debug # added by CernVM-FS
%everyone ALL=(cvmfs:cvmfs) NOPASSWD: ${INSTALL_BASE}/bin/cvmfs_talk -i * mountpoint # added by CernVM-FS
%everyone ALL=(cvmfs:cvmfs) NOPASSWD: /bin/mkdir # added by CernVM-FS
cvmfs ALL= NOPASSWD: /usr/sbin/sysctl -w kern.maxfilesperproc=* kern.maxfiles=* # added by CernVM-FS
%everyone ALL= NOPASSWD: /bin/mkdir -p /var/run/cvmfs # added by CernVM-FS
%everyone ALL= NOPASSWD: /usr/sbin/chown $CVMFS_USER /var/run/cvmfs # added by CernVM-FS
EOF
  fi
}


get_attr() {
  attr_name=$1
  path=$2
  if [ "x$path" = "x" ]; then
    path=.
  fi

  case $sys_arch in
    Linux )
      attr_value=$(attr -q -g $attr_name $path 2>/dev/null || exit 33) ;;

    Darwin )
      attr_value=$(xattr -p user.$attr_name $path 2>/dev/null || exit 33) ;;

    * )
      echo "Architecture $sys_arch is not supported"
      exit 1 ;;
   esac
}

# Ensures that binaries needed during reload are found. Reload creates itself
# a clean environment before execution.
get_minimal_path() {
  case $sys_arch in
    Linux)
      # On Linux, all the binaries needed during reload are in the default
      # search paths
      echo ""
    ;;
    Darwin)
      echo "PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
    ;;
    *)
      echo "Architecture $sys_arch is not supported"
      exit 1
    ;;
  esac
}


check_is_on() {
  local parameter=$1
  eval value=\$$parameter
  if [ "x$value" = "x" ]; then
    return 1
  fi
  if [ "$value" = "yes" -o "$value" = "YES" -o "$value" = "on" -o "$value" = "ON" -o "$value" = "1" ]; then
    return 0
  fi
  return 1
}


runascvmfsuser() {
  local cmd=$1

  if [ -z "$CVMFS_USER" ]; then
    return 1
  fi

  case $sys_arch in
    Linux )
      su -s /bin/sh $CVMFS_USER -c "$cmd" ;;

    Darwin )
      sudo -u $CVMFS_USER $cmd ;;
  esac
}


base64_nowrap() {
  case $(uname) in
    Linux )
      base64 -w0
      ;;
    Darwin )
      base64 -b0
      ;;
  esac
}

canonicalize_path() {
  local path_name="$1"

  case $(uname) in
    Linux )
      echo $(readlink -f "$path_name")
      ;;
    Darwin )
      echo $(greadlink -f "$path_name")
      ;;
  esac
}


__usrlocal_migrate_clean_legacy_binaries() {
  echo "cleaning up legacy binaries..."
  for binary in /usr/bin/cvmfs2               \
                /usr/bin/cvmfs_config         \
                /usr/bin/cvmfs_talk           \
                /usr/bin/cvmfs_fsck           \
                /sbin/mount_cvmfs             \
                /usr/lib/libcvmfs_fuse*.dylib \
                /etc/auto_cvmfs; do
    if [ -f $binary   ] && \
       [ ! -L $binary ]; then
      rm -f "$binary"
      echo "found and removed legacy binary '$binary'"
    fi
  done
}

__usrlocal_migrate_clean_legacy_directories() {
  echo "cleaning up legacy directories..."
  for directory in /etc/bash_completion.d/cvmfs \
                   /usr/share/doc/cvmfs-*; do
    if [ -d $directory ]; then
      rm -fR "$directory"
      echo "found and removed legacy directory '$directory'"
    fi
  done
}

__usrlocal_migrate_clean_and_rescue_legacy_configuration() {
  local old_conf_dir="/etc/cvmfs"
  local new_conf_dir="/usr/local/etc/cvmfs"

  echo "cleaning up legacy configuration..."
  if [ -L $old_conf_dir ] &&
     [ x"$(readlink $old_conf_dir)" = x"$new_conf_dir" ]; then
    echo "configuration already points to $new_conf_dir. do nothing."
    return 0
  fi

  if [ ! -e $old_conf_dir ]; then
    echo "no legacy configuration found. do nothing."
    return 0
  fi

  # detect and remove all configuration files of previous CernVM-FS installs
  # Note: This deletes only the configuration files that have been shipped with
  #       CernVM-FS. Everything user-added stays intact.
  for conf_file in config.d/atlas-nightlies.cern.ch.conf            \
                   config.d/cms.cern.ch.conf                        \
                   config.d/grid.cern.ch.conf                       \
                   config.sh                                        \
                   default.conf                                     \
                   default.d/50-cern.conf                           \
                   default.d/60-egi.conf                            \
                   default.d/README                                 \
                   domain.d/cern.ch.conf                            \
                   domain.d/egi.eu.conf                             \
                   domain.d/opensciencegrid.org.conf                \
                   keys/cern.ch/cern-it1.cern.ch.pub                \
                   keys/cern.ch/cern-it2.cern.ch.pub                \
                   keys/cern.ch/cern-it3.cern.ch.pub                \
                   keys/cern.ch/cern.ch.pub                         \
                   keys/egi.eu/egi.eu.pub                           \
                   keys/opensciencegrid.org/opensciencegrid.org.pub \
                   serverorder.sh; do
    local old_conf_file="${old_conf_dir}/${conf_file}"
    if [ -f $old_conf_file ]; then
      local quarantined_conf_file="${new_conf_dir}/quarantine/${conf_file}"
      mkdir -p "$(dirname $quarantined_conf_file)"
      mv -f "$old_conf_file" "$quarantined_conf_file"
      echo "found and quarantined legacy configuration file '$conf_file'"
    fi
  done

  # rescue all configuration files that have been added by the user
  local cwd="$(pwd)"
  cd "$old_conf_dir" # remove $old_conf_dir prefix from `find` output
  for conf_file in $(find . -type f); do
    echo "found and rescued user added configuration file '$conf_file'"
    local rescued_conf_file="${new_conf_dir}/${conf_file}"
    mkdir -p "$(dirname $rescued_conf_file)"
    mv -f "$conf_file" "$rescued_conf_file"
  done
  cd "$cwd"

  # remove legacy configuration directory
  rm -fR /etc/cvmfs
  echo "removed legacy configuration directory"
}

__usrlocal_migrate_clean_legacy_sudoers() {
  echo "cleaning up legacy sudoers configuration..."
  if cat /etc/sudoers | grep -q '# added by CernVM-FS'; then
    local doomed_sudoers="$(cat /etc/sudoers | grep '# added by CernVM-FS')"
    sed -i .cvmfs_backup -e "/# added by CernVM-FS/d" /etc/sudoers
    echo "found and removed the following legacy sudoers configurations:"
    echo "-----"
    echo "$doomed_sudoers"
    echo "-----"
  fi
}

_usrlocal_migrate() {
  # this cleans out left-overs from CernVM-FS versions prior to 2.2.0 as they
  # used to be installed into /usr/... in contrast to newer versions that are
  # compliant with Apple's "System Integrity Protection" that has been intro-
  # duced with OS X El Capitan.
  [ x"$sys_arch" = x"Darwin" ] || return 1

  __usrlocal_migrate_clean_legacy_binaries
  __usrlocal_migrate_clean_legacy_directories
  __usrlocal_migrate_clean_and_rescue_legacy_configuration
  __usrlocal_migrate_clean_legacy_sudoers
}

case "$1" in
  setup)
    if [ `id -u` -ne 0 ]; then
      echo "root privileges required"
      exit 1
    fi
    shift 1
    cvmfs_setup $@
    RETVAL=$?
    ;;

  chksetup)
    if [ `id -u` -ne 0 ]; then
      echo "root privileges required"
      exit 1
    fi
    shift 1
    cvmfs_chksetup
    RETVAL=$?
    ;;

  showconfig)
    shift 1
    cvmfs_showconfig $@
    RETVAL=$?
    ;;

  stat)
    shift 1
    cvmfs_stat $@
    RETVAL=$?
    ;;

  status)
    shift 1
    cvmfs_status $@
    RETVAL=$?
    ;;

  reload)
    if [ `id -u` -ne 0 ]; then
      echo "root privileges required"
      exit 1
    fi
    shift 1
    cvmfs_reload $@
    RETVAL=$?
    ;;

  probe)
    shift 1
    cvmfs_probe $@
    RETVAL=$?
    ;;

  fsck)
    shift 1
    cvmfs_config_fsck $@
    RETVAL=$?
    ;;

  fuser)
    if [ `id -u` -ne 0 ]; then
      echo "root privileges required"
      exit 1
    fi
    shift 1
    cvmfs_config_fuser $@
    RETVAL=$?
    ;;

  umount)
    if [ `id -u` -ne 0 ]; then
      echo "root privileges required"
      exit 1
    fi
    shift 1
    cvmfs_umount $@
    RETVAL=$?
    ;;

  wipecache)
    if [ `id -u` -ne 0 ]; then
      echo "root privileges required"
      exit 1
    fi
    shift 1
    cvmfs_reload -c
    RETVAL=$?
    ;;

  killall)
    if [ `id -u` -ne 0 ]; then
      echo "root privileges required"
      exit 1
    fi
    cvmfs_killall $@
    RETVAL=$?
    ;;

  bugreport)
     if [ `id -u` -ne 0 ]; then
       echo "root privileges required"
       exit 1
     fi
     shift 1
     cvmfs_bugreport $@
     RETVAL=$?
     ;;

   wsl2_start)
     shift 1
     cvmfs_wsl2_start $@
     RETVAL=$?
     ;;

   --version)
     echo $(cvmfs2 --version)
     RETVAL=0
     ;;
   --help)
     cvmfs_config_usage
     RETVAL=0
     ;;
   -h)
     cvmfs_config_usage
     RETVAL=0
     ;;
   *)
     cvmfs_config_usage
     RETVAL=1
     ;;
esac

exit $RETVAL
