/* * Copyright (C) 2011 Karel Zak */ #include #include "c.h" #include "at.h" #include "pathnames.h" #include "sysfs.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) { /* * Create path to /sys/block///dev */ int len = snprintf(buf, sizeof(buf), _PATH_SYS_BLOCK "/%s/%s/dev", parent, name); if (len < 0 || (size_t) len + 1 > sizeof(buf)) return 0; path = buf; } else if (!dev) { /* * Create path to /sys/block//dev */ int len = snprintf(buf, sizeof(buf), _PATH_SYS_BLOCK "/%s/dev", 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"); 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. * * Note that the @buf has to be large enough to store /sys/dev/block/ * symlinks. * * 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); 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); cxt->devno = 0; cxt->dir_fd = -1; cxt->parent = NULL; cxt->dir_path = NULL; } 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 fd = open_at(cxt->dir_fd, cxt->dir_path, attr, O_RDONLY); 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, O_RDONLY); } 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; if (attr) fd = sysfs_open(cxt, attr); else /* 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(cxt->dir_fd); 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); return fd < 0 ? NULL : fdopen(fd, "r"); } 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) 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:]" */ return strncmp(p, d->d_name, len) == 0 && 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_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; } char *sysfs_strdup(struct sysfs_cxt *cxt, const char *attr) { char buf[1024]; 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; } /* * Note that the @buf has to be large enough to store /sys/dev/block/ * symlinks. */ char *sysfs_get_devname(struct sysfs_cxt *cxt, char *buf, size_t bufsiz) { char *name = NULL; ssize_t sz; sz = sysfs_readlink(cxt, NULL, buf, bufsiz - 1); if (sz < 0) return NULL; buf[sz] = '\0'; name = strrchr(buf, '/'); if (!name) return NULL; name++; sz = strlen(name); memmove(buf, name, sz + 1); return buf; } /* returns basename and keeps dirname in the @path */ static char *stripoff_last_component(char *path) { char *p = strrchr(path, '/'); if (!p) return NULL; *p = '\0'; return ++p; } 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; } 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; int 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; 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; } #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; char path[PATH_MAX]; int i; 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"); printf("NAME: %s\n", devname); printf("DEVNO: %u\n", (unsigned int) devno); printf("DEVNOPATH: %s\n", sysfs_devno_path(devno, path, sizeof(path))); printf("DEVPATH: %s\n", sysfs_devno_to_devpath(devno, path, sizeof(path))); printf("PARTITION: %s\n", sysfs_devno_has_attribute(devno, "partition") ? "YES" : "NOT"); if (sysfs_init(&cxt, devno, NULL)) return EXIT_FAILURE; len = sysfs_readlink(&cxt, NULL, path, sizeof(path) - 1); if (len > 0) { path[len] = '\0'; printf("DEVNOLINK: %s\n", path); } printf("SLAVES: %d\n", sysfs_count_dirents(&cxt, "slaves")); 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); printf("DEVNAME: %s\n", sysfs_get_devname(&cxt, path, sizeof(path))); sysfs_deinit(&cxt); return EXIT_SUCCESS; } #endif