#!/usr/bin/bash
#
# cvmfsexec-osg-wrapper
#
# Runs the specified command with CVMFS repos mounted (using cvmfsexec if
# possible, otherwise mountrepo) in a temp directory; the directory will be
# in the command's environment as $CVMFS_BASE.
#
# Uses the tarball in $CVMFSEXEC_TARBALL to get an osg distribution of
# cvmfsexec; if missing or empty, clones cvmfsexec from GitHub and builds
# the distribution on the fly.
#
# Normally bails on an error; pass `-safe` as the first argument in order
# to run the command anyway (without CVMFS).
#
# To use this in jobs, edit blah.config and set
#
#       blah_job_wrapper=/path/to/cvmfsexec-osg-wrapper
#
# or
#
#       blah_job_wrapper=/path/to/cvmfsexec-osg-wrapper -safe
#
# Then set CVMFSEXEC_REPOS in the environment to a comma-or-space-separated
# list of CVMFS repos to mount.
#
# Other variables:
# - CVMFS_HTTP_PROXY: the proxy to use for CVMFS
# - UMOUNTREPO_UNMOUNT_ALL:
#       no effect unless using `mountrepo`; set this to unmount all mounted
#       repos before and after.  This makes cleanup more reliable but may
#       conflict if multiple copies of this script are running, so only use
#       it with whole-machine jobs.
# - CVMFSEXEC_TARBALL: pre-created tarball containing cvmfsexec

CVMFSEXEC_REPOS=$(tr -s ',' ' ' <<<"${CVMFSEXEC_REPOS-}")
CVMFS_HTTP_PROXY=${CVMFS_HTTP_PROXY-}
UMOUNTREPO_UNMOUNT_ALL=${UMOUNTREPO_UNMOUNT_ALL-}
CVMFSEXEC_TARBALL=${CVMFSEXEC_TARBALL-}

SAFE=false
if [[ $1 = -safe ]]; then
    SAFE=true
    shift
fi

msg () {
    echo >&2 "*** $*"
}

if [[ -z $CVMFSEXEC_REPOS ]]; then
    msg "\$CVMFSEXEC_REPOS not specified"
    exec "$@"
fi

job=("$@")

fail () {
    msg "$@"
    if $SAFE; then
        # so it shows up in environment dumps in the job
        export CVMFSEXEC_WRAPPER_FAILURE="$*"
        msg "$0 failed; cvmfs will be unavailable ***"
        [[ -n ${prevdir-} ]] && cd "$prevdir"
        "${job[@]}"
        exit $?
    else
        exit 1
    fi
}


add_or_replace () {
    local file="$1"
    local var="$2"
    local value="$3"

    if [[ -f $file ]] && grep -q "^${var}=" "$file"; then
        sed -i -e "s^${var}=.*${var}=\"${value}\"" "$file"
    else
        echo "${var}=\"${value}\"" >> "$file"
    fi
}


#
# Begin
#

# We use $CVMFSEXEC_REPOS unquoted so check for bad characters
badchars=$(grep -o '[^0-9a-zA-Z. -]' <<<"$CVMFSEXEC_REPOS")
if [[ -n $badchars ]]; then
    fail "Illegal character(s) $badchars in \$CVMFSEXEC_REPOS"
fi

set -o nounset
PS4='+ ${LINENO}: '
#set -x

[[ $(uname -p) == "x86_64" ]] || fail "Only the x86_64 architecture is supported"
grep -Eq '^ID(_LIKE)?=.*rhel.*' /etc/os-release || fail "Only rhel-like distributions are supported"
OS_VERSION_ID=$(grep '^VERSION_ID=' /etc/os-release | tr -d '"' | cut -d= -f2-)
if [[ $OS_VERSION_ID = 7* ]]; then
    distro=rhel7-x86_64
elif [[ $OS_VERSION_ID = 8* ]]; then
    distro=rhel8-x86_64
elif [[ $OS_VERSION_ID = 9* ]]; then
    distro=rhel9-x86_64
else
    fail "Unsupported OS version $OS_VERSION_ID"
fi

prevdir=$(pwd)
if [[ -n ${CVMFSEXEC_WRAPPER_WORKDIR:-} ]]; then
    workdir=$CVMFSEXEC_WRAPPER_WORKDIR
    mkdir -p "$workdir"
    chmod 0700 "$workdir"
    rm -rf "$workdir"/cvmfsexec >& /dev/null || :
else
    workdir=$(mktemp -d "${OSG_WN_TMP:-${TMPDIR:-/tmp}}/$USER-cvmfsexec-XXXXXX")
fi
trap 'cd "$prevdir"; rm -rf "$workdir"' EXIT
cd "$workdir" || fail "Couldn't enter work dir $workdir"

# Obtain cvmfsexec and the OSG CVMFS distribution
if [[ $CVMFSEXEC_TARBALL = *://* ]]; then
    # We are given a URL; download the tarball from there.
    msg "Downloading $CVMFSEXEC_TARBALL"
    errmsg="Couldn't download $CVMFSEXEC_TARBALL"
    newname=cvmfsexec-osg.tar.gz
    if command -v curl &>/dev/null; then
        curl -LSso "$newname" "$CVMFSEXEC_TARBALL" || fail "$errmsg"
    elif command -v wget &>/dev/null; then
        wget -O "$newname" "$CVMFSEXEC_TARBALL" || fail "$errmsg"
    elif command -v GET &>/dev/null; then
        GET "$CVMFSEXEC_TARBALL" > "$newname" || fail "$errmsg"
    else
        fail "No program to download $CVMFSEXEC_TARBALL with"
    fi
    CVMFSEXEC_TARBALL=$newname
fi

if [[ -n $CVMFSEXEC_TARBALL ]]; then
    # We are given a tarball: use it.  Turn hardlinks into copies since some filesystems don't support hardlinks
    tar --hard-dereference -xzf "$CVMFSEXEC_TARBALL" || fail "Couldn't extract $CVMFSEXEC_TARBALL"
    if [[ -d ./cvmfsexec/$distro ]]; then
        cvmfsexec_dir=$workdir/cvmfsexec/$distro
    else
        cvmfsexec_dir=$workdir/cvmfsexec
    fi
else
    # Make the CVMFS distribution ourselves because we weren't given a tarball
    msg "\$CVMFSEXEC_TARBALL not found or not specified"
    command -v git &>/dev/null || fail "Required command 'git' not found"
    git clone -c advice.detachedHead=false https://github.com/cvmfs/cvmfsexec || fail "Error downloading cvmfsexec from GitHub"
    cd cvmfsexec
    # Check out latest tagged version. Only git 2.0+ has "git tag --sort"
    version_tags=$(git tag --list 'v*' | sort -V)
    tag=$(tail -n 1 <<<"$version_tags")
    [[ -n $tag ]] || fail "Couldn't get latest tagged version"
    git checkout "$tag" || fail "Couldn't switch to $tag"
    ./makedist osg || fail "Couldn't get osg cvmfs distribution"
    cvmfsexec_dir=$workdir/cvmfsexec
fi

# Add local configuration if any
cvmfs_local_config=$cvmfsexec_dir/dist/etc/cvmfs/default.local
if [[ -e $cvmfsexec_dir/default.local ]]; then
    cp -f "$cvmfsexec_dir/default.local"  "$cvmfs_local_config"
fi

if [[ -n $CVMFS_HTTP_PROXY ]]; then
    msg "Setting CVMFS_HTTP_PROXY to ${CVMFS_HTTP_PROXY}"
    add_or_replace "$cvmfs_local_config" CVMFS_HTTP_PROXY "${CVMFS_HTTP_PROXY}"
fi

# If we have to use this script to mount CVMFS, we can't use images from there anyway.
export ALLOW_NONCVMFS_IMAGES=true

msg "Running cvmfsexec smoke test:"
if timeout 45 "$cvmfsexec_dir"/cvmfsexec -N -- /bin/true </dev/null; then
    msg "cvmfsexec smoke test passed: we have the permissions to run cvmfsexec directly"

    # cvmfsexec lets us have CVMFS mounted at `/cvmfs`; set $CVMFS_BASE for
    # consistency with the mountrepo path

    export CVMFS_BASE=/cvmfs
    export GLIDEIN_SINGULARITY_BINDPATH="$CVMFS_BASE:/cvmfs"
    msg "CVMFS successfully mounted"
    msg ""
    msg "\$CVMFS_BASE=$CVMFS_BASE"

    # Note: do not use 'exec'; cleanup won't run
    # $CVMFSEXEC_REPOS is a space-separated list so it's unquoted on purpose.
    cd "$prevdir"
    "$cvmfsexec_dir"/cvmfsexec -N $CVMFSEXEC_REPOS -- "$@"
else
    msg "cvmfsexec smoke test failed: we will use mountrepo instead"
    command -v fusermount >& /dev/null || fail "Required command 'fusermount' not found"

    if [[ -n $UMOUNTREPO_UNMOUNT_ALL ]]; then
        # Unmount repos from a previous run that couldn't clean up
        "$cvmfsexec_dir"/umountrepo -a || :
    fi

    mounted_repos=

    unmount_repos () {
        if [[ -n $UMOUNTREPO_UNMOUNT_ALL ]]; then
            "$cvmfsexec_dir"/umountrepo -a || :
        else
            for repo in $mounted_repos; do
                "$cvmfsexec_dir"/umountrepo "$repo" || :
            done
        fi
    }

    trap 'unmount_repos; cd "$prevdir"; rm -rf "$workdir"' EXIT

    # Mount repos
    # $CVMFSEXEC_REPOS is a space-separated list so it's unquoted on purpose.
    for repo in config-osg.opensciencegrid.org $CVMFSEXEC_REPOS; do
        if ! "$cvmfsexec_dir"/mountrepo "$repo"; then
            msg "$repo mount failed, dumping logs:"
            tail -n 20 "$cvmfsexec_dir"/log/"$repo".log >&2
            fail "Unable to mount cvmfs repo $repo"
        fi
        mounted_repos="$mounted_repos $repo"
    done

    # mountrepo can't mount CVMFS to `/cvmfs`.  Set $CVMFS_BASE to its
    # location so scripts know where to find it.

    export CVMFS_BASE="$cvmfsexec_dir/dist/cvmfs"
    export GLIDEIN_SINGULARITY_BINDPATH="$CVMFS_BASE:/cvmfs"
    msg "CVMFS successfully mounted"
    msg ""
    msg "\$CVMFS_BASE=$CVMFS_BASE"

    # Note: do not use 'exec'; cleanup won't run
    cd "$prevdir"
    "$@"
fi
