From 7fe16fdaf40283cd8ff6fd05958f9f9aa3811409 Mon Sep 17 00:00:00 2001 From: Karel Zak Date: Mon, 16 May 2011 14:08:50 +0200 Subject: lib: add generic sysfs utils Signed-off-by: Karel Zak --- lib/sysfs.c | 348 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 lib/sysfs.c (limited to 'lib/sysfs.c') diff --git a/lib/sysfs.c b/lib/sysfs.c new file mode 100644 index 000000000..aba12864e --- /dev/null +++ b/lib/sysfs.c @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2011 Karel Zak + */ + +#include "c.h" +#include "at.h" +#include "pathnames.h" +#include "sysfs.h" + +char *sysfs_devno_attribute_path(dev_t devno, char *buf, + size_t buflen, const char *attr) +{ + int len; + + if (attr) + len = snprintf(buf, buflen, _PATH_SYS_DEVBLOCK "/%d:%d/%s", + major(devno), minor(devno), attr); + else + len = snprintf(buf, buflen, _PATH_SYS_DEVBLOCK "/%d:%d", + major(devno), minor(devno)); + + return (len < 0 || len + 1 > buflen) ? 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 buflen) +{ + return sysfs_devno_attribute_path(devno, buf, buflen, 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 || 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 || 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, "%u:%u", &maj, &min) == 2) + dev = makedev(maj, min); + fclose(f); + } + return dev; +} + +int sysfs_init(struct sysfs_cxt *cxt, dev_t devno, struct sysfs_cxt *parent) +{ + char path[PATH_MAX]; + int fd, rc = 0; + + if (!sysfs_devno_path(devno, path, sizeof(path))) + goto err; + + fd = open(path, O_RDONLY); + if (fd < 0) + goto err; +#ifndef HAVE_FSTATAT + cxt->dir_path = strdup(path); + if (!cxt->dir_path) + goto err; +#endif + cxt->devno = devno; + cxt->dir_fd = fd; + cxt->parent = parent; + return 0; +err: + rc = -errno; + sysfs_deinit(cxt); + return rc; +} + +void sysfs_deinit(struct sysfs_cxt *cxt) +{ + if (!cxt) + return; + + if (cxt->dir_fd >= 0) + close(cxt->dir_fd); + cxt->devno = 0; + cxt->dir_fd = -1; + cxt->parent = NULL; +#ifndef HAVE_FSTATAT + free(cxt->dir_path); +#endif +} + +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; +} + +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; +} + +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) + return 0; +#endif + if (strncmp(parent_name, d->d_name, strlen(parent_name))) + return 0; + + /* 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; +} + +int64_t sysfs_read_s64(struct sysfs_cxt *cxt, const char *attr) +{ + uint64_t x; + return sysfs_scanf(cxt, attr, "%"SCNd64, &x) == 1 ? x : 0; +} + +uint64_t sysfs_read_u64(struct sysfs_cxt *cxt, const char *attr) +{ + uint64_t x; + return sysfs_scanf(cxt, attr, "%"SCNu64, &x) == 1 ? x : 0; +} + +int sysfs_read_int(struct sysfs_cxt *cxt, const char *attr) +{ + int x; + return sysfs_scanf(cxt, attr, "%d", &x) == 1 ? x : 0; +} + +char *sysfs_strdup(struct sysfs_cxt *cxt, const char *attr) +{ + char buf[1024]; + return sysfs_scanf(cxt, attr, "%1024[^\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; +} + + +#ifdef TEST_PROGRAM +#include +#include +#include + +int main(int argc, char *argv[]) +{ + struct sysfs_cxt cxt; + char *devname; + dev_t devno; + char path[PATH_MAX]; + + 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("PARTITION: %s\n", + sysfs_devno_has_attribute(devno, "partition") ? "YES" : "NOT"); + + sysfs_init(&cxt, devno, NULL); + + printf("SLAVES: %d\n", sysfs_count_dirents(&cxt, "slaves")); + printf("SIZE: %jd\n", sysfs_read_u64(&cxt, "size")); + printf("SECTOR: %d\n", sysfs_read_int(&cxt, "queue/hw_sector_size")); + + return EXIT_SUCCESS; +} +#endif -- cgit v1.2.3-55-g7522