// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Ceph - scalable distributed file system * * Copyright (c) 2015 Hewlett-Packard Development Company, L.P. * * This is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1, as published by the Free Software * Foundation. See file COPYING. * */ #include "include/compat.h" #ifdef __FreeBSD__ #include #include #include #include #endif #include #include #include #include #include #include #include #include //#include "common/debug.h" #include "include/scope_guard.h" #include "include/uuid.h" #include "include/stringify.h" #include "blkdev.h" #include "numa.h" #include "json_spirit/json_spirit_reader.h" int get_device_by_path(const char *path, char* partition, char* device, size_t max) { int fd = ::open(path, O_RDONLY|O_DIRECTORY); if (fd < 0) { return -errno; } auto close_fd = make_scope_guard([fd] { ::close(fd); }); BlkDev blkdev(fd); if (auto ret = blkdev.partition(partition, max); ret) { return ret; } if (auto ret = blkdev.wholedisk(device, max); ret) { return ret; } return 0; } #include "common/blkdev.h" #ifdef __linux__ #include #include #include #include #include #include "common/SubProcess.h" #include "common/errno.h" #define UUID_LEN 36 #endif using namespace std::literals; using std::string; using ceph::bufferlist; BlkDev::BlkDev(int f) : fd(f) {} BlkDev::BlkDev(const std::string& devname) : devname(devname) {} int BlkDev::get_devid(dev_t *id) const { struct stat st; int r; if (fd >= 0) { r = fstat(fd, &st); } else { char path[PATH_MAX]; snprintf(path, sizeof(path), "/dev/%s", devname.c_str()); r = stat(path, &st); } if (r < 0) { return -errno; } *id = S_ISBLK(st.st_mode) ? st.st_rdev : st.st_dev; return 0; } #ifdef __linux__ const char *BlkDev::sysfsdir() const { return "/sys"; } int BlkDev::get_size(int64_t *psize) const { #ifdef BLKGETSIZE64 int ret = ::ioctl(fd, BLKGETSIZE64, psize); #elif defined(BLKGETSIZE) unsigned long sectors = 0; int ret = ::ioctl(fd, BLKGETSIZE, §ors); *psize = sectors * 512ULL; #else // cppcheck-suppress preprocessorErrorDirective # error "Linux configuration error (get_size)" #endif if (ret < 0) ret = -errno; return ret; } /** * get a block device property as a string * * store property in *val, up to maxlen chars * return 0 on success * return negative error on error */ int64_t BlkDev::get_string_property(const char* prop, char *val, size_t maxlen) const { char filename[PATH_MAX], wd[PATH_MAX]; const char* dev = nullptr; if (fd >= 0) { // sysfs isn't fully populated for partitions, so we need to lookup the sysfs // entry for the underlying whole disk. if (int r = wholedisk(wd, sizeof(wd)); r < 0) return r; dev = wd; } else { dev = devname.c_str(); } if (snprintf(filename, sizeof(filename), "%s/block/%s/%s", sysfsdir(), dev, prop) >= static_cast(sizeof(filename))) { return -ERANGE; } FILE *fp = fopen(filename, "r"); if (fp == NULL) { return -errno; } int r = 0; if (fgets(val, maxlen - 1, fp)) { // truncate at newline char *p = val; while (*p && *p != '\n') ++p; *p = 0; } else { r = -EINVAL; } fclose(fp); return r; } /** * get a block device property * * return the value (we assume it is positive) * return negative error on error */ int64_t BlkDev::get_int_property(const char* prop) const { char buff[256] = {0}; int r = get_string_property(prop, buff, sizeof(buff)); if (r < 0) return r; // take only digits for (char *p = buff; *p; ++p) { if (!isdigit(*p)) { *p = 0; break; } } char *endptr = 0; r = strtoll(buff, &endptr, 10); if (endptr != buff + strlen(buff)) r = -EINVAL; return r; } bool BlkDev::support_discard() const { return get_int_property("queue/discard_granularity") > 0; } int BlkDev::discard(int64_t offset, int64_t len) const { uint64_t range[2] = {(uint64_t)offset, (uint64_t)len}; return ioctl(fd, BLKDISCARD, range); } bool BlkDev::is_rotational() const { return get_int_property("queue/rotational") > 0; } int BlkDev::get_numa_node(int *node) const { int numa = get_int_property("device/device/numa_node"); if (numa < 0) return -1; *node = numa; return 0; } int BlkDev::dev(char *dev, size_t max) const { return get_string_property("dev", dev, max); } int BlkDev::vendor(char *vendor, size_t max) const { return get_string_property("device/device/vendor", vendor, max); } int BlkDev::model(char *model, size_t max) const { return get_string_property("device/model", model, max); } int BlkDev::serial(char *serial, size_t max) const { return get_string_property("device/serial", serial, max); } int BlkDev::partition(char *partition, size_t max) const { dev_t id; int r = get_devid(&id); if (r < 0) return -EINVAL; // hrm. char *t = blkid_devno_to_devname(id); if (!t) { return -EINVAL; } strncpy(partition, t, max); free(t); return 0; } int BlkDev::wholedisk(char *device, size_t max) const { dev_t id; int r = get_devid(&id); if (r < 0) return -EINVAL; // hrm. r = blkid_devno_to_wholedisk(id, device, max, nullptr); if (r < 0) { return -EINVAL; } return 0; } static int easy_readdir(const std::string& dir, std::set *out) { DIR *h = ::opendir(dir.c_str()); if (!h) { return -errno; } struct dirent *de = nullptr; while ((de = ::readdir(h))) { if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) { continue; } out->insert(de->d_name); } closedir(h); return 0; } void get_dm_parents(const std::string& dev, std::set *ls) { std::string p = std::string("/sys/block/") + dev + "/slaves"; std::set parents; easy_readdir(p, &parents); for (auto& d : parents) { ls->insert(d); // recurse in case it is dm-on-dm if (d.find("dm-") == 0) { get_dm_parents(d, ls); } } } void get_raw_devices(const std::string& in, std::set *ls) { if (in.substr(0, 3) == "dm-") { std::set o; get_dm_parents(in, &o); for (auto& d : o) { get_raw_devices(d, ls); } } else { BlkDev d(in); std::string wholedisk; if (d.wholedisk(&wholedisk) == 0) { ls->insert(wholedisk); } else { ls->insert(in); } } } int _get_vdo_stats_handle(const char *devname, std::string *vdo_name) { int vdo_fd = -1; // we need to go from the raw devname (e.g., dm-4) to the VDO volume name. // currently the best way seems to be to look at /dev/mapper/* ... std::string expect = std::string("../") + devname; // expected symlink target DIR *dir = ::opendir("/dev/mapper"); if (!dir) { return -1; } struct dirent *de = nullptr; while ((de = ::readdir(dir))) { if (de->d_name[0] == '.') continue; char fn[4096], target[4096]; snprintf(fn, sizeof(fn), "/dev/mapper/%s", de->d_name); int r = readlink(fn, target, sizeof(target)); if (r < 0 || r >= (int)sizeof(target)) continue; target[r] = 0; if (expect == target) { snprintf(fn, sizeof(fn), "/sys/kvdo/%s/statistics", de->d_name); vdo_fd = ::open(fn, O_RDONLY|O_CLOEXEC); //DIRECTORY); if (vdo_fd >= 0) { *vdo_name = de->d_name; break; } } } closedir(dir); return vdo_fd; } int get_vdo_stats_handle(const char *devname, std::string *vdo_name) { std::set devs = { devname }; while (!devs.empty()) { std::string dev = *devs.begin(); devs.erase(devs.begin()); int fd = _get_vdo_stats_handle(dev.c_str(), vdo_name); if (fd >= 0) { // yay, it's vdo return fd; } // ok, see if there are constituent devices if (dev.find("dm-") == 0) { get_dm_parents(dev, &devs); } } return -1; } int64_t get_vdo_stat(int vdo_fd, const char *property) { int64_t ret = 0; int fd = ::openat(vdo_fd, property, O_RDONLY|O_CLOEXEC); if (fd < 0) { return 0; } char buf[1024]; int r = ::read(fd, buf, sizeof(buf) - 1); if (r > 0) { buf[r] = 0; ret = atoll(buf); } TEMP_FAILURE_RETRY(::close(fd)); return ret; } bool get_vdo_utilization(int fd, uint64_t *total, uint64_t *avail) { int64_t block_size = get_vdo_stat(fd, "block_size"); int64_t physical_blocks = get_vdo_stat(fd, "physical_blocks"); int64_t overhead_blocks_used = get_vdo_stat(fd, "overhead_blocks_used"); int64_t data_blocks_used = get_vdo_stat(fd, "data_blocks_used"); if (!block_size || !physical_blocks || !overhead_blocks_used || !data_blocks_used) { return false; } int64_t avail_blocks = physical_blocks - overhead_blocks_used - data_blocks_used; *total = block_size * physical_blocks; *avail = block_size * avail_blocks; return true; } std::string _decode_model_enc(const std::string& in) { auto v = boost::replace_all_copy(in, "\\x20", " "); if (auto found = v.find_last_not_of(" "); found != v.npos) { v.erase(found + 1); } std::replace(v.begin(), v.end(), ' ', '_'); return v; } // trying to use udev first, and if it doesn't work, we fall back to // reading /sys/block/$devname/device/(vendor/model/serial). std::string get_device_id(const std::string& devname, std::string *err) { struct udev_device *dev; static struct udev *udev; const char *data; udev = udev_new(); if (!udev) { if (err) { *err = "udev_new failed"; } return {}; } dev = udev_device_new_from_subsystem_sysname(udev, "block", devname.c_str()); if (!dev) { if (err) { *err = std::string("udev_device_new_from_subsystem_sysname failed on '") + devname + "'"; } udev_unref(udev); return {}; } // **** // NOTE: please keep this implementation in sync with _get_device_id() in // src/ceph-volume/ceph_volume/util/device.py // **** std::string id_vendor, id_model, id_serial, id_serial_short, id_scsi_serial; data = udev_device_get_property_value(dev, "ID_VENDOR"); if (data) { id_vendor = data; } data = udev_device_get_property_value(dev, "ID_MODEL"); if (data) { id_model = data; // sometimes, ID_MODEL is "LVM ..." but ID_MODEL_ENC is correct (but // encoded with \x20 for space). if (id_model.substr(0, 7) == "LVM PV ") { const char *enc = udev_device_get_property_value(dev, "ID_MODEL_ENC"); if (enc) { id_model = _decode_model_enc(enc); } else { // ignore ID_MODEL then id_model.clear(); } } } data = udev_device_get_property_value(dev, "ID_SERIAL_SHORT"); if (data) { id_serial_short = data; } data = udev_device_get_property_value(dev, "ID_SCSI_SERIAL"); if (data) { id_scsi_serial = data; } data = udev_device_get_property_value(dev, "ID_SERIAL"); if (data) { id_serial = data; } udev_device_unref(dev); udev_unref(udev); // ID_SERIAL is usually $vendor_$model_$serial, but not always // ID_SERIAL_SHORT is mostly always just the serial // ID_MODEL is sometimes $vendor_$model, but // 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) std::string device_id; if (id_vendor.size() && id_model.size() && id_scsi_serial.size()) { device_id = id_vendor + '_' + id_model + '_' + id_scsi_serial; } else if (id_model.size() && id_serial_short.size()) { device_id = id_model + '_' + id_serial_short; } else if (id_serial.size()) { device_id = id_serial; if (device_id.substr(0, 4) == "MTFD") { // Micron NVMes hide the vendor device_id = "Micron_" + device_id; } } if (device_id.size()) { std::replace(device_id.begin(), device_id.end(), ' ', '_'); return device_id; } // either udev_device_get_property_value() failed, or succeeded but // returned nothing; trying to read from files. note that the 'vendor' // file rarely contains the actual vendor; it's usually 'ATA'. std::string model, serial; char buf[1024] = {0}; BlkDev blkdev(devname); if (!blkdev.model(buf, sizeof(buf))) { model = buf; } if (!blkdev.serial(buf, sizeof(buf))) { serial = buf; } if (err) { if (model.empty() && serial.empty()) { *err = std::string("fallback method has no model nor serial'"); return {}; } else if (model.empty()) { *err = std::string("fallback method has serial '") + serial + "' but no model'"; return {}; } else if (serial.empty()) { *err = std::string("fallback method has model '") + model + "' but no serial'"; return {}; } } device_id = model + "_" + serial; std::replace(device_id.begin(), device_id.end(), ' ', '_'); return device_id; } static std::string get_device_vendor(const std::string& devname) { struct udev_device *dev; static struct udev *udev; const char *data; udev = udev_new(); if (!udev) { return {}; } dev = udev_device_new_from_subsystem_sysname(udev, "block", devname.c_str()); if (!dev) { udev_unref(udev); return {}; } std::string id_vendor, id_model; data = udev_device_get_property_value(dev, "ID_VENDOR"); if (data) { id_vendor = data; } data = udev_device_get_property_value(dev, "ID_MODEL"); if (data) { id_model = data; } udev_device_unref(dev); udev_unref(udev); std::transform(id_vendor.begin(), id_vendor.end(), id_vendor.begin(), ::tolower); std::transform(id_model.begin(), id_model.end(), id_model.begin(), ::tolower); if (id_vendor.size()) { return id_vendor; } if (id_model.size()) { int pos = id_model.find(" "); if (pos > 0) { return id_model.substr(0, pos); } else { return id_model; } } std::string vendor, model; char buf[1024] = {0}; BlkDev blkdev(devname); if (!blkdev.vendor(buf, sizeof(buf))) { vendor = buf; } if (!blkdev.model(buf, sizeof(buf))) { model = buf; } if (vendor.size()) { return vendor; } if (model.size()) { int pos = model.find(" "); if (pos > 0) { return model.substr(0, pos); } else { return model; } } return {}; } static int block_device_run_vendor_nvme( const string& devname, const string& vendor, int timeout, std::string *result) { string device = "/dev/" + devname; SubProcessTimed nvmecli( "sudo", SubProcess::CLOSE, SubProcess::PIPE, SubProcess::CLOSE, timeout); nvmecli.add_cmd_args( "nvme", vendor.c_str(), "smart-log-add", "--json", device.c_str(), NULL); int ret = nvmecli.spawn(); if (ret != 0) { *result = std::string("error spawning nvme command: ") + nvmecli.err(); return ret; } bufferlist output; ret = output.read_fd(nvmecli.get_stdout(), 100*1024); if (ret < 0) { bufferlist err; err.read_fd(nvmecli.get_stderr(), 100 * 1024); *result = std::string("failed to execute nvme: ") + err.to_str(); } else { ret = 0; *result = output.to_str(); } if (nvmecli.join() != 0) { *result = std::string("nvme returned an error: ") + nvmecli.err(); return -EINVAL; } return ret; } std::string get_device_path(const std::string& devname, std::string *err) { std::set links; int r = easy_readdir("/dev/disk/by-path", &links); if (r < 0) { *err = "unable to list contents of /dev/disk/by-path: "s + cpp_strerror(r); return {}; } for (auto& i : links) { char fn[PATH_MAX]; char target[PATH_MAX+1]; snprintf(fn, sizeof(fn), "/dev/disk/by-path/%s", i.c_str()); int r = readlink(fn, target, sizeof(target)); if (r < 0 || r >= (int)sizeof(target)) continue; target[r] = 0; if ((unsigned)r > devname.size() + 1 && strncmp(target + r - devname.size(), devname.c_str(), r) == 0 && target[r - devname.size() - 1] == '/') { return fn; } } *err = "no symlink to "s + devname + " in /dev/disk/by-path"; return {}; } static int block_device_run_smartctl(const string& devname, int timeout, std::string *result) { string device = "/dev/" + devname; // when using --json, smartctl will report its errors in JSON format to stdout SubProcessTimed smartctl( "sudo", SubProcess::CLOSE, SubProcess::PIPE, SubProcess::CLOSE, timeout); smartctl.add_cmd_args( "smartctl", //"-a", // all SMART info "-x", // all SMART and non-SMART info "--json=o", device.c_str(), NULL); int ret = smartctl.spawn(); if (ret != 0) { *result = std::string("error spawning smartctl: ") + smartctl.err(); return ret; } bufferlist output; ret = output.read_fd(smartctl.get_stdout(), 100*1024); if (ret < 0) { *result = std::string("failed read smartctl output: ") + cpp_strerror(-ret); } else { ret = 0; *result = output.to_str(); } int joinerr = smartctl.join(); // Bit 0: Command line did not parse. // 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). // 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). // Bit 3: SMART status check returned "DISK FAILING". // Bit 4: We found prefail Attributes <= threshold. // 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. // Bit 6: The device error log contains records of errors. // 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. if (joinerr & 3) { *result = "smartctl returned an error ("s + stringify(joinerr) + "): stderr:\n"s + smartctl.err() + "\nstdout:\n"s + *result; return -EINVAL; } return ret; } static std::string escape_quotes(const std::string& s) { std::string r = s; auto pos = r.find("\""); while (pos != std::string::npos) { r.replace(pos, 1, "\""); pos = r.find("\"", pos + 1); } return r; } int block_device_get_metrics(const string& devname, int timeout, json_spirit::mValue *result) { std::string s; // smartctl if (int r = block_device_run_smartctl(devname, timeout, &s); r != 0) { string orig = s; s = "{\"error\": \"smartctl failed\", \"dev\": \"/dev/"; s += devname; s += "\", \"smartctl_error_code\": " + stringify(r); s += ", \"smartctl_output\": \"" + escape_quotes(orig); s += + "\"}"; } else if (!json_spirit::read(s, *result)) { string orig = s; s = "{\"error\": \"smartctl returned invalid JSON\", \"dev\": \"/dev/"; s += devname; s += "\",\"output\":\""; s += escape_quotes(orig); s += "\"}"; } if (!json_spirit::read(s, *result)) { return -EINVAL; } json_spirit::mObject& base = result->get_obj(); string vendor = get_device_vendor(devname); if (vendor.size()) { base["nvme_vendor"] = vendor; s.clear(); json_spirit::mValue nvme_json; if (int r = block_device_run_vendor_nvme(devname, vendor, timeout, &s); r == 0) { if (json_spirit::read(s, nvme_json) != 0) { base["nvme_smart_health_information_add_log"] = nvme_json; } else { base["nvme_smart_health_information_add_log_error"] = "bad json output: " + s; } } else { base["nvme_smart_health_information_add_log_error_code"] = r; base["nvme_smart_health_information_add_log_error"] = s; } } else { base["nvme_vendor"] = "unknown"; } return 0; } #elif defined(__APPLE__) #include const char *BlkDev::sysfsdir() const { assert(false); // Should never be called on Apple return ""; } int BlkDev::dev(char *dev, size_t max) const { struct stat sb; if (fstat(fd, &sb) < 0) return -errno; snprintf(dev, max, "%" PRIu64, (uint64_t)sb.st_rdev); return 0; } int BlkDev::get_size(int64_t *psize) const { unsigned long blocksize = 0; int ret = ::ioctl(fd, DKIOCGETBLOCKSIZE, &blocksize); if (!ret) { unsigned long nblocks; ret = ::ioctl(fd, DKIOCGETBLOCKCOUNT, &nblocks); if (!ret) *psize = (int64_t)nblocks * blocksize; } if (ret < 0) ret = -errno; return ret; } int64_t BlkDev::get_int_property(const char* prop) const { return 0; } bool BlkDev::support_discard() const { return false; } int BlkDev::discard(int64_t offset, int64_t len) const { return -EOPNOTSUPP; } bool BlkDev::is_rotational() const { return false; } int BlkDev::get_numa_node(int *node) const { return -1; } int BlkDev::model(char *model, size_t max) const { return -EOPNOTSUPP; } int BlkDev::serial(char *serial, size_t max) const { return -EOPNOTSUPP; } int BlkDev::partition(char *partition, size_t max) const { return -EOPNOTSUPP; } int BlkDev::wholedisk(char *device, size_t max) const { } void get_dm_parents(const std::string& dev, std::set *ls) { } void get_raw_devices(const std::string& in, std::set *ls) { } int get_vdo_stats_handle(const char *devname, std::string *vdo_name) { return -1; } int64_t get_vdo_stat(int fd, const char *property) { return 0; } bool get_vdo_utilization(int fd, uint64_t *total, uint64_t *avail) { return false; } std::string get_device_id(const std::string& devname, std::string *err) { // FIXME: implement me if (err) { *err = "not implemented"; } return std::string(); } std::string get_device_path(const std::string& devname, std::string *err) { // FIXME: implement me if (err) { *err = "not implemented"; } return std::string(); } #elif defined(__FreeBSD__) const char *BlkDev::sysfsdir() const { assert(false); // Should never be called on FreeBSD return ""; } int BlkDev::dev(char *dev, size_t max) const { struct stat sb; if (fstat(fd, &sb) < 0) return -errno; snprintf(dev, max, "%" PRIu64, (uint64_t)sb.st_rdev); return 0; } int BlkDev::get_size(int64_t *psize) const { int ret = ::ioctl(fd, DIOCGMEDIASIZE, psize); if (ret < 0) ret = -errno; return ret; } int64_t BlkDev::get_int_property(const char* prop) const { return 0; } bool BlkDev::support_discard() const { #ifdef FREEBSD_WITH_TRIM // there is no point to claim support of discard, but // unable to do so. struct diocgattr_arg arg; strlcpy(arg.name, "GEOM::candelete", sizeof(arg.name)); arg.len = sizeof(arg.value.i); if (ioctl(fd, DIOCGATTR, &arg) == 0) { return (arg.value.i != 0); } else { return false; } #endif return false; } int BlkDev::discard(int64_t offset, int64_t len) const { return -EOPNOTSUPP; } bool BlkDev::is_rotational() const { #if __FreeBSD_version >= 1200049 struct diocgattr_arg arg; strlcpy(arg.name, "GEOM::rotation_rate", sizeof(arg.name)); arg.len = sizeof(arg.value.u16); int ioctl_ret = ioctl(fd, DIOCGATTR, &arg); bool ret; if (ioctl_ret < 0 || arg.value.u16 == DISK_RR_UNKNOWN) // DISK_RR_UNKNOWN usually indicates an old drive, which is usually spinny ret = true; else if (arg.value.u16 == DISK_RR_NON_ROTATING) ret = false; else if (arg.value.u16 >= DISK_RR_MIN && arg.value.u16 <= DISK_RR_MAX) ret = true; else ret = true; // Invalid value. Probably spinny? return ret; #else return true; // When in doubt, it's probably spinny #endif } int BlkDev::get_numa_node(int *node) const { int numa = get_int_property("device/device/numa_node"); if (numa < 0) return -1; *node = numa; return 0; } int BlkDev::model(char *model, size_t max) const { struct diocgattr_arg arg; strlcpy(arg.name, "GEOM::descr", sizeof(arg.name)); arg.len = sizeof(arg.value.str); if (ioctl(fd, DIOCGATTR, &arg) < 0) { return -errno; } // The GEOM description is of the form "vendor product" for SCSI disks // and "ATA device_model" for ATA disks. Some vendors choose to put the // vendor name in device_model, and some don't. Strip the first bit. char *p = arg.value.str; if (p == NULL || *p == '\0') { *model = '\0'; } else { (void) strsep(&p, " "); snprintf(model, max, "%s", p); } return 0; } int BlkDev::serial(char *serial, size_t max) const { char ident[DISK_IDENT_SIZE]; if (ioctl(fd, DIOCGIDENT, ident) < 0) return -errno; snprintf(serial, max, "%s", ident); return 0; } void get_dm_parents(const std::string& dev, std::set *ls) { } void get_raw_devices(const std::string& in, std::set *ls) { } int get_vdo_stats_handle(const char *devname, std::string *vdo_name) { return -1; } int64_t get_vdo_stat(int fd, const char *property) { return 0; } bool get_vdo_utilization(int fd, uint64_t *total, uint64_t *avail) { return false; } std::string get_device_id(const std::string& devname, std::string *err) { // FIXME: implement me for freebsd if (err) { *err = "not implemented for FreeBSD"; } return std::string(); } std::string get_device_path(const std::string& devname, std::string *err) { // FIXME: implement me for freebsd if (err) { *err = "not implemented for FreeBSD"; } return std::string(); } int block_device_run_smartctl(const char *device, int timeout, std::string *result) { // FIXME: implement me for freebsd return -EOPNOTSUPP; } int block_device_get_metrics(const string& devname, int timeout, json_spirit::mValue *result) { // FIXME: implement me for freebsd return -EOPNOTSUPP; } int block_device_run_nvme(const char *device, const char *vendor, int timeout, std::string *result) { return -EOPNOTSUPP; } static int block_device_devname(int fd, char *devname, size_t max) { struct fiodgname_arg arg; arg.buf = devname; arg.len = max; if (ioctl(fd, FIODGNAME, &arg) < 0) return -errno; return 0; } int BlkDev::partition(char *partition, size_t max) const { char devname[PATH_MAX]; if (block_device_devname(fd, devname, sizeof(devname)) < 0) return -errno; snprintf(partition, max, "/dev/%s", devname); return 0; } int BlkDev::wholedisk(char *wd, size_t max) const { char devname[PATH_MAX]; if (block_device_devname(fd, devname, sizeof(devname)) < 0) return -errno; size_t first_digit = strcspn(devname, "0123456789"); // first_digit now indexes the first digit or null character of devname size_t next_nondigit = strspn(&devname[first_digit], "0123456789"); next_nondigit += first_digit; // next_nondigit now indexes the first alphabetic or null character after the // unit number strlcpy(wd, devname, next_nondigit + 1); return 0; } #else const char *BlkDev::sysfsdir() const { assert(false); // Should never be called on non-Linux return ""; } int BlkDev::dev(char *dev, size_t max) const { return -EOPNOTSUPP; } int BlkDev::get_size(int64_t *psize) const { return -EOPNOTSUPP; } bool BlkDev::support_discard() const { return false; } int BlkDev::discard(int fd, int64_t offset, int64_t len) const { return -EOPNOTSUPP; } bool BlkDev::is_rotational(const char *devname) const { return false; } int BlkDev::model(char *model, size_t max) const { return -EOPNOTSUPP; } int BlkDev::serial(char *serial, size_t max) const { return -EOPNOTSUPP; } int BlkDev::partition(char *partition, size_t max) const { return -EOPNOTSUPP; } int BlkDev::wholedisk(char *wd, size_t max) const { return -EOPNOTSUPP; } void get_dm_parents(const std::string& dev, std::set *ls) { } void get_raw_devices(const std::string& in, std::set *ls) { } int get_vdo_stats_handle(const char *devname, std::string *vdo_name) { return -1; } int64_t get_vdo_stat(int fd, const char *property) { return 0; } bool get_vdo_utilization(int fd, uint64_t *total, uint64_t *avail) { return false; } std::string get_device_id(const std::string& devname, std::string *err) { // not implemented if (err) { *err = "not implemented"; } return std::string(); } std::string get_device_path(const std::string& devname, std::string *err) { // not implemented if (err) { *err = "not implemented"; } return std::string(); } int block_device_run_smartctl(const char *device, int timeout, std::string *result) { return -EOPNOTSUPP; } int block_device_get_metrics(const string& devname, int timeout, json_spirit::mValue *result) { return -EOPNOTSUPP; } int block_device_run_nvme(const char *device, const char *vendor, int timeout, std::string *result) { return -EOPNOTSUPP; } #endif void get_device_metadata( const std::set& devnames, std::map *pm, std::map *errs) { (*pm)["devices"] = stringify(devnames); string &devids = (*pm)["device_ids"]; string &devpaths = (*pm)["device_paths"]; for (auto& dev : devnames) { string err; string id = get_device_id(dev, &err); if (id.size()) { if (!devids.empty()) { devids += ","; } devids += dev + "=" + id; } else { (*errs)[dev] = " no unique device id for "s + dev + ": " + err; } string path = get_device_path(dev, &err); if (path.size()) { if (!devpaths.empty()) { devpaths += ","; } devpaths += dev + "=" + path; } else { (*errs)[dev] + " no unique device path for "s + dev + ": " + err; } } }