blob: fbcb92bc6bc9c0dfd7c9044f9564a4b0a47020b0 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
#pragma allow_unsafe_libc_calls
#endif
#include "headless/lib/browser/headless_browser_main_parts.h"
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include "base/files/file_descriptor_watcher_posix.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/no_destructor.h"
#include "base/posix/eintr_wrapper.h"
#include "build/build_config.h"
#include "build/config/linux/dbus/buildflags.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "headless/lib/browser/headless_browser_impl.h"
#if BUILDFLAG(IS_LINUX)
#include "base/command_line.h"
#include "components/os_crypt/sync/key_storage_config_linux.h"
#include "components/os_crypt/sync/os_crypt.h"
#include "headless/public/switches.h"
#if BUILDFLAG(USE_DBUS)
#include "components/dbus/thread_linux/dbus_thread_linux.h"
#include "dbus/bus.h"
#include "device/bluetooth/dbus/bluez_dbus_manager.h"
#endif
#endif // BUILDFLAG(IS_LINUX)
namespace headless {
namespace {
int g_read_fd = 0;
int g_write_fd = 0;
bool CreatePipe() {
CHECK(!g_read_fd);
CHECK(!g_write_fd);
int pipe_fd[2];
int result = pipe(pipe_fd);
if (result < 0) {
PLOG(ERROR) << "Could not create signal pipe";
return false;
}
g_read_fd = pipe_fd[0];
g_write_fd = pipe_fd[1];
return true;
}
class BrowserShutdownHandler {
public:
typedef base::OnceCallback<void(int)> ShutdownCallback;
BrowserShutdownHandler(const BrowserShutdownHandler&) = delete;
BrowserShutdownHandler& operator=(const BrowserShutdownHandler&) = delete;
static void Install(ShutdownCallback shutdown_callback) {
GetInstance().Init(std::move(shutdown_callback));
// We need to handle SIGTERM, because that is how many POSIX-based distros
// ask processes to quit gracefully at shutdown time.
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = SIGTERMHandler;
PCHECK(sigaction(SIGTERM, &action, nullptr) == 0);
// Also handle SIGINT - when the user terminates the browser via Ctrl+C. If
// the browser process is being debugged, GDB will catch the SIGINT first.
action.sa_handler = SIGINTHandler;
PCHECK(sigaction(SIGINT, &action, nullptr) == 0);
// And SIGHUP, for when the terminal disappears. On shutdown, many Linux
// distros send SIGHUP, SIGTERM, and then SIGKILL.
action.sa_handler = SIGHUPHandler;
PCHECK(sigaction(SIGHUP, &action, nullptr) == 0);
}
private:
friend class base::NoDestructor<BrowserShutdownHandler>;
BrowserShutdownHandler() = default;
~BrowserShutdownHandler() = default;
static BrowserShutdownHandler& GetInstance() {
static base::NoDestructor<BrowserShutdownHandler> instance;
return *instance;
}
void Init(ShutdownCallback shutdown_callback) {
shutdown_callback_ = std::move(shutdown_callback);
// We cannot just PostTask from a signal handler, so route the signal
// through a pipe.
CHECK(CreatePipe());
file_descriptor_watcher_controller_ =
base::FileDescriptorWatcher::WatchReadable(
g_read_fd,
base::BindRepeating(
&BrowserShutdownHandler::OnFileCanReadWithoutBlocking,
base::Unretained(this)));
}
// This is called whenever data is available in |g_read_fd|.
void OnFileCanReadWithoutBlocking() {
int pipe_data;
if (HANDLE_EINTR(read(g_read_fd, &pipe_data, sizeof(pipe_data))) > 0) {
Shutdown(pipe_data);
}
}
void Shutdown(int signal) {
if (shutdown_callback_) {
int exit_code = 0x80u + signal;
std::move(shutdown_callback_).Run(exit_code);
}
}
static void SIGHUPHandler(int signal) {
RAW_CHECK(signal == SIGHUP);
ShutdownHandler(signal);
}
static void SIGINTHandler(int signal) {
RAW_CHECK(signal == SIGINT);
ShutdownHandler(signal);
}
static void SIGTERMHandler(int signal) {
RAW_CHECK(signal == SIGTERM);
ShutdownHandler(signal);
}
static void ShutdownHandler(int signal) {
// Reinstall the default handler. We have only one shot at graceful
// shutdown.
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = SIG_DFL;
RAW_CHECK(sigaction(signal, &action, nullptr) == 0);
// Send signal number through the pipe.
int pipe_data = signal;
std::ignore = write(g_write_fd, &pipe_data, sizeof(pipe_data));
}
ShutdownCallback shutdown_callback_;
std::unique_ptr<base::FileDescriptorWatcher::Controller>
file_descriptor_watcher_controller_;
};
} // namespace
#if BUILDFLAG(IS_LINUX)
constexpr char kProductName[] = "HeadlessChrome";
#endif
void HeadlessBrowserMainParts::PostCreateMainMessageLoop() {
BrowserShutdownHandler::Install(base::BindOnce(
&HeadlessBrowserImpl::ShutdownWithExitCode, browser_->GetWeakPtr()));
#if BUILDFLAG(IS_LINUX)
#if BUILDFLAG(USE_DBUS)
bluez::BluezDBusManager::Initialize(
dbus_thread_linux::GetSharedSystemBus().get());
#endif
// Set up crypt config. This needs to be done before anything starts the
// network service, as the raw encryption key needs to be shared with the
// network service for encrypted cookie storage.
std::unique_ptr<os_crypt::Config> config =
std::make_unique<os_crypt::Config>();
// Forward to os_crypt the flag to use a specific password store.
config->store = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kPasswordStore);
// Use a default product name
config->product_name = kProductName;
// OSCrypt can be disabled in a special settings file, but headless doesn't
// need to support that.
config->should_use_preference = false;
config->user_data_path = base::FilePath();
OSCrypt::SetConfig(std::move(config));
#endif // BUILDFLAG(IS_LINUX)
}
} // namespace headless