| // Copyright 2016 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Library to provide access to the Chrome OS master configuration |
| |
| #include "chromeos-config/libcros_config/cros_config.h" |
| |
| #include <string> |
| #include <sys/mount.h> |
| #include <utility> |
| |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/strings/string_split.h> |
| #include <brillo/file_utils.h> |
| #include "chromeos-config/libcros_config/configfs.h" |
| #include "chromeos-config/libcros_config/cros_config_fallback.h" |
| #include "chromeos-config/libcros_config/cros_config_json.h" |
| #include "chromeos-config/libcros_config/identity.h" |
| |
| namespace { |
| const char kCustomizationId[] = "/sys/firmware/vpd/ro/customization_id"; |
| const char kWhitelabelTag[] = "/sys/firmware/vpd/ro/whitelabel_tag"; |
| const char kProductName[] = "/sys/devices/virtual/dmi/id/product_name"; |
| const char kProductSku[] = "/sys/devices/virtual/dmi/id/product_sku"; |
| const char kArmSkuId[] = "/proc/device-tree/firmware/coreboot/sku-id"; |
| const char kDeviceTreeCompatiblePath[] = "/proc/device-tree/compatible"; |
| const char kConfigFSBasePath[] = "/run/chromeos-config/v1"; |
| } // namespace |
| |
| namespace brillo { |
| |
| CrosConfig::CrosConfig() {} |
| |
| CrosConfig::~CrosConfig() {} |
| |
| bool CrosConfig::GetDefaultIdentityFiles(const SystemArchitecture arch, |
| base::FilePath* vpd_file_out, |
| base::FilePath* product_name_file_out, |
| base::FilePath* product_sku_file_out) { |
| *vpd_file_out = base::FilePath(kWhitelabelTag); |
| if (!base::PathExists(*vpd_file_out)) { |
| *vpd_file_out = base::FilePath(kCustomizationId); |
| } |
| if (arch == SystemArchitecture::kX86) { |
| *product_name_file_out = base::FilePath(kProductName); |
| *product_sku_file_out = base::FilePath(kProductSku); |
| } else if (arch == SystemArchitecture::kArm) { |
| *product_name_file_out = base::FilePath(kDeviceTreeCompatiblePath); |
| *product_sku_file_out = base::FilePath(kArmSkuId); |
| } else { |
| CROS_CONFIG_LOG(ERROR) << "System architecture is unknown"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool CrosConfig::Init() { |
| // Nothing to do, we're just reading from ConfigFS. |
| return true; |
| } |
| |
| bool CrosConfig::InitForTest(const int sku_id, |
| const base::FilePath& json_path, |
| const SystemArchitecture arch, |
| const std::string& name, |
| const std::string& customization_id) { |
| auto identity = CrosConfigIdentity::FromArchitecture(arch); |
| if (!identity) { |
| CROS_CONFIG_LOG(ERROR) << "Provided architecture is unknown"; |
| return false; |
| } |
| base::FilePath product_name_file, product_sku_file, vpd_file; |
| if (!identity->FakeVpdFileForTesting(customization_id, &vpd_file)) { |
| CROS_CONFIG_LOG(ERROR) << "FakeVpdFileForTesting() failed"; |
| return false; |
| } |
| if (!identity->FakeProductFilesForTesting(name, sku_id, &product_name_file, |
| &product_sku_file)) { |
| CROS_CONFIG_LOG(ERROR) << "FakeProductFilesForTesting() failed"; |
| return false; |
| } |
| return InitInternal(sku_id, json_path, arch, product_name_file, |
| product_sku_file, vpd_file); |
| } |
| |
| bool CrosConfig::MountConfigFS(const base::FilePath& image_path, |
| const base::FilePath& mount_path) { |
| base::FilePath private_dir; |
| base::FilePath v1_dir; |
| |
| if (!SetupMountPath(mount_path, &private_dir, &v1_dir)) { |
| return false; |
| } |
| |
| base::FilePath loop_device; |
| if (!SetupLoopDevice(image_path, &loop_device)) { |
| return false; |
| } |
| |
| if (!Mount(loop_device, private_dir, kConfigFSPrivateFSType, MS_RDONLY)) { |
| return false; |
| } |
| |
| const auto private_v1_dir = private_dir.Append(kConfigFSV1DirName); |
| |
| if (!cros_config_) { |
| // Init hasn't been called yet (which is the typical case of using |
| // MountConfigFS). We can use the identity stored inside of the |
| // ConfigFS for faster initialization. |
| const auto identity_path = private_v1_dir.Append(kConfigFSIdentityName); |
| |
| if (!base::PathExists(identity_path)) { |
| // We have checks at build-time in cros_config_host that makes |
| // sure this file exists in the filesystem. But it's worth the |
| // sanity check here too (in case a developer-constructed image |
| // was invalid). |
| CROS_CONFIG_LOG(ERROR) << identity_path.value() << " is missing!"; |
| return false; |
| } |
| const auto arch = CrosConfigIdentity::CurrentSystemArchitecture(); |
| base::FilePath vpd_file; |
| base::FilePath product_name_file; |
| base::FilePath product_sku_file; |
| if (!GetDefaultIdentityFiles(arch, &vpd_file, &product_name_file, |
| &product_sku_file)) { |
| return false; |
| } |
| if (!InitInternal(kDefaultSkuId, identity_path, arch, product_name_file, |
| product_sku_file, vpd_file)) { |
| CROS_CONFIG_LOG(ERROR) << "Identity probing failed!"; |
| return false; |
| } |
| } |
| |
| int device_index; |
| if (!GetDeviceIndex(&device_index)) { |
| return false; |
| } |
| const auto device_config_dir = |
| private_v1_dir.Append(CrosConfigJson::kRootName) |
| .Append(CrosConfigJson::kConfigListName) |
| .Append(std::to_string(device_index)); |
| return brillo::Bind(device_config_dir, v1_dir); |
| } |
| |
| bool CrosConfig::MountFallbackConfigFS(const base::FilePath& mount_path) { |
| base::FilePath private_dir; |
| base::FilePath v1_dir; |
| |
| if (!SetupMountPath(mount_path, &private_dir, &v1_dir)) { |
| return false; |
| } |
| |
| if (!Mount(base::FilePath("tmpfs"), private_dir, "tmpfs", 0)) { |
| return false; |
| } |
| |
| const auto fallback_dir = private_dir.Append("fallback"); |
| if (!MkdirRecursively(fallback_dir, 0755).is_valid()) { |
| CROS_CONFIG_LOG(ERROR) << "Failed to create directory " |
| << fallback_dir.value(); |
| return false; |
| } |
| |
| CrosConfigFallback cros_config; |
| if (!cros_config.WriteConfigFS(fallback_dir)) { |
| CROS_CONFIG_LOG(ERROR) << "Creating fallback ConfigFS failed!"; |
| return false; |
| } |
| |
| if (!Remount(private_dir, MS_RDONLY)) { |
| CROS_CONFIG_LOG(ERROR) << "Unable to make fallback ConfigFS read-only " |
| << "after writing out files."; |
| return false; |
| } |
| |
| return brillo::Bind(fallback_dir, v1_dir); |
| } |
| |
| bool CrosConfig::Unmount(const base::FilePath& mount_path) const { |
| bool success = true; |
| for (const auto& dir : {kConfigFSV1DirName, kConfigFSPrivateDirName}) { |
| const auto mountpoint = mount_path.Append(dir).value(); |
| if (umount2(mountpoint.c_str(), MNT_DETACH) < 0) { |
| success = false; |
| CROS_CONFIG_LOG(ERROR) << "Failed to unmount " << mountpoint << ": " |
| << logging::SystemErrorCodeToString( |
| logging::GetLastSystemErrorCode()); |
| } |
| } |
| return success; |
| } |
| |
| bool CrosConfig::InitInternal(const int sku_id, |
| const base::FilePath& json_path, |
| const SystemArchitecture arch, |
| const base::FilePath& product_name_file, |
| const base::FilePath& product_sku_file, |
| const base::FilePath& vpd_file) { |
| auto cros_config_json = std::make_unique<CrosConfigJson>(); |
| CROS_CONFIG_LOG(INFO) << ">>>>> reading config file: path=" |
| << json_path.MaybeAsASCII(); |
| if (!cros_config_json->ReadConfigFile(json_path)) |
| return false; |
| CROS_CONFIG_LOG(INFO) << ">>>>> config file successfully read"; |
| |
| CROS_CONFIG_LOG(INFO) << ">>>>> Starting to read identity"; |
| auto identity = CrosConfigIdentity::FromArchitecture(arch); |
| if (!identity->ReadVpd(vpd_file)) { |
| CROS_CONFIG_LOG(ERROR) << "Cannot read VPD identity"; |
| return false; |
| } |
| if (!identity->ReadInfo(product_name_file, product_sku_file)) { |
| CROS_CONFIG_LOG(ERROR) << "Cannot read SMBIOS or dt-compatible info"; |
| return false; |
| } |
| if (sku_id != kDefaultSkuId) { |
| identity->SetSkuId(sku_id); |
| CROS_CONFIG_LOG(INFO) << "Set sku_id to explicitly assigned value " |
| << sku_id; |
| } |
| if (!cros_config_json->SelectConfigByIdentity(*identity)) { |
| CROS_CONFIG_LOG(ERROR) << "Cannot find config for " |
| << identity->DebugString() << " (VPD ID from " |
| << vpd_file.MaybeAsASCII() << ")"; |
| return false; |
| } |
| CROS_CONFIG_LOG(INFO) << ">>>>> Completed initialization"; |
| |
| // Downgrade CrosConfigJson to CrosConfigInterface now that |
| // initialization has finished |
| cros_config_ = std::move(cros_config_json); |
| return true; |
| } |
| |
| bool CrosConfig::GetString(const std::string& path, |
| const std::string& property, |
| std::string* val_out) { |
| if (path.empty() || path[0] != '/') { |
| CROS_CONFIG_LOG(ERROR) << "Path parameter must begin with \"/\"."; |
| return false; |
| } |
| |
| if (!cros_config_) { |
| // Using ConfigFS (typical case). |
| auto filepath = base::FilePath(kConfigFSBasePath); |
| for (const auto& part : base::SplitStringPiece( |
| path, "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) { |
| filepath = filepath.Append(part); |
| } |
| filepath = filepath.Append(property); |
| return base::ReadFileToString(filepath, val_out); |
| } |
| |
| // Only happens if InitForTest was called. |
| return cros_config_->GetString(path, property, val_out); |
| } |
| |
| bool CrosConfig::GetDeviceIndex(int* device_index_out) { |
| if (!cros_config_) { |
| CROS_CONFIG_LOG(ERROR) << "No device identity has been probed."; |
| return false; |
| } |
| return cros_config_->GetDeviceIndex(device_index_out); |
| } |
| |
| } // namespace brillo |