blob: 5dea6d3d7edd5e78bd20544a755aa263503c40cf [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/components/arc/session/arc_vm_client_adapter.h"
#include <inttypes.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <time.h>
#include <unistd.h>
#include <algorithm>
#include <deque>
#include <limits>
#include <set>
#include <utility>
#include <vector>
#include "ash/components/arc/arc_features.h"
#include "ash/components/arc/arc_util.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_dlc_installer.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "ash/components/arc/session/arc_session.h"
#include "ash/components/arc/session/connection_holder.h"
#include "ash/components/arc/session/file_system_status.h"
#include "ash/components/cryptohome/cryptohome_parameters.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/command_line.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/format_macros.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/launch.h"
#include "base/process/process_metrics.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/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/platform_thread.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "chromeos/ash/components/dbus/concierge/concierge_client.h"
#include "chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.h"
#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
#include "chromeos/components/sensors/buildflags.h"
#include "chromeos/dbus/common/dbus_method_call_status.h"
#include "chromeos/system/core_scheduling.h"
#include "chromeos/system/statistics_provider.h"
#include "components/version_info/version_info.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
namespace arc {
namespace {
// The "_2d" in job names below corresponds to "-". Upstart escapes characters
// that aren't valid in D-Bus object paths with underscore followed by its
// ascii code in hex. So "arc_2dcreate_2ddata" becomes "arc-create-data".
constexpr const char kArcVmPerBoardFeaturesJobName[] =
"arcvm_2dper_2dboard_2dfeatures";
constexpr char kArcVmPreLoginServicesJobName[] =
"arcvm_2dpre_2dlogin_2dservices";
constexpr char kArcVmPostLoginServicesJobName[] =
"arcvm_2dpost_2dlogin_2dservices";
constexpr char kArcVmPostVmStartServicesJobName[] =
"arcvm_2dpost_2dvm_2dstart_2dservices";
constexpr const char kArcVmBootNotificationServerSocketPath[] =
"/run/arcvm_boot_notification_server/host.socket";
constexpr base::TimeDelta kArcBugReportBackupTimeMetricMinTime =
base::Milliseconds(1);
constexpr base::TimeDelta kArcBugReportBackupTimeMetricMaxTime =
base::Seconds(60);
constexpr int kArcBugReportBackupTimeMetricBuckets = 50;
constexpr const char kArcBugReportBackupTimeMetric[] =
"Login.ArcBugReportBackupTime";
constexpr int kLogdConfigSizeSmall = 256; // kBytes
constexpr int kLogdConfigSizeMed = 512; // kBytes
constexpr int kLogdConfigSizeLarge = 1024; // kBytes
constexpr int64_t kInvalidCid = -1;
constexpr base::TimeDelta kConnectTimeoutLimit = base::Seconds(20);
constexpr base::TimeDelta kConnectSleepDurationInitial =
base::Milliseconds(100);
constexpr const char kEmptyDiskPath[] = "/dev/null";
absl::optional<base::TimeDelta> g_connect_timeout_limit_for_testing;
absl::optional<base::TimeDelta> g_connect_sleep_duration_initial_for_testing;
absl::optional<int> g_boot_notification_server_fd;
ash::ConciergeClient* GetConciergeClient() {
return ash::ConciergeClient::Get();
}
ash::DebugDaemonClient* GetDebugDaemonClient() {
return ash::DebugDaemonClient::Get();
}
ArcBinaryTranslationType IdentifyBinaryTranslationType(
const StartParams& start_params) {
const auto* command_line = base::CommandLine::ForCurrentProcess();
const bool is_houdini_available =
command_line->HasSwitch(ash::switches::kEnableHoudini) ||
command_line->HasSwitch(ash::switches::kEnableHoudini64);
const bool is_ndk_translation_available =
command_line->HasSwitch(ash::switches::kEnableNdkTranslation) ||
command_line->HasSwitch(ash::switches::kEnableNdkTranslation64);
if (!is_houdini_available && !is_ndk_translation_available)
return ArcBinaryTranslationType::NONE;
const bool prefer_ndk_translation =
!is_houdini_available || start_params.native_bridge_experiment;
if (is_ndk_translation_available && prefer_ndk_translation)
return ArcBinaryTranslationType::NDK_TRANSLATION;
return ArcBinaryTranslationType::HOUDINI;
}
std::vector<std::string> GenerateUpgradeProps(
const UpgradeParams& upgrade_params,
const std::string& serial_number,
const std::string& prefix) {
std::vector<std::string> result = {
base::StringPrintf("%s.disable_boot_completed=%d", prefix.c_str(),
upgrade_params.skip_boot_completed_broadcast),
base::StringPrintf("%s.enable_adb_sideloading=%d", prefix.c_str(),
upgrade_params.is_adb_sideloading_enabled),
base::StringPrintf("%s.copy_packages_cache=%d", prefix.c_str(),
static_cast<int>(upgrade_params.packages_cache_mode)),
base::StringPrintf("%s.skip_gms_core_cache=%d", prefix.c_str(),
upgrade_params.skip_gms_core_cache),
base::StringPrintf("%s.arc_demo_mode=%d", prefix.c_str(),
upgrade_params.is_demo_session),
base::StringPrintf("%s.enable_arc_nearby_share=%d", prefix.c_str(),
upgrade_params.enable_arc_nearby_share),
base::StringPrintf(
"%s.supervision.transition=%d", prefix.c_str(),
static_cast<int>(upgrade_params.management_transition)),
base::StringPrintf("%s.serialno=%s", prefix.c_str(),
serial_number.c_str()),
base::StringPrintf("%s.skip_tts_cache=%d", prefix.c_str(),
upgrade_params.skip_tts_cache),
};
// Conditionally sets more properties based on |upgrade_params|.
if (!upgrade_params.locale.empty()) {
result.push_back(base::StringPrintf("%s.locale=%s", prefix.c_str(),
upgrade_params.locale.c_str()));
if (!upgrade_params.preferred_languages.empty()) {
result.push_back(base::StringPrintf(
"%s.preferred_languages=%s", prefix.c_str(),
base::JoinString(upgrade_params.preferred_languages, ",").c_str()));
}
}
// TODO(lgcheng): Handle |is_account_managed| and
// |is_managed_adb_sideloading_allowed| in |upgrade_params| when we
// implement apk sideloading for ARCVM.
return result;
}
std::vector<std::string> GenerateKernelCmdline(
const StartParams& start_params,
const FileSystemStatus& file_system_status,
bool is_host_on_vm) {
std::string native_bridge;
switch (IdentifyBinaryTranslationType(start_params)) {
case ArcBinaryTranslationType::NONE:
native_bridge = "0";
break;
case ArcBinaryTranslationType::HOUDINI:
native_bridge = "libhoudini.so";
break;
case ArcBinaryTranslationType::NDK_TRANSLATION:
native_bridge = "libndk_translation.so";
break;
}
const int guest_zram_size = kGuestZramSize.Get();
VLOG(1) << "Setting ARCVM guest's zram size to " << guest_zram_size;
std::vector<std::string> result = {
base::StringPrintf("androidboot.native_bridge=%s", native_bridge.c_str()),
base::StringPrintf("androidboot.host_is_in_vm=%d", is_host_on_vm),
base::StringPrintf("androidboot.lcd_density=%d",
start_params.lcd_density),
base::StringPrintf("androidboot.arc_file_picker=%d",
start_params.arc_file_picker_experiment),
base::StringPrintf("androidboot.arc_custom_tabs=%d",
start_params.arc_custom_tabs_experiment),
base::StringPrintf(
"androidboot.keyboard_shortcut_helper_integration=%d",
start_params.enable_keyboard_shortcut_helper_integration),
base::StringPrintf("androidboot.enable_notifications_refresh=%d",
start_params.enable_notifications_refresh),
base::StringPrintf("androidboot.zram_size=%d", guest_zram_size),
};
const ArcVmUreadaheadMode mode =
GetArcVmUreadaheadMode(base::BindRepeating(&base::GetSystemMemoryInfo));
switch (mode) {
case ArcVmUreadaheadMode::READAHEAD:
result.push_back("androidboot.arcvm_ureadahead_mode=readahead");
break;
case ArcVmUreadaheadMode::GENERATE:
result.push_back("androidboot.arcvm_ureadahead_mode=generate");
break;
case ArcVmUreadaheadMode::DISABLED:
break;
}
// Only add boot property if flag to disable media store maintenance is set.
if (start_params.disable_media_store_maintenance)
result.push_back("androidboot.disable_media_store_maintenance=1");
if (start_params.arc_generate_play_auto_install)
result.push_back("androidboot.arc_generate_pai=1");
if (base::FeatureList::IsEnabled(kEnableVirtioBlkForData))
result.push_back("androidboot.arcvm_virtio_blk_data=1");
else
result.push_back("androidboot.arcvm_virtio_blk_data=0");
// Conditionally sets some properties based on |start_params|.
switch (start_params.play_store_auto_update) {
case StartParams::PlayStoreAutoUpdate::AUTO_UPDATE_DEFAULT:
break;
case StartParams::PlayStoreAutoUpdate::AUTO_UPDATE_ON:
result.push_back("androidboot.play_store_auto_update=1");
break;
case StartParams::PlayStoreAutoUpdate::AUTO_UPDATE_OFF:
result.push_back("androidboot.play_store_auto_update=0");
break;
}
// Set logcat size, only if configured to one of the few supported sizes.
if (base::FeatureList::IsEnabled(kLogdConfig)) {
auto logd_config_size = kLogdConfigSize.Get();
switch (logd_config_size) {
case kLogdConfigSizeSmall:
result.push_back("androidboot.arcvm.logd.size=256K");
break;
case kLogdConfigSizeMed:
result.push_back("androidboot.arcvm.logd.size=512K");
break;
case kLogdConfigSizeLarge:
result.push_back("androidboot.arcvm.logd.size=1M");
break;
default:
VLOG(1) << "WARNING: Invalid logd size ignored: [" << logd_config_size
<< "]";
break;
}
}
if (base::FeatureList::IsEnabled(kVmMemoryPSIReports)) {
auto period = kVmMemoryPSIReportsPeriod.Get();
// Since Android performs parameter validation, not doing it here.
result.push_back(base::StringPrintf(
"androidboot.arcvm_metrics_mem_psi_period=%d", period));
}
if (base::FeatureList::IsEnabled(arc::kUseDalvikMemoryProfile)) {
switch (start_params.dalvik_memory_profile) {
case StartParams::DalvikMemoryProfile::DEFAULT:
case StartParams::DalvikMemoryProfile::M4G:
// Use the 4G profile for devices with 4GB RAM or less.
result.push_back("androidboot.arc_dalvik_memory_profile=4G");
break;
case StartParams::DalvikMemoryProfile::M8G:
result.push_back("androidboot.arc_dalvik_memory_profile=8G");
break;
case StartParams::DalvikMemoryProfile::M16G:
result.push_back("androidboot.arc_dalvik_memory_profile=16G");
break;
}
} else {
VLOG(1) << "Dalvik memory profile is not enabled, the default setting is "
<< "used.";
}
std::string log_profile_name;
switch (start_params.usap_profile) {
case StartParams::UsapProfile::DEFAULT:
log_profile_name = "default low-memory";
break;
case StartParams::UsapProfile::M4G:
result.push_back("androidboot.usap_profile=4G");
log_profile_name = "high-memory 4G";
break;
case StartParams::UsapProfile::M8G:
result.push_back("androidboot.usap_profile=8G");
log_profile_name = "high-memory 8G";
break;
case StartParams::UsapProfile::M16G:
result.push_back("androidboot.usap_profile=16G");
log_profile_name = "high-memory 16G";
break;
}
VLOG(1) << "Applied " << log_profile_name << " USAP profile";
if (start_params.disable_download_provider)
result.push_back("androidboot.disable_download_provider=1");
if (base::FeatureList::IsEnabled(arc::kVmGmsCoreLowMemoryKillerProtection))
result.push_back("androidboot.arc_enable_gmscore_lmk_protection=1");
if (start_params.enable_tts_caching)
result.push_back("androidboot.arc.tts.caching=1");
if (base::FeatureList::IsEnabled(arc::kVmBroadcastPreNotifyANR))
result.push_back("androidboot.arc.broadcast_anr_prenotify=1");
return result;
}
vm_tools::concierge::StartArcVmRequest CreateStartArcVmRequest(
const std::string& user_id_hash,
uint32_t cpus,
const base::FilePath& demo_session_apps_path,
const absl::optional<base::FilePath>& data_disk_path,
const FileSystemStatus& file_system_status,
bool use_per_vm_core_scheduling,
std::vector<std::string> kernel_cmdline,
ArcVmClientAdapterDelegate* delegate) {
vm_tools::concierge::StartArcVmRequest request;
request.set_name(kArcVmName);
request.set_owner_id(user_id_hash);
request.set_use_per_vm_core_scheduling(use_per_vm_core_scheduling);
if (file_system_status.is_host_rootfs_writable() &&
file_system_status.is_system_image_ext_format()) {
request.add_params("rw");
}
for (auto& entry : kernel_cmdline)
request.add_params(std::move(entry));
const bool should_set_blocksize =
!base::FeatureList::IsEnabled(arc::kUseDefaultBlockSize);
constexpr uint32_t kBlockSize = 4096;
// Add rootfs as /dev/vda.
request.set_rootfs_writable(file_system_status.is_host_rootfs_writable() &&
file_system_status.is_system_image_ext_format());
if (should_set_blocksize)
request.set_rootfs_block_size(kBlockSize);
// Add /vendor as /dev/block/vdb. The device name has to be consistent with
// the one in GenerateFirstStageFstab() in platform2/arc/setup/.
vm_tools::concierge::DiskImage* disk_image = request.add_disks();
disk_image->set_path(file_system_status.vendor_image_path().value());
disk_image->set_image_type(vm_tools::concierge::DISK_IMAGE_AUTO);
disk_image->set_writable(false);
disk_image->set_do_mount(true);
if (should_set_blocksize)
disk_image->set_block_size(kBlockSize);
// Add /run/imageloader/.../android_demo_apps.squash as /dev/block/vdc if
// needed. If it's not needed we pass /dev/null so that /dev/block/vdc
// always corresponds to the demo image.
disk_image = request.add_disks();
disk_image->set_image_type(vm_tools::concierge::DISK_IMAGE_AUTO);
disk_image->set_writable(false);
disk_image->set_do_mount(true);
if (!demo_session_apps_path.empty()) {
disk_image->set_path(demo_session_apps_path.value());
if (should_set_blocksize)
disk_image->set_block_size(kBlockSize);
} else {
// This should never be mounted as it's only mounted if
// ro.boot.arc_demo_mode is set.
disk_image->set_path(kEmptyDiskPath);
}
// Add /opt/google/vms/android/apex/payload.img as /dev/block/vdd if
// needed. If it's not needed we pass /dev/null so that /dev/block/vdd
// always corresponds to the block apex composite disk.
disk_image = request.add_disks();
disk_image->set_image_type(vm_tools::concierge::DISK_IMAGE_AUTO);
disk_image->set_writable(false);
disk_image->set_do_mount(true);
if (!file_system_status.block_apex_path().empty()) {
disk_image->set_path(file_system_status.block_apex_path().value());
} else {
// Android will not mount this is the system property
// apexd.payload_metadata.path is not set, and it should
// always be set if the block apex payload exists.
disk_image->set_path(kEmptyDiskPath);
}
// Add |data_disk_path| path as /dev/block/vde for mounting Android /data.
disk_image = request.add_disks();
disk_image->set_image_type(vm_tools::concierge::DISK_IMAGE_AUTO);
disk_image->set_do_mount(true);
if (data_disk_path) {
disk_image->set_path(data_disk_path->value());
disk_image->set_writable(true);
if (should_set_blocksize)
disk_image->set_block_size(kBlockSize);
} else {
// This should never be mounted as it's only mounted if
// ro.boot.arcvm_virtio_blk_data=1 is set.
disk_image->set_path(kEmptyDiskPath);
// Ensure to set writable to false, otherwise crosvm will exit with
// "failed to lock disk image" error.
disk_image->set_writable(false);
}
// Add cpus.
request.set_cpus(cpus);
// Add ignore_dev_conf setting for dev mode.
request.set_ignore_dev_conf(IsArcVmDevConfIgnored());
// Add enable_rt_vcpu.
request.set_enable_rt_vcpu(IsArcVmRtVcpuEnabled(cpus));
// Add hugepages.
request.set_use_hugepages(IsArcVmUseHugePages());
// Request guest memory locking, if configured.
request.set_lock_guest_memory(base::FeatureList::IsEnabled(kLockGuestMemory));
// Specify VM Memory.
if (base::FeatureList::IsEnabled(kVmMemorySize)) {
base::SystemMemoryInfoKB info;
if (delegate->GetSystemMemoryInfo(&info)) {
const int ram_mib = info.total / 1024;
const int shift_mib = kVmMemorySizeShiftMiB.Get();
const int max_mib = kVmMemorySizeMaxMiB.Get();
int vm_ram_mib = std::min(max_mib, ram_mib + shift_mib);
constexpr int kVmRamMinMib = 2048;
if (delegate->IsCrosvm32bit()) {
// This is a workaround for ARM Chromebooks where userland including
// crosvm is compiled in 32 bit.
// TODO(yusukes): Remove this once crosvm becomes 64 bit binary on ARM.
if (vm_ram_mib > static_cast<int>(k32bitVmRamMaxMib)) {
VLOG(1) << "VmMemorySize is enabled, but we are on a 32-bit device, "
<< "so limit the size to " << k32bitVmRamMaxMib << " MiB.";
vm_ram_mib = k32bitVmRamMaxMib;
}
}
if (vm_ram_mib > kVmRamMinMib) {
request.set_memory_mib(vm_ram_mib);
VLOG(1) << "VmMemorySize is enabled. memory_mib=" << vm_ram_mib;
} else {
VLOG(1) << "VmMemorySize is enabled, but computed size is "
<< "min(" << ram_mib << " + " << shift_mib << "," << max_mib
<< ") == " << vm_ram_mib << "MiB, less than " << kVmRamMinMib
<< " MiB safe minium.";
}
} else {
VLOG(1) << "VmMemorySize is enabled, but GetSystemMemoryInfo failed.";
}
} else {
VLOG(1) << "VmMemorySize is disabled.";
}
// Specify balloon policy.
if (base::FeatureList::IsEnabled(kVmBalloonPolicy)) {
vm_tools::concierge::BalloonPolicyOptions* balloon_policy =
request.mutable_balloon_policy();
const int64_t moderate_kib = kVmBalloonPolicyModerateKiB.Get();
const int64_t critical_kib = kVmBalloonPolicyCriticalKiB.Get();
const int64_t reclaim_kib = kVmBalloonPolicyReclaimKiB.Get();
balloon_policy->set_moderate_target_cache(moderate_kib * 1024);
balloon_policy->set_critical_target_cache(critical_kib * 1024);
balloon_policy->set_reclaim_target_cache(reclaim_kib * 1024);
balloon_policy->set_responsive(kVmBalloonPolicyResponsive.Get());
balloon_policy->set_responsive_timeout_ms(
kVmBalloonPolicyResponsiveTimeoutMs.Get());
balloon_policy->set_responsive_max_deflate_bytes(
kVmBalloonPolicyResponsiveMaxDeflateBytes.Get());
VLOG(1) << "Use LimitCacheBalloonPolicy. ModerateKiB=" << moderate_kib
<< ", CriticalKiB=" << critical_kib
<< ", ReclaimKiB=" << reclaim_kib;
} else {
VLOG(1) << "Use BalanceAvailableBalloonPolicy";
}
if (base::FeatureList::IsEnabled(kGuestZram))
request.set_guest_swappiness(kGuestZramSwappiness.Get());
request.set_enable_consumer_auto_update_toggle(base::FeatureList::IsEnabled(
ash::features::kConsumerAutoUpdateToggleAllowed));
auto orientation = display::Display::ROTATE_0;
if (auto* screen = display::Screen::GetScreen())
orientation = screen->GetPrimaryDisplay().panel_rotation();
switch (orientation) {
using StartArcVmRequest = vm_tools::concierge::StartArcVmRequest;
case display::Display::ROTATE_0:
request.set_panel_orientation(StartArcVmRequest::ORIENTATION_0);
break;
case display::Display::ROTATE_90:
request.set_panel_orientation(StartArcVmRequest::ORIENTATION_90);
break;
case display::Display::ROTATE_180:
request.set_panel_orientation(StartArcVmRequest::ORIENTATION_180);
break;
case display::Display::ROTATE_270:
request.set_panel_orientation(StartArcVmRequest::ORIENTATION_270);
break;
}
return request;
}
const sockaddr_un* GetArcVmBootNotificationServerAddress() {
static struct sockaddr_un address {
.sun_family = AF_UNIX,
.sun_path = "/run/arcvm_boot_notification_server/host.socket"
};
return &address;
}
// Connects to UDS socket at |kArcVmBootNotificationServerSocketPath|.
// Returns the connected socket fd if successful, or else an invalid fd. This
// function can only be called with base::MayBlock().
base::ScopedFD ConnectToArcVmBootNotificationServer() {
if (g_boot_notification_server_fd)
return base::ScopedFD(HANDLE_EINTR(dup(*g_boot_notification_server_fd)));
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::WILL_BLOCK);
base::ScopedFD fd(socket(AF_UNIX, SOCK_STREAM, 0));
DCHECK(fd.is_valid());
if (HANDLE_EINTR(connect(fd.get(),
reinterpret_cast<const sockaddr*>(
GetArcVmBootNotificationServerAddress()),
sizeof(sockaddr_un)))) {
PLOG(ERROR) << "Unable to connect to "
<< kArcVmBootNotificationServerSocketPath;
return {};
}
return fd;
}
// Connects to arcvm-boot-notification-server to verify that it is listening.
// When this function is called, the server has just started and may not be
// listening on the socket yet, so this function will retry connecting for up
// to 20s, with exponential backoff. This function can only be called with
// base::MayBlock().
bool IsArcVmBootNotificationServerListening() {
const base::ElapsedTimer timer;
const base::TimeDelta limit = g_connect_timeout_limit_for_testing
? *g_connect_timeout_limit_for_testing
: kConnectTimeoutLimit;
base::TimeDelta sleep_duration =
g_connect_sleep_duration_initial_for_testing
? *g_connect_sleep_duration_initial_for_testing
: kConnectSleepDurationInitial;
do {
if (ConnectToArcVmBootNotificationServer().is_valid())
return true;
LOG(ERROR) << "Retrying to connect to boot notification server in "
<< sleep_duration;
base::PlatformThread::Sleep(sleep_duration);
sleep_duration *= 2;
} while (timer.Elapsed() < limit);
return false;
}
// Sends upgrade props to arcvm-boot-notification-server over socket at
// |kArcVmBootNotificationServerSocketPath|. This function can only be called
// with base::MayBlock().
bool SendUpgradePropsToArcVmBootNotificationServer(
int64_t cid,
const UpgradeParams& params,
const std::string& serial_number) {
std::string props = base::StringPrintf(
"CID=%" PRId64 "\n%s", cid,
base::JoinString(GenerateUpgradeProps(params, serial_number, "ro.boot"),
"\n")
.c_str());
base::ScopedFD fd = ConnectToArcVmBootNotificationServer();
if (!fd.is_valid())
return false;
if (!base::WriteFileDescriptor(fd.get(), props)) {
PLOG(ERROR) << "Unable to write props to "
<< kArcVmBootNotificationServerSocketPath;
return false;
}
return true;
}
} // namespace
bool ArcVmClientAdapterDelegate::GetSystemMemoryInfo(
base::SystemMemoryInfoKB* info) {
// Call the base function by default.
return base::GetSystemMemoryInfo(info);
}
bool ArcVmClientAdapterDelegate::IsCrosvm32bit() {
// Assume that crosvm is 32-bit if chrome is 32-bit.
return sizeof(uintptr_t) == 4;
}
class ArcVmClientAdapter : public ArcClientAdapter,
public ash::ConciergeClient::VmObserver,
public ash::ConciergeClient::Observer,
public ConnectionObserver<arc::mojom::AppInstance> {
public:
// Initializing |is_host_on_vm_| is not always very fast.
// Try to initialize them in the constructor and in StartMiniArc respectively.
// They usually run when the system is not busy.
ArcVmClientAdapter() : ArcVmClientAdapter(FileSystemStatusRewriter{}) {}
// For testing purposes and the internal use (by the other ctor) only.
explicit ArcVmClientAdapter(const FileSystemStatusRewriter& rewriter)
: delegate_(std::make_unique<ArcVmClientAdapterDelegate>()),
is_host_on_vm_(chromeos::system::StatisticsProvider::GetInstance()
->IsRunningOnVm()),
file_system_status_rewriter_for_testing_(rewriter) {
auto* client = GetConciergeClient();
client->AddVmObserver(this);
client->AddObserver(this);
auto* arc_service_manager = arc::ArcServiceManager::Get();
DCHECK(arc_service_manager);
arc_service_manager->arc_bridge_service()->app()->AddObserver(this);
}
ArcVmClientAdapter(const ArcVmClientAdapter&) = delete;
ArcVmClientAdapter& operator=(const ArcVmClientAdapter&) = delete;
~ArcVmClientAdapter() override {
auto* arc_service_manager = arc::ArcServiceManager::Get();
if (arc_service_manager)
arc_service_manager->arc_bridge_service()->app()->RemoveObserver(this);
auto* client = GetConciergeClient();
client->RemoveObserver(this);
client->RemoveVmObserver(this);
}
// ash::ConciergeClient::VmObserver overrides:
void OnVmStarted(
const vm_tools::concierge::VmStartedSignal& signal) override {
if (signal.name() == kArcVmName)
VLOG(1) << "OnVmStarted: ARCVM cid=" << signal.vm_info().cid();
}
void OnVmStopped(
const vm_tools::concierge::VmStoppedSignal& signal) override {
if (signal.name() != kArcVmName)
return;
const int64_t cid = signal.cid();
if (cid != current_cid_) {
VLOG(1) << "Ignoring VmStopped signal: current CID=" << current_cid_
<< ", stopped CID=" << cid;
return;
}
VLOG(1) << "OnVmStopped: ARCVM cid=" << cid;
current_cid_ = kInvalidCid;
const bool is_system_shutdown =
signal.reason() == vm_tools::concierge::SERVICE_SHUTDOWN;
OnArcInstanceStopped(is_system_shutdown);
}
// ArcClientAdapter overrides:
void StartMiniArc(StartParams params,
chromeos::VoidDBusMethodCallback callback) override {
// This step is mandatory regardless of StartMiniArc is called or not
// from |ArcSessionManager|. It is also called after login for ARCVM.
if (user_id_hash_.empty()) {
LOG(ERROR) << "User ID hash is not set";
StopArcInstanceInternal();
std::move(callback).Run(false);
return;
}
start_params_ = std::move(params);
std::vector<std::string> enviroment;
if (start_params_.disable_ureadahead)
enviroment.emplace_back("DISABLE_UREADAHEAD=1");
std::deque<JobDesc> jobs{
// Note: the first Upstart job is a task, and the callback for the start
// request won't be called until the task finishes. When the callback is
// called with true, it is ensured that the per-board features files
// exist.
JobDesc{kArcVmPerBoardFeaturesJobName, UpstartOperation::JOB_START, {}},
JobDesc{
kArcVmMediaSharingServicesJobName, UpstartOperation::JOB_STOP, {}},
JobDesc{
kArcVmPostVmStartServicesJobName, UpstartOperation::JOB_STOP, {}},
JobDesc{kArcVmPostLoginServicesJobName, UpstartOperation::JOB_STOP, {}},
JobDesc{kArcVmPreLoginServicesJobName,
UpstartOperation::JOB_STOP_AND_START, std::move(enviroment)},
};
ConfigureUpstartJobs(
std::move(jobs),
base::BindOnce(
&ArcVmClientAdapter::OnConfigureUpstartJobsOnStartMiniArc,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void UpgradeArc(UpgradeParams params,
chromeos::VoidDBusMethodCallback callback) override {
DCHECK(!user_id_hash_.empty());
if (serial_number_.empty()) {
LOG(ERROR) << "Serial number is not set";
StopArcInstanceInternal();
std::move(callback).Run(false);
return;
}
VLOG(1) << "Checking adb sideload status";
ash::SessionManagerClient::Get()->QueryAdbSideload(base::BindOnce(
&ArcVmClientAdapter::OnQueryAdbSideload, weak_factory_.GetWeakPtr(),
std::move(params), std::move(callback)));
}
void StopArcInstance(bool on_shutdown, bool should_backup_log) override {
if (on_shutdown) {
// Do nothing when |on_shutdown| is true because either vm_concierge.conf
// job (in case of user session termination) or session_manager (in case
// of browser-initiated exit on e.g. chrome://flags or UI language change)
// will stop all VMs including ARCVM right after the browser exits.
VLOG(1)
<< "StopArcInstance is called during browser shutdown. Do nothing.";
return;
}
DCHECK_NE(current_cid_, kInvalidCid) << "ARCVM is not running.";
if (should_backup_log) {
GetDebugDaemonClient()->BackupArcBugReport(
cryptohome::CreateAccountIdentifierFromIdentification(cryptohome_id_),
base::BindOnce(&ArcVmClientAdapter::OnArcBugReportBackedUp,
weak_factory_.GetWeakPtr(), base::TimeTicks::Now()));
} else {
StopArcInstanceInternal();
}
}
void SetUserInfo(const cryptohome::Identification& cryptohome_id,
const std::string& hash,
const std::string& serial_number) override {
DCHECK(cryptohome_id_.id().empty());
DCHECK(user_id_hash_.empty());
DCHECK(serial_number_.empty());
if (cryptohome_id.id().empty())
LOG(WARNING) << "cryptohome_id is empty";
if (hash.empty())
LOG(WARNING) << "hash is empty";
if (serial_number.empty())
LOG(WARNING) << "serial_number is empty";
cryptohome_id_ = cryptohome_id;
user_id_hash_ = hash;
serial_number_ = serial_number;
}
void SetDemoModeDelegate(DemoModeDelegate* delegate) override {
demo_mode_delegate_ = delegate;
}
void TrimVmMemory(TrimVmMemoryCallback callback, int page_limit) override {
VLOG(2) << "Start trimming VM memory";
if (user_id_hash_.empty()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), /*success=*/false,
/*failure_reason=*/"user_id_hash_ is not set"));
return;
}
vm_tools::concierge::ReclaimVmMemoryRequest request;
request.set_name(kArcVmName);
request.set_owner_id(user_id_hash_);
request.set_page_limit(page_limit);
GetConciergeClient()->ReclaimVmMemory(
request,
base::BindOnce(&ArcVmClientAdapter::OnTrimVmMemory,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
// ash::ConciergeClient::Observer overrides:
void ConciergeServiceStopped() override {
VLOG(1) << "vm_concierge stopped";
// At this point, all crosvm processes are gone. Notify the observer of the
// event.
// NOTE: In a normal system shutdown OnVmStopped() is called before this.
// When vm_concierge crashes, this is called without OnVmStopped().
OnArcInstanceStopped(false /* is_system_shutdown */);
}
void ConciergeServiceStarted() override {}
// ConnectionObserver<arc::mojom::AppInstance> overrides:
void OnConnectionReady() override {
VLOG(2) << "Sending ArcVmCompleteBoot Request";
auto* arc_service_manager = arc::ArcServiceManager::Get();
DCHECK(arc_service_manager);
arc_service_manager->arc_bridge_service()->app()->RemoveObserver(this);
vm_tools::concierge::ArcVmCompleteBootRequest request;
DCHECK(!user_id_hash_.empty());
request.set_owner_id(user_id_hash_);
GetConciergeClient()->ArcVmCompleteBoot(
request,
base::BindOnce(&ArcVmClientAdapter::OnArcVmCompleteBootResponse));
}
void set_delegate_for_testing( // IN-TEST
std::unique_ptr<ArcVmClientAdapterDelegate> delegate) {
delegate_ = std::move(delegate);
}
private:
void OnArcBugReportBackedUp(base::TimeTicks arc_bug_report_backup_time,
bool result) {
if (result) {
base::TimeDelta elapsed_time =
base::TimeTicks::Now() - arc_bug_report_backup_time;
base::UmaHistogramCustomTimes(kArcBugReportBackupTimeMetric, elapsed_time,
kArcBugReportBackupTimeMetricMinTime,
kArcBugReportBackupTimeMetricMaxTime,
kArcBugReportBackupTimeMetricBuckets);
} else {
LOG(ERROR) << "Error contacting debugd to back up ARC bug report.";
}
StopArcInstanceInternal();
}
void StopArcInstanceInternal() {
VLOG(1) << "Stopping arcvm";
// This may be called before ARCVM has been upgraded and the proper VM id
// has been set. Since ConciergeClient::StopVm() returns successfully
// regardless of whether the VM exists, check to see which VM is actually
// running.
vm_tools::concierge::StopVmRequest request;
request.set_name(kArcVmName);
request.set_owner_id(user_id_hash_);
GetConciergeClient()->StopVm(
request, base::BindOnce(&ArcVmClientAdapter::OnStopVmReply,
weak_factory_.GetWeakPtr()));
}
void OnConfigureUpstartJobsOnStartMiniArc(
chromeos::VoidDBusMethodCallback callback,
bool result) {
if (!result) {
LOG(ERROR) << "ConfigureUpstartJobs (on starting mini ARCVM) failed";
std::move(callback).Run(false);
return;
}
VLOG(1) << "Waiting for Concierge to be available";
GetConciergeClient()->WaitForServiceToBeAvailable(
base::BindOnce(&ArcVmClientAdapter::OnConciergeAvailable,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void OnConciergeAvailable(chromeos::VoidDBusMethodCallback callback,
bool service_available) {
if (!service_available) {
LOG(ERROR) << "Failed to wait for Concierge to be available";
std::move(callback).Run(false);
return;
}
// Stop the existing VM if any (e.g. in case of a chrome crash).
VLOG(1) << "Stopping the existing VM if any.";
vm_tools::concierge::StopVmRequest request;
request.set_name(kArcVmName);
request.set_owner_id(user_id_hash_);
GetConciergeClient()->StopVm(
request,
base::BindOnce(&ArcVmClientAdapter::OnExistingVmStopped,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void OnExistingVmStopped(
chromeos::VoidDBusMethodCallback callback,
absl::optional<vm_tools::concierge::StopVmResponse> reply) {
// reply->success() returns true even when there was no VM running.
if (!reply.has_value() || !reply->success()) {
LOG(ERROR) << "StopVm failed: "
<< (reply.has_value() ? reply->failure_reason()
: "No D-Bus response.");
std::move(callback).Run(false);
return;
}
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&IsArcVmBootNotificationServerListening),
base::BindOnce(
&ArcVmClientAdapter::OnArcVmBootNotificationServerIsListening,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void OnArcVmBootNotificationServerIsListening(
chromeos::VoidDBusMethodCallback callback,
bool result) {
if (!result) {
LOG(ERROR) << "Failed to connect to arcvm-boot-notification-server";
std::move(callback).Run(false);
return;
}
VLOG(2) << "Checking file system status";
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&FileSystemStatus::GetFileSystemStatusBlocking),
base::BindOnce(&ArcVmClientAdapter::OnFileSystemStatus,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void OnFileSystemStatus(chromeos::VoidDBusMethodCallback callback,
FileSystemStatus file_system_status) {
VLOG(2) << "Got file system status";
if (file_system_status_rewriter_for_testing_)
file_system_status_rewriter_for_testing_.Run(&file_system_status);
VLOG(2) << "Wait for DLC installation if necessary";
// Waits for a stable state (kInstalled/kUninstalled) and proceeds
// regardless of installation result because even if the installation
// has failed, it will only affect limited functionality (e.g. without
// Houdini library for ARM apps). ARCVM should still continue to start.
ArcDlcInstaller::Get()->WaitForStableState(base::BindOnce(
&ArcVmClientAdapter::LoadDemoResources, weak_factory_.GetWeakPtr(),
std::move(callback), std::move(file_system_status)));
}
void LoadDemoResources(chromeos::VoidDBusMethodCallback callback,
FileSystemStatus file_system_status) {
VLOG(2) << "Retrieving demo session apps path";
DCHECK(demo_mode_delegate_);
demo_mode_delegate_->EnsureResourcesLoaded(base::BindOnce(
&ArcVmClientAdapter::OnDemoResourcesLoaded, weak_factory_.GetWeakPtr(),
std::move(callback), std::move(file_system_status)));
}
void OnDemoResourcesLoaded(chromeos::VoidDBusMethodCallback callback,
FileSystemStatus file_system_status) {
if (!base::FeatureList::IsEnabled(kEnableVirtioBlkForData)) {
VLOG(1) << "Using virtio-fs for /data";
StartArcVm(std::move(callback), std::move(file_system_status),
/*data_disk_path=*/absl::nullopt);
return;
}
if (kEnableVirtioBlkForDataUseLvm.Get()) {
VLOG(1) << "Using virtio-blk with the LVM-provided disk for /data";
// LVM disk name is generated by cryptohome::DmcryptVolumePrefix in
// src/platform2/cryptohome.
const std::string lvm_disk_path =
base::StringPrintf("/dev/mapper/vm/dmcrypt-%s-arcvm",
user_id_hash_.substr(0, 8).c_str());
StartArcVm(std::move(callback), std::move(file_system_status),
base::FilePath(lvm_disk_path));
return;
}
VLOG(1) << "Using virtio-blk with the concierge-provided disk for /data";
// If request.disk_size is not set, concierge calculates the desired size
// (90% of the available space) and creates a sparse disk image.
vm_tools::concierge::CreateDiskImageRequest request;
request.set_cryptohome_id(user_id_hash_);
request.set_vm_name(kArcVmName);
request.set_image_type(vm_tools::concierge::DISK_IMAGE_AUTO);
request.set_storage_location(vm_tools::concierge::STORAGE_CRYPTOHOME_ROOT);
GetConciergeClient()->CreateDiskImage(
std::move(request),
base::BindOnce(&ArcVmClientAdapter::OnDataDiskImageCreated,
weak_factory_.GetWeakPtr(), std::move(callback),
std::move(file_system_status)));
}
void OnDataDiskImageCreated(
chromeos::VoidDBusMethodCallback callback,
FileSystemStatus file_system_status,
absl::optional<vm_tools::concierge::CreateDiskImageResponse> res) {
if (!res) {
LOG(ERROR) << "Failed to create a disk image for /data. Empty response.";
std::move(callback).Run(false);
return;
}
switch (res->status()) {
case vm_tools::concierge::DISK_STATUS_CREATED:
VLOG(1) << "Created a disk image for /data at " << res->disk_path();
StartArcVm(std::move(callback), std::move(file_system_status),
base::FilePath(res->disk_path()));
return;
case vm_tools::concierge::DISK_STATUS_EXISTS:
VLOG(1) << "Disk image for /data already exists: " << res->disk_path();
StartArcVm(std::move(callback), std::move(file_system_status),
base::FilePath(res->disk_path()));
return;
// TODO(niwa): Also handle DISK_STATUS_NOT_ENOUGH_SPACE.
default:
LOG(ERROR) << "Failed to create a disk image for /data. Status:"
<< res->status() << " Reason:" << res->failure_reason();
std::move(callback).Run(false);
return;
}
}
void StartArcVm(chromeos::VoidDBusMethodCallback callback,
FileSystemStatus file_system_status,
absl::optional<base::FilePath> data_disk_path) {
const base::FilePath demo_session_apps_path =
demo_mode_delegate_->GetDemoAppsPath();
const bool use_per_vm_core_scheduling =
base::FeatureList::IsEnabled(kEnablePerVmCoreScheduling);
// When the CPU has MDS or L1TF vulnerabilities, and per-VM core scheduling
// is not enabled via |kEnablePerVmCoreScheduling|, crosvm won't be allowed
// to run two vCPUs on the same physical core at the same time. This mode is
// called per-vCPU core scheduling, and it effectively disables SMT on
// crosvm. Because of this restriction, when per-vCPU core scheduling is in
// use, set |cpus| to the number of physical cores. Otherwise, set the
// variable to the number of logical cores minus the ones disabled by
// chrome://flags/#scheduler-configuration.
const int32_t cpus = (chromeos::system::IsCoreSchedulingAvailable() &&
!use_per_vm_core_scheduling)
? chromeos::system::NumberOfPhysicalCores()
: base::SysInfo::NumberOfProcessors() -
start_params_.num_cores_disabled;
DCHECK_LT(0, cpus);
std::vector<std::string> kernel_cmdline = GenerateKernelCmdline(
start_params_, file_system_status, is_host_on_vm_);
auto start_request = CreateStartArcVmRequest(
user_id_hash_, cpus, demo_session_apps_path, data_disk_path,
file_system_status, use_per_vm_core_scheduling,
std::move(kernel_cmdline), delegate_.get());
GetConciergeClient()->StartArcVm(
start_request,
base::BindOnce(&ArcVmClientAdapter::OnStartArcVmReply,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void OnStartArcVmReply(
chromeos::VoidDBusMethodCallback callback,
absl::optional<vm_tools::concierge::StartVmResponse> reply) {
if (!reply.has_value()) {
LOG(ERROR) << "Failed to start arcvm. Empty response.";
std::move(callback).Run(false);
return;
}
const vm_tools::concierge::StartVmResponse& response = reply.value();
if (response.status() != vm_tools::concierge::VM_STATUS_RUNNING) {
LOG(ERROR) << "Failed to start arcvm: status=" << response.status()
<< ", reason=" << response.failure_reason();
std::move(callback).Run(false);
return;
}
current_cid_ = response.vm_info().cid();
should_notify_observers_ = true;
VLOG(1) << "ARCVM started cid=" << current_cid_;
std::move(callback).Run(true);
}
void OnQueryAdbSideload(
UpgradeParams params,
chromeos::VoidDBusMethodCallback callback,
ash::SessionManagerClient::AdbSideloadResponseCode response_code,
bool enabled) {
VLOG(1) << "IsAdbSideloadAllowed, response_code="
<< static_cast<int>(response_code) << ", enabled=" << enabled;
switch (response_code) {
case ash::SessionManagerClient::AdbSideloadResponseCode::FAILED:
LOG(ERROR) << "Failed response from QueryAdbSideload";
StopArcInstanceInternal();
std::move(callback).Run(false);
return;
case ash::SessionManagerClient::AdbSideloadResponseCode::NEED_POWERWASH:
params.is_adb_sideloading_enabled = false;
break;
case ash::SessionManagerClient::AdbSideloadResponseCode::SUCCESS:
params.is_adb_sideloading_enabled = enabled;
break;
}
VLOG(1) << "Starting upstart jobs for UpgradeArc()";
std::vector<std::string> environment{
"CHROMEOS_USER=" +
cryptohome::CreateAccountIdentifierFromIdentification(cryptohome_id_)
.account_id()};
std::deque<JobDesc> jobs{
JobDesc{kArcVmPostLoginServicesJobName, UpstartOperation::JOB_START,
std::move(environment)},
};
ConfigureUpstartJobs(
std::move(jobs),
base::BindOnce(&ArcVmClientAdapter::OnConfigureUpstartJobsOnUpgradeArc,
weak_factory_.GetWeakPtr(), std::move(params),
std::move(callback)));
}
void OnConfigureUpstartJobsOnUpgradeArc(
UpgradeParams params,
chromeos::VoidDBusMethodCallback callback,
bool result) {
if (!result) {
LOG(ERROR) << "ConfigureUpstartJobs (on upgrading ARCVM) failed. ";
StopArcInstanceInternal();
std::move(callback).Run(false);
return;
}
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&SendUpgradePropsToArcVmBootNotificationServer,
current_cid_, std::move(params), serial_number_),
base::BindOnce(&ArcVmClientAdapter::OnUpgradePropsSent,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void OnUpgradePropsSent(chromeos::VoidDBusMethodCallback callback,
bool result) {
if (!result) {
LOG(ERROR)
<< "Failed to send upgrade props to arcvm-boot-notification-server";
StopArcInstanceInternal();
std::move(callback).Run(false);
return;
}
VLOG(1) << "Starting arcvm-post-vm-start-services.";
std::vector<std::string> environment;
std::deque<JobDesc> jobs{JobDesc{kArcVmPostVmStartServicesJobName,
UpstartOperation::JOB_START,
std::move(environment)}};
ConfigureUpstartJobs(
std::move(jobs),
base::BindOnce(&ArcVmClientAdapter::OnConfigureUpstartJobsAfterVmStart,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void OnConfigureUpstartJobsAfterVmStart(
chromeos::VoidDBusMethodCallback callback,
bool result) {
if (!result) {
LOG(ERROR) << "ConfigureUpstartJobs (after starting ARCVM) failed.";
StopArcInstanceInternal();
std::move(callback).Run(false);
return;
}
VLOG(1) << "ARCVM upgrade completed";
std::move(callback).Run(true);
}
void OnArcInstanceStopped(bool is_system_shutdown) {
VLOG(1) << "ARCVM stopped.";
// If this method is called before even mini VM is started (e.g. very early
// vm_concierge crash), or this method is called twice (e.g. crosvm crash
// followed by vm_concierge crash), do nothing.
if (!should_notify_observers_)
return;
should_notify_observers_ = false;
for (auto& observer : observer_list_)
observer.ArcInstanceStopped(is_system_shutdown);
}
void OnStopVmReply(
absl::optional<vm_tools::concierge::StopVmResponse> reply) {
// If the reply indicates the D-Bus call is successfully done, do nothing.
// Concierge will call OnVmStopped() eventually.
if (reply.has_value() && reply.value().success())
return;
// StopVm always returns successfully, so the only case where this happens
// is if the reply is empty, which means Concierge isn't running and ARCVM
// isn't either.
LOG(ERROR) << "Failed to stop ARCVM: empty reply.";
OnArcInstanceStopped(false /* is_system_shutdown */);
}
void OnTrimVmMemory(
TrimVmMemoryCallback callback,
absl::optional<vm_tools::concierge::ReclaimVmMemoryResponse> reply) {
bool success = false;
std::string failure_reason;
if (!reply.has_value()) {
failure_reason = "Empty response";
} else {
const vm_tools::concierge::ReclaimVmMemoryResponse& response =
reply.value();
success = response.success();
if (!success)
failure_reason = response.failure_reason();
}
VLOG(2) << "Finished trimming memory: success=" << success
<< (failure_reason.empty() ? "" : " reason=") << failure_reason;
std::move(callback).Run(success, failure_reason);
}
static void OnArcVmCompleteBootResponse(
absl::optional<vm_tools::concierge::ArcVmCompleteBootResponse> reply) {
vm_tools::concierge::ArcVmCompleteBootResult result =
reply.has_value()
? reply.value().result()
: vm_tools::concierge::ArcVmCompleteBootResult::BAD_REQUEST;
VLOG(2) << "ArcVmCompleteBoot: result=" << result;
if (result != vm_tools::concierge::ArcVmCompleteBootResult::SUCCESS)
LOG(WARNING) << "Failed ArcVmCompleteBoot: result=" << result;
}
std::unique_ptr<ArcVmClientAdapterDelegate> delegate_;
// True when the *host* is running on a VM.
const bool is_host_on_vm_;
// A cryptohome ID of the primary profile.
cryptohome::Identification cryptohome_id_;
// A hash of the primary profile user ID.
std::string user_id_hash_;
// A serial number for the current profile.
std::string serial_number_;
StartParams start_params_;
bool should_notify_observers_ = false;
int64_t current_cid_ = kInvalidCid;
FileSystemStatusRewriter file_system_status_rewriter_for_testing_;
// The delegate is owned by ArcSessionRunner.
DemoModeDelegate* demo_mode_delegate_ = nullptr;
// For callbacks.
base::WeakPtrFactory<ArcVmClientAdapter> weak_factory_{this};
};
std::unique_ptr<ArcClientAdapter> CreateArcVmClientAdapter() {
return std::make_unique<ArcVmClientAdapter>();
}
std::unique_ptr<ArcClientAdapter> CreateArcVmClientAdapterForTesting(
const FileSystemStatusRewriter& rewriter) {
return std::make_unique<ArcVmClientAdapter>(rewriter);
}
void SetArcVmBootNotificationServerAddressForTesting(
const std::string& new_address,
base::TimeDelta connect_timeout_limit,
base::TimeDelta connect_sleep_duration_initial) {
sockaddr_un* address =
const_cast<sockaddr_un*>(GetArcVmBootNotificationServerAddress());
DCHECK_GE(sizeof(address->sun_path), new_address.size());
DCHECK_GT(connect_timeout_limit, connect_sleep_duration_initial);
memset(address->sun_path, 0, sizeof(address->sun_path));
// |new_address| may contain '\0' if it is an abstract socket address, so use
// memcpy instead of strcpy.
memcpy(address->sun_path, new_address.data(), new_address.size());
g_connect_timeout_limit_for_testing = connect_timeout_limit;
g_connect_sleep_duration_initial_for_testing = connect_sleep_duration_initial;
}
void SetArcVmBootNotificationServerFdForTesting(absl::optional<int> fd) {
g_boot_notification_server_fd = fd;
}
std::vector<std::string> GenerateUpgradePropsForTesting(
const UpgradeParams& upgrade_params,
const std::string& serial_number,
const std::string& prefix) {
return GenerateUpgradeProps(upgrade_params, serial_number, prefix);
}
void SetArcVmClientAdapterDelegateForTesting( // IN-TEST
ArcClientAdapter* adapter,
std::unique_ptr<ArcVmClientAdapterDelegate> delegate) {
static_cast<ArcVmClientAdapter*>(adapter)
->set_delegate_for_testing( // IN-TEST
std::move(delegate));
}
} // namespace arc