blob: 0b868bec2694fb77005eed6b2b649357be02cc16 [file] [log] [blame]
// Copyright 2014 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "login_manager/chrome_setup.h"
#include <sys/mount.h>
#include <sys/stat.h>
#include <unistd.h>
#include <array>
#include <memory>
#include <set>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/functional/bind.h>
#include <base/hash/sha1.h>
#include <base/json/json_writer.h>
#include <base/logging.h>
#include <base/process/launch.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/system/sys_info.h>
#include <base/values.h>
#include <brillo/files/file_util.h>
#include <brillo/udev/udev.h>
#include <brillo/udev/udev_device.h>
#include <brillo/udev/udev_enumerate.h>
#include <brillo/userdb_utils.h>
#include <chromeos-config/libcros_config/cros_config_interface.h>
#include <libsegmentation/feature_management.h>
#include <policy/device_policy.h>
#include <policy/libpolicy.h>
#include "login_manager/chromium_command_builder.h"
#include "login_manager/util.h"
// IMPORTANT: If you want to check for the presence of a new USE flag within
// this file via UseFlagIsSet(), you need to add it to the IUSE list in the
// libchromeos-use-flags package's ebuild file. See docs/flags.md for more
// information about this file.
using chromeos::ui::ChromiumCommandBuilder;
namespace login_manager {
constexpr char kUiPath[] = "/ui";
constexpr char kSerializedAshSwitchesProperty[] = "serialized-ash-switches";
constexpr char kHelpContentIdProperty[] = "help-content-id";
const char kWallpaperProperty[] = "wallpaper";
const char kRegulatoryLabelProperty[] = "regulatory-label";
const char kModelNameProperty[] = "name";
const char kPowerButtonPositionPath[] = "/ui/power-button";
const char kPowerButtonEdgeField[] = "edge";
const char kPowerButtonPositionField[] = "position";
const char kSideVolumeButtonPath[] = "/ui/side-volume-button";
const char kSideVolumeButtonRegion[] = "region";
const char kSideVolumeButtonSide[] = "side";
const char kHardwarePropertiesPath[] = "/hardware-properties";
const char kStylusCategoryField[] = "stylus-category";
const char kDisplayCategoryField[] = "display-type";
const char kFormFactorField[] = "form-factor";
constexpr char kFingerprintPath[] = "/fingerprint";
constexpr char kFingerprintSensorLocationField[] = "sensor-location";
constexpr char kArcScalePath[] = "/arc";
constexpr char kArcScaleProperty[] = "scale";
constexpr char kInstantTetheringPath[] = "/cross-device/instant-tethering";
constexpr char kDisableInstantTetheringProperty[] = "disable-instant-tethering";
constexpr char kOzoneNNPalmPropertiesPath[] = "/nnpalm";
constexpr char kOzoneNNPalmCompatibleProperty[] = "touch-compatible";
constexpr char kOzoneNNPalmModelVersionProperty[] = "model";
constexpr char kOzoneNNPalmRadiusProperty[] = "radius-polynomial";
constexpr std::array<const char*, 3> kOzoneNNPalmOptionalProperties = {
kOzoneNNPalmCompatibleProperty, kOzoneNNPalmModelVersionProperty,
kOzoneNNPalmRadiusProperty};
const char kPowerPath[] = "/power";
const char kAllowAmbientEQField[] = "allow-ambient-eq";
const char kAllowAmbientEQFeature[] = "AllowAmbientEQ";
constexpr char kPowerdRoPrefPath[] = "/usr/share/power_manager";
constexpr char kPowerdBoardSpecificPrefPath[] =
"/usr/share/power_manager/board_specific";
constexpr std::array<const char*, 2> kPowerdPrefPaths = {
kPowerdBoardSpecificPrefPath,
kPowerdRoPrefPath,
};
const char kSchedulerTunePath[] = "/scheduler-tune";
const char kBoostUrgentProperty[] = "boost-urgent";
constexpr char kHpsPath[] = "/hps";
constexpr char kHasHpsProperty[] = "has-hps";
// These hashes are only being used temporarily till we can determine if a
// device is a Chromebox for Meetings or not from the Install Time attributes.
// TODO(rkc, pbos): Remove these and related code once crbug.com/706523 is
// fixed.
const char* kChromeboxForMeetingAppIdHashes[] = {
"E703483CEF33DEC18B4B6DD84B5C776FB9182BDB",
"A3BC37E2148AC4E99BE4B16AF9D42DD1E592BBBE",
"1C93BD3CF875F4A73C0B2A163BB8FBDA8B8B3D80",
"307E96539209F95A1A8740C713E6998A73657D96",
"4F25792AF1AA7483936DE29C07806F203C7170A0",
"BD8781D757D830FC2E85470A1B6E8A718B7EE0D9",
"4AC2B6C63C6480D150DFDA13E4A5956EB1D0DDBB",
"81986D4F846CEDDDB962643FA501D1780DD441BB",
};
constexpr char kDmiProductNameFile[] = "/sys/class/dmi/id/product_name";
namespace {
// Path to file containing developer-supplied modifications to Chrome's
// environment and command line. Passed to
// ChromiumCommandBuilder::ApplyUserConfig().
const char kChromeDevConfigPath[] = "/etc/chrome_dev.conf";
// Returns a base::FilePath corresponding to the DATA_DIR environment variable.
base::FilePath GetDataDir(ChromiumCommandBuilder* builder) {
return base::FilePath(builder->ReadEnvVar("DATA_DIR"));
}
// Enables the "AutoNightLight" feature if "auto-night-light" is set to "True"
// in cros_config.
void SetUpAutoNightLightFlag(ChromiumCommandBuilder* builder,
brillo::CrosConfigInterface* cros_config) {
std::string auto_night_light_str;
if (!cros_config ||
!cros_config->GetString("/", "auto-night-light", &auto_night_light_str)) {
return;
}
if (auto_night_light_str != "true") {
return;
}
builder->AddFeatureEnableOverride("AutoNightLight");
}
void SetUpHasHpsFlag(ChromiumCommandBuilder* builder,
brillo::CrosConfigInterface* cros_config) {
std::string has_hps;
if (!cros_config ||
!cros_config->GetString(kHpsPath, kHasHpsProperty, &has_hps) ||
has_hps != "true") {
return;
}
builder->AddArg("--has-hps");
}
// Enables the "HandwritingRecognitionWebPlatformApi" Blink feature flag if
// "handwriting-web-platform-api" is set to "true" in cros_config.
void SetUpHandwritingRecognitionWebPlatformApiFlag(
ChromiumCommandBuilder* builder, brillo::CrosConfigInterface* cros_config) {
std::string handwriting_recognition_web_platform_api_str;
if (!cros_config ||
!cros_config->GetString("/ui", "handwriting-recognition-web-platform-api",
&handwriting_recognition_web_platform_api_str) ||
handwriting_recognition_web_platform_api_str != "true") {
return;
}
builder->AddFeatureEnableOverride("HandwritingRecognitionWebPlatformApi");
}
void SetUpOsInstallFlags(ChromiumCommandBuilder* builder) {
if (!builder->UseFlagIsSet("os_install_service")) {
return;
}
std::string output;
if (!base::GetAppOutput({"is_running_from_installer"}, &output)) {
LOG(ERROR) << "Failed to run is_running_from_installer";
return;
}
if (output == "yes\n") {
builder->AddArg("--allow-os-install");
}
}
// Called by AddUiFlags() to take a wallpaper flag type ("default" or "guest"
// or "child") and file type (e.g. "child", "default", "oem", "guest") and
// add the corresponding flags to |builder| if the files exist. Returns false
// if the files don't exist.
bool AddWallpaperFlags(
ChromiumCommandBuilder* builder,
const std::string& flag_type,
const std::string& file_type,
const base::RepeatingCallback<bool(const base::FilePath&)>& path_exists) {
const base::FilePath large_path(base::StringPrintf(
"/usr/share/chromeos-assets/wallpaper/%s_large.jpg", file_type.c_str()));
const base::FilePath small_path(base::StringPrintf(
"/usr/share/chromeos-assets/wallpaper/%s_small.jpg", file_type.c_str()));
if (!path_exists.Run(large_path) || !path_exists.Run(small_path)) {
LOG(WARNING) << "Could not find both paths: " << large_path.MaybeAsASCII()
<< " and " << small_path.MaybeAsASCII();
return false;
}
builder->AddArg(base::StringPrintf("--%s-wallpaper-large=%s",
flag_type.c_str(),
large_path.value().c_str()));
builder->AddArg(base::StringPrintf("--%s-wallpaper-small=%s",
flag_type.c_str(),
small_path.value().c_str()));
return true;
}
void AddCrostiniFlags(ChromiumCommandBuilder* builder) {
if (builder->UseFlagIsSet("kvm_host")) {
builder->AddFeatureEnableOverride("Crostini");
}
}
void AddPluginVmFlags(ChromiumCommandBuilder* builder) {
if (builder->UseFlagIsSet("pita")) {
builder->AddFeatureEnableOverride("PluginVm");
}
if (builder->UseFlagIsSet("pita-camera")) {
builder->AddFeatureEnableOverride("PluginVmShowCameraPermissions");
}
if (builder->UseFlagIsSet("pita-microphone")) {
builder->AddFeatureEnableOverride("PluginVmShowMicrophonePermissions");
}
}
void AddBorealisFlags(ChromiumCommandBuilder* builder) {
if (builder->UseFlagIsSet("borealis_host")) {
builder->AddFeatureEnableOverride("Borealis");
}
}
// Adds system-related flags to the command line.
void AddSystemFlags(ChromiumCommandBuilder* builder,
brillo::CrosConfigInterface* cros_config) {
const base::FilePath data_dir = GetDataDir(builder);
// We need to delete these files as Chrome may have left them around from its
// prior run (if it crashed).
brillo::DeleteFile(data_dir.Append("SingletonLock"));
brillo::DeleteFile(data_dir.Append("SingletonSocket"));
// Some targets (embedded, VMs) do not need component updates.
if (!builder->UseFlagIsSet("compupdates")) {
builder->AddArg("--disable-component-update");
}
// On developer systems, set a flag to let the browser know.
if (builder->is_developer_end_user()) {
builder->AddArg("--system-developer-mode");
}
if (builder->UseFlagIsSet("diagnostics")) {
builder->AddFeatureEnableOverride("UmaStorageDimensions");
}
// TODO(b/187516317): remove when the issue is resolved in FW.
if (builder->UseFlagIsSet("broken_24hours_wake")) {
builder->AddFeatureDisableOverride("SupportsRtcWakeOver24Hours");
}
// Enable Wilco only features.
if (builder->UseFlagIsSet("wilco")) {
builder->AddFeatureEnableOverride("WilcoDtc");
// Needed for scheduled update checks on Wilco.
builder->AddArg("--register-max-dark-suspend-delay");
}
// Some platforms have SMT enabled by default.
if (builder->UseFlagIsSet("scheduler_configuration_performance")) {
builder->AddArg("--scheduler-configuration-default=performance");
}
// Enable runtime TPM selection. This UseFlag is set only on reven board.
if (builder->UseFlagIsSet("tpm_dynamic")) {
builder->AddArg("--tpm-is-dynamic");
}
// Enable special branded strings. This UseFlag is set only on reven board.
if (builder->UseFlagIsSet("reven_branding")) {
builder->AddArg("--reven-branding");
}
// TODO(b/274706377): remove when the GPU Hang issue is resolved.
if (builder->UseFlagIsSet("disable_webrtc_hw_decoding")) {
builder->AddFeatureDisableOverride("webrtc-hw-decoding");
}
// In ash, we use mojo service manager as the mojo broker so disable it here.
builder->AddArg("--disable-mojo-broker");
builder->AddArg("--ash-use-cros-mojo-service-manager");
builder->AddArg("--cros-healthd-uses-service-manager");
// Enable MojoIpcz in ash if the ipcz USE flag is enabled.
if (builder->UseFlagIsSet("ipcz")) {
builder->AddFeatureEnableOverride("MojoIpcz");
}
SetUpOsInstallFlags(builder);
SetUpSchedulerFlags(builder, cros_config);
}
std::string ConvertNullToEmptyString(const char* str) {
return str ? str : std::string();
}
void SetUpHPEngageOneProAIOSystem(ChromiumCommandBuilder* builder) {
std::string dmi_product_name;
if (!base::ReadFileToString(base::FilePath(kDmiProductNameFile),
&dmi_product_name)) {
LOG(ERROR) << "failed to load product_name dmi id file";
return;
}
base::TrimWhitespaceASCII(dmi_product_name, base::TRIM_TRAILING,
&dmi_product_name);
if (dmi_product_name != std::string("HP Engage One Pro AIO System")) {
return;
}
auto udev = brillo::Udev::Create();
auto enumerate = udev->CreateEnumerate();
if (!enumerate->AddMatchSubsystem("input") || !enumerate->ScanDevices()) {
return;
}
for (std::unique_ptr<brillo::UdevListEntry> list_entry =
enumerate->GetListEntry();
list_entry; list_entry = list_entry->GetNext()) {
std::string sys_path = ConvertNullToEmptyString(list_entry->GetName());
std::unique_ptr<brillo::UdevDevice> device =
udev->CreateDeviceFromSysPath(sys_path.c_str());
if (!device) {
continue;
}
double touch_slop_distance = 0;
std::string touch_slop_distance_string = ConvertNullToEmptyString(
device->GetPropertyValue("CROS_TOUCH_SLOP_DISTANCE"));
if (!base::StringToDouble(touch_slop_distance_string,
&touch_slop_distance)) {
if (touch_slop_distance_string != "") {
LOG(WARNING) << "Invalid touch-slop-distance: '"
<< touch_slop_distance_string << "'.";
}
continue;
}
builder->AddArg(
base::StringPrintf("--touch-slop-distance=%f", touch_slop_distance));
break;
}
}
// Adds UI-related flags to the command line.
void AddUiFlags(ChromiumCommandBuilder* builder,
brillo::CrosConfigInterface* cros_config) {
const base::FilePath data_dir = GetDataDir(builder);
// Force OOBE on test images that have requested it.
if (base::PathExists(base::FilePath("/root/.test_repeat_oobe"))) {
brillo::DeleteFile(data_dir.Append(".oobe_completed"));
brillo::DeleteFile(data_dir.Append("Local State"));
}
// Disable logging redirection on test images to make debugging easier.
if (builder->is_test_build()) {
builder->AddArg("--disable-logging-redirect");
}
if (builder->UseFlagIsSet("cfm_enabled_device") &&
builder->UseFlagIsSet("screenshare_sw_codec")) {
builder->AddFeatureEnableOverride("WebRtcScreenshareSwEncoding");
}
if (builder->UseFlagIsSet("touch_centric_device")) {
// Tapping the power button should turn the screen off in laptop mode.
builder->AddArg("--force-tablet-power-button");
// Show touch centric OOBE screens during the first user run in laptop mode.
builder->AddArg("--oobe-force-tablet-first-run");
}
builder->AddArg("--login-manager");
builder->AddArg("--login-profile=user");
if (builder->UseFlagIsSet("natural_scroll_default")) {
builder->AddArg("--enable-natural-scroll-default");
}
if (!builder->UseFlagIsSet("legacy_keyboard")) {
builder->AddArg("--has-chromeos-keyboard");
}
if (builder->UseFlagIsSet("legacy_power_button")) {
builder->AddArg("--aura-legacy-power-button");
}
if (builder->UseFlagIsSet("touchview")) {
builder->AddArg("--enable-touchview");
}
if (builder->UseFlagIsSet("touchscreen_wakeup")) {
builder->AddArg("--touchscreen-usable-while-screen-off");
}
if (builder->UseFlagIsSet("oobe_skip_to_login")) {
builder->AddArg("--oobe-skip-to-login");
}
if (builder->UseFlagIsSet("oobe_skip_postlogin")) {
builder->AddArg("--oobe-skip-postlogin");
}
if (builder->UseFlagIsSet("disable_background_blur")) {
builder->AddFeatureDisableOverride("EnableBackgroundBlur");
}
if (builder->UseFlagIsSet("disable_explicit_dma_fences")) {
builder->AddArg("--disable-explicit-dma-fences");
}
if (builder->UseFlagIsSet("shelf-hotseat")) {
builder->AddFeatureEnableOverride("ShelfHotseat");
}
if (builder->UseFlagIsSet("webui-tab-strip")) {
builder->AddFeatureEnableOverride("WebUITabStrip");
builder->AddFeatureEnableOverride("WebUITabStripTabDragIntegration");
}
// TODO(b/180138001): Remove the following flag when a proper fix for
// the freeze issue is found.
if (builder->UseFlagIsSet("set_hw_overlay_strategy_none")) {
builder->AddArg("--enable-hardware-overlays=\"\"");
}
SetUpAutoDimFlag(builder, cros_config);
SetUpFormFactorFlag(builder, cros_config);
SetUpWallpaperFlags(builder, cros_config,
base::BindRepeating(base::PathExists));
// TODO(yongjaek): Remove the following flag when the kiosk mode app is ready
// at crbug.com/309806.
if (builder->UseFlagIsSet("moblab")) {
builder->AddArg("--disable-demo-mode");
}
if (builder->UseFlagIsSet("allow_consumer_kiosk")) {
builder->AddArg("--enable-consumer-kiosk");
}
if (builder->UseFlagIsSet("biod")) {
builder->AddFeatureEnableOverride("QuickUnlockFingerprint");
}
if (builder->UseFlagIsSet("clear_fast_ink_buffer")) {
builder->AddArg("--ash-clear-fast-ink-buffer");
}
if (builder->UseFlagIsSet("enable_dsp_hotword")) {
builder->AddFeatureEnableOverride("EnableDspHotword");
}
// TODO(http://b/367799751): Remove when the issue gets fixed.
if (builder->UseFlagIsSet("ignore_holdback_experiments")) {
builder->AddFeatureEnableOverride("IgnoreM129Holdback");
}
SetUpDelayOnActiveCameraClientChangeForNotificationFlag(builder, cros_config);
SetUpPowerButtonPositionFlag(builder, cros_config);
SetUpSideVolumeButtonPositionFlag(builder, cros_config);
SetUpHelpContentSwitch(builder, cros_config);
SetUpRegulatoryLabelFlag(builder, cros_config);
SetUpInternalStylusFlag(builder, cros_config);
SetUpFingerprintSensorLocationFlag(builder, cros_config);
SetUpOzoneNNPalmPropertiesFlag(builder, cros_config);
SetUpAutoNightLightFlag(builder, cros_config);
SetUpAllowAmbientEQFlag(builder, cros_config);
SetUpInstantTetheringFlag(builder, cros_config);
SetUpHPEngageOneProAIOSystem(builder);
std::string model_name;
if (cros_config->GetString("/", kModelNameProperty, &model_name) &&
model_name == "xol") {
// TODO(b/358470481): Revisit this flag after finding a better approach.
builder->AddFeatureEnableOverride("OledScaleFactorEnabled");
}
}
// Adds enterprise-related flags to the command line.
void AddEnterpriseFlags(ChromiumCommandBuilder* builder) {
builder->AddArg("--enterprise-enrollment-initial-modulus=15");
builder->AddArg("--enterprise-enrollment-modulus-limit=19");
}
} // namespace
ChromeSetup::ChromeSetup(brillo::CrosConfigInterface& cros_config,
segmentation::FeatureManagement& feature_management)
: cros_config_(cros_config), feature_management_(feature_management) {}
ChromeSetup::~ChromeSetup() = default;
std::optional<ChromeSetup::Result> ChromeSetup::Run() {
ChromiumCommandBuilder builder;
std::set<std::string> disallowed_prefixes;
CHECK(builder.Init());
CHECK(builder.SetUpChromium());
// Please add new code to the most-appropriate helper function instead of
// putting it here. Things that apply to all Chromium-derived binaries (e.g.
// app_shell, content_shell, etc.) rather than just to Chrome belong in the
// ChromiumCommandBuilder class instead.
CreateDirectories(&builder);
AddSerializedAshSwitches(&builder, &cros_config_);
AddSystemFlags(&builder, &cros_config_);
AddUiFlags(&builder, &cros_config_);
AddArcFlags(&builder, &disallowed_prefixes, &cros_config_);
AddCrostiniFlags(&builder);
AddPluginVmFlags(&builder);
AddBorealisFlags(&builder);
AddEnterpriseFlags(&builder);
AddMlFlags(&builder, &cros_config_);
AddFeatureManagementFlags(&builder, &feature_management_);
AddDeviceSpecificFlags(&builder);
AddMantisFlags(&builder);
AddXSFlags(&builder);
// Apply any modifications requested by the developer.
if (builder.is_developer_end_user()) {
builder.ApplyUserConfig(base::FilePath(kChromeDevConfigPath),
disallowed_prefixes);
}
// Do not add code here. Potentially-expensive work should be done between
// StartServer() and WaitForServer().
std::vector<std::string> env;
for (auto& item : builder.environment_variables()) {
env.push_back(
base::StringPrintf("%s=%s", item.first.c_str(), item.second.c_str()));
}
return Result{
std::move(builder.arguments()),
std::move(env),
builder.is_developer_end_user(),
builder.uid(),
};
}
void ChromeSetup::CreateDirectories(ChromiumCommandBuilder* builder) {
const uid_t uid = builder->uid();
const gid_t gid = builder->gid();
const uid_t kRootUid = 0;
const gid_t kRootGid = 0;
const base::FilePath data_dir("/home/chronos");
builder->AddEnvVar("DATA_DIR", data_dir.value());
CHECK(EnsureDirectoryExists(data_dir, uid, gid, 0755));
builder->AddArg("--user-data-dir=" + data_dir.value());
const base::FilePath user_dir = data_dir.Append("user");
CHECK(EnsureDirectoryExists(user_dir, uid, gid, 0755));
// TODO(keescook): Remove Chrome's use of $HOME.
builder->AddEnvVar("HOME", user_dir.value());
// Old builds will have a profile dir that's owned by root; newer ones won't
// have this directory at all.
CHECK(EnsureDirectoryExists(data_dir.Append("Default"), uid, gid, 0755));
const base::FilePath state_dir("/run/state");
CHECK(brillo::DeletePathRecursively(state_dir));
CHECK(EnsureDirectoryExists(state_dir, kRootUid, kRootGid, 0710));
// Create a directory where the session manager can store a copy of the user
// policy key, that will be readable by the chrome process as chronos.
const base::FilePath policy_dir("/run/user_policy");
CHECK(brillo::DeletePathRecursively(policy_dir));
CHECK(EnsureDirectoryExists(policy_dir, kRootUid, gid, 0710));
// Create a directory where the chrome process can store a reboot request so
// that it persists across browser crashes but is always removed on reboot.
// This directory also houses the default wayland and arc-bridge sockets that
// are exported to VMs and Android.
CHECK(EnsureDirectoryExists(base::FilePath("/run/chrome"), uid, gid, 0755));
// Create a directory where the libassistant V2 can create socket files for
// gRPC.
const base::FilePath libassistant_dir("/run/libassistant");
CHECK(brillo::DeletePathRecursively(libassistant_dir));
CHECK(EnsureDirectoryExists(libassistant_dir, uid, gid, 0700));
// The directory where Chrome Remote Desktop stores session information to
// allow resuming the CRD session after Chrome restarts.
// This must be cleared on a device reboot so we use the `/run` tmpfs.
base::FilePath crd_storage_dir("/run/crd");
CHECK(EnsureDirectoryExists(crd_storage_dir, uid, gid, 0700));
// Create the directory where policies for extensions installed in
// device-local accounts are cached. This data is read and written by chronos.
CHECK(EnsureDirectoryExists(
base::FilePath("/var/cache/device_local_account_component_policy"), uid,
gid, 0700));
// Create the directory where external data referenced by policies is cached
// for device-local accounts. This data is read and written by chronos.
CHECK(EnsureDirectoryExists(
base::FilePath("/var/cache/device_local_account_external_policy_data"),
uid, gid, 0700));
// Create the directory where external data referenced by device policy is
// cached. This data is read and written by chronos.
CHECK(EnsureDirectoryExists(
base::FilePath("/var/cache/device_policy_external_data"), uid, gid,
0700));
// Create the directory where screensaver images data referenced by device
// policy is cached. This data is read and written by chronos.
CHECK(EnsureDirectoryExists(base::FilePath("/var/cache/managed_screensaver"),
uid, gid, 0700));
// Create the directory where the AppPack extensions are cached.
// These extensions are read and written by chronos.
CHECK(EnsureDirectoryExists(base::FilePath("/var/cache/app_pack"), uid, gid,
0700));
// Create the directory where extensions for device-local accounts are cached.
// These extensions are read and written by chronos.
CHECK(EnsureDirectoryExists(
base::FilePath("/var/cache/device_local_account_extensions"), uid, gid,
0700));
// Create the directory where the Quirks Client can store downloaded
// icc and other display profiles.
CHECK(EnsureDirectoryExists(base::FilePath("/var/cache/display_profiles"),
uid, gid, 0700));
// Create the directory for shared installed extensions.
// Shared extensions are validated at runtime by the browser.
// These extensions are read and written by chronos.
CHECK(EnsureDirectoryExists(base::FilePath("/var/cache/shared_extensions"),
uid, gid, 0700));
// Create the directory where policies for extensions installed in the
// sign-in profile are cached. This data is read and written by chronos.
CHECK(EnsureDirectoryExists(
base::FilePath("/var/cache/signin_profile_component_policy"), uid, gid,
0700));
// Create the directory where extensions for the sign-in profile are cached.
// This data is read and written by chronos.
CHECK(EnsureDirectoryExists(
base::FilePath("/var/cache/signin_profile_extensions"), uid, gid, 0700));
SetUpTimezoneSymlink(builder->uid(), builder->gid());
SetUpDebugfsGpu();
// Tell Chrome where to write logging messages before the user logs in.
base::FilePath system_log_dir("/var/log/chrome");
CHECK(EnsureDirectoryExists(system_log_dir, uid, gid, 0755));
builder->AddEnvVar("CHROME_LOG_FILE",
system_log_dir.Append("chrome").value());
// Log directory for the user session. Note that the user dir won't be mounted
// until later (when the cryptohome is mounted), so we don't create
// CHROMEOS_SESSION_LOG_DIR here.
builder->AddEnvVar("CHROMEOS_SESSION_LOG_DIR",
user_dir.Append("log").value());
// Disable Mesa's internal shader disk caching feature, since Chrome has its
// own shader cache implementation and the GPU process sandbox does not
// allow threads (Mesa uses threads for this feature).
builder->AddEnvVar("MESA_GLSL_CACHE_DISABLE", "true"); // Mesa classic
builder->AddEnvVar("MESA_SHADER_CACHE_DISABLE", "true"); // Mesa iris
}
void ChromeSetup::SetUpTimezoneSymlink(uid_t uid, gid_t gid) {
CreateSymlinkIfMissing(base::FilePath("/usr/share/zoneinfo/US/Pacific"),
base::FilePath("/var/lib/timezone/localtime"), uid,
gid);
}
void ChromeSetup::CreateSymlinkIfMissing(const base::FilePath& source,
const base::FilePath& target,
uid_t uid,
gid_t gid) const {
CHECK(EnsureDirectoryExists(target.DirName(), uid, gid, 0755));
if (base::PathExists(target)) {
return;
}
// base::PathExists() dereferences symlinks, so make sure that there's not a
// dangling symlink there before we create a new link.
brillo::DeleteFile(target);
// TODO(hidehiko): currently uid/gid are not set to the created symlink
// for historical reason. Consider to set them.
PCHECK(base::CreateSymbolicLink(source, target));
}
bool ChromeSetup::EnsureDirectoryExists(const base::FilePath& path,
uid_t uid,
gid_t gid,
mode_t mode) const {
if (!base::DirectoryExists(path)) {
// Remove the existing file or link if any.
if (!brillo::DeleteFile(path)) {
PLOG(ERROR) << "Unable to delete " << path.value();
return false;
}
if (!base::CreateDirectory(path)) {
PLOG(ERROR) << "Unable to create " << path.value();
return false;
}
}
base::ScopedFD fd(HANDLE_EINTR(
open(path.value().c_str(), O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC)));
if (!fd.is_valid()) {
PLOG(ERROR) << "Couldn't open " << path.value();
return false;
}
if (fchown(fd.get(), uid, gid) != 0) {
PLOG(ERROR) << "Couldn't chown " << path.value() << " to " << uid << ":"
<< gid;
return false;
}
if (fchmod(fd.get(), mode) != 0) {
PLOG(ERROR) << "Unable to chmod " << path.value() << " to " << std::oct
<< mode;
return false;
}
return true;
}
void SetUpSchedulerFlags(ChromiumCommandBuilder* builder,
brillo::CrosConfigInterface* cros_config) {
// A platform can override default scheduler boosting value.
std::string boost_urgent_str;
int boost_urgent;
if (cros_config &&
cros_config->GetString(kSchedulerTunePath, kBoostUrgentProperty,
&boost_urgent_str) &&
base::StringToInt(boost_urgent_str, &boost_urgent)) {
builder->AddArg(
base::StringPrintf("--scheduler-boost-urgent=%d", boost_urgent));
}
}
void AddSerializedAshSwitches(ChromiumCommandBuilder* builder,
brillo::CrosConfigInterface* cros_config) {
using std::string_literals::operator""s;
std::string serialized_ash_switches;
if (!cros_config->GetString(kUiPath, kSerializedAshSwitchesProperty,
&serialized_ash_switches)) {
return;
}
for (const auto& flag :
base::SplitString(serialized_ash_switches, "\0"s, base::KEEP_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)) {
if (base::StartsWith(flag,
"--enable-features=", base::CompareCase::SENSITIVE) ||
base::StartsWith(flag,
"--disable-features=", base::CompareCase::SENSITIVE)) {
std::vector<std::string> pieces = base::SplitString(
flag, "=,", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
CHECK_GE(pieces.size(), 2u);
const bool is_enable_features = pieces[0] == "--enable-features";
for (size_t i = 1; i < pieces.size(); i++) {
if (is_enable_features) {
builder->AddFeatureEnableOverride(pieces[i]);
} else {
builder->AddFeatureDisableOverride(pieces[i]);
}
}
} else {
builder->AddArg(flag);
}
}
}
void SetUpDelayOnActiveCameraClientChangeForNotificationFlag(
ChromiumCommandBuilder* builder, brillo::CrosConfigInterface* cros_config) {
if (!cros_config) {
return;
}
// It is sufficient to directly index the first device, as the workaround
// triggered by the switch that we are adding to the command line is
// compensating for an issue specific to the camera privacy switch behavior on
// Jinlon and Jinlon has only one camera.
const char* kCameraDevicePath = "/camera/devices/0";
std::string property_value;
if (cros_config->GetString(kCameraDevicePath, "privacy-switch-is-delayed",
&property_value) &&
(property_value == "true")) {
builder->AddArg("--delay_on_active_camera_client_change_for_notification");
}
}
void SetUpHelpContentSwitch(ChromiumCommandBuilder* builder,
brillo::CrosConfigInterface* cros_config) {
std::string help_content_id;
if (cros_config && cros_config->GetString(kUiPath, kHelpContentIdProperty,
&help_content_id)) {
builder->AddArg("--device-help-content-id=" + help_content_id);
}
}
void SetUpRegulatoryLabelFlag(ChromiumCommandBuilder* builder,
brillo::CrosConfigInterface* cros_config) {
std::string subdir;
if (cros_config &&
cros_config->GetString("/", kRegulatoryLabelProperty, &subdir)) {
builder->AddArg("--regulatory-label-dir=" + subdir);
}
}
void SetUpWallpaperFlags(
ChromiumCommandBuilder* builder,
brillo::CrosConfigInterface* cros_config,
const base::RepeatingCallback<bool(const base::FilePath&)>& path_exists) {
AddWallpaperFlags(builder, "guest", "guest", path_exists);
AddWallpaperFlags(builder, "child", "child", path_exists);
// Use the configuration if available.
std::string filename;
if (cros_config &&
cros_config->GetString("/", kWallpaperProperty, &filename) &&
AddWallpaperFlags(builder, "default", filename, path_exists)) {
// If there's a wallpaper defined in cros config, mark this as an OEM
// wallpaper.
builder->AddArg("--default-wallpaper-is-oem");
return;
}
// Fall back to oem.
if (AddWallpaperFlags(builder, "default", "oem", path_exists)) {
builder->AddArg("--default-wallpaper-is-oem");
return;
}
// Fall back to default.
AddWallpaperFlags(builder, "default", "default", path_exists);
}
void SetUpInternalStylusFlag(ChromiumCommandBuilder* builder,
brillo::CrosConfigInterface* cros_config) {
std::string stylus_category;
if (cros_config &&
cros_config->GetString(kHardwarePropertiesPath, kStylusCategoryField,
&stylus_category) &&
stylus_category == "internal") {
builder->AddArg("--has-internal-stylus");
}
}
void SetUpFingerprintSensorLocationFlag(
ChromiumCommandBuilder* builder, brillo::CrosConfigInterface* cros_config) {
std::string fingerprint_sensor_location;
if (!cros_config ||
!cros_config->GetString(kFingerprintPath, kFingerprintSensorLocationField,
&fingerprint_sensor_location)) {
return;
}
if (fingerprint_sensor_location != "none") {
builder->AddArg(base::StringPrintf("--fingerprint-sensor-location=%s",
fingerprint_sensor_location.c_str()));
}
}
void SetUpAutoDimFlag(ChromiumCommandBuilder* builder,
brillo::CrosConfigInterface* cros_config) {
std::string display_type;
if (cros_config &&
cros_config->GetString(kHardwarePropertiesPath, kDisplayCategoryField,
&display_type) &&
display_type == "old") {
builder->AddArg("--enable-dim-shelf");
}
}
void SetUpFormFactorFlag(ChromiumCommandBuilder* builder,
brillo::CrosConfigInterface* cros_config) {
std::string form_factor;
if (cros_config && cros_config->GetString(kHardwarePropertiesPath,
kFormFactorField, &form_factor)) {
builder->AddArg(
base::StringPrintf("--form-factor=%s", form_factor.c_str()));
}
}
void SetUpPowerButtonPositionFlag(ChromiumCommandBuilder* builder,
brillo::CrosConfigInterface* cros_config) {
std::string edge_as_string, position_as_string;
if (!cros_config ||
!cros_config->GetString(kPowerButtonPositionPath, kPowerButtonEdgeField,
&edge_as_string) ||
!cros_config->GetString(kPowerButtonPositionPath,
kPowerButtonPositionField, &position_as_string)) {
return;
}
double position_as_double = 0;
if (!base::StringToDouble(position_as_string, &position_as_double)) {
LOG(ERROR) << "Invalid value for power button position: "
<< position_as_string;
return;
}
base::Value::Dict position_info;
position_info.Set(kPowerButtonEdgeField, std::move(edge_as_string));
position_info.Set(kPowerButtonPositionField, position_as_double);
std::string json_position_info;
base::JSONWriter::Write(position_info, &json_position_info);
builder->AddArg(base::StringPrintf("--ash-power-button-position=%s",
json_position_info.c_str()));
}
void SetUpSideVolumeButtonPositionFlag(
ChromiumCommandBuilder* builder, brillo::CrosConfigInterface* cros_config) {
std::string region_as_string, side_as_string;
if (!cros_config ||
!cros_config->GetString(kSideVolumeButtonPath, kSideVolumeButtonRegion,
&region_as_string) ||
!cros_config->GetString(kSideVolumeButtonPath, kSideVolumeButtonSide,
&side_as_string)) {
return;
}
base::Value::Dict position_info;
position_info.Set(kSideVolumeButtonRegion, std::move(region_as_string));
position_info.Set(kSideVolumeButtonSide, std::move(side_as_string));
std::string json_position_info;
if (!base::JSONWriter::Write(position_info, &json_position_info)) {
LOG(ERROR) << "JSONWriter::Write failed in writing side volume button "
<< "position info.";
return;
}
builder->AddArg("--ash-side-volume-button-position=" + json_position_info);
}
void SetUpOzoneNNPalmPropertiesFlag(ChromiumCommandBuilder* builder,
brillo::CrosConfigInterface* cros_config) {
base::Value::Dict info;
if (cros_config) {
std::string value;
for (const char* property : kOzoneNNPalmOptionalProperties) {
if (cros_config->GetString(kOzoneNNPalmPropertiesPath, property,
&value)) {
info.Set(property, std::move(value));
continue;
}
}
}
std::string json_info;
if (!base::JSONWriter::Write(info, &json_info)) {
LOG(ERROR)
<< "JSONWriter::Write failed in writing Ozone NNPalm properties.";
return;
}
builder->AddArg("--ozone-nnpalm-properties=" + json_info);
}
// Enables the "AllowAmbientEQ" feature if "allow-ambient-eq" is set to "1"
// in cros_config.
void SetUpAllowAmbientEQFlag(ChromiumCommandBuilder* builder,
brillo::CrosConfigInterface* cros_config) {
std::string allow_ambient_eq_str;
if (!cros_config || !cros_config->GetString(kPowerPath, kAllowAmbientEQField,
&allow_ambient_eq_str)) {
return;
}
if (allow_ambient_eq_str != "1") {
return;
}
builder->AddFeatureEnableOverride("AllowAmbientEQ");
}
// Gets a powerd pref from |cros_config|, falling back on searching the
// file-based powerd preferences if not found. Powerd has a hierarchy of
// preferences it searches for a given key, so search both the core defaults
// set by boxster as well as file-based preferences customized by different
// overlays.
bool GetPowerdPref(const char* pref_name,
brillo::CrosConfigInterface* cros_config,
std::string* val_out) {
if (cros_config && cros_config->GetString(kPowerPath, pref_name, val_out)) {
return true;
}
std::string pref_name_underscores;
base::ReplaceChars(pref_name, "-", "_", &pref_name_underscores);
for (const char* pref_dir : kPowerdPrefPaths) {
base::FilePath dir_path = base::FilePath(pref_dir);
base::FilePath pref_path =
base::FilePath(dir_path.Append(pref_name_underscores));
if (base::PathExists(pref_path)) {
if (base::ReadFileToString(pref_path, val_out)) {
return true;
}
}
}
return false;
}
void SetUpInstantTetheringFlag(ChromiumCommandBuilder* builder,
brillo::CrosConfigInterface* cros_config) {
if (builder->UseFlagIsSet("disable_instant_tethering")) {
builder->AddFeatureDisableOverride("InstantTethering");
return;
}
std::string disable_instant_tethering_str;
if (!cros_config || !cros_config->GetString(kInstantTetheringPath,
kDisableInstantTetheringProperty,
&disable_instant_tethering_str)) {
return;
}
if (disable_instant_tethering_str == "true") {
builder->AddFeatureDisableOverride("InstantTethering");
}
}
// Adds ARC related flags.
void AddArcFlags(ChromiumCommandBuilder* builder,
std::set<std::string>* disallowed_params_out,
brillo::CrosConfigInterface* cros_config) {
if (builder->UseFlagIsSet("arc") ||
(builder->UseFlagIsSet("cheets") && builder->is_test_build())) {
builder->AddArg("--arc-availability=officially-supported");
} else if (builder->UseFlagIsSet("cheets")) {
builder->AddArg("--arc-availability=installed");
} else {
// Don't pass ARC availability related flags in chrome_dev.conf to Chrome if
// ARC is not installed at all.
disallowed_params_out->insert("--arc-availability");
disallowed_params_out->insert("--enable-arc");
disallowed_params_out->insert("--arc-available");
disallowed_params_out->insert("-arc-availability");
disallowed_params_out->insert("-enable-arc");
disallowed_params_out->insert("-arc-available");
}
if (builder->UseFlagIsSet("arc_adb_sideloading")) {
builder->AddFeatureEnableOverride("ArcAdbSideloading");
}
if (builder->UseFlagIsSet("arc_container_app_killer")) {
builder->AddFeatureEnableOverride("ContainerAppKiller");
}
if (builder->UseFlagIsSet("arc_transition_m_to_n")) {
builder->AddArg("--arc-transition-migration-required");
}
if (builder->UseFlagIsSet("arc_force_2x_scaling")) {
builder->AddArg("--force-remote-shell-scale=2");
}
if (builder->UseFlagIsSet("arcvm") && !builder->UseFlagIsSet("arcpp")) {
builder->AddArg("--enable-arcvm");
}
if (builder->UseFlagIsSet("arcvm_dlc")) {
builder->AddArg("--enable-arcvm-dlc");
}
if (builder->UseFlagIsSet("arcvm_data_migration")) {
builder->AddFeatureEnableOverride("ArcVmDataMigration");
}
if (builder->UseFlagIsSet("arcvm_virtio_blk_data")) {
builder->AddFeatureEnableOverride("ArcEnableVirtioBlkForData");
}
if (builder->UseFlagIsSet("arcvm_block_io_scheduler")) {
builder->AddFeatureEnableOverride("ArcBlockIoScheduler");
}
if (builder->UseFlagIsSet("arcvm_virtio_blk_multiple_workers")) {
builder->AddFeatureEnableOverride("ArcEnableVirtioBlkMultipleWorkers");
}
if (builder->UseFlagIsSet("lvm_application_containers")) {
builder->AddFeatureEnableOverride("ArcLvmApplicationContainers");
}
// Devices of tablet form factor will have special app behaviour.
if (builder->UseFlagIsSet("tablet_form_factor")) {
builder->AddArg("--enable-tablet-form-factor");
}
if (builder->UseFlagIsSet("arc_erofs")) {
builder->AddArg("--arc-erofs");
}
if (builder->UseFlagIsSet("arcvm_gki")) {
builder->AddFeatureEnableOverride("ArcVmGki");
// Disable VMMMS as it's not supported on GKI (b/333650576 for context).
builder->AddFeatureDisableOverride("CrOSLateBootVmMemoryManagementService");
}
std::string arc_scale;
if (cros_config &&
cros_config->GetString(kArcScalePath, kArcScaleProperty, &arc_scale)) {
builder->AddArg("--arc-scale=" + arc_scale);
}
// Pass USE flag of blocking KeyMint to Chrome.
// TODO(b/302648164): Remove the block_keymint flag on selected boards
// once the migration issue of older keyblobs is resolved.
if (builder->UseFlagIsSet("block_keymint")) {
LOG(INFO) << "KeyMint is temporarily blocked on this board.";
builder->AddArg("--arc-block-keymint");
}
// Pass USE flag of enabling ARC Key and ID Attestation to Chrome.
if (builder->UseFlagIsSet("arc_enable_attestation")) {
builder->AddArg("--arc-enable-attestation");
}
// Pass USE flags of ARM binary translation libraries to Chrome.
if (builder->UseFlagIsSet("houdini")) {
builder->AddArg("--enable-houdini");
}
if (builder->UseFlagIsSet("houdini64")) {
builder->AddArg("--enable-houdini64");
}
if (builder->UseFlagIsSet("houdini_dlc")) {
builder->AddArg("--enable-houdini-dlc");
}
if (builder->UseFlagIsSet("ndk_translation")) {
builder->AddArg("--enable-ndk-translation");
}
if (builder->UseFlagIsSet("ndk_translation64")) {
builder->AddArg("--enable-ndk-translation64");
}
}
// Adds flags related to machine learning features that are enabled only on a
// supported subset of devices.
void AddMlFlags(ChromiumCommandBuilder* builder,
brillo::CrosConfigInterface* cros_config) {
if (builder->UseFlagIsSet("ml_service")) {
builder->AddArg("--ml_service=enabled");
}
if (builder->UseFlagIsSet("smartdim")) {
builder->AddFeatureEnableOverride("SmartDim");
}
if (builder->UseFlagIsSet("enable_neural_palm_detection_filter")) {
builder->AddFeatureEnableOverride("EnableNeuralPalmDetectionFilter");
}
if (builder->UseFlagIsSet("enable_heuristic_palm_detection_filter")) {
builder->AddFeatureEnableOverride("EnableHeuristicPalmDetectionFilter");
}
if (!builder->UseFlagIsSet("ondevice_grammar")) {
builder->AddFeatureDisableOverride("OnDeviceGrammarCheck");
}
if (builder->UseFlagIsSet("ondevice_handwriting")) {
builder->AddArg("--ondevice_handwriting=use_rootfs");
} else if (builder->UseFlagIsSet("ondevice_handwriting_dlc")) {
builder->AddArg("--ondevice_handwriting=use_dlc");
}
if (builder->UseFlagIsSet("ondevice_speech")) {
// libsoda is supported on devices with 4GB+ of physical RAM. base::SysInfo
// reports total RAM minus some reserved stuff e.g. the kernel, so in
// practice, we compare against 3GiB not 4GiB.
// Theoretically: this will match devices with RAM > (3GiB + something).
// In practice: all such devices have 4GB+.
constexpr int kSodaLibraryMinRamMB = 3072;
if (base::SysInfo::AmountOfPhysicalMemoryMB() >= kSodaLibraryMinRamMB) {
builder->AddFeatureEnableOverride("OnDeviceSpeechRecognition");
}
}
if (builder->UseFlagIsSet("ondevice_document_scanner")) {
builder->AddArg("--ondevice_document_scanner=use_rootfs");
} else if (builder->UseFlagIsSet("ondevice_document_scanner_dlc")) {
builder->AddArg("--ondevice_document_scanner=use_dlc");
}
if (!builder->UseFlagIsSet("federated_service")) {
builder->AddFeatureDisableOverride("FederatedService");
}
if (builder->UseFlagIsSet("camera_feature_effects")) {
builder->AddArg("--camera-effects-supported-by-hardware");
builder->AddFeatureEnableOverride("CameraEffectsSupportedByHardware");
}
if (builder->UseFlagIsSet("camera_feature_super_res")) {
builder->AddFeatureEnableOverride("CameraSuperResSupported");
}
if (builder->UseFlagIsSet("ondevice_image_content_annotation")) {
builder->AddFeatureEnableOverride("ICASupportedByHardware");
}
SetUpHandwritingRecognitionWebPlatformApiFlag(builder, cros_config);
SetUpHasHpsFlag(builder, cros_config);
}
// Adds flags related to feature management that must be enabled for this
// device.
void AddFeatureManagementFlags(
ChromiumCommandBuilder* builder,
segmentation::FeatureManagement* feature_management) {
std::set<std::string> features =
feature_management->ListFeatures(segmentation::USAGE_CHROME);
for (auto feature : features) {
builder->AddFeatureEnableOverride(feature);
}
builder->AddArg(base::StringPrintf("--feature-management-level=%d",
feature_management->GetFeatureLevel()));
builder->AddArg(base::StringPrintf("--feature-management-max-level=%d",
feature_management->GetMaxFeatureLevel()));
builder->AddArg(base::StringPrintf("--feature-management-scope=%d",
feature_management->GetScopeLevel()));
}
void AddDeviceSpecificFlags(ChromiumCommandBuilder* builder) {
// Ferrochrome (go/ferrochrome) specific custiomization
if (builder->UseFlagIsSet("ferrochrome")) {
// Disable Cross-Device features, e.g. Nearby Share, Smart Lock, Fast Pair,
// etc.
builder->AddFeatureDisableOverride("AllowCrossDeviceFeatureSuite");
}
}
void AddMantisFlags(ChromiumCommandBuilder* builder) {
if (builder->UseFlagIsSet("mantis")) {
builder->AddFeatureEnableOverride("MediaAppImageMantis");
// The CrosSafetyService is required for the MantisService
builder->AddFeatureEnableOverride("CrosSafetyService");
}
}
void AddXSFlags(ChromiumCommandBuilder* builder) {
if (builder->UseFlagIsSet("odml_xs_base_model")) {
builder->AddFeatureEnableOverride("ConchLargeModel");
}
}
void SetUpDebugfsGpu() {
// Location where GPU debug information is bind-mounted.
static const char kDebugfsGpuPath[] = "/run/debugfs_gpu";
const base::FilePath debugfs_gpu_path(kDebugfsGpuPath);
if (base::DirectoryExists(debugfs_gpu_path)) {
return;
}
if (!base::CreateDirectory(debugfs_gpu_path)) {
PLOG(ERROR) << "Unable to create " << kDebugfsGpuPath;
return;
}
if (mount("/sys/kernel/debug/dri/0", kDebugfsGpuPath, "", MS_BIND, nullptr) <
0) {
PLOG(ERROR) << "Unable to mount";
return;
}
}
} // namespace login_manager