#!/bin/bash

## Copyright (C) 2012 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

set -o errexit
set -o nounset
set -o pipefail
set -o errtrace
shopt -s inherit_errexit
shopt -s shift_verbose

## Every command-line option target. Each is assigned only when its option is
## given, and the required ones are handed straight to check(), whose job is to
## report a missing value. Under nounset an unset read aborts BEFORE check()
## can say which option is missing, replacing a precise message with a bash
## error. Empty is what these expanded to before strict mode.
did_echo=""
identifier=""
progress=""
progressbaridx=""
verbose=""

scriptname="$(basename -- "${BASH_SOURCE[0]}")"

## Bound the best-effort filesystem ops in error_handler, so a stuck home dir
## cannot hang the error path. Matches msgprogressbar.
timeout_command=("timeout" "--kill-after" "1" "2")

error_handler() {
   local exit_code="$?"

   local msg="\
###############################################################################
## ${scriptname} script bug.
## No panic. Nothing is broken. Just some rare condition has been hit.
## Try again later. There is likely a solution for this problem.
## Please see Whonix News, Whonix Blog and Whonix User Help Forum.
## Please report this bug!
##
## BASH_COMMAND: ${BASH_COMMAND}
## exit_code: ${exit_code}
###############################################################################\
"
   printf '%s\n' "${msg}" >&2
   if [ ! -d ~/".msgcollector" ]; then
      mkdir --parents ~/".msgcollector"
   fi
   ## Owner-execute for traversal (see msgcollector's error_handler): defensive
   ## against a dir pre-created under a umask that masks the 0100 bit.
   "${timeout_command[@]}" chmod u+rwx -- ~/".msgcollector" || true
   "${timeout_command[@]}" append ~/".msgcollector/msgdispatcher-error.log" "${scriptname}: BASH_COMMAND: ${BASH_COMMAND} | exit_code: ${exit_code}" >/dev/null || true
   exit 1
}

trap "error_handler" ERR

parse_cmd_options() {
   trap "error_handler" ERR

   ## Thanks to:
   ## http://mywiki.wooledge.org/BashFAQ/035

   while true; do
       case "${1:-}" in
           --verbose)
               set -x
               ## Parsed for compatibility with the shared option set; this
               ## script has no verbose output of its own.
               # shellcheck disable=SC2034
               verbose="1"
               shift
               ;;
           --identifier)
               identifier="${2:-}"
               if [ "$#" -ge 2 ]; then
                  shift 2
               else
                  shift 1
               fi
               ;;
           --progress)
               progress="${2:-}"
               if [ "$#" -ge 2 ]; then
                  shift 2
               else
                  shift 1
               fi
               ;;
           --progressbaridx)
               progressbaridx="${2:-}"
               if [ "$#" -ge 2 ]; then
                  shift 2
               else
                  shift 1
               fi
               ;;
           --)
               shift
               break
               ;;
           -*)
               ## sanitize-echo: $1 is an untrusted caller argument; printing it
               ## raw would let a crafted option inject terminal escapes.
               sanitize-echo -- "${scriptname} unknown option: $1" >&2
               exit 1
               ;;
           *)
               break
               ;;
       esac
   done

   ## If there are input files (for example) that follow the options, they
   ## will remain in the "$@" positional parameters.
}

safe_file_write() {
  ## For extra security against freezing while trying to write to a non-existing pipe, let's use timeout.
  ## Use printf with positional arg to avoid shell injection.
  ## Single quotes are deliberate: '$1' must reach the inner bash as a
  ## literal so the value is passed as an ARGUMENT rather than interpolated
  ## into the code it runs.
  # shellcheck disable=SC2016
  timeout 1 /bin/bash -c 'printf "%s\n" "$1"' -- "$1" > "$2"
}

progress_bar() {
   trap "error_handler" ERR

   ## provided by: /usr/libexec/msgcollector/msgcollector_shared
   loop_protection

   local fifo="${msgcollector_run_dir}/${identifier}_${progressbaridx}_fifo"
   local progress_txt_file="${msgcollector_run_dir}/${identifier}_${progressbaridx}_progresstxt"

   if [ "${did_echo}" = "1" ]; then
      true "did already echo to progress_txt_file"
   else
      did_echo="1"
      safe_file_write "${progress}" "${progress_txt_file}"
   fi

   if [ -p "${msgcollector_run_dir}/${identifier}_${progressbaridx}_fifo" ]; then
      if [ -f "${msgcollector_run_dir}/${identifier}_${progressbaridx}_yadprogresspid" ]; then
         yad_progress_pid="$(cat "${msgcollector_run_dir}/${identifier}_${progressbaridx}_yadprogresspid")"
         ## Validate PID is numeric before using in ps.
         if ! is_whole_number "${yad_progress_pid}"; then
            true "yad_progress_pid is not numeric: ${yad_progress_pid}"
            return 0
         fi
         ## Check if 'yad' is running.
         local ps_p_exit_code
         ps_p_exit_code="0"
         ps -p "${yad_progress_pid}" >/dev/null 2>/dev/null || { ps_p_exit_code="$?"; true; };
         if [ "${ps_p_exit_code}" = "0" ]; then
            true "yad is running."
            ## || true to catch an error if the pipe no longer exists.
            safe_file_write "${progress}" "${fifo}"
            ## Debugging.
            #caller="$(ps -p $PPID)" || true
            #echo "progress: $progress | caller: $PPID | $caller" >> /home/user/progresslog
            return 0
         else
            true "ps_p_exit_code was not 0, was ${ps_p_exit_code}"
         fi
      else
         true "yadprogresspid does not exist: ${msgcollector_run_dir}/${identifier}_${progressbaridx}_yadprogresspid"
      fi
   else
      true "fifo does not exist: ${msgcollector_run_dir}/${identifier}_${progressbaridx}_fifo"
   fi
   return 0
}

source "${MSGCOLLECTOR_REPO:-}/usr/libexec/msgcollector/msgcollector_shared"
## sets: ${msgcollector_run_dir}
folder_init

## provides: msgcollector_check, is_whole_number
source "${MSGCOLLECTOR_REPO:-}/usr/libexec/msgcollector/check"

parse_cmd_options "$@"

## The ERR trap is lifted for these two calls only.
##
## R-010 adds errtrace, which makes the ERR trap fire for a failure INSIDE a
## function -- so a missing command-line option started printing the "script
## bug ... Please report this bug!" banner on top of check()'s own precise
## message. A missing option is a USER error, not a bug to report.
##
## '|| exit 1' would NOT do: putting check() in a condition context disables
## errexit inside it, so it runs on past the first failed validation and prints
## a second, contradictory complaint. Lifting the trap keeps errexit intact --
## same exit code, same single message as before strict mode.
trap - ERR
msgcollector_check "${identifier}" || exit 1
msgcollector_check "${progressbaridx}" || exit 1
trap "error_handler" ERR

if [ "${progress}" = "" ]; then
   error "Variable 'progress' does not exist"
   exit 1
fi

## Validate that progress is numeric to prevent injection.
if ! is_whole_number "${progress}"; then
   error "Variable 'progress' is not numeric: '${progress}'"
   exit 1
fi

progress_bar
