#!/bin/sh

# Part of shell-toolbox 20180719
# For bug reports, see "https://github.com/kusalananda/shell-toolbox/issues"

utility_name=shell      # used in perrorf() and pinfo()
main_pid=$$             # used in get_shells()

# Variables set by the configure script:
MKTEMP="/usr/bin/mktemp"
MKTEMP_TEMPLATE=""

perrorf ()
{
    # Simple function for outputting error messages.  Prefixes the error
    # message with the name of the utility.

    # Parameters:   same as printf
    # stdin:        unused
    # stdout:       unused
    # stderr:       error message

    # We mimic printf here, disable shellcheck warnings.
    # See https://unix.stackexchange.com/questions/438694
    # shellcheck disable=SC2059,SC2145
    printf "$utility_name: error: $@" >&2
}

pinfof ()
{
    # Same as above, but with 'info:' instead of 'info:'.  Also
    # disables output if the global variable $quiet is 1.

    if [ "${quiet:-0}" -ne 1 ]; then
        # shellcheck disable=SC2059,SC2145
        printf "$utility_name: info: $@" >&2
    fi
}

output_version ()
{
    # Simple function for outputting version information.

    # Parameters:   none
    # stdin:        unused
    # stdout:       unused
    # stderr:       version information

    pinfof 'Part of %s (release "%s")\n' "shell-toolbox" "20180719"
    pinfof 'For bug reports, see %s\n' "https://github.com/kusalananda/shell-toolbox/issues"
}

get_solaris_shells ()
{
    # Returns a list of valid Solaris 11.4 shells.  The list is taken
    # from the shells(4) manual and is shortened to only contain one
    # single instance of each shell.

    # Parameters:   none
    # stdin:        unused
    # stdout:       list of shells (one per line)

    cat <<END_LIST
/bin/bash
/bin/csh
/bin/jsh
/bin/ksh
/bin/ksh93
/bin/pfcsh
/bin/pfksh
/bin/pfsh
/bin/sh
/bin/tcsh
/bin/zsh
END_LIST
}

get_shells ()
{
    # Returns a list of absolute paths to installed shells.  The shells
    # are validated (it's made sure that they are executable files)
    # before returned.

    # Rationale: OpenBSD and NetBSD can use "getent shells" to get a
    # list of shells, but this doesn't work on Ubuntu.  Solaris is a
    # problem since "getent shells" doesn't work and /etc/shells may not
    # exist.  The list of valid Solaris shells is instead taken from
    # the shells(4) manual on a vanilla Solaris 11.4 system.

    # Parameters:   none
    # stdin:        unused
    # stdout:       list of shells (one per line)

    # Try using "getent shells", if that fails, try parsing /etc/shells,
    # and if that fails, see if we're on Solaris and pass a predifined
    # list of shells.  Else fail.

    if ! getent shells 2>/dev/null &&
       ! grep '^[^#]' /etc/shells 2>/dev/null
    then
        if [ "$( uname -s )" = "SunOS" ]; then
            get_solaris_shells
        else
            perrorf 'Can not get list of shells!\n'
            perrorf 'Please file a bug report at %s\n' "https://github.com/kusalananda/shell-toolbox/issues"
            # We HUP the script here since if we "exit 1" instead, we
            # will be left it the main part of the script and get further
            # error messages before properly terminating.
            kill -s HUP "$main_pid"
        fi
    fi |
    while read -r realshell; do
        if [ -x "$realshell" ]; then
            printf '%s\n' "$realshell"
        fi
    done
}

exit_handler () {
    if [ "$keep" -eq 1 ]; then
        pinfof 'Leaving %s in place\n' "$tmpwd" >&2
    else
        pinfof 'Removing %s\n' "$tmpwd" >&2
        cd / && rm -rf -- "$tmpwd"
    fi
}

usr1_handler () { keep=1; }

force=0 # when 1, bypasses the call to get_shells
keep=0  # when 1, does not remove working directory when exiting
quiet=0 # when 1, informational messages are not outputted
while getopts 'd:fks:qv' opt; do
    case "$opt" in
        d) tmpwd=$OPTARG
           keep=1       ;;
        f) force=1      ;;
        k) keep=1       ;;
        q) quiet=1      ;;
        s) skel=$OPTARG ;;
        v) quiet=0
           output_version
           exit 0       ;;
        *) perrorf 'Unknown option on command line\n'
           exit 1
    esac
done
shift "$(( OPTIND - 1 ))"

if [ -n "$tmpwd" ] && [ -n "$skel" ]; then
    perrorf 'Can not use both the -d and the -s option at the same time\n'
    exit 1
elif [ -z "$tmpwd" ] && [ -z "$skel" ] && [ -n "$SHELL_SKEL" ]; then
    skel=$SHELL_SKEL
fi

if [ -n "$1" ]; then
    shell="$1"
    shift
else
    shell="${SHELL:-/bin/sh}"
fi

if [ "$force" -eq 0 ]; then
    # Get the absolute path to a real shell.  If multiple shells are
    # returned from get_shells, we will use the first one.
    realshell="$( get_shells | sed -n '\#/'"${shell##*/}"'$#{p;q;}' )"

    if [ -z "$realshell" ]; then
        perrorf 'No such shell: %s\n' "$shell"
        echo 'Valid shells:' >&2
        get_shells | awk '{ printf("\t%s\n", $0) }' >&2
        exit 1
    fi
elif [ ! -x "$shell" ]; then
        perrorf 'No such executable: %s\n' "$shell"
        exit 1
else
    realshell="$shell"
fi

if [ -n "$skel" ] && [ ! -d "$skel" ]; then
    perrorf 'No such directory: %s\n' "$skel"
    exit 1
fi

if [ -z "$tmpwd" ]; then
    template="shell-${shell##*/}$MKTEMP_TEMPLATE"
    tmpwd="$( "$MKTEMP" -d -t "$template" )"
elif [ ! -d "$tmpwd" ]; then
    if [ -e "$tmpwd" ]; then
        perrorf 'No such directory: %s\n' "$tmpwd"
        exit 1
    fi

    if ! mkdir -p "$tmpwd"; then
        perrorf 'Failed to create directory: %s\n' "$tmpwd"
        exit 1
    fi
fi

trap exit_handler EXIT
trap usr1_handler USR1

if [ -n "$skel" ]; then
    pinfof 'Copying %s into %s\n' "$skel" "$tmpwd" >&2
    if command -v pax >/dev/null 2>&1; then
        ( cd -- "$skel" && pax -rw -p p . "$tmpwd" )
    elif command -v tar >/dev/null 2>&1; then
        tar -c -f - -C "$skel" . | tar -x -p -f - -C "$tmpwd"
    else
        perrorf 'Can not use pax nor tar to copy "%s" to "%s"\n' \
            "$skel" "$tmpwd"
        exit 1
    fi
fi

pinfof 'Starting %s in %s\n' "$realshell" "$tmpwd" >&2

cd -- "$tmpwd" &&
env -i  HOME="$tmpwd" \
        PATH="$( getconf PATH )" \
        PS1='$ ' \
        SHELL="$realshell" \
        TERM="$TERM" \
        "$realshell" "$@"


# vim: ft=sh
