// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/crash/core/app/crashpad.h"

#include <pthread.h>
#include <sys/prctl.h>

#include <limits>

#include "base/command_line.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/posix/global_descriptors.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "build/branding_buildflags.h"
#include "components/crash/core/app/crash_reporter_client.h"
#include "components/crash/core/app/crash_switches.h"
#include "sandbox/linux/services/namespace_sandbox.h"
#include "services/service_manager/embedder/descriptors.h"
#include "third_party/crashpad/crashpad/client/crashpad_client.h"

namespace crash_reporter {

namespace {

// TODO(jperaza): This is the first chance handler type used by Breakpad and v8.
// The Crashpad FirstChanceHandler type explicitly declares the third parameter
// to be a ucontext_t* instead of a void*. Using a reinterpret cast to convert
// one function type to another causes calling the handler to fail with SIGILL
// when CFI is enabled. Use a helper with Crashpad's FirstChanceHandler type
// to delegate to the real first chance handler instead of re-interpreting the
// handler itself. This can be removed if the two function types converge.
bool (*g_first_chance_handler)(int, siginfo_t*, void*);

bool FirstChanceHandlerHelper(int signo,
                              siginfo_t* siginfo,
                              ucontext_t* context) {
  return g_first_chance_handler(signo, siginfo, context);
}

}  // namespace

void SetFirstChanceExceptionHandler(bool (*handler)(int, siginfo_t*, void*)) {
  DCHECK(!g_first_chance_handler);
  g_first_chance_handler = handler;
  crashpad::CrashpadClient::SetFirstChanceExceptionHandler(
      FirstChanceHandlerHelper);
}

// TODO(jperaza): Remove kEnableCrashpad and IsCrashpadEnabled() when Crashpad
// is fully enabled on Linux.
const char kEnableCrashpad[] = "enable-crashpad";

bool IsCrashpadEnabled() {
  return base::CommandLine::ForCurrentProcess()->HasSwitch(kEnableCrashpad);
}

bool GetHandlerSocket(int* fd, pid_t* pid) {
  return crashpad::CrashpadClient::GetHandlerSocket(fd, pid);
}

void SetPtracerAtFork() {
  pid_t pid;
  if (!GetHandlerSocket(nullptr, &pid)) {
    return;
  }
  if (pid > 0 && prctl(PR_SET_PTRACER, pid, 0, 0, 0) != 0) {
    PLOG(ERROR) << "prctl";
  }
}

namespace internal {

base::FilePath PlatformCrashpadInitialization(
    bool initial_client,
    bool browser_process,
    bool embedded_handler,
    const std::string& user_data_dir,
    const base::FilePath& exe_path,
    const std::vector<std::string>& initial_arguments) {
  DCHECK_EQ(initial_client, browser_process);
  DCHECK(initial_arguments.empty());

  // Not used on Linux.
  DCHECK(!embedded_handler);
  DCHECK(exe_path.empty());

  crashpad::CrashpadClient client;
#if defined(OS_CHROMEOS)
  std::string crash_loop_before =
      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
          switches::kCrashLoopBefore);
  if (!crash_loop_before.empty()) {
    uint64_t crash_loop_before_time;
    if (!base::StringToUint64(crash_loop_before, &crash_loop_before_time)) {
      LOG(ERROR) << "Couldn't parse --crash-loop-before=" << crash_loop_before;
      DCHECK(false);
      crash_loop_before_time = std::numeric_limits<uint64_t>::max();
    }
    client.SetCrashLoopBefore(crash_loop_before_time);
  }
#endif

  if (initial_client) {
    CrashReporterClient* crash_reporter_client = GetCrashReporterClient();
    base::FilePath database_path, metrics_path;
    crash_reporter_client->GetCrashDumpLocation(&database_path);
    crash_reporter_client->GetCrashMetricsLocation(&metrics_path);

    base::FilePath handler_path;
    if (!base::PathService::Get(base::DIR_EXE, &handler_path)) {
      return database_path;
    }
    handler_path = handler_path.Append("crashpad_handler");

    // When --use-cros-crash-reporter is set (below), the handler passes dumps
    // to ChromeOS's /sbin/crash_reporter which in turn passes the dump to
    // crash_sender which handles the upload.
    std::string url;
#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && defined(OFFICIAL_BUILD) && \
    !defined(OS_CHROMEOS)
    url = "https://clients2.google.com/cr/report";
#else
    url = std::string();
#endif

    std::string product_name, product_version, channel;
    crash_reporter_client->GetProductNameAndVersion(&product_name,
                                                    &product_version, &channel);

    std::map<std::string, std::string> annotations;
    annotations["prod"] = product_name;
    annotations["ver"] = product_version;

#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
    // Empty means stable.
    const bool allow_empty_channel = true;
#else
    const bool allow_empty_channel = false;
#endif
    if (allow_empty_channel || !channel.empty()) {
      annotations["channel"] = channel;
    }

    annotations["plat"] = std::string("Linux");

    std::vector<std::string> arguments;
    if (crash_reporter_client->ShouldMonitorCrashHandlerExpensively()) {
      arguments.push_back("--monitor-self");
    }

    // Set up --monitor-self-annotation even in the absence of --monitor-self
    // so that minidumps produced by Crashpad's generate_dump tool will
    // contain these annotations.
    arguments.push_back("--monitor-self-annotation=ptype=crashpad-handler");

#if defined(OS_CHROMEOS)
    arguments.push_back("--use-cros-crash-reporter");

    if (crash_reporter_client->IsRunningUnattended()) {
      arguments.push_back(base::StringPrintf("--minidump-dir-for-tests=%s",
                                             database_path.value().c_str()));
      arguments.push_back("--always-allow-feedback");
    }
#endif

    bool result =
        client.StartHandler(handler_path, database_path, metrics_path, url,
                            annotations, arguments, false, false);
    DCHECK(result);

    pthread_atfork(nullptr, nullptr, SetPtracerAtFork);
    return database_path;
  }

  int fd = base::GlobalDescriptors::GetInstance()->Get(
      service_manager::kCrashDumpSignal);

  pid_t pid = 0;
  if (!sandbox::NamespaceSandbox::InNewUserNamespace()) {
    std::string pid_string =
        base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
            switches::kCrashpadHandlerPid);
    bool parsed = base::StringToInt(pid_string, &pid);
    DCHECK(parsed);
  }

  // SIGSYS handling is reserved for the sandbox.
  client.SetUnhandledSignals({SIGSYS});

  client.SetHandlerSocket(crashpad::ScopedFileHandle(fd), pid);

  pthread_atfork(nullptr, nullptr, SetPtracerAtFork);
  return base::FilePath();
}

}  // namespace internal
}  // namespace crash_reporter
