blob: 6d9e8ed9dedf697008b5f76d3b348aa401c99999 [file] [log] [blame]
Gediminas Ramanauskas2f0b8852013-03-14 13:52:32 -07001// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Alex Vakulenko262be3f2014-07-30 15:25:50 -07005#include "debugd/src/storage_tool.h"
Gediminas Ramanauskas2f0b8852013-03-14 13:52:32 -07006
Alexis Saverye39d3492017-11-07 18:10:08 -08007#include <fstream>
8#include <iostream>
9#include <linux/limits.h>
10#include <mntent.h>
11#include <string>
12#include <unistd.h>
Wayne Kung48d36542020-02-17 15:24:21 +080013#include <utility>
Alexis Saverye39d3492017-11-07 18:10:08 -080014#include <vector>
15
Wayne Kung48d36542020-02-17 15:24:21 +080016#include <base/base64.h>
17#include <base/files/file.h>
18#include <base/files/file_path.h>
19#include <base/files/file_util.h>
20#include <base/strings/string_split.h>
21#include <base/strings/string_util.h>
22
Hardik Goyalb09d6b02019-08-13 16:15:50 -070023#include "debugd/src/helper_utils.h"
Alex Vakulenko262be3f2014-07-30 15:25:50 -070024#include "debugd/src/process_with_id.h"
25#include "debugd/src/process_with_output.h"
Gediminas Ramanauskas2f0b8852013-03-14 13:52:32 -070026
Gediminas Ramanauskas2f0b8852013-03-14 13:52:32 -070027namespace debugd {
28
Ben Chanaf125862017-02-08 23:11:18 -080029namespace {
30
31const char kSmartctl[] = "/usr/sbin/smartctl";
32const char kBadblocks[] = "/sbin/badblocks";
Kartik Hegde0227a062018-10-03 16:48:04 -060033const char kMountFile[] = "/proc/1/mounts";
Satoru Takabayashicf730fb2018-08-03 16:42:36 +090034const char kSource[] = "/mnt/stateful_partition";
Ting Shen92dd7832018-12-21 17:45:07 +080035const char kMmc[] = "/usr/bin/mmc";
Oleh Lamzinfb868682019-09-03 19:23:28 +020036const char kNvme[] = "/usr/sbin/nvme";
Ben Chanaf125862017-02-08 23:11:18 -080037
38} // namespace
Gediminas Ramanauskas2f0b8852013-03-14 13:52:32 -070039
Alexis Saverye39d3492017-11-07 18:10:08 -080040const std::string StorageTool::GetPartition(const std::string& dst) {
41 std::string::const_reverse_iterator part = dst.rbegin();
42
43 if (!isdigit(*part))
44 return "";
45 part++;
46
47 while (part < dst.rend() && isdigit(*part))
48 part++;
49
50 return std::string(part.base(), dst.end());
51}
52
53void StorageTool::StripPartition(base::FilePath* dstPath) {
54 std::string dst = dstPath->value();
55 std::string part = StorageTool::GetPartition(dst);
56 if (part.empty())
57 return;
58
59 size_t location = dst.rfind(part);
60 size_t part_size = part.length();
61
62 // For devices matching dm-NN, the digits are not a partition.
63 if (dst.at(location - 1) == '-')
64 return;
65
66 // For devices that end with a digit, the kernel uses a 'p'
67 // as a separator. E.g., mmcblk1p2 and loop0p1, but not loop0.
68 if (dst.at(location - 1) == 'p') {
69 location--;
70 part_size++;
71
72 base::FilePath basename = dstPath->BaseName();
73 std::string bname = basename.value();
Oleh Lamzinfb868682019-09-03 19:23:28 +020074 if (bname.compare(0, 4, "loop") == 0 &&
75 bname.compare(bname.size() - part.length(), part.length(), part) == 0)
Alexis Saverye39d3492017-11-07 18:10:08 -080076 return;
77 }
78 dst.erase(location, part_size);
79 *dstPath = base::FilePath(dst);
80}
81
82const base::FilePath StorageTool::GetDevice(const base::FilePath& filesystemIn,
83 const base::FilePath& mountsFile) {
84 base::FilePath device;
85 base::ScopedFILE mountinfo(fopen(mountsFile.value().c_str(), "re"));
86 if (!mountinfo) {
87 PLOG(ERROR) << "Failed to open " << mountsFile.value();
88 return base::FilePath();
89 }
90
91 struct mntent mount_entry;
92 char buffer[PATH_MAX];
93
94 while (getmntent_r(mountinfo.get(), &mount_entry, buffer, sizeof(buffer))) {
95 const std::string mountpoint = mount_entry.mnt_dir;
96 if (mountpoint == filesystemIn.value()) {
97 device = base::FilePath(mount_entry.mnt_fsname);
98 break;
99 }
100 }
101
102 StorageTool::StripPartition(&device);
103 return device;
104}
105
106// This function is called by Smartctl to check for ATA devices.
107// Smartctl is only supported on ATA devices, so this function
108// will return false when other devices are used.
109bool StorageTool::IsSupported(const base::FilePath typeFile,
110 const base::FilePath vendFile,
111 std::string* errorMsg) {
112 base::FilePath r;
113 bool link = base::NormalizeFilePath(typeFile, &r);
114 if (!link) {
115 PLOG(ERROR) << "Failed to read device type link";
116 errorMsg->assign("<Failed to read device type link>");
117 return false;
118 }
119
120 size_t target = r.value().find("target");
121 if (target == -1) {
122 errorMsg->assign("<This feature is not supported>");
123 return false;
124 }
125
126 std::string vend;
127
128 if (!base::ReadFileToString(vendFile, &vend)) {
129 PLOG(ERROR) << "Failed to open " << vendFile.value();
130 errorMsg->assign("<Failed to open vendor file>");
131 return false;
132 }
133
134 if (vend.empty()) {
135 errorMsg->assign("<Failed to find device type>");
136 return false;
137 }
138
139 if (vend.compare(0, 3, "ATA") != 0) {
140 errorMsg->assign("<This feature is not supported>");
141 return false;
142 }
143
144 return true;
145}
146
Eric Carusoc93a15c2017-04-24 16:15:12 -0700147std::string StorageTool::Smartctl(const std::string& option) {
Oleh Lamzinfb868682019-09-03 19:23:28 +0200148 const base::FilePath device =
149 GetDevice(base::FilePath(kSource), base::FilePath(kMountFile));
Alexis Saverye39d3492017-11-07 18:10:08 -0800150
151 if (device.empty()) {
152 LOG(ERROR) << "Failed to find device for " << kSource;
153 return "<Failed to find device>";
154 }
155
156 base::FilePath bname = device.BaseName();
157
Ben Chan297c3c22013-07-17 17:34:12 -0700158 std::string path;
Hardik Goyalb09d6b02019-08-13 16:15:50 -0700159 if (!GetHelperPath("storage", &path))
Gediminas Ramanauskas2f0b8852013-03-14 13:52:32 -0700160 return "<path too long>";
161
162 ProcessWithOutput process;
163 // Disabling sandboxing since smartctl requires higher privileges.
164 process.DisableSandbox();
165 if (!process.Init())
166 return "<process init failed>";
167
Alexis Saverye39d3492017-11-07 18:10:08 -0800168 if (bname.value().compare(0, 4, "nvme") == 0) {
169 process.AddArg(kSmartctl);
Gediminas Ramanauskas2f0b8852013-03-14 13:52:32 -0700170
Alexis Saverye39d3492017-11-07 18:10:08 -0800171 if (option == "attributes")
172 process.AddArg("-A");
173 if (option == "capabilities")
174 process.AddArg("-c");
175 if (option == "error")
176 process.AddStringOption("-l", "error");
Oleh Lamzinfb868682019-09-03 19:23:28 +0200177 if (option == "abort_test" || option == "health" || option == "selftest" ||
178 option == "short_test")
Alexis Saverye39d3492017-11-07 18:10:08 -0800179 return "<Option not supported>";
Gediminas Ramanauskas2f0b8852013-03-14 13:52:32 -0700180
Alexis Saverye39d3492017-11-07 18:10:08 -0800181 } else {
Oleh Lamzinfb868682019-09-03 19:23:28 +0200182 const base::FilePath dir =
183 base::FilePath("/sys/block/" + bname.value() + "/device/");
Alexis Saverye39d3492017-11-07 18:10:08 -0800184 const base::FilePath typeFile = dir.Append("type");
185 const base::FilePath vendFile = dir.Append("vendor");
186 std::string message;
187
188 if (!IsSupported(typeFile, vendFile, &message)) {
189 return message;
190 }
191
192 process.AddArg(kSmartctl);
193
194 if (option == "abort_test")
195 process.AddArg("-X");
196 if (option == "attributes")
197 process.AddArg("-A");
198 if (option == "capabilities")
199 process.AddArg("-c");
200 if (option == "error")
201 process.AddStringOption("-l", "error");
202 if (option == "health")
203 process.AddArg("-H");
204 if (option == "selftest")
205 process.AddStringOption("-l", "selftest");
206 if (option == "short_test")
207 process.AddStringOption("-t", "short");
208 }
209
210 process.AddArg(device.value());
Gediminas Ramanauskas2f0b8852013-03-14 13:52:32 -0700211 process.Run();
212 std::string output;
213 process.GetOutput(&output);
214 return output;
215}
216
Eric Caruso0b241882018-04-04 13:43:46 -0700217std::string StorageTool::Start(const base::ScopedFD& outfd) {
Oleh Lamzinfb868682019-09-03 19:23:28 +0200218 const base::FilePath device =
219 GetDevice(base::FilePath(kSource), base::FilePath(kMountFile));
Alexis Saverye39d3492017-11-07 18:10:08 -0800220
221 if (device.empty()) {
222 LOG(ERROR) << "Failed to find device for " << kSource;
223 return "<Failed to find device>";
224 }
225
Ben Chan0d840a72018-09-27 00:24:48 -0700226 ProcessWithId* p =
227 CreateProcess(false /* sandboxed */, false /* access_root_mount_ns */);
Gediminas Ramanauskas2f0b8852013-03-14 13:52:32 -0700228 if (!p)
229 return "";
230
231 p->AddArg(kBadblocks);
232 p->AddArg("-sv");
Alexis Saverye39d3492017-11-07 18:10:08 -0800233 p->AddArg(device.value());
Eric Caruso0b241882018-04-04 13:43:46 -0700234 p->BindFd(outfd.get(), STDOUT_FILENO);
235 p->BindFd(outfd.get(), STDERR_FILENO);
Gediminas Ramanauskas2f0b8852013-03-14 13:52:32 -0700236 LOG(INFO) << "badblocks: running process id: " << p->id();
237 p->Start();
238 return p->id();
239}
240
Ting Shen92dd7832018-12-21 17:45:07 +0800241std::string StorageTool::Mmc(const std::string& option) {
242 ProcessWithOutput process;
243 process.DisableSandbox();
244 if (!process.Init())
245 return "<process init failed>";
246
247 process.AddArg(kMmc);
248
249 if (option == "extcsd_read") {
250 process.AddArg("extcsd");
251 process.AddArg("read");
252 } else if (option == "extcsd_dump") {
253 process.AddArg("extcsd");
254 process.AddArg("dump");
255 } else {
256 return "<Option not supported>";
257 }
258
Oleh Lamzinfb868682019-09-03 19:23:28 +0200259 const base::FilePath rootdev =
260 GetDevice(base::FilePath(kSource), base::FilePath(kMountFile));
261 process.AddArg(rootdev.value());
262 process.Run();
263 std::string output;
264 process.GetOutput(&output);
265 return output;
266}
267
268std::string StorageTool::Nvme(const std::string& option) {
269 ProcessWithOutput process;
270 // Disabling sandboxing since nvme requires higher privileges.
271 process.DisableSandbox();
272 if (!process.Init())
273 return "<process init failed>";
274
275 process.AddArg(kNvme);
276
277 if (option == "identify_controller") {
278 process.AddArg("id-ctrl");
279 process.AddArg("--vendor-specific");
Wayne Kung8a786fb2019-12-24 12:19:11 +0800280 } else if (option == "short_self_test") {
281 // Command for selftest
282 process.AddArg("device-self-test");
283 // Namespace of NVMe
284 process.AddArg("-n 1");
285 // type of selftest: short
286 process.AddArg("-s 1");
287 } else if (option == "long_self_test") {
288 // command for selftest
289 process.AddArg("device-self-test");
290 // Namespace of NVMe
291 process.AddArg("-n 1");
292 // type of selftest: long
293 process.AddArg("-s 2");
Wayne Kungdb4fc582020-02-18 14:46:27 +0800294 } else if (option == "stop_self_test") {
295 // command for selftest
296 process.AddArg("device-self-test");
297 // Namespace of NVMe
298 process.AddArg("-n 1");
299 // type of selftest: abort
300 process.AddArg("-s 0xf");
Oleh Lamzinfb868682019-09-03 19:23:28 +0200301 } else {
302 return "<Option not supported>";
303 }
304
305 const base::FilePath rootdev =
306 GetDevice(base::FilePath(kSource), base::FilePath(kMountFile));
Ting Shen92dd7832018-12-21 17:45:07 +0800307 process.AddArg(rootdev.value());
308 process.Run();
309 std::string output;
310 process.GetOutput(&output);
311 return output;
312}
313
Wayne Kung8a786fb2019-12-24 12:19:11 +0800314std::string StorageTool::NvmeLog(const uint32_t& page_id,
315 const uint32_t& length,
316 bool raw_binary) {
317 ProcessWithOutput process;
318 // Disabling sandboxing since nvme requires higher privileges.
319 process.DisableSandbox();
320 if (!process.Init())
321 return "<process init failed>";
322
323 process.AddArg(kNvme);
324 process.AddArg("get-log");
325
326 // Log page ID ranging from 0 to 255.
327 if (page_id <= 0xff) {
328 process.AddArg(base::StringPrintf("--log-id=%u", page_id));
329 } else {
330 return "<Page ID invalid>";
331 }
332
333 // Length of byte-data must be larger than 3.
334 if (length >= 4) {
335 process.AddArg(base::StringPrintf("--log-len=%u", length));
336 } else {
337 return "<Length of byte-data invalid. At least 4 bytes for a request>";
338 }
339
Wayne Kung48d36542020-02-17 15:24:21 +0800340 // Output in raw format.
Wayne Kung8a786fb2019-12-24 12:19:11 +0800341 if (raw_binary) {
342 process.AddArg("--raw-binary");
343 }
344
345 const base::FilePath rootdev =
346 GetDevice(base::FilePath(kSource), base::FilePath(kMountFile));
347 process.AddArg(rootdev.value());
348 process.Run();
349 std::string output;
350 process.GetOutput(&output);
Wayne Kung48d36542020-02-17 15:24:21 +0800351
352 if (raw_binary) {
353 std::string input = std::move(output);
354 // Encode output as base64 in case D-Bus drops invalid UTF8 string.
355 base::Base64Encode(input, &output);
356 }
357
Wayne Kung8a786fb2019-12-24 12:19:11 +0800358 return output;
359}
360
Ben Chana0011d82014-05-13 00:19:29 -0700361} // namespace debugd