]> git.proxmox.com Git - ceph.git/blame - ceph/src/common/blkdev.cc
update source to Ceph Pacific 16.2.2
[ceph.git] / ceph / src / common / blkdev.cc
CommitLineData
11fdf7f2
TL
1// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2// vim: ts=8 sw=2 smarttab
7c673cae
FG
3/*
4 * Ceph - scalable distributed file system
5 *
6 * Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
7 *
8 * This is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
11fdf7f2 10 * License version 2.1, as published by the Free Software
7c673cae 11 * Foundation. See file COPYING.
11fdf7f2 12 *
7c673cae 13 */
31f18b77 14
11fdf7f2
TL
15#include "include/compat.h"
16
17#ifdef __FreeBSD__
18#include <sys/param.h>
19#include <geom/geom_disk.h>
20#include <sys/disk.h>
21#include <fcntl.h>
22#endif
23
7c673cae 24#include <errno.h>
7c673cae
FG
25#include <sys/ioctl.h>
26#include <sys/stat.h>
11fdf7f2
TL
27#include <sys/types.h>
28#include <sys/stat.h>
29#include <fcntl.h>
7c673cae 30#include <dirent.h>
11fdf7f2
TL
31#include <boost/algorithm/string/replace.hpp>
32//#include "common/debug.h"
33#include "include/scope_guard.h"
7c673cae 34#include "include/uuid.h"
11fdf7f2
TL
35#include "include/stringify.h"
36#include "blkdev.h"
37#include "numa.h"
38
39#include "json_spirit/json_spirit_reader.h"
40
f67539c2 41
11fdf7f2
TL
42int get_device_by_path(const char *path, char* partition, char* device,
43 size_t max)
44{
45 int fd = ::open(path, O_RDONLY|O_DIRECTORY);
46 if (fd < 0) {
47 return -errno;
48 }
49 auto close_fd = make_scope_guard([fd] {
50 ::close(fd);
51 });
52 BlkDev blkdev(fd);
53 if (auto ret = blkdev.partition(partition, max); ret) {
54 return ret;
55 }
56 if (auto ret = blkdev.wholedisk(device, max); ret) {
57 return ret;
58 }
59 return 0;
60}
61
62
63#include "common/blkdev.h"
7c673cae
FG
64
65#ifdef __linux__
11fdf7f2 66#include <libudev.h>
7c673cae 67#include <linux/fs.h>
11fdf7f2 68#include <linux/kdev_t.h>
7c673cae
FG
69#include <blkid/blkid.h>
70
11fdf7f2
TL
71#include <set>
72
73#include "common/SubProcess.h"
74#include "common/errno.h"
75
76
7c673cae
FG
77#define UUID_LEN 36
78
11fdf7f2
TL
79#endif
80
f67539c2
TL
81using namespace std::literals;
82
83using std::string;
84
85using ceph::bufferlist;
86
7c673cae 87
11fdf7f2
TL
88BlkDev::BlkDev(int f)
89 : fd(f)
90{}
91
92BlkDev::BlkDev(const std::string& devname)
93 : devname(devname)
94{}
95
96int BlkDev::get_devid(dev_t *id) const
7c673cae 97{
11fdf7f2
TL
98 struct stat st;
99 int r;
100 if (fd >= 0) {
101 r = fstat(fd, &st);
102 } else {
103 char path[PATH_MAX];
104 snprintf(path, sizeof(path), "/dev/%s", devname.c_str());
105 r = stat(path, &st);
106 }
107 if (r < 0) {
108 return -errno;
109 }
110 *id = S_ISBLK(st.st_mode) ? st.st_rdev : st.st_dev;
111 return 0;
112}
113
114#ifdef __linux__
11fdf7f2
TL
115
116const char *BlkDev::sysfsdir() const {
117 return "/sys";
7c673cae
FG
118}
119
11fdf7f2 120int BlkDev::get_size(int64_t *psize) const
7c673cae
FG
121{
122#ifdef BLKGETSIZE64
123 int ret = ::ioctl(fd, BLKGETSIZE64, psize);
124#elif defined(BLKGETSIZE)
125 unsigned long sectors = 0;
126 int ret = ::ioctl(fd, BLKGETSIZE, &sectors);
127 *psize = sectors * 512ULL;
128#else
129// cppcheck-suppress preprocessorErrorDirective
11fdf7f2 130# error "Linux configuration error (get_size)"
7c673cae
FG
131#endif
132 if (ret < 0)
133 ret = -errno;
134 return ret;
135}
136
7c673cae
FG
137/**
138 * get a block device property as a string
139 *
140 * store property in *val, up to maxlen chars
141 * return 0 on success
142 * return negative error on error
143 */
9f95a23c 144int64_t BlkDev::get_string_property(const char* prop,
11fdf7f2 145 char *val, size_t maxlen) const
7c673cae 146{
11fdf7f2
TL
147 char filename[PATH_MAX], wd[PATH_MAX];
148 const char* dev = nullptr;
11fdf7f2
TL
149
150 if (fd >= 0) {
151 // sysfs isn't fully populated for partitions, so we need to lookup the sysfs
152 // entry for the underlying whole disk.
153 if (int r = wholedisk(wd, sizeof(wd)); r < 0)
154 return r;
155 dev = wd;
156 } else {
157 dev = devname.c_str();
158 }
9f95a23c
TL
159 if (snprintf(filename, sizeof(filename), "%s/block/%s/%s", sysfsdir(), dev,
160 prop) >= static_cast<int>(sizeof(filename))) {
161 return -ERANGE;
162 }
7c673cae
FG
163
164 FILE *fp = fopen(filename, "r");
165 if (fp == NULL) {
166 return -errno;
167 }
168
169 int r = 0;
170 if (fgets(val, maxlen - 1, fp)) {
171 // truncate at newline
172 char *p = val;
173 while (*p && *p != '\n')
174 ++p;
175 *p = 0;
176 } else {
177 r = -EINVAL;
178 }
179 fclose(fp);
180 return r;
181}
182
183/**
184 * get a block device property
185 *
186 * return the value (we assume it is positive)
187 * return negative error on error
188 */
9f95a23c 189int64_t BlkDev::get_int_property(const char* prop) const
7c673cae
FG
190{
191 char buff[256] = {0};
11fdf7f2 192 int r = get_string_property(prop, buff, sizeof(buff));
7c673cae
FG
193 if (r < 0)
194 return r;
195 // take only digits
196 for (char *p = buff; *p; ++p) {
197 if (!isdigit(*p)) {
198 *p = 0;
199 break;
200 }
201 }
202 char *endptr = 0;
203 r = strtoll(buff, &endptr, 10);
204 if (endptr != buff + strlen(buff))
205 r = -EINVAL;
206 return r;
207}
208
11fdf7f2 209bool BlkDev::support_discard() const
7c673cae 210{
9f95a23c 211 return get_int_property("queue/discard_granularity") > 0;
7c673cae
FG
212}
213
11fdf7f2 214int BlkDev::discard(int64_t offset, int64_t len) const
7c673cae
FG
215{
216 uint64_t range[2] = {(uint64_t)offset, (uint64_t)len};
217 return ioctl(fd, BLKDISCARD, range);
218}
219
11fdf7f2 220bool BlkDev::is_rotational() const
7c673cae 221{
9f95a23c 222 return get_int_property("queue/rotational") > 0;
7c673cae
FG
223}
224
11fdf7f2 225int BlkDev::get_numa_node(int *node) const
7c673cae 226{
9f95a23c 227 int numa = get_int_property("device/device/numa_node");
11fdf7f2
TL
228 if (numa < 0)
229 return -1;
230 *node = numa;
231 return 0;
7c673cae
FG
232}
233
11fdf7f2 234int BlkDev::dev(char *dev, size_t max) const
7c673cae 235{
9f95a23c 236 return get_string_property("dev", dev, max);
11fdf7f2 237}
7c673cae 238
11fdf7f2
TL
239int BlkDev::vendor(char *vendor, size_t max) const
240{
9f95a23c 241 return get_string_property("device/device/vendor", vendor, max);
11fdf7f2 242}
7c673cae 243
11fdf7f2
TL
244int BlkDev::model(char *model, size_t max) const
245{
9f95a23c 246 return get_string_property("device/model", model, max);
11fdf7f2
TL
247}
248
249int BlkDev::serial(char *serial, size_t max) const
250{
9f95a23c 251 return get_string_property("device/serial", serial, max);
11fdf7f2
TL
252}
253
254int BlkDev::partition(char *partition, size_t max) const
255{
256 dev_t id;
257 int r = get_devid(&id);
258 if (r < 0)
259 return -EINVAL; // hrm.
260
261 char *t = blkid_devno_to_devname(id);
262 if (!t) {
263 return -EINVAL;
264 }
265 strncpy(partition, t, max);
266 free(t);
267 return 0;
268}
269
270int BlkDev::wholedisk(char *device, size_t max) const
271{
272 dev_t id;
273 int r = get_devid(&id);
274 if (r < 0)
275 return -EINVAL; // hrm.
276
277 r = blkid_devno_to_wholedisk(id, device, max, nullptr);
278 if (r < 0) {
7c673cae 279 return -EINVAL;
11fdf7f2
TL
280 }
281 return 0;
282}
283
284static int easy_readdir(const std::string& dir, std::set<std::string> *out)
285{
286 DIR *h = ::opendir(dir.c_str());
287 if (!h) {
288 return -errno;
289 }
290 struct dirent *de = nullptr;
291 while ((de = ::readdir(h))) {
292 if (strcmp(de->d_name, ".") == 0 ||
293 strcmp(de->d_name, "..") == 0) {
294 continue;
295 }
296 out->insert(de->d_name);
297 }
298 closedir(h);
299 return 0;
300}
7c673cae 301
11fdf7f2
TL
302void get_dm_parents(const std::string& dev, std::set<std::string> *ls)
303{
304 std::string p = std::string("/sys/block/") + dev + "/slaves";
305 std::set<std::string> parents;
306 easy_readdir(p, &parents);
307 for (auto& d : parents) {
308 ls->insert(d);
309 // recurse in case it is dm-on-dm
310 if (d.find("dm-") == 0) {
311 get_dm_parents(d, ls);
312 }
313 }
314}
315
316void get_raw_devices(const std::string& in,
317 std::set<std::string> *ls)
318{
319 if (in.substr(0, 3) == "dm-") {
320 std::set<std::string> o;
321 get_dm_parents(in, &o);
322 for (auto& d : o) {
323 get_raw_devices(d, ls);
324 }
325 } else {
326 BlkDev d(in);
327 std::string wholedisk;
328 if (d.wholedisk(&wholedisk) == 0) {
329 ls->insert(wholedisk);
7c673cae 330 } else {
11fdf7f2
TL
331 ls->insert(in);
332 }
333 }
334}
335
336int _get_vdo_stats_handle(const char *devname, std::string *vdo_name)
337{
338 int vdo_fd = -1;
339
340 // we need to go from the raw devname (e.g., dm-4) to the VDO volume name.
341 // currently the best way seems to be to look at /dev/mapper/* ...
342 std::string expect = std::string("../") + devname; // expected symlink target
343 DIR *dir = ::opendir("/dev/mapper");
344 if (!dir) {
345 return -1;
346 }
347 struct dirent *de = nullptr;
348 while ((de = ::readdir(dir))) {
349 if (de->d_name[0] == '.')
350 continue;
351 char fn[4096], target[4096];
352 snprintf(fn, sizeof(fn), "/dev/mapper/%s", de->d_name);
353 int r = readlink(fn, target, sizeof(target));
354 if (r < 0 || r >= (int)sizeof(target))
355 continue;
356 target[r] = 0;
357 if (expect == target) {
358 snprintf(fn, sizeof(fn), "/sys/kvdo/%s/statistics", de->d_name);
359 vdo_fd = ::open(fn, O_RDONLY|O_CLOEXEC); //DIRECTORY);
360 if (vdo_fd >= 0) {
361 *vdo_name = de->d_name;
362 break;
363 }
364 }
365 }
366 closedir(dir);
367 return vdo_fd;
368}
369
370int get_vdo_stats_handle(const char *devname, std::string *vdo_name)
371{
372 std::set<std::string> devs = { devname };
373 while (!devs.empty()) {
374 std::string dev = *devs.begin();
375 devs.erase(devs.begin());
376 int fd = _get_vdo_stats_handle(dev.c_str(), vdo_name);
377 if (fd >= 0) {
378 // yay, it's vdo
379 return fd;
380 }
381 // ok, see if there are constituent devices
382 if (dev.find("dm-") == 0) {
383 get_dm_parents(dev, &devs);
384 }
385 }
386 return -1;
387}
388
389int64_t get_vdo_stat(int vdo_fd, const char *property)
390{
391 int64_t ret = 0;
392 int fd = ::openat(vdo_fd, property, O_RDONLY|O_CLOEXEC);
393 if (fd < 0) {
394 return 0;
395 }
396 char buf[1024];
397 int r = ::read(fd, buf, sizeof(buf) - 1);
398 if (r > 0) {
399 buf[r] = 0;
400 ret = atoll(buf);
401 }
402 TEMP_FAILURE_RETRY(::close(fd));
403 return ret;
404}
405
406bool get_vdo_utilization(int fd, uint64_t *total, uint64_t *avail)
407{
408 int64_t block_size = get_vdo_stat(fd, "block_size");
409 int64_t physical_blocks = get_vdo_stat(fd, "physical_blocks");
410 int64_t overhead_blocks_used = get_vdo_stat(fd, "overhead_blocks_used");
411 int64_t data_blocks_used = get_vdo_stat(fd, "data_blocks_used");
412 if (!block_size
413 || !physical_blocks
414 || !overhead_blocks_used
415 || !data_blocks_used) {
416 return false;
417 }
418 int64_t avail_blocks =
419 physical_blocks - overhead_blocks_used - data_blocks_used;
420 *total = block_size * physical_blocks;
421 *avail = block_size * avail_blocks;
422 return true;
423}
424
425std::string _decode_model_enc(const std::string& in)
426{
427 auto v = boost::replace_all_copy(in, "\\x20", " ");
428 if (auto found = v.find_last_not_of(" "); found != v.npos) {
429 v.erase(found + 1);
430 }
431 std::replace(v.begin(), v.end(), ' ', '_');
432 return v;
433}
434
435// trying to use udev first, and if it doesn't work, we fall back to
436// reading /sys/block/$devname/device/(vendor/model/serial).
437std::string get_device_id(const std::string& devname,
438 std::string *err)
439{
440 struct udev_device *dev;
441 static struct udev *udev;
442 const char *data;
443
444 udev = udev_new();
445 if (!udev) {
446 if (err) {
447 *err = "udev_new failed";
448 }
449 return {};
450 }
451 dev = udev_device_new_from_subsystem_sysname(udev, "block", devname.c_str());
452 if (!dev) {
453 if (err) {
454 *err = std::string("udev_device_new_from_subsystem_sysname failed on '")
455 + devname + "'";
456 }
457 udev_unref(udev);
458 return {};
459 }
460
461 // ****
462 // NOTE: please keep this implementation in sync with _get_device_id() in
463 // src/ceph-volume/ceph_volume/util/device.py
464 // ****
465
466 std::string id_vendor, id_model, id_serial, id_serial_short, id_scsi_serial;
467 data = udev_device_get_property_value(dev, "ID_VENDOR");
468 if (data) {
469 id_vendor = data;
470 }
471 data = udev_device_get_property_value(dev, "ID_MODEL");
472 if (data) {
473 id_model = data;
474 // sometimes, ID_MODEL is "LVM ..." but ID_MODEL_ENC is correct (but
475 // encoded with \x20 for space).
476 if (id_model.substr(0, 7) == "LVM PV ") {
477 const char *enc = udev_device_get_property_value(dev, "ID_MODEL_ENC");
478 if (enc) {
479 id_model = _decode_model_enc(enc);
480 } else {
481 // ignore ID_MODEL then
482 id_model.clear();
483 }
7c673cae 484 }
11fdf7f2
TL
485 }
486 data = udev_device_get_property_value(dev, "ID_SERIAL_SHORT");
487 if (data) {
488 id_serial_short = data;
489 }
490 data = udev_device_get_property_value(dev, "ID_SCSI_SERIAL");
491 if (data) {
492 id_scsi_serial = data;
493 }
494 data = udev_device_get_property_value(dev, "ID_SERIAL");
495 if (data) {
496 id_serial = data;
497 }
498 udev_device_unref(dev);
499 udev_unref(udev);
500
501 // ID_SERIAL is usually $vendor_$model_$serial, but not always
502 // ID_SERIAL_SHORT is mostly always just the serial
503 // ID_MODEL is sometimes $vendor_$model, but
504 // ID_VENDOR is sometimes $vendor and ID_MODEL just $model and ID_SCSI_SERIAL the real serial number, with ID_SERIAL and ID_SERIAL_SHORT gibberish (ick)
505 std::string device_id;
506 if (id_vendor.size() && id_model.size() && id_scsi_serial.size()) {
507 device_id = id_vendor + '_' + id_model + '_' + id_scsi_serial;
508 } else if (id_model.size() && id_serial_short.size()) {
509 device_id = id_model + '_' + id_serial_short;
510 } else if (id_serial.size()) {
511 device_id = id_serial;
512 if (device_id.substr(0, 4) == "MTFD") {
513 // Micron NVMes hide the vendor
514 device_id = "Micron_" + device_id;
515 }
516 }
517 if (device_id.size()) {
518 std::replace(device_id.begin(), device_id.end(), ' ', '_');
519 return device_id;
520 }
521
522 // either udev_device_get_property_value() failed, or succeeded but
523 // returned nothing; trying to read from files. note that the 'vendor'
524 // file rarely contains the actual vendor; it's usually 'ATA'.
525 std::string model, serial;
526 char buf[1024] = {0};
527 BlkDev blkdev(devname);
528 if (!blkdev.model(buf, sizeof(buf))) {
529 model = buf;
530 }
9f95a23c 531 if (!blkdev.serial(buf, sizeof(buf))) {
11fdf7f2
TL
532 serial = buf;
533 }
9f95a23c
TL
534 if (err) {
535 if (model.empty() && serial.empty()) {
536 *err = std::string("fallback method has no model nor serial'");
537 return {};
538 } else if (model.empty()) {
11fdf7f2 539 *err = std::string("fallback method has serial '") + serial
9f95a23c
TL
540 + "' but no model'";
541 return {};
542 } else if (serial.empty()) {
543 *err = std::string("fallback method has model '") + model
544 + "' but no serial'";
545 return {};
11fdf7f2 546 }
11fdf7f2
TL
547 }
548
549 device_id = model + "_" + serial;
550 std::replace(device_id.begin(), device_id.end(), ' ', '_');
551 return device_id;
552}
553
554static std::string get_device_vendor(const std::string& devname)
555{
556 struct udev_device *dev;
557 static struct udev *udev;
558 const char *data;
559
560 udev = udev_new();
561 if (!udev) {
562 return {};
563 }
564 dev = udev_device_new_from_subsystem_sysname(udev, "block", devname.c_str());
565 if (!dev) {
566 udev_unref(udev);
567 return {};
568 }
569
570 std::string id_vendor, id_model;
571 data = udev_device_get_property_value(dev, "ID_VENDOR");
572 if (data) {
573 id_vendor = data;
574 }
575 data = udev_device_get_property_value(dev, "ID_MODEL");
576 if (data) {
577 id_model = data;
578 }
579 udev_device_unref(dev);
580 udev_unref(udev);
581
582 std::transform(id_vendor.begin(), id_vendor.end(), id_vendor.begin(),
583 ::tolower);
584 std::transform(id_model.begin(), id_model.end(), id_model.begin(),
585 ::tolower);
586
587 if (id_vendor.size()) {
588 return id_vendor;
589 }
590 if (id_model.size()) {
591 int pos = id_model.find(" ");
592 if (pos > 0) {
593 return id_model.substr(0, pos);
594 } else {
595 return id_model;
596 }
597 }
598
599 std::string vendor, model;
600 char buf[1024] = {0};
601 BlkDev blkdev(devname);
602 if (!blkdev.vendor(buf, sizeof(buf))) {
603 vendor = buf;
604 }
605 if (!blkdev.model(buf, sizeof(buf))) {
606 model = buf;
607 }
608 if (vendor.size()) {
609 return vendor;
610 }
611 if (model.size()) {
612 int pos = model.find(" ");
613 if (pos > 0) {
614 return model.substr(0, pos);
615 } else {
616 return model;
617 }
618 }
619
620 return {};
621}
622
623static int block_device_run_vendor_nvme(
624 const string& devname, const string& vendor, int timeout,
625 std::string *result)
626{
627 string device = "/dev/" + devname;
628
629 SubProcessTimed nvmecli(
630 "sudo", SubProcess::CLOSE, SubProcess::PIPE, SubProcess::CLOSE,
631 timeout);
632 nvmecli.add_cmd_args(
633 "nvme",
634 vendor.c_str(),
635 "smart-log-add",
636 "--json",
637 device.c_str(),
638 NULL);
639 int ret = nvmecli.spawn();
640 if (ret != 0) {
641 *result = std::string("error spawning nvme command: ") + nvmecli.err();
642 return ret;
643 }
644
645 bufferlist output;
646 ret = output.read_fd(nvmecli.get_stdout(), 100*1024);
647 if (ret < 0) {
648 bufferlist err;
649 err.read_fd(nvmecli.get_stderr(), 100 * 1024);
650 *result = std::string("failed to execute nvme: ") + err.to_str();
7c673cae 651 } else {
11fdf7f2
TL
652 ret = 0;
653 *result = output.to_str();
654 }
655
656 if (nvmecli.join() != 0) {
657 *result = std::string("nvme returned an error: ") + nvmecli.err();
658 return -EINVAL;
7c673cae
FG
659 }
660
11fdf7f2 661 return ret;
7c673cae
FG
662}
663
9f95a23c
TL
664std::string get_device_path(const std::string& devname,
665 std::string *err)
666{
667 std::set<std::string> links;
668 int r = easy_readdir("/dev/disk/by-path", &links);
669 if (r < 0) {
670 *err = "unable to list contents of /dev/disk/by-path: "s +
671 cpp_strerror(r);
672 return {};
673 }
674 for (auto& i : links) {
675 char fn[PATH_MAX];
676 char target[PATH_MAX+1];
677 snprintf(fn, sizeof(fn), "/dev/disk/by-path/%s", i.c_str());
678 int r = readlink(fn, target, sizeof(target));
679 if (r < 0 || r >= (int)sizeof(target))
680 continue;
681 target[r] = 0;
682 if ((unsigned)r > devname.size() + 1 &&
683 strncmp(target + r - devname.size(), devname.c_str(), r) == 0 &&
684 target[r - devname.size() - 1] == '/') {
685 return fn;
686 }
687 }
688 *err = "no symlink to "s + devname + " in /dev/disk/by-path";
689 return {};
690}
691
11fdf7f2
TL
692static int block_device_run_smartctl(const string& devname, int timeout,
693 std::string *result)
7c673cae 694{
11fdf7f2
TL
695 string device = "/dev/" + devname;
696
697 // when using --json, smartctl will report its errors in JSON format to stdout
698 SubProcessTimed smartctl(
699 "sudo", SubProcess::CLOSE, SubProcess::PIPE, SubProcess::CLOSE,
700 timeout);
701 smartctl.add_cmd_args(
702 "smartctl",
f67539c2
TL
703 //"-a", // all SMART info
704 "-x", // all SMART and non-SMART info
9f95a23c 705 "--json=o",
11fdf7f2
TL
706 device.c_str(),
707 NULL);
708
709 int ret = smartctl.spawn();
710 if (ret != 0) {
711 *result = std::string("error spawning smartctl: ") + smartctl.err();
712 return ret;
7c673cae 713 }
11fdf7f2
TL
714
715 bufferlist output;
716 ret = output.read_fd(smartctl.get_stdout(), 100*1024);
717 if (ret < 0) {
718 *result = std::string("failed read smartctl output: ") + cpp_strerror(-ret);
719 } else {
720 ret = 0;
721 *result = output.to_str();
722 }
723
9f95a23c
TL
724 int joinerr = smartctl.join();
725 // Bit 0: Command line did not parse.
726 // Bit 1: Device open failed, device did not return an IDENTIFY DEVICE structure, or device is in a low-power mode (see '-n' option above).
727 // Bit 2: Some SMART or other ATA command to the disk failed, or there was a checksum error in a SMART data structure (see '-b' option above).
728 // Bit 3: SMART status check returned "DISK FAILING".
729 // Bit 4: We found prefail Attributes <= threshold.
730 // Bit 5: SMART status check returned "DISK OK" but we found that some (usage or prefail) Attributes have been <= threshold at some time in the past.
731 // Bit 6: The device error log contains records of errors.
732 // Bit 7: The device self-test log contains records of errors. [ATA only] Failed self-tests outdated by a newer successful extended self-test are ignored.
733 if (joinerr & 3) {
734 *result = "smartctl returned an error ("s + stringify(joinerr) +
735 "): stderr:\n"s + smartctl.err() + "\nstdout:\n"s + *result;
7c673cae
FG
736 return -EINVAL;
737 }
11fdf7f2
TL
738
739 return ret;
740}
741
9f95a23c
TL
742static std::string escape_quotes(const std::string& s)
743{
744 std::string r = s;
745 auto pos = r.find("\"");
746 while (pos != std::string::npos) {
747 r.replace(pos, 1, "\"");
748 pos = r.find("\"", pos + 1);
749 }
750 return r;
751}
752
11fdf7f2
TL
753int block_device_get_metrics(const string& devname, int timeout,
754 json_spirit::mValue *result)
755{
756 std::string s;
757
758 // smartctl
759 if (int r = block_device_run_smartctl(devname, timeout, &s);
760 r != 0) {
9f95a23c 761 string orig = s;
11fdf7f2
TL
762 s = "{\"error\": \"smartctl failed\", \"dev\": \"/dev/";
763 s += devname;
764 s += "\", \"smartctl_error_code\": " + stringify(r);
9f95a23c 765 s += ", \"smartctl_output\": \"" + escape_quotes(orig);
11fdf7f2 766 s += + "\"}";
9f95a23c
TL
767 } else if (!json_spirit::read(s, *result)) {
768 string orig = s;
11fdf7f2
TL
769 s = "{\"error\": \"smartctl returned invalid JSON\", \"dev\": \"/dev/";
770 s += devname;
9f95a23c
TL
771 s += "\",\"output\":\"";
772 s += escape_quotes(orig);
11fdf7f2
TL
773 s += "\"}";
774 }
775 if (!json_spirit::read(s, *result)) {
7c673cae
FG
776 return -EINVAL;
777 }
11fdf7f2
TL
778
779 json_spirit::mObject& base = result->get_obj();
780 string vendor = get_device_vendor(devname);
781 if (vendor.size()) {
782 base["nvme_vendor"] = vendor;
783 s.clear();
784 json_spirit::mValue nvme_json;
785 if (int r = block_device_run_vendor_nvme(devname, vendor, timeout, &s);
786 r == 0) {
787 if (json_spirit::read(s, nvme_json) != 0) {
788 base["nvme_smart_health_information_add_log"] = nvme_json;
789 } else {
790 base["nvme_smart_health_information_add_log_error"] = "bad json output: "
791 + s;
792 }
793 } else {
794 base["nvme_smart_health_information_add_log_error_code"] = r;
795 base["nvme_smart_health_information_add_log_error"] = s;
796 }
797 } else {
798 base["nvme_vendor"] = "unknown";
799 }
800
7c673cae
FG
801 return 0;
802}
803
804#elif defined(__APPLE__)
805#include <sys/disk.h>
806
11fdf7f2
TL
807const char *BlkDev::sysfsdir() const {
808 assert(false); // Should never be called on Apple
809 return "";
810}
811
812int BlkDev::dev(char *dev, size_t max) const
813{
814 struct stat sb;
815
816 if (fstat(fd, &sb) < 0)
817 return -errno;
818
819 snprintf(dev, max, "%" PRIu64, (uint64_t)sb.st_rdev);
820
821 return 0;
822}
823
824int BlkDev::get_size(int64_t *psize) const
7c673cae
FG
825{
826 unsigned long blocksize = 0;
827 int ret = ::ioctl(fd, DKIOCGETBLOCKSIZE, &blocksize);
828 if (!ret) {
829 unsigned long nblocks;
830 ret = ::ioctl(fd, DKIOCGETBLOCKCOUNT, &nblocks);
831 if (!ret)
832 *psize = (int64_t)nblocks * blocksize;
833 }
834 if (ret < 0)
835 ret = -errno;
836 return ret;
837}
838
9f95a23c 839int64_t BlkDev::get_int_property(const char* prop) const
11fdf7f2
TL
840{
841 return 0;
842}
843
844bool BlkDev::support_discard() const
7c673cae
FG
845{
846 return false;
847}
848
11fdf7f2 849int BlkDev::discard(int64_t offset, int64_t len) const
7c673cae
FG
850{
851 return -EOPNOTSUPP;
852}
853
11fdf7f2 854bool BlkDev::is_rotational() const
7c673cae
FG
855{
856 return false;
857}
858
11fdf7f2
TL
859int BlkDev::get_numa_node(int *node) const
860{
861 return -1;
862}
863
864int BlkDev::model(char *model, size_t max) const
865{
866 return -EOPNOTSUPP;
867}
868
869int BlkDev::serial(char *serial, size_t max) const
7c673cae
FG
870{
871 return -EOPNOTSUPP;
872}
11fdf7f2
TL
873
874int BlkDev::partition(char *partition, size_t max) const
875{
876 return -EOPNOTSUPP;
877}
878
879int BlkDev::wholedisk(char *device, size_t max) const
880{
881}
882
883
884void get_dm_parents(const std::string& dev, std::set<std::string> *ls)
885{
886}
887
888void get_raw_devices(const std::string& in,
889 std::set<std::string> *ls)
890{
891}
892
893int get_vdo_stats_handle(const char *devname, std::string *vdo_name)
894{
895 return -1;
896}
897
898int64_t get_vdo_stat(int fd, const char *property)
899{
900 return 0;
901}
902
903bool get_vdo_utilization(int fd, uint64_t *total, uint64_t *avail)
904{
905 return false;
906}
907
908std::string get_device_id(const std::string& devname,
909 std::string *err)
910{
911 // FIXME: implement me
912 if (err) {
913 *err = "not implemented";
914 }
915 return std::string();
916}
917
9f95a23c
TL
918std::string get_device_path(const std::string& devname,
919 std::string *err)
920{
921 // FIXME: implement me
922 if (err) {
923 *err = "not implemented";
924 }
925 return std::string();
926}
927
7c673cae 928#elif defined(__FreeBSD__)
7c673cae 929
11fdf7f2
TL
930const char *BlkDev::sysfsdir() const {
931 assert(false); // Should never be called on FreeBSD
932 return "";
933}
934
935int BlkDev::dev(char *dev, size_t max) const
936{
937 struct stat sb;
938
939 if (fstat(fd, &sb) < 0)
940 return -errno;
941
942 snprintf(dev, max, "%" PRIu64, (uint64_t)sb.st_rdev);
943
944 return 0;
945}
946
947int BlkDev::get_size(int64_t *psize) const
7c673cae
FG
948{
949 int ret = ::ioctl(fd, DIOCGMEDIASIZE, psize);
950 if (ret < 0)
951 ret = -errno;
952 return ret;
953}
954
9f95a23c 955int64_t BlkDev::get_int_property(const char* prop) const
7c673cae 956{
11fdf7f2
TL
957 return 0;
958}
959
960bool BlkDev::support_discard() const
961{
962#ifdef FREEBSD_WITH_TRIM
963 // there is no point to claim support of discard, but
964 // unable to do so.
965 struct diocgattr_arg arg;
966
967 strlcpy(arg.name, "GEOM::candelete", sizeof(arg.name));
968 arg.len = sizeof(arg.value.i);
969 if (ioctl(fd, DIOCGATTR, &arg) == 0) {
970 return (arg.value.i != 0);
971 } else {
972 return false;
973 }
974#endif
7c673cae
FG
975 return false;
976}
977
11fdf7f2 978int BlkDev::discard(int64_t offset, int64_t len) const
7c673cae
FG
979{
980 return -EOPNOTSUPP;
981}
982
11fdf7f2
TL
983bool BlkDev::is_rotational() const
984{
985#if __FreeBSD_version >= 1200049
986 struct diocgattr_arg arg;
987
988 strlcpy(arg.name, "GEOM::rotation_rate", sizeof(arg.name));
989 arg.len = sizeof(arg.value.u16);
990
991 int ioctl_ret = ioctl(fd, DIOCGATTR, &arg);
992 bool ret;
993 if (ioctl_ret < 0 || arg.value.u16 == DISK_RR_UNKNOWN)
994 // DISK_RR_UNKNOWN usually indicates an old drive, which is usually spinny
995 ret = true;
996 else if (arg.value.u16 == DISK_RR_NON_ROTATING)
997 ret = false;
998 else if (arg.value.u16 >= DISK_RR_MIN && arg.value.u16 <= DISK_RR_MAX)
999 ret = true;
1000 else
1001 ret = true; // Invalid value. Probably spinny?
1002
1003 return ret;
1004#else
1005 return true; // When in doubt, it's probably spinny
1006#endif
1007}
1008
1009int BlkDev::get_numa_node(int *node) const
1010{
9f95a23c 1011 int numa = get_int_property("device/device/numa_node");
11fdf7f2
TL
1012 if (numa < 0)
1013 return -1;
1014 *node = numa;
1015 return 0;
1016}
1017
1018int BlkDev::model(char *model, size_t max) const
1019{
1020 struct diocgattr_arg arg;
1021
1022 strlcpy(arg.name, "GEOM::descr", sizeof(arg.name));
1023 arg.len = sizeof(arg.value.str);
1024 if (ioctl(fd, DIOCGATTR, &arg) < 0) {
1025 return -errno;
1026 }
1027
1028 // The GEOM description is of the form "vendor product" for SCSI disks
1029 // and "ATA device_model" for ATA disks. Some vendors choose to put the
1030 // vendor name in device_model, and some don't. Strip the first bit.
1031 char *p = arg.value.str;
1032 if (p == NULL || *p == '\0') {
1033 *model = '\0';
1034 } else {
1035 (void) strsep(&p, " ");
1036 snprintf(model, max, "%s", p);
1037 }
1038
1039 return 0;
1040}
1041
1042int BlkDev::serial(char *serial, size_t max) const
1043{
1044 char ident[DISK_IDENT_SIZE];
1045
1046 if (ioctl(fd, DIOCGIDENT, ident) < 0)
1047 return -errno;
1048
1049 snprintf(serial, max, "%s", ident);
1050
1051 return 0;
1052}
1053
1054void get_dm_parents(const std::string& dev, std::set<std::string> *ls)
1055{
1056}
1057
1058void get_raw_devices(const std::string& in,
1059 std::set<std::string> *ls)
1060{
1061}
1062
1063int get_vdo_stats_handle(const char *devname, std::string *vdo_name)
1064{
1065 return -1;
1066}
1067
1068int64_t get_vdo_stat(int fd, const char *property)
1069{
1070 return 0;
1071}
1072
1073bool get_vdo_utilization(int fd, uint64_t *total, uint64_t *avail)
7c673cae
FG
1074{
1075 return false;
1076}
1077
11fdf7f2
TL
1078std::string get_device_id(const std::string& devname,
1079 std::string *err)
7c673cae 1080{
11fdf7f2
TL
1081 // FIXME: implement me for freebsd
1082 if (err) {
1083 *err = "not implemented for FreeBSD";
1084 }
1085 return std::string();
1086}
1087
9f95a23c
TL
1088std::string get_device_path(const std::string& devname,
1089 std::string *err)
1090{
1091 // FIXME: implement me for freebsd
1092 if (err) {
1093 *err = "not implemented for FreeBSD";
1094 }
1095 return std::string();
1096}
1097
11fdf7f2
TL
1098int block_device_run_smartctl(const char *device, int timeout,
1099 std::string *result)
1100{
1101 // FIXME: implement me for freebsd
1102 return -EOPNOTSUPP;
7c673cae 1103}
11fdf7f2
TL
1104
1105int block_device_get_metrics(const string& devname, int timeout,
1106 json_spirit::mValue *result)
1107{
1108 // FIXME: implement me for freebsd
1109 return -EOPNOTSUPP;
1110}
1111
1112int block_device_run_nvme(const char *device, const char *vendor, int timeout,
1113 std::string *result)
7c673cae
FG
1114{
1115 return -EOPNOTSUPP;
1116}
11fdf7f2
TL
1117
1118static int block_device_devname(int fd, char *devname, size_t max)
1119{
1120 struct fiodgname_arg arg;
1121
1122 arg.buf = devname;
1123 arg.len = max;
1124 if (ioctl(fd, FIODGNAME, &arg) < 0)
1125 return -errno;
1126 return 0;
1127}
1128
1129int BlkDev::partition(char *partition, size_t max) const
1130{
1131 char devname[PATH_MAX];
1132
1133 if (block_device_devname(fd, devname, sizeof(devname)) < 0)
1134 return -errno;
1135 snprintf(partition, max, "/dev/%s", devname);
1136 return 0;
1137}
1138
1139int BlkDev::wholedisk(char *wd, size_t max) const
1140{
1141 char devname[PATH_MAX];
1142
1143 if (block_device_devname(fd, devname, sizeof(devname)) < 0)
1144 return -errno;
1145
1146 size_t first_digit = strcspn(devname, "0123456789");
1147 // first_digit now indexes the first digit or null character of devname
1148 size_t next_nondigit = strspn(&devname[first_digit], "0123456789");
1149 next_nondigit += first_digit;
1150 // next_nondigit now indexes the first alphabetic or null character after the
1151 // unit number
1152 strlcpy(wd, devname, next_nondigit + 1);
1153 return 0;
1154}
1155
7c673cae 1156#else
11fdf7f2
TL
1157
1158const char *BlkDev::sysfsdir() const {
1159 assert(false); // Should never be called on non-Linux
1160 return "";
1161}
1162
1163int BlkDev::dev(char *dev, size_t max) const
1164{
1165 return -EOPNOTSUPP;
1166}
1167
1168int BlkDev::get_size(int64_t *psize) const
1169{
1170 return -EOPNOTSUPP;
1171}
1172
1173bool BlkDev::support_discard() const
1174{
1175 return false;
1176}
1177
1178int BlkDev::discard(int fd, int64_t offset, int64_t len) const
7c673cae
FG
1179{
1180 return -EOPNOTSUPP;
1181}
1182
11fdf7f2 1183bool BlkDev::is_rotational(const char *devname) const
7c673cae
FG
1184{
1185 return false;
1186}
1187
11fdf7f2
TL
1188int BlkDev::model(char *model, size_t max) const
1189{
1190 return -EOPNOTSUPP;
1191}
1192
1193int BlkDev::serial(char *serial, size_t max) const
1194{
1195 return -EOPNOTSUPP;
1196}
1197
1198int BlkDev::partition(char *partition, size_t max) const
7c673cae
FG
1199{
1200 return -EOPNOTSUPP;
1201}
1202
11fdf7f2
TL
1203int BlkDev::wholedisk(char *wd, size_t max) const
1204{
1205 return -EOPNOTSUPP;
1206}
1207
1208void get_dm_parents(const std::string& dev, std::set<std::string> *ls)
1209{
1210}
1211
1212void get_raw_devices(const std::string& in,
1213 std::set<std::string> *ls)
1214{
1215}
1216
1217int get_vdo_stats_handle(const char *devname, std::string *vdo_name)
1218{
1219 return -1;
1220}
1221
1222int64_t get_vdo_stat(int fd, const char *property)
1223{
1224 return 0;
1225}
1226
1227bool get_vdo_utilization(int fd, uint64_t *total, uint64_t *avail)
7c673cae
FG
1228{
1229 return false;
1230}
1231
11fdf7f2
TL
1232std::string get_device_id(const std::string& devname,
1233 std::string *err)
1234{
1235 // not implemented
1236 if (err) {
1237 *err = "not implemented";
1238 }
1239 return std::string();
1240}
1241
9f95a23c
TL
1242std::string get_device_path(const std::string& devname,
1243 std::string *err)
1244{
1245 // not implemented
1246 if (err) {
1247 *err = "not implemented";
1248 }
1249 return std::string();
1250}
1251
11fdf7f2
TL
1252int block_device_run_smartctl(const char *device, int timeout,
1253 std::string *result)
7c673cae
FG
1254{
1255 return -EOPNOTSUPP;
1256}
1257
11fdf7f2
TL
1258int block_device_get_metrics(const string& devname, int timeout,
1259 json_spirit::mValue *result)
7c673cae
FG
1260{
1261 return -EOPNOTSUPP;
1262}
11fdf7f2
TL
1263
1264int block_device_run_nvme(const char *device, const char *vendor, int timeout,
1265 std::string *result)
1266{
1267 return -EOPNOTSUPP;
1268}
1269
7c673cae 1270#endif
9f95a23c
TL
1271
1272
1273
1274void get_device_metadata(
1275 const std::set<std::string>& devnames,
1276 std::map<std::string,std::string> *pm,
1277 std::map<std::string,std::string> *errs)
1278{
1279 (*pm)["devices"] = stringify(devnames);
1280 string &devids = (*pm)["device_ids"];
1281 string &devpaths = (*pm)["device_paths"];
1282 for (auto& dev : devnames) {
1283 string err;
1284 string id = get_device_id(dev, &err);
1285 if (id.size()) {
1286 if (!devids.empty()) {
1287 devids += ",";
1288 }
1289 devids += dev + "=" + id;
1290 } else {
1291 (*errs)[dev] = " no unique device id for "s + dev + ": " + err;
1292 }
1293 string path = get_device_path(dev, &err);
1294 if (path.size()) {
1295 if (!devpaths.empty()) {
1296 devpaths += ",";
1297 }
1298 devpaths += dev + "=" + path;
1299 } else {
1300 (*errs)[dev] + " no unique device path for "s + dev + ": " + err;
1301 }
1302 }
1303}