blob: 0fc2b85e1836fd397ffed6788619c774d587dedf [file] [log] [blame]
/* 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, &sector_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