blob: e138f14fa658bd5331702207ee436b98c80d79e9 [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
Chung-Sheng Wu560f3592021-04-28 17:30:41 +0800277 const base::FilePath rootdev =
278 GetDevice(base::FilePath(kSource), base::FilePath(kMountFile));
Oleh Lamzinfb868682019-09-03 19:23:28 +0200279 if (option == "identify_controller") {
280 process.AddArg("id-ctrl");
281 process.AddArg("--vendor-specific");
Chung-Sheng Wu560f3592021-04-28 17:30:41 +0800282 process.AddArg(rootdev.value());
Wayne Kung8a786fb2019-12-24 12:19:11 +0800283 } else if (option == "short_self_test") {
284 // Command for selftest
285 process.AddArg("device-self-test");
286 // Namespace of NVMe
Chung-Sheng Wu560f3592021-04-28 17:30:41 +0800287 process.AddStringOption("-n", "1");
Wayne Kung8a786fb2019-12-24 12:19:11 +0800288 // type of selftest: short
Chung-Sheng Wu560f3592021-04-28 17:30:41 +0800289 process.AddStringOption("-s", "1");
290 process.AddArg(rootdev.value());
Wayne Kung8a786fb2019-12-24 12:19:11 +0800291 } else if (option == "long_self_test") {
292 // command for selftest
293 process.AddArg("device-self-test");
294 // Namespace of NVMe
Chung-Sheng Wu560f3592021-04-28 17:30:41 +0800295 process.AddStringOption("-n", "1");
Wayne Kung8a786fb2019-12-24 12:19:11 +0800296 // type of selftest: long
Chung-Sheng Wu560f3592021-04-28 17:30:41 +0800297 process.AddStringOption("-s", "2");
298 process.AddArg(rootdev.value());
Wayne Kungdb4fc582020-02-18 14:46:27 +0800299 } else if (option == "stop_self_test") {
300 // command for selftest
301 process.AddArg("device-self-test");
302 // Namespace of NVMe
Chung-Sheng Wu560f3592021-04-28 17:30:41 +0800303 process.AddStringOption("-n", "1");
Wayne Kungdb4fc582020-02-18 14:46:27 +0800304 // type of selftest: abort
Chung-Sheng Wu560f3592021-04-28 17:30:41 +0800305 process.AddStringOption("-s", "0xf");
306 process.AddArg(rootdev.value());
307 } else if (option == "list") {
308 // command for list all the nvme devices and their properties
309 process.AddArg("list");
310 // output format json
311 process.AddStringOption("-o", "json");
Oleh Lamzinfb868682019-09-03 19:23:28 +0200312 } else {
313 return "<Option not supported>";
314 }
315
Ting Shen92dd7832018-12-21 17:45:07 +0800316 process.Run();
317 std::string output;
318 process.GetOutput(&output);
319 return output;
320}
321
Wayne Kung8a786fb2019-12-24 12:19:11 +0800322std::string StorageTool::NvmeLog(const uint32_t& page_id,
323 const uint32_t& length,
324 bool raw_binary) {
325 ProcessWithOutput process;
326 // Disabling sandboxing since nvme requires higher privileges.
327 process.DisableSandbox();
328 if (!process.Init())
329 return "<process init failed>";
330
331 process.AddArg(kNvme);
332 process.AddArg("get-log");
333
334 // Log page ID ranging from 0 to 255.
335 if (page_id <= 0xff) {
336 process.AddArg(base::StringPrintf("--log-id=%u", page_id));
337 } else {
338 return "<Page ID invalid>";
339 }
340
341 // Length of byte-data must be larger than 3.
342 if (length >= 4) {
343 process.AddArg(base::StringPrintf("--log-len=%u", length));
344 } else {
345 return "<Length of byte-data invalid. At least 4 bytes for a request>";
346 }
347
Wayne Kung48d36542020-02-17 15:24:21 +0800348 // Output in raw format.
Wayne Kung8a786fb2019-12-24 12:19:11 +0800349 if (raw_binary) {
350 process.AddArg("--raw-binary");
351 }
352
353 const base::FilePath rootdev =
354 GetDevice(base::FilePath(kSource), base::FilePath(kMountFile));
355 process.AddArg(rootdev.value());
356 process.Run();
357 std::string output;
358 process.GetOutput(&output);
Wayne Kung48d36542020-02-17 15:24:21 +0800359
360 if (raw_binary) {
361 std::string input = std::move(output);
362 // Encode output as base64 in case D-Bus drops invalid UTF8 string.
363 base::Base64Encode(input, &output);
364 }
365
Wayne Kung8a786fb2019-12-24 12:19:11 +0800366 return output;
367}
368
Ben Chana0011d82014-05-13 00:19:29 -0700369} // namespace debugd