]> git.proxmox.com Git - ceph.git/blob - ceph/src/common/blkdev.cc
update ceph source to reef 18.2.0
[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
42 int 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"
64
65 #ifdef __linux__
66 #include <libudev.h>
67 #include <linux/fs.h>
68 #include <linux/kdev_t.h>
69 #include <blkid/blkid.h>
70
71 #include <set>
72
73 #include "common/SubProcess.h"
74 #include "common/errno.h"
75
76
77 #define UUID_LEN 36
78
79 #endif
80
81 using namespace std::literals;
82
83 using std::string;
84
85 using ceph::bufferlist;
86
87
88 BlkDev::BlkDev(int f)
89 : fd(f)
90 {}
91
92 BlkDev::BlkDev(const std::string& devname)
93 : devname(devname)
94 {}
95
96 int BlkDev::get_devid(dev_t *id) const
97 {
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__
115
116 const char *BlkDev::sysfsdir() const {
117 return "/sys";
118 }
119
120 int BlkDev::get_size(int64_t *psize) const
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
130 # error "Linux configuration error (get_size)"
131 #endif
132 if (ret < 0)
133 ret = -errno;
134 return ret;
135 }
136
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 */
144 int64_t BlkDev::get_string_property(const char* prop,
145 char *val, size_t maxlen) const
146 {
147 char filename[PATH_MAX], wd[PATH_MAX];
148 const char* dev = nullptr;
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 }
159 if (snprintf(filename, sizeof(filename), "%s/block/%s/%s", sysfsdir(), dev,
160 prop) >= static_cast<int>(sizeof(filename))) {
161 return -ERANGE;
162 }
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 */
189 int64_t BlkDev::get_int_property(const char* prop) const
190 {
191 char buff[256] = {0};
192 int r = get_string_property(prop, buff, sizeof(buff));
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
209 bool BlkDev::support_discard() const
210 {
211 return get_int_property("queue/discard_granularity") > 0;
212 }
213
214 int BlkDev::discard(int64_t offset, int64_t len) const
215 {
216 uint64_t range[2] = {(uint64_t)offset, (uint64_t)len};
217 return ioctl(fd, BLKDISCARD, range);
218 }
219
220 int BlkDev::get_optimal_io_size() const
221 {
222 return get_int_property("queue/optimal_io_size");
223 }
224
225 bool BlkDev::is_rotational() const
226 {
227 return get_int_property("queue/rotational") > 0;
228 }
229
230 int BlkDev::get_numa_node(int *node) const
231 {
232 int numa = get_int_property("device/device/numa_node");
233 if (numa < 0)
234 return -1;
235 *node = numa;
236 return 0;
237 }
238
239 int BlkDev::dev(char *dev, size_t max) const
240 {
241 return get_string_property("dev", dev, max);
242 }
243
244 int BlkDev::vendor(char *vendor, size_t max) const
245 {
246 return get_string_property("device/device/vendor", vendor, max);
247 }
248
249 int BlkDev::model(char *model, size_t max) const
250 {
251 return get_string_property("device/model", model, max);
252 }
253
254 int BlkDev::serial(char *serial, size_t max) const
255 {
256 return get_string_property("device/serial", serial, max);
257 }
258
259 int 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
275 int 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) {
284 return -EINVAL;
285 }
286 return 0;
287 }
288
289 static 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 }
306
307 void 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
321 void 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);
335 } else {
336 ls->insert(in);
337 }
338 }
339 }
340
341 std::string _decode_model_enc(const std::string& in)
342 {
343 auto v = boost::replace_all_copy(in, "\\x20", " ");
344 if (auto found = v.find_last_not_of(" "); found != v.npos) {
345 v.erase(found + 1);
346 }
347 std::replace(v.begin(), v.end(), ' ', '_');
348
349 // remove "__", which seems to come up on by ubuntu box for some reason.
350 while (true) {
351 auto p = v.find("__");
352 if (p == std::string::npos) break;
353 v.replace(p, 2, "_");
354 }
355
356 return v;
357 }
358
359 // trying to use udev first, and if it doesn't work, we fall back to
360 // reading /sys/block/$devname/device/(vendor/model/serial).
361 std::string get_device_id(const std::string& devname,
362 std::string *err)
363 {
364 struct udev_device *dev;
365 static struct udev *udev;
366 const char *data;
367
368 udev = udev_new();
369 if (!udev) {
370 if (err) {
371 *err = "udev_new failed";
372 }
373 return {};
374 }
375 dev = udev_device_new_from_subsystem_sysname(udev, "block", devname.c_str());
376 if (!dev) {
377 if (err) {
378 *err = std::string("udev_device_new_from_subsystem_sysname failed on '")
379 + devname + "'";
380 }
381 udev_unref(udev);
382 return {};
383 }
384
385 // ****
386 // NOTE: please keep this implementation in sync with _get_device_id() in
387 // src/ceph-volume/ceph_volume/util/device.py
388 // ****
389
390 std::string id_vendor, id_model, id_serial, id_serial_short, id_scsi_serial;
391 data = udev_device_get_property_value(dev, "ID_VENDOR");
392 if (data) {
393 id_vendor = data;
394 }
395 data = udev_device_get_property_value(dev, "ID_MODEL");
396 if (data) {
397 id_model = data;
398 // sometimes, ID_MODEL is "LVM ..." but ID_MODEL_ENC is correct (but
399 // encoded with \x20 for space).
400 if (id_model.substr(0, 7) == "LVM PV ") {
401 const char *enc = udev_device_get_property_value(dev, "ID_MODEL_ENC");
402 if (enc) {
403 id_model = _decode_model_enc(enc);
404 } else {
405 // ignore ID_MODEL then
406 id_model.clear();
407 }
408 }
409 }
410 data = udev_device_get_property_value(dev, "ID_SERIAL_SHORT");
411 if (data) {
412 id_serial_short = data;
413 }
414 data = udev_device_get_property_value(dev, "ID_SCSI_SERIAL");
415 if (data) {
416 id_scsi_serial = data;
417 }
418 data = udev_device_get_property_value(dev, "ID_SERIAL");
419 if (data) {
420 id_serial = data;
421 }
422 udev_device_unref(dev);
423 udev_unref(udev);
424
425 // ID_SERIAL is usually $vendor_$model_$serial, but not always
426 // ID_SERIAL_SHORT is mostly always just the serial
427 // ID_MODEL is sometimes $vendor_$model, but
428 // 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)
429 std::string device_id;
430 if (id_vendor.size() && id_model.size() && id_scsi_serial.size()) {
431 device_id = id_vendor + '_' + id_model + '_' + id_scsi_serial;
432 } else if (id_model.size() && id_serial_short.size()) {
433 device_id = id_model + '_' + id_serial_short;
434 } else if (id_serial.size()) {
435 device_id = id_serial;
436 if (device_id.substr(0, 4) == "MTFD") {
437 // Micron NVMes hide the vendor
438 device_id = "Micron_" + device_id;
439 }
440 }
441 if (device_id.size()) {
442 std::replace(device_id.begin(), device_id.end(), ' ', '_');
443 return device_id;
444 }
445
446 // either udev_device_get_property_value() failed, or succeeded but
447 // returned nothing; trying to read from files. note that the 'vendor'
448 // file rarely contains the actual vendor; it's usually 'ATA'.
449 std::string model, serial;
450 char buf[1024] = {0};
451 BlkDev blkdev(devname);
452 if (!blkdev.model(buf, sizeof(buf))) {
453 model = buf;
454 }
455 if (!blkdev.serial(buf, sizeof(buf))) {
456 serial = buf;
457 }
458 if (err) {
459 if (model.empty() && serial.empty()) {
460 *err = std::string("fallback method has no model nor serial");
461 return {};
462 } else if (model.empty()) {
463 *err = std::string("fallback method has serial '") + serial
464 + "' but no model'";
465 return {};
466 } else if (serial.empty()) {
467 *err = std::string("fallback method has model '") + model
468 + "' but no serial'";
469 return {};
470 }
471 }
472
473 device_id = model + "_" + serial;
474 std::replace(device_id.begin(), device_id.end(), ' ', '_');
475 return device_id;
476 }
477
478 static std::string get_device_vendor(const std::string& devname)
479 {
480 struct udev_device *dev;
481 static struct udev *udev;
482 const char *data;
483
484 udev = udev_new();
485 if (!udev) {
486 return {};
487 }
488 dev = udev_device_new_from_subsystem_sysname(udev, "block", devname.c_str());
489 if (!dev) {
490 udev_unref(udev);
491 return {};
492 }
493
494 std::string id_vendor, id_model;
495 data = udev_device_get_property_value(dev, "ID_VENDOR");
496 if (data) {
497 id_vendor = data;
498 }
499 data = udev_device_get_property_value(dev, "ID_MODEL");
500 if (data) {
501 id_model = data;
502 }
503 udev_device_unref(dev);
504 udev_unref(udev);
505
506 std::transform(id_vendor.begin(), id_vendor.end(), id_vendor.begin(),
507 ::tolower);
508 std::transform(id_model.begin(), id_model.end(), id_model.begin(),
509 ::tolower);
510
511 if (id_vendor.size()) {
512 return id_vendor;
513 }
514 if (id_model.size()) {
515 int pos = id_model.find(" ");
516 if (pos > 0) {
517 return id_model.substr(0, pos);
518 } else {
519 return id_model;
520 }
521 }
522
523 std::string vendor, model;
524 char buf[1024] = {0};
525 BlkDev blkdev(devname);
526 if (!blkdev.vendor(buf, sizeof(buf))) {
527 vendor = buf;
528 }
529 if (!blkdev.model(buf, sizeof(buf))) {
530 model = buf;
531 }
532 if (vendor.size()) {
533 return vendor;
534 }
535 if (model.size()) {
536 int pos = model.find(" ");
537 if (pos > 0) {
538 return model.substr(0, pos);
539 } else {
540 return model;
541 }
542 }
543
544 return {};
545 }
546
547 static int block_device_run_vendor_nvme(
548 const string& devname, const string& vendor, int timeout,
549 std::string *result)
550 {
551 string device = "/dev/" + devname;
552
553 SubProcessTimed nvmecli(
554 "sudo", SubProcess::CLOSE, SubProcess::PIPE, SubProcess::CLOSE,
555 timeout);
556 nvmecli.add_cmd_args(
557 "nvme",
558 vendor.c_str(),
559 "smart-log-add",
560 "--json",
561 device.c_str(),
562 NULL);
563 int ret = nvmecli.spawn();
564 if (ret != 0) {
565 *result = std::string("error spawning nvme command: ") + nvmecli.err();
566 return ret;
567 }
568
569 bufferlist output;
570 ret = output.read_fd(nvmecli.get_stdout(), 100*1024);
571 if (ret < 0) {
572 bufferlist err;
573 err.read_fd(nvmecli.get_stderr(), 100 * 1024);
574 *result = std::string("failed to execute nvme: ") + err.to_str();
575 } else {
576 ret = 0;
577 *result = output.to_str();
578 }
579
580 if (nvmecli.join() != 0) {
581 *result = std::string("nvme returned an error: ") + nvmecli.err();
582 return -EINVAL;
583 }
584
585 return ret;
586 }
587
588 std::string get_device_path(const std::string& devname,
589 std::string *err)
590 {
591 std::set<std::string> links;
592 int r = easy_readdir("/dev/disk/by-path", &links);
593 if (r < 0) {
594 *err = "unable to list contents of /dev/disk/by-path: "s +
595 cpp_strerror(r);
596 return {};
597 }
598 for (auto& i : links) {
599 char fn[PATH_MAX];
600 char target[PATH_MAX+1];
601 snprintf(fn, sizeof(fn), "/dev/disk/by-path/%s", i.c_str());
602 int r = readlink(fn, target, sizeof(target));
603 if (r < 0 || r >= (int)sizeof(target))
604 continue;
605 target[r] = 0;
606 if ((unsigned)r > devname.size() + 1 &&
607 strncmp(target + r - devname.size(), devname.c_str(), r) == 0 &&
608 target[r - devname.size() - 1] == '/') {
609 return fn;
610 }
611 }
612 *err = "no symlink to "s + devname + " in /dev/disk/by-path";
613 return {};
614 }
615
616 static int block_device_run_smartctl(const string& devname, int timeout,
617 std::string *result)
618 {
619 string device = "/dev/" + devname;
620
621 // when using --json, smartctl will report its errors in JSON format to stdout
622 SubProcessTimed smartctl(
623 "sudo", SubProcess::CLOSE, SubProcess::PIPE, SubProcess::CLOSE,
624 timeout);
625 smartctl.add_cmd_args(
626 "smartctl",
627 //"-a", // all SMART info
628 "-x", // all SMART and non-SMART info
629 "--json=o",
630 device.c_str(),
631 NULL);
632
633 int ret = smartctl.spawn();
634 if (ret != 0) {
635 *result = std::string("error spawning smartctl: ") + smartctl.err();
636 return ret;
637 }
638
639 bufferlist output;
640 ret = output.read_fd(smartctl.get_stdout(), 100*1024);
641 if (ret < 0) {
642 *result = std::string("failed read smartctl output: ") + cpp_strerror(-ret);
643 } else {
644 ret = 0;
645 *result = output.to_str();
646 }
647
648 int joinerr = smartctl.join();
649 // Bit 0: Command line did not parse.
650 // 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).
651 // 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).
652 // Bit 3: SMART status check returned "DISK FAILING".
653 // Bit 4: We found prefail Attributes <= threshold.
654 // 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.
655 // Bit 6: The device error log contains records of errors.
656 // 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.
657 if (joinerr & 3) {
658 *result = "smartctl returned an error ("s + stringify(joinerr) +
659 "): stderr:\n"s + smartctl.err() + "\nstdout:\n"s + *result;
660 return -EINVAL;
661 }
662
663 return ret;
664 }
665
666 static std::string escape_quotes(const std::string& s)
667 {
668 std::string r = s;
669 auto pos = r.find("\"");
670 while (pos != std::string::npos) {
671 r.replace(pos, 1, "\"");
672 pos = r.find("\"", pos + 1);
673 }
674 return r;
675 }
676
677 int block_device_get_metrics(const string& devname, int timeout,
678 json_spirit::mValue *result)
679 {
680 std::string s;
681
682 // smartctl
683 if (int r = block_device_run_smartctl(devname, timeout, &s);
684 r != 0) {
685 string orig = s;
686 s = "{\"error\": \"smartctl failed\", \"dev\": \"/dev/";
687 s += devname;
688 s += "\", \"smartctl_error_code\": " + stringify(r);
689 s += ", \"smartctl_output\": \"" + escape_quotes(orig);
690 s += + "\"}";
691 } else if (!json_spirit::read(s, *result)) {
692 string orig = s;
693 s = "{\"error\": \"smartctl returned invalid JSON\", \"dev\": \"/dev/";
694 s += devname;
695 s += "\",\"output\":\"";
696 s += escape_quotes(orig);
697 s += "\"}";
698 }
699 if (!json_spirit::read(s, *result)) {
700 return -EINVAL;
701 }
702
703 json_spirit::mObject& base = result->get_obj();
704 string vendor = get_device_vendor(devname);
705 if (vendor.size()) {
706 base["nvme_vendor"] = vendor;
707 s.clear();
708 json_spirit::mValue nvme_json;
709 if (int r = block_device_run_vendor_nvme(devname, vendor, timeout, &s);
710 r == 0) {
711 if (json_spirit::read(s, nvme_json) != 0) {
712 base["nvme_smart_health_information_add_log"] = nvme_json;
713 } else {
714 base["nvme_smart_health_information_add_log_error"] = "bad json output: "
715 + s;
716 }
717 } else {
718 base["nvme_smart_health_information_add_log_error_code"] = r;
719 base["nvme_smart_health_information_add_log_error"] = s;
720 }
721 } else {
722 base["nvme_vendor"] = "unknown";
723 }
724
725 return 0;
726 }
727
728 #elif defined(__APPLE__)
729 #include <sys/disk.h>
730
731 const char *BlkDev::sysfsdir() const {
732 assert(false); // Should never be called on Apple
733 return "";
734 }
735
736 int BlkDev::dev(char *dev, size_t max) const
737 {
738 struct stat sb;
739
740 if (fstat(fd, &sb) < 0)
741 return -errno;
742
743 snprintf(dev, max, "%" PRIu64, (uint64_t)sb.st_rdev);
744
745 return 0;
746 }
747
748 int BlkDev::get_size(int64_t *psize) const
749 {
750 unsigned long blocksize = 0;
751 int ret = ::ioctl(fd, DKIOCGETBLOCKSIZE, &blocksize);
752 if (!ret) {
753 unsigned long nblocks;
754 ret = ::ioctl(fd, DKIOCGETBLOCKCOUNT, &nblocks);
755 if (!ret)
756 *psize = (int64_t)nblocks * blocksize;
757 }
758 if (ret < 0)
759 ret = -errno;
760 return ret;
761 }
762
763 int64_t BlkDev::get_int_property(const char* prop) const
764 {
765 return 0;
766 }
767
768 bool BlkDev::support_discard() const
769 {
770 return false;
771 }
772
773 int BlkDev::discard(int64_t offset, int64_t len) const
774 {
775 return -EOPNOTSUPP;
776 }
777
778 int BlkDev::get_optimal_io_size() const
779 {
780 return 0;
781 }
782
783 bool BlkDev::is_rotational() const
784 {
785 return false;
786 }
787
788 int BlkDev::get_numa_node(int *node) const
789 {
790 return -1;
791 }
792
793 int BlkDev::model(char *model, size_t max) const
794 {
795 return -EOPNOTSUPP;
796 }
797
798 int BlkDev::serial(char *serial, size_t max) const
799 {
800 return -EOPNOTSUPP;
801 }
802
803 int BlkDev::partition(char *partition, size_t max) const
804 {
805 return -EOPNOTSUPP;
806 }
807
808 int BlkDev::wholedisk(char *device, size_t max) const
809 {
810 }
811
812
813 void get_dm_parents(const std::string& dev, std::set<std::string> *ls)
814 {
815 }
816
817 void get_raw_devices(const std::string& in,
818 std::set<std::string> *ls)
819 {
820 }
821
822 std::string get_device_id(const std::string& devname,
823 std::string *err)
824 {
825 // FIXME: implement me
826 if (err) {
827 *err = "not implemented";
828 }
829 return std::string();
830 }
831
832 std::string get_device_path(const std::string& devname,
833 std::string *err)
834 {
835 // FIXME: implement me
836 if (err) {
837 *err = "not implemented";
838 }
839 return std::string();
840 }
841
842 #elif defined(__FreeBSD__)
843
844 const char *BlkDev::sysfsdir() const {
845 assert(false); // Should never be called on FreeBSD
846 return "";
847 }
848
849 int BlkDev::dev(char *dev, size_t max) const
850 {
851 struct stat sb;
852
853 if (fstat(fd, &sb) < 0)
854 return -errno;
855
856 snprintf(dev, max, "%" PRIu64, (uint64_t)sb.st_rdev);
857
858 return 0;
859 }
860
861 int BlkDev::get_size(int64_t *psize) const
862 {
863 int ret = ::ioctl(fd, DIOCGMEDIASIZE, psize);
864 if (ret < 0)
865 ret = -errno;
866 return ret;
867 }
868
869 int64_t BlkDev::get_int_property(const char* prop) const
870 {
871 return 0;
872 }
873
874 bool BlkDev::support_discard() const
875 {
876 #ifdef FREEBSD_WITH_TRIM
877 // there is no point to claim support of discard, but
878 // unable to do so.
879 struct diocgattr_arg arg;
880
881 strlcpy(arg.name, "GEOM::candelete", sizeof(arg.name));
882 arg.len = sizeof(arg.value.i);
883 if (ioctl(fd, DIOCGATTR, &arg) == 0) {
884 return (arg.value.i != 0);
885 } else {
886 return false;
887 }
888 #endif
889 return false;
890 }
891
892 int BlkDev::discard(int64_t offset, int64_t len) const
893 {
894 return -EOPNOTSUPP;
895 }
896
897 int BlkDev::get_optimal_io_size() const
898 {
899 return 0;
900 }
901
902 bool BlkDev::is_rotational() const
903 {
904 #if __FreeBSD_version >= 1200049
905 struct diocgattr_arg arg;
906
907 strlcpy(arg.name, "GEOM::rotation_rate", sizeof(arg.name));
908 arg.len = sizeof(arg.value.u16);
909
910 int ioctl_ret = ioctl(fd, DIOCGATTR, &arg);
911 bool ret;
912 if (ioctl_ret < 0 || arg.value.u16 == DISK_RR_UNKNOWN)
913 // DISK_RR_UNKNOWN usually indicates an old drive, which is usually spinny
914 ret = true;
915 else if (arg.value.u16 == DISK_RR_NON_ROTATING)
916 ret = false;
917 else if (arg.value.u16 >= DISK_RR_MIN && arg.value.u16 <= DISK_RR_MAX)
918 ret = true;
919 else
920 ret = true; // Invalid value. Probably spinny?
921
922 return ret;
923 #else
924 return true; // When in doubt, it's probably spinny
925 #endif
926 }
927
928 int BlkDev::get_numa_node(int *node) const
929 {
930 int numa = get_int_property("device/device/numa_node");
931 if (numa < 0)
932 return -1;
933 *node = numa;
934 return 0;
935 }
936
937 int BlkDev::model(char *model, size_t max) const
938 {
939 struct diocgattr_arg arg;
940
941 strlcpy(arg.name, "GEOM::descr", sizeof(arg.name));
942 arg.len = sizeof(arg.value.str);
943 if (ioctl(fd, DIOCGATTR, &arg) < 0) {
944 return -errno;
945 }
946
947 // The GEOM description is of the form "vendor product" for SCSI disks
948 // and "ATA device_model" for ATA disks. Some vendors choose to put the
949 // vendor name in device_model, and some don't. Strip the first bit.
950 char *p = arg.value.str;
951 if (p == NULL || *p == '\0') {
952 *model = '\0';
953 } else {
954 (void) strsep(&p, " ");
955 snprintf(model, max, "%s", p);
956 }
957
958 return 0;
959 }
960
961 int BlkDev::serial(char *serial, size_t max) const
962 {
963 char ident[DISK_IDENT_SIZE];
964
965 if (ioctl(fd, DIOCGIDENT, ident) < 0)
966 return -errno;
967
968 snprintf(serial, max, "%s", ident);
969
970 return 0;
971 }
972
973 void get_dm_parents(const std::string& dev, std::set<std::string> *ls)
974 {
975 }
976
977 void get_raw_devices(const std::string& in,
978 std::set<std::string> *ls)
979 {
980 }
981
982 std::string get_device_id(const std::string& devname,
983 std::string *err)
984 {
985 // FIXME: implement me for freebsd
986 if (err) {
987 *err = "not implemented for FreeBSD";
988 }
989 return std::string();
990 }
991
992 std::string get_device_path(const std::string& devname,
993 std::string *err)
994 {
995 // FIXME: implement me for freebsd
996 if (err) {
997 *err = "not implemented for FreeBSD";
998 }
999 return std::string();
1000 }
1001
1002 int block_device_run_smartctl(const char *device, int timeout,
1003 std::string *result)
1004 {
1005 // FIXME: implement me for freebsd
1006 return -EOPNOTSUPP;
1007 }
1008
1009 int block_device_get_metrics(const string& devname, int timeout,
1010 json_spirit::mValue *result)
1011 {
1012 // FIXME: implement me for freebsd
1013 return -EOPNOTSUPP;
1014 }
1015
1016 int block_device_run_nvme(const char *device, const char *vendor, int timeout,
1017 std::string *result)
1018 {
1019 return -EOPNOTSUPP;
1020 }
1021
1022 static int block_device_devname(int fd, char *devname, size_t max)
1023 {
1024 struct fiodgname_arg arg;
1025
1026 arg.buf = devname;
1027 arg.len = max;
1028 if (ioctl(fd, FIODGNAME, &arg) < 0)
1029 return -errno;
1030 return 0;
1031 }
1032
1033 int BlkDev::partition(char *partition, size_t max) const
1034 {
1035 char devname[PATH_MAX];
1036
1037 if (block_device_devname(fd, devname, sizeof(devname)) < 0)
1038 return -errno;
1039 snprintf(partition, max, "/dev/%s", devname);
1040 return 0;
1041 }
1042
1043 int BlkDev::wholedisk(char *wd, size_t max) const
1044 {
1045 char devname[PATH_MAX];
1046
1047 if (block_device_devname(fd, devname, sizeof(devname)) < 0)
1048 return -errno;
1049
1050 size_t first_digit = strcspn(devname, "0123456789");
1051 // first_digit now indexes the first digit or null character of devname
1052 size_t next_nondigit = strspn(&devname[first_digit], "0123456789");
1053 next_nondigit += first_digit;
1054 // next_nondigit now indexes the first alphabetic or null character after the
1055 // unit number
1056 strlcpy(wd, devname, next_nondigit + 1);
1057 return 0;
1058 }
1059
1060 #else
1061
1062 const char *BlkDev::sysfsdir() const {
1063 assert(false); // Should never be called on non-Linux
1064 return "";
1065 }
1066
1067 int BlkDev::dev(char *dev, size_t max) const
1068 {
1069 return -EOPNOTSUPP;
1070 }
1071
1072 int BlkDev::get_size(int64_t *psize) const
1073 {
1074 return -EOPNOTSUPP;
1075 }
1076
1077 bool BlkDev::support_discard() const
1078 {
1079 return false;
1080 }
1081
1082 int BlkDev::discard(int fd, int64_t offset, int64_t len) const
1083 {
1084 return -EOPNOTSUPP;
1085 }
1086
1087 bool BlkDev::is_rotational(const char *devname) const
1088 {
1089 return false;
1090 }
1091
1092 int BlkDev::model(char *model, size_t max) const
1093 {
1094 return -EOPNOTSUPP;
1095 }
1096
1097 int BlkDev::serial(char *serial, size_t max) const
1098 {
1099 return -EOPNOTSUPP;
1100 }
1101
1102 int BlkDev::partition(char *partition, size_t max) const
1103 {
1104 return -EOPNOTSUPP;
1105 }
1106
1107 int BlkDev::wholedisk(char *wd, size_t max) const
1108 {
1109 return -EOPNOTSUPP;
1110 }
1111
1112 void get_dm_parents(const std::string& dev, std::set<std::string> *ls)
1113 {
1114 }
1115
1116 void get_raw_devices(const std::string& in,
1117 std::set<std::string> *ls)
1118 {
1119 }
1120
1121 std::string get_device_id(const std::string& devname,
1122 std::string *err)
1123 {
1124 // not implemented
1125 if (err) {
1126 *err = "not implemented";
1127 }
1128 return std::string();
1129 }
1130
1131 std::string get_device_path(const std::string& devname,
1132 std::string *err)
1133 {
1134 // not implemented
1135 if (err) {
1136 *err = "not implemented";
1137 }
1138 return std::string();
1139 }
1140
1141 int block_device_run_smartctl(const char *device, int timeout,
1142 std::string *result)
1143 {
1144 return -EOPNOTSUPP;
1145 }
1146
1147 int block_device_get_metrics(const string& devname, int timeout,
1148 json_spirit::mValue *result)
1149 {
1150 return -EOPNOTSUPP;
1151 }
1152
1153 int block_device_run_nvme(const char *device, const char *vendor, int timeout,
1154 std::string *result)
1155 {
1156 return -EOPNOTSUPP;
1157 }
1158
1159 #endif
1160
1161
1162
1163 void get_device_metadata(
1164 const std::set<std::string>& devnames,
1165 std::map<std::string,std::string> *pm,
1166 std::map<std::string,std::string> *errs)
1167 {
1168 (*pm)["devices"] = stringify(devnames);
1169 string &devids = (*pm)["device_ids"];
1170 string &devpaths = (*pm)["device_paths"];
1171 for (auto& dev : devnames) {
1172 string err;
1173 string id = get_device_id(dev, &err);
1174 if (id.size()) {
1175 if (!devids.empty()) {
1176 devids += ",";
1177 }
1178 devids += dev + "=" + id;
1179 } else {
1180 (*errs)[dev] = " no unique device id for "s + dev + ": " + err;
1181 }
1182 string path = get_device_path(dev, &err);
1183 if (path.size()) {
1184 if (!devpaths.empty()) {
1185 devpaths += ",";
1186 }
1187 devpaths += dev + "=" + path;
1188 } else {
1189 (*errs)[dev] + " no unique device path for "s + dev + ": " + err;
1190 }
1191 }
1192 }