/*
    This file is part of TON Blockchain Library.
    TON Blockchain Library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.
    TON Blockchain Library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public License
    along with TON Blockchain Library.  If not, see .
    Copyright 2017-2019 Telegram Systems LLP
*/
#include "td/utils/port/signals.h"
#include "td/utils/port/config.h"
#include "td/utils/port/stacktrace.h"
#include "td/utils/port/StdStreams.h"
#include "td/utils/common.h"
#include "td/utils/format.h"
#if TD_PORT_POSIX
#include 
#include 
#include 
#endif
#if TD_PORT_WINDOWS
#include 
#endif
#include 
#include 
#include 
#include 
#include 
#include 
namespace td {
#if TD_PORT_POSIX && !TD_DARWIN_TV_OS && !TD_DARWIN_WATCH_OS
static Status protect_memory(void *addr, size_t len) {
  if (mprotect(addr, len, PROT_NONE) != 0) {
    return OS_ERROR("mprotect failed");
  }
  return Status::OK();
}
#endif
Status setup_signals_alt_stack() {
#if TD_PORT_POSIX && !TD_DARWIN_TV_OS && !TD_DARWIN_WATCH_OS
  auto page_size = getpagesize();
  auto stack_size = (MINSIGSTKSZ + 16 * page_size - 1) / page_size * page_size;
  void *stack = mmap(nullptr, stack_size + 2 * page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
  if (stack == MAP_FAILED) {
    return OS_ERROR("Mmap failed");
  }
  TRY_STATUS(protect_memory(stack, page_size));
  TRY_STATUS(protect_memory(static_cast(stack) + stack_size + page_size, page_size));
  stack_t signal_stack;
  signal_stack.ss_sp = static_cast(stack) + page_size;
  signal_stack.ss_size = stack_size;
  signal_stack.ss_flags = 0;
  if (sigaltstack(&signal_stack, nullptr) != 0) {
    return OS_ERROR("sigaltstack failed");
  }
#endif
  return Status::OK();
}
#if TD_PORT_POSIX
static void set_handler(struct sigaction &act, decltype(act.sa_handler) handler) {
  act.sa_handler = handler;
}
static void set_handler(struct sigaction &act, decltype(act.sa_sigaction) handler) {
  act.sa_sigaction = handler;
  act.sa_flags |= SA_SIGINFO;
}
template 
static Status set_signal_handler_impl(vector signals, F func) {
  struct sigaction act;
  std::memset(&act, '\0', sizeof(act));
  sigemptyset(&act.sa_mask);
  for (auto signal : signals) {
    sigaddset(&act.sa_mask, signal);
  }
  act.sa_flags = SA_RESTART | SA_ONSTACK;
  set_handler(act, func);
  for (auto signal : signals) {
    if (sigaction(signal, &act, nullptr) != 0) {
      return OS_ERROR("sigaction failed");
    }
  }
  return Status::OK();
}
static vector get_native_signals(SignalType type) {
  switch (type) {
    case SignalType::Abort:
      return {SIGABRT, SIGXCPU, SIGXFSZ};
    case SignalType::Error:
      return {SIGILL, SIGFPE, SIGBUS, SIGSEGV, SIGSYS};
    case SignalType::Quit:
      return {SIGINT, SIGTERM, SIGQUIT};
    case SignalType::Pipe:
      return {SIGPIPE};
    case SignalType::HangUp:
      return {SIGHUP};
    case SignalType::User:
      return {SIGUSR1, SIGUSR2};
    case SignalType::Other:
      return {SIGTRAP, SIGALRM, SIGVTALRM, SIGPROF, SIGTSTP, SIGTTIN, SIGTTOU};
    default:
      return {};
  }
}
#endif
#if TD_PORT_WINDOWS
static Status set_signal_handler_impl(vector signals, void (*func)(int sig)) {
  for (auto signal : signals) {
    if (std::signal(signal, func) == SIG_ERR) {
      return Status::Error("Failed to set signal handler");
    }
  }
  return Status::OK();
}
static vector get_native_signals(SignalType type) {
  switch (type) {
    case SignalType::Abort:
      return {SIGABRT};
    case SignalType::Error:
      return {SIGILL, SIGFPE, SIGSEGV};
    case SignalType::Quit:
      return {SIGINT, SIGTERM};
    case SignalType::Pipe:
      return {};
    case SignalType::HangUp:
      return {};
    case SignalType::User:
      return {};
    case SignalType::Other:
      return {};
    default:
      return {};
  }
}
#endif
Status set_signal_handler(SignalType type, void (*func)(int)) {
  return set_signal_handler_impl(get_native_signals(type), func == nullptr ? SIG_DFL : func);
}
using extended_signal_handler = void (*)(int sig, void *addr);
static extended_signal_handler extended_signal_handlers[NSIG] = {};
#if TD_PORT_POSIX
static void siginfo_handler(int signum, siginfo_t *info, void *data) {
  auto handler = extended_signal_handlers[signum];
  handler(signum, info->si_addr);
}
#elif TD_PORT_WINDOWS
static void siginfo_handler(int signum) {
  auto handler = extended_signal_handlers[signum];
  handler(signum, nullptr);
}
#endif
Status set_extended_signal_handler(SignalType type, extended_signal_handler func) {
  CHECK(func != nullptr);
  auto signals = get_native_signals(type);
  for (auto signal : signals) {
    if (0 <= signal && signal < NSIG) {
      extended_signal_handlers[signal] = func;
    } else {
      UNREACHABLE();
    }
  }
  return set_signal_handler_impl(std::move(signals), siginfo_handler);
}
Status set_runtime_signal_handler(int runtime_signal_number, void (*func)(int)) {
#ifdef SIGRTMIN
  CHECK(SIGRTMIN + runtime_signal_number <= SIGRTMAX);
  return set_signal_handler_impl({SIGRTMIN + runtime_signal_number}, func == nullptr ? SIG_DFL : func);
#else
  return Status::OK();
#endif
}
Status ignore_signal(SignalType type) {
  return set_signal_handler_impl(get_native_signals(type), SIG_IGN);
}
static void signal_safe_append_int(char **s, Slice name, int number) {
  if (number < 0) {
    number = std::numeric_limits::max();
  }
  *--*s = ' ';
  *--*s = ']';
  do {
    *--*s = static_cast(number % 10 + '0');
    number /= 10;
  } while (number > 0);
  *--*s = ' ';
  for (auto pos = static_cast(name.size()) - 1; pos >= 0; pos--) {
    *--*s = name[pos];
  }
  *--*s = '[';
}
static void signal_safe_write_data(Slice data) {
#if TD_PORT_POSIX
  while (!data.empty()) {
    auto res = write(2, data.begin(), data.size());
    if (res < 0 && errno == EINTR) {
      continue;
    }
    if (res <= 0) {
      break;
    }
    if (res > 0) {
      data.remove_prefix(res);
    }
  }
#elif TD_PORT_WINDOWS
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)
  HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE);
  DWORD bytes_written;
  WriteFile(stderr_handle, data.data(), static_cast(data.size()), &bytes_written, nullptr);
#else
// there is no stderr
#endif
#endif
}
static int get_process_id() {
#if TD_PORT_POSIX
  return getpid();
#elif TD_PORT_WINDOWS
  return GetCurrentProcessId();
#endif
}
void signal_safe_write(Slice data, bool add_header) {
  auto old_errno = errno;
  if (add_header) {
    constexpr size_t HEADER_BUF_SIZE = 100;
    char header[HEADER_BUF_SIZE];
    char *header_end = header + HEADER_BUF_SIZE;
    char *header_begin = header_end;
    signal_safe_append_int(&header_begin, "time", static_cast(std::time(nullptr)));
    signal_safe_append_int(&header_begin, "pid", get_process_id());
    signal_safe_write_data(Slice(header_begin, header_end));
  }
  signal_safe_write_data(data);
  errno = old_errno;
}
void signal_safe_write_signal_number(int sig, bool add_header) {
  char buf[100];
  char *end = buf + sizeof(buf);
  char *ptr = end;
  *--ptr = '\n';
  do {
    *--ptr = static_cast(sig % 10 + '0');
    sig /= 10;
  } while (sig != 0);
  ptr -= 8;
  std::memcpy(ptr, "Signal: ", 8);
  signal_safe_write(Slice(ptr, end), add_header);
}
void signal_safe_write_pointer(void *p, bool add_header) {
  std::uintptr_t addr = reinterpret_cast(p);
  char buf[100];
  char *end = buf + sizeof(buf);
  char *ptr = end;
  *--ptr = '\n';
  do {
    *--ptr = td::format::hex_digit(addr % 16);
    addr /= 16;
  } while (addr != 0);
  *--ptr = 'x';
  *--ptr = '0';
  ptr -= 9;
  std::memcpy(ptr, "Address: ", 9);
  signal_safe_write(Slice(ptr, end), add_header);
}
static void block_stdin() {
#if TD_PORT_POSIX
  Stdin().get_native_fd().set_is_blocking(true).ignore();
#endif
}
static void default_failure_signal_handler(int sig) {
  signal_safe_write_signal_number(sig);
  Stacktrace::PrintOptions options;
  options.use_gdb = true;
  Stacktrace::print_to_stderr(options);
  block_stdin();
  _Exit(EXIT_FAILURE);
}
Status set_default_failure_signal_handler() {
#if TD_PORT_POSIX
  Stdin();  // init static variables before atexit
#endif
  std::atexit(block_stdin);
  TRY_STATUS(setup_signals_alt_stack());
  TRY_STATUS(set_signal_handler(SignalType::Abort, default_failure_signal_handler));
  TRY_STATUS(set_signal_handler(SignalType::Error, default_failure_signal_handler));
  return Status::OK();
}
}  // namespace td