#!/bin/bash

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

## NOTE: Runs as user `systemcheck`. Can run privleap actions
## `read-journalctl-logs-this-boot`,
## `read-journalctl-logs-last-boot`, and
## `read-journalctl-logs-whonix-firewall`.

#set -x

set -o errexit
set -o nounset
set -o errtrace
#set -o pipefail

## Redirect all stderr to stdout.
exec 2>&1

bash -n /usr/libexec/helper-scripts/strings.bsh
source /usr/libexec/helper-scripts/strings.bsh

## Copied from /usr/libexec/systemcheck/preparation.bsh.
source_config() {
   shopt -s nullglob
   local i
   for i in \
      /etc/systemcheck.d/*.conf \
      /usr/local/etc/systemcheck.d/*.conf \
      ; do
         bash -n "$i"
         source "$i"
   done
}

prep_temp_dir() {
  ## TMPDIR out of space
  TMPDIR='/var/cache/systemcheck-log-checker'
  TMP="$TMPDIR"
  export TMP TMPDIR
  mkdir --parents -m700 -- "$TMPDIR"
  test -w "$TMPDIR"
}

check_service_logs() {
  local counter patterns grep_ignore_fixed_items_command journal_ignore_fixed_item journal_search_pattern_list check_boot

  check_boot="${1:-}"

  safe-rm --force -- "$TMPDIR/journalctl_output.txt"
  touch -- "$TMPDIR/journalctl_output.txt"

  if [ "${check_boot}" = 'this_boot' ]; then
    ( leaprun read-journalctl-logs-this-boot || true ) 2>&1 | tee --append -- "$TMPDIR/journalctl_output.txt" >/dev/null
  elif [ "${check_boot}" = 'last_boot' ]; then
    ( leaprun read-journalctl-logs-last-boot || true ) 2>&1 | tee --append -- "$TMPDIR/journalctl_output.txt" >/dev/null
  else
    printf '%s\n' "$0: ${FUNCNAME[0]}: ERROR: Unrecognized boot identifier '${check_boot}', expected 'this_boot' or 'last_boot'!"
    return 1
  fi

  journal_search_pattern_list="SECCOMP.*syscall=|warn|fail|error|ordering cycle|BUG|segfault|killed|(^|[^a-zA-Z])fault($|[^a-zA-Z])|self-detected stall on CPU"

  if test -e /usr/share/qubes/marker-vm; then
    journal_ignore_patterns_list+=( "virtualbox" )
  fi

  grep --extended-regexp --ignore-case "$journal_search_pattern_list" -- "$TMPDIR/journalctl_output.txt" \
    | tee -- "$TMPDIR/journalctl_matched.txt" >/dev/null

  safe-rm --force -- "$TMPDIR/journalctl_output.txt"

  ## Output by 'apparmor-info' is already de-duplicated.
  leaprun read-apparmor-info | tee --append -- "$TMPDIR/journalctl_matched.txt" >/dev/null

  grep_ignore_fixed_items_command=()
  grep_ignore_fixed_items_command+=("grep")
  grep_ignore_fixed_items_command+=("--invert-match")
  grep_ignore_fixed_items_command+=("--fixed-strings")
  grep_ignore_fixed_items_command+=("--ignore-case")

  #counter=0
  for journal_ignore_fixed_item in "${journal_ignore_fixed_list[@]}"; do
    #counter=$(( counter + 1 ))
    ## Debugging.
    ## Either this should be commented out when unneeded, or it should
    ## be a `true` if it's meant to show up when `set -x` is enabled.
    ## Outputting it to /dev/null like this is a needless waste of resources.
    #printf '%s\n' "$counter: '$journal_ignore_fixed_item'" >/dev/null

    grep_ignore_fixed_items_command+=("-e")
    grep_ignore_fixed_items_command+=("$journal_ignore_fixed_item")
  done

#   counter=0
#   local journal_ignore_pattern_item
#   for journal_ignore_pattern_item in "${journal_ignore_patterns_list[@]}"; do
#     counter=$(( counter + 1 ))
#     ## Debugging.
#     #printf '%s\n' "$counter: '$journal_ignore_pattern_item'" >/dev/null
#   done

  "${grep_ignore_fixed_items_command[@]}" -- "$TMPDIR/journalctl_matched.txt" \
    | tee -- "$TMPDIR/journalctl_fixed_filtered.txt" >/dev/null

  safe-rm --force -- "$TMPDIR/journalctl_matched.txt"

  patterns="$(printf '%s|' "${journal_ignore_patterns_list[@]}" | head -c-1)"

  grep --invert-match --extended-regexp --ignore-case "$patterns" -- "$TMPDIR/journalctl_fixed_filtered.txt" \
    | tee -- "$TMPDIR/journalctl_match_filtered.txt" >/dev/null

  ## Creates file: '$TMPDIR/journalctl_match_filtered.txt_br'
  br_add_to_file "$TMPDIR/journalctl_match_filtered.txt"

  safe-rm --force -- "$TMPDIR/journalctl_match_filtered.txt"

  stcatn "$TMPDIR/journalctl_match_filtered.txt_br" | sort --unique
}

check_critical_logs() {
   local critical_pattern

   if ! test -e "$TMPDIR/journalctl_match_filtered.txt_br"; then
      return 0
   fi

   ## Patterns that are considered "so bad they must be shown" always.
   ## The space before ' BUG:' is important to avoid matching 'debug:'.
   critical_pattern='Bad RAM detected|self-detected stall on CPU| BUG:'

   grep --ignore-case --fixed-strings "$critical_pattern" -- "$TMPDIR/journalctl_match_filtered.txt_br" | \
     stcatn

   safe-rm --force -- "$TMPDIR/journalctl_match_filtered.txt_br"
}

source_config
prep_temp_dir

check_mode="${1:-}"
if [ "$check_mode" = 'check_service_logs' ]; then
  check_service_logs "${2:-}"
elif [ "$check_mode" = 'check_critical_logs' ]; then
  check_critical_logs
else
  printf '%s\n' "$0: ERROR: Unrecognized mode '${check_mode}' specified!" >&2
  exit 1
fi
