| // Copyright (c) 2012 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 "debugd/src/storage_tool.h" |
| |
| #include <fstream> |
| #include <iostream> |
| #include <linux/limits.h> |
| #include <mntent.h> |
| #include <string> |
| #include <unistd.h> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/base64.h> |
| #include <base/files/file.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/string_util.h> |
| |
| #include "debugd/src/helper_utils.h" |
| #include "debugd/src/process_with_id.h" |
| #include "debugd/src/process_with_output.h" |
| |
| namespace debugd { |
| |
| namespace { |
| |
| const char kSmartctl[] = "/usr/sbin/smartctl"; |
| const char kBadblocks[] = "/sbin/badblocks"; |
| const char kMountFile[] = "/proc/1/mounts"; |
| const char kSource[] = "/mnt/stateful_partition"; |
| const char kMmc[] = "/usr/bin/mmc"; |
| const char kNvme[] = "/usr/sbin/nvme"; |
| |
| } // namespace |
| |
| const std::string StorageTool::GetPartition(const std::string& dst) { |
| std::string::const_reverse_iterator part = dst.rbegin(); |
| |
| if (!isdigit(*part)) |
| return ""; |
| part++; |
| |
| while (part < dst.rend() && isdigit(*part)) |
| part++; |
| |
| return std::string(part.base(), dst.end()); |
| } |
| |
| void StorageTool::StripPartition(base::FilePath* dstPath) { |
| std::string dst = dstPath->value(); |
| std::string part = StorageTool::GetPartition(dst); |
| if (part.empty()) |
| return; |
| |
| size_t location = dst.rfind(part); |
| size_t part_size = part.length(); |
| |
| // For devices matching dm-NN, the digits are not a partition. |
| if (dst.at(location - 1) == '-') |
| return; |
| |
| // For devices that end with a digit, the kernel uses a 'p' |
| // as a separator. E.g., mmcblk1p2 and loop0p1, but not loop0. |
| if (dst.at(location - 1) == 'p') { |
| location--; |
| part_size++; |
| |
| base::FilePath basename = dstPath->BaseName(); |
| std::string bname = basename.value(); |
| if (bname.compare(0, 4, "loop") == 0 && |
| bname.compare(bname.size() - part.length(), part.length(), part) == 0) |
| return; |
| } |
| dst.erase(location, part_size); |
| *dstPath = base::FilePath(dst); |
| } |
| |
| const base::FilePath StorageTool::GetDevice(const base::FilePath& filesystemIn, |
| const base::FilePath& mountsFile) { |
| base::FilePath device; |
| base::ScopedFILE mountinfo(fopen(mountsFile.value().c_str(), "re")); |
| if (!mountinfo) { |
| PLOG(ERROR) << "Failed to open " << mountsFile.value(); |
| return base::FilePath(); |
| } |
| |
| struct mntent mount_entry; |
| char buffer[PATH_MAX]; |
| |
| while (getmntent_r(mountinfo.get(), &mount_entry, buffer, sizeof(buffer))) { |
| const std::string mountpoint = mount_entry.mnt_dir; |
| if (mountpoint == filesystemIn.value()) { |
| device = base::FilePath(mount_entry.mnt_fsname); |
| break; |
| } |
| } |
| |
| StorageTool::StripPartition(&device); |
| return device; |
| } |
| |
| // This function is called by Smartctl to check for ATA devices. |
| // Smartctl is only supported on ATA devices, so this function |
| // will return false when other devices are used. |
| bool StorageTool::IsSupported(const base::FilePath typeFile, |
| const base::FilePath vendFile, |
| std::string* errorMsg) { |
| base::FilePath r; |
| bool link = base::NormalizeFilePath(typeFile, &r); |
| if (!link) { |
| PLOG(ERROR) << "Failed to read device type link"; |
| errorMsg->assign("<Failed to read device type link>"); |
| return false; |
| } |
| |
| size_t target = r.value().find("target"); |
| if (target == -1) { |
| errorMsg->assign("<This feature is not supported>"); |
| return false; |
| } |
| |
| std::string vend; |
| |
| if (!base::ReadFileToString(vendFile, &vend)) { |
| PLOG(ERROR) << "Failed to open " << vendFile.value(); |
| errorMsg->assign("<Failed to open vendor file>"); |
| return false; |
| } |
| |
| if (vend.empty()) { |
| errorMsg->assign("<Failed to find device type>"); |
| return false; |
| } |
| |
| if (vend.compare(0, 3, "ATA") != 0) { |
| errorMsg->assign("<This feature is not supported>"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| std::string StorageTool::Smartctl(const std::string& option) { |
| const base::FilePath device = |
| GetDevice(base::FilePath(kSource), base::FilePath(kMountFile)); |
| |
| if (device.empty()) { |
| LOG(ERROR) << "Failed to find device for " << kSource; |
| return "<Failed to find device>"; |
| } |
| |
| base::FilePath bname = device.BaseName(); |
| |
| std::string path; |
| if (!GetHelperPath("storage", &path)) |
| return "<path too long>"; |
| |
| ProcessWithOutput process; |
| // Disabling sandboxing since smartctl requires higher privileges. |
| process.DisableSandbox(); |
| if (!process.Init()) |
| return "<process init failed>"; |
| |
| if (bname.value().compare(0, 4, "nvme") == 0) { |
| process.AddArg(kSmartctl); |
| |
| if (option == "attributes") |
| process.AddArg("-A"); |
| if (option == "capabilities") |
| process.AddArg("-c"); |
| if (option == "error") |
| process.AddStringOption("-l", "error"); |
| if (option == "abort_test" || option == "health" || option == "selftest" || |
| option == "short_test") |
| return "<Option not supported>"; |
| |
| } else { |
| const base::FilePath dir = |
| base::FilePath("/sys/block/" + bname.value() + "/device/"); |
| const base::FilePath typeFile = dir.Append("type"); |
| const base::FilePath vendFile = dir.Append("vendor"); |
| std::string message; |
| |
| if (!IsSupported(typeFile, vendFile, &message)) { |
| return message; |
| } |
| |
| process.AddArg(kSmartctl); |
| |
| if (option == "abort_test") |
| process.AddArg("-X"); |
| if (option == "attributes") |
| process.AddArg("-A"); |
| if (option == "capabilities") |
| process.AddArg("-c"); |
| if (option == "error") |
| process.AddStringOption("-l", "error"); |
| if (option == "health") |
| process.AddArg("-H"); |
| if (option == "selftest") |
| process.AddStringOption("-l", "selftest"); |
| if (option == "short_test") |
| process.AddStringOption("-t", "short"); |
| } |
| |
| process.AddArg(device.value()); |
| process.Run(); |
| std::string output; |
| process.GetOutput(&output); |
| return output; |
| } |
| |
| std::string StorageTool::Start(const base::ScopedFD& outfd) { |
| const base::FilePath device = |
| GetDevice(base::FilePath(kSource), base::FilePath(kMountFile)); |
| |
| if (device.empty()) { |
| LOG(ERROR) << "Failed to find device for " << kSource; |
| return "<Failed to find device>"; |
| } |
| |
| ProcessWithId* p = |
| CreateProcess(false /* sandboxed */, false /* access_root_mount_ns */); |
| if (!p) |
| return ""; |
| |
| p->AddArg(kBadblocks); |
| p->AddArg("-sv"); |
| p->AddArg(device.value()); |
| p->BindFd(outfd.get(), STDOUT_FILENO); |
| p->BindFd(outfd.get(), STDERR_FILENO); |
| LOG(INFO) << "badblocks: running process id: " << p->id(); |
| p->Start(); |
| return p->id(); |
| } |
| |
| std::string StorageTool::Mmc(const std::string& option) { |
| ProcessWithOutput process; |
| process.DisableSandbox(); |
| if (!process.Init()) |
| return "<process init failed>"; |
| |
| process.AddArg(kMmc); |
| |
| if (option == "extcsd_read") { |
| process.AddArg("extcsd"); |
| process.AddArg("read"); |
| } else if (option == "extcsd_dump") { |
| process.AddArg("extcsd"); |
| process.AddArg("dump"); |
| } else { |
| return "<Option not supported>"; |
| } |
| |
| const base::FilePath rootdev = |
| GetDevice(base::FilePath(kSource), base::FilePath(kMountFile)); |
| process.AddArg(rootdev.value()); |
| process.Run(); |
| std::string output; |
| process.GetOutput(&output); |
| return output; |
| } |
| |
| std::string StorageTool::Nvme(const std::string& option) { |
| ProcessWithOutput process; |
| // Disabling sandboxing since nvme requires higher privileges. |
| process.DisableSandbox(); |
| if (!process.Init()) |
| return "<process init failed>"; |
| |
| process.AddArg(kNvme); |
| |
| if (option == "identify_controller") { |
| process.AddArg("id-ctrl"); |
| process.AddArg("--vendor-specific"); |
| } else if (option == "short_self_test") { |
| // Command for selftest |
| process.AddArg("device-self-test"); |
| // Namespace of NVMe |
| process.AddArg("-n 1"); |
| // type of selftest: short |
| process.AddArg("-s 1"); |
| } else if (option == "long_self_test") { |
| // command for selftest |
| process.AddArg("device-self-test"); |
| // Namespace of NVMe |
| process.AddArg("-n 1"); |
| // type of selftest: long |
| process.AddArg("-s 2"); |
| } else if (option == "stop_self_test") { |
| // command for selftest |
| process.AddArg("device-self-test"); |
| // Namespace of NVMe |
| process.AddArg("-n 1"); |
| // type of selftest: abort |
| process.AddArg("-s 0xf"); |
| } else { |
| return "<Option not supported>"; |
| } |
| |
| const base::FilePath rootdev = |
| GetDevice(base::FilePath(kSource), base::FilePath(kMountFile)); |
| process.AddArg(rootdev.value()); |
| process.Run(); |
| std::string output; |
| process.GetOutput(&output); |
| return output; |
| } |
| |
| std::string StorageTool::NvmeLog(const uint32_t& page_id, |
| const uint32_t& length, |
| bool raw_binary) { |
| ProcessWithOutput process; |
| // Disabling sandboxing since nvme requires higher privileges. |
| process.DisableSandbox(); |
| if (!process.Init()) |
| return "<process init failed>"; |
| |
| process.AddArg(kNvme); |
| process.AddArg("get-log"); |
| |
| // Log page ID ranging from 0 to 255. |
| if (page_id <= 0xff) { |
| process.AddArg(base::StringPrintf("--log-id=%u", page_id)); |
| } else { |
| return "<Page ID invalid>"; |
| } |
| |
| // Length of byte-data must be larger than 3. |
| if (length >= 4) { |
| process.AddArg(base::StringPrintf("--log-len=%u", length)); |
| } else { |
| return "<Length of byte-data invalid. At least 4 bytes for a request>"; |
| } |
| |
| // Output in raw format. |
| if (raw_binary) { |
| process.AddArg("--raw-binary"); |
| } |
| |
| const base::FilePath rootdev = |
| GetDevice(base::FilePath(kSource), base::FilePath(kMountFile)); |
| process.AddArg(rootdev.value()); |
| process.Run(); |
| std::string output; |
| process.GetOutput(&output); |
| |
| if (raw_binary) { |
| std::string input = std::move(output); |
| // Encode output as base64 in case D-Bus drops invalid UTF8 string. |
| base::Base64Encode(input, &output); |
| } |
| |
| return output; |
| } |
| |
| } // namespace debugd |