]> git.proxmox.com Git - ceph.git/blame - ceph/src/common/blkdev.cc
import quincy beta 17.1.0
[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
20effc67
TL
220int BlkDev::get_optimal_io_size() const
221{
222 return get_int_property("queue/optimal_io_size");
223}
224
11fdf7f2 225bool BlkDev::is_rotational() const
7c673cae 226{
9f95a23c 227 return get_int_property("queue/rotational") > 0;
7c673cae
FG
228}
229
11fdf7f2 230int BlkDev::get_numa_node(int *node) const
7c673cae 231{
9f95a23c 232 int numa = get_int_property("device/device/numa_node");
11fdf7f2
TL
233 if (numa < 0)
234 return -1;
235 *node = numa;
236 return 0;
7c673cae
FG
237}
238
11fdf7f2 239int BlkDev::dev(char *dev, size_t max) const
7c673cae 240{
9f95a23c 241 return get_string_property("dev", dev, max);
11fdf7f2 242}
7c673cae 243
11fdf7f2
TL
244int BlkDev::vendor(char *vendor, size_t max) const
245{
9f95a23c 246 return get_string_property("device/device/vendor", vendor, max);
11fdf7f2 247}
7c673cae 248
11fdf7f2
TL
249int BlkDev::model(char *model, size_t max) const
250{
9f95a23c 251 return get_string_property("device/model", model, max);
11fdf7f2
TL
252}
253
254int BlkDev::serial(char *serial, size_t max) const
255{
9f95a23c 256 return get_string_property("device/serial", serial, max);
11fdf7f2
TL
257}
258
259int BlkDev::partition(char *partition, size_t max) const
260{
261 dev_t id;
262 int r = get_devid(&id);
263 if (r < 0)
264 return -EINVAL; // hrm.
265
266 char *t = blkid_devno_to_devname(id);
267 if (!t) {
268 return -EINVAL;
269 }
270 strncpy(partition, t, max);
271 free(t);
272 return 0;
273}
274
275int BlkDev::wholedisk(char *device, size_t max) const
276{
277 dev_t id;
278 int r = get_devid(&id);
279 if (r < 0)
280 return -EINVAL; // hrm.
281
282 r = blkid_devno_to_wholedisk(id, device, max, nullptr);
283 if (r < 0) {
7c673cae 284 return -EINVAL;
11fdf7f2
TL
285 }
286 return 0;
287}
288
289static int easy_readdir(const std::string& dir, std::set<std::string> *out)
290{
291 DIR *h = ::opendir(dir.c_str());
292 if (!h) {
293 return -errno;
294 }
295 struct dirent *de = nullptr;
296 while ((de = ::readdir(h))) {
297 if (strcmp(de->d_name, ".") == 0 ||
298 strcmp(de->d_name, "..") == 0) {
299 continue;
300 }
301 out->insert(de->d_name);
302 }
303 closedir(h);
304 return 0;
305}
7c673cae 306
11fdf7f2
TL
307void get_dm_parents(const std::string& dev, std::set<std::string> *ls)
308{
309 std::string p = std::string("/sys/block/") + dev + "/slaves";
310 std::set<std::string> parents;
311 easy_readdir(p, &parents);
312 for (auto& d : parents) {
313 ls->insert(d);
314 // recurse in case it is dm-on-dm
315 if (d.find("dm-") == 0) {
316 get_dm_parents(d, ls);
317 }
318 }
319}
320
321void get_raw_devices(const std::string& in,
322 std::set<std::string> *ls)
323{
324 if (in.substr(0, 3) == "dm-") {
325 std::set<std::string> o;
326 get_dm_parents(in, &o);
327 for (auto& d : o) {
328 get_raw_devices(d, ls);
329 }
330 } else {
331 BlkDev d(in);
332 std::string wholedisk;
333 if (d.wholedisk(&wholedisk) == 0) {
334 ls->insert(wholedisk);
7c673cae 335 } else {
11fdf7f2
TL
336 ls->insert(in);
337 }
338 }
339}
340
341int _get_vdo_stats_handle(const char *devname, std::string *vdo_name)
342{
343 int vdo_fd = -1;
344
345 // we need to go from the raw devname (e.g., dm-4) to the VDO volume name.
346 // currently the best way seems to be to look at /dev/mapper/* ...
347 std::string expect = std::string("../") + devname; // expected symlink target
348 DIR *dir = ::opendir("/dev/mapper");
349 if (!dir) {
350 return -1;
351 }
352 struct dirent *de = nullptr;
353 while ((de = ::readdir(dir))) {
354 if (de->d_name[0] == '.')
355 continue;
356 char fn[4096], target[4096];
357 snprintf(fn, sizeof(fn), "/dev/mapper/%s", de->d_name);
358 int r = readlink(fn, target, sizeof(target));
359 if (r < 0 || r >= (int)sizeof(target))
360 continue;
361 target[r] = 0;
362 if (expect == target) {
363 snprintf(fn, sizeof(fn), "/sys/kvdo/%s/statistics", de->d_name);
364 vdo_fd = ::open(fn, O_RDONLY|O_CLOEXEC); //DIRECTORY);
365 if (vdo_fd >= 0) {
366 *vdo_name = de->d_name;
367 break;
368 }
369 }
370 }
371 closedir(dir);
372 return vdo_fd;
373}
374
375int get_vdo_stats_handle(const char *devname, std::string *vdo_name)
376{
377 std::set<std::string> devs = { devname };
378 while (!devs.empty()) {
379 std::string dev = *devs.begin();
380 devs.erase(devs.begin());
381 int fd = _get_vdo_stats_handle(dev.c_str(), vdo_name);
382 if (fd >= 0) {
383 // yay, it's vdo
384 return fd;
385 }
386 // ok, see if there are constituent devices
387 if (dev.find("dm-") == 0) {
388 get_dm_parents(dev, &devs);
389 }
390 }
391 return -1;
392}
393
394int64_t get_vdo_stat(int vdo_fd, const char *property)
395{
396 int64_t ret = 0;
397 int fd = ::openat(vdo_fd, property, O_RDONLY|O_CLOEXEC);
398 if (fd < 0) {
399 return 0;
400 }
401 char buf[1024];
402 int r = ::read(fd, buf, sizeof(buf) - 1);
403 if (r > 0) {
404 buf[r] = 0;
405 ret = atoll(buf);
406 }
407 TEMP_FAILURE_RETRY(::close(fd));
408 return ret;
409}
410
411bool get_vdo_utilization(int fd, uint64_t *total, uint64_t *avail)
412{
413 int64_t block_size = get_vdo_stat(fd, "block_size");
414 int64_t physical_blocks = get_vdo_stat(fd, "physical_blocks");
415 int64_t overhead_blocks_used = get_vdo_stat(fd, "overhead_blocks_used");
416 int64_t data_blocks_used = get_vdo_stat(fd, "data_blocks_used");
417 if (!block_size
418 || !physical_blocks
419 || !overhead_blocks_used
420 || !data_blocks_used) {
421 return false;
422 }
423 int64_t avail_blocks =
424 physical_blocks - overhead_blocks_used - data_blocks_used;
425 *total = block_size * physical_blocks;
426 *avail = block_size * avail_blocks;
427 return true;
428}
429
430std::string _decode_model_enc(const std::string& in)
431{
432 auto v = boost::replace_all_copy(in, "\\x20", " ");
433 if (auto found = v.find_last_not_of(" "); found != v.npos) {
434 v.erase(found + 1);
435 }
436 std::replace(v.begin(), v.end(), ' ', '_');
20effc67
TL
437
438 // remove "__", which seems to come up on by ubuntu box for some reason.
439 while (true) {
440 auto p = v.find("__");
441 if (p == std::string::npos) break;
442 v.replace(p, 2, "_");
443 }
444
11fdf7f2
TL
445 return v;
446}
447
448// trying to use udev first, and if it doesn't work, we fall back to
449// reading /sys/block/$devname/device/(vendor/model/serial).
450std::string get_device_id(const std::string& devname,
451 std::string *err)
452{
453 struct udev_device *dev;
454 static struct udev *udev;
455 const char *data;
456
457 udev = udev_new();
458 if (!udev) {
459 if (err) {
460 *err = "udev_new failed";
461 }
462 return {};
463 }
464 dev = udev_device_new_from_subsystem_sysname(udev, "block", devname.c_str());
465 if (!dev) {
466 if (err) {
467 *err = std::string("udev_device_new_from_subsystem_sysname failed on '")
468 + devname + "'";
469 }
470 udev_unref(udev);
471 return {};
472 }
473
474 // ****
475 // NOTE: please keep this implementation in sync with _get_device_id() in
476 // src/ceph-volume/ceph_volume/util/device.py
477 // ****
478
479 std::string id_vendor, id_model, id_serial, id_serial_short, id_scsi_serial;
480 data = udev_device_get_property_value(dev, "ID_VENDOR");
481 if (data) {
482 id_vendor = data;
483 }
484 data = udev_device_get_property_value(dev, "ID_MODEL");
485 if (data) {
486 id_model = data;
487 // sometimes, ID_MODEL is "LVM ..." but ID_MODEL_ENC is correct (but
488 // encoded with \x20 for space).
489 if (id_model.substr(0, 7) == "LVM PV ") {
490 const char *enc = udev_device_get_property_value(dev, "ID_MODEL_ENC");
491 if (enc) {
492 id_model = _decode_model_enc(enc);
493 } else {
494 // ignore ID_MODEL then
495 id_model.clear();
496 }
7c673cae 497 }
11fdf7f2
TL
498 }
499 data = udev_device_get_property_value(dev, "ID_SERIAL_SHORT");
500 if (data) {
501 id_serial_short = data;
502 }
503 data = udev_device_get_property_value(dev, "ID_SCSI_SERIAL");
504 if (data) {
505 id_scsi_serial = data;
506 }
507 data = udev_device_get_property_value(dev, "ID_SERIAL");
508 if (data) {
509 id_serial = data;
510 }
511 udev_device_unref(dev);
512 udev_unref(udev);
513
514 // ID_SERIAL is usually $vendor_$model_$serial, but not always
515 // ID_SERIAL_SHORT is mostly always just the serial
516 // ID_MODEL is sometimes $vendor_$model, but
517 // 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)
518 std::string device_id;
519 if (id_vendor.size() && id_model.size() && id_scsi_serial.size()) {
520 device_id = id_vendor + '_' + id_model + '_' + id_scsi_serial;
521 } else if (id_model.size() && id_serial_short.size()) {
522 device_id = id_model + '_' + id_serial_short;
523 } else if (id_serial.size()) {
524 device_id = id_serial;
525 if (device_id.substr(0, 4) == "MTFD") {
526 // Micron NVMes hide the vendor
527 device_id = "Micron_" + device_id;
528 }
529 }
530 if (device_id.size()) {
531 std::replace(device_id.begin(), device_id.end(), ' ', '_');
532 return device_id;
533 }
534
535 // either udev_device_get_property_value() failed, or succeeded but
536 // returned nothing; trying to read from files. note that the 'vendor'
537 // file rarely contains the actual vendor; it's usually 'ATA'.
538 std::string model, serial;
539 char buf[1024] = {0};
540 BlkDev blkdev(devname);
541 if (!blkdev.model(buf, sizeof(buf))) {
542 model = buf;
543 }
9f95a23c 544 if (!blkdev.serial(buf, sizeof(buf))) {
11fdf7f2
TL
545 serial = buf;
546 }
9f95a23c
TL
547 if (err) {
548 if (model.empty() && serial.empty()) {
20effc67 549 *err = std::string("fallback method has no model nor serial");
9f95a23c
TL
550 return {};
551 } else if (model.empty()) {
11fdf7f2 552 *err = std::string("fallback method has serial '") + serial
9f95a23c
TL
553 + "' but no model'";
554 return {};
555 } else if (serial.empty()) {
556 *err = std::string("fallback method has model '") + model
557 + "' but no serial'";
558 return {};
11fdf7f2 559 }
11fdf7f2
TL
560 }
561
562 device_id = model + "_" + serial;
563 std::replace(device_id.begin(), device_id.end(), ' ', '_');
564 return device_id;
565}
566
567static std::string get_device_vendor(const std::string& devname)
568{
569 struct udev_device *dev;
570 static struct udev *udev;
571 const char *data;
572
573 udev = udev_new();
574 if (!udev) {
575 return {};
576 }
577 dev = udev_device_new_from_subsystem_sysname(udev, "block", devname.c_str());
578 if (!dev) {
579 udev_unref(udev);
580 return {};
581 }
582
583 std::string id_vendor, id_model;
584 data = udev_device_get_property_value(dev, "ID_VENDOR");
585 if (data) {
586 id_vendor = data;
587 }
588 data = udev_device_get_property_value(dev, "ID_MODEL");
589 if (data) {
590 id_model = data;
591 }
592 udev_device_unref(dev);
593 udev_unref(udev);
594
595 std::transform(id_vendor.begin(), id_vendor.end(), id_vendor.begin(),
596 ::tolower);
597 std::transform(id_model.begin(), id_model.end(), id_model.begin(),
598 ::tolower);
599
600 if (id_vendor.size()) {
601 return id_vendor;
602 }
603 if (id_model.size()) {
604 int pos = id_model.find(" ");
605 if (pos > 0) {
606 return id_model.substr(0, pos);
607 } else {
608 return id_model;
609 }
610 }
611
612 std::string vendor, model;
613 char buf[1024] = {0};
614 BlkDev blkdev(devname);
615 if (!blkdev.vendor(buf, sizeof(buf))) {
616 vendor = buf;
617 }
618 if (!blkdev.model(buf, sizeof(buf))) {
619 model = buf;
620 }
621 if (vendor.size()) {
622 return vendor;
623 }
624 if (model.size()) {
625 int pos = model.find(" ");
626 if (pos > 0) {
627 return model.substr(0, pos);
628 } else {
629 return model;
630 }
631 }
632
633 return {};
634}
635
636static int block_device_run_vendor_nvme(
637 const string& devname, const string& vendor, int timeout,
638 std::string *result)
639{
640 string device = "/dev/" + devname;
641
642 SubProcessTimed nvmecli(
643 "sudo", SubProcess::CLOSE, SubProcess::PIPE, SubProcess::CLOSE,
644 timeout);
645 nvmecli.add_cmd_args(
646 "nvme",
647 vendor.c_str(),
648 "smart-log-add",
649 "--json",
650 device.c_str(),
651 NULL);
652 int ret = nvmecli.spawn();
653 if (ret != 0) {
654 *result = std::string("error spawning nvme command: ") + nvmecli.err();
655 return ret;
656 }
657
658 bufferlist output;
659 ret = output.read_fd(nvmecli.get_stdout(), 100*1024);
660 if (ret < 0) {
661 bufferlist err;
662 err.read_fd(nvmecli.get_stderr(), 100 * 1024);
663 *result = std::string("failed to execute nvme: ") + err.to_str();
7c673cae 664 } else {
11fdf7f2
TL
665 ret = 0;
666 *result = output.to_str();
667 }
668
669 if (nvmecli.join() != 0) {
670 *result = std::string("nvme returned an error: ") + nvmecli.err();
671 return -EINVAL;
7c673cae
FG
672 }
673
11fdf7f2 674 return ret;
7c673cae
FG
675}
676
9f95a23c
TL
677std::string get_device_path(const std::string& devname,
678 std::string *err)
679{
680 std::set<std::string> links;
681 int r = easy_readdir("/dev/disk/by-path", &links);
682 if (r < 0) {
683 *err = "unable to list contents of /dev/disk/by-path: "s +
684 cpp_strerror(r);
685 return {};
686 }
687 for (auto& i : links) {
688 char fn[PATH_MAX];
689 char target[PATH_MAX+1];
690 snprintf(fn, sizeof(fn), "/dev/disk/by-path/%s", i.c_str());
691 int r = readlink(fn, target, sizeof(target));
692 if (r < 0 || r >= (int)sizeof(target))
693 continue;
694 target[r] = 0;
695 if ((unsigned)r > devname.size() + 1 &&
696 strncmp(target + r - devname.size(), devname.c_str(), r) == 0 &&
697 target[r - devname.size() - 1] == '/') {
698 return fn;
699 }
700 }
701 *err = "no symlink to "s + devname + " in /dev/disk/by-path";
702 return {};
703}
704
11fdf7f2
TL
705static int block_device_run_smartctl(const string& devname, int timeout,
706 std::string *result)
7c673cae 707{
11fdf7f2
TL
708 string device = "/dev/" + devname;
709
710 // when using --json, smartctl will report its errors in JSON format to stdout
711 SubProcessTimed smartctl(
712 "sudo", SubProcess::CLOSE, SubProcess::PIPE, SubProcess::CLOSE,
713 timeout);
714 smartctl.add_cmd_args(
715 "smartctl",
f67539c2
TL
716 //"-a", // all SMART info
717 "-x", // all SMART and non-SMART info
9f95a23c 718 "--json=o",
11fdf7f2
TL
719 device.c_str(),
720 NULL);
721
722 int ret = smartctl.spawn();
723 if (ret != 0) {
724 *result = std::string("error spawning smartctl: ") + smartctl.err();
725 return ret;
7c673cae 726 }
11fdf7f2
TL
727
728 bufferlist output;
729 ret = output.read_fd(smartctl.get_stdout(), 100*1024);
730 if (ret < 0) {
731 *result = std::string("failed read smartctl output: ") + cpp_strerror(-ret);
732 } else {
733 ret = 0;
734 *result = output.to_str();
735 }
736
9f95a23c
TL
737 int joinerr = smartctl.join();
738 // Bit 0: Command line did not parse.
739 // 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).
740 // 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).
741 // Bit 3: SMART status check returned "DISK FAILING".
742 // Bit 4: We found prefail Attributes <= threshold.
743 // 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.
744 // Bit 6: The device error log contains records of errors.
745 // 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.
746 if (joinerr & 3) {
747 *result = "smartctl returned an error ("s + stringify(joinerr) +
748 "): stderr:\n"s + smartctl.err() + "\nstdout:\n"s + *result;
7c673cae
FG
749 return -EINVAL;
750 }
11fdf7f2
TL
751
752 return ret;
753}
754
9f95a23c
TL
755static std::string escape_quotes(const std::string& s)
756{
757 std::string r = s;
758 auto pos = r.find("\"");
759 while (pos != std::string::npos) {
760 r.replace(pos, 1, "\"");
761 pos = r.find("\"", pos + 1);
762 }
763 return r;
764}
765
11fdf7f2
TL
766int block_device_get_metrics(const string& devname, int timeout,
767 json_spirit::mValue *result)
768{
769 std::string s;
770
771 // smartctl
772 if (int r = block_device_run_smartctl(devname, timeout, &s);
773 r != 0) {
9f95a23c 774 string orig = s;
11fdf7f2
TL
775 s = "{\"error\": \"smartctl failed\", \"dev\": \"/dev/";
776 s += devname;
777 s += "\", \"smartctl_error_code\": " + stringify(r);
9f95a23c 778 s += ", \"smartctl_output\": \"" + escape_quotes(orig);
11fdf7f2 779 s += + "\"}";
9f95a23c
TL
780 } else if (!json_spirit::read(s, *result)) {
781 string orig = s;
11fdf7f2
TL
782 s = "{\"error\": \"smartctl returned invalid JSON\", \"dev\": \"/dev/";
783 s += devname;
9f95a23c
TL
784 s += "\",\"output\":\"";
785 s += escape_quotes(orig);
11fdf7f2
TL
786 s += "\"}";
787 }
788 if (!json_spirit::read(s, *result)) {
7c673cae
FG
789 return -EINVAL;
790 }
11fdf7f2
TL
791
792 json_spirit::mObject& base = result->get_obj();
793 string vendor = get_device_vendor(devname);
794 if (vendor.size()) {
795 base["nvme_vendor"] = vendor;
796 s.clear();
797 json_spirit::mValue nvme_json;
798 if (int r = block_device_run_vendor_nvme(devname, vendor, timeout, &s);
799 r == 0) {
800 if (json_spirit::read(s, nvme_json) != 0) {
801 base["nvme_smart_health_information_add_log"] = nvme_json;
802 } else {
803 base["nvme_smart_health_information_add_log_error"] = "bad json output: "
804 + s;
805 }
806 } else {
807 base["nvme_smart_health_information_add_log_error_code"] = r;
808 base["nvme_smart_health_information_add_log_error"] = s;
809 }
810 } else {
811 base["nvme_vendor"] = "unknown";
812 }
813
7c673cae
FG
814 return 0;
815}
816
817#elif defined(__APPLE__)
818#include <sys/disk.h>
819
11fdf7f2
TL
820const char *BlkDev::sysfsdir() const {
821 assert(false); // Should never be called on Apple
822 return "";
823}
824
825int BlkDev::dev(char *dev, size_t max) const
826{
827 struct stat sb;
828
829 if (fstat(fd, &sb) < 0)
830 return -errno;
831
832 snprintf(dev, max, "%" PRIu64, (uint64_t)sb.st_rdev);
833
834 return 0;
835}
836
837int BlkDev::get_size(int64_t *psize) const
7c673cae
FG
838{
839 unsigned long blocksize = 0;
840 int ret = ::ioctl(fd, DKIOCGETBLOCKSIZE, &blocksize);
841 if (!ret) {
842 unsigned long nblocks;
843 ret = ::ioctl(fd, DKIOCGETBLOCKCOUNT, &nblocks);
844 if (!ret)
845 *psize = (int64_t)nblocks * blocksize;
846 }
847 if (ret < 0)
848 ret = -errno;
849 return ret;
850}
851
9f95a23c 852int64_t BlkDev::get_int_property(const char* prop) const
11fdf7f2
TL
853{
854 return 0;
855}
856
857bool BlkDev::support_discard() const
7c673cae
FG
858{
859 return false;
860}
861
11fdf7f2 862int BlkDev::discard(int64_t offset, int64_t len) const
7c673cae
FG
863{
864 return -EOPNOTSUPP;
865}
866
20effc67
TL
867int BlkDev::get_optimal_io_size() const
868{
869 return 0;
870}
871
11fdf7f2 872bool BlkDev::is_rotational() const
7c673cae
FG
873{
874 return false;
875}
876
11fdf7f2
TL
877int BlkDev::get_numa_node(int *node) const
878{
879 return -1;
880}
881
882int BlkDev::model(char *model, size_t max) const
883{
884 return -EOPNOTSUPP;
885}
886
887int BlkDev::serial(char *serial, size_t max) const
7c673cae
FG
888{
889 return -EOPNOTSUPP;
890}
11fdf7f2
TL
891
892int BlkDev::partition(char *partition, size_t max) const
893{
894 return -EOPNOTSUPP;
895}
896
897int BlkDev::wholedisk(char *device, size_t max) const
898{
899}
900
901
902void get_dm_parents(const std::string& dev, std::set<std::string> *ls)
903{
904}
905
906void get_raw_devices(const std::string& in,
907 std::set<std::string> *ls)
908{
909}
910
911int get_vdo_stats_handle(const char *devname, std::string *vdo_name)
912{
913 return -1;
914}
915
916int64_t get_vdo_stat(int fd, const char *property)
917{
918 return 0;
919}
920
921bool get_vdo_utilization(int fd, uint64_t *total, uint64_t *avail)
922{
923 return false;
924}
925
926std::string get_device_id(const std::string& devname,
927 std::string *err)
928{
929 // FIXME: implement me
930 if (err) {
931 *err = "not implemented";
932 }
933 return std::string();
934}
935
9f95a23c
TL
936std::string get_device_path(const std::string& devname,
937 std::string *err)
938{
939 // FIXME: implement me
940 if (err) {
941 *err = "not implemented";
942 }
943 return std::string();
944}
945
7c673cae 946#elif defined(__FreeBSD__)
7c673cae 947
11fdf7f2
TL
948const char *BlkDev::sysfsdir() const {
949 assert(false); // Should never be called on FreeBSD
950 return "";
951}
952
953int BlkDev::dev(char *dev, size_t max) const
954{
955 struct stat sb;
956
957 if (fstat(fd, &sb) < 0)
958 return -errno;
959
960 snprintf(dev, max, "%" PRIu64, (uint64_t)sb.st_rdev);
961
962 return 0;
963}
964
965int BlkDev::get_size(int64_t *psize) const
7c673cae
FG
966{
967 int ret = ::ioctl(fd, DIOCGMEDIASIZE, psize);
968 if (ret < 0)
969 ret = -errno;
970 return ret;
971}
972
9f95a23c 973int64_t BlkDev::get_int_property(const char* prop) const
7c673cae 974{
11fdf7f2
TL
975 return 0;
976}
977
978bool BlkDev::support_discard() const
979{
980#ifdef FREEBSD_WITH_TRIM
981 // there is no point to claim support of discard, but
982 // unable to do so.
983 struct diocgattr_arg arg;
984
985 strlcpy(arg.name, "GEOM::candelete", sizeof(arg.name));
986 arg.len = sizeof(arg.value.i);
987 if (ioctl(fd, DIOCGATTR, &arg) == 0) {
988 return (arg.value.i != 0);
989 } else {
990 return false;
991 }
992#endif
7c673cae
FG
993 return false;
994}
995
11fdf7f2 996int BlkDev::discard(int64_t offset, int64_t len) const
7c673cae
FG
997{
998 return -EOPNOTSUPP;
999}
1000
20effc67
TL
1001int BlkDev::get_optimal_io_size() const
1002{
1003 return 0;
1004}
1005
11fdf7f2
TL
1006bool BlkDev::is_rotational() const
1007{
1008#if __FreeBSD_version >= 1200049
1009 struct diocgattr_arg arg;
1010
1011 strlcpy(arg.name, "GEOM::rotation_rate", sizeof(arg.name));
1012 arg.len = sizeof(arg.value.u16);
1013
1014 int ioctl_ret = ioctl(fd, DIOCGATTR, &arg);
1015 bool ret;
1016 if (ioctl_ret < 0 || arg.value.u16 == DISK_RR_UNKNOWN)
1017 // DISK_RR_UNKNOWN usually indicates an old drive, which is usually spinny
1018 ret = true;
1019 else if (arg.value.u16 == DISK_RR_NON_ROTATING)
1020 ret = false;
1021 else if (arg.value.u16 >= DISK_RR_MIN && arg.value.u16 <= DISK_RR_MAX)
1022 ret = true;
1023 else
1024 ret = true; // Invalid value. Probably spinny?
1025
1026 return ret;
1027#else
1028 return true; // When in doubt, it's probably spinny
1029#endif
1030}
1031
1032int BlkDev::get_numa_node(int *node) const
1033{
9f95a23c 1034 int numa = get_int_property("device/device/numa_node");
11fdf7f2
TL
1035 if (numa < 0)
1036 return -1;
1037 *node = numa;
1038 return 0;
1039}
1040
1041int BlkDev::model(char *model, size_t max) const
1042{
1043 struct diocgattr_arg arg;
1044
1045 strlcpy(arg.name, "GEOM::descr", sizeof(arg.name));
1046 arg.len = sizeof(arg.value.str);
1047 if (ioctl(fd, DIOCGATTR, &arg) < 0) {
1048 return -errno;
1049 }
1050
1051 // The GEOM description is of the form "vendor product" for SCSI disks
1052 // and "ATA device_model" for ATA disks. Some vendors choose to put the
1053 // vendor name in device_model, and some don't. Strip the first bit.
1054 char *p = arg.value.str;
1055 if (p == NULL || *p == '\0') {
1056 *model = '\0';
1057 } else {
1058 (void) strsep(&p, " ");
1059 snprintf(model, max, "%s", p);
1060 }
1061
1062 return 0;
1063}
1064
1065int BlkDev::serial(char *serial, size_t max) const
1066{
1067 char ident[DISK_IDENT_SIZE];
1068
1069 if (ioctl(fd, DIOCGIDENT, ident) < 0)
1070 return -errno;
1071
1072 snprintf(serial, max, "%s", ident);
1073
1074 return 0;
1075}
1076
1077void get_dm_parents(const std::string& dev, std::set<std::string> *ls)
1078{
1079}
1080
1081void get_raw_devices(const std::string& in,
1082 std::set<std::string> *ls)
1083{
1084}
1085
1086int get_vdo_stats_handle(const char *devname, std::string *vdo_name)
1087{
1088 return -1;
1089}
1090
1091int64_t get_vdo_stat(int fd, const char *property)
1092{
1093 return 0;
1094}
1095
1096bool get_vdo_utilization(int fd, uint64_t *total, uint64_t *avail)
7c673cae
FG
1097{
1098 return false;
1099}
1100
11fdf7f2
TL
1101std::string get_device_id(const std::string& devname,
1102 std::string *err)
7c673cae 1103{
11fdf7f2
TL
1104 // FIXME: implement me for freebsd
1105 if (err) {
1106 *err = "not implemented for FreeBSD";
1107 }
1108 return std::string();
1109}
1110
9f95a23c
TL
1111std::string get_device_path(const std::string& devname,
1112 std::string *err)
1113{
1114 // FIXME: implement me for freebsd
1115 if (err) {
1116 *err = "not implemented for FreeBSD";
1117 }
1118 return std::string();
1119}
1120
11fdf7f2
TL
1121int block_device_run_smartctl(const char *device, int timeout,
1122 std::string *result)
1123{
1124 // FIXME: implement me for freebsd
1125 return -EOPNOTSUPP;
7c673cae 1126}
11fdf7f2
TL
1127
1128int block_device_get_metrics(const string& devname, int timeout,
1129 json_spirit::mValue *result)
1130{
1131 // FIXME: implement me for freebsd
1132 return -EOPNOTSUPP;
1133}
1134
1135int block_device_run_nvme(const char *device, const char *vendor, int timeout,
1136 std::string *result)
7c673cae
FG
1137{
1138 return -EOPNOTSUPP;
1139}
11fdf7f2
TL
1140
1141static int block_device_devname(int fd, char *devname, size_t max)
1142{
1143 struct fiodgname_arg arg;
1144
1145 arg.buf = devname;
1146 arg.len = max;
1147 if (ioctl(fd, FIODGNAME, &arg) < 0)
1148 return -errno;
1149 return 0;
1150}
1151
1152int BlkDev::partition(char *partition, size_t max) const
1153{
1154 char devname[PATH_MAX];
1155
1156 if (block_device_devname(fd, devname, sizeof(devname)) < 0)
1157 return -errno;
1158 snprintf(partition, max, "/dev/%s", devname);
1159 return 0;
1160}
1161
1162int BlkDev::wholedisk(char *wd, size_t max) const
1163{
1164 char devname[PATH_MAX];
1165
1166 if (block_device_devname(fd, devname, sizeof(devname)) < 0)
1167 return -errno;
1168
1169 size_t first_digit = strcspn(devname, "0123456789");
1170 // first_digit now indexes the first digit or null character of devname
1171 size_t next_nondigit = strspn(&devname[first_digit], "0123456789");
1172 next_nondigit += first_digit;
1173 // next_nondigit now indexes the first alphabetic or null character after the
1174 // unit number
1175 strlcpy(wd, devname, next_nondigit + 1);
1176 return 0;
1177}
1178
7c673cae 1179#else
11fdf7f2
TL
1180
1181const char *BlkDev::sysfsdir() const {
1182 assert(false); // Should never be called on non-Linux
1183 return "";
1184}
1185
1186int BlkDev::dev(char *dev, size_t max) const
1187{
1188 return -EOPNOTSUPP;
1189}
1190
1191int BlkDev::get_size(int64_t *psize) const
1192{
1193 return -EOPNOTSUPP;
1194}
1195
1196bool BlkDev::support_discard() const
1197{
1198 return false;
1199}
1200
1201int BlkDev::discard(int fd, int64_t offset, int64_t len) const
7c673cae
FG
1202{
1203 return -EOPNOTSUPP;
1204}
1205
11fdf7f2 1206bool BlkDev::is_rotational(const char *devname) const
7c673cae
FG
1207{
1208 return false;
1209}
1210
11fdf7f2
TL
1211int BlkDev::model(char *model, size_t max) const
1212{
1213 return -EOPNOTSUPP;
1214}
1215
1216int BlkDev::serial(char *serial, size_t max) const
1217{
1218 return -EOPNOTSUPP;
1219}
1220
1221int BlkDev::partition(char *partition, size_t max) const
7c673cae
FG
1222{
1223 return -EOPNOTSUPP;
1224}
1225
11fdf7f2
TL
1226int BlkDev::wholedisk(char *wd, size_t max) const
1227{
1228 return -EOPNOTSUPP;
1229}
1230
1231void get_dm_parents(const std::string& dev, std::set<std::string> *ls)
1232{
1233}
1234
1235void get_raw_devices(const std::string& in,
1236 std::set<std::string> *ls)
1237{
1238}
1239
1240int get_vdo_stats_handle(const char *devname, std::string *vdo_name)
1241{
1242 return -1;
1243}
1244
1245int64_t get_vdo_stat(int fd, const char *property)
1246{
1247 return 0;
1248}
1249
1250bool get_vdo_utilization(int fd, uint64_t *total, uint64_t *avail)
7c673cae
FG
1251{
1252 return false;
1253}
1254
11fdf7f2
TL
1255std::string get_device_id(const std::string& devname,
1256 std::string *err)
1257{
1258 // not implemented
1259 if (err) {
1260 *err = "not implemented";
1261 }
1262 return std::string();
1263}
1264
9f95a23c
TL
1265std::string get_device_path(const std::string& devname,
1266 std::string *err)
1267{
1268 // not implemented
1269 if (err) {
1270 *err = "not implemented";
1271 }
1272 return std::string();
1273}
1274
11fdf7f2
TL
1275int block_device_run_smartctl(const char *device, int timeout,
1276 std::string *result)
7c673cae
FG
1277{
1278 return -EOPNOTSUPP;
1279}
1280
11fdf7f2
TL
1281int block_device_get_metrics(const string& devname, int timeout,
1282 json_spirit::mValue *result)
7c673cae
FG
1283{
1284 return -EOPNOTSUPP;
1285}
11fdf7f2
TL
1286
1287int block_device_run_nvme(const char *device, const char *vendor, int timeout,
1288 std::string *result)
1289{
1290 return -EOPNOTSUPP;
1291}
1292
7c673cae 1293#endif
9f95a23c
TL
1294
1295
1296
1297void get_device_metadata(
1298 const std::set<std::string>& devnames,
1299 std::map<std::string,std::string> *pm,
1300 std::map<std::string,std::string> *errs)
1301{
1302 (*pm)["devices"] = stringify(devnames);
1303 string &devids = (*pm)["device_ids"];
1304 string &devpaths = (*pm)["device_paths"];
1305 for (auto& dev : devnames) {
1306 string err;
1307 string id = get_device_id(dev, &err);
1308 if (id.size()) {
1309 if (!devids.empty()) {
1310 devids += ",";
1311 }
1312 devids += dev + "=" + id;
1313 } else {
1314 (*errs)[dev] = " no unique device id for "s + dev + ": " + err;
1315 }
1316 string path = get_device_path(dev, &err);
1317 if (path.size()) {
1318 if (!devpaths.empty()) {
1319 devpaths += ",";
1320 }
1321 devpaths += dev + "=" + path;
1322 } else {
1323 (*errs)[dev] + " no unique device path for "s + dev + ": " + err;
1324 }
1325 }
1326}