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