| // 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 <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 <fstream> |
| #include <iostream> |
| #include <linux/limits.h> |
| #include <mntent.h> |
| #include <string> |
| #include <unistd.h> |
| #include <vector> |
| |
| #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 base::FilePath::CharType kMountFile[] = |
| FILE_PATH_LITERAL("/proc/mounts"); |
| const base::FilePath::CharType kSource[] = |
| FILE_PATH_LITERAL("/mnt/stateful_partition"); |
| |
| } // 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 (!SandboxedProcess::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 dbus::FileDescriptor& 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); |
| if (!p) |
| return ""; |
| |
| p->AddArg(kBadblocks); |
| p->AddArg("-sv"); |
| p->AddArg(device.value()); |
| p->BindFd(outfd.value(), STDOUT_FILENO); |
| p->BindFd(outfd.value(), STDERR_FILENO); |
| LOG(INFO) << "badblocks: running process id: " << p->id(); |
| p->Start(); |
| return p->id(); |
| } |
| |
| } // namespace debugd |