// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "chrome/browser/ash/policy/status_collector/device_status_collector.h"
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <limits>
#include <optional>
#include <set>
#include <sstream>
#include <string_view>
#include <utility>
#include "ash/components/arc/mojom/enterprise_reporting.mojom.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "ash/constants/ash_features.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/feature_list.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/format_macros.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/posix/eintr_wrapper.h"
#include "base/sequence_checker.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system/sys_info.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/values.h"
#include "base/version.h"
#include "chrome/browser/ash/app_mode/kiosk_chrome_app_manager.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ash/crostini/crostini_pref_names.h"
#include "chrome/browser/ash/crostini/crostini_reporting_util.h"
#include "chrome/browser/ash/crostini/crostini_util.h"
#include "chrome/browser/ash/guest_os/guest_os_registry_service.h"
#include "chrome/browser/ash/guest_os/guest_os_registry_service_factory.h"
#include "chrome/browser/ash/login/demo_mode/demo_mode_dimensions.h"
#include "chrome/browser/ash/login/demo_mode/demo_session.h"
#include "chrome/browser/ash/policy/core/device_local_account.h"
#include "chrome/browser/ash/policy/core/reporting_user_tracker.h"
#include "chrome/browser/ash/policy/status_collector/enterprise_activity_storage.h"
#include "chrome/browser/ash/policy/status_collector/status_collector_state.h"
#include "chrome/browser/ash/policy/status_collector/tpm_status_combiner.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/crash_upload_list/crash_upload_list.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/ui/webui/ash/settings/pages/storage/device_storage_util.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/audio/cras_audio_handler.h"
#include "chromeos/ash/components/dbus/attestation/attestation_client.h"
#include "chromeos/ash/components/dbus/cryptohome/rpc.pb.h"
#include "chromeos/ash/components/dbus/hermes/hermes_euicc_client.h"
#include "chromeos/ash/components/dbus/hermes/hermes_manager_client.h"
#include "chromeos/ash/components/dbus/spaced/spaced_client.h"
#include "chromeos/ash/components/dbus/update_engine/update_engine_client.h"
#include "chromeos/ash/components/disks/disk_mount_manager.h"
#include "chromeos/ash/components/login/login_state/login_state.h"
#include "chromeos/ash/components/network/device_state.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "chromeos/ash/components/settings/timezone_settings.h"
#include "chromeos/ash/components/system/statistics_provider.h"
#include "chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_probe.mojom.h"
#include "chromeos/dbus/power_manager/idle.pb.h"
#include "chromeos/dbus/tpm_manager/tpm_manager.pb.h"
#include "chromeos/dbus/tpm_manager/tpm_manager_client.h"
#include "chromeos/version/version_loader.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "components/policy/core/common/cloud/cloud_policy_util.h"
#include "components/policy/core/common/device_local_account_type.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "components/user_manager/user_type.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/gpu_data_manager.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
#include "gpu/config/gpu_info.h"
#include "gpu/ipc/common/memory_stats.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/rect.h"
namespace policy {
namespace {
namespace em = ::enterprise_management;
// How many seconds of inactivity triggers the idle state.
const int kIdleStateThresholdSeconds = 300;
// How much time in the past to store active periods for.
constexpr base::TimeDelta kMaxStoredPastActivityInterval = base::Days(30);
// How much time in the future to store active periods for.
constexpr base::TimeDelta kMaxStoredFutureActivityInterval = base::Days(2);
// How often, in seconds, to sample the hardware resource usage.
const unsigned int kResourceUsageSampleIntervalSeconds = 120;
// The location we read our CPU statistics from.
const char kProcStat[] = "/proc/stat";
// The location we read our CPU temperature and channel label from.
const char kHwmonDir[] = "/sys/class/hwmon/";
const char kDeviceDir[] = "device";
const char kHwmonDirectoryPattern[] = "hwmon*";
const char kCPUTempFilePattern[] = "temp*_input";
// The location where storage device statistics are read from.
const char kStorageInfoPath[] = "/var/log/storage_info.txt";
// The location where stateful partition info is read from.
const char kStatefulPartitionPath[] = "/home/.shadow";
// TODO(b/144081278): Remove when resolved.
// Debug values for cases when firmware version is not present.
const char kFirmwareFileEmpty[] = "FirmwareFileEmpty";
const char kFirmwareFileNotRead[] = "FirmwareFileNotRead";
const char kFirmwareNotInitialized[] = "FirmwareNotInitialized";
const char kFirmwareNotParsed[] = "FirmwareNotParsed";
// File to look for firmware number in.
const char kPathFirmware[] = "/var/log/bios_info.txt";
// O°C in deciKelvin.
const unsigned int kZeroCInDeciKelvin = 2731;
// The duration for crash report collection.
constexpr base::TimeDelta kCrashReportInfoDuration = base::Days(1);
// The sources of crash report leads to device restart.
const char kCrashReportSourceKernel[] = "kernel";
const char kCrashReportSourceEC[] = "embedded-controller";
// The maximum number of crash report entries to be read.
// According to the official document, the crash reporter uploads no more than
// 24 MB (compressed) or 32 reports (whichever comes last) in any 24 hour
// window. Therefore, it looks safe to set max size as 100 here.
const int kCrashReportEntryMaxSize = 100;
// Helper function (invoked via blocking pool) to fetch information about
// mounted disks.
std::vector<em::VolumeInfo> GetVolumeInfo(
const std::vector<std::string>& mount_points) {
std::vector<em::VolumeInfo> result;
for (const std::string& mount_point : mount_points) {
base::FilePath mount_path(mount_point);
// Non-native file systems do not have a mount point in the local file
// system. However, it's worth checking here, as it's easier than checking
// earlier which mount point is local, and which one is not.
if (mount_point.empty() || !base::PathExists(mount_path)) {
continue;
}
int64_t free_size = base::SysInfo::AmountOfFreeDiskSpace(mount_path);
int64_t total_size = base::SysInfo::AmountOfTotalDiskSpace(mount_path);
if (free_size < 0 || total_size < 0) {
LOG(ERROR) << "Unable to get volume status for " << mount_point;
continue;
}
em::VolumeInfo info;
info.set_volume_id(mount_point);
info.set_storage_total(total_size);
info.set_storage_free(free_size);
result.push_back(info);
}
return result;
}
// Reads the first CPU line from /proc/stat. Returns an empty string if
// the cpu data could not be read.
// The format of this line from /proc/stat is:
//
// cpu user_time nice_time system_time idle_time
//
// where user_time, nice_time, system_time, and idle_time are all integer
// values measured in jiffies from system startup.
std::string ReadCPUStatistics() {
std::string contents;
if (base::ReadFileToString(base::FilePath(kProcStat), &contents)) {
size_t eol = contents.find("\n");
if (eol != std::string::npos) {
std::string line = contents.substr(0, eol);
if (line.compare(0, 4, "cpu ") == 0) {
return line;
}
}
// First line should always start with "cpu ".
NOTREACHED_IN_MIGRATION()
<< "Could not parse /proc/stat contents: " << contents;
}
LOG(WARNING) << "Unable to read CPU statistics from " << kProcStat;
return std::string();
}
// Read system temperature sensor data into |out_contents| from
//
// |sensor_dir|/temp*_input
//
// which contains millidegree Celsius temperature and
//
// |sensor_dir|/temp*_label or
// |sensor_dir|/name
//
// which contains an appropriate label name for the given sensor.
// Returns |true| iff there was at least one sensor value in given |sensor_dir|.
bool ReadTemperatureSensorInfo(const base::FilePath& sensor_dir,
std::vector<em::CPUTempInfo>* out_contents) {
bool has_data = false;
base::FileEnumerator enumerator(
sensor_dir, false, base::FileEnumerator::FILES, kCPUTempFilePattern);
for (base::FilePath temperature_path = enumerator.Next();
!temperature_path.empty(); temperature_path = enumerator.Next()) {
// Get appropriate temp*_label file.
std::string label_path = temperature_path.MaybeAsASCII();
if (label_path.empty()) {
LOG(WARNING) << "Unable to parse a path to temp*_input file as ASCII";
continue;
}
base::ReplaceSubstringsAfterOffset(&label_path, 0, "input", "label");
base::FilePath name_path = sensor_dir.Append("name");
// Get the label describing this temperature. Use temp*_label
// if present, fall back on name file or blank.
std::string label;
if (base::PathExists(base::FilePath(label_path))) {
base::ReadFileToString(base::FilePath(label_path), &label);
} else if (base::PathExists(base::FilePath(name_path))) {
base::ReadFileToString(name_path, &label);
} else {
label = std::string();
}
// Read temperature in millidegree Celsius.
std::string temperature_string;
int32_t temperature = 0;
if (base::ReadFileToString(temperature_path, &temperature_string) &&
sscanf(temperature_string.c_str(), "%d", &temperature) == 1) {
has_data = true;
// CPU temp in millidegree Celsius to Celsius
temperature /= 1000;
em::CPUTempInfo info;
info.set_cpu_label(label);
info.set_cpu_temp(temperature);
out_contents->push_back(info);
} else {
LOG(WARNING) << "Unable to read CPU temp from "
<< temperature_path.MaybeAsASCII();
}
}
return has_data;
}
// Read system temperature sensors from
//
// /sys/class/hwmon/hwmon*/(device/)?
std::vector<em::CPUTempInfo> ReadCPUTempInfo() {
std::vector<em::CPUTempInfo> contents;
// Get directories /sys/class/hwmon/hwmon*
base::FileEnumerator hwmon_enumerator(base::FilePath(kHwmonDir), false,
base::FileEnumerator::DIRECTORIES,
kHwmonDirectoryPattern);
for (base::FilePath hwmon_path = hwmon_enumerator.Next(); !hwmon_path.empty();
hwmon_path = hwmon_enumerator.Next()) {
// Get temp*_input files in hwmon*/ and hwmon*/device/
base::FilePath device_path = hwmon_path.Append(kDeviceDir);
if (base::PathExists(device_path)) {
// We might have hwmon*/device/, but sensor values are still in hwmon*/
if (!ReadTemperatureSensorInfo(device_path, &contents)) {
ReadTemperatureSensorInfo(hwmon_path, &contents);
}
} else {
ReadTemperatureSensorInfo(hwmon_path, &contents);
}
}
return contents;
}
// If |contents| contains |prefix| followed by a hex integer, parses the hex
// integer of specified length and returns it.
// Otherwise, returns std::nullopt.
std::optional<int> ExtractHexIntegerAfterPrefix(std::string_view contents,
std::string_view prefix,
size_t hex_number_length) {
size_t prefix_position = contents.find(prefix);
if (prefix_position == std::string::npos) {
return std::nullopt;
}
if (prefix_position + prefix.size() + hex_number_length >= contents.size()) {
return std::nullopt;
}
int parsed_number;
if (!base::HexStringToInt(
contents.substr(prefix_position + prefix.size(), hex_number_length),
&parsed_number)) {
return std::nullopt;
}
return parsed_number;
}
// Read life time estimation value for eMMC from data generated by
// chromeos_disk_metrics. The data is stored in format:
// [DEVICE_LIFE_TIME_EST_TYP_[AB]: 0xXX]
// where A, B indicate the area of MMC being assesed(SLC and MLC), XX -- hex
// integer representing wear out of selected area.
// reference: e.MMC Device Health Report
// https://www.micron.com/products/managed-nand/emmc/emmc-software
em::DiskLifetimeEstimation ReadDiskLifeTimeEstimation() {
em::DiskLifetimeEstimation est;
std::string contents;
const std::string pattern_slc = "[DEVICE_LIFE_TIME_EST_TYP_A: 0x";
const std::string pattern_mlc = "[DEVICE_LIFE_TIME_EST_TYP_B: 0x";
if (!base::ReadFileToStringWithMaxSize(
base::FilePath(kStorageInfoPath), &contents,
40000)) { // max size in case somebody tackles with the file
return est;
}
auto slc_est = ExtractHexIntegerAfterPrefix(contents, pattern_slc, 2);
if (slc_est) {
est.set_slc(slc_est.value());
}
auto mlc_est = ExtractHexIntegerAfterPrefix(contents, pattern_mlc, 2);
if (mlc_est) {
est.set_mlc(mlc_est.value());
}
return est;
}
// Read stateful partition info for user data.
em::StatefulPartitionInfo ReadStatefulPartitionInfo() {
em::StatefulPartitionInfo spi;
const base::FilePath statefulPartitionPath(kStatefulPartitionPath);
const int64_t available_space =
base::SysInfo::AmountOfFreeDiskSpace(statefulPartitionPath);
const int64_t total_space =
base::SysInfo::AmountOfTotalDiskSpace(statefulPartitionPath);
if (available_space == -1) {
LOG(ERROR) << "ReadStatefulPartitionInfo failed fetching available space.";
return spi;
}
if (total_space == -1) {
LOG(ERROR) << "ReadStatefulPartitionInfo failed fetching total space.";
return spi;
}
spi.set_available_space(available_space);
spi.set_total_space(total_space);
return spi;
}
// Collects all the display related information.
void GetDisplayStatus(em::GraphicsStatus* graphics_status) {
const std::vector<display::Display> displays =
display::Screen::GetScreen()->GetAllDisplays();
for (const auto& display : displays) {
em::DisplayInfo* display_info = graphics_status->add_displays();
display_info->set_resolution_width(display.GetSizeInPixel().width());
display_info->set_resolution_height(display.GetSizeInPixel().height());
display_info->set_refresh_rate(display.display_frequency());
display_info->set_is_internal(display.IsInternal());
}
}
// Makes the requested |gpu_memory_stats| available. Collects the other required
// graphics properties next. Finally, calls |callback|.
void OnVideoMemoryUsageStatsUpdate(
DeviceStatusCollector::GraphicsStatusReceiver callback,
std::unique_ptr<em::GraphicsStatus> graphics_status,
const gpu::VideoMemoryUsageStats& gpu_memory_stats) {
auto* gpu_data_manager = content::GpuDataManager::GetInstance();
gpu::GPUInfo gpu_info = gpu_data_manager->GetGPUInfo();
// Adapter information
em::GraphicsAdapterInfo* graphics_info = graphics_status->mutable_adapter();
graphics_info->set_name(gpu_info.gpu.device_string);
graphics_info->set_driver_version(gpu_info.gpu.driver_version);
graphics_info->set_device_id(gpu_info.gpu.device_id);
graphics_info->set_system_ram_usage(gpu_memory_stats.bytes_allocated);
std::move(callback).Run(*graphics_status);
}
// Fetches display-related and graphics-adapter information.
void FetchGraphicsStatus(
DeviceStatusCollector::GraphicsStatusReceiver callback) {
std::unique_ptr<em::GraphicsStatus> graphics_status =
std::make_unique<em::GraphicsStatus>();
GetDisplayStatus(graphics_status.get());
auto* gpu_data_manager = content::GpuDataManager::GetInstance();
gpu_data_manager->RequestVideoMemoryUsageStatsUpdate(
base::BindOnce(&OnVideoMemoryUsageStatsUpdate, std::move(callback),
std::move(graphics_status)));
}
bool ReadAndroidStatus(StatusCollector::AndroidStatusReceiver receiver) {
auto* const arc_service_manager = arc::ArcServiceManager::Get();
if (!arc_service_manager) {
return false;
}
auto* const instance_holder =
arc_service_manager->arc_bridge_service()->enterprise_reporting();
if (!instance_holder) {
return false;
}
auto* const instance =
ARC_GET_INSTANCE_FOR_METHOD(instance_holder, GetStatus);
if (!instance) {
return false;
}
instance->GetStatus(std::move(receiver));
return true;
}
void ReadTpmStatus(DeviceStatusCollector::TpmStatusReceiver callback) {
// D-Bus calls are allowed only on the UI thread.
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto tpm_status_combiner =
base::MakeRefCounted<TpmStatusCombiner>(std::move(callback));
chromeos::TpmManagerClient::Get()->GetTpmNonsensitiveStatus(
::tpm_manager::GetTpmNonsensitiveStatusRequest(),
base::BindOnce(&TpmStatusCombiner::OnGetTpmStatus, tpm_status_combiner));
ash::AttestationClient::Get()->GetStatus(
::attestation::GetStatusRequest(),
base::BindOnce(&TpmStatusCombiner::OnGetEnrollmentStatus,
tpm_status_combiner));
chromeos::TpmManagerClient::Get()->GetDictionaryAttackInfo(
::tpm_manager::GetDictionaryAttackInfoRequest(),
base::BindOnce(&TpmStatusCombiner::OnGetDictionaryAttackInfo,
tpm_status_combiner));
chromeos::TpmManagerClient::Get()->GetSupportedFeatures(
::tpm_manager::GetSupportedFeaturesRequest(),
base::BindOnce(&TpmStatusCombiner::OnGetSupportedFeatures,
tpm_status_combiner));
}
base::Version GetPlatformVersion() {
return base::Version(base::SysInfo::OperatingSystemVersion());
}
// Helper routine to convert from Shill-provided signal strength (percent)
// to dBm units expected by server.
int ConvertWifiSignalStrength(int signal_strength) {
// Shill attempts to convert WiFi signal strength from its internal dBm to a
// percentage range (from 0-100) using the equation 25 * dBm_value / 11 + 200,
// and then clamping the result to the range 0-100 (see
// shill::WiFiService::SignalToStrength()).
//
// To convert back to dBm, we use the inverse of the function above to yield
// a clamped dBm value in the range of -88 to -44dBm.
//
// TODO(atwilson): Tunnel the raw dBm signal strength from Shill instead of
// doing the conversion here so we can report non-clamped values
// (crbug.com/463334).
DCHECK_GT(signal_strength, 0);
DCHECK_LE(signal_strength, 100);
return (signal_strength - 200) * 11 / 25;
}
bool IsKioskSession() {
return ash::LoginState::Get()->GetLoggedInUserType() ==
ash::LoginState::LOGGED_IN_USER_KIOSK;
}
// Utility method to turn cpu_temp_fetcher_ to OnceCallback
std::vector<em::CPUTempInfo> InvokeCpuTempFetcher(
DeviceStatusCollector::CPUTempFetcher fetcher) {
return fetcher.Run();
}
// Utility method to complete information for a reported Crostini App.
// Returns whether all required App information could be retrieved or not.
bool AddCrostiniAppInfo(
const guest_os::GuestOsRegistryService::Registration& registration,
em::CrostiniApp* const app) {
app->set_app_name(registration.Name());
const base::Time last_launch_time = registration.LastLaunchTime();
if (!last_launch_time.is_null()) {
app->set_last_launch_time_window_start_timestamp(
crostini::GetThreeDayWindowStart(last_launch_time)
.InMillisecondsSinceUnixEpoch());
}
app->set_app_type(em::CROSTINI_APP_TYPE_INTERACTIVE);
const std::string& package_id = registration.PackageId();
if (package_id.empty()) {
return true;
}
const std::vector<std::string> package_info = base::SplitString(
package_id, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
// The package identifier is in the form of a semicolon delimited string of
// the format: name;version;arch;data (see cicerone_service.proto)
if (package_info.size() != 4) {
LOG(ERROR) << "Package id has the wrong format: " << package_id;
return false;
}
app->set_package_name(package_info[0]);
app->set_package_version(package_info[1]);
return true;
}
// Utility method to add a list of installed Crostini Apps to Crostini status
void AddCrostiniAppListForProfile(Profile* const profile,
em::CrostiniStatus* const crostini_status) {
const std::map<std::string, guest_os::GuestOsRegistryService::Registration>&
registered_apps =
guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile)
->GetRegisteredApps(guest_os::VmType::TERMINA);
for (const auto& pair : registered_apps) {
const std::string& registered_app_id = pair.first;
const guest_os::GuestOsRegistryService::Registration& registration =
pair.second;
em::CrostiniApp* const app = crostini_status->add_installed_apps();
if (!AddCrostiniAppInfo(registration, app)) {
LOG(ERROR) << "Could not retrieve all required information for "
"registered app_id: "
<< registered_app_id;
}
}
}
// Reads content of firmware file.
// Returns pair of the firmware version and fetch error if not fetched.
// TODO(b/144081278): Just call chromeos::version_loader::ParseFirmware() when
// it's resolved.
std::pair<std::string, std::string> ReadFirmwareVersion() {
std::string firmware;
std::string contents;
const base::FilePath file_path(kPathFirmware);
if (!base::ReadFileToString(file_path, &contents)) {
return {firmware, kFirmwareFileNotRead};
}
if (contents.empty()) {
return {firmware, kFirmwareFileEmpty};
}
firmware = chromeos::version_loader::ParseFirmware(contents);
if (firmware.empty()) {
return {firmware, kFirmwareNotParsed};
}
return {firmware, std::string()};
}
em::CrashReportInfo::CrashReportUploadStatus GetCrashReportUploadStatus(
UploadList::UploadInfo::State state) {
switch (state) {
case UploadList::UploadInfo::State::NotUploaded:
return em::CrashReportInfo::UPLOAD_STATUS_NOT_UPLOADED;
case UploadList::UploadInfo::State::Pending:
return em::CrashReportInfo::UPLOAD_STATUS_PENDING;
case UploadList::UploadInfo::State::Pending_UserRequested:
return em::CrashReportInfo::UPLOAD_STATUS_PENDING_USER_REQUESTED;
case UploadList::UploadInfo::State::Uploaded:
return em::CrashReportInfo::UPLOAD_STATUS_UPLOADED;
default:
return em::CrashReportInfo::UPLOAD_STATUS_UNKNOWN;
}
NOTREACHED_IN_MIGRATION();
}
// Filter the loaded crash reports.
// - the |upload_time| should be with last 24 hours.
// - the |source| should be 'kernel' or 'embedded-controller'.
void CrashReportsLoaded(
scoped_refptr<UploadList> upload_list,
DeviceStatusCollector::CrashReportInfoReceiver callback) {
const std::vector<const UploadList::UploadInfo*> uploads =
upload_list->GetUploads(kCrashReportEntryMaxSize);
const auto end_time = base::Time::Now();
const auto start_time = end_time - kCrashReportInfoDuration;
std::vector<em::CrashReportInfo> contents;
for (const UploadList::UploadInfo* crash_report : uploads) {
if (crash_report->upload_time >= start_time &&
crash_report->upload_time < end_time &&
(crash_report->source == kCrashReportSourceKernel ||
crash_report->source == kCrashReportSourceEC)) {
em::CrashReportInfo info;
info.set_remote_id(crash_report->upload_id);
info.set_capture_timestamp(
crash_report->capture_time.InMillisecondsSinceUnixEpoch());
info.set_cause(crash_report->source);
info.set_upload_status(GetCrashReportUploadStatus(crash_report->state));
contents.push_back(info);
}
}
std::move(callback).Run(contents);
}
// Read the crash reports stored in the uploads.log file.
void ReadCrashReportInfo(
DeviceStatusCollector::CrashReportInfoReceiver callback) {
scoped_refptr<UploadList> upload_list = CreateCrashUploadList();
upload_list->Load(
base::BindOnce(CrashReportsLoaded, upload_list, std::move(callback)));
}
em::ActiveTimePeriod::SessionType GetSessionType(
const std::string& user_email) {
auto type = GetDeviceLocalAccountType(user_email);
if (!type.has_value()) {
return em::ActiveTimePeriod::SESSION_AFFILIATED_USER;
}
switch (type.value()) {
case DeviceLocalAccountType::kPublicSession:
case DeviceLocalAccountType::kSamlPublicSession:
return em::ActiveTimePeriod::SESSION_MANAGED_GUEST;
case DeviceLocalAccountType::kKioskApp:
return em::ActiveTimePeriod::SESSION_KIOSK;
case DeviceLocalAccountType::kWebKioskApp:
return em::ActiveTimePeriod::SESSION_WEB_KIOSK;
default:
NOTREACHED_IN_MIGRATION();
}
NOTREACHED_IN_MIGRATION();
return em::ActiveTimePeriod::SESSION_UNKNOWN;
}
// Remap GscVersion using switch-case even though the values match
// to ensure that the compiler complains if a new value has been added.
em::TpmVersionInfo_GscVersion ConvertTpmGscVersion(
tpm_manager::GscVersion gsc_version) {
switch (gsc_version) {
case tpm_manager::GscVersion::GSC_VERSION_NOT_GSC:
return em::TpmVersionInfo::GSC_VERSION_NOT_GSC;
case tpm_manager::GscVersion::GSC_VERSION_CR50:
return em::TpmVersionInfo::GSC_VERSION_CR50;
case tpm_manager::GscVersion::GSC_VERSION_TI50:
return em::TpmVersionInfo::GSC_VERSION_TI50;
}
NOTREACHED_IN_MIGRATION();
return em::TpmVersionInfo::GSC_VERSION_UNSPECIFIED;
}
} // namespace
class DeviceStatusCollectorState : public StatusCollectorState {
public:
explicit DeviceStatusCollectorState(
const scoped_refptr<base::SequencedTaskRunner> task_runner,
StatusCollectorCallback response)
: StatusCollectorState(task_runner, std::move(response)) {}
// Queues an async callback to query disk volume information.
void SampleVolumeInfo(
const DeviceStatusCollector::VolumeInfoFetcher& volume_info_fetcher) {
// Create list of mounted disk volumes to query status.
std::vector<storage::MountPoints::MountPointInfo> external_mount_points;
storage::ExternalMountPoints::GetSystemInstance()->AddMountPointInfosTo(
&external_mount_points);
std::vector<std::string> mount_points;
for (const auto& info : external_mount_points) {
mount_points.push_back(info.path.value());
}
for (const auto& mount_point :
ash::disks::DiskMountManager::GetInstance()->mount_points()) {
// Extract a list of mount points to populate.
mount_points.push_back(mount_point.mount_path);
}
// Call out to the blocking pool to sample disk volume info.
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(volume_info_fetcher, mount_points),
base::BindOnce(&DeviceStatusCollectorState::OnVolumeInfoReceived,
this));
}
// Queues an async callback to query CPU temperature information.
void SampleCPUTempInfo(
const DeviceStatusCollector::CPUTempFetcher& cpu_temp_fetcher) {
// Call out to the blocking pool to sample CPU temp.
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(cpu_temp_fetcher),
base::BindOnce(&DeviceStatusCollectorState::OnCPUTempInfoReceived,
this));
}
bool FetchAndroidStatus(
const StatusCollector::AndroidStatusFetcher& android_status_fetcher) {
return android_status_fetcher.Run(base::BindOnce(
&DeviceStatusCollectorState::OnAndroidInfoReceived, this));
}
void FetchTpmStatus(
const DeviceStatusCollector::TpmStatusFetcher& tpm_status_fetcher) {
tpm_status_fetcher.Run(
base::BindOnce(&DeviceStatusCollectorState::OnTpmStatusReceived, this));
}
void FetchCrosHealthdData(
const DeviceStatusCollector::CrosHealthdDataFetcher&
cros_healthd_data_fetcher,
std::vector<ash::cros_healthd::mojom::ProbeCategoryEnum> probe_categories,
bool report_system_info,
bool report_vpd_info,
bool report_storage_status,
bool report_version_info,
bool report_network_configuration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
cros_healthd_data_fetcher.Run(
probe_categories,
base::BindOnce(&DeviceStatusCollectorState::OnCrosHealthdDataReceived,
this, report_system_info, report_vpd_info,
report_storage_status, report_version_info,
report_network_configuration));
}
void FetchEMMCLifeTime(
const DeviceStatusCollector::EMMCLifetimeFetcher& emmc_lifetime_fetcher) {
// Call out to the blocking pool to read disklifetimeestimation.
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(emmc_lifetime_fetcher),
base::BindOnce(&DeviceStatusCollectorState::OnEMMCLifetimeReceived,
this));
}
void FetchStatefulPartitionInfo(
const DeviceStatusCollector::StatefulPartitionInfoFetcher&
stateful_partition_info_fetcher) {
// Call out to the blocking pool to read stateful partition information.
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(stateful_partition_info_fetcher),
base::BindOnce(
&DeviceStatusCollectorState::OnStatefulPartitionInfoReceived,
this));
}
void FetchRootDeviceSize() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ash::SpacedClient::Get()->GetRootDeviceSize(
base::BindOnce(&DeviceStatusCollectorState::OnGetRootDeviceSize, this));
}
void FetchGraphicsStatus(const DeviceStatusCollector::GraphicsStatusFetcher&
graphics_status_fetcher) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
graphics_status_fetcher.Run(base::BindOnce(
&DeviceStatusCollectorState::OnGraphicsStatusReceived, this));
}
void FetchCrashReportInfo(const DeviceStatusCollector::CrashReportInfoFetcher&
crash_report_fetcher) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
crash_report_fetcher.Run(base::BindOnce(
&DeviceStatusCollectorState::OnCrashReportInfoReceived, this));
}
void SetDeviceStatusReported() { device_status_reported_ = true; }
private:
~DeviceStatusCollectorState() override {
if (!device_status_reported_) {
response_params_.device_status.reset();
}
}
void OnVolumeInfoReceived(const std::vector<em::VolumeInfo>& volume_info) {
if (!volume_info.empty()) {
SetDeviceStatusReported();
}
response_params_.device_status->clear_volume_infos();
for (const em::VolumeInfo& info : volume_info) {
*response_params_.device_status->add_volume_infos() = info;
}
}
void OnCPUTempInfoReceived(
const std::vector<em::CPUTempInfo>& cpu_temp_info) {
// Only one of OnCrosHealthdDataReceived or OnCPUTempInfoReceived should be
// called.
DCHECK_EQ(response_params_.device_status->cpu_temp_infos_size(), 0);
DLOG_IF(WARNING, cpu_temp_info.empty())
<< "Unable to read CPU temp information.";
base::Time timestamp = base::Time::Now();
if (!cpu_temp_info.empty()) {
SetDeviceStatusReported();
}
for (const em::CPUTempInfo& info : cpu_temp_info) {
auto* new_info = response_params_.device_status->add_cpu_temp_infos();
*new_info = info;
new_info->set_timestamp(timestamp.InMillisecondsSinceUnixEpoch());
}
}
void OnAndroidInfoReceived(const std::string& status,
const std::string& droid_guard_info) {
em::AndroidStatus* const android_status =
response_params_.session_status->mutable_android_status();
android_status->set_status_payload(status);
android_status->set_droid_guard_info(droid_guard_info);
}
void OnTpmStatusReceived(const em::TpmStatusInfo& tpm_status_info) {
// Make sure we edit the state on the right thread.
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
response_params_.device_status->mutable_tpm_status_info()->MergeFrom(
tpm_status_info);
SetDeviceStatusReported();
}
// Stores the contents of |probe_result| and |samples| to |response_params_|.
void OnCrosHealthdDataReceived(
bool report_system_info,
bool report_vpd_info,
bool report_storage_status,
bool report_version_info,
bool report_network_configuration,
ash::cros_healthd::mojom::TelemetryInfoPtr probe_result,
const base::circular_deque<std::unique_ptr<SampledData>>& samples) {
namespace cros_healthd = ::ash::cros_healthd::mojom;
// Make sure we edit the state on the right thread.
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (probe_result.is_null()) {
return;
}
// Process NonRemovableBlockDeviceResult.
const auto& block_device_result = probe_result->block_device_result;
if (!block_device_result.is_null()) {
switch (block_device_result->which()) {
case cros_healthd::NonRemovableBlockDeviceResult::Tag::kError: {
LOG(ERROR) << "cros_healthd: Error getting block device info: "
<< block_device_result->get_error()->msg;
break;
}
case cros_healthd::NonRemovableBlockDeviceResult::Tag::
kBlockDeviceInfo: {
em::StorageStatus* const storage_status_out =
response_params_.device_status->mutable_storage_status();
for (const auto& storage :
block_device_result->get_block_device_info()) {
em::DiskInfo* const disk_info_out = storage_status_out->add_disks();
disk_info_out->set_serial(base::NumberToString(storage->serial));
disk_info_out->set_manufacturer(
base::NumberToString(storage->manufacturer_id));
disk_info_out->set_model(storage->name);
disk_info_out->set_type(storage->type);
disk_info_out->set_size(storage->size);
disk_info_out->set_bytes_read_since_last_boot(
storage->bytes_read_since_last_boot);
disk_info_out->set_bytes_written_since_last_boot(
storage->bytes_written_since_last_boot);
disk_info_out->set_read_time_seconds_since_last_boot(
storage->read_time_seconds_since_last_boot);
disk_info_out->set_write_time_seconds_since_last_boot(
storage->write_time_seconds_since_last_boot);
disk_info_out->set_io_time_seconds_since_last_boot(
storage->io_time_seconds_since_last_boot);
const auto& discard_time =
storage->discard_time_seconds_since_last_boot;
if (!discard_time.is_null()) {
disk_info_out->set_discard_time_seconds_since_last_boot(
discard_time->value);
}
// vendor_id
const auto& vendor_id = storage->vendor_id;
switch (vendor_id->which()) {
case cros_healthd::BlockDeviceVendor::Tag::kNvmeSubsystemVendor:
disk_info_out->set_nvme_subsystem_vendor(
vendor_id->get_nvme_subsystem_vendor());
break;
case cros_healthd::BlockDeviceVendor::Tag::kEmmcOemid:
disk_info_out->set_emmc_oemid(vendor_id->get_emmc_oemid());
break;
case cros_healthd::BlockDeviceVendor::Tag::kOther:
disk_info_out->set_other_vendor(vendor_id->get_other());
break;
case cros_healthd::BlockDeviceVendor::Tag::kUnknown:
LOG(ERROR) << "cros_healthd: Unknown storage vendor tag";
break;
case cros_healthd::BlockDeviceVendor::Tag::kJedecManfid:
disk_info_out->set_jedec_manfid(vendor_id->get_jedec_manfid());
break;
}
// product_id
const auto& product_id = storage->product_id;
switch (product_id->which()) {
case cros_healthd::BlockDeviceProduct::Tag::kNvmeSubsystemDevice:
disk_info_out->set_nvme_subsystem_device(
product_id->get_nvme_subsystem_device());
break;
case cros_healthd::BlockDeviceProduct::Tag::kEmmcPnm:
disk_info_out->set_emmc_pnm(product_id->get_emmc_pnm());
break;
case cros_healthd::BlockDeviceProduct::Tag::kOther:
disk_info_out->set_other_product(product_id->get_other());
break;
case cros_healthd::BlockDeviceProduct::Tag::kUnknown:
LOG(ERROR) << "cros_healthd: Unknown storage product tag";
break;
}
// revision
const auto& revision = storage->revision;
switch (revision->which()) {
case cros_healthd::BlockDeviceRevision::Tag::kNvmePcieRev:
disk_info_out->set_nvme_hardware_rev(
revision->get_nvme_pcie_rev());
break;
case cros_healthd::BlockDeviceRevision::Tag::kEmmcPrv:
disk_info_out->set_emmc_hardware_rev(revision->get_emmc_prv());
break;
case cros_healthd::BlockDeviceRevision::Tag::kOther:
disk_info_out->set_other_hardware_rev(revision->get_other());
break;
case cros_healthd::BlockDeviceRevision::Tag::kUnknown:
LOG(ERROR) << "cros_healthd: Unknown storage revision tag";
break;
}
// firmware version
const auto& fw_version = storage->firmware_version;
switch (fw_version->which()) {
case cros_healthd::BlockDeviceFirmware::Tag::kNvmeFirmwareRev:
disk_info_out->set_nvme_firmware_rev(
fw_version->get_nvme_firmware_rev());
break;
case cros_healthd::BlockDeviceFirmware::Tag::kEmmcFwrev:
disk_info_out->set_emmc_firmware_rev(
fw_version->get_emmc_fwrev());
break;
case cros_healthd::BlockDeviceFirmware::Tag::kOther:
disk_info_out->set_other_firmware_rev(fw_version->get_other());
break;
case cros_healthd::BlockDeviceFirmware::Tag::kUnknown:
LOG(ERROR) << "cros_healthd: Unknown storage firmware tag";
break;
case cros_healthd::BlockDeviceFirmware::Tag::kUfsFwrev:
disk_info_out->set_ufs_firmware_rev(
fw_version->get_ufs_fwrev());
break;
}
switch (storage->purpose) {
case cros_healthd::StorageDevicePurpose::kUnknown:
disk_info_out->set_purpose(em::DiskInfo::PURPOSE_UNKNOWN);
break;
case cros_healthd::StorageDevicePurpose::kBootDevice:
disk_info_out->set_purpose(em::DiskInfo::PURPOSE_BOOT);
break;
case cros_healthd::StorageDevicePurpose::DEPRECATED_kSwapDevice:
disk_info_out->set_purpose(em::DiskInfo::PURPOSE_SWAP);
break;
case cros_healthd::StorageDevicePurpose::kNonBootDevice:
break;
}
}
SetDeviceStatusReported();
break;
}
}
}
// Process BatteryResult.
const auto& battery_result = probe_result->battery_result;
if (!battery_result.is_null()) {
switch (battery_result->which()) {
case cros_healthd::BatteryResult::Tag::kError: {
LOG(ERROR) << "cros_healthd: Error getting battery info: "
<< battery_result->get_error()->msg;
break;
}
case cros_healthd::BatteryResult::Tag::kBatteryInfo: {
const auto& battery_info = battery_result->get_battery_info();
// Device does not have a battery.
if (battery_info.is_null()) {
break;
}
em::PowerStatus* const power_status_out =
response_params_.device_status->mutable_power_status();
em::BatteryInfo* const battery_info_out =
power_status_out->add_batteries();
battery_info_out->set_serial(battery_info->serial_number);
battery_info_out->set_manufacturer(battery_info->vendor);
battery_info_out->set_cycle_count(battery_info->cycle_count);
battery_info_out->set_technology(battery_info->technology);
// Convert Ah to mAh:
battery_info_out->set_design_capacity(
std::lround(battery_info->charge_full_design * 1000));
battery_info_out->set_full_charge_capacity(
std::lround(battery_info->charge_full * 1000));
// Convert V to mV:
battery_info_out->set_design_min_voltage(
std::lround(battery_info->voltage_min_design * 1000));
if (battery_info->manufacture_date) {
battery_info_out->set_manufacture_date(
battery_info->manufacture_date.value());
}
for (const std::unique_ptr<SampledData>& sample_data : samples) {
auto it =
sample_data->battery_samples.find(battery_info->model_name);
if (it != sample_data->battery_samples.end()) {
battery_info_out->add_samples()->CheckTypeAndMergeFrom(
it->second);
}
}
SetDeviceStatusReported();
break;
}
}
}
// Process CpuResult.
const auto& cpu_result = probe_result->cpu_result;
if (!cpu_result.is_null()) {
switch (cpu_result->which()) {
case cros_healthd::CpuResult::Tag::kError: {
LOG(ERROR) << "cros_healthd: Error getting CPU info: "
<< cpu_result->get_error()->msg;
break;
}
case cros_healthd::CpuResult::Tag::kCpuInfo: {
const auto& cpu_info = cpu_result->get_cpu_info();
if (cpu_info.is_null()) {
LOG(ERROR) << "Null CpuInfo from cros_healthd";
break;
}
long clock_ticks_per_second = sysconf(_SC_CLK_TCK);
if (clock_ticks_per_second == -1 || clock_ticks_per_second == 0) {
LOG(ERROR) << "Failed getting number of clock ticks per second";
break;
}
em::GlobalCpuInfo* const global_cpu_info_out =
response_params_.device_status->mutable_global_cpu_info();
global_cpu_info_out->set_num_total_threads(
cpu_info->num_total_threads);
for (const auto& physical_cpu : cpu_info->physical_cpus) {
if (physical_cpu.is_null()) {
continue;
}
em::CpuInfo* const cpu_info_out =
response_params_.device_status->add_cpu_info();
if (physical_cpu->model_name) {
cpu_info_out->set_model_name(physical_cpu->model_name.value());
}
cpu_info_out->set_architecture(
static_cast<em::CpuInfo::Architecture>(cpu_info->architecture));
for (const auto& logical_cpu : physical_cpu->logical_cpus) {
if (logical_cpu.is_null()) {
continue;
}
em::LogicalCpuInfo* const logical_cpu_info_out =
cpu_info_out->add_logical_cpus();
logical_cpu_info_out->set_scaling_max_frequency_khz(
logical_cpu->scaling_max_frequency_khz);
logical_cpu_info_out->set_scaling_current_frequency_khz(
logical_cpu->scaling_current_frequency_khz);
logical_cpu_info_out->set_idle_time_seconds(
logical_cpu->idle_time_user_hz / clock_ticks_per_second);
if (!cpu_info_out->has_max_clock_speed_khz()) {
cpu_info_out->set_max_clock_speed_khz(
logical_cpu->max_clock_speed_khz);
}
for (const auto& c_state : logical_cpu->c_states) {
if (c_state.is_null()) {
continue;
}
em::CpuCStateInfo* const c_state_info_out =
logical_cpu_info_out->add_c_states();
c_state_info_out->set_name(c_state->name);
c_state_info_out->set_time_in_state_since_last_boot_us(
c_state->time_in_state_since_last_boot_us);
}
}
}
SetDeviceStatusReported();
break;
}
}
}
// Process TimezoneResult.
const auto& timezone_result = probe_result->timezone_result;
if (!timezone_result.is_null()) {
switch (timezone_result->which()) {
case cros_healthd::TimezoneResult::Tag::kError: {
LOG(ERROR) << "cros_healthd: Error getting timezone info: "
<< timezone_result->get_error()->msg;
break;
}
case cros_healthd::TimezoneResult::Tag::kTimezoneInfo: {
const auto& timezone_info = timezone_result->get_timezone_info();
em::TimezoneInfo* const timezone_info_out =
response_params_.device_status->mutable_timezone_info();
timezone_info_out->set_posix(timezone_info->posix);
timezone_info_out->set_region(timezone_info->region);
SetDeviceStatusReported();
break;
}
}
}
// Process MemoryResult.
const auto& memory_result = probe_result->memory_result;
if (!memory_result.is_null()) {
switch (memory_result->which()) {
case cros_healthd::MemoryResult::Tag::kError: {
LOG(ERROR) << "cros_healthd: Error getting memory info: "
<< memory_result->get_error()->msg;
break;
}
case cros_healthd::MemoryResult::Tag::kMemoryInfo: {
const auto& memory_info = memory_result->get_memory_info();
em::MemoryInfo* const memory_info_out =
response_params_.device_status->mutable_memory_info();
memory_info_out->set_total_memory_kib(memory_info->total_memory_kib);
memory_info_out->set_free_memory_kib(memory_info->free_memory_kib);
memory_info_out->set_available_memory_kib(
memory_info->available_memory_kib);
memory_info_out->set_page_faults_since_last_boot(
memory_info->page_faults_since_last_boot);
SetDeviceStatusReported();
break;
}
}
}
// Process BacklightResult.
const auto& backlight_result = probe_result->backlight_result;
if (!backlight_result.is_null()) {
switch (backlight_result->which()) {
case cros_healthd::BacklightResult::Tag::kError: {
LOG(ERROR) << "cros_healthd: Error getting backlight info: "
<< backlight_result->get_error()->msg;
break;
}
case cros_healthd::BacklightResult::Tag::kBacklightInfo: {
for (const auto& backlight : backlight_result->get_backlight_info()) {
em::BacklightInfo* const backlight_info_out =
response_params_.device_status->add_backlight_info();
backlight_info_out->set_path(backlight->path);
backlight_info_out->set_max_brightness(backlight->max_brightness);
backlight_info_out->set_brightness(backlight->brightness);
}
if (response_params_.device_status->backlight_info_size() > 0) {
SetDeviceStatusReported();
}
break;
}
}
}
// Process FanResult.
const auto& fan_result = probe_result->fan_result;
if (!fan_result.is_null()) {
switch (fan_result->which()) {
case cros_healthd::FanResult::Tag::kError: {
LOG(ERROR) << "cros_healthd: Error getting fan info: "
<< fan_result->get_error()->msg;
break;
}
case cros_healthd::FanResult::Tag::kFanInfo: {
for (const auto& fan : fan_result->get_fan_info()) {
em::FanInfo* const fan_info_out =
response_params_.device_status->add_fan_info();
fan_info_out->set_speed_rpm(fan->speed_rpm);
}
if (response_params_.device_status->fan_info_size() > 0) {
SetDeviceStatusReported();
}
break;
}
}
}
// Process Bluetooth result.
const auto& bluetooth_result = probe_result->bluetooth_result;
if (!bluetooth_result.is_null()) {
switch (bluetooth_result->which()) {
case cros_healthd::BluetoothResult::Tag::kError: {
LOG(ERROR) << "cros_healthd: Error getting Bluetooth info: "
<< bluetooth_result->get_error()->msg;
break;
}
case cros_healthd::BluetoothResult::Tag::kBluetoothAdapterInfo: {
for (const auto& adapter :
bluetooth_result->get_bluetooth_adapter_info()) {
em::BluetoothAdapterInfo* const adapter_info_out =
response_params_.device_status->add_bluetooth_adapter_info();
adapter_info_out->set_name(adapter->name);
adapter_info_out->set_address(adapter->address);
adapter_info_out->set_powered(adapter->powered);
adapter_info_out->set_num_connected_devices(
adapter->num_connected_devices);
}
if (response_params_.device_status->bluetooth_adapter_info_size() >
0) {
SetDeviceStatusReported();
}
break;
}
}
}
// Process SystemResult.
const auto& system_result = probe_result->system_result;
if (!system_result.is_null()) {
switch (system_result->which()) {
case cros_healthd::SystemResult::Tag::kError: {
LOG(ERROR) << "cros_healthd: Error getting system info: "
<< system_result->get_error()->msg;
break;
}
// The System Info tag is always called to get vendor, product name,
// and product version. Because of this, make sure to wrap additional
// data collection behind a policy, similar to bios version and os
// info below.
case cros_healthd::SystemResult::Tag::kSystemInfo: {
const auto& system_info = system_result->get_system_info();
if (report_vpd_info || report_system_info) {
em::SystemStatus* const system_status_out =
response_params_.device_status->mutable_system_status();
if (report_vpd_info && !system_info->vpd_info.is_null()) {
const auto& vpd_info = system_info->vpd_info;
if (vpd_info->activate_date.has_value()) {
system_status_out->set_first_power_date(
vpd_info->activate_date.value());
SetDeviceStatusReported();
}
if (vpd_info->mfg_date.has_value()) {
system_status_out->set_manufacture_date(
vpd_info->mfg_date.value());
SetDeviceStatusReported();
}
if (vpd_info->sku_number.has_value()) {
system_status_out->set_vpd_sku_number(
vpd_info->sku_number.value());
SetDeviceStatusReported();
}
if (vpd_info->serial_number.has_value()) {
system_status_out->set_vpd_serial_number(
vpd_info->serial_number.value());
SetDeviceStatusReported();
}
}
if (report_system_info) {
if (!system_info->dmi_info.is_null()) {
const auto& dmi_info = system_info->dmi_info;
if (dmi_info->bios_version.has_value()) {
system_status_out->set_bios_version(
dmi_info->bios_version.value());
SetDeviceStatusReported();
}
if (dmi_info->board_name.has_value()) {
system_status_out->set_board_name(
dmi_info->board_name.value());
SetDeviceStatusReported();
}
if (dmi_info->board_version.has_value()) {
system_status_out->set_board_version(
dmi_info->board_version.value());
SetDeviceStatusReported();
}
if (dmi_info->chassis_type) {
system_status_out->set_chassis_type(
dmi_info->chassis_type->value);
SetDeviceStatusReported();
}
}
if (!system_info->os_info.is_null()) {
const auto& os_info = system_info->os_info;
if (os_info->marketing_name.has_value()) {
system_status_out->set_marketing_name(
os_info->marketing_name.value());
}
system_status_out->set_product_name(os_info->code_name);
SetDeviceStatusReported();
}
}
}
em::SmbiosInfo* const smbios_info_out =
response_params_.device_status->mutable_smbios_info();
if (!system_info->dmi_info.is_null()) {
const auto& dmi_info = system_info->dmi_info;
// The vendor, product name, and product version should always be
// reported.
if (dmi_info->sys_vendor.has_value()) {
smbios_info_out->set_sys_vendor(dmi_info->sys_vendor.value());
}
if (dmi_info->product_name.has_value()) {
smbios_info_out->set_product_name(dmi_info->product_name.value());
}
if (dmi_info->product_version.has_value()) {
smbios_info_out->set_product_version(
dmi_info->product_version.value());
}
if (report_system_info && dmi_info->bios_version.has_value()) {
smbios_info_out->set_bios_version(dmi_info->bios_version.value());
}
SetDeviceStatusReported();
}
if (report_system_info && !system_info->os_info.is_null()) {
em::BootInfo* const boot_info_out =
response_params_.device_status->mutable_boot_info();
const auto& os_info = system_info->os_info;
boot_info_out->set_boot_method(
static_cast<em::BootInfo::BootMethod>(os_info->boot_mode));
SetDeviceStatusReported();
}
break;
}
}
}
// Process StatefulPartition result.
const auto& stateful_partition_result =
probe_result->stateful_partition_result;
if (!stateful_partition_result.is_null() && report_storage_status) {
switch (stateful_partition_result->which()) {
case cros_healthd::StatefulPartitionResult::Tag::kError: {
LOG(ERROR) << "cros_healthd: Error getting Stateful Partition info: "
<< stateful_partition_result->get_error()->msg;
break;
}
case cros_healthd::StatefulPartitionResult::Tag::kPartitionInfo: {
const auto& partition_info =
stateful_partition_result->get_partition_info();
if (partition_info.is_null()) {
LOG(ERROR) << "Null PartitionInfo from cros_healthd";
break;
}
em::StatefulPartitionInfo* partition_info_out =
response_params_.device_status->mutable_stateful_partition_info();
partition_info_out->set_available_space(
partition_info->available_space);
partition_info_out->set_total_space(partition_info->total_space);
partition_info_out->set_filesystem(partition_info->filesystem);
partition_info_out->set_mount_source(partition_info->mount_source);
break;
}
}
}
// Process Tpm Result.
const auto& tpm_result = probe_result->tpm_result;
if (!tpm_result.is_null() && report_version_info) {
switch (tpm_result->which()) {
case cros_healthd::TpmResult::Tag::kError: {
LOG(ERROR) << "cros_healthd: Error getting Tpm info: "
<< tpm_result->get_error()->msg;
break;
}
case cros_healthd::TpmResult::Tag::kTpmInfo: {
const auto& tpm_info = tpm_result->get_tpm_info();
if (tpm_info.is_null()) {
LOG(ERROR) << "Null TpmInfo from cros_healthd";
break;
}
em::TpmVersionInfo* tpm_version_info_out =
response_params_.device_status->mutable_tpm_version_info();
if (tpm_info->did_vid.has_value()) {
tpm_version_info_out->set_did_vid(tpm_info->did_vid.value());
}
break;
}
}
}
// Process Bus Result.
const auto& bus_result = probe_result->bus_result;
if (!bus_result.is_null() && report_network_configuration) {
switch (bus_result->which()) {
case cros_healthd::BusResult::Tag::kError: {
LOG(ERROR) << "cros_healthd: Error getting Bus info: "
<< bus_result->get_error()->msg;
break;
}
case cros_healthd::BusResult::Tag::kBusDevices: {
for (const auto& bus_device : bus_result->get_bus_devices()) {
switch (bus_device->device_class) {
case cros_healthd::BusDeviceClass::kEthernetController:
case cros_healthd::BusDeviceClass::kWirelessController:
case cros_healthd::BusDeviceClass::kBluetoothAdapter: {
em::NetworkAdapterInfo* network_adapter_info_out =
response_params_.device_status->add_network_adapter_info();
network_adapter_info_out->set_vendor_name(
bus_device->vendor_name);
network_adapter_info_out->set_device_name(
bus_device->product_name);
network_adapter_info_out->set_device_class(
static_cast<em::BusDeviceClass>(bus_device->device_class));
if (bus_device->bus_info->is_pci_bus_info()) {
network_adapter_info_out->set_bus_type(em::PCI_BUS);
network_adapter_info_out->set_vendor_id(
bus_device->bus_info->get_pci_bus_info()->vendor_id);
network_adapter_info_out->set_device_id(
bus_device->bus_info->get_pci_bus_info()->device_id);
if (bus_device->bus_info->get_pci_bus_info()
->driver.has_value()) {
network_adapter_info_out->add_driver(
bus_device->bus_info->get_pci_bus_info()
->driver.value());
}
}
if (bus_device->bus_info->is_usb_bus_info()) {
network_adapter_info_out->set_bus_type(em::USB_BUS);
network_adapter_info_out->set_vendor_id(
bus_device->bus_info->get_usb_bus_info()->vendor_id);
network_adapter_info_out->set_device_id(
bus_device->bus_info->get_usb_bus_info()->product_id);
for (const auto& usb_interface :
bus_device->bus_info->get_usb_bus_info()->interfaces) {
if (usb_interface->driver.has_value()) {
network_adapter_info_out->add_driver(
usb_interface->driver.value());
}
}
}
break;
}
default:
break;
}
}
if (response_params_.device_status->network_adapter_info_size() > 0) {
SetDeviceStatusReported();
}
break;
}
}
}
}
void OnEMMCLifetimeReceived(const em::DiskLifetimeEstimation& est) {
if (!est.has_slc() && !est.has_mlc()) {
return;
}
em::DiskLifetimeEstimation* state =
response_params_.device_status->mutable_storage_status()
->mutable_lifetime_estimation();
state->CopyFrom(est);
SetDeviceStatusReported();
}
void OnStatefulPartitionInfoReceived(const em::StatefulPartitionInfo& hdsi) {
if (!hdsi.has_available_space() && !hdsi.has_total_space()) {
return;
}
em::StatefulPartitionInfo* stateful_partition_info =
response_params_.device_status->mutable_stateful_partition_info();
DCHECK_GE(hdsi.total_space(), hdsi.available_space());
stateful_partition_info->CopyFrom(hdsi);
SetDeviceStatusReported();
}
void OnGetRootDeviceSize(std::optional<int64_t> root_device_size) {
if (!root_device_size.has_value()) {
DVLOG(1) << "Could not fetch root device size from spaced.";
return;
}
if (root_device_size.value() <= 0) {
DVLOG(1) << "Invalid root device size " << root_device_size.value();
return;
}
response_params_.device_status->set_root_device_total_storage_bytes(
ash::settings::RoundByteSize(root_device_size.value()));
SetDeviceStatusReported();
}
void OnGraphicsStatusReceived(const em::GraphicsStatus& gs) {
*response_params_.device_status->mutable_graphics_status() = gs;
SetDeviceStatusReported();
}
void OnCrashReportInfoReceived(
const std::vector<em::CrashReportInfo>& crash_report_infos) {
DCHECK_EQ(response_params_.device_status->crash_report_infos_size(), 0);
for (const em::CrashReportInfo& info : crash_report_infos) {
*response_params_.device_status->add_crash_report_infos() = info;
}
if (response_params_.device_status->crash_report_infos_size() > 0) {
SetDeviceStatusReported();
}
}
SEQUENCE_CHECKER(sequence_checker_);
// Every time the device status is to be reported, the shared object,
// DeviceStatusCollector` is created with this being false. asynchronous
// status collectors then set this variable to true if any data is collected.
// Then, When the `DeviceStatusCollector` object is released, A response is
// generated only if this is true.
bool device_status_reported_ = false;
};
SampledData::SampledData() = default;
SampledData::~SampledData() = default;
DeviceStatusCollector::DeviceStatusCollector(
PrefService* pref_service,
ReportingUserTracker* reporting_user_tracker,
ash::system::StatisticsProvider* provider,
ManagedSessionService* managed_session_service,
const VolumeInfoFetcher& volume_info_fetcher,
const CPUStatisticsFetcher& cpu_statistics_fetcher,
const CPUTempFetcher& cpu_temp_fetcher,
const AndroidStatusFetcher& android_status_fetcher,
const TpmStatusFetcher& tpm_status_fetcher,
const EMMCLifetimeFetcher& emmc_lifetime_fetcher,
const StatefulPartitionInfoFetcher& stateful_partition_info_fetcher,
const GraphicsStatusFetcher& graphics_status_fetcher,
const CrashReportInfoFetcher& crash_report_info_fetcher,
base::Clock* clock)
: StatusCollector(provider, ash::CrosSettings::Get(), clock),
pref_service_(pref_service),
reporting_user_tracker_(reporting_user_tracker),
firmware_fetch_error_(kFirmwareNotInitialized),
volume_info_fetcher_(volume_info_fetcher),
cpu_statistics_fetcher_(cpu_statistics_fetcher),
cpu_temp_fetcher_(cpu_temp_fetcher),
android_status_fetcher_(android_status_fetcher),
tpm_status_fetcher_(tpm_status_fetcher),
emmc_lifetime_fetcher_(emmc_lifetime_fetcher),
stateful_partition_info_fetcher_(stateful_partition_info_fetcher),
graphics_status_fetcher_(graphics_status_fetcher),
crash_report_info_fetcher_(crash_report_info_fetcher),
power_manager_(chromeos::PowerManagerClient::Get()),
app_info_generator_(managed_session_service,
kMaxStoredPastActivityInterval,
clock_) {
// protected fields of `StatusCollector`.
max_stored_past_activity_interval_ = kMaxStoredPastActivityInterval;
max_stored_future_activity_interval_ = kMaxStoredFutureActivityInterval;
// Get the task runner of the current thread, so we can queue status responses
// on this thread.
CHECK(base::SequencedTaskRunner::HasCurrentDefault());
task_runner_ = base::SequencedTaskRunner::GetCurrentDefault();
if (volume_info_fetcher_.is_null()) {
volume_info_fetcher_ = base::BindRepeating(&GetVolumeInfo);
}
if (cpu_statistics_fetcher_.is_null()) {
cpu_statistics_fetcher_ = base::BindRepeating(&ReadCPUStatistics);
}
if (cpu_temp_fetcher_.is_null()) {
cpu_temp_fetcher_ = base::BindRepeating(&ReadCPUTempInfo);
}
if (android_status_fetcher_.is_null()) {
android_status_fetcher_ = base::BindRepeating(&ReadAndroidStatus);
}
if (tpm_status_fetcher_.is_null()) {
tpm_status_fetcher_ = base::BindRepeating(&ReadTpmStatus);
}
if (emmc_lifetime_fetcher_.is_null()) {
emmc_lifetime_fetcher_ = base::BindRepeating(&ReadDiskLifeTimeEstimation);
}
if (stateful_partition_info_fetcher_.is_null()) {
stateful_partition_info_fetcher_ =
base::BindRepeating(&ReadStatefulPartitionInfo);
}
cros_healthd_data_fetcher_ = base::BindRepeating(
&DeviceStatusCollector::FetchCrosHealthdData, weak_factory_.GetWeakPtr());
if (graphics_status_fetcher_.is_null()) {
graphics_status_fetcher_ = base::BindRepeating(&FetchGraphicsStatus);
}
if (crash_report_info_fetcher_.is_null()) {
crash_report_info_fetcher_ = base::BindRepeating(&ReadCrashReportInfo);
}
idle_poll_timer_.Start(FROM_HERE, kIdlePollInterval, this,
&DeviceStatusCollector::CheckIdleState);
cpu_usage_sampling_timer_.Start(
FROM_HERE, base::Seconds(kResourceUsageSampleIntervalSeconds), this,
&DeviceStatusCollector::SampleCpuUsage);
memory_usage_sampling_timer_.Start(
FROM_HERE, base::Seconds(kResourceUsageSampleIntervalSeconds), this,
&DeviceStatusCollector::SampleMemoryUsage);
// Watch for changes to the individual policies that control what the status
// reports contain.
auto callback = base::BindRepeating(
&DeviceStatusCollector::UpdateReportingSettings, base::Unretained(this));
version_info_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportDeviceVersionInfo, callback);
activity_times_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportDeviceActivityTimes, callback);
boot_mode_subscription_ =
cros_settings_->AddSettingsObserver(ash::kReportDeviceBootMode, callback);
audio_status_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportDeviceAudioStatus, callback);
network_configuration_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportDeviceNetworkConfiguration, callback);
network_status_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportDeviceNetworkStatus, callback);
users_subscription_ =
cros_settings_->AddSettingsObserver(ash::kReportDeviceUsers, callback);
session_status_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportDeviceSessionStatus, callback);
os_update_status_subscription_ =
cros_settings_->AddSettingsObserver(ash::kReportOsUpdateStatus, callback);
running_kiosk_app_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportRunningKioskApp, callback);
power_status_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportDevicePowerStatus, callback);
security_status_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportDeviceSecurityStatus, callback);
storage_status_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportDeviceStorageStatus, callback);
board_status_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportDeviceBoardStatus, callback);
cpu_info_subscription_ =
cros_settings_->AddSettingsObserver(ash::kReportDeviceCpuInfo, callback);
graphics_status_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportDeviceGraphicsStatus, callback);
timezone_info_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportDeviceTimezoneInfo, callback);
memory_info_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportDeviceMemoryInfo, callback);
backlight_info_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportDeviceBacklightInfo, callback);
crash_report_info_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportDeviceCrashReportInfo, callback);
bluetooth_info_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportDeviceBluetoothInfo, callback);
fan_info_subscription_ =
cros_settings_->AddSettingsObserver(ash::kReportDeviceFanInfo, callback);
vpd_info_subscription_ =
cros_settings_->AddSettingsObserver(ash::kReportDeviceVpdInfo, callback);
app_info_subscription_ =
cros_settings_->AddSettingsObserver(ash::kReportDeviceAppInfo, callback);
system_info_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportDeviceSystemInfo, callback);
stats_reporting_pref_subscription_ =
cros_settings_->AddSettingsObserver(ash::kStatsReportingPref, callback);
power_manager_observation_.Observe(power_manager_.get());
// Fetch the current values of the policies.
UpdateReportingSettings();
// Get the OS, firmware, and TPM version info.
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(&chromeos::version_loader::GetVersion,
chromeos::version_loader::VERSION_FULL),
base::BindOnce(&DeviceStatusCollector::OnOSVersion,
weak_factory_.GetWeakPtr()));
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(&ReadFirmwareVersion),
base::BindOnce(&DeviceStatusCollector::OnOSFirmware,
weak_factory_.GetWeakPtr()));
chromeos::TpmManagerClient::Get()->GetVersionInfo(
::tpm_manager::GetVersionInfoRequest(),
base::BindOnce(&DeviceStatusCollector::OnGetTpmVersion,
weak_factory_.GetWeakPtr()));
pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
pref_change_registrar_->Init(pref_service_);
pref_change_registrar_->Add(
prefs::kReportingUsers,
base::BindRepeating(&DeviceStatusCollector::ReportingUsersChanged,
weak_factory_.GetWeakPtr()));
DCHECK(pref_service_->GetInitializationStatus() !=
PrefService::INITIALIZATION_STATUS_WAITING);
activity_storage_ = std::make_unique<EnterpriseActivityStorage>(
pref_service_, prefs::kDeviceActivityTimes);
}
DeviceStatusCollector::DeviceStatusCollector(
PrefService* pref_service,
ReportingUserTracker* reporting_user_tracker,
ash::system::StatisticsProvider* provider,
ManagedSessionService* managed_session_service)
: DeviceStatusCollector(
pref_service,
reporting_user_tracker,
provider,
managed_session_service,
DeviceStatusCollector::VolumeInfoFetcher(),
DeviceStatusCollector::CPUStatisticsFetcher(),
DeviceStatusCollector::CPUTempFetcher(),
StatusCollector::AndroidStatusFetcher(),
DeviceStatusCollector::TpmStatusFetcher(),
DeviceStatusCollector::EMMCLifetimeFetcher(),
DeviceStatusCollector::StatefulPartitionInfoFetcher(),
DeviceStatusCollector::GraphicsStatusFetcher(),
DeviceStatusCollector::CrashReportInfoFetcher()) {}
DeviceStatusCollector::~DeviceStatusCollector() = default;
// static
constexpr base::TimeDelta DeviceStatusCollector::kIdlePollInterval;
// static
void DeviceStatusCollector::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterDictionaryPref(prefs::kDeviceActivityTimes);
}
void DeviceStatusCollector::CheckIdleState() {
ProcessIdleState(ui::CalculateIdleState(kIdleStateThresholdSeconds));
}
void DeviceStatusCollector::UpdateReportingSettings() {
// Attempt to fetch the current value of the reporting settings.
// If trusted values are not available, register this function to be called
// back when they are available.
if (ash::CrosSettingsProvider::TRUSTED !=
cros_settings_->PrepareTrustedValues(
base::BindOnce(&DeviceStatusCollector::UpdateReportingSettings,
weak_factory_.GetWeakPtr()))) {
return;
}
// if either of these are set from false to true, gather an initial sample.
bool already_reporting_cpu_info = report_cpu_info_;
bool already_reporting_memory_info = report_memory_info_;
// Keep the default values in sync with DeviceReportingProto in
// components/policy/proto/chrome_device_policy.proto.
// TODO(b/195030842): Refactor how reporting policy variables are set.
if (!cros_settings_->GetBoolean(ash::kReportDeviceVersionInfo,
&report_version_info_)) {
report_version_info_ = true;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceActivityTimes,
&report_activity_times_)) {
report_activity_times_ = true;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceAudioStatus,
&report_audio_status_)) {
report_audio_status_ = true;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceBootMode,
&report_boot_mode_)) {
report_boot_mode_ = true;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceSessionStatus,
&report_kiosk_session_status_)) {
report_kiosk_session_status_ = true;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceNetworkConfiguration,
&report_network_configuration_)) {
report_network_configuration_ = true;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceNetworkStatus,
&report_network_status_)) {
report_network_status_ = true;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceUsers, &report_users_)) {
report_users_ = true;
}
if (!cros_settings_->GetBoolean(ash::kReportDevicePowerStatus,
&report_power_status_)) {
report_power_status_ = false;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceStorageStatus,
&report_storage_status_)) {
report_storage_status_ = false;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceBoardStatus,
&report_board_status_)) {
report_board_status_ = false;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceCpuInfo,
&report_cpu_info_)) {
report_cpu_info_ = false;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceGraphicsStatus,
&report_graphics_status_)) {
report_graphics_status_ = false;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceTimezoneInfo,
&report_timezone_info_)) {
report_timezone_info_ = false;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceMemoryInfo,
&report_memory_info_)) {
report_memory_info_ = false;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceBacklightInfo,
&report_backlight_info_)) {
report_backlight_info_ = false;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceCrashReportInfo,
&report_crash_report_info_)) {
report_crash_report_info_ = false;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceBluetoothInfo,
&report_bluetooth_info_)) {
report_bluetooth_info_ = false;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceSystemInfo,
&report_system_info_)) {
report_system_info_ = false;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceFanInfo,
&report_fan_info_)) {
report_fan_info_ = false;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceVpdInfo,
&report_vpd_info_)) {
report_vpd_info_ = false;
}
report_app_info_ = false;
if (!cros_settings_->GetBoolean(ash::kReportDeviceAppInfo,
&report_app_info_)) {
report_app_info_ = false;
}
app_info_generator_.OnReportingChanged(report_app_info_);
if (!cros_settings_->GetBoolean(ash::kStatsReportingPref,
&stat_reporting_pref_)) {
stat_reporting_pref_ = false;
}
// Os update status and running kiosk app reporting are disabled by default.
if (!cros_settings_->GetBoolean(ash::kReportOsUpdateStatus,
&report_os_update_status_)) {
report_os_update_status_ = false;
}
if (!cros_settings_->GetBoolean(ash::kReportRunningKioskApp,
&report_running_kiosk_app_)) {
report_running_kiosk_app_ = false;
}
if (!cros_settings_->GetBoolean(ash::kReportDeviceSecurityStatus,
&report_security_status_)) {
report_security_status_ = false;
}
// Take initial samples.
if (!already_reporting_cpu_info && report_cpu_info_) {
SampleCpuUsage();
}
if (!already_reporting_memory_info && report_memory_info_) {
SampleMemoryUsage();
}
// Clear caches for any info no longer being collected.
if (!report_memory_info_) {
ClearCachedMemoryUsage();
}
if (!report_cpu_info_) {
ClearCachedCpuUsage();
}
}
void DeviceStatusCollector::ClearCachedMemoryUsage() {
memory_usage_.clear();
}
void DeviceStatusCollector::ClearCachedCpuUsage() {
cpu_usage_.clear();
last_cpu_active_ = 0;
last_cpu_idle_ = 0;
}
void DeviceStatusCollector::ProcessIdleState(ui::IdleState state) {
// Do nothing if device activity reporting is disabled.
if (!report_activity_times_) {
return;
}
base::Time now = clock_->Now();
// For kiosk session we report total uptime instead of active time.
if (state == ui::IDLE_STATE_ACTIVE || IsKioskSession()) {
std::string user_email = GetUserForActivityReporting();
// If it's been too long since the last report, or if the activity is
// negative (which can happen when the clock changes), assume a single
// interval of activity.
base::TimeDelta active_seconds = now - last_idle_check_;
base::Time start;
if (active_seconds < base::Seconds(0) ||
active_seconds >= 2 * kIdlePollInterval || last_idle_check_.is_null()) {
start = now - kIdlePollInterval;
} else {
start = last_idle_check_;
}
activity_storage_->AddActivityPeriod(start, now, user_email);
activity_storage_->PruneActivityPeriods(
now, max_stored_past_activity_interval_,
max_stored_future_activity_interval_);
}
last_idle_check_ = now;
}
void DeviceStatusCollector::PowerChanged(
const power_manager::PowerSupplyProperties& prop) {
if (!power_status_callback_.is_null()) {
std::move(power_status_callback_).Run(prop);
}
}
void DeviceStatusCollector::SampleMemoryUsage() {
// Results must be written in the creation thread since that's where they
// are read from in the Get*StatusAsync methods.
DCHECK(thread_checker_.CalledOnValidThread());
if (!report_memory_info_) {
return;
}
MemoryUsage usage = {base::SysInfo::AmountOfAvailablePhysicalMemory(),
base::Time::Now()};
memory_usage_.push_back(usage);
if (memory_usage_.size() > kMaxResourceUsageSamples) {
memory_usage_.pop_front();
}
}
void DeviceStatusCollector::SampleCpuUsage() {
// Results must be written in the creation thread since that's where they
// are read from in the Get*StatusAsync methods.
DCHECK(thread_checker_.CalledOnValidThread());
// If report cpu info has been disabled, do nothing here.
if (!report_cpu_info_) {
return;
}
// Call out to the blocking pool to sample CPU stats.
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(cpu_statistics_fetcher_),
base::BindOnce(&DeviceStatusCollector::ReceiveCPUStatistics,
weak_factory_.GetWeakPtr()));
}
void DeviceStatusCollector::ReceiveCPUStatistics(const std::string& stats) {
int cpu_usage_percent = 0;
if (stats.empty()) {
DLOG(WARNING) << "Unable to read CPU statistics";
} else {
// Parse the data from /proc/stat, whose format is defined at
// https://www.kernel.org/doc/Documentation/filesystems/proc.txt.
//
// The CPU usage values in /proc/stat are measured in the imprecise unit
// "jiffies", but we just care about the relative magnitude of "active" vs
// "idle" so the exact value of a jiffy is irrelevant.
//
// An example value for this line:
//
// cpu 123 456 789 012 345 678
//
// We only care about the first four numbers: user_time, nice_time,
// sys_time, and idle_time.
uint64_t user = 0, nice = 0, system = 0, idle = 0;
int vals = sscanf(stats.c_str(),
"cpu %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, &user,
&nice, &system, &idle);
DCHECK_EQ(4, vals);
// The values returned from /proc/stat are cumulative totals, so calculate
// the difference between the last sample and this one.
uint64_t active = user + nice + system;
uint64_t total = active + idle;
uint64_t last_total = last_cpu_active_ + last_cpu_idle_;
DCHECK_GE(active, last_cpu_active_);
DCHECK_GE(idle, last_cpu_idle_);
DCHECK_GE(total, last_total);
if ((total - last_total) > 0) {
cpu_usage_percent =
(100 * (active - last_cpu_active_)) / (total - last_total);
}
last_cpu_active_ = active;
last_cpu_idle_ = idle;
}
DCHECK_LE(cpu_usage_percent, 100);
// This timestamp is used in both ResourceUsage and SampledData for CPU
// termporary, which is expected to be same according to existing
// implementation.
const base::Time timestamp = base::Time::Now();
CpuUsage usage = {cpu_usage_percent, timestamp};
cpu_usage_.push_back(usage);
// If our cache of samples is full, throw out old samples to make room for new
// sample.
if (cpu_usage_.size() > kMaxResourceUsageSamples) {
cpu_usage_.pop_front();
}
std::unique_ptr<SampledData> sample = std::make_unique<SampledData>();
sample->timestamp = timestamp;
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(&InvokeCpuTempFetcher, cpu_temp_fetcher_),
base::BindOnce(&DeviceStatusCollector::ReceiveCPUTemperature,
weak_factory_.GetWeakPtr(), std::move(sample),
SamplingCallback()));
}
void DeviceStatusCollector::SampleProbeData(
std::unique_ptr<SampledData> sample,
SamplingProbeResultCallback callback,
ash::cros_healthd::mojom::TelemetryInfoPtr result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (result.is_null()) {
return;
}
const auto& battery_result = result->battery_result;
if (!battery_result.is_null()) {
if (battery_result->is_error()) {
LOG(ERROR) << "cros_healthd: Error getting battery info: "
<< battery_result->get_error()->msg;
} else if (!battery_result->get_battery_info().is_null()) {
const auto& battery = battery_result->get_battery_info();
em::BatterySample battery_sample;
battery_sample.set_timestamp(
sample->timestamp.InMillisecondsSinceUnixEpoch());
// Convert V to mV:
battery_sample.set_voltage(std::lround(battery->voltage_now * 1000));
// Convert Ah to mAh:
battery_sample.set_remaining_capacity(
std::lround(battery->charge_now * 1000));
// Convert A to mA:
battery_sample.set_current(std::lround(battery->current_now * 1000));
battery_sample.set_status(battery->status);
// Convert 0.1 Kelvin to Celsius:
if (battery->temperature) {
battery_sample.set_temperature(
(battery->temperature->value - kZeroCInDeciKelvin) / 10);
}
sample->battery_samples[battery->model_name] = battery_sample;
}
}
SamplingCallback completion_callback;
if (!callback.is_null()) {
completion_callback =
base::BindOnce(std::move(callback), std::move(result));
}
// PowerManagerClient::Observer::PowerChanged can be called as a result of
// power_manager_->RequestStatusUpdate() as well as for other reasons,
// so we store power_status_callback_ here instead of triggering
// SampleDischargeRate from PowerChanged().
DCHECK(power_status_callback_.is_null()); // Previous sampling is completed.
power_status_callback_ = base::BindOnce(
&DeviceStatusCollector::SampleDischargeRate, weak_factory_.GetWeakPtr(),
std::move(sample), std::move(completion_callback));
power_manager_->RequestStatusUpdate();
}
void DeviceStatusCollector::SampleDischargeRate(
std::unique_ptr<SampledData> sample,
SamplingCallback callback,
const power_manager::PowerSupplyProperties& prop) {
if (prop.has_battery_discharge_rate()) {
int discharge_rate_mW =
static_cast<int>(prop.battery_discharge_rate() * 1000);
for (auto it = sample->battery_samples.begin();
it != sample->battery_samples.end(); it++) {
it->second.set_discharge_rate(discharge_rate_mW);
}
}
if (prop.has_battery_percent() && prop.battery_percent() >= 0) {
int percent = static_cast<int>(prop.battery_percent());
for (auto it = sample->battery_samples.begin();
it != sample->battery_samples.end(); it++) {
it->second.set_charge_rate(percent);
}
}
AddDataSample(std::move(sample), std::move(callback));
}
void DeviceStatusCollector::ReceiveCPUTemperature(
std::unique_ptr<SampledData> sample,
SamplingCallback callback,
std::vector<em::CPUTempInfo> measurements) {
auto timestamp = sample->timestamp.InMillisecondsSinceUnixEpoch();
for (const auto& measurement : measurements) {
sample->cpu_samples[measurement.cpu_label()] = measurement;
sample->cpu_samples[measurement.cpu_label()].set_timestamp(timestamp);
}
AddDataSample(std::move(sample), std::move(callback));
}
void DeviceStatusCollector::AddDataSample(std::unique_ptr<SampledData> sample,
SamplingCallback callback) {
sampled_data_.push_back(std::move(sample));
// If our cache of samples is full, throw out old samples to make room for new
// sample.
if (sampled_data_.size() > kMaxResourceUsageSamples) {
sampled_data_.pop_front();
}
// We have two code paths that end here. One is regular sampling, that does
// not have final callback, and full report request, that would use callback
// to receive ProbeResponse.
if (!callback.is_null()) {
std::move(callback).Run();
}
}
void DeviceStatusCollector::FetchCrosHealthdData(
std::vector<ash::cros_healthd::mojom::ProbeCategoryEnum> probe_categories,
CrosHealthdDataReceiver callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
SamplingProbeResultCallback completion_callback;
completion_callback =
base::BindOnce(&DeviceStatusCollector::OnProbeDataFetched,
weak_factory_.GetWeakPtr(), std::move(callback));
auto sample = std::make_unique<SampledData>();
sample->timestamp = base::Time::Now();
ash::cros_healthd::ServiceConnection::GetInstance()
->GetProbeService()
->ProbeTelemetryInfo(
probe_categories,
base::BindOnce(&DeviceStatusCollector::SampleProbeData,
weak_factory_.GetWeakPtr(), std::move(sample),
std::move(completion_callback)));
}
void DeviceStatusCollector::OnProbeDataFetched(
CrosHealthdDataReceiver callback,
ash::cros_healthd::mojom::TelemetryInfoPtr reply) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::move(callback).Run(std::move(reply), sampled_data_);
}
void DeviceStatusCollector::ReportingUsersChanged() {
std::vector<std::string> reporting_users;
for (auto& value : pref_service_->GetList(prefs::kReportingUsers)) {
if (value.is_string()) {
reporting_users.push_back(value.GetString());
}
}
activity_storage_->FilterActivityPeriodsByUsers(reporting_users);
}
std::string DeviceStatusCollector::GetUserForActivityReporting() const {
// Primary user is used as unique identifier of a single session, even for
// multi-user sessions.
const user_manager::User* const primary_user =
user_manager::UserManager::Get()->GetPrimaryUser();
if (!primary_user) {
return std::string();
}
// Store affiliated user emails or the kiosk app id / guest session account
// emails. Those emails will be used to calculate the session type when
// constructing the ActiveTimePeriod protos sent as part of the report.
std::string primary_user_email = primary_user->GetAccountId().GetUserEmail();
if (primary_user->HasGaiaAccount() &&
!reporting_user_tracker_->ShouldReportUser(primary_user_email)) {
return std::string();
}
return primary_user_email;
}
bool DeviceStatusCollector::IncludeEmailsInActivityReports() const {
// Including the users' email addresses in enterprise reporting depends on the
// |kReportDeviceUsers| preference.
return report_users_;
}
bool DeviceStatusCollector::GetActivityTimes(
em::DeviceStatusReportRequest* status) {
// If user reporting is off, data should be aggregated per day.
// Signed-in user is reported in non-enterprise reporting.
activity_storage_->RemoveOverlappingActivityPeriods();
auto activity_times = activity_storage_->GetFilteredActivityPeriods(
!IncludeEmailsInActivityReports());
bool anything_reported = false;
for (const auto& activity_pair : activity_times) {
const auto& user_email = activity_pair.first;
const auto& activity_periods = activity_pair.second;
for (const auto& activity_period : activity_periods) {
// This is correct even when there are leap seconds, because when a leap
// second occurs, two consecutive seconds have the same timestamp.
int64_t end_timestamp =
activity_period.start_timestamp() + base::Time::kMillisecondsPerDay;
em::ActiveTimePeriod* active_period = status->add_active_periods();
em::TimePeriod* period = active_period->mutable_time_period();
period->set_start_timestamp(activity_period.start_timestamp());
period->set_end_timestamp(end_timestamp);
active_period->set_active_duration(activity_period.end_timestamp() -
activity_period.start_timestamp());
// Report user email and session_type only if users reporting is on.
if (!user_email.empty()) {
em::ActiveTimePeriod::SessionType session_type =
GetSessionType(user_email);
// Don't report the email address for MGS / Kiosk apps
if (session_type == em::ActiveTimePeriod::SESSION_AFFILIATED_USER) {
active_period->set_user_email(user_email);
}
if (session_type != em::ActiveTimePeriod::SESSION_UNKNOWN &&
base::FeatureList::IsEnabled(
features::kActivityReportingSessionType)) {
active_period->set_session_type(session_type);
}
}
if (last_reported_end_timestamp_ < end_timestamp) {
last_reported_end_timestamp_ = end_timestamp;
}
anything_reported = true;
}
}
return anything_reported;
}
bool DeviceStatusCollector::GetVersionInfo(
em::DeviceStatusReportRequest* status) {
status->set_os_version(os_version_);
status->set_browser_version(std::string(version_info::GetVersionNumber()));
status->set_is_lacros_primary_browser(
crosapi::browser_util::IsLacrosEnabled());
status->set_channel(ConvertToProtoChannel(chrome::GetChannel()));
// TODO(b/144081278): Remove when resolved.
// When firmware version is not fetched, report error instead.
status->set_firmware_version(
!firmware_version_.empty() ? firmware_version_ : firmware_fetch_error_);
em::TpmVersionInfo* const tpm_version_info =
status->mutable_tpm_version_info();
tpm_version_info->set_family(tpm_version_reply_.family());
tpm_version_info->set_spec_level(tpm_version_reply_.spec_level());
tpm_version_info->set_manufacturer(tpm_version_reply_.manufacturer());
tpm_version_info->set_tpm_model(tpm_version_reply_.tpm_model());
tpm_version_info->set_firmware_version(tpm_version_reply_.firmware_version());
tpm_version_info->set_vendor_specific(tpm_version_reply_.vendor_specific());
tpm_version_info->set_gsc_version(
ConvertTpmGscVersion(tpm_version_reply_.gsc_version()));
return true;
}
bool DeviceStatusCollector::GetWriteProtectSwitch(
em::DeviceStatusReportRequest* status) {
const std::optional<std::string_view> firmware_write_protect =
statistics_provider_->GetMachineStatistic(
ash::system::kFirmwareWriteProtectCurrentKey);
if (!firmware_write_protect) {
return false;
}
if (firmware_write_protect ==
ash::system::kFirmwareWriteProtectCurrentValueOff) {
status->set_write_protect_switch(false);
} else if (firmware_write_protect ==
ash::system::kFirmwareWriteProtectCurrentValueOn) {
status->set_write_protect_switch(true);
} else {
return false;
}
return true;
}
bool DeviceStatusCollector::GetNetworkConfiguration(
em::DeviceStatusReportRequest* status) {
// Note: keep in sync with `::reporting::NetworkInfoSampler`
static const struct {
const char* type_string;
em::NetworkInterface::NetworkDeviceType type_constant;
} kDeviceTypeMap[] = {
{
shill::kTypeEthernet,
em::NetworkInterface::TYPE_ETHERNET,
},
{
shill::kTypeWifi,
em::NetworkInterface::TYPE_WIFI,
},
{
shill::kTypeCellular,
em::NetworkInterface::TYPE_CELLULAR,
},
};
ash::NetworkStateHandler::DeviceStateList device_list;
ash::NetworkStateHandler* network_state_handler =
ash::NetworkHandler::Get()->network_state_handler();
network_state_handler->GetDeviceList(&device_list);
bool anything_reported = false;
ash::NetworkStateHandler::DeviceStateList::const_iterator device;
for (device = device_list.begin(); device != device_list.end(); ++device) {
// Determine the type enum constant for |device|.
size_t type_idx = 0;
for (; type_idx < std::size(kDeviceTypeMap); ++type_idx) {
if ((*device)->type() == kDeviceTypeMap[type_idx].type_string) {
break;
}
}
// If the type isn't in |kDeviceTypeMap|, the interface is not relevant for
// reporting. This filters out VPN devices.
if (type_idx >= std::size(kDeviceTypeMap)) {
continue;
}
em::NetworkInterface* interface = status->add_network_interfaces();
interface->set_type(kDeviceTypeMap[type_idx].type_constant);
if (!(*device)->mac_address().empty()) {
interface->set_mac_address((*device)->mac_address());
}
if (!(*device)->meid().empty()) {
interface->set_meid((*device)->meid());
}
if (!(*device)->imei().empty()) {
interface->set_imei((*device)->imei());
}
if (!(*device)->mdn().empty()) {
interface->set_mdn((*device)->mdn());
}
if (!(*device)->iccid().empty()) {
interface->set_iccid((*device)->iccid());
}
if (!(*device)->path().empty()) {
interface->set_device_path((*device)->path());
}
// Report EIDs for cellular connections.
if ((*device)->type() == shill::kTypeCellular) {
std::vector<std::string> eids;
for (const auto& euicc_path :
ash::HermesManagerClient::Get()->GetAvailableEuiccs()) {
ash::HermesEuiccClient::Properties* properties =
ash::HermesEuiccClient::Get()->GetProperties(euicc_path);
interface->add_eids(properties->eid().value());
}
}
anything_reported = true;
}
return anything_reported;
}
bool DeviceStatusCollector::GetNetworkStatus(
em::DeviceStatusReportRequest* status) {
// Maps shill device connection status to proto enum constants.
static const struct {
const char* state_string;
em::NetworkState::ConnectionState state_constant;
} kConnectionStateMap[] = {
{shill::kStateIdle, em::NetworkState::IDLE},
{shill::kStateAssociation, em::NetworkState::ASSOCIATION},
{shill::kStateConfiguration, em::NetworkState::CONFIGURATION},
{shill::kStateReady, em::NetworkState::READY},
{shill::kStateNoConnectivity, em::NetworkState::PORTAL},
{shill::kStateRedirectFound, em::NetworkState::PORTAL},
{shill::kStatePortalSuspected, em::NetworkState::PORTAL},
{shill::kStateOnline, em::NetworkState::ONLINE},
{shill::kStateDisconnecting, em::NetworkState::DISCONNECT},
{shill::kStateFailure, em::NetworkState::FAILURE},
};
bool anything_reported = false;
ash::NetworkStateHandler* network_state_handler =
ash::NetworkHandler::Get()->network_state_handler();
user_manager::UserManager* user_manager = user_manager::UserManager::Get();
const user_manager::User* const primary_user = user_manager->GetPrimaryUser();
// Don't write network state for unaffiliated users or when no user is signed
// in.
if (!primary_user || !primary_user->IsAffiliated()) {
return anything_reported;
}
// Walk the various networks and store their state in the status report.
ash::NetworkStateHandler::NetworkStateList state_list;
network_state_handler->GetNetworkListByType(
ash::NetworkTypePattern::Default(),
true, // configured_only
false, // visible_only
0, // no limit to number of results
&state_list);
for (const ash::NetworkState* state : state_list) {
// Determine the connection state and signal strength for |state|.
em::NetworkState::ConnectionState connection_state_enum =
em::NetworkState::UNKNOWN;
const std::string connection_state_string(state->connection_state());
for (size_t i = 0; i < std::size(kConnectionStateMap); ++i) {
if (connection_state_string == kConnectionStateMap[i].state_string) {
connection_state_enum = kConnectionStateMap[i].state_constant;
break;
}
}
// Copy fields from NetworkState into the status report.
em::NetworkState* proto_state = status->add_network_states();
proto_state->set_connection_state(connection_state_enum);
anything_reported = true;
// Report signal strength for wifi connections.
if (state->type() == shill::kTypeWifi) {
// If shill has provided a signal strength, convert it to dBm and store it
// in the status report. A signal_strength() of 0 connotes "no signal"
// rather than "really weak signal", so we only report signal strength if
// it is non-zero.
if (state->signal_strength()) {
proto_state->set_signal_strength(
ConvertWifiSignalStrength(state->signal_strength()));
}
}
if (!state->device_path().empty()) {
proto_state->set_device_path(state->device_path());
}
std::string ip_address = state->GetIpAddress();
if (!ip_address.empty()) {
proto_state->set_ip_address(ip_address);
}
std::string gateway = state->GetGateway();
if (!gateway.empty()) {
proto_state->set_gateway(gateway);
}
}
return anything_reported;
}
bool DeviceStatusCollector::GetUsers(em::DeviceStatusReportRequest* status) {
const user_manager::UserList& users =
user_manager::UserManager::Get()->GetUsers();
bool anything_reported = false;
for (user_manager::User* user : users) {
// Only users with gaia accounts (regular) are reported.
if (!user->HasGaiaAccount()) {
continue;
}
em::DeviceUser* device_user = status->add_users();
if (reporting_user_tracker_->ShouldReportUser(
user->GetAccountId().GetUserEmail())) {
device_user->set_type(em::DeviceUser::USER_TYPE_MANAGED);
device_user->set_email(user->GetAccountId().GetUserEmail());
} else {
device_user->set_type(em::DeviceUser::USER_TYPE_UNMANAGED);
// Do not report the email address of unmanaged users.
}
anything_reported = true;
}
return anything_reported;
}
bool DeviceStatusCollector::GetMemoryInfo(
em::DeviceStatusReportRequest* status) {
status->clear_system_ram_free_infos();
status->set_system_ram_total(base::SysInfo::AmountOfPhysicalMemory());
for (const MemoryUsage& usage : memory_usage_) {
em::SystemFreeRamInfo* system_ram_free_info =
status->add_system_ram_free_infos();
system_ram_free_info->set_size_in_bytes(usage.bytes_of_ram_free);
system_ram_free_info->set_timestamp(
usage.timestamp.InMillisecondsSinceUnixEpoch());
}
return true;
}
bool DeviceStatusCollector::GetCPUInfo(em::DeviceStatusReportRequest* status) {
status->clear_cpu_utilization_infos();
for (const CpuUsage& usage : cpu_usage_) {
em::CpuUtilizationInfo* cpu_utilization_info =
status->add_cpu_utilization_infos();
cpu_utilization_info->set_cpu_utilization_pct(usage.cpu_usage_percent);
cpu_utilization_info->set_timestamp(
usage.timestamp.InMillisecondsSinceUnixEpoch());
}
return true;
}
bool DeviceStatusCollector::GetAudioStatus(
em::DeviceStatusReportRequest* status) {
ash::CrasAudioHandler* audio_handler = ash::CrasAudioHandler::Get();
status->set_sound_volume(audio_handler->GetOutputVolumePercent());
return true;
}
bool DeviceStatusCollector::GetOsUpdateStatus(
em::DeviceStatusReportRequest* status) {
const base::Version platform_version(GetPlatformVersion());
if (!platform_version.IsValid()) {
return false;
}
std::string required_platform_version_string;
// Can be uninitialized in tests.
if (ash::KioskChromeAppManager::IsInitialized()) {
required_platform_version_string =
ash::KioskChromeAppManager::Get()
->GetAutoLaunchAppRequiredPlatformVersion();
}
em::OsUpdateStatus* os_update_status = status->mutable_os_update_status();
const update_engine::StatusResult update_engine_status =
ash::UpdateEngineClient::Get()->GetLastStatus();
std::optional<base::Version> required_platform_version;
if (required_platform_version_string.empty()) {
// If this is non-Kiosk session, the OS is considered as up-to-date if the
// status of UpdateEngineClient is idle.
if (update_engine_status.current_operation() ==
update_engine::Operation::IDLE) {
required_platform_version = base::Version(platform_version);
}
} else {
// If this is Kiosk session, |required_platform_version| can be searched
// from the KioskAppClient instance.
required_platform_version = base::Version(required_platform_version_string);
os_update_status->set_new_required_platform_version(
required_platform_version->GetString());
}
// Get last reboot timestamp.
const base::Time last_reboot_timestamp =
base::Time::Now() - base::SysInfo::Uptime();
os_update_status->set_last_reboot_timestamp(
last_reboot_timestamp.InMillisecondsSinceUnixEpoch());
// Get last check timestamp.
// As the timestamp precision return from UpdateEngine is in seconds (see
// time_t). It should be converted to milliseconds before being reported.
const base::Time last_checked_timestamp =
base::Time::FromTimeT(update_engine_status.last_checked_time());
os_update_status->set_last_checked_timestamp(
last_checked_timestamp.InMillisecondsSinceUnixEpoch());
if (required_platform_version &&
platform_version == *required_platform_version) {
os_update_status->set_update_status(em::OsUpdateStatus::OS_UP_TO_DATE);
return true;
}
if (update_engine_status.current_operation() ==
update_engine::Operation::DOWNLOADING ||
update_engine_status.current_operation() ==
update_engine::Operation::VERIFYING ||
update_engine_status.current_operation() ==
update_engine::Operation::FINALIZING) {
os_update_status->set_update_status(
em::OsUpdateStatus::OS_IMAGE_DOWNLOAD_IN_PROGRESS);
os_update_status->set_new_platform_version(
update_engine_status.new_version());
} else if (update_engine_status.current_operation() ==
update_engine::Operation::UPDATED_NEED_REBOOT) {
os_update_status->set_update_status(
em::OsUpdateStatus::OS_UPDATE_NEED_REBOOT);
// Note the new_version could be a dummy "0.0.0.0" for some edge cases,
// e.g. update engine is somehow restarted without a reboot.
os_update_status->set_new_platform_version(
update_engine_status.new_version());
} else {
os_update_status->set_update_status(
em::OsUpdateStatus::OS_IMAGE_DOWNLOAD_NOT_STARTED);
}
return true;
}
bool DeviceStatusCollector::GetRunningKioskApp(
em::DeviceStatusReportRequest* status) {
// Must be on creation thread since some stats are written to in that thread
// and accessing them from another thread would lead to race conditions.
DCHECK(thread_checker_.CalledOnValidThread());
std::unique_ptr<const DeviceLocalAccount> account =
GetAutoLaunchedKioskSessionInfo();
// Only generate running kiosk app reports if we are in an auto-launched kiosk
// session.
if (!account) {
return false;
}
em::AppStatus* running_kiosk_app = status->mutable_running_kiosk_app();
switch (account->type) {
case DeviceLocalAccountType::kKioskApp: {
running_kiosk_app->set_app_id(account->kiosk_app_id);
const std::string app_version = GetAppVersion(account->kiosk_app_id);
if (app_version.empty()) {
DLOG(ERROR) << "Unable to get version for extension: "
<< account->kiosk_app_id;
} else {
running_kiosk_app->set_extension_version(app_version);
}
ash::KioskChromeAppManager::App app_info;
if (ash::KioskChromeAppManager::Get()->GetApp(account->kiosk_app_id,
&app_info)) {
running_kiosk_app->set_required_platform_version(
app_info.required_platform_version);
}
break;
}
case DeviceLocalAccountType::kWebKioskApp:
running_kiosk_app->set_app_id(account->web_kiosk_app_info.url());
break;
case DeviceLocalAccountType::kKioskIsolatedWebApp:
running_kiosk_app->set_app_id(account->kiosk_iwa_info.web_bundle_id());
break;
case DeviceLocalAccountType::kPublicSession:
case DeviceLocalAccountType::kSamlPublicSession:
NOTREACHED_IN_MIGRATION();
}
return true;
}
bool DeviceStatusCollector::GetDeviceBootMode(
em::DeviceStatusReportRequest* status) {
std::optional<std::string> boot_mode =
StatusCollector::GetBootMode(statistics_provider_);
if (boot_mode) {
status->set_boot_mode(*boot_mode);
return true;
}
return false;
}
bool DeviceStatusCollector::GetDemoModeDimensions(
em::DeviceStatusReportRequest* status) {
bool anything_reported = ash::DemoSession::IsDeviceInDemoMode();
if (anything_reported) {
*status->mutable_demo_mode_dimensions() =
ash::demo_mode::GetDemoModeDimensions();
}
return anything_reported;
}
void DeviceStatusCollector::GetStorageStatus(
scoped_refptr<DeviceStatusCollectorState> state) {
state->FetchStatefulPartitionInfo(stateful_partition_info_fetcher_);
state->SampleVolumeInfo(volume_info_fetcher_);
state->FetchEMMCLifeTime(emmc_lifetime_fetcher_);
state->FetchRootDeviceSize();
}
void DeviceStatusCollector::GetGraphicsStatus(
scoped_refptr<DeviceStatusCollectorState> state) {
// Fetch Graphics status on a background thread.
state->FetchGraphicsStatus(graphics_status_fetcher_);
}
void DeviceStatusCollector::GetCrashReportInfo(
scoped_refptr<DeviceStatusCollectorState> state) {
state->FetchCrashReportInfo(crash_report_info_fetcher_);
}
void DeviceStatusCollector::GetStatusAsync(StatusCollectorCallback response) {
last_requested_ = clock_->Now();
app_info_generator_.OnWillReport();
// Must be on creation thread since some stats are written to in that thread
// and accessing them from another thread would lead to race conditions.
DCHECK(thread_checker_.CalledOnValidThread());
// Some of the data we're collecting is gathered in background threads.
// This object keeps track of the state of each async request.
scoped_refptr<DeviceStatusCollectorState> state(
new DeviceStatusCollectorState(task_runner_, std::move(response)));
// Gather device status (might queue some async queries)
GetDeviceStatus(state);
// Gather session status (might queue some async queries)
GetSessionStatus(state);
// If there are no outstanding async queries, e.g. from FetchCrosHealthddata,
// the destructor of |state| calls |response|. If there are async queries, the
// queries hold references to |state|, so that |state| is only destroyed when
// the last async query has finished.
}
// GetDeviceStatus must make the call state->SetDeviceStatusReported() to send
// data to the server. Asynchronous calls to get metrics do this down their
// call stack, typically in OnXDataReceived.
void DeviceStatusCollector::GetDeviceStatus(
scoped_refptr<DeviceStatusCollectorState> state) {
using ::ash::cros_healthd::mojom::ProbeCategoryEnum;
em::DeviceStatusReportRequest* status =
state->response_params().device_status.get();
bool anything_reported = false;
std::vector<ProbeCategoryEnum> probe_categories;
// Always probe System to get device vendor, product name, and product
// version
probe_categories.push_back(ProbeCategoryEnum::kSystem);
if (report_timezone_info_) {
probe_categories.push_back(ProbeCategoryEnum::kTimezone);
}
if (report_backlight_info_) {
probe_categories.push_back(ProbeCategoryEnum::kBacklight);
}
if (report_bluetooth_info_) {
probe_categories.push_back(ProbeCategoryEnum::kBluetooth);
}
if (report_fan_info_) {
probe_categories.push_back(ProbeCategoryEnum::kFan);
}
if (report_power_status_) {
probe_categories.push_back(ProbeCategoryEnum::kBattery);
}
if (report_activity_times_) {
anything_reported |= GetActivityTimes(status);
}
if (report_audio_status_) {
anything_reported |= GetAudioStatus(status);
}
if (report_version_info_) {
probe_categories.push_back(ProbeCategoryEnum::kTpm);
anything_reported |= GetVersionInfo(status);
}
if (report_boot_mode_) {
anything_reported |= GetDeviceBootMode(status);
}
if (report_network_configuration_) {
probe_categories.push_back(ProbeCategoryEnum::kBus);
anything_reported |= GetNetworkConfiguration(status);
}
if (report_network_status_) {
anything_reported |= GetNetworkStatus(status);
}
if (report_users_) {
anything_reported |= GetUsers(status);
}
if (report_os_update_status_) {
anything_reported |= GetOsUpdateStatus(status);
}
if (report_running_kiosk_app_) {
anything_reported |= GetRunningKioskApp(status);
}
if (report_memory_info_) {
probe_categories.push_back(ProbeCategoryEnum::kMemory);
anything_reported |= GetMemoryInfo(status);
}
if (report_cpu_info_) {
probe_categories.push_back(ProbeCategoryEnum::kCpu);
state->SampleCPUTempInfo(cpu_temp_fetcher_);
anything_reported |= GetCPUInfo(status);
}
if (report_security_status_) {
state->FetchTpmStatus(tpm_status_fetcher_);
}
if (report_system_info_) {
anything_reported |= GetWriteProtectSwitch(status);
}
// Demo Mode dimensions are only reported when the device is in Demo Mode.
anything_reported |= GetDemoModeDimensions(status);
// Mark if any of the above functions reported data so that the response is
// sent.
if (anything_reported) {
state->SetDeviceStatusReported();
}
if (report_storage_status_) {
probe_categories.push_back(ProbeCategoryEnum::kNonRemovableBlockDevices);
GetStorageStatus(state);
}
if (report_graphics_status_) {
GetGraphicsStatus(state);
}
if (report_crash_report_info_ && stat_reporting_pref_) {
GetCrashReportInfo(state);
}
// The health daemon should always be queried to get the device vendor,
// product name, and product version.
state->FetchCrosHealthdData(cros_healthd_data_fetcher_, probe_categories,
report_system_info_, report_vpd_info_,
report_storage_status_, report_version_info_,
report_network_configuration_);
}
bool DeviceStatusCollector::GetSessionStatusForUser(
scoped_refptr<DeviceStatusCollectorState> state,
em::SessionStatusReportRequest* status,
const user_manager::User* user) {
Profile* const profile = ash::ProfileHelper::Get()->GetProfileByUser(user);
if (!profile) {
return false;
}
bool anything_reported_user = false;
const bool report_android_status =
profile->GetPrefs()->GetBoolean(prefs::kReportArcStatusEnabled);
if (report_android_status) {
anything_reported_user |= GetAndroidStatus(status, state);
}
const bool report_crostini_usage = profile->GetPrefs()->GetBoolean(
crostini::prefs::kReportCrostiniUsageEnabled);
if (report_crostini_usage) {
anything_reported_user |= GetCrostiniUsage(status, profile);
}
if (anything_reported_user && !user->IsDeviceLocalAccount()) {
status->set_user_dm_token(GetDMTokenForProfile(profile));
}
// Time zone is not reported in enterprise reports.
return anything_reported_user;
}
void DeviceStatusCollector::GetSessionStatus(
scoped_refptr<DeviceStatusCollectorState> state) {
em::SessionStatusReportRequest* status =
state->response_params().session_status.get();
bool anything_reported = false;
user_manager::UserManager* user_manager = user_manager::UserManager::Get();
const user_manager::User* const primary_user = user_manager->GetPrimaryUser();
if (report_kiosk_session_status_) {
anything_reported |= GetKioskSessionStatus(status);
}
// Only report affiliated users' data in enterprise reporting. Note that
// device-local accounts are also affiliated. Currently we only report for the
// primary user.
if (primary_user && primary_user->IsAffiliated()) {
anything_reported |= GetSessionStatusForUser(state, status, primary_user);
}
// |app_infos|
const auto app_infos = app_info_generator_.Generate();
anything_reported |= app_infos.has_value();
if (app_infos) {
*status->mutable_app_infos() = {app_infos.value().begin(),
app_infos.value().end()};
}
// Wipe pointer if we didn't actually add any data.
if (!anything_reported) {
state->response_params().session_status.reset();
}
}
bool DeviceStatusCollector::GetKioskSessionStatus(
em::SessionStatusReportRequest* status) {
std::unique_ptr<const DeviceLocalAccount> account =
GetAutoLaunchedKioskSessionInfo();
if (!account) {
return false;
}
// Get the account ID associated with this user.
status->set_device_local_account_id(account->account_id);
em::AppStatus* app_status = status->add_installed_apps();
switch (account->type) {
case DeviceLocalAccountType::kKioskApp: {
app_status->set_app_id(account->kiosk_app_id);
// Look up the app and get the version.
const std::string app_version = GetAppVersion(account->kiosk_app_id);
if (app_version.empty()) {
DLOG(ERROR) << "Unable to get version for extension: "
<< account->kiosk_app_id;
} else {
app_status->set_extension_version(app_version);
}
break;
}
case DeviceLocalAccountType::kWebKioskApp:
app_status->set_app_id(account->web_kiosk_app_info.url());
break;
case DeviceLocalAccountType::kKioskIsolatedWebApp:
app_status->set_app_id(account->kiosk_iwa_info.web_bundle_id());
break;
case DeviceLocalAccountType::kPublicSession:
case DeviceLocalAccountType::kSamlPublicSession:
NOTREACHED_IN_MIGRATION();
}
return true;
}
bool DeviceStatusCollector::GetAndroidStatus(
em::SessionStatusReportRequest* status,
const scoped_refptr<DeviceStatusCollectorState>& state) {
return state->FetchAndroidStatus(android_status_fetcher_);
}
bool DeviceStatusCollector::GetCrostiniUsage(
em::SessionStatusReportRequest* status,
Profile* profile) {
if (!profile->GetPrefs()->HasPrefPath(
crostini::prefs::kCrostiniLastLaunchTimeWindowStart)) {
return false;
}
em::CrostiniStatus* const crostini_status = status->mutable_crostini_status();
const int64_t last_launch_time_window_start = profile->GetPrefs()->GetInt64(
crostini::prefs::kCrostiniLastLaunchTimeWindowStart);
const std::string& termina_version = profile->GetPrefs()->GetString(
crostini::prefs::kCrostiniLastLaunchTerminaComponentVersion);
crostini_status->set_last_launch_time_window_start_timestamp(
last_launch_time_window_start);
crostini_status->set_last_launch_vm_image_version(termina_version);
if (profile->GetPrefs()->GetBoolean(crostini::prefs::kCrostiniEnabled) &&
base::FeatureList::IsEnabled(
features::kCrostiniAdditionalEnterpriseReporting)) {
const std::string& vm_kernel_version = profile->GetPrefs()->GetString(
crostini::prefs::kCrostiniLastLaunchTerminaKernelVersion);
crostini_status->set_last_launch_vm_kernel_version(vm_kernel_version);
AddCrostiniAppListForProfile(profile, crostini_status);
}
return true;
}
std::string DeviceStatusCollector::GetAppVersion(
const std::string& kiosk_app_id) {
Profile* const profile = ash::ProfileHelper::Get()->GetProfileByUser(
user_manager::UserManager::Get()->GetActiveUser());
// TODO(b/191334671): Replace with DCHECK once we no longer hit this timing
// issue.
if (!profile) {
return std::string();
}
const extensions::ExtensionRegistry* const registry =
extensions::ExtensionRegistry::Get(profile);
const extensions::Extension* const extension = registry->GetExtensionById(
kiosk_app_id, extensions::ExtensionRegistry::EVERYTHING);
if (!extension) {
return std::string();
}
return extension->VersionString();
}
// TODO(crbug.com/40569404): move public API methods above private ones after
// common methods are extracted.
void DeviceStatusCollector::OnSubmittedSuccessfully() {
activity_storage_->TrimActivityPeriods(last_reported_end_timestamp_,
std::numeric_limits<int64_t>::max());
app_info_generator_.OnReportedSuccessfully(last_requested_);
}
bool DeviceStatusCollector::IsReportingActivityTimes() const {
// This function is used for checking if a message about activity reporting
// should be displayed to a user in the transparency panel. User activity for
// a current user is reported only if the user is managed by the same
// organization as a device.
if (!report_activity_times_) {
return false;
}
std::string user_email = GetUserForActivityReporting();
return !user_email.empty() && !IsDeviceLocalAccountUser(user_email);
}
bool DeviceStatusCollector::IsReportingNetworkData() const {
return report_network_configuration_ || report_network_status_;
}
bool DeviceStatusCollector::IsReportingHardwareData() const {
return report_power_status_ || report_storage_status_ ||
report_audio_status_ || report_board_status_ || report_memory_info_ ||
report_cpu_info_ || report_backlight_info_ || report_bluetooth_info_ ||
report_fan_info_ || report_vpd_info_ || report_system_info_ ||
report_boot_mode_ || report_version_info_ || report_graphics_status_;
}
bool DeviceStatusCollector::IsReportingUsers() const {
// For more details, see comment in
// DeviceStatusCollector::IsReportingActivityTimes() function.
if (!report_users_) {
return false;
}
std::string user_email = GetUserForActivityReporting();
return !user_email.empty() && !IsDeviceLocalAccountUser(user_email);
}
bool DeviceStatusCollector::IsReportingCrashReportInfo() const {
return report_crash_report_info_ && stat_reporting_pref_;
}
bool DeviceStatusCollector::IsReportingAppInfoAndActivity() const {
return report_app_info_;
}
// TODO(crbug.com/40239083)
// Make this function fallible when the optional received is empty
void DeviceStatusCollector::OnOSVersion(
const std::optional<std::string>& version) {
os_version_ = version.value_or("0.0.0.0");
}
void DeviceStatusCollector::OnOSFirmware(
std::pair<const std::string&, const std::string&> version) {
firmware_version_ = version.first;
firmware_fetch_error_ = version.second;
}
void DeviceStatusCollector::OnGetTpmVersion(
const ::tpm_manager::GetVersionInfoReply& reply) {
if (reply.status() != ::tpm_manager::STATUS_SUCCESS) {
LOG(WARNING) << "Failed to get tpm version; status: " << reply.status();
}
tpm_version_reply_ = reply;
}
} // namespace policy