#!/bin/sh

set -o errexit

TMPDIR='/tmp'
VERSION='1.1'

PRINT_ALL=
KEEP_TEMP=
REDIRECT_COMMAND=
REDIRECT_FILE=
RESULT_FILE=

print_version()
{
    echo "${VERSION}"
    exit 0
}

print_help()
{
    echo "\
Usage:
usedfiles [-t TMDIR] [-a] [-k] [-s] [-f FILEOUTPUT] [-o FILERESULT] COMMAND
usedfiles --help | --version

Print list of files, used during some COMMAND.
Note options of usedfiles are placed strictly before the target COMMAND.

Util requires >=strace-4.10.
"
    exit 0
}

error()
{
    echo "Error:" 1>&2
    echo "$@" 1>&2
    exit 1
}

check_arg_value()
{
    arg="$1"

    check_val="${arg#-}" || true
    if [ "${check_val}" != "${arg}" ] ; then
        error "
Values of options -t, -f and -o must not begin with '-'.
Most probably, that means forgotten argument for them.
"
    fi
}

check_tmp()
{
    check_arg_value "${TMPDIR}"

    mkdir -p "${TMPDIR}"
    touch "${TMPDIR}/try"
    rm "${TMPDIR}/try"
}

check_strace()
{
    check_string="$(strace -h | grep -e '-y' 2> /dev/null)"
    if [ -z "${check_string}" ] ; then
        error "
Your strace doesn't have option -y, needed for the module.
strace-4.10 and higher should have it.
"
    fi

    if [ ! -h "/proc/self/fd/0" ] ; then
        error "
proc filesystem must be mounted for the needed work of strace in the util.
That can be done with
# mkdir -p /proc
# mount -t proc proc /proc
"
    fi
}

check_exec()
{
    if [ -z "${1}" ] ; then
        error "Nothing to execute"
    fi
}

check_redirect()
{
    check_arg_value "${REDIRECT_FILE}"

    contain_dir="$(dirname ${REDIRECT_FILE})"
    mkdir -p "${contain_dir}"
    touch "${REDIRECT_FILE}"
}

check_result()
{
    check_arg_value "${RESULT_FILE}"

    contain_dir="$(dirname ${RESULT_FILE})"
    mkdir -p "${contain_dir}"
    touch "${RESULT_FILE}"
}

filter_rules()
{
    if [ -z "${PRINT_ALL}" ] ; then
        echo '
-v
-e ^/tmp/
-e ^/proc/
-e ^/sys/
-e ^/run/
-e ^/dev/
'
    else
        echo '.'
    fi
}

launch_strace()
{
    strace -f -D -y \
        -eopen,openat,execve,fstat,stat,lstat \
        -o "${trace_FILE}" \
        "$@" || true
}

launch_filter()
{
    sed -n \
        -e   "/open([A-Z_]*[, ]*\"/s#.*[0-9]*[0-9]<\([^<>]*\)>#\1#p" \
        -e "/openat([A-Z_]*[, ]*\"/s#.*[0-9]*[0-9]<\([^<>]*\)>#\1#p" \
        -e "/execve/s#.*execve([A-Z_]*[, ]*\"\([^\"]*\)\",.*#\1#p" \
        -e "/ fstat/s#.*fstat([0-9][0-9]*<\([^<>]*\)>.*#\1#p" \
        -e  "/ stat/s#.*stat(\"\([^\"]*\)\",.* = [0-9].*#\1#p" \
        -e "/ lstat/s#.*lstat(\"\([^\"]*\)\",.* = [0-9].*#\1#p" \
        "${trace_FILE}" \
        | egrep $(filter_rules) \
        | sort | uniq
}

in_arg_cycle=1

while [ -n "$in_arg_cycle" ] ; do
    arg="$1"
    in_arg_cycle=

    checked_arg="${arg#-}" || true
    if [ "$checked_arg" != "${arg}" ] ; then
        in_arg_cycle=1
        shift

        case ${checked_arg} in
            t)
                TMPDIR="$1"
                shift || error "No argument for option -t"
            ;;
            a)
                PRINT_ALL=1
            ;;
            k)
                KEEP_TEMP=1
            ;;
            s)
                REDIRECT_COMMAND=1
            ;;
            f)
                REDIRECT_COMMAND=1
                REDIRECT_FILE="$1"
                check_redirect
                shift || error "No argument for option -f"
            ;;
            o)
                RESULT_FILE="$1"
                check_result
                shift || error "No argument for option -o"
            ;;
            -version)
                print_version
            ;;
            -help)
                print_help
            ;;
            -)
                in_arg_cycle=
            ;;
            *)
                error "Unknown option ${arg}"
            ;;
        esac
    fi
done

check_tmp
check_strace
check_exec "$@"

trace_FILE="$(mktemp -p "${TMPDIR}" usedfiles_strace.XXXXXX)"

if [ -n "${REDIRECT_COMMAND}" ] ; then

    if [ -n "${REDIRECT_FILE}" ] ; then
        output_file="${REDIRECT_FILE}"
    else
        output_file="$(mktemp -p "${TMPDIR}" usedfiles_output.XXXXXX)"
    fi
    launch_strace "${@}" \
        > "${output_file}" 2>&1 \
        || true
else
    launch_strace "${@}" \
        || true
fi

if [ -n "$(head -1 "${trace_FILE}")" ] ; then

    if [ -z "${REDIRECT_COMMAND}" -a -z "${RESULT_FILE}" ] ; then
        # separator between command and files
        echo ''
    fi
    
    if [ -n "${RESULT_FILE}" ] ; then
        launch_filter \
            > "${RESULT_FILE}" 2>&1
    else
        launch_filter
    fi
fi

if [ -z "${KEEP_TEMP}" ] ; then
    rm "${trace_FILE}"
    if [ -n "${output_file}" -a -z "${REDIRECT_FILE}" ] ; then
        rm "${output_file}"
    fi
fi


# ---------------------------------------------------------

# Copyright (c) 2017-2020 Random Crew.
#
# Available under ISC License.

# ---------------------------------------------------------

