/** @file

  A brief file description

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
 */

#include "traffic_crashlog.h"
#include "ink_args.h"
#include "ink_cap.h"
#include "I_Version.h"
#include "I_Layout.h"
#include "I_RecProcess.h"
#include "RecordsConfig.h"

static int syslog_mode = false;
static int debug_mode = false;
static int wait_mode = false;
static char * host_triplet = NULL;
static int  target_pid = getppid();

// If pid_t is not sizeof(int), we will have to jiggle argument parsing.
extern char __pid_size_static_assert[sizeof(pid_t) == sizeof(int) ? 0 : -1];

static AppVersionInfo appVersionInfo;
static const ArgumentDescription argument_descriptions[] = {
  { "target", '-', "Target process ID", "I", &target_pid, NULL, NULL },
  { "host", '-', "Host triplet for the process being logged", "S*", &host_triplet, NULL, NULL },
  { "wait", '-', "Stop until signalled at startup", "F", &wait_mode, NULL, NULL },
  { "syslog", '-', "Syslog after writing a crash log", "F", &syslog_mode, NULL, NULL },
  { "debug", '-', "Enable debugging mode", "F", &debug_mode, NULL, NULL },
  HELP_ARGUMENT_DESCRIPTION(),
  VERSION_ARGUMENT_DESCRIPTION()
};

static struct tm
timestamp()
{
  time_t now = time(NULL);
  struct tm tm;

  localtime_r(&now, &tm);
  return tm;
}

static char *
crashlog_name()
{
  char            filename[64];
  struct tm       now = timestamp();
  ats_scoped_str  logdir(RecConfigReadLogDir());
  ats_scoped_str  pathname;

  strftime(filename, sizeof(filename), "crash-%Y-%m-%d-%H%M%S.log", &now);
  pathname = Layout::relative_to(logdir, filename);

  return pathname.release();
}

static FILE *
crashlog_open(const char * path)
{
  int fd;

  fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, 0400);
  return (fd == -1) ? NULL : fdopen(fd, "w");
}

int
main(int /* argc ATS_UNUSED */, char **argv)
{
  FILE * fp;
  char * logname;
  TSMgmtError mgmterr;
  crashlog_target target;

  diags = new Diags("" /* tags */, "" /* actions */, stderr);

  appVersionInfo.setup(PACKAGE_NAME, "traffic_crashlog", PACKAGE_VERSION,
          __DATE__, __TIME__, BUILD_MACHINE, BUILD_PERSON, "");

  // Process command line arguments and dump into variables
  process_args(&appVersionInfo, argument_descriptions, countof(argument_descriptions), argv);

  if (wait_mode) {
    EnableDeathSignal(SIGKILL);
    kill(getpid(), SIGSTOP);
  }

  // XXX This is a hack. traffic_manager starts traffic_server with the euid of the admin user. We are still
  // privileged, but won't be able to open files in /proc or ptrace the target. This really should be fixed
  // in traffic_manager.
  if (getuid() == 0) {
    ATS_UNUSED_RETURN(seteuid(0));
  }

  Layout::create();
  RecProcessInit(RECM_STAND_ALONE, NULL /* diags */);
  LibRecordsConfigInit();

  if (syslog_mode) {
    RecString name;
    int facility = -1;

    if (REC_ReadConfigStringAlloc(name, "proxy.config.syslog_facility") == REC_ERR_OKAY) {
      facility = facility_string_to_int(name);
      ats_free(name);
    }

    if (facility < 0) {
      facility = LOG_DAEMON;
    }

    openlog(appVersionInfo.AppStr, LOG_PID | LOG_NDELAY | LOG_NOWAIT, facility);
    diags->config.outputs[DL_Debug].to_syslog = true;
    diags->config.outputs[DL_Status].to_syslog = true;
    diags->config.outputs[DL_Note].to_syslog = true;
    diags->config.outputs[DL_Warning].to_syslog = true;
    diags->config.outputs[DL_Error].to_syslog = true;
    diags->config.outputs[DL_Fatal].to_syslog = true;
    diags->config.outputs[DL_Alert].to_syslog = true;
    diags->config.outputs[DL_Emergency].to_syslog = true;
  }

  Note("crashlog started, target=%ld, debug=%s syslog=%s, uid=%ld euid=%ld",
      (long)target_pid, debug_mode ? "true" : "false", syslog_mode ? "true" : "false",
      (long)getuid(), (long)geteuid());

  mgmterr = TSInit(NULL, (TSInitOptionT)(TS_MGMT_OPT_NO_EVENTS | TS_MGMT_OPT_NO_SOCK_TESTS));
  if (mgmterr != TS_ERR_OKAY) {
    char * msg = TSGetErrorMessage(mgmterr);
    Warning("failed to intialize management API: %s", msg);
    TSfree(msg);
  }

  ink_zero(target);
  target.pid = (pid_t)target_pid;
  target.timestamp = timestamp();

  if (host_triplet && strncmp(host_triplet, "x86_64-unknown-linux", sizeof("x86_64-unknown-linux") - 1) == 0) {
    ssize_t nbytes;
    target.flags |= CRASHLOG_HAVE_THREADINFO;

    nbytes = read(STDIN_FILENO, &target.siginfo, sizeof(target.siginfo));
    if (nbytes < (ssize_t)sizeof(target.siginfo)) {
      Warning("received %zd of %zu expected signal info bytes", nbytes, sizeof(target.siginfo));
      target.flags &= ~CRASHLOG_HAVE_THREADINFO;
    }

    nbytes = read(STDIN_FILENO, &target.ucontext, sizeof(target.ucontext));
    if (nbytes < (ssize_t)sizeof(target.ucontext)) {
      Warning("received %zd of %zu expected thread context bytes", nbytes, sizeof(target.ucontext));
      target.flags &= ~CRASHLOG_HAVE_THREADINFO;
    }
  }

  logname = crashlog_name();

  fp = debug_mode ? stdout : crashlog_open(logname);
  if (fp == NULL) {
    Error("failed to create '%s': %s", logname, strerror(errno));
    return 1;
  }

  Note("logging to %p", fp);

  crashlog_write_procname(fp, target);
  crashlog_write_exename(fp, target);
  fprintf(fp, LABELFMT "Traffic Server %s\n", "Version:", PACKAGE_VERSION);
  crashlog_write_uname(fp, target);
  crashlog_write_datime(fp, target);

  fprintf(fp, "\n");
  crashlog_write_siginfo(fp, target);

  fprintf(fp, "\n");
  crashlog_write_registers(fp, target);

  fprintf(fp, "\n");
  crashlog_write_backtrace(fp, target);

  fprintf(fp, "\n");
  crashlog_write_procstatus(fp, target);

  fprintf(fp, "\n");
  crashlog_write_regions(fp, target);

  fprintf(fp, "\n");
  crashlog_write_records(fp, target);

  Error("wrote crash log to %s", logname);

  fflush(fp);
  fclose(fp);
  return 0;
}
