/* Copyright (c) 2001, Matej Pfajfar.
 * Copyright (c) 2001-2004, Roger Dingledine.
 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
 * Copyright (c) 2007-2021, The Tor Project, Inc. */
/* See LICENSE for licensing information */

/**
 * \file torerr.c
 *
 * \brief Handling code for unrecoverable emergencies, at a lower level
 *   than the logging code.
 *
 * There are plenty of places that things can go wrong in Tor's backend
 * libraries: the allocator can fail, the locking subsystem can fail, and so
 * on.  But since these subsystems are used themselves by the logging module,
 * they can't use the logging code directly to report their errors.
 *
 * As a workaround, the logging code provides this module with a set of raw
 * fds to be used for reporting errors in the lowest-level Tor code.
 */

#include "orconfig.h"
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#include "lib/err/torerr.h"
#include "lib/err/backtrace.h"

/** Array of fds to log crash-style warnings to. */
static int sigsafe_log_fds[TOR_SIGSAFE_LOG_MAX_FDS] = { STDERR_FILENO };
/** The number of elements used in sigsafe_log_fds */
static int n_sigsafe_log_fds = 1;
/** Log granularity in milliseconds. */
static int log_granularity = 1000;

/** Write <b>s</b> to each element of sigsafe_log_fds. Return 0 on success, -1
 * on failure. */
static int
tor_log_err_sigsafe_write(const char *s)
{
  int i;
  ssize_t r;
  size_t len = strlen(s);
  int err = 0;
  for (i=0; i < n_sigsafe_log_fds; ++i) {
    r = write(sigsafe_log_fds[i], s, len);
    err += (r != (ssize_t)len);
  }
  return err ? -1 : 0;
}

/** Given a list of string arguments ending with a NULL, writes them
 * to our logs and to stderr (if possible).  This function is safe to call
 * from within a signal handler. */
void
tor_log_err_sigsafe(const char *m, ...)
{
  va_list ap;
  const char *x;
  char timebuf[33];
  time_t now = time(NULL);

  if (!m)
    return;
  if (log_granularity >= 2000) {
    int g = log_granularity / 1000;
    now -= now % g;
  }
  timebuf[0] = now < 0 ? '-' : ' ';
  if (now < 0) now = -now;
  timebuf[1] = '\0';
  format_dec_number_sigsafe(now, timebuf+1, sizeof(timebuf)-1);
  tor_log_err_sigsafe_write("\n=========================================="
                             "================== T=");
  tor_log_err_sigsafe_write(timebuf);
  tor_log_err_sigsafe_write("\n");
  tor_log_err_sigsafe_write(m);
  va_start(ap, m);
  while ((x = va_arg(ap, const char*))) {
    tor_log_err_sigsafe_write(x);
  }
  va_end(ap);
}

/** Set *<b>out</b> to a pointer to an array of the fds to log errors to from
 * inside a signal handler or other emergency condition. Return the number of
 * elements in the array. */
int
tor_log_get_sigsafe_err_fds(const int **out)
{
  *out = sigsafe_log_fds;
  return n_sigsafe_log_fds;
}

/**
 * Update the list of fds that get errors from inside a signal handler or
 * other emergency condition. Ignore any beyond the first
 * TOR_SIGSAFE_LOG_MAX_FDS.
 *
 * These fds must remain open even after the log module has shut down. (And
 * they should remain open even while logs are being reconfigured.) Therefore,
 * any fds closed by the log module should be dup()ed, and the duplicate fd
 * should be given to the err module in fds. In particular, the log module
 * closes the file log fds, but does not close the stdio log fds.
 *
 * If fds is NULL or n is 0, clears the list of error fds.
 */
void
tor_log_set_sigsafe_err_fds(const int *fds, int n)
{
  if (n > TOR_SIGSAFE_LOG_MAX_FDS) {
    n = TOR_SIGSAFE_LOG_MAX_FDS;
  }

  /* Clear the entire array. This code mitigates against some race conditions,
   * but there are still some races here:
   * - err logs are disabled while the array is cleared, and
   * - a thread can read the old value of n_sigsafe_log_fds, then read a
   *   partially written array.
   * We could fix these races using atomics, but atomics use the err module. */
  n_sigsafe_log_fds = 0;
  memset(sigsafe_log_fds, 0, sizeof(sigsafe_log_fds));
  if (fds && n > 0) {
    memcpy(sigsafe_log_fds, fds, n * sizeof(int));
    n_sigsafe_log_fds = n;
  }
}

/**
 * Reset the list of emergency error fds to its default.
 */
void
tor_log_reset_sigsafe_err_fds(void)
{
  int fds[] = { STDERR_FILENO };
  tor_log_set_sigsafe_err_fds(fds, 1);
}

/**
 * Flush the list of fds that get errors from inside a signal handler or
 * other emergency condition. These fds are shared with the logging code:
 * flushing them also flushes the log buffers.
 *
 * This function is safe to call during signal handlers.
 */
void
tor_log_flush_sigsafe_err_fds(void)
{
  /* If we don't have fsync() in unistd.h, we can't flush the logs. */
#ifdef HAVE_FSYNC
  int n_fds, i;
  const int *fds = NULL;

  n_fds = tor_log_get_sigsafe_err_fds(&fds);
  for (i = 0; i < n_fds; ++i) {
    /* This function is called on error and on shutdown, so we don't log, or
     * take any other action, if fsync() fails. */
    (void)fsync(fds[i]);
  }
#endif /* defined(HAVE_FSYNC) */
}

/**
 * Set the granularity (in ms) to use when reporting fatal errors outside
 * the logging system.
 */
void
tor_log_sigsafe_err_set_granularity(int ms)
{
  log_granularity = ms;
}

/**
 * Log an emergency assertion failure message.
 *
 * This kind of message is safe to send from within a log handler,
 * a signal handler, or other emergency situation.
 */
void
tor_raw_assertion_failed_msg_(const char *file, int line, const char *expr,
                              const char *msg)
{
  char linebuf[16];
  format_dec_number_sigsafe(line, linebuf, sizeof(linebuf));
  tor_log_err_sigsafe("INTERNAL ERROR: Raw assertion failed in ",
                      get_tor_backtrace_version(), " at ",
                      file, ":", linebuf, ": ", expr, "\n", NULL);
  if (msg) {
    tor_log_err_sigsafe_write(msg);
    tor_log_err_sigsafe_write("\n");
  }

  dump_stack_symbols_to_error_fds();

  /* Some platforms (macOS, maybe others?) can swallow the last write before an
   * abort. This issue is probably caused by a race condition between write
   * buffer cache flushing, and process termination. So we write an extra
   * newline, to make sure that the message always gets through. */
  tor_log_err_sigsafe_write("\n");
}

/**
 * Call the abort() function to kill the current process with a fatal
 * error. But first, flush the raw error file descriptors, so error messages
 * are written before process termination.
 **/
void
tor_raw_abort_(void)
{
  tor_log_flush_sigsafe_err_fds();
  abort();
}

/* As format_{hex,dex}_number_sigsafe, but takes a <b>radix</b> argument
 * in range 2..16 inclusive. */
static int
format_number_sigsafe(unsigned long x, char *buf, int buf_len,
                      unsigned int radix)
{
  unsigned long tmp;
  int len;
  char *cp;

  /* NOT tor_assert. This needs to be safe to run from within a signal
   * handler, and from within the 'tor_assert() has failed' code.  Not even
   * raw_assert(), since raw_assert() calls this function on failure. */
  if (radix < 2 || radix > 16)
    return 0;

  /* Count how many digits we need. */
  tmp = x;
  len = 1;
  while (tmp >= radix) {
    tmp /= radix;
    ++len;
  }

  /* Not long enough */
  if (!buf || len >= buf_len)
    return 0;

  cp = buf + len;
  *cp = '\0';
  do {
    unsigned digit = (unsigned) (x % radix);
    if (cp <= buf) {
      /* Not tor_assert(); see above. */
      tor_raw_abort_();
    }
    --cp;
    *cp = "0123456789ABCDEF"[digit];
    x /= radix;
  } while (x);

  /* NOT tor_assert; see above. */
  if (cp != buf) {
    tor_raw_abort_(); // LCOV_EXCL_LINE
  }

  return len;
}

/**
 * Helper function to output hex numbers from within a signal handler.
 *
 * Writes the nul-terminated hexadecimal digits of <b>x</b> into a buffer
 * <b>buf</b> of size <b>buf_len</b>, and return the actual number of digits
 * written, not counting the terminal NUL.
 *
 * If there is insufficient space, write nothing and return 0.
 *
 * This accepts an unsigned int because format_helper_exit_status() needs to
 * call it with a signed int and an unsigned char, and since the C standard
 * does not guarantee that an int is wider than a char (an int must be at
 * least 16 bits but it is permitted for a char to be that wide as well), we
 * can't assume a signed int is sufficient to accommodate an unsigned char.
 * Thus, callers will still need to add any required '-' to the final string.
 *
 * For most purposes, you'd want to use tor_snprintf("%x") instead of this
 * function; it's designed to be used in code paths where you can't call
 * arbitrary C functions.
 */
int
format_hex_number_sigsafe(unsigned long x, char *buf, int buf_len)
{
  return format_number_sigsafe(x, buf, buf_len, 16);
}

/** As format_hex_number_sigsafe, but format the number in base 10. */
int
format_dec_number_sigsafe(unsigned long x, char *buf, int buf_len)
{
  return format_number_sigsafe(x, buf, buf_len, 10);
}
