/*
* 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 "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 && strncmp("dm-", name, 3)) {
/*
* Create path to /sys/block/<parent>/<name>/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/<name>/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/<maj:min>
* 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/<name>" 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);
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/<attr>". 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/<attr>". 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/<maj:min> 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);
else if (cxt->dir_fd >= 0)
/* request to open root of device in sysfs (/sys/block/<dev>)
* -- 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
* "<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;
}
/*
* 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;
}
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/<maj:min>
* 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;
}
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, '/') + 1;
if (!hctl)
return -1;
if (sscanf(hctl, "%d:%d:%d:%d", &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")))
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 <errno.h>
#include <err.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
struct sysfs_cxt cxt = UL_SYSFSCXT_EMPTY;
char *devname;
dev_t devno;
char path[PATH_MAX];
int i, is_part;
uint64_t u64;
ssize_t len;
if (argc != 2)
errx(EXIT_FAILURE, "usage: %s <devname>", argv[0]);
devname = argv[1];
devno = sysfs_devname_to_devno(devname, NULL);
if (!devno)
err(EXIT_FAILURE, "failed to read devno");
is_part = sysfs_devno_has_attribute(devno, "partition");
printf("NAME: %s\n", devname);
printf("DEVNO: %u (%d:%d)\n", (unsigned int) devno, major(devno), minor(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", is_part ? "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);
}
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));
}
}
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