/* * No copyright is claimed. This code is in the public domain; do with * it what you wish. * * Written by Karel Zak */ #include #include #include "c.h" #include "at.h" #include "pathnames.h" #include "sysfs.h" #include "fileutils.h" #include "all-io.h" char *sysfs_devno_attribute_path(dev_t devno, char *buf, size_t bufsiz, const char *attr) { int len; if (attr) len = snprintf(buf, bufsiz, _PATH_SYS_DEVBLOCK "/%d:%d/%s", major(devno), minor(devno), attr); else len = snprintf(buf, bufsiz, _PATH_SYS_DEVBLOCK "/%d:%d", major(devno), minor(devno)); return (len < 0 || (size_t) len + 1 > bufsiz) ? NULL : buf; } int sysfs_devno_has_attribute(dev_t devno, const char *attr) { char path[PATH_MAX]; struct stat info; if (!sysfs_devno_attribute_path(devno, path, sizeof(path), attr)) return 0; if (stat(path, &info) == 0) return 1; return 0; } char *sysfs_devno_path(dev_t devno, char *buf, size_t bufsiz) { return sysfs_devno_attribute_path(devno, buf, bufsiz, NULL); } dev_t sysfs_devname_to_devno(const char *name, const char *parent) { char buf[PATH_MAX], *path = NULL; dev_t dev = 0; if (strncmp("/dev/", name, 5) == 0) { /* * Read from /dev */ struct stat st; if (stat(name, &st) == 0) dev = st.st_rdev; else name += 5; /* unaccesible, or not node in /dev */ } if (!dev && parent && strncmp("dm-", name, 3)) { /* * Create path to /sys/block///dev */ char *_name = strdup(name), *_parent = strdup(parent); int len; if (!_name || !_parent) { free(_name); free(_parent); return 0; } sysfs_devname_dev_to_sys(_name); sysfs_devname_dev_to_sys(_parent); len = snprintf(buf, sizeof(buf), _PATH_SYS_BLOCK "/%s/%s/dev", _parent, _name); free(_name); free(_parent); if (len < 0 || (size_t) len + 1 > sizeof(buf)) return 0; path = buf; } else if (!dev) { /* * Create path to /sys/block//dev */ char *_name = strdup(name); int len; if (!_name) return 0; sysfs_devname_dev_to_sys(_name); len = snprintf(buf, sizeof(buf), _PATH_SYS_BLOCK "/%s/dev", _name); free(_name); if (len < 0 || (size_t) len + 1 > sizeof(buf)) return 0; path = buf; } if (path) { /* * read devno from sysfs */ FILE *f; int maj = 0, min = 0; f = fopen(path, "r" UL_CLOEXECSTR); if (!f) return 0; if (fscanf(f, "%d:%d", &maj, &min) == 2) dev = makedev(maj, min); fclose(f); } return dev; } /* * Returns devname (e.g. "/dev/sda1") for the given devno. * * Please, use more robust blkid_devno_to_devname() in your applications. */ char *sysfs_devno_to_devpath(dev_t devno, char *buf, size_t bufsiz) { struct sysfs_cxt cxt; char *name; size_t sz; struct stat st; if (sysfs_init(&cxt, devno, NULL)) return NULL; name = sysfs_get_devname(&cxt, buf, bufsiz); sysfs_deinit(&cxt); if (!name) return NULL; sz = strlen(name); if (sz + sizeof("/dev/") > bufsiz) return NULL; /* create the final "/dev/" string */ memmove(buf + 5, name, sz + 1); memcpy(buf, "/dev/", 5); if (!stat(buf, &st) && S_ISBLK(st.st_mode) && st.st_rdev == devno) return buf; return NULL; } int sysfs_init(struct sysfs_cxt *cxt, dev_t devno, struct sysfs_cxt *parent) { char path[PATH_MAX]; int fd, rc; memset(cxt, 0, sizeof(*cxt)); cxt->dir_fd = -1; if (!sysfs_devno_path(devno, path, sizeof(path))) goto err; fd = open(path, O_RDONLY|O_CLOEXEC); if (fd < 0) goto err; cxt->dir_fd = fd; cxt->dir_path = strdup(path); if (!cxt->dir_path) goto err; cxt->devno = devno; cxt->parent = parent; return 0; err: rc = errno > 0 ? -errno : -1; sysfs_deinit(cxt); return rc; } void sysfs_deinit(struct sysfs_cxt *cxt) { if (!cxt) return; if (cxt->dir_fd >= 0) close(cxt->dir_fd); free(cxt->dir_path); memset(cxt, 0, sizeof(*cxt)); cxt->dir_fd = -1; } int sysfs_stat(struct sysfs_cxt *cxt, const char *attr, struct stat *st) { int rc = fstat_at(cxt->dir_fd, cxt->dir_path, attr, st, 0); if (rc != 0 && errno == ENOENT && strncmp(attr, "queue/", 6) == 0 && cxt->parent) { /* Exception for "queue/". These attributes are available * for parental devices only */ return fstat_at(cxt->parent->dir_fd, cxt->parent->dir_path, attr, st, 0); } return rc; } int sysfs_has_attribute(struct sysfs_cxt *cxt, const char *attr) { struct stat st; return sysfs_stat(cxt, attr, &st) == 0; } static int sysfs_open(struct sysfs_cxt *cxt, const char *attr, int flags) { int fd = open_at(cxt->dir_fd, cxt->dir_path, attr, flags); if (fd == -1 && errno == ENOENT && strncmp(attr, "queue/", 6) == 0 && cxt->parent) { /* Exception for "queue/". These attributes are available * for parental devices only */ fd = open_at(cxt->parent->dir_fd, cxt->dir_path, attr, flags); } return fd; } ssize_t sysfs_readlink(struct sysfs_cxt *cxt, const char *attr, char *buf, size_t bufsiz) { if (!cxt->dir_path) return -1; if (attr) return readlink_at(cxt->dir_fd, cxt->dir_path, attr, buf, bufsiz); /* read /sys/dev/block/ link */ return readlink(cxt->dir_path, buf, bufsiz); } DIR *sysfs_opendir(struct sysfs_cxt *cxt, const char *attr) { DIR *dir; int fd = -1; if (attr) fd = sysfs_open(cxt, attr, O_RDONLY|O_CLOEXEC); else if (cxt->dir_fd >= 0) /* request to open root of device in sysfs (/sys/block/) * -- we cannot use cxt->sysfs_fd directly, because closedir() * will close this our persistent file descriptor. */ fd = dup_fd_cloexec(cxt->dir_fd, STDERR_FILENO + 1); if (fd < 0) return NULL; dir = fdopendir(fd); if (!dir) { close(fd); return NULL; } if (!attr) rewinddir(dir); return dir; } static FILE *sysfs_fopen(struct sysfs_cxt *cxt, const char *attr) { int fd = sysfs_open(cxt, attr, O_RDONLY|O_CLOEXEC); return fd < 0 ? NULL : fdopen(fd, "r" UL_CLOEXECSTR); } static struct dirent *xreaddir(DIR *dp) { struct dirent *d; while ((d = readdir(dp))) { if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) continue; /* blacklist here? */ break; } return d; } int sysfs_is_partition_dirent(DIR *dir, struct dirent *d, const char *parent_name) { char path[256]; #ifdef _DIRENT_HAVE_D_TYPE if (d->d_type != DT_DIR && d->d_type != DT_LNK && d->d_type != DT_UNKNOWN) return 0; #endif if (parent_name) { const char *p = parent_name; size_t len; /* /dev/sda --> "sda" */ if (*parent_name == '/') { p = strrchr(parent_name, '/'); if (!p) return 0; p++; } len = strlen(p); if (strlen(d->d_name) <= len) return 0; /* partitions subdir name is * "[:digit:]" or "p[:digit:]" */ return strncmp(p, d->d_name, len) == 0 && ((*(d->d_name + len) == 'p' && isdigit(*(d->d_name + len + 1))) || isdigit(*(d->d_name + len))); } /* Cannot use /partition file, not supported on old sysfs */ snprintf(path, sizeof(path), "%s/start", d->d_name); return faccessat(dirfd(dir), path, R_OK, 0) == 0; } /* * Converts @partno (partition number) to devno of the partition. * The @cxt handles wholedisk device. * * Note that this code does not expect any special format of the * partitions devnames. */ dev_t sysfs_partno_to_devno(struct sysfs_cxt *cxt, int partno) { DIR *dir; struct dirent *d; char path[256]; dev_t devno = 0; dir = sysfs_opendir(cxt, NULL); if (!dir) return 0; while ((d = xreaddir(dir))) { int n, maj, min; if (!sysfs_is_partition_dirent(dir, d, NULL)) continue; snprintf(path, sizeof(path), "%s/partition", d->d_name); if (sysfs_read_int(cxt, path, &n)) continue; if (n == partno) { snprintf(path, sizeof(path), "%s/dev", d->d_name); if (sysfs_scanf(cxt, path, "%d:%d", &maj, &min) == 2) devno = makedev(maj, min); break; } } closedir(dir); return devno; } int sysfs_scanf(struct sysfs_cxt *cxt, const char *attr, const char *fmt, ...) { FILE *f = sysfs_fopen(cxt, attr); va_list ap; int rc; if (!f) return -EINVAL; va_start(ap, fmt); rc = vfscanf(f, fmt, ap); va_end(ap); fclose(f); return rc; } int sysfs_read_s64(struct sysfs_cxt *cxt, const char *attr, int64_t *res) { int64_t x = 0; if (sysfs_scanf(cxt, attr, "%"SCNd64, &x) == 1) { if (res) *res = x; return 0; } return -1; } int sysfs_read_u64(struct sysfs_cxt *cxt, const char *attr, uint64_t *res) { uint64_t x = 0; if (sysfs_scanf(cxt, attr, "%"SCNu64, &x) == 1) { if (res) *res = x; return 0; } return -1; } int sysfs_read_int(struct sysfs_cxt *cxt, const char *attr, int *res) { int x = 0; if (sysfs_scanf(cxt, attr, "%d", &x) == 1) { if (res) *res = x; return 0; } return -1; } int sysfs_write_string(struct sysfs_cxt *cxt, const char *attr, const char *str) { int fd = sysfs_open(cxt, attr, O_WRONLY|O_CLOEXEC); int rc, errsv; if (fd < 0) return -errno; rc = write_all(fd, str, strlen(str)); errsv = errno; close(fd); errno = errsv; return rc; } int sysfs_write_u64(struct sysfs_cxt *cxt, const char *attr, uint64_t num) { char buf[sizeof(stringify_value(ULLONG_MAX))]; int fd, rc = 0, len, errsv; fd = sysfs_open(cxt, attr, O_WRONLY|O_CLOEXEC); if (fd < 0) return -errno; len = snprintf(buf, sizeof(buf), "%" PRIu64, num); if (len < 0 || (size_t) len + 1 > sizeof(buf)) rc = len < 0 ? -errno : -E2BIG; else rc = write_all(fd, buf, len); errsv = errno; close(fd); errno = errsv; return rc; } char *sysfs_strdup(struct sysfs_cxt *cxt, const char *attr) { char buf[BUFSIZ]; return sysfs_scanf(cxt, attr, "%1023[^\n]", buf) == 1 ? strdup(buf) : NULL; } int sysfs_count_dirents(struct sysfs_cxt *cxt, const char *attr) { DIR *dir; int r = 0; if (!(dir = sysfs_opendir(cxt, attr))) return 0; while (xreaddir(dir)) r++; closedir(dir); return r; } int sysfs_count_partitions(struct sysfs_cxt *cxt, const char *devname) { DIR *dir; struct dirent *d; int r = 0; if (!(dir = sysfs_opendir(cxt, NULL))) return 0; while ((d = xreaddir(dir))) { if (sysfs_is_partition_dirent(dir, d, devname)) r++; } closedir(dir); return r; } /* * Returns slave name if there is only one slave, otherwise returns NULL. * The result should be deallocated by free(). */ char *sysfs_get_slave(struct sysfs_cxt *cxt) { DIR *dir; struct dirent *d; char *name = NULL; if (!(dir = sysfs_opendir(cxt, "slaves"))) return NULL; while ((d = xreaddir(dir))) { if (name) goto err; /* more slaves */ name = strdup(d->d_name); } closedir(dir); return name; err: free(name); closedir(dir); return NULL; } char *sysfs_get_devname(struct sysfs_cxt *cxt, char *buf, size_t bufsiz) { char linkpath[PATH_MAX]; char *name; ssize_t sz; sz = sysfs_readlink(cxt, NULL, linkpath, sizeof(linkpath) - 1); if (sz < 0) return NULL; linkpath[sz] = '\0'; name = strrchr(linkpath, '/'); if (!name) return NULL; name++; sz = strlen(name); if ((size_t) sz + 1 > bufsiz) return NULL; memcpy(buf, name, sz + 1); sysfs_devname_sys_to_dev(buf); return buf; } #define SUBSYSTEM_LINKNAME "/subsystem" /* * For example: * * chain: /sys/dev/block/../../devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/ \ * 1-1.2:1.0/host65/target65:0:0/65:0:0:0/block/sdb * * The function check if /subsystem symlink exists, if yes then returns * basename of the readlink result, and remove the last subdirectory from the * path. */ static char *get_subsystem(char *chain, char *buf, size_t bufsz) { size_t len; char *p; if (!chain || !*chain) return NULL; len = strlen(chain); if (len + sizeof(SUBSYSTEM_LINKNAME) > PATH_MAX) return NULL; do { ssize_t sz; /* append "/subsystem" to the path */ memcpy(chain + len, SUBSYSTEM_LINKNAME, sizeof(SUBSYSTEM_LINKNAME)); /* try if subsystem symlink exists */ sz = readlink(chain, buf, bufsz - 1); /* remove last subsystem from chain */ chain[len] = '\0'; p = strrchr(chain, '/'); if (p) { *p = '\0'; len = p - chain; } if (sz > 0) { /* we found symlink to subsystem, return basename */ buf[sz] = '\0'; return basename(buf); } } while (p); return NULL; } /* * Returns complete path to the device, the patch contains all all sybsystems * used for the device. */ char *sysfs_get_devchain(struct sysfs_cxt *cxt, char *buf, size_t bufsz) { /* read /sys/dev/block/: symlink */ ssize_t sz = sysfs_readlink(cxt, NULL, buf, bufsz); if (sz <= 0 || sz + sizeof(_PATH_SYS_DEVBLOCK "/") > bufsz) return NULL; buf[sz++] = '\0'; /* create absolute patch from the link */ memmove(buf + sizeof(_PATH_SYS_DEVBLOCK "/") - 1, buf, sz); memcpy(buf, _PATH_SYS_DEVBLOCK "/", sizeof(_PATH_SYS_DEVBLOCK "/") - 1); return buf; } /* * The @subsys returns the next subsystem in the chain. Function modifies * @devchain string. * * Returns: 0 in success, <0 on error, 1 on end of chain */ int sysfs_next_subsystem(struct sysfs_cxt *cxt __attribute__((unused)), char *devchain, char **subsys) { char subbuf[PATH_MAX]; char *sub; if (!subsys || !devchain) return -EINVAL; *subsys = NULL; while ((sub = get_subsystem(devchain, subbuf, sizeof(subbuf)))) { *subsys = strdup(sub); if (!*subsys) return -ENOMEM; return 0; } return 1; } static int is_hotpluggable_subsystem(const char *name) { static const char * const hotplug_subsystems[] = { "usb", "ieee1394", "pcmcia", "mmc", "ccw" }; size_t i; for (i = 0; i < ARRAY_SIZE(hotplug_subsystems); i++) if (strcmp(name, hotplug_subsystems[i]) == 0) return 1; return 0; } int sysfs_is_hotpluggable(struct sysfs_cxt *cxt) { char buf[PATH_MAX], *chain, *sub; int rc = 0; /* check /sys/dev/block/:/removable attribute */ if (sysfs_read_int(cxt, "removable", &rc) == 0 && rc == 1) return 1; chain = sysfs_get_devchain(cxt, buf, sizeof(buf)); while (chain && sysfs_next_subsystem(cxt, chain, &sub) == 0) { rc = is_hotpluggable_subsystem(sub); if (rc) { free(sub); break; } free(sub); } return rc; } static int get_dm_wholedisk(struct sysfs_cxt *cxt, char *diskname, size_t len, dev_t *diskdevno) { int rc = 0; char *name; /* Note, sysfs_get_slave() returns the first slave only, * if there is more slaves, then return NULL */ name = sysfs_get_slave(cxt); if (!name) return -1; if (diskname && len) { strncpy(diskname, name, len); diskname[len - 1] = '\0'; } if (diskdevno) { *diskdevno = sysfs_devname_to_devno(name, NULL); if (!*diskdevno) rc = -1; } free(name); return rc; } /* * Returns by @diskdevno whole disk device devno and (optionaly) by * @diskname the whole disk device name. */ int sysfs_devno_to_wholedisk(dev_t dev, char *diskname, size_t len, dev_t *diskdevno) { struct sysfs_cxt cxt; int is_part = 0; if (!dev || sysfs_init(&cxt, dev, NULL) != 0) return -1; is_part = sysfs_has_attribute(&cxt, "partition"); if (!is_part) { /* * Extra case for partitions mapped by device-mapper. * * All regualar partitions (added by BLKPG ioctl or kernel PT * parser) have the /sys/.../partition file. The partitions * mapped by DM don't have such file, but they have "part" * prefix in DM UUID. */ char *uuid = sysfs_strdup(&cxt, "dm/uuid"); char *tmp = uuid; char *prefix = uuid ? strsep(&tmp, "-") : NULL; if (prefix && strncasecmp(prefix, "part", 4) == 0) is_part = 1; free(uuid); if (is_part && get_dm_wholedisk(&cxt, diskname, len, diskdevno) == 0) /* * partitioned device, mapped by DM */ goto done; is_part = 0; } if (!is_part) { /* * unpartitioned device */ if (diskname && len) { if (!sysfs_get_devname(&cxt, diskname, len)) goto err; } if (diskdevno) *diskdevno = dev; } else { /* * partitioned device * - readlink /sys/dev/block/8:1 = ../../block/sda/sda1 * - dirname ../../block/sda/sda1 = ../../block/sda * - basename ../../block/sda = sda */ char linkpath[PATH_MAX]; char *name; ssize_t linklen; linklen = sysfs_readlink(&cxt, NULL, linkpath, sizeof(linkpath) - 1); if (linklen < 0) goto err; linkpath[linklen] = '\0'; stripoff_last_component(linkpath); /* dirname */ name = stripoff_last_component(linkpath); /* basename */ if (!name) goto err; sysfs_devname_sys_to_dev(name); if (diskname && len) { strncpy(diskname, name, len); diskname[len - 1] = '\0'; } if (diskdevno) { *diskdevno = sysfs_devname_to_devno(name, NULL); if (!*diskdevno) goto err; } } done: sysfs_deinit(&cxt); return 0; err: sysfs_deinit(&cxt); return -1; } /* * Returns 1 if the device is private LVM device. */ int sysfs_devno_is_lvm_private(dev_t devno) { struct sysfs_cxt cxt = UL_SYSFSCXT_EMPTY; char *uuid = NULL; int rc = 0; if (sysfs_init(&cxt, devno, NULL) != 0) return 0; uuid = sysfs_strdup(&cxt, "dm/uuid"); /* Private LVM devices use "LVM--" uuid format (important * is the "LVM" prefix and "-" postfix). */ if (uuid && strncmp(uuid, "LVM-", 4) == 0) { char *p = strrchr(uuid + 4, '-'); if (p && *(p + 1)) rc = 1; } sysfs_deinit(&cxt); free(uuid); return rc; } /* * Return 0 or 1, or < 0 in case of error */ int sysfs_devno_is_wholedisk(dev_t devno) { dev_t disk; if (sysfs_devno_to_wholedisk(devno, NULL, 0, &disk) != 0) return -1; return devno == disk; } int sysfs_scsi_get_hctl(struct sysfs_cxt *cxt, int *h, int *c, int *t, int *l) { char buf[PATH_MAX], *hctl; ssize_t len; if (!cxt) return -EINVAL; if (cxt->has_hctl) goto done; len = sysfs_readlink(cxt, "device", buf, sizeof(buf) - 1); if (len < 0) return len; buf[len] = '\0'; hctl = strrchr(buf, '/'); if (!hctl) return -1; hctl++; if (sscanf(hctl, "%u:%u:%u:%u", &cxt->scsi_host, &cxt->scsi_channel, &cxt->scsi_target, &cxt->scsi_lun) != 4) return -1; cxt->has_hctl = 1; done: if (h) *h = cxt->scsi_host; if (c) *c = cxt->scsi_channel; if (t) *t = cxt->scsi_target; if (l) *l = cxt->scsi_lun; return 0; } static char *sysfs_scsi_host_attribute_path(struct sysfs_cxt *cxt, const char *type, char *buf, size_t bufsz, const char *attr) { int len; int host; if (sysfs_scsi_get_hctl(cxt, &host, NULL, NULL, NULL)) return NULL; if (attr) len = snprintf(buf, bufsz, _PATH_SYS_CLASS "/%s_host/host%d/%s", type, host, attr); else len = snprintf(buf, bufsz, _PATH_SYS_CLASS "/%s_host/host%d", type, host); return (len < 0 || (size_t) len + 1 > bufsz) ? NULL : buf; } char *sysfs_scsi_host_strdup_attribute(struct sysfs_cxt *cxt, const char *type, const char *attr) { char buf[1024]; int rc; FILE *f; if (!attr || !type || !sysfs_scsi_host_attribute_path(cxt, type, buf, sizeof(buf), attr)) return NULL; if (!(f = fopen(buf, "r" UL_CLOEXECSTR))) return NULL; rc = fscanf(f, "%1023[^\n]", buf); fclose(f); return rc == 1 ? strdup(buf) : NULL; } int sysfs_scsi_host_is(struct sysfs_cxt *cxt, const char *type) { char buf[PATH_MAX]; struct stat st; if (!type || !sysfs_scsi_host_attribute_path(cxt, type, buf, sizeof(buf), NULL)) return 0; return stat(buf, &st) == 0 && S_ISDIR(st.st_mode); } static char *sysfs_scsi_attribute_path(struct sysfs_cxt *cxt, char *buf, size_t bufsz, const char *attr) { int len, h, c, t, l; if (sysfs_scsi_get_hctl(cxt, &h, &c, &t, &l) != 0) return NULL; if (attr) len = snprintf(buf, bufsz, _PATH_SYS_SCSI "/devices/%d:%d:%d:%d/%s", h,c,t,l, attr); else len = snprintf(buf, bufsz, _PATH_SYS_SCSI "/devices/%d:%d:%d:%d", h,c,t,l); return (len < 0 || (size_t) len + 1 > bufsz) ? NULL : buf; } int sysfs_scsi_has_attribute(struct sysfs_cxt *cxt, const char *attr) { char path[PATH_MAX]; struct stat st; if (!sysfs_scsi_attribute_path(cxt, path, sizeof(path), attr)) return 0; return stat(path, &st) == 0; } int sysfs_scsi_path_contains(struct sysfs_cxt *cxt, const char *pattern) { char path[PATH_MAX], linkc[PATH_MAX]; struct stat st; ssize_t len; if (!sysfs_scsi_attribute_path(cxt, path, sizeof(path), NULL)) return 0; if (stat(path, &st) != 0) return 0; len = readlink(path, linkc, sizeof(linkc) - 1); if (len < 0) return 0; linkc[len] = '\0'; return strstr(linkc, pattern) != NULL; } #ifdef TEST_PROGRAM_SYSFS #include #include #include int main(int argc, char *argv[]) { struct sysfs_cxt cxt = UL_SYSFSCXT_EMPTY; char *devname; dev_t devno, disk_devno; char path[PATH_MAX], *sub, *chain; char diskname[32]; int i, is_part; uint64_t u64; ssize_t len; if (argc != 2) errx(EXIT_FAILURE, "usage: %s ", argv[0]); devname = argv[1]; devno = sysfs_devname_to_devno(devname, NULL); if (!devno) err(EXIT_FAILURE, "failed to read devno"); if (sysfs_init(&cxt, devno, NULL)) return EXIT_FAILURE; printf("NAME: %s\n", devname); printf("DEVNAME: %s\n", sysfs_get_devname(&cxt, path, sizeof(path))); printf("DEVPATH: %s\n", sysfs_devno_to_devpath(devno, path, sizeof(path))); printf("DEVNO: %u (%d:%d)\n", (unsigned int) devno, major(devno), minor(devno)); printf("DEVNO-PATH: %s\n", sysfs_devno_path(devno, path, sizeof(path))); sysfs_devno_to_wholedisk(devno, diskname, sizeof(diskname), &disk_devno); printf("WHOLEDISK-DEVNO: %u (%d:%d)\n", (unsigned int) disk_devno, major(disk_devno), minor(disk_devno)); printf("WHOLEDISK-DEVNAME: %s\n", diskname); is_part = sysfs_devno_has_attribute(devno, "partition"); printf("PARTITION: %s\n", is_part ? "YES" : "NOT"); printf("HOTPLUG: %s\n", sysfs_is_hotpluggable(&cxt) ? "yes" : "no"); printf("SLAVES: %d\n", sysfs_count_dirents(&cxt, "slaves")); len = sysfs_readlink(&cxt, NULL, path, sizeof(path) - 1); if (len > 0) { path[len] = '\0'; printf("DEVNOLINK: %s\n", path); } if (!is_part) { printf("First 5 partitions:\n"); for (i = 1; i <= 5; i++) { dev_t dev = sysfs_partno_to_devno(&cxt, i); if (dev) printf("\t#%d %d:%d\n", i, major(dev), minor(dev)); } } if (sysfs_read_u64(&cxt, "size", &u64)) printf("read SIZE failed\n"); else printf("SIZE: %jd\n", u64); if (sysfs_read_int(&cxt, "queue/hw_sector_size", &i)) printf("read SECTOR failed\n"); else printf("SECTOR: %d\n", i); chain = sysfs_get_devchain(&cxt, path, sizeof(path)); printf("SUBSUSTEMS:\n"); while (chain && sysfs_next_subsystem(&cxt, chain, &sub) == 0) { printf("\t%s\n", sub); free(sub); } sysfs_deinit(&cxt); return EXIT_SUCCESS; } #endif