diff options
Diffstat (limited to 'src/common/blkdev.cc')
-rw-r--r-- | src/common/blkdev.cc | 1192 |
1 files changed, 1192 insertions, 0 deletions
diff --git a/src/common/blkdev.cc b/src/common/blkdev.cc new file mode 100644 index 000000000..6fc2965a1 --- /dev/null +++ b/src/common/blkdev.cc @@ -0,0 +1,1192 @@ +// -*- 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 <sys/param.h> +#include <geom/geom_disk.h> +#include <sys/disk.h> +#include <fcntl.h> +#endif + +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dirent.h> +#include <boost/algorithm/string/replace.hpp> +//#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 <libudev.h> +#include <linux/fs.h> +#include <linux/kdev_t.h> +#include <blkid/blkid.h> + +#include <set> + +#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<int>(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); +} + +int BlkDev::get_optimal_io_size() const +{ + return get_int_property("queue/optimal_io_size"); +} + +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<std::string> *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<std::string> *ls) +{ + std::string p = std::string("/sys/block/") + dev + "/slaves"; + std::set<std::string> 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<std::string> *ls) +{ + if (in.substr(0, 3) == "dm-") { + std::set<std::string> 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); + } + } +} + +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(), ' ', '_'); + + // remove "__", which seems to come up on by ubuntu box for some reason. + while (true) { + auto p = v.find("__"); + if (p == std::string::npos) break; + v.replace(p, 2, "_"); + } + + 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<std::string> 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 <sys/disk.h> + +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; +} + +int BlkDev::get_optimal_io_size() const +{ + return 0; +} + +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<std::string> *ls) +{ +} + +void get_raw_devices(const std::string& in, + std::set<std::string> *ls) +{ +} + +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; +} + +int BlkDev::get_optimal_io_size() const +{ + return 0; +} + +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<std::string> *ls) +{ +} + +void get_raw_devices(const std::string& in, + std::set<std::string> *ls) +{ +} + +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<std::string> *ls) +{ +} + +void get_raw_devices(const std::string& in, + std::set<std::string> *ls) +{ +} + +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<std::string>& devnames, + std::map<std::string,std::string> *pm, + std::map<std::string,std::string> *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; + } + } +} |