/* * No copyright is claimed. This code is in the public domain; do with * it what you wish. * * Written by Karel Zak */ #include #include #include #include #include #include "c.h" #include "pathnames.h" #include "sysfs.h" #include "fileutils.h" #include "all-io.h" #include "debug.h" #include "strutils.h" static void sysfs_blkdev_deinit_path(struct path_cxt *pc); static int sysfs_blkdev_enoent_redirect(struct path_cxt *pc, const char *path, int *dirfd); /* * Debug stuff (based on include/debug.h) */ static UL_DEBUG_DEFINE_MASK(ulsysfs); UL_DEBUG_DEFINE_MASKNAMES(ulsysfs) = UL_DEBUG_EMPTY_MASKNAMES; #define ULSYSFS_DEBUG_INIT (1 << 1) #define ULSYSFS_DEBUG_CXT (1 << 2) #define DBG(m, x) __UL_DBG(ulsysfs, ULSYSFS_DEBUG_, m, x) #define ON_DBG(m, x) __UL_DBG_CALL(ulsysfs, ULSYSFS_DEBUG_, m, x) #define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulsysfs) #include "debugobj.h" void ul_sysfs_init_debug(void) { if (ulsysfs_debug_mask) return; __UL_INIT_DEBUG_FROM_ENV(ulsysfs, ULSYSFS_DEBUG_, 0, ULSYSFS_DEBUG); } struct path_cxt *ul_new_sysfs_path(dev_t devno, struct path_cxt *parent, const char *prefix) { struct path_cxt *pc = ul_new_path(NULL); if (!pc) return NULL; if (prefix) ul_path_set_prefix(pc, prefix); if (sysfs_blkdev_init_path(pc, devno, parent) != 0) { ul_unref_path(pc); return NULL; } DBG(CXT, ul_debugobj(pc, "alloc")); return pc; } /* * sysfs_blkdev_* is sysfs extension to ul_path_* API for block devices. * * The function is possible to call in loop and without sysfs_blkdev_deinit_path(). * The sysfs_blkdev_deinit_path() is automatically called by ul_unref_path(). * */ int sysfs_blkdev_init_path(struct path_cxt *pc, dev_t devno, struct path_cxt *parent) { struct sysfs_blkdev *blk; int rc; char buf[sizeof(_PATH_SYS_DEVBLOCK) + sizeof(stringify_value(UINT32_MAX)) * 2 + 3]; /* define path to devno stuff */ snprintf(buf, sizeof(buf), _PATH_SYS_DEVBLOCK "/%d:%d", major(devno), minor(devno)); rc = ul_path_set_dir(pc, buf); if (rc) return rc; /* make sure path exists */ rc = ul_path_get_dirfd(pc); if (rc < 0) return rc; /* initialize sysfs blkdev specific stuff */ blk = ul_path_get_dialect(pc); if (!blk) { DBG(CXT, ul_debugobj(pc, "alloc new sysfs handler")); blk = calloc(1, sizeof(struct sysfs_blkdev)); if (!blk) return -ENOMEM; ul_path_set_dialect(pc, blk, sysfs_blkdev_deinit_path); ul_path_set_enoent_redirect(pc, sysfs_blkdev_enoent_redirect); } DBG(CXT, ul_debugobj(pc, "init sysfs stuff")); blk->devno = devno; sysfs_blkdev_set_parent(pc, parent); return 0; } static void sysfs_blkdev_deinit_path(struct path_cxt *pc) { struct sysfs_blkdev *blk; if (!pc) return; DBG(CXT, ul_debugobj(pc, "deinit")); blk = ul_path_get_dialect(pc); if (!blk) return; ul_unref_path(blk->parent); free(blk); ul_path_set_dialect(pc, NULL, NULL); } int sysfs_blkdev_set_parent(struct path_cxt *pc, struct path_cxt *parent) { struct sysfs_blkdev *blk = ul_path_get_dialect(pc); if (!pc || !blk) return -EINVAL; if (blk->parent) { ul_unref_path(blk->parent); blk->parent = NULL; } if (parent) { ul_ref_path(parent); blk->parent = parent; } else blk->parent = NULL; DBG(CXT, ul_debugobj(pc, "new parent")); return 0; } struct path_cxt *sysfs_blkdev_get_parent(struct path_cxt *pc) { struct sysfs_blkdev *blk = ul_path_get_dialect(pc); return blk ? blk->parent : NULL; } /* * Redirects ENOENT errors to the parent, if the path is to the queue/ * sysfs directory. For example * * /sys/dev/block/8:1/queue/logical_block_size redirects to * /sys/dev/block/8:0/queue/logical_block_size */ static int sysfs_blkdev_enoent_redirect(struct path_cxt *pc, const char *path, int *dirfd) { struct sysfs_blkdev *blk = ul_path_get_dialect(pc); if (blk && blk->parent && strncmp(path, "queue/", 6) == 0) { *dirfd = ul_path_get_dirfd(blk->parent); if (*dirfd >= 0) { DBG(CXT, ul_debugobj(pc, "%s redirected to parent", path)); return 0; } } return 1; /* no redirect */ } char *sysfs_blkdev_get_name(struct path_cxt *pc, char *buf, size_t bufsiz) { char link[PATH_MAX]; char *name; ssize_t sz; /* read /sys/dev/block/ link */ sz = ul_path_readlink(pc, link, sizeof(link) - 1, NULL); if (sz < 0) return NULL; link[sz] = '\0'; name = strrchr(link, '/'); 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; } int sysfs_blkdev_is_partition_dirent(DIR *dir, struct dirent *d, const char *parent_name) { char path[NAME_MAX + 6 + 1]; #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; } int sysfs_blkdev_count_partitions(struct path_cxt *pc, const char *devname) { DIR *dir; struct dirent *d; int r = 0; dir = ul_path_opendir(pc, NULL); if (!dir) return 0; while ((d = xreaddir(dir))) { if (sysfs_blkdev_is_partition_dirent(dir, d, devname)) r++; } closedir(dir); return r; } /* * Converts @partno (partition number) to devno of the partition. * The @pc handles wholedisk device. * * Note that this code does not expect any special format of the * partitions devnames. */ dev_t sysfs_blkdev_partno_to_devno(struct path_cxt *pc, int partno) { DIR *dir; struct dirent *d; dev_t devno = 0; dir = ul_path_opendir(pc, NULL); if (!dir) return 0; while ((d = xreaddir(dir))) { int n; if (!sysfs_blkdev_is_partition_dirent(dir, d, NULL)) continue; if (ul_path_readf_s32(pc, &n, "%s/partition", d->d_name)) continue; if (n == partno) { if (ul_path_readf_majmin(pc, &devno, "%s/dev", d->d_name) == 0) break; } } closedir(dir); DBG(CXT, ul_debugobj(pc, "partno (%d) -> devno (%d)", (int) partno, (int) devno)); return devno; } /* * Returns slave name if there is only one slave, otherwise returns NULL. * The result should be deallocated by free(). */ char *sysfs_blkdev_get_slave(struct path_cxt *pc) { DIR *dir; struct dirent *d; char *name = NULL; dir = ul_path_opendir(pc, "slaves"); if (!dir) 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; } #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 subsystems * used for the device. */ char *sysfs_blkdev_get_devchain(struct path_cxt *pc, char *buf, size_t bufsz) { /* read /sys/dev/block/: symlink */ ssize_t sz = ul_path_readlink(pc, buf, bufsz, NULL); const char *prefix; size_t psz = 0; if (sz <= 0 || sz + sizeof(_PATH_SYS_DEVBLOCK "/") > bufsz) return NULL; buf[sz++] = '\0'; prefix = ul_path_get_prefix(pc); if (prefix) psz = strlen(prefix); /* create absolute patch from the link */ memmove(buf + psz + sizeof(_PATH_SYS_DEVBLOCK "/") - 1, buf, sz); if (prefix) memcpy(buf, prefix, psz); memcpy(buf + psz, _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_blkdev_next_subsystem(struct path_cxt *pc __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_blkdev_is_hotpluggable(struct path_cxt *pc) { char buf[PATH_MAX], *chain, *sub; int rc = 0; /* check /sys/dev/block/:/removable attribute */ if (ul_path_read_s32(pc, &rc, "removable") == 0 && rc == 1) return 1; chain = sysfs_blkdev_get_devchain(pc, buf, sizeof(buf)); while (chain && sysfs_blkdev_next_subsystem(pc, chain, &sub) == 0) { rc = is_hotpluggable_subsystem(sub); if (rc) { free(sub); break; } free(sub); } return rc; } static int get_dm_wholedisk(struct path_cxt *pc, char *diskname, size_t len, dev_t *diskdevno) { int rc = 0; char *name; /* Note, sysfs_blkdev_get_slave() returns the first slave only, * if there is more slaves, then return NULL */ name = sysfs_blkdev_get_slave(pc); if (!name) return -1; if (diskname && len) xstrncpy(diskname, name, len); if (diskdevno) { *diskdevno = __sysfs_devname_to_devno(ul_path_get_prefix(pc), name, NULL); if (!*diskdevno) rc = -1; } free(name); return rc; } /* * Returns by @diskdevno whole disk device devno and (optionally) by * @diskname the whole disk device name. */ int sysfs_blkdev_get_wholedisk( struct path_cxt *pc, char *diskname, size_t len, dev_t *diskdevno) { int is_part = 0; if (!pc) return -1; is_part = ul_path_access(pc, F_OK, "partition") == 0; if (!is_part) { /* * Extra case for partitions mapped by device-mapper. * * All regular 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 = NULL, *tmp, *prefix; ul_path_read_string(pc, &uuid, "dm/uuid"); tmp = uuid; prefix = uuid ? strsep(&tmp, "-") : NULL; if (prefix && strncasecmp(prefix, "part", 4) == 0) is_part = 1; free(uuid); if (is_part && get_dm_wholedisk(pc, diskname, len, diskdevno) == 0) /* * partitioned device, mapped by DM */ goto done; is_part = 0; } if (!is_part) { /* * unpartitioned device */ if (diskname && !sysfs_blkdev_get_name(pc, diskname, len)) goto err; if (diskdevno) *diskdevno = sysfs_blkdev_get_devno(pc); } 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 = ul_path_readlink(pc, linkpath, sizeof(linkpath) - 1, NULL); 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) xstrncpy(diskname, name, len); if (diskdevno) { *diskdevno = __sysfs_devname_to_devno(ul_path_get_prefix(pc), name, NULL); if (!*diskdevno) goto err; } } done: return 0; err: return -1; } int sysfs_devno_to_wholedisk(dev_t devno, char *diskname, size_t len, dev_t *diskdevno) { struct path_cxt *pc; int rc = 0; if (!devno) return -EINVAL; pc = ul_new_sysfs_path(devno, NULL, NULL); if (!pc) return -ENOMEM; rc = sysfs_blkdev_get_wholedisk(pc, diskname, len, diskdevno); ul_unref_path(pc); return rc; } /* * Returns 1 if the device is private device mapper device. The @uuid * (if not NULL) returns DM device UUID, use free() to deallocate. */ int sysfs_devno_is_dm_private(dev_t devno, char **uuid) { struct path_cxt *pc = NULL; char *id = NULL; int rc = 0; pc = ul_new_sysfs_path(devno, NULL, NULL); if (!pc) goto done; if (ul_path_read_string(pc, &id, "dm/uuid") <= 0 || !id) goto done; /* Private LVM devices use "LVM--" uuid format (important * is the "LVM" prefix and "-" postfix). */ if (strncmp(id, "LVM-", 4) == 0) { char *p = strrchr(id + 4, '-'); if (p && *(p + 1)) rc = 1; /* Private Stratis devices prefix the UUID with "stratis-1-private" */ } else if (strncmp(id, "stratis-1-private", 17) == 0) { rc = 1; } done: ul_unref_path(pc); if (uuid) *uuid = id; else free(id); 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_blkdev_scsi_get_hctl(struct path_cxt *pc, int *h, int *c, int *t, int *l) { char buf[PATH_MAX], *hctl; struct sysfs_blkdev *blk; ssize_t len; blk = ul_path_get_dialect(pc); if (!blk || blk->hctl_error) return -EINVAL; if (blk->has_hctl) goto done; blk->hctl_error = 1; len = ul_path_readlink(pc, buf, sizeof(buf) - 1, "device"); if (len < 0) return len; buf[len] = '\0'; hctl = strrchr(buf, '/'); if (!hctl) return -1; hctl++; if (sscanf(hctl, "%u:%u:%u:%u", &blk->scsi_host, &blk->scsi_channel, &blk->scsi_target, &blk->scsi_lun) != 4) return -1; blk->has_hctl = 1; done: if (h) *h = blk->scsi_host; if (c) *c = blk->scsi_channel; if (t) *t = blk->scsi_target; if (l) *l = blk->scsi_lun; blk->hctl_error = 0; return 0; } static char *scsi_host_attribute_path( struct path_cxt *pc, const char *type, char *buf, size_t bufsz, const char *attr) { int len; int host; const char *prefix; if (sysfs_blkdev_scsi_get_hctl(pc, &host, NULL, NULL, NULL)) return NULL; prefix = ul_path_get_prefix(pc); if (!prefix) prefix = ""; if (attr) len = snprintf(buf, bufsz, "%s%s/%s_host/host%d/%s", prefix, _PATH_SYS_CLASS, type, host, attr); else len = snprintf(buf, bufsz, "%s%s/%s_host/host%d", prefix, _PATH_SYS_CLASS, type, host); return (len < 0 || (size_t) len >= bufsz) ? NULL : buf; } char *sysfs_blkdev_scsi_host_strdup_attribute(struct path_cxt *pc, const char *type, const char *attr) { char buf[1024]; int rc; FILE *f; if (!attr || !type || !scsi_host_attribute_path(pc, 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_blkdev_scsi_host_is(struct path_cxt *pc, const char *type) { char buf[PATH_MAX]; struct stat st; if (!type || !scsi_host_attribute_path(pc, type, buf, sizeof(buf), NULL)) return 0; return stat(buf, &st) == 0 && S_ISDIR(st.st_mode); } static char *scsi_attribute_path(struct path_cxt *pc, char *buf, size_t bufsz, const char *attr) { int len, h, c, t, l; const char *prefix; if (sysfs_blkdev_scsi_get_hctl(pc, &h, &c, &t, &l) != 0) return NULL; prefix = ul_path_get_prefix(pc); if (!prefix) prefix = ""; if (attr) len = snprintf(buf, bufsz, "%s%s/devices/%d:%d:%d:%d/%s", prefix, _PATH_SYS_SCSI, h,c,t,l, attr); else len = snprintf(buf, bufsz, "%s%s/devices/%d:%d:%d:%d", prefix, _PATH_SYS_SCSI, h,c,t,l); return (len < 0 || (size_t) len >= bufsz) ? NULL : buf; } int sysfs_blkdev_scsi_has_attribute(struct path_cxt *pc, const char *attr) { char path[PATH_MAX]; struct stat st; if (!scsi_attribute_path(pc, path, sizeof(path), attr)) return 0; return stat(path, &st) == 0; } int sysfs_blkdev_scsi_path_contains(struct path_cxt *pc, const char *pattern) { char path[PATH_MAX], linkc[PATH_MAX]; struct stat st; ssize_t len; if (!scsi_attribute_path(pc, 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; } static dev_t read_devno(const char *path) { FILE *f; int maj = 0, min = 0; dev_t dev = 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; } int sysfs_devname_is_hidden(const char *prefix, const char *name) { char buf[PATH_MAX]; int rc = 0, hidden = 0, len; FILE *f; if (strncmp("/dev/", name, 5) == 0) return 0; if (!prefix) prefix = ""; /* * Create path to /sys/block//hidden */ len = snprintf(buf, sizeof(buf), "%s" _PATH_SYS_BLOCK "/%s/hidden", prefix, name); if (len < 0 || (size_t) len + 1 > sizeof(buf)) return 0; f = fopen(buf, "r" UL_CLOEXECSTR); if (!f) return 0; rc = fscanf(f, "%d", &hidden); fclose(f); return rc == 1 ? hidden : 0; } dev_t __sysfs_devname_to_devno(const char *prefix, const char *name, const char *parent) { char buf[PATH_MAX]; char *_name = NULL; /* name as encoded in sysfs */ dev_t dev = 0; int len; if (!prefix) prefix = ""; assert(name); if (strncmp("/dev/", name, 5) == 0) { /* * Read from /dev */ struct stat st; if (stat(name, &st) == 0) { dev = st.st_rdev; goto done; } name += 5; /* unaccessible, or not node in /dev */ } _name = strdup(name); if (!_name) goto done; sysfs_devname_dev_to_sys(_name); if (parent && strncmp("dm-", name, 3) != 0) { /* * Create path to /sys/block///dev */ char *_parent = strdup(parent); if (!_parent) goto done; sysfs_devname_dev_to_sys(_parent); len = snprintf(buf, sizeof(buf), "%s" _PATH_SYS_BLOCK "/%s/%s/dev", prefix, _parent, _name); free(_parent); if (len < 0 || (size_t) len >= sizeof(buf)) goto done; /* don't try anything else for dm-* */ dev = read_devno(buf); goto done; } /* * Read from /sys/block//dev */ len = snprintf(buf, sizeof(buf), "%s" _PATH_SYS_BLOCK "/%s/dev", prefix, _name); if (len < 0 || (size_t) len >= sizeof(buf)) goto done; dev = read_devno(buf); if (!dev) { /* * Read from /sys/block//device/dev */ len = snprintf(buf, sizeof(buf), "%s" _PATH_SYS_BLOCK "/%s/device/dev", prefix, _name); if (len < 0 || (size_t) len >= sizeof(buf)) goto done; dev = read_devno(buf); } done: free(_name); return dev; } dev_t sysfs_devname_to_devno(const char *name) { return __sysfs_devname_to_devno(NULL, name, NULL); } char *sysfs_blkdev_get_path(struct path_cxt *pc, char *buf, size_t bufsiz) { const char *name = sysfs_blkdev_get_name(pc, buf, bufsiz); char *res = NULL; size_t sz; struct stat st; if (!name) goto done; sz = strlen(name); if (sz + sizeof("/dev/") > bufsiz) goto done; /* 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 == sysfs_blkdev_get_devno(pc)) res = buf; done: return res; } dev_t sysfs_blkdev_get_devno(struct path_cxt *pc) { return ((struct sysfs_blkdev *) ul_path_get_dialect(pc))->devno; } /* * 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 path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL); char *res = NULL; if (pc) { res = sysfs_blkdev_get_path(pc, buf, bufsiz); ul_unref_path(pc); } return res; } char *sysfs_devno_to_devname(dev_t devno, char *buf, size_t bufsiz) { struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL); char *res = NULL; if (pc) { res = sysfs_blkdev_get_name(pc, buf, bufsiz); ul_unref_path(pc); } return res; } int sysfs_devno_count_partitions(dev_t devno) { struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL); int n = 0; if (pc) { char buf[PATH_MAX + 1]; char *name = sysfs_blkdev_get_name(pc, buf, sizeof(buf)); n = sysfs_blkdev_count_partitions(pc, name); ul_unref_path(pc); } return n; } #ifdef TEST_PROGRAM_SYSFS #include #include #include int main(int argc, char *argv[]) { struct path_cxt *pc; char *devname; dev_t devno, disk_devno; char path[PATH_MAX], *sub, *chain; char diskname[32]; int i, is_part, rc = EXIT_SUCCESS; uint64_t u64; if (argc != 2) errx(EXIT_FAILURE, "usage: %s ", argv[0]); ul_sysfs_init_debug(); devname = argv[1]; devno = sysfs_devname_to_devno(devname); if (!devno) err(EXIT_FAILURE, "failed to read devno"); printf("non-context:\n"); printf(" DEVNO: %u (%d:%d)\n", (unsigned int) devno, major(devno), minor(devno)); printf(" DEVNAME: %s\n", sysfs_devno_to_devname(devno, path, sizeof(path))); printf(" DEVPATH: %s\n", sysfs_devno_to_devpath(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); pc = ul_new_sysfs_path(devno, NULL, NULL); if (!pc) goto done; printf("context based:\n"); devno = sysfs_blkdev_get_devno(pc); printf(" DEVNO: %u (%d:%d)\n", (unsigned int) devno, major(devno), minor(devno)); printf(" DEVNAME: %s\n", sysfs_blkdev_get_name(pc, path, sizeof(path))); printf(" DEVPATH: %s\n", sysfs_blkdev_get_path(pc, 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 = ul_path_access(pc, F_OK, "partition") == 0; printf(" PARTITION: %s\n", is_part ? "YES" : "NOT"); if (is_part && disk_devno) { struct path_cxt *disk_pc = ul_new_sysfs_path(disk_devno, NULL, NULL); sysfs_blkdev_set_parent(pc, disk_pc); ul_unref_path(disk_pc); } printf(" HOTPLUG: %s\n", sysfs_blkdev_is_hotpluggable(pc) ? "yes" : "no"); printf(" SLAVES: %d\n", ul_path_count_dirents(pc, "slaves")); if (!is_part) { printf("First 5 partitions:\n"); for (i = 1; i <= 5; i++) { dev_t dev = sysfs_blkdev_partno_to_devno(pc, i); if (dev) printf("\t#%d %d:%d\n", i, major(dev), minor(dev)); } } if (ul_path_read_u64(pc, &u64, "size") != 0) printf(" (!) read SIZE failed\n"); else printf(" SIZE: %jd\n", u64); if (ul_path_read_s32(pc, &i, "queue/hw_sector_size")) printf(" (!) read SECTOR failed\n"); else printf(" SECTOR: %d\n", i); chain = sysfs_blkdev_get_devchain(pc, path, sizeof(path)); printf(" SUBSUSTEMS:\n"); while (chain && sysfs_blkdev_next_subsystem(pc, chain, &sub) == 0) { printf("\t%s\n", sub); free(sub); } rc = EXIT_SUCCESS; done: ul_unref_path(pc); return rc; } #endif /* TEST_PROGRAM_SYSFS */