blob: 822cc3ba644c98d9fb4ba58ceccbe483d050a5cb [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cstdint>
#include <functional>
#include <optional>
#include <string>
#include <base/base64.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/system/sys_info.h>
#include <base/values.h>
#include <brillo/process/process.h>
#include <rootdev/rootdev.h>
#include "libsegmentation/device_info.pb.h"
#include "libsegmentation/feature_management_hwid.h"
#include "libsegmentation/feature_management_impl.h"
#include "libsegmentation/feature_management_interface.h"
#include "libsegmentation/feature_management_util.h"
namespace segmentation {
namespace {
// The path for the "gsctool" binary.
const char kGscToolBinaryPath[] = "/usr/sbin/gsctool";
// The output of |kGscToolBinaryPath| will contain a "chassis_x_branded:" line.
const char kChassisXBrandedKey[] = "chassis_x_branded:";
// The output of |kGscToolBinaryPath| will contain a "hw_compliance_version:"
// line.
const char kHwXComplianceVersion[] = "hw_x_compliance_version:";
// The output from the "gsctool" binary. Some or all of these fields may not be
// present in the output.
struct GscToolOutput {
bool chassis_x_branded;
int32_t hw_compliance_version;
};
// Parses output from running |kGscToolBinaryPath| into GscToolOutput.
std::optional<GscToolOutput> ParseGscToolOutput(
const std::string& gsc_tool_output) {
GscToolOutput output;
std::istringstream iss(gsc_tool_output);
std::string line;
// Flags to indicate we've found the required fields.
bool found_chassis = false;
bool found_compliance_version = false;
// Keep going till there are lines in the output or we've found both the
// fields.
while (std::getline(iss, line) &&
(!found_chassis || !found_compliance_version)) {
std::istringstream line_stream(line);
std::string key;
line_stream >> key;
if (key == kChassisXBrandedKey) {
bool value;
line_stream >> std::boolalpha >> value;
output.chassis_x_branded = value;
found_chassis = true;
} else if (key == kHwXComplianceVersion) {
int32_t value;
line_stream >> value;
output.hw_compliance_version = value;
found_compliance_version = true;
}
}
if (found_chassis && found_compliance_version) {
return output;
}
return std::nullopt;
}
// Returns the device information parsed from the output of the GSC tool binary
// on the device.
std::optional<GscToolOutput> GetDeviceInfoFromGSC() {
if (!base::PathExists(base::FilePath(kGscToolBinaryPath))) {
LOG(ERROR) << "Can't find gsctool binary";
return std::nullopt;
}
base::FilePath output_path;
if (!base::CreateTemporaryFile(&output_path)) {
LOG(ERROR) << "Failed to open output file";
return std::nullopt;
}
brillo::ProcessImpl process;
process.AddArg(kGscToolBinaryPath);
std::vector<std::string> args = {"--factory_config", "--any"};
for (const auto& arg : args) {
process.AddArg(arg);
}
process.RedirectOutput(output_path);
if (!process.Start()) {
LOG(ERROR) << "Failed to start gsctool process";
return std::nullopt;
}
if (process.Wait() < 0) {
LOG(ERROR) << "Failed to wait for the gsctool process";
return std::nullopt;
}
std::string output;
if (!base::ReadFileToString(output_path, &output)) {
LOG(ERROR) << "Failed to read output from the gsctool";
return std::nullopt;
}
std::optional<GscToolOutput> gsc_tool_output = ParseGscToolOutput(output);
if (!gsc_tool_output) {
LOG(ERROR) << "Failed to parse output from the gsctool";
return std::nullopt;
}
return gsc_tool_output;
}
// Write |device_info| as base64 to the "vpd" binary by spawning a new process.
bool WriteToVpd(const libsegmentation::DeviceInfo& device_info) {
std::string serialized = device_info.SerializeAsString();
std::string base64_encoded;
base::Base64Encode(serialized, &base64_encoded);
brillo::ProcessImpl process;
process.AddArg("/usr/sbin/vpd");
process.AddArg("-i");
process.AddArg("RW_VPD");
process.AddArg("-s");
process.AddArg("feature_device_info=" + base64_encoded);
if (!process.Start()) {
LOG(ERROR) << "Failed to start VPD process";
return false;
}
int return_code = process.Wait();
if (return_code != 0) {
LOG(ERROR) << "VPD process exited with return code: " << return_code;
return false;
}
return true;
}
} // namespace
FeatureManagementInterface::FeatureLevel
FeatureManagementImpl::GetFeatureLevel() {
if (cached_device_info_) {
return FeatureManagementUtil::ConvertProtoFeatureLevel(
cached_device_info_->feature_level());
}
if (!CacheDeviceInfo()) {
return FeatureLevel::FEATURE_LEVEL_UNKNOWN;
}
return FeatureManagementUtil::ConvertProtoFeatureLevel(
cached_device_info_->feature_level());
}
FeatureManagementInterface::ScopeLevel FeatureManagementImpl::GetScopeLevel() {
if (cached_device_info_) {
return FeatureManagementUtil::ConvertProtoScopeLevel(
cached_device_info_->scope_level());
}
if (!CacheDeviceInfo()) {
return ScopeLevel::SCOPE_LEVEL_UNKNOWN;
}
return FeatureManagementUtil::ConvertProtoScopeLevel(
cached_device_info_->scope_level());
}
bool FeatureManagementImpl::CacheDeviceInfo() {
// Read from the tmpfs file in case the VPD has been writen but the device has
// not been rebooted.
std::optional<libsegmentation::DeviceInfo> device_info_result;
if (persist_via_vpd_ && base::PathExists(temp_device_info_file_path_)) {
device_info_result = FeatureManagementUtil::ReadDeviceInfoFromFile(
temp_device_info_file_path_);
// To ease testing, overwrite hash check.
if (device_info_result)
device_info_result->set_cached_version_hash(current_version_hash_);
} else {
device_info_result =
FeatureManagementUtil::ReadDeviceInfoFromFile(device_info_file_path_);
}
// If the device info isn't cached, read it form the hardware id and write it
// to the VPD for subsequent boots.
if (!device_info_result ||
device_info_result->cached_version_hash() != current_version_hash_) {
auto gsc_tool_output = GetDeviceInfoFromGSC();
if (!gsc_tool_output) {
LOG(ERROR) << "Failed to get device info from the hardware id";
return false;
}
FeatureManagementHwid::GetDeviceSelectionFn get_device_callback =
[this](bool check) { return this->GetDeviceInfoFromHwid(check); };
device_info_result = FeatureManagementHwid::GetDeviceInfo(
get_device_callback, gsc_tool_output->chassis_x_branded,
gsc_tool_output->hw_compliance_version);
// Persist the device info read from "gsctool" via "vpd" or to a regular
// file for testing. If we fail to write it then don't cache it.
device_info_result->set_cached_version_hash(current_version_hash_);
if (persist_via_vpd_) {
if (!WriteToVpd(device_info_result.value())) {
LOG(ERROR) << "Failed to persist device info via vpd";
return false;
}
// Best effort.
FeatureManagementUtil::WriteDeviceInfoToFile(device_info_result.value(),
temp_device_info_file_path_);
} else {
if (!FeatureManagementUtil::WriteDeviceInfoToFile(
device_info_result.value(), device_info_file_path_)) {
LOG(ERROR) << "Failed to persist device info";
return false;
}
}
}
// At this point device information is present on stateful. We can cache it.
cached_device_info_ = device_info_result.value();
return true;
}
std::optional<DeviceSelection> FeatureManagementImpl::GetDeviceInfoFromHwid(
bool check_prefix_only) {
std::optional<std::string> hwid =
crossystem_->VbGetSystemPropertyString("hwid");
if (!hwid) {
LOG(ERROR) << "Unable to retrieve HWID";
return std::nullopt;
}
std::optional<DeviceSelection> selection =
FeatureManagementHwid::GetSelectionFromHWID(
selection_bundle_, hwid.value(), check_prefix_only);
if (!selection)
return std::nullopt;
if (!check_prefix_only && !Check_HW_Requirement(selection.value())) {
LOG(ERROR) << hwid.value() << " do not meet feature level "
<< selection->feature_level() << " requirement.";
return std::nullopt;
}
return selection;
}
bool FeatureManagementImpl::Check_HW_Requirement(
const DeviceSelection& selection) {
if (selection.feature_level() == 0) {
LOG(ERROR) << "Unexpected feature level: 0";
return false;
}
if (selection.feature_level() > 1) {
LOG(ERROR) << "Requirement not defined yet for feature_level "
<< selection.feature_level();
return false;
}
// Feature level 1:
// DRAM >= 8GiB. But since not all the physical RAM is available (PCI hole),
// settle for 7GiB.
// Obtain the size of the physical memory of the system.
const size_t k7GiB = 7 * 1024 * 1024 * 1024ULL;
if (base::SysInfo::AmountOfPhysicalMemory() < k7GiB)
return false;
// SSD >= 128GB
// But since SSD counts in power of 10 and controller may even take a bigger
// share, settle for 110GiB.
// sysinfo AmountOfTotalDiskSpace can not be used, it returns the size of the
// underlying filesystem.
std::optional<base::FilePath> root_device =
FeatureManagementUtil::GetDefaultRoot(base::FilePath("/"));
if (!root_device)
return false;
std::optional<int64_t> size =
FeatureManagementUtil::GetDiskSpace(*root_device);
if (!size)
return false;
const size_t k110GiB = 110 * 1024 * 1024 * 1024ULL;
if (*size < k110GiB)
return false;
return true;
}
} // namespace segmentation