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

#include <memory>
#include <string>
#include <utility>

#include "base/functional/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/no_destructor.h"
#include "base/not_fatal_until.h"
#include "base/observer_list.h"
#include "base/process/process.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "content/browser/utility_process_host.h"
#include "content/common/child_process.mojom.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/service_process_host.h"
#include "content/public/browser/service_process_info.h"
#include "content/public/common/content_client.h"
#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "sandbox/policy/mojom/sandbox.mojom.h"

namespace content {

namespace {

// Changes to this function should be reviewed by a security person.
bool ShouldEnableSandbox(sandbox::mojom::Sandbox sandbox) {
  if (sandbox == sandbox::mojom::Sandbox::kAudio)
    return GetContentClient()->browser()->ShouldSandboxAudioService();
  if (sandbox == sandbox::mojom::Sandbox::kNetwork)
    return GetContentClient()->browser()->ShouldSandboxNetworkService();
  return true;
}

// Internal helper to track running service processes.
class ServiceProcessTracker {
 public:
  ServiceProcessTracker() = default;

  ServiceProcessTracker(const ServiceProcessTracker&) = delete;
  ServiceProcessTracker& operator=(const ServiceProcessTracker&) = delete;

  ~ServiceProcessTracker() = default;

  ServiceProcessInfo AddProcess(base::Process process,
                                const std::optional<GURL>& site,
                                const std::string& service_interface_name) {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    auto id = GenerateNextId();
    ServiceProcessInfo info(service_interface_name, site, id,
                            std::move(process));
    auto info_dup = info.Duplicate();
    processes_.insert({id, std::move(info)});
    for (auto& observer : observers_)
      observer.OnServiceProcessLaunched(info_dup);
    return info_dup;
  }

  void NotifyTerminated(ServiceProcessId id) {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    auto iter = processes_.find(id);
    CHECK(iter != processes_.end(), base::NotFatalUntil::M130);

    for (auto& observer : observers_)
      observer.OnServiceProcessTerminatedNormally(iter->second.Duplicate());
    processes_.erase(iter);
  }

  void NotifyCrashed(ServiceProcessId id) {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    auto iter = processes_.find(id);
    CHECK(iter != processes_.end(), base::NotFatalUntil::M130);
    for (auto& observer : observers_)
      observer.OnServiceProcessCrashed(iter->second.Duplicate());
    processes_.erase(iter);
  }

  void AddObserver(ServiceProcessHost::Observer* observer) {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    observers_.AddObserver(observer);
  }

  void RemoveObserver(ServiceProcessHost::Observer* observer) {
    // NOTE: Some tests may remove observers after BrowserThreads are shut down.
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
           !BrowserThread::IsThreadInitialized(BrowserThread::UI));
    observers_.RemoveObserver(observer);
  }

  std::vector<ServiceProcessInfo> GetProcesses() {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    std::vector<ServiceProcessInfo> processes;
    for (const auto& entry : processes_)
      processes.push_back(entry.second.Duplicate());
    return processes;
  }

 private:
  ServiceProcessId GenerateNextId() {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    return service_process_id_generator_.GenerateNextId();
  }

  ServiceProcessId::Generator service_process_id_generator_;

  std::map<ServiceProcessId, ServiceProcessInfo> processes_;

  // Observers are owned and used exclusively on the UI thread.
  base::ObserverList<ServiceProcessHost::Observer> observers_;
};

ServiceProcessTracker& GetServiceProcessTracker() {
  static base::NoDestructor<ServiceProcessTracker> tracker;
  return *tracker;
}

// Helper to bridge UtilityProcessHost IO thread events to the
// ServiceProcessTracker. Every UtilityProcessHost created for a service process
// has a unique instance of this class associated with it.
class UtilityProcessClient : public UtilityProcessHost::Client {
 public:
  UtilityProcessClient(
      const std::string& service_interface_name,
      const std::optional<GURL>& site,
      base::OnceCallback<void(const base::Process&)> process_callback)
      : service_interface_name_(service_interface_name),
        site_(std::move(site)),
        process_callback_(std::move(process_callback)) {}

  UtilityProcessClient(const UtilityProcessClient&) = delete;
  UtilityProcessClient& operator=(const UtilityProcessClient&) = delete;

  ~UtilityProcessClient() override = default;

  // UtilityProcessHost::Client:
  void OnProcessLaunched(const base::Process& process) override {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    process_info_.emplace(GetServiceProcessTracker().AddProcess(
        process.Duplicate(), site_, service_interface_name_));
    if (process_callback_) {
      std::move(process_callback_).Run(process);
    }
  }

  void OnProcessTerminatedNormally() override {
    GetServiceProcessTracker().NotifyTerminated(
        process_info_->service_process_id());
  }

  void OnProcessCrashed() override {
    // TODO(crbug.com/40654042): It is unclear how we can observe
    // |OnProcessCrashed()| without observing |OnProcessLaunched()| first, but
    // it can happen on Android. Ignore the notification in this case.
    if (!process_info_)
      return;

    GetServiceProcessTracker().NotifyCrashed(
        process_info_->service_process_id());
  }

 private:
  const std::string service_interface_name_;

  // Optional site GURL for per-site utility processes.
  const std::optional<GURL> site_;

  base::OnceCallback<void(const base::Process&)> process_callback_;
  std::optional<ServiceProcessInfo> process_info_;
};

// TODO(crbug.com/40633267): Once UtilityProcessHost is used only by service
// processes, its logic can be inlined here.
void LaunchServiceProcess(mojo::GenericPendingReceiver receiver,
                          ServiceProcessHost::Options options,
                          sandbox::mojom::Sandbox sandbox) {
  UtilityProcessHost* host =
      new UtilityProcessHost(std::make_unique<UtilityProcessClient>(
          *receiver.interface_name(), options.site,
          std::move(options.process_callback)));
  host->SetName(!options.display_name.empty()
                    ? options.display_name
                    : base::UTF8ToUTF16(*receiver.interface_name()));
  host->SetMetricsName(*receiver.interface_name());
  if (!ShouldEnableSandbox(sandbox)) {
    sandbox = sandbox::mojom::Sandbox::kNoSandbox;
  }
  host->SetSandboxType(sandbox);
  host->SetExtraCommandLineSwitches(std::move(options.extra_switches));
  if (options.child_flags) {
    host->set_child_flags(*options.child_flags);
  }
#if BUILDFLAG(IS_WIN)
  if (!options.preload_libraries.empty()) {
    host->SetPreloadLibraries(options.preload_libraries);
  }
#endif  // BUILDFLAG(IS_WIN)
  if (options.allow_gpu_client.has_value() &&
      options.allow_gpu_client.value()) {
    host->SetAllowGpuClient();
  }
  host->Start();
  host->GetChildProcess()->BindServiceInterface(std::move(receiver));
}

}  // namespace

// static
std::vector<ServiceProcessInfo> ServiceProcessHost::GetRunningProcessInfo() {
  return GetServiceProcessTracker().GetProcesses();
}

// static
void ServiceProcessHost::AddObserver(Observer* observer) {
  GetServiceProcessTracker().AddObserver(observer);
}

// static
void ServiceProcessHost::RemoveObserver(Observer* observer) {
  GetServiceProcessTracker().RemoveObserver(observer);
}

// static
void ServiceProcessHost::Launch(mojo::GenericPendingReceiver receiver,
                                Options options,
                                sandbox::mojom::Sandbox sandbox) {
  DCHECK(receiver.interface_name().has_value());
  if (GetUIThreadTaskRunner({})->BelongsToCurrentThread()) {
    LaunchServiceProcess(std::move(receiver), std::move(options), sandbox);
  } else {
    GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE, base::BindOnce(&LaunchServiceProcess, std::move(receiver),
                                  std::move(options), sandbox));
  }
}

}  // namespace content