/* * 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 #include #include #include "include/uuid.h" #ifdef __linux__ #include #include #define UUID_LEN 36 static const char *sandbox_dir = ""; void set_block_device_sandbox_dir(const char *dir) { if (dir) sandbox_dir = dir; else sandbox_dir = ""; } int get_block_device_size(int fd, int64_t *psize) { #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_block_device_size)" #endif if (ret < 0) ret = -errno; return ret; } /** * get the base device (strip off partition suffix and /dev/ prefix) * e.g., * /dev/sda3 -> sda * /dev/cciss/c0d1p2 -> cciss/c0d1 * dev can a symbolic link. */ int get_block_device_base(const char *dev, char *out, size_t out_len) { struct stat st; int r = 0; DIR *dir; char devname[PATH_MAX] = {0}, fn[PATH_MAX] = {0}; char *p; char realname[PATH_MAX] = {0}; if (strncmp(dev, "/dev/", 5) != 0) { if (realpath(dev, realname) == NULL || (strncmp(realname, "/dev/", 5) != 0)) { return -EINVAL; } } if (strlen(realname)) strncpy(devname, realname + 5, PATH_MAX - 5); else strncpy(devname, dev + 5, strlen(dev) - 5); devname[PATH_MAX - 1] = '\0'; for (p = devname; *p; ++p) if (*p == '/') *p = '!'; snprintf(fn, sizeof(fn), "%s/sys/block/%s", sandbox_dir, devname); if (stat(fn, &st) == 0) { if (strlen(devname) + 1 > out_len) { return -ERANGE; } strncpy(out, devname, out_len); return 0; } snprintf(fn, sizeof(fn), "%s/sys/block", sandbox_dir); dir = opendir(fn); if (!dir) return -errno; struct dirent *de = nullptr; while ((de = ::readdir(dir))) { if (de->d_name[0] == '.') continue; snprintf(fn, sizeof(fn), "%s/sys/block/%s/%s", sandbox_dir, de->d_name, devname); if (stat(fn, &st) == 0) { // match! if (strlen(de->d_name) + 1 > out_len) { r = -ERANGE; goto out; } strncpy(out, de->d_name, out_len); r = 0; goto out; } } r = -ENOENT; out: closedir(dir); return r; } /** * 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 get_block_device_string_property(const char *devname, const char *property, char *val, size_t maxlen) { char filename[PATH_MAX]; snprintf(filename, sizeof(filename), "%s/sys/block/%s/%s", sandbox_dir, devname, property); 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 get_block_device_int_property(const char *devname, const char *property) { char buff[256] = {0}; int r = get_block_device_string_property(devname, property, 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 block_device_support_discard(const char *devname) { return get_block_device_int_property(devname, "queue/discard_granularity") > 0; } int block_device_discard(int fd, int64_t offset, int64_t len) { uint64_t range[2] = {(uint64_t)offset, (uint64_t)len}; return ioctl(fd, BLKDISCARD, range); } bool block_device_is_rotational(const char *devname) { return get_block_device_int_property(devname, "queue/rotational") > 0; } int block_device_model(const char *devname, char *model, size_t max) { return get_block_device_string_property(devname, "device/model", model, max); } int get_device_by_uuid(uuid_d dev_uuid, const char* label, char* partition, char* device) { char uuid_str[UUID_LEN+1]; char basename[PATH_MAX]; const char* temp_partition_ptr = NULL; blkid_cache cache = NULL; blkid_dev dev = NULL; int rc = 0; dev_uuid.print(uuid_str); if (blkid_get_cache(&cache, NULL) >= 0) dev = blkid_find_dev_with_tag(cache, label, (const char*)uuid_str); else return -EINVAL; if (dev) { temp_partition_ptr = blkid_dev_devname(dev); strncpy(partition, temp_partition_ptr, PATH_MAX); rc = get_block_device_base(partition, basename, sizeof(basename)); if (rc >= 0) { strncpy(device, basename, sizeof(basename)); rc = 0; } else { rc = -ENODEV; } } else { rc = -EINVAL; } /* From what I can tell, blkid_put_cache cleans up dev, which * appears to be a pointer into cache, as well */ if (cache) blkid_put_cache(cache); return rc; } int get_device_by_fd(int fd, char *partition, char *device, size_t max) { struct stat st; int r = fstat(fd, &st); if (r < 0) { return -EINVAL; // hrm. } dev_t devid = S_ISBLK(st.st_mode) ? st.st_rdev : st.st_dev; char *t = blkid_devno_to_devname(devid); if (!t) { return -EINVAL; } strncpy(partition, t, max); free(t); dev_t diskdev; r = blkid_devno_to_wholedisk(devid, device, max, &diskdev); if (r < 0) { return -EINVAL; } return 0; } #elif defined(__APPLE__) #include int get_block_device_size(int fd, int64_t *psize) { 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; } bool block_device_support_discard(const char *devname) { return false; } int block_device_discard(int fd, int64_t offset, int64_t len) { return -EOPNOTSUPP; } bool block_device_is_rotational(const char *devname) { return false; } int get_device_by_uuid(uuid_d dev_uuid, const char* label, char* partition, char* device) { return -EOPNOTSUPP; } #elif defined(__FreeBSD__) #include int get_block_device_size(int fd, int64_t *psize) { int ret = ::ioctl(fd, DIOCGMEDIASIZE, psize); if (ret < 0) ret = -errno; return ret; } bool block_device_support_discard(const char *devname) { return false; } int block_device_discard(int fd, int64_t offset, int64_t len) { return -EOPNOTSUPP; } bool block_device_is_rotational(const char *devname) { return false; } int get_device_by_uuid(uuid_d dev_uuid, const char* label, char* partition, char* device) { return -EOPNOTSUPP; } int get_device_by_fd(int fd, char *partition, char *device, size_t max) { return -EOPNOTSUPP; } #else int get_block_device_size(int fd, int64_t *psize) { return -EOPNOTSUPP; } bool block_device_support_discard(const char *devname) { return false; } int block_device_discard(int fd, int64_t offset, int64_t len) { return -EOPNOTSUPP; } bool block_device_is_rotational(const char *devname) { return false; } int get_device_by_uuid(uuid_d dev_uuid, const char* label, char* partition, char* device) { return -EOPNOTSUPP; } int get_device_by_fd(int fd, char *partition, char *device, size_t max) { return -EOPNOTSUPP; } #endif