// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/crosapi/browser_launcher.h"
#include <fcntl.h>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "base/barrier_closure.h"
#include "base/base_switches.h"
#include "base/check_is_test.h"
#include "base/command_line.h"
#include "base/environment.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system/sys_info.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "build/build_config.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ash/crosapi/crosapi_id.h"
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/browser/ash/crosapi/crosapi_util.h"
#include "chrome/browser/ash/crosapi/primary_profile_creation_waiter.h"
#include "chrome/browser/browser_process.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/logging_chrome.h"
#include "chromeos/ash/components/channel/channel_info.h"
#include "chromeos/ash/components/standalone_browser/channel_util.h"
#include "chromeos/ash/components/standalone_browser/lacros_selection.h"
#include "chromeos/crosapi/cpp/crosapi_constants.h"
#include "chromeos/crosapi/mojom/crosapi.mojom-shared.h"
#include "chromeos/dbus/constants/dbus_switches.h"
#include "chromeos/startup/startup_switches.h"
#include "components/crash/core/app/crashpad.h"
#include "components/feature_engagement/public/tracker.h"
#include "components/nacl/common/buildflags.h"
#include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
#include "components/policy/core/common/values_util.h"
#include "components/session_manager/core/session_manager.h"
#include "components/user_manager/device_ownership_waiter.h"
#include "components/user_manager/device_ownership_waiter_impl.h"
#include "components/version_info/version_info.h"
#include "content/public/common/content_switches.h"
#include "gpu/config/gpu_switches.h"
#include "media/base/media_switches.h"
#include "media/capture/capture_switches.h"
#include "media/media_buildflags.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "third_party/widevine/cdm/buildflags.h"
#include "ui/base/resource/temporary_shared_resource_path_chromeos.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/ui_base_switches.h"
#include "ui/ozone/public/ozone_switches.h"
#if BUILDFLAG(ENABLE_NACL)
#include "components/nacl/common/nacl_switches.h"
#endif
#if BUILDFLAG(ENABLE_WIDEVINE)
#include "base/path_service.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/media/cdm_registration.h"
#endif
namespace crosapi {
namespace {
using LaunchParamsFromBackground = BrowserLauncher::LaunchParamsFromBackground;
using LaunchParams = BrowserLauncher::LaunchParams;
using LaunchResults = BrowserLauncher::LaunchResults;
// Resources file sharing mode.
enum class ResourcesFileSharingMode {
kDefault = 0,
// Failed to handle cached shared resources properly.
kError = 1,
};
// Global flag to skip the device ownership fetch. Global because some tests
// need to set this value before BrowserManager is constructed.
bool g_skip_device_ownership_wait_for_testing = false;
base::FilePath LacrosPostLoginLogPath() {
return browser_util::GetUserDataDir().Append("lacros.log");
}
base::FilePath LacrosLogDirectory() {
#if BUILDFLAG(IS_CHROMEOS_DEVICE)
// When pre-launching Lacros at login screen is enabled:
// - In test images, we always save Lacros logs in /var/log/lacros.
// - In non-test images, we save Lacros logs in /var/log/lacros
// only when Lacros is running at login screen. Lacros will
// redirect user-specific logs to the cryptohome after login.
// - In gLinux, there's no /var/log/lacros, so we stick with the
// default path.
if (base::FeatureList::IsEnabled(browser_util::kLacrosLaunchAtLoginScreen) &&
(base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableLoggingRedirect) ||
session_manager::SessionManager::Get()->session_state() ==
session_manager::SessionState::LOGIN_PRIMARY)) {
return base::FilePath("/var/log/lacros");
}
#endif // BUILDFLAG(IS_CHROMEOS_DEVICE)
return browser_util::GetUserDataDir();
}
base::FilePath LacrosLogPath() {
return LacrosLogDirectory().Append("lacros.log");
}
base::FilePath LacrosCrashDumpDirectory() {
return LacrosLogDirectory().Append("Crash Reports");
}
std::string GetXdgRuntimeDir() {
// If ash-chrome was given an environment variable, use it.
std::unique_ptr<base::Environment> env = base::Environment::Create();
std::string xdg_runtime_dir;
if (env->GetVar("XDG_RUNTIME_DIR", &xdg_runtime_dir)) {
return xdg_runtime_dir;
}
// Otherwise provide the default for Chrome OS devices.
return "/run/chrome";
}
// Rotate existing Lacros's log file. Returns true if a log file existed before
// being moved, and false if no log file was found.
bool RotateLacrosLogs() {
base::FilePath log_path = LacrosLogPath();
if (!base::PathExists(log_path)) {
return false;
}
if (!logging::RotateLogFile(log_path)) {
PLOG(ERROR) << "Failed to rotate the log file: " << log_path.value()
<< ". Keeping using the same log file without rotating.";
}
return true;
}
void PreloadFile(const base::FilePath& file_path) {
DLOG(WARNING) << "Preloading " << file_path;
base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
DPCHECK(file.IsValid());
if (!file.IsValid()) {
PLOG(WARNING) << "Failed opening " << file_path << " while preloading";
return;
}
int64_t file_size = file.GetLength();
if (file_size < 0) {
PLOG(WARNING) << "Failed getting size of " << file_path
<< "while preloading";
return;
}
if (readahead(file.GetPlatformFile(), 0, file_size) < 0) {
PLOG(WARNING) << "Failed preloading " << file_path;
return;
}
DLOG(WARNING) << "Preloaded " << file_path;
}
ResourcesFileSharingMode ClearOrMoveSharedResourceFileInternal(
bool clear_shared_resource_file,
base::FilePath shared_resource_path) {
// If shared resource pak doesn't exit, do nothing.
if (!base::PathExists(shared_resource_path)) {
return ResourcesFileSharingMode::kDefault;
}
// Clear shared resource file cache if `clear_shared_resource_file` is true.
if (clear_shared_resource_file) {
if (!base::DeleteFile(shared_resource_path)) {
LOG(ERROR) << "Failed to delete cached shared resource file.";
return ResourcesFileSharingMode::kError;
}
return ResourcesFileSharingMode::kDefault;
}
base::FilePath renamed_shared_resource_path =
ui::GetPathForTemporarySharedResourceFile(shared_resource_path);
// Move shared resource pak to `renamed_shared_resource_path`.
if (!base::Move(shared_resource_path, renamed_shared_resource_path)) {
LOG(ERROR) << "Failed to move cached shared resource file to temporary "
<< "location.";
return ResourcesFileSharingMode::kError;
}
return ResourcesFileSharingMode::kDefault;
}
ResourcesFileSharingMode ClearOrMoveSharedResourceFile(
bool clear_shared_resource_file) {
// Check 3 resource paks, resources.pak, chrome_100_percent.pak and
// chrome_200_percent.pak.
ResourcesFileSharingMode resources_file_sharing_mode =
ResourcesFileSharingMode::kDefault;
// Return kError if any of the resources failed to clear or move.
// Make sure that ClearOrMoveSharedResourceFileInternal() runs for all
// resources even if it already fails for some resource.
if (ClearOrMoveSharedResourceFileInternal(
clear_shared_resource_file, browser_util::GetUserDataDir().Append(
crosapi::kSharedResourcesPackName)) ==
ResourcesFileSharingMode::kError) {
resources_file_sharing_mode = ResourcesFileSharingMode::kError;
}
if (ClearOrMoveSharedResourceFileInternal(
clear_shared_resource_file,
browser_util::GetUserDataDir().Append(
crosapi::kSharedChrome100PercentPackName)) ==
ResourcesFileSharingMode::kError) {
resources_file_sharing_mode = ResourcesFileSharingMode::kError;
}
if (ClearOrMoveSharedResourceFileInternal(
clear_shared_resource_file,
browser_util::GetUserDataDir().Append(
crosapi::kSharedChrome200PercentPackName)) ==
ResourcesFileSharingMode::kError) {
resources_file_sharing_mode = ResourcesFileSharingMode::kError;
}
return resources_file_sharing_mode;
}
// This method runs some work on a background thread prior to launching lacros.
// The returns struct is used by the main thread as parameters to launch Lacros.
void DoLacrosBackgroundWorkPreLaunch(
const base::FilePath& lacros_dir,
bool clear_shared_resource_file,
bool launching_at_login_screen,
BrowserLauncher::LaunchParamsFromBackground& params) {
if (!RotateLacrosLogs()) {
// If log file does not exist, most likely the user directory does not
// exist either. So create it here.
base::File::Error error;
base::FilePath lacros_log_dir = LacrosLogDirectory();
if (!base::CreateDirectoryAndGetError(lacros_log_dir, &error)) {
LOG(ERROR) << "Failed to make directory " << lacros_log_dir << ": "
<< base::File::ErrorToString(error);
return;
}
}
int fd = HANDLE_EINTR(
open(LacrosLogPath().value().c_str(), O_WRONLY | O_CREAT | O_EXCL, 0644));
if (fd < 0) {
PLOG(ERROR) << "Failed to get file descriptor for " << LacrosLogPath();
return;
}
params.logfd = base::ScopedFD(fd);
params.enable_resource_file_sharing =
base::FeatureList::IsEnabled(features::kLacrosResourcesFileSharing);
// If resource file sharing feature is disabled, clear the cached shared
// resource file anyway.
if (!params.enable_resource_file_sharing) {
clear_shared_resource_file = true;
}
// Clear shared resource file cache if it's initial lacros launch after ash
// reboot. If not, rename shared resource file cache to temporal name on
// Lacros launch.
if (ClearOrMoveSharedResourceFile(clear_shared_resource_file) ==
ResourcesFileSharingMode::kError) {
params.enable_resource_file_sharing = false;
}
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kLacrosChromeAdditionalArgsFile)) {
const base::FilePath path =
base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
ash::switches::kLacrosChromeAdditionalArgsFile);
std::string data;
if (!base::ReadFileToString(path, &data)) {
PLOG(WARNING) << "Unable to read from lacros additional args file "
<< path.value();
}
std::vector<std::string_view> delimited_flags =
base::SplitStringPieceUsingSubstr(data, "\n", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
for (const auto& flag : delimited_flags) {
if (flag[0] != '#') {
params.lacros_additional_args.emplace_back(flag);
}
}
}
// When launching at login screen, we can take advantage of the time before
// the user inputs the password and logs in to preload Lacros-related files.
// This speeds up the perceived startup time, as they will be loaded anyway
// in the later stages of Lacros's lifetime.
if (launching_at_login_screen) {
for (const auto& file : BrowserLauncher::GetPreloadFiles(lacros_dir)) {
PreloadFile(file);
}
}
}
void TerminateProcessBackground(base::Process process,
base::TimeDelta timeout) {
// Here, lacros-chrome process may crash, or be in the shutdown procedure.
// Give some amount of time for the collection. In most cases,
// this waits until it captures the process termination.
if (process.WaitForExitWithTimeout(timeout, nullptr)) {
return;
}
// Here, the process is not yet terminated.
// This happens if some critical error happens on the mojo connection,
// while both ash-chrome and lacros-chrome are still alive.
// Terminate the lacros-chrome.
bool success = process.Terminate(/*exit_code=*/0, /*wait=*/true);
LOG_IF(ERROR, !success) << "Failed to terminate the lacros-chrome.";
}
// NOTE: Do NOT add the command line here unless it is very fundamental. Find
// the method suited the best from `SetUp*` or create a new one.
base::CommandLine CreateCommandLine(const base::FilePath& chrome_path) {
base::CommandLine command_line(chrome_path);
command_line.AppendSwitchASCII(switches::kOzonePlatform, "wayland");
// Paths are UTF-8 safe on Chrome OS.
command_line.AppendSwitchASCII("user-data-dir",
browser_util::GetUserDataDir().AsUTF8Unsafe());
// Passes the locale via command line instead of via LacrosInitParams because
// the Lacros browser process needs it early in startup, before zygote fork.
command_line.AppendSwitchASCII(switches::kLang,
g_browser_process->GetApplicationLocale());
#if BUILDFLAG(USE_CRAS)
// CrAS is the default audio server in Chrome OS.
if (base::SysInfo::IsRunningOnChromeOS()) {
command_line.AppendSwitch(switches::kUseCras);
}
#endif
return command_line;
}
// NOTE: Do NOT add the options here unless it is very fundamental. Find
// the method suited the best from `SetUp*` or create a new one.
base::LaunchOptions CreateLaunchOptions() {
base::LaunchOptions options;
options.kill_on_parent_death = true;
return options;
}
void SetUpEnvironment(ash::standalone_browser::LacrosSelection lacros_selection,
base::LaunchOptions& options) {
// If Ash is an unknown channel then this is not a production build and we
// should be using an unknown channel for Lacros as well. This prevents Lacros
// from picking up Finch experiments.
version_info::Channel update_channel = version_info::Channel::UNKNOWN;
if (ash::GetChannel() != version_info::Channel::UNKNOWN) {
update_channel = ash::standalone_browser::GetLacrosSelectionUpdateChannel(
lacros_selection);
// If we don't have channel information, we default to the "dev" channel.
if (update_channel == version_info::Channel::UNKNOWN) {
update_channel = ash::standalone_browser::kLacrosDefaultChannel;
}
}
options.environment["EGL_PLATFORM"] = "surfaceless";
options.environment["XDG_RUNTIME_DIR"] = GetXdgRuntimeDir();
options.environment["CHROME_VERSION_EXTRA"] =
version_info::GetChannelString(update_channel);
if (base::FeatureList::IsEnabled(ash::features::kLacrosWaylandLogging)) {
options.environment["WAYLAND_DEBUG"] = "1";
}
// LsbRelease and LsbReleaseTime are used by sys_info in Lacros to determine
// hardware class.
std::unique_ptr<base::Environment> env = base::Environment::Create();
std::string lsb_release;
std::string lsb_release_time;
if (env->GetVar(base::kLsbReleaseKey, &lsb_release) &&
env->GetVar(base::kLsbReleaseTimeKey, &lsb_release_time)) {
options.environment[base::kLsbReleaseKey] = std::move(lsb_release);
options.environment[base::kLsbReleaseTimeKey] = std::move(lsb_release_time);
}
}
void SetUpForDevMode(base::CommandLine& command_line) {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kSystemDevMode)) {
command_line.AppendSwitch(chromeos::switches::kSystemDevMode);
}
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAllowRAInDevMode)) {
command_line.AppendSwitch(switches::kAllowRAInDevMode);
}
}
#if BUILDFLAG(ENABLE_NACL)
void SetUpForNacl(base::CommandLine& command_line) {
// This switch is forwarded to nacl_helper and is needed before zygote fork.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kVerboseLoggingInNacl)) {
command_line.AppendSwitchASCII(
switches::kVerboseLoggingInNacl,
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kVerboseLoggingInNacl));
}
}
#endif
#if BUILDFLAG(ENABLE_WIDEVINE)
void SetUpForWidevine(base::CommandLine& command_line) {
if (base::FeatureList::IsEnabled(media::kLacrosUseAshWidevine)) {
// These directories are needed to load the Widevine CDM before zygote fork.
base::FilePath bundled_dir;
if (base::PathService::Get(chrome::DIR_BUNDLED_WIDEVINE_CDM,
&bundled_dir)) {
command_line.AppendSwitchASCII(switches::kCrosWidevineBundledDir,
bundled_dir.AsUTF8Unsafe());
}
base::FilePath component_updated_hint_file;
if (base::PathService::Get(chrome::FILE_COMPONENT_WIDEVINE_CDM_HINT,
&component_updated_hint_file)) {
command_line.AppendSwitchASCII(
switches::kCrosWidevineComponentUpdatedHintFile,
component_updated_hint_file.AsUTF8Unsafe());
}
}
}
#endif // BUILDFLAG(ENABLE_WIDEVINE)
void SetUpLacrosAdditionalParameters(const LaunchParamsFromBackground& params,
LaunchParams& parameters) {
std::string additional_env =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
ash::switches::kLacrosChromeAdditionalEnv);
base::StringPairs env_pairs;
if (base::SplitStringIntoKeyValuePairsUsingSubstr(additional_env, '=', "####",
&env_pairs)) {
for (const auto& env_pair : env_pairs) {
if (!env_pair.first.empty()) {
LOG(WARNING) << "Applying lacros env " << env_pair.first << "="
<< env_pair.second;
parameters.options.environment[env_pair.first] = env_pair.second;
}
}
}
std::string additional_flags =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
ash::switches::kLacrosChromeAdditionalArgs);
// `addtional_flags` is a string composed with flags and "####" is in between
// flags and this has to be separated one by one.
// TODO(elkurin): We should console an error log if flags are not in the
// correct format. For example, If "###" is in between flags, they become 1
// flag without an error for now.
std::vector<std::string> delimited_flags = base::SplitStringUsingSubstr(
additional_flags, "####", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
parameters.command_line.AppendArguments(
base::CommandLine::FromArgvWithoutProgram(delimited_flags), false);
parameters.command_line.AppendArguments(
base::CommandLine::FromArgvWithoutProgram(params.lacros_additional_args),
false);
}
void SetUpForGpu(base::CommandLine& command_line) {
command_line.AppendSwitch(switches::kEnableGpuRasterization);
command_line.AppendSwitch(switches::kEnableWebGLImageChromium);
// Forward flag for zero copy video capture to Lacros if it is enabled.
if (switches::IsVideoCaptureUseGpuMemoryBufferEnabled()) {
command_line.AppendSwitch(switches::kVideoCaptureUseGpuMemoryBuffer);
}
}
void SetUpLogging(bool launching_at_login_screen,
std::optional<int> logfd,
LaunchParams& parameters) {
// If logfd is valid, enable logging and redirect stdout/stderr to logfd.
if (!logfd) {
return;
}
// The next flag will make chrome log only via stderr. See
// DetermineLoggingDestination in logging_chrome.cc.
parameters.command_line.AppendSwitchASCII(switches::kEnableLogging, "stderr");
auto* current_command_line = base::CommandLine::ForCurrentProcess();
if (current_command_line->HasSwitch(switches::kLoggingLevel)) {
parameters.command_line.AppendSwitchASCII(
switches::kLoggingLevel,
current_command_line->GetSwitchValueASCII(switches::kLoggingLevel));
}
parameters.command_line.AppendSwitchASCII(
switches::kVModule,
// TODO(crbug.com/40061238): Remove after fix.
"wayland_window_drag_controller=1,wayland_data_source=1,tab_drag_"
// TODO(crbug.com/40069512): Remove after fix.
"controller=1, wayland_data_drag_controller=1");
if (launching_at_login_screen &&
!current_command_line->HasSwitch(switches::kDisableLoggingRedirect)) {
// Redirects logs to cryptohome after login on non-test images.
parameters.command_line.AppendSwitchASCII(
chromeos::switches::kCrosPostLoginLogFile,
LacrosPostLoginLogPath().value());
}
// These options will assign stdout/stderr fds to logfd in the fd table of
// the new process.
parameters.options.fds_to_remap.push_back(
std::make_pair(logfd.value(), STDOUT_FILENO));
parameters.options.fds_to_remap.push_back(
std::make_pair(logfd.value(), STDERR_FILENO));
}
// Sets up switches and arguments of command line for startup and post-login
// data.
void SetUpForStartupData(std::optional<int> startup_fd,
std::optional<int> read_pipe_fd,
LaunchParams& parameters) {
if (startup_fd) {
// Hardcoded to use FD 3 to make the ash-chrome's behavior more predictable.
// Lacros-chrome should not depend on the hardcoded value though. Instead
// it should take a look at the value passed via the command line flag.
constexpr int kStartupDataFD = 3;
parameters.command_line.AppendSwitchASCII(
chromeos::switches::kCrosStartupDataFD,
base::NumberToString(kStartupDataFD));
parameters.options.fds_to_remap.emplace_back(startup_fd.value(),
kStartupDataFD);
}
// If at login screen, open an anonymous pipe to pass post-login parameters to
// Lacros later on.
if (read_pipe_fd) {
// Pass the read side of the pipe to the Lacros process.
constexpr int kPostLoginDataFD = 4;
parameters.command_line.AppendSwitchASCII(
chromeos::switches::kCrosPostLoginDataFD,
base::NumberToString(kPostLoginDataFD));
parameters.options.fds_to_remap.emplace_back(read_pipe_fd.value(),
kPostLoginDataFD);
}
}
void SetUpForMojo(mojo::PlatformChannel& channel, LaunchParams& parameters) {
// Prepare to invite lacros-chrome to the Mojo universe of Crosapi.
std::string channel_flag_value;
channel.PrepareToPassRemoteEndpoint(¶meters.options.fds_to_remap,
&channel_flag_value);
CHECK(!channel_flag_value.empty());
parameters.command_line.AppendSwitchASCII(kCrosapiMojoPlatformChannelHandle,
channel_flag_value);
}
void SetUpForCrashpad(base::CommandLine& command_line) {
// Paths are UTF-8 safe on Chrome OS.
std::string crash_dir = LacrosCrashDumpDirectory().AsUTF8Unsafe();
command_line.AppendSwitchASCII("breakpad-dump-location", crash_dir);
}
// Sets up switches and arguments of command line for anything shared to
// Lacros.
void SetUpFeatures(const LaunchParamsFromBackground& params,
LaunchParams& parameters) {
if (params.enable_resource_file_sharing) {
// Passes a flag to enable resources file sharing to Lacros.
// To use resources file sharing feature on Lacros, it's required for ash to
// run with enabling the feature as well since the feature is based on some
// ash behavior(clear or move cached shared resource file at lacros launch).
parameters.command_line.AppendSwitch(switches::kEnableResourcesFileSharing);
}
}
} // namespace
// To be sure the lacros is running with neutral thread type.
class LacrosThreadTypeDelegate : public base::LaunchOptions::PreExecDelegate {
public:
void RunAsyncSafe() override {
// TODO(crbug.com/40212082): Currently, this is causing some deadlock issue.
// It looks like inside the function, we seem to call async unsafe API.
// For the mitigation, disabling this temporarily.
// We should revisit here, and see the impact of performance.
// SetCurrentThreadType() needs file I/O on /proc and /sys.
// base::ScopedAllowBlocking allow_blocking;
// base::PlatformThread::SetCurrentThreadType(
// base::ThreadType::kDefault);
}
};
BrowserLauncher::BrowserLauncher()
: device_ownership_waiter_(
std::make_unique<user_manager::DeviceOwnershipWaiterImpl>()) {}
BrowserLauncher::~BrowserLauncher() = default;
LaunchParamsFromBackground::LaunchParamsFromBackground() = default;
LaunchParamsFromBackground::LaunchParamsFromBackground(
LaunchParamsFromBackground&&) = default;
LaunchParamsFromBackground& LaunchParamsFromBackground::operator=(
LaunchParamsFromBackground&&) = default;
LaunchParamsFromBackground::~LaunchParamsFromBackground() = default;
LaunchParams::LaunchParams(base::CommandLine command_line,
base::LaunchOptions options)
: command_line(std::move(command_line)), options(std::move(options)) {}
LaunchParams::LaunchParams(LaunchParams&&) = default;
LaunchParams& LaunchParams::operator=(LaunchParams&&) = default;
LaunchParams::~LaunchParams() = default;
LaunchResults::LaunchResults() = default;
LaunchResults::LaunchResults(LaunchResults&&) = default;
LaunchResults& LaunchResults::operator=(LaunchResults&&) = default;
LaunchResults::~LaunchResults() = default;
// static
std::vector<base::FilePath> BrowserLauncher::GetPreloadFiles(
const base::FilePath& lacros_dir) {
std::vector<base::FilePath> paths;
// These files are the Lacros equivalent of Ash's files preloaded at boot by
// ureadahead.
static constexpr const char* kPreloadFiles[] = {
"chrome",
"chrome_100_percent.pak",
"chrome_200_percent.pak",
"chrome_crashpad_handler",
"icudtl.dat",
#if BUILDFLAG(ENABLE_NACL)
"nacl_helper",
#endif
"resources.pak",
"snapshot_blob.bin",
};
// Preload common files.
for (const char* file_name : kPreloadFiles) {
paths.push_back(lacros_dir.Append(base::FilePath(file_name)));
}
// Preload localization pack.
std::string locale = g_browser_process->GetApplicationLocale();
paths.push_back(
lacros_dir.Append(base::StringPrintf("locales/%s.pak", locale.c_str())));
// Preload Widevine CDM. Not needed for hardware secure CDMs as they are done
// by the device firmware.
#if BUILDFLAG(ENABLE_WIDEVINE)
// Locate the Widevine CDM used by Ash. Lacros is switching to use it.
for (const auto& cdm : GetSoftwareSecureWidevine()) {
paths.push_back(cdm.path);
}
#endif // BUILDFLAG(ENABLE_WIDEVINE)
return paths;
}
void BrowserLauncher::Launch(
const base::FilePath& chrome_path,
bool launching_at_login_screen,
ash::standalone_browser::LacrosSelection lacros_selection,
base::OnceClosure mojo_disconnection_cb,
bool is_keep_alive_enabled,
LaunchCompletionCallback callback) {
auto* params = new LaunchParamsFromBackground();
// Represents the number of tasks to complete before starting launch. If we
// are launching at login screen, we only need to complete background
// preparation. If not, we also wait for device owner and primary profile to
// be ready.
const int kNumTasks = launching_at_login_screen ? 1 : 3;
auto barrier_closure = base::BarrierClosure(
kNumTasks,
base::BindOnce(&BrowserLauncher::LaunchProcess,
weak_factory_.GetWeakPtr(), chrome_path,
base::WrapUnique(params), launching_at_login_screen,
lacros_selection, std::move(mojo_disconnection_cb),
is_keep_alive_enabled, std::move(callback)));
// Prepare on the background thread.
WaitForBackgroundWorkPreLaunch(chrome_path.DirName(), is_first_lacros_launch_,
launching_at_login_screen, barrier_closure,
*params);
// Set false to prepare for the next Lacros launch.
is_first_lacros_launch_ = false;
// If we are launching at login screen, do not wait for the device owner nor
// the primary profile as they won't be ready until login.
if (launching_at_login_screen) {
return;
}
WaitForDeviceOwnerFetchedAndThen(barrier_closure);
WaitForPrimaryProfileAddedAndThen(barrier_closure);
}
void BrowserLauncher::ResumeLaunch(
base::OnceCallback<
void(base::expected<base::TimeTicks, LaunchFailureReason>)> callback) {
// ResumeLaunch should be called only when the postlogin data is not yet ready
// on Lacros process.
CHECK(postlogin_pipe_fd_.is_valid());
const int kNumTasks = 2;
auto barrier_closure = base::BarrierClosure(
kNumTasks,
base::BindOnce(&BrowserLauncher::WritePostLoginData,
weak_factory_.GetWeakPtr(), std::move(callback)));
WaitForDeviceOwnerFetchedAndThen(barrier_closure);
WaitForPrimaryProfileAddedAndThen(barrier_closure);
}
bool BrowserLauncher::IsProcessValid() const {
return process_.IsValid();
}
bool BrowserLauncher::TriggerTerminate(int exit_code) const {
if (!process_.IsValid()) {
return false;
}
process_.Terminate(/*exit_code=*/exit_code, /*wait=*/false);
// TODO(mayukoaiba): We should reset `process_` by base::Process() in order to
// manage the state of process properly
return true;
}
void BrowserLauncher::EnsureProcessTerminated(base::OnceClosure callback,
base::TimeDelta timeout) {
CHECK(process_.IsValid());
base::ThreadPool::PostTaskAndReply(
FROM_HERE,
{base::WithBaseSyncPrimitives(),
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&TerminateProcessBackground, std::move(process_), timeout),
std::move(callback));
CHECK(!process_.IsValid());
}
const base::Process& BrowserLauncher::GetProcessForTesting() const {
return process_;
}
bool BrowserLauncher::LaunchProcessForTesting(const LaunchParams& parameters) {
return LaunchProcessWithParameters(parameters);
}
LaunchParams BrowserLauncher::CreateLaunchParamsForTesting(
const base::FilePath& chrome_path,
const LaunchParamsFromBackground& params,
bool launching_at_login_screen,
std::optional<int> startup_fd,
std::optional<int> read_pipe_fd,
mojo::PlatformChannel& channel,
ash::standalone_browser::LacrosSelection lacros_selection) {
return CreateLaunchParams(chrome_path, params, launching_at_login_screen,
startup_fd, read_pipe_fd, channel,
lacros_selection);
}
base::ScopedFD BrowserLauncher::CreatePostLoginPipeForTesting() {
base::ScopedFD read_pipe_fd;
CHECK(base::CreatePipe(&read_pipe_fd, &postlogin_pipe_fd_));
return read_pipe_fd;
}
void BrowserLauncher::SetUpAdditionalParametersForTesting(
LaunchParamsFromBackground& params,
LaunchParams& parameters) const {
SetUpLacrosAdditionalParameters(params, parameters);
}
void BrowserLauncher::WaitForBackgroundWorkPreLaunchForTesting(
const base::FilePath& lacros_dir,
bool clear_shared_resource_file,
bool launching_at_login_screen,
base::OnceClosure callback,
LaunchParamsFromBackground& params) {
WaitForBackgroundWorkPreLaunch(lacros_dir, clear_shared_resource_file,
launching_at_login_screen, std::move(callback),
params);
}
void BrowserLauncher::set_device_ownership_waiter_for_testing(
std::unique_ptr<user_manager::DeviceOwnershipWaiter>
device_ownership_waiter) {
CHECK(!device_ownership_waiter_called_);
device_ownership_waiter_ = std::move(device_ownership_waiter);
}
// static
void BrowserLauncher::SkipDeviceOwnershipWaitForTesting(bool skip) {
CHECK_IS_TEST();
g_skip_device_ownership_wait_for_testing = skip;
}
void BrowserLauncher::WaitForBackgroundWorkPreLaunch(
const base::FilePath& lacros_dir,
bool clear_shared_resource_file,
bool launching_at_login_screen,
base::OnceClosure callback,
LaunchParamsFromBackground& params) {
base::ThreadPool::PostTaskAndReply(
FROM_HERE, base::MayBlock(),
base::BindOnce(&DoLacrosBackgroundWorkPreLaunch, lacros_dir,
clear_shared_resource_file, launching_at_login_screen,
std::ref(params)),
base::BindOnce(std::move(callback)));
}
void BrowserLauncher::WaitForDeviceOwnerFetchedAndThen(
base::OnceClosure callback) {
if (g_skip_device_ownership_wait_for_testing) {
CHECK_IS_TEST();
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(callback));
} else {
device_ownership_waiter_called_ = true;
device_ownership_waiter_->WaitForOwnershipFetched(std::move(callback));
}
}
void BrowserLauncher::WaitForPrimaryProfileAddedAndThen(
base::OnceClosure callback) {
CHECK(!primary_profile_creation_waiter_);
primary_profile_creation_waiter_ = PrimaryProfileCreationWaiter::WaitOrRun(
g_browser_process->profile_manager(), std::move(callback));
}
void BrowserLauncher::LaunchProcess(
const base::FilePath& chrome_path,
std::unique_ptr<LaunchParamsFromBackground> params,
bool launching_at_login_screen,
ash::standalone_browser::LacrosSelection lacros_selection,
base::OnceClosure mojo_disconnection_cb,
bool is_keep_alive_enabled,
LaunchCompletionCallback callback) {
if (shutdown_requested_) {
std::move(callback).Run(
base::unexpected(LaunchFailureReason::kShutdownRequested));
return;
}
LOG(WARNING) << "Starting lacros-chrome launching at "
<< chrome_path.MaybeAsASCII();
// Creates FD for startup.
// For backward compatibility, we want to pass all the parameters at
// startup if we're not launching at login screen.
// Vice versa, if we're launching at login screen, we want to split
// the parameters in pre-login and post-login.
base::ScopedFD startup_fd = browser_util::CreateStartupData(
browser_util::InitialBrowserAction(
mojom::InitialBrowserAction::kDoNotOpenWindow),
!is_keep_alive_enabled, lacros_selection, !launching_at_login_screen);
// Creates a pipe between FDs when Lacros is launching at login screen.
base::ScopedFD read_pipe_fd;
if (launching_at_login_screen) {
CHECK(base::CreatePipe(&read_pipe_fd, &postlogin_pipe_fd_));
}
// Sets up Mojo channel.
// Uses new Crosapi mojo connection to detect process termination always.
LaunchResults launch_results;
mojo::PlatformChannel channel;
launch_results.crosapi_id = CrosapiManager::Get()->SendInvitation(
channel.TakeLocalEndpoint(), std::move(mojo_disconnection_cb));
// Initialize command line and options for launching Lacros.
// Do NOT include any codes with side effects because we just set up command
// line and options in this function. Do NOT modify LaunchParams outside of
// `CreateLaunchParams`.
LaunchParams parameters = CreateLaunchParams(
chrome_path, *params.get(), launching_at_login_screen,
startup_fd.is_valid() ? std::optional(startup_fd.get()) : std::nullopt,
read_pipe_fd.is_valid() ? std::optional(read_pipe_fd.get())
: std::nullopt,
channel, lacros_selection);
base::RecordAction(base::UserMetricsAction("Lacros.Launch"));
launch_results.lacros_launch_time = base::TimeTicks::Now();
bool success = LaunchProcessWithParameters(parameters);
channel.RemoteProcessLaunchAttempted();
// If Lacros failed to launch, it's most likely a permanent problem.
if (!success) {
std::move(callback).Run(base::unexpected(LaunchFailureReason::kUnknown));
return;
}
std::move(callback).Run(base::ok(std::move(launch_results)));
}
LaunchParams BrowserLauncher::CreateLaunchParams(
const base::FilePath& chrome_path,
const LaunchParamsFromBackground& params,
bool launching_at_login_screen,
std::optional<int> startup_fd,
std::optional<int> read_pipe_fd,
mojo::PlatformChannel& channel,
ash::standalone_browser::LacrosSelection lacros_selection) {
// Static configuration should be enabled from Lacros rather than Ash. This
// vector should only be used for dynamic configuration.
// TODO(crbug.com/40729628): Remove existing static configuration.
LaunchParams parameters(CreateCommandLine(chrome_path),
CreateLaunchOptions());
SetUpEnvironment(lacros_selection, parameters.options);
SetUpForDevMode(parameters.command_line);
#if BUILDFLAG(ENABLE_NACL)
SetUpForNacl(parameters.command_line);
#endif
#if BUILDFLAG(ENABLE_WIDEVINE)
SetUpForWidevine(parameters.command_line);
#endif
SetUpForGpu(parameters.command_line);
SetUpLogging(launching_at_login_screen,
params.logfd.is_valid() ? std::optional(params.logfd.get())
: std::nullopt,
parameters);
SetUpForStartupData(startup_fd, read_pipe_fd, parameters);
SetUpForMojo(channel, parameters);
SetUpForCrashpad(parameters.command_line);
// Ensures that child processes have the same rules about what help features,
// sharing feature and location share may show as the current process.
// NOTE: this may add an --enable-features flag to the command line if not
// already present, or append to the flag if it is.
feature_engagement::Tracker::PropagateTestStateToChildProcess(
parameters.command_line);
SetUpFeatures(params, parameters);
// Process additional parameters at the end so that /etc/chrome_dev.conf can
// override choices made by the SetUp* functions above.
SetUpLacrosAdditionalParameters(params, parameters);
return parameters;
}
bool BrowserLauncher::LaunchProcessWithParameters(
const LaunchParams& parameters) {
LOG(WARNING) << "Launching lacros with command: "
<< parameters.command_line.GetCommandLineString();
// Create the lacros-chrome subprocess.
// Checks whether process_ is valid or not in order not to overwrite
// process_.
CHECK(!process_.IsValid());
// If process_ already exists, because it does not call waitpid(2),
// the process will never be collected.
process_ = base::LaunchProcess(parameters.command_line, parameters.options);
if (!process_.IsValid()) {
LOG(ERROR) << "Failed to launch lacros-chrome";
return false;
}
LOG(WARNING) << "Launched lacros-chrome with pid " << process_.Pid();
return true;
}
void BrowserLauncher::WritePostLoginData(
base::OnceCallback<
void(base::expected<base::TimeTicks, LaunchFailureReason>)> callback) {
if (shutdown_requested_) {
std::move(callback).Run(
base::unexpected(LaunchFailureReason::kShutdownRequested));
return;
}
LOG(WARNING) << "Resume launching lacros with postlogin data.";
auto lacros_resume_time = base::TimeTicks::Now();
// Write post-login parameters into the anonymous pipe.
bool write_success = browser_util::WritePostLoginData(
postlogin_pipe_fd_.get(),
browser_util::InitialBrowserAction(
mojom::InitialBrowserAction::kDoNotOpenWindow));
PCHECK(write_success);
postlogin_pipe_fd_.reset();
std::move(callback).Run(base::ok(lacros_resume_time));
}
} // namespace crosapi