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

#include "chromeos/ash/experiences/arc/crash_collector/arc_crash_collector_bridge.h"

#include <inttypes.h>
#include <sysexits.h>
#include <unistd.h>

#include <utility>

#include "base/files/scoped_file.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/process/launch.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chromeos/ash/experiences/arc/arc_browser_context_keyed_service_factory_base.h"
#include "chromeos/ash/experiences/arc/session/arc_bridge_service.h"
#include "mojo/public/cpp/system/platform_handle.h"

namespace {

const char kCrashReporterPath[] = "/sbin/crash_reporter";

bool RunCrashReporter(const std::vector<std::string>& args, int stdin_fd) {
  base::LaunchOptions options;
  options.fds_to_remap.emplace_back(stdin_fd, STDIN_FILENO);

  auto process = base::LaunchProcess(args, options);

  int exit_code = 0;
  if (!process.WaitForExit(&exit_code)) {
    LOG(ERROR) << "Failed to wait for " << kCrashReporterPath;
    return false;
  }
  if (exit_code != EX_OK) {
    LOG(ERROR) << kCrashReporterPath << " failed with exit code " << exit_code;
    return false;
  }
  return true;
}

// Runs crash_reporter to save the java crash info provided via the pipe.
void RunJavaCrashReporter(const std::string& crash_type,
                          base::ScopedFD pipe,
                          std::vector<std::string> args,
                          std::optional<base::TimeDelta> uptime) {
  args.push_back("--arc_java_crash=" + crash_type);
  if (uptime) {
    args.push_back(
        base::StringPrintf("--arc_uptime=%" PRId64, uptime->InMilliseconds()));
  }

  if (!RunCrashReporter(args, pipe.get())) {
    LOG(ERROR) << "Failed to run crash_reporter";
  }
}

// Runs crash_reporter to save the native crash info provided via the files.
void RunNativeCrashReporter(const std::string& exec_name,
                            int32_t pid,
                            int64_t timestamp,
                            base::ScopedFD minidump_fd,
                            std::vector<std::string> args) {
  args.insert(args.end(),
              {"--exe=" + exec_name, base::StringPrintf("--pid=%d", pid),
               base::StringPrintf("--arc_native_time=%" PRId64, timestamp),
               "--arc_native"});

  if (!RunCrashReporter(args, minidump_fd.get())) {
    LOG(ERROR) << "Failed to run crash_reporter";
  }
}

// Runs crash_reporter to save the kernel crash info.
void RunKernelCrashReporter(base::ScopedFD ramoops_fd,
                            std::vector<std::string> args) {
  args.push_back("--arc_kernel");

  if (!RunCrashReporter(args, ramoops_fd.get())) {
    LOG(ERROR) << "Failed to run crash_reporter";
  }
}

// Only allow visible ASCII characters as parameters for crash reporter.
const char kValidCharacters[] =
    "!\"#$%&'()*+,-./"
    "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
    "abcdefghijklmnopqrstuvwxyz{|}~";
const size_t kMaxValidLength = 255;
bool ValidateString(const std::string& str) {
  if (str.empty() || str.length() > kMaxValidLength) {
    return false;
  }
  if (!base::ContainsOnlyChars(str, kValidCharacters)) {
    return false;
  }
  return true;
}

}  // namespace

namespace arc {
namespace {

// Singleton factory for ArcCrashCollectorBridge.
class ArcCrashCollectorBridgeFactory
    : public internal::ArcBrowserContextKeyedServiceFactoryBase<
          ArcCrashCollectorBridge,
          ArcCrashCollectorBridgeFactory> {
 public:
  // Factory name used by ArcBrowserContextKeyedServiceFactoryBase.
  static constexpr const char* kName = "ArcCrashCollectorBridgeFactory";

  static ArcCrashCollectorBridgeFactory* GetInstance() {
    static base::NoDestructor<ArcCrashCollectorBridgeFactory> instance;
    return instance.get();
  }

 private:
  friend base::NoDestructor<ArcCrashCollectorBridgeFactory>;
  ArcCrashCollectorBridgeFactory() = default;
  ~ArcCrashCollectorBridgeFactory() override = default;
};

}  // namespace

// static
ArcCrashCollectorBridge* ArcCrashCollectorBridge::GetForBrowserContext(
    content::BrowserContext* context) {
  return ArcCrashCollectorBridgeFactory::GetForBrowserContext(context);
}

// static
ArcCrashCollectorBridge*
ArcCrashCollectorBridge::GetForBrowserContextForTesting(
    content::BrowserContext* context) {
  return ArcCrashCollectorBridgeFactory::GetForBrowserContextForTesting(
      context);
}

ArcCrashCollectorBridge::ArcCrashCollectorBridge(
    content::BrowserContext* context,
    ArcBridgeService* bridge_service)
    : arc_bridge_service_(bridge_service) {
  arc_bridge_service_->crash_collector()->SetHost(this);
}

ArcCrashCollectorBridge::~ArcCrashCollectorBridge() {
  arc_bridge_service_->crash_collector()->SetHost(nullptr);
}

void ArcCrashCollectorBridge::DumpCrash(const std::string& type,
                                        mojo::ScopedHandle pipe,
                                        std::optional<base::TimeDelta> uptime) {
  if (!ValidateString(type)) {
    LOG(ERROR) << "Invalid type " << type;
    return;
  }
  base::ThreadPool::PostTask(
      FROM_HERE, {base::WithBaseSyncPrimitives()},
      base::BindOnce(&RunJavaCrashReporter, type,
                     mojo::UnwrapPlatformHandle(std::move(pipe)).TakeFD(),
                     CreateCrashReporterArgs(), uptime));
}

void ArcCrashCollectorBridge::DumpNativeCrash(const std::string& exec_name,
                                              int32_t pid,
                                              int64_t timestamp,
                                              mojo::ScopedHandle minidump_fd) {
  if (!ValidateString(exec_name)) {
    LOG(ERROR) << "Invalid exec_name " << exec_name;
    return;
  }
  base::ThreadPool::PostTask(
      FROM_HERE, {base::WithBaseSyncPrimitives()},
      base::BindOnce(
          &RunNativeCrashReporter, exec_name, pid, timestamp,
          mojo::UnwrapPlatformHandle(std::move(minidump_fd)).TakeFD(),
          CreateCrashReporterArgs()));
}

void ArcCrashCollectorBridge::DumpKernelCrash(
    mojo::ScopedHandle ramoops_handle) {
  base::ThreadPool::PostTask(
      FROM_HERE, {base::WithBaseSyncPrimitives()},
      base::BindOnce(
          &RunKernelCrashReporter,
          mojo::UnwrapPlatformHandle(std::move(ramoops_handle)).TakeFD(),
          CreateCrashReporterArgs()));
}

void ArcCrashCollectorBridge::SetBuildProperties(
    const std::string& device,
    const std::string& board,
    const std::string& cpu_abi,
    const std::optional<std::string>& fingerprint) {
  if (ValidateString(device)) {
    device_ = device;
  } else {
    LOG(ERROR) << "Invalid device " << device;
  }
  if (ValidateString(board)) {
    board_ = board;
  } else {
    LOG(ERROR) << "Invalid board " << board;
  }
  if (ValidateString(cpu_abi)) {
    cpu_abi_ = cpu_abi;
  } else {
    LOG(ERROR) << "Invalid cpu_abi " << cpu_abi;
  }
  if (!fingerprint || ValidateString(fingerprint.value())) {
    fingerprint_ = fingerprint;
  } else {
    LOG(ERROR) << "Invalid fingerprint " << fingerprint.value();
  }
}

std::vector<std::string> ArcCrashCollectorBridge::CreateCrashReporterArgs() {
  std::vector<std::string> args = {
      kCrashReporterPath,
      "--arc_device=" + device_,
      "--arc_board=" + board_,
      "--arc_cpu_abi=" + cpu_abi_,
  };
  if (fingerprint_) {
    args.push_back("--arc_fingerprint=" + fingerprint_.value());
  }

  return args;
}

// static
void ArcCrashCollectorBridge::EnsureFactoryBuilt() {
  ArcCrashCollectorBridgeFactory::GetInstance();
}

}  // namespace arc
