| /* |
| * No copyright is claimed. This code is in the public domain; do with |
| * it what you wish. |
| * |
| * Written by Karel Zak <kzak@redhat.com> |
| */ |
| #include <ctype.h> |
| #include <libgen.h> |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include "c.h" |
| #include "pathnames.h" |
| #include "sysfs.h" |
| #include "fileutils.h" |
| #include "all-io.h" |
| #include "debug.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_ref_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/<maj:min> 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; |
| } |
| |
| 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_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 |
| * "<parent>[:digit:]" or "<parent>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 <chain>/subsystem symlink exists, if yes then returns |
| * basename of the readlink result, and remove the last subdirectory from the |
| * <chain> 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/<maj>:<min> 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/<maj>:<min>/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) { |
| strncpy(diskname, name, len); |
| diskname[len - 1] = '\0'; |
| } |
| |
| 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) { |
| strncpy(diskname, name, len); |
| diskname[len - 1] = '\0'; |
| } |
| |
| 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>-<name>" uuid format (important |
| * is the "LVM" prefix and "-<name>" 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; |
| } |
| |
| 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; /* unaccesible, or not node in /dev */ |
| } |
| |
| _name = strdup(name); |
| if (!_name) |
| goto done; |
| sysfs_devname_dev_to_sys(_name); |
| |
| if (parent && strncmp("dm-", name, 3)) { |
| /* |
| * Create path to /sys/block/<parent>/<name>/dev |
| */ |
| char *_parent = strdup(parent); |
| |
| if (!_parent) { |
| free(_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/<sysname>/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/<sysname>/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/<name>" 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; |
| } |
| |
| |
| |
| #ifdef TEST_PROGRAM_SYSFS |
| #include <errno.h> |
| #include <err.h> |
| #include <stdlib.h> |
| |
| 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 <devname>", 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 */ |