#!/bin/sh -e

# Part of shell-toolbox 20190528
# 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=""
PAX="/bin/pax"

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

    _fmt=$1; shift

    # We mimic printf here, disable shellcheck warnings about using
    # variables in the printf format string.
    # See https://unix.stackexchange.com/questions/438694
    # shellcheck disable=SC2059
    printf "$utility_name: error: $_fmt" "$@" >&2
    unset _fmt
}

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

    _fmt=$1; shift

    if ! "$quiet"; then
        # shellcheck disable=SC2059
        printf "$utility_name: info: $_fmt" "$@" >&2
    fi
    unset _fmt
}

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" "20190528"
    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"; then
        pinfof 'Leaving %s in place\n' "$tmpwd" >&2
    else
        pinfof 'Removing %s\n' "$tmpwd" >&2
        cd / && rm -rf -- "$tmpwd"
    fi
}

usr1_handler () { keep=true; }

force=false     # when true, bypasses the call to get_shells
keep=false      # when true, does not remove working directory when exiting
quiet=false     # when true, informational messages are not outputted

skel=${SHELL_SKEL:-}    # skeleton directory

while getopts 'd:fks:Sqv' opt; do
    case "$opt" in
        d) tmpwd=$OPTARG
           unset skel   ;;
        f) force=true   ;;
        k) keep=true    ;;
        q) quiet=true   ;;
        s) skel=$OPTARG
           unset tmpwd  ;;
        S) unset skel   ;;
        v) quiet=false
           output_version
           exit 0       ;;
        *) perrorf 'Unable to parse command line\n'
           exit 1
    esac
done
shift "$(( OPTIND - 1 ))"

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

if ! "$force"; 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
else
    realshell=$shell
fi

if [ ! -x "$realshell" ]; then
    perrorf 'No such executable: %s\n' "$shell"
    exit 1
fi

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

    # Using -d implies -k
    keep=true
else
    tmpwd="$("$MKTEMP" -d -t "shell-${shell##*/}$MKTEMP_TEMPLATE" )"
fi

trap exit_handler EXIT
trap usr1_handler USR1

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

    pinfof 'Copying %s into %s\n' "$skel" "$tmpwd" >&2
    case $PAX in
        */pax)
            ( cd -- "$skel" && "$PAX" -rw -p p . "$tmpwd" )
            ;;
        */cp)
            ( cd -- "$skel" && "$PAX" -R -p . "$tmpwd" )
            ;;
        *)
            perrorf 'Neither pax nor cp is available\n'
            exit 1
    esac
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
