| /* Copyright 2018 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. |
| */ |
| #include <utility> |
| |
| #include <base/files/file_enumerator.h> |
| #include <base/files/file_util.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_split.h> |
| #include <brillo/strings/string_utils.h> |
| #include <chromeos/dbus/service_constants.h> |
| #include <dbus/bus.h> |
| #include <dbus/message.h> |
| #include <dbus/object_proxy.h> |
| #include <pcrecpp.h> |
| |
| #include "runtime_probe/functions/generic_storage.h" |
| #include "runtime_probe/utils/file_utils.h" |
| |
| namespace runtime_probe { |
| namespace { |
| constexpr auto kStorageDirPath("/sys/class/block/"); |
| constexpr auto kReadFileMaxSize = 1024; |
| // TODO(b/123097249): Remove the following hard-coded constant once |
| // authenticated source of storage size to use is determined |
| constexpr auto kBytesPerSector = 512; |
| |
| // DBus related constant to issue dbus call to debugd |
| constexpr auto kDebugdMmcMethodName = "Mmc"; |
| constexpr auto kDebugdMmcOption = "extcsd_read"; |
| constexpr auto kDebugdMmcDefaultTimeout = 10 * 1000; // in ms |
| |
| const std::vector<std::string> kAtaFields{"vendor", "model"}; |
| const std::vector<std::string> kEmmcFields{"type", "name", "oemid", "manfid", |
| "serial"}; |
| // Attributes in optional fields: |
| // prv: SD and MMCv4 only |
| // hwrev: SD and MMCv1 only |
| const std::vector<std::string> kEmmcOptionalFields{"prv", "hwrev"}; |
| const std::vector<std::string> kNvmeFields{"vendor", "device", "class"}; |
| |
| // Check if the string represented by |input_string| is printable |
| bool IsPrintable(const std::string& input_string) { |
| for (const auto& cha : input_string) { |
| if (!isprint(cha)) |
| return false; |
| } |
| return true; |
| } |
| |
| // Return the formatted string "%s (%s)" % |v|, |v_decode| |
| std::string VersionFormattedString(const std::string& v, |
| const std::string& v_decode) { |
| return v + " (" + v_decode + ")"; |
| } |
| |
| } // namespace |
| |
| bool GenericStorageFunction::GetOutputOfMmcExtcsd( |
| const base::FilePath& node_path, std::string* output) const { |
| VLOG(1) << "Issuing D-Bus call to debugd to retrieve eMMC 5.0 firmware info."; |
| |
| dbus::Bus::Options ops; |
| ops.bus_type = dbus::Bus::SYSTEM; |
| scoped_refptr<dbus::Bus> bus(new dbus::Bus(std::move(ops))); |
| |
| if (!bus->Connect()) { |
| LOG(ERROR) << "Failed to connect to system D-Bus service."; |
| return false; |
| } |
| |
| dbus::ObjectProxy* object_proxy = bus->GetObjectProxy( |
| debugd::kDebugdServiceName, dbus::ObjectPath(debugd::kDebugdServicePath)); |
| |
| dbus::MethodCall method_call(debugd::kDebugdInterface, kDebugdMmcMethodName); |
| dbus::MessageWriter writer(&method_call); |
| |
| writer.AppendString(kDebugdMmcOption); |
| std::unique_ptr<dbus::Response> response = |
| object_proxy->CallMethodAndBlock(&method_call, kDebugdMmcDefaultTimeout); |
| if (!response) { |
| LOG(ERROR) << "Failed to issue D-Bus mmc call to debugd."; |
| return false; |
| } |
| |
| dbus::MessageReader reader(response.get()); |
| if (!reader.PopString(output)) { |
| LOG(ERROR) << "Failed to read reply from debugd."; |
| return false; |
| } |
| return true; |
| } |
| |
| std::vector<base::FilePath> GenericStorageFunction::GetFixedDevices() const { |
| std::vector<base::FilePath> res{}; |
| const base::FilePath storage_dir_path(kStorageDirPath); |
| base::FileEnumerator storage_dir_it(storage_dir_path, true, |
| base::FileEnumerator::SHOW_SYM_LINKS | |
| base::FileEnumerator::FILES | |
| base::FileEnumerator::DIRECTORIES); |
| |
| while (true) { |
| const auto storage_path = storage_dir_it.Next(); |
| if (storage_path.empty()) |
| break; |
| // Only return non-removable devices |
| const auto storage_removable_path = storage_path.Append("removable"); |
| std::string removable_res; |
| if (!base::ReadFileToString(storage_removable_path, &removable_res)) { |
| VLOG(1) << "Storage device " << storage_path.value() |
| << " does not specify the removable property. Assume removable."; |
| continue; |
| } |
| |
| if (base::TrimWhitespaceASCII(removable_res, base::TrimPositions::TRIM_ALL) |
| .as_string() != "0") { |
| VLOG(1) << "Storage device " << storage_path.value() << " is removable."; |
| continue; |
| } |
| |
| // Skip Loobpack or dm-verity device |
| if (base::StartsWith(storage_path.BaseName().value(), "loop", |
| base::CompareCase::SENSITIVE) || |
| base::StartsWith(storage_path.BaseName().value(), "dm-", |
| base::CompareCase::SENSITIVE)) |
| continue; |
| |
| res.push_back(storage_path); |
| } |
| |
| return res; |
| } |
| |
| std::string GenericStorageFunction::GetEMMC5FirmwareVersion( |
| const base::FilePath& node_path) const { |
| VLOG(1) << "Checking eMMC firmware version of " |
| << node_path.BaseName().value(); |
| |
| std::string ext_csd_res; |
| |
| if (!GetOutputOfMmcExtcsd(node_path, &ext_csd_res)) { |
| LOG(WARNING) << "Fail to retrieve information from mmc extcsd for /dev/" |
| << node_path.BaseName().value(); |
| return std::string{""}; |
| } |
| |
| /* The output of firmware version looks like hexdump of ASCII strings or |
| * hexadecimal values, which depends on vendors. |
| |
| * Example of version "ABCDEFGH" (ASCII hexdump) |
| * [FIRMWARE_VERSION[261]]: 0x48 |
| * [FIRMWARE_VERSION[260]]: 0x47 |
| * [FIRMWARE_VERSION[259]]: 0x46 |
| * [FIRMWARE_VERSION[258]]: 0x45 |
| * [FIRMWARE_VERSION[257]]: 0x44 |
| * [FIRMWARE_VERSION[256]]: 0x43 |
| * [FIRMWARE_VERSION[255]]: 0x42 |
| * [FIRMWARE_VERSION[254]]: 0x41 |
| |
| * Example of version 3 (hexadecimal values hexdump) |
| * [FIRMWARE_VERSION[261]]: 0x00 |
| * [FIRMWARE_VERSION[260]]: 0x00 |
| * [FIRMWARE_VERSION[259]]: 0x00 |
| * [FIRMWARE_VERSION[258]]: 0x00 |
| * [FIRMWARE_VERSION[257]]: 0x00 |
| * [FIRMWARE_VERSION[256]]: 0x00 |
| * [FIRMWARE_VERSION[255]]: 0x00 |
| * [FIRMWARE_VERSION[254]]: 0x03 |
| */ |
| pcrecpp::RE re(R"(^\[FIRMWARE_VERSION\[\d+\]\]: (.*)$)", |
| pcrecpp::RE_Options()); |
| /* version list stores each byte as the format "ff" (two hex digits) |
| * raw version list stores each byte as an int |
| */ |
| |
| const auto ext_csd_lines = base::SplitString( |
| ext_csd_res, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| |
| std::vector<std::string> hex_version_components; |
| std::string char_version{""}; |
| |
| // The memory snapshots of version output from mmc are in reverse order |
| for (auto it = ext_csd_lines.rbegin(); it != ext_csd_lines.rend(); it++) { |
| std::string cur_version_str; |
| if (!re.PartialMatch(*it, &cur_version_str)) |
| continue; |
| |
| // 0xff => ff |
| const auto cur_version_component = |
| cur_version_str.substr(2, std::string::npos); |
| |
| hex_version_components.push_back(cur_version_component); |
| |
| int cur_version_char; |
| if (!base::HexStringToInt(cur_version_str, &cur_version_char)) { |
| LOG(ERROR) << "Failed to convert one byte hex representation " |
| << cur_version_str << " to char."; |
| return std::string{""}; |
| } |
| char_version += static_cast<char>(cur_version_char); |
| } |
| |
| const auto hex_version = brillo::string_utils::JoinRange( |
| "", hex_version_components.begin(), hex_version_components.end()); |
| VLOG(1) << "eMMC 5.0 firmware version is " << hex_version; |
| // Convert each int in raw_version_list to char and concat them |
| if (IsPrintable(char_version)) { |
| return VersionFormattedString(hex_version, char_version); |
| |
| } else { |
| // Represent the version in the little endian format |
| const std::string hex_version_le = brillo::string_utils::JoinRange( |
| "", hex_version_components.rbegin(), hex_version_components.rend()); |
| uint64_t version_decode_le; |
| if (!base::HexStringToUInt64(hex_version_le, &version_decode_le)) { |
| LOG(ERROR) << "Failed to convert " << hex_version_le |
| << " to 64-bit unsigned integer"; |
| return std::string{""}; |
| } |
| return VersionFormattedString(hex_version, |
| std::to_string(version_decode_le)); |
| } |
| } |
| |
| GenericStorageFunction::DataType GenericStorageFunction::Eval() const { |
| const auto storage_nodes_path_list = GetFixedDevices(); |
| DataType result{}; |
| |
| for (const auto& node_path : storage_nodes_path_list) { |
| VLOG(1) << "Processnig the node " << node_path.value(); |
| base::DictionaryValue node_res{}; |
| |
| const auto dev_path = node_path.Append("device"); |
| // For NVMe device, "/<node_path>/device/device/.." is expected. |
| const auto nvme_dev_path = dev_path.Append("device"); |
| |
| // dev_path is the paraent directory of nvme_dev_path |
| if (!base::PathExists(dev_path)) { |
| VLOG(1) << "None of ATA, NVMe or eMMC fields exist on storage device " |
| << node_path.value(); |
| continue; |
| } |
| |
| // ATA, NVMe and eMMC are mutually exclusive indicators |
| // TODO(b/122027599): Add "ATA" to field "type" if ata_res is not empty |
| const base::DictionaryValue ata_res = |
| MapFilesToDict(dev_path, kAtaFields, {}); |
| const base::DictionaryValue emmc_res = |
| MapFilesToDict(dev_path, kEmmcFields, kEmmcOptionalFields); |
| |
| if (!emmc_res.empty()) { |
| // Get eMMC 5.0 firmaware version |
| const auto emmc5_fw_ver = GetEMMC5FirmwareVersion(node_path); |
| if (!emmc5_fw_ver.empty()) |
| node_res.SetString("emmc5_fw_ver", emmc5_fw_ver); |
| } |
| |
| node_res.MergeDictionary(&ata_res); |
| node_res.MergeDictionary(&emmc_res); |
| |
| if (base::PathExists(nvme_dev_path)) { |
| // TODO(b/122027599): Add "NVMe" to field "type" if nvme_res is not empty |
| const base::DictionaryValue nvme_res = |
| MapFilesToDict(nvme_dev_path, kNvmeFields, {}); |
| node_res.MergeDictionary(&nvme_res); |
| } |
| |
| if (node_res.empty()) { |
| VLOG(1) << "Cannot probe ATA, NVMe or eMMC fields on storage device " |
| << node_path.value(); |
| continue; |
| } |
| |
| // Report the absolute path we probe the reported info from |
| node_res.SetString("path", node_path.value()); |
| |
| // Size info |
| const auto size_path = node_path.Append("size"); |
| std::string size_content; |
| if (base::ReadFileToStringWithMaxSize(size_path, &size_content, |
| kReadFileMaxSize)) { |
| const auto sector_str = base::TrimWhitespaceASCII( |
| size_content, base::TrimPositions::TRIM_ALL); |
| node_res.SetString("sectors", sector_str); |
| int64_t sector_int; |
| if (!base::StringToInt64(sector_str, §or_int)) { |
| LOG(ERROR) << "Failed to parse recorded sector of" << node_path.value() |
| << " to integer!"; |
| node_res.SetString("size", "-1"); |
| } else { |
| node_res.SetString("size", |
| base::Int64ToString(sector_int * kBytesPerSector)); |
| } |
| } else { |
| VLOG(1) << "Storage device " << node_path.value() |
| << " does not specify size"; |
| node_res.SetString("sectors", "-1"); |
| node_res.SetString("size", "-1"); |
| } |
| |
| result.emplace_back(); |
| result.back().Swap(&node_res); |
| } |
| |
| return result; |
| } |
| |
| } // namespace runtime_probe |