#!/bin/bash
# Copyright (c) Contributors to the Apptainer project, established as
#   Apptainer a Series of LF Projects LLC.
#   For website terms of use, trademark policy, privacy policy and other
#   project policies see https://lfprojects.org/policies
# Copyright (c) 2020-2022, Sylabs Inc. All rights reserved.
# This software is licensed under a 3-clause BSD license. Please consult the
# LICENSE.md file distributed with the sources of this project regarding your
# rights to use or distribute this software.

declare -r __exported_env__=$(getallenv)
declare -r __apptainer_cmd__=${APPTAINER_COMMAND:-}

if test -n "${SINGULARITY_APPNAME:-}"; then
    readonly SINGULARITY_APPNAME
fi

export PWD

unsupported_builtin() {
    sylog warning "$1 is not supported by this shell interpreter"
}

# create alias for unsupported builtin that trigger a panic
alias umask="umask_builtin"
alias trap="unsupported_builtin trap"
alias fg="unsupported_builtin fg"
alias bg="unsupported_builtin bg"

clear_env() {
    local IFS=$'\n'

    # disable globbing as __exported_env__ may contain
    # wildcard evaluated by shell. It can cause serious
    # performance issue when the current directory contains
    # a lot of files/directories, see:
    # https://github.com/apptainer/singularity/issues/5389
    set -o noglob

    for e in ${__exported_env__}; do
        key=${e%%=*}
        case "${key}" in
        PWD|HOME|OPTIND|UID|GID|SINGULARITY_APPNAME|SINGULARITY_SHELL)
            ;;
        APPTAINER_NAME|APPTAINER_CONTAINER|APPTAINER_INSTANCE)
            readonly "${key}"
            ;;
        *)
            unset "${key}"
            ;;
        esac
    done

    set +o noglob
}

restore_env() {
    local IFS=$'\n'

    # disable globbing as __exported_env__ and the export
    # statement below may contain wildcard evaluated by shell.
    # It can cause serious performance issue when the current
    # directory contains a lot of files/directories, see:
    # https://github.com/apptainer/singularity/issues/5389
    set -o noglob

    # restore environment variables which haven't been
    # defined by docker or virtual file above, empty
    # variables are also unset
    for e in ${__exported_env__}; do
        key=${e%%=*}
        if ! test -v "${key}"; then
            export "${e//'\u000A'/$IFS}"
        elif test -z "${!key}"; then
            unset "${key}"
        fi
    done

    set +o noglob
}

clear_env
shopt -s expand_aliases

# If /.singularity.d/libs isn't empty we want it to be searched last so it
# doesn't hide libraries in the container.  The trouble is that LD_LIRARY_PATH
# is always searched before system libraries.  So if LD_LIBRARY_PATH is the
# default value set by 99-base.sh, prepend default system library directories.
# Also prepend the default LD_LIBRARY_PATH if PREPEND_LD_LIBRARY_PATH or
# APPEND_LD_LIBRARY_PATH are set.
set_default_ld_library_path()
{
    # /.singularity.d/libs is the default setting at this point if no
    # other value was given to LD_LIBRARY_PATH
    if [ "$LD_LIBRARY_PATH" != "/.singularity.d/libs" ]; then
        return
    fi
    if [ "$(cd /.singularity.d/libs 2>/dev/null && echo *)" = "*" ] &&
      [ -z "${PREPEND_LD_LIBRARY_PATH:-}" ] && \
      [ -z "${APPEND_LD_LIBRARY_PATH:-}" ]; then
      return
    fi
    # Only glibc's ldconfig lists these directories; musl from alpine, for
    # example, prints nothing, but this is mostly for GPU libraries and
    # those require glibc anyway.
    typeset __dirs__
    __dirs__="$( (/sbin/ldconfig -Nv | /bin/sed '/^\t/d;s/:.*//') 2>/dev/null )"
    if [ -n "$__dirs__" ]; then
        # change newlines into spaces and spaces into colons
        # shellcheck disable=SC2116
        __dirs__="$(echo $__dirs__)"
        export LD_LIBRARY_PATH="${__dirs__// /:}:$LD_LIBRARY_PATH"
        sylog debug "Setting LD_LIBRARY_PATH to $LD_LIBRARY_PATH"
    fi
}

if test -d "/.singularity.d/env"; then
    for __script__ in /.singularity.d/env/*.sh; do
        if test -f "${__script__}"; then
            sylog debug "Sourcing ${__script__}"

            case "${__script__}" in
            /.singularity.d/env/90-environment.sh)
                # docker files below may not be present depending of image source
                # and build, so we also fix the PATH if not defined here
                if ! test -v PATH; then
                    export PATH="$(fixpath)"
                fi
                source "${__script__}"
                ;;
            /.singularity.d/env/10-docker2singularity.sh| \
            /.singularity.d/env/10-docker.sh)
                source "${__script__}"
                # append potential missing path from the default PATH
                # used by Apptainer
                export PATH="$(fixpath)"
                ;;
            /.singularity.d/env/99-base.sh)
                # this file is the common denominator in image built since
                # Singularity 2.3, inject forwarded variables right after
                source "${__script__}"
                source "/.inject-apptainer-env.sh"
                # set default LD_LIBRARY_PATH if it hasn't been set but needs
                # to be
                set_default_ld_library_path
                ;;
            *)
                source "${__script__}"
                ;;
            esac
        fi
    done
else
    # this is for old images built with Singularity version prior to 2.3
    if test -f "/environment"; then
        source "/environment"
        export PATH="$(fixpath)"
    fi
    source "/.inject-apptainer-env.sh"
    set_default_ld_library_path
fi

if ! test -f "/.singularity.d/env/99-runtimevars.sh"; then
    source "/.singularity.d/env/99-runtimevars.sh"
fi

if [ -n "${LD_LIBRARY_PATH:-}" ]; then
    if [ -n "${PREPEND_LD_LIBRARY_PATH:-}" ]; then
        sylog debug "Prepending $PREPEND_LD_LIBRARY_PATH to LD_LIBRARY_PATH"
        LD_LIBRARY_PATH="$PREPEND_LD_LIBRARY_PATH:$LD_LIBRARY_PATH"
    fi
    if [ -n "${APPEND_LD_LIBRARY_PATH:-}" ]; then
        sylog debug "Appending $APPEND_LD_LIBRARY_PATH to LD_LIBRARY_PATH"
        LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$APPEND_LD_LIBRARY_PATH"
    fi
fi

shopt -u expand_aliases
restore_env

# See https://github.com/apptainer/singularity/issues/5340
# If there is no .singularity.d then a custom PS1 wasn't set.
# If we were called through a script and PS1 is empty this
# gives a confusing silent prompt. Force a PS1 if it's empty.
if test -z "${PS1:-}"; then
    export PS1="Apptainer> "
fi

# See https://github.com/apptainer/singularity/issues/2721,
# as bash is often used as the current shell it may confuse
# users if the provided command is /bin/bash implying to
# override PS1 set by apptainer, then we may end up
# with a shell prompt identical to the host one, so we
# force PS1 through bash PROMPT_COMMAND
if test -z "${PROMPT_COMMAND:-}"; then
    export PROMPT_COMMAND="PS1=\"${PS1}\"; unset PROMPT_COMMAND"
else
    export PROMPT_COMMAND="${PROMPT_COMMAND:-}; PROMPT_COMMAND=\"\${PROMPT_COMMAND%%; PROMPT_COMMAND=*}\"; PS1=\"${PS1}\""
fi

export APPTAINER_ENVIRONMENT="${APPTAINER_ENVIRONMENT:-/.singularity.d/env/91-environment.sh}"
export SINGULARITY_ENVIRONMENT=${APPTAINER_ENVIRONMENT}

sylog debug "Running action command ${__apptainer_cmd__}"

case "${__apptainer_cmd__}" in
exec)
    exec "$@" ;;
shell)
    if test -n "${SINGULARITY_SHELL:-}" -a -x "${SINGULARITY_SHELL:-}"; then
        exec "${SINGULARITY_SHELL:-}" "$@"
    elif test -x "/bin/bash"; then
        export SHELL=/bin/bash
        exec "/bin/bash" --norc "$@"
    elif test -x "/bin/sh"; then
        export SHELL=/bin/sh
        exec "/bin/sh" "$@"
    fi

    sylog error "/bin/sh does not exist in container"
    exit 1 ;;
run)
    if test -n "${SINGULARITY_APPNAME:-}"; then
        if test -x "/scif/apps/${SINGULARITY_APPNAME:-}/scif/runscript"; then
            exec "/scif/apps/${SINGULARITY_APPNAME:-}/scif/runscript" "$@"
        elif test -x "/scif/apps/singularity/scif/test"; then
            exec "/scif/apps/singularity/scif/test" "$@"
        fi
        sylog error "no runscript for contained app: ${SINGULARITY_APPNAME:-}"
        exit 1
    elif test -x "/.singularity.d/runscript"; then
        exec "/.singularity.d/runscript" "$@"
    elif test -x "/apptainer"; then
        exec "/apptainer" "$@"
    elif test -x "/bin/sh"; then
        sylog info "No runscript found in container, executing /bin/sh"
        exec "/bin/sh" "$@"
    fi

    sylog error "No runscript and no /bin/sh executable found in container, aborting"
    exit 1 ;;
test)
    if test -n "${SINGULARITY_APPNAME:-}"; then
        if test -x "/scif/apps/${SINGULARITY_APPNAME:-}/scif/test"; then
            exec "/scif/apps/${SINGULARITY_APPNAME:-}/scif/test" "$@"
        elif test -x "/scif/apps/singularity/scif/test"; then
            exec "/scif/apps/singularity/scif/test" "$@"
        fi
        sylog error "No tests for contained app: ${SINGULARITY_APPNAME:-}"
        exit 1
    elif test -x "/.singularity.d/test"; then
        exec "/.singularity.d/test" "$@"
    fi

    sylog info "No test script found in container, exiting"
    exit 0 ;;
start)
    if test -n "${SINGULARITY_APPNAME:-}"; then
        if test -x "/scif/apps/${SINGULARITY_APPNAME:-}/scif/startscript"; then
            exec "/scif/apps/${SINGULARITY_APPNAME:-}/scif/startscript" "$@"
        elif test -x "/scif/apps/singularity/scif/test"; then
            exec "/scif/apps/singularity/scif/test" "$@"
        fi
        sylog error "No startscript for contained app: ${SINGULARITY_APPNAME:-}"
        exit 1
    elif test -x "/.singularity.d/startscript"; then
        exec "/.singularity.d/startscript" "$@"
    fi

    sylog info "No instance start script found in container"
    exit 0 ;;
*)
    sylog error "Unknown action ${__apptainer_cmd__}"
    exit 1 ;;
esac
