blob: 626451bddb3efa62492ef9c29013d545afa7b020 [file] [log] [blame]
/*
* 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 */