blob: 6d9e8ed9dedf697008b5f76d3b348aa401c99999 [file] [log] [blame]
// 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