]> git.ipfire.org Git - thirdparty/util-linux.git/blame - lib/sysfs.c
lsblk: add WWN, improve udev support
[thirdparty/util-linux.git] / lib / sysfs.c
CommitLineData
7fe16fda
KZ
1/*
2 * Copyright (C) 2011 Karel Zak <kzak@redhat.com>
3 */
4
093b20ba
KZ
5#include <ctype.h>
6
7fe16fda
KZ
7#include "c.h"
8#include "at.h"
9#include "pathnames.h"
10#include "sysfs.h"
11
12char *sysfs_devno_attribute_path(dev_t devno, char *buf,
413993fc 13 size_t bufsiz, const char *attr)
7fe16fda
KZ
14{
15 int len;
16
17 if (attr)
413993fc 18 len = snprintf(buf, bufsiz, _PATH_SYS_DEVBLOCK "/%d:%d/%s",
7fe16fda
KZ
19 major(devno), minor(devno), attr);
20 else
413993fc 21 len = snprintf(buf, bufsiz, _PATH_SYS_DEVBLOCK "/%d:%d",
7fe16fda
KZ
22 major(devno), minor(devno));
23
6b9166ce 24 return (len < 0 || (size_t) len + 1 > bufsiz) ? NULL : buf;
7fe16fda
KZ
25}
26
27int sysfs_devno_has_attribute(dev_t devno, const char *attr)
28{
29 char path[PATH_MAX];
30 struct stat info;
31
32 if (!sysfs_devno_attribute_path(devno, path, sizeof(path), attr))
33 return 0;
34 if (stat(path, &info) == 0)
35 return 1;
36 return 0;
37}
38
413993fc 39char *sysfs_devno_path(dev_t devno, char *buf, size_t bufsiz)
7fe16fda 40{
413993fc 41 return sysfs_devno_attribute_path(devno, buf, bufsiz, NULL);
7fe16fda
KZ
42}
43
44dev_t sysfs_devname_to_devno(const char *name, const char *parent)
45{
46 char buf[PATH_MAX], *path = NULL;
47 dev_t dev = 0;
48
49 if (strncmp("/dev/", name, 5) == 0) {
50 /*
51 * Read from /dev
52 */
53 struct stat st;
54
55 if (stat(name, &st) == 0)
56 dev = st.st_rdev;
57 else
58 name += 5; /* unaccesible, or not node in /dev */
59 }
60
61 if (!dev && parent) {
62 /*
63 * Create path to /sys/block/<parent>/<name>/dev
64 */
65 int len = snprintf(buf, sizeof(buf),
66 _PATH_SYS_BLOCK "/%s/%s/dev", parent, name);
6b9166ce 67 if (len < 0 || (size_t) len + 1 > sizeof(buf))
7fe16fda
KZ
68 return 0;
69 path = buf;
70
71 } else if (!dev) {
72 /*
73 * Create path to /sys/block/<name>/dev
74 */
75 int len = snprintf(buf, sizeof(buf),
76 _PATH_SYS_BLOCK "/%s/dev", name);
6b9166ce 77 if (len < 0 || (size_t) len + 1 > sizeof(buf))
7fe16fda
KZ
78 return 0;
79 path = buf;
80 }
81
82 if (path) {
83 /*
84 * read devno from sysfs
85 */
86 FILE *f;
87 int maj = 0, min = 0;
88
89 f = fopen(path, "r");
90 if (!f)
91 return 0;
92
9d72cbc1 93 if (fscanf(f, "%d:%d", &maj, &min) == 2)
7fe16fda
KZ
94 dev = makedev(maj, min);
95 fclose(f);
96 }
97 return dev;
98}
99
413993fc
KZ
100/*
101 * Returns devname (e.g. "/dev/sda1") for the given devno.
102 *
103 * Note that the @buf has to be large enough to store /sys/dev/block/<maj:min>
104 * symlinks.
105 *
106 * Please, use more robust blkid_devno_to_devname() in your applications.
107 */
108char *sysfs_devno_to_devpath(dev_t devno, char *buf, size_t bufsiz)
109{
110 struct sysfs_cxt cxt;
111 char *name;
112 size_t sz;
113 struct stat st;
114
115 if (sysfs_init(&cxt, devno, NULL))
116 return NULL;
117
118 name = sysfs_get_devname(&cxt, buf, bufsiz);
119 sysfs_deinit(&cxt);
120
121 if (!name)
122 return NULL;
123
124 sz = strlen(name);
125
126 if (sz + sizeof("/dev/") > bufsiz)
127 return NULL;
128
129 /* create the final "/dev/<name>" string */
130 memmove(buf + 5, name, sz + 1);
131 memcpy(buf, "/dev/", 5);
132
133 if (!stat(buf, &st) && S_ISBLK(st.st_mode) && st.st_rdev == devno)
134 return buf;
135
136 return NULL;
137}
138
7fe16fda
KZ
139int sysfs_init(struct sysfs_cxt *cxt, dev_t devno, struct sysfs_cxt *parent)
140{
141 char path[PATH_MAX];
4c5bbc5d 142 int fd, rc;
7fe16fda 143
413993fc 144 memset(cxt, 0, sizeof(*cxt));
3a18db62 145 cxt->dir_fd = -1;
413993fc 146
7fe16fda
KZ
147 if (!sysfs_devno_path(devno, path, sizeof(path)))
148 goto err;
149
150 fd = open(path, O_RDONLY);
151 if (fd < 0)
152 goto err;
06ae069b
KZ
153 cxt->dir_fd = fd;
154
7fe16fda
KZ
155 cxt->dir_path = strdup(path);
156 if (!cxt->dir_path)
157 goto err;
7fe16fda 158 cxt->devno = devno;
7fe16fda
KZ
159 cxt->parent = parent;
160 return 0;
161err:
4c5bbc5d 162 rc = errno > 0 ? -errno : -1;
7fe16fda
KZ
163 sysfs_deinit(cxt);
164 return rc;
165}
166
167void sysfs_deinit(struct sysfs_cxt *cxt)
168{
169 if (!cxt)
170 return;
171
172 if (cxt->dir_fd >= 0)
173 close(cxt->dir_fd);
87e77645
KZ
174 free(cxt->dir_path);
175
7fe16fda
KZ
176 cxt->devno = 0;
177 cxt->dir_fd = -1;
178 cxt->parent = NULL;
87e77645 179 cxt->dir_path = NULL;
7fe16fda
KZ
180}
181
182int sysfs_stat(struct sysfs_cxt *cxt, const char *attr, struct stat *st)
183{
184 int rc = fstat_at(cxt->dir_fd, cxt->dir_path, attr, st, 0);
185
186 if (rc != 0 && errno == ENOENT &&
187 strncmp(attr, "queue/", 6) == 0 && cxt->parent) {
188
189 /* Exception for "queue/<attr>". These attributes are available
190 * for parental devices only
191 */
192 return fstat_at(cxt->parent->dir_fd,
193 cxt->parent->dir_path, attr, st, 0);
194 }
195 return rc;
196}
197
d8a84552
KZ
198int sysfs_has_attribute(struct sysfs_cxt *cxt, const char *attr)
199{
200 struct stat st;
201
202 return sysfs_stat(cxt, attr, &st) == 0;
203}
204
7fe16fda
KZ
205static int sysfs_open(struct sysfs_cxt *cxt, const char *attr)
206{
207 int fd = open_at(cxt->dir_fd, cxt->dir_path, attr, O_RDONLY);
208
209 if (fd == -1 && errno == ENOENT &&
210 strncmp(attr, "queue/", 6) == 0 && cxt->parent) {
211
212 /* Exception for "queue/<attr>". These attributes are available
213 * for parental devices only
214 */
215 fd = open_at(cxt->parent->dir_fd, cxt->dir_path, attr, O_RDONLY);
216 }
217 return fd;
218}
219
413993fc
KZ
220ssize_t sysfs_readlink(struct sysfs_cxt *cxt, const char *attr,
221 char *buf, size_t bufsiz)
222{
cffee0de
CW
223 if (!cxt->dir_path)
224 return -1;
225
413993fc
KZ
226 if (attr)
227 return readlink_at(cxt->dir_fd, cxt->dir_path, attr, buf, bufsiz);
228
229 /* read /sys/dev/block/<maj:min> link */
230 return readlink(cxt->dir_path, buf, bufsiz);
231}
232
7fe16fda
KZ
233DIR *sysfs_opendir(struct sysfs_cxt *cxt, const char *attr)
234{
235 DIR *dir;
236 int fd;
237
238 if (attr)
239 fd = sysfs_open(cxt, attr);
413993fc 240 else
7fe16fda
KZ
241 /* request to open root of device in sysfs (/sys/block/<dev>)
242 * -- we cannot use cxt->sysfs_fd directly, because closedir()
243 * will close this our persistent file descriptor.
244 */
245 fd = dup(cxt->dir_fd);
7fe16fda
KZ
246
247 if (fd < 0)
248 return NULL;
249
250 dir = fdopendir(fd);
251 if (!dir) {
252 close(fd);
253 return NULL;
254 }
255 if (!attr)
256 rewinddir(dir);
257 return dir;
258}
259
260
261static FILE *sysfs_fopen(struct sysfs_cxt *cxt, const char *attr)
262{
263 int fd = sysfs_open(cxt, attr);
264
265 return fd < 0 ? NULL : fdopen(fd, "r");
266}
267
268
269static struct dirent *xreaddir(DIR *dp)
270{
271 struct dirent *d;
272
273 while ((d = readdir(dp))) {
274 if (!strcmp(d->d_name, ".") ||
275 !strcmp(d->d_name, ".."))
276 continue;
277
278 /* blacklist here? */
279 break;
280 }
281 return d;
282}
283
284int sysfs_is_partition_dirent(DIR *dir, struct dirent *d, const char *parent_name)
285{
286 char path[256];
287
288#ifdef _DIRENT_HAVE_D_TYPE
09a71aa1
PR
289 if (d->d_type != DT_DIR &&
290 d->d_type != DT_LNK)
7fe16fda
KZ
291 return 0;
292#endif
093b20ba
KZ
293 if (parent_name) {
294 const char *p = parent_name;
295 size_t len;
296
297 /* /dev/sda --> "sda" */
298 if (*parent_name == '/') {
299 p = strrchr(parent_name, '/');
300 if (!p)
301 return 0;
302 p++;
303 }
304
305 len = strlen(p);
306 if (strlen(d->d_name) <= len)
307 return 0;
308
a3b78053
KZ
309 /* partitions subdir name is
310 * "<parent>[:digit:]" or "<parent>p[:digit:]"
311 */
312 return strncmp(p, d->d_name, len) == 0 &&
313 ((*(d->d_name + len) == 'p' && isdigit(*(d->d_name + len + 1)))
314 || isdigit(*(d->d_name + len)));
093b20ba 315 }
7fe16fda
KZ
316
317 /* Cannot use /partition file, not supported on old sysfs */
318 snprintf(path, sizeof(path), "%s/start", d->d_name);
319
320 return faccessat(dirfd(dir), path, R_OK, 0) == 0;
321}
322
778ad369
KZ
323/*
324 * Copnverts @partno (partition number) to devno of the partition. The @cxt
325 * handles wholedisk device.
326 *
327 * Note that this code does not expect any special format of the
328 * partitions devnames.
329 */
330dev_t sysfs_partno_to_devno(struct sysfs_cxt *cxt, int partno)
331{
332 DIR *dir;
333 struct dirent *d;
334 char path[256];
335 dev_t devno = 0;
336
337 dir = sysfs_opendir(cxt, NULL);
338 if (!dir)
339 return 0;
340
341 while ((d = xreaddir(dir))) {
342 int n, maj, min;
343
344 if (!sysfs_is_partition_dirent(dir, d, NULL))
345 continue;
346
347 snprintf(path, sizeof(path), "%s/partition", d->d_name);
348 if (sysfs_read_int(cxt, path, &n) || n != partno)
349 continue;
350
351 snprintf(path, sizeof(path), "%s/dev", d->d_name);
352 if (sysfs_scanf(cxt, path, "%d:%d", &maj, &min) == 2)
353 devno = makedev(maj, min);
354 break;
355 }
356
357 closedir(dir);
358 return devno;
359}
360
361
7fe16fda
KZ
362int sysfs_scanf(struct sysfs_cxt *cxt, const char *attr, const char *fmt, ...)
363{
364 FILE *f = sysfs_fopen(cxt, attr);
365 va_list ap;
366 int rc;
367
368 if (!f)
369 return -EINVAL;
370 va_start(ap, fmt);
371 rc = vfscanf(f, fmt, ap);
372 va_end(ap);
373
374 fclose(f);
375 return rc;
376}
377
413993fc 378
90e9fcda 379int sysfs_read_s64(struct sysfs_cxt *cxt, const char *attr, int64_t *res)
7fe16fda 380{
90e9fcda
KZ
381 int64_t x = 0;
382
383 if (sysfs_scanf(cxt, attr, "%"SCNd64, &x) == 1) {
384 if (res)
385 *res = x;
386 return 0;
387 }
388 return -1;
7fe16fda
KZ
389}
390
90e9fcda 391int sysfs_read_u64(struct sysfs_cxt *cxt, const char *attr, uint64_t *res)
7fe16fda 392{
90e9fcda
KZ
393 uint64_t x = 0;
394
395 if (sysfs_scanf(cxt, attr, "%"SCNu64, &x) == 1) {
396 if (res)
397 *res = x;
398 return 0;
399 }
400 return -1;
7fe16fda
KZ
401}
402
90e9fcda 403int sysfs_read_int(struct sysfs_cxt *cxt, const char *attr, int *res)
7fe16fda 404{
90e9fcda
KZ
405 int x = 0;
406
407 if (sysfs_scanf(cxt, attr, "%d", &x) == 1) {
408 if (res)
409 *res = x;
410 return 0;
411 }
412 return -1;
7fe16fda
KZ
413}
414
415char *sysfs_strdup(struct sysfs_cxt *cxt, const char *attr)
416{
417 char buf[1024];
657d9adb 418 return sysfs_scanf(cxt, attr, "%1023[^\n]", buf) == 1 ?
7fe16fda
KZ
419 strdup(buf) : NULL;
420}
421
422int sysfs_count_dirents(struct sysfs_cxt *cxt, const char *attr)
423{
424 DIR *dir;
425 int r = 0;
426
427 if (!(dir = sysfs_opendir(cxt, attr)))
428 return 0;
429
430 while (xreaddir(dir)) r++;
431
432 closedir(dir);
433 return r;
434}
435
436int sysfs_count_partitions(struct sysfs_cxt *cxt, const char *devname)
437{
438 DIR *dir;
439 struct dirent *d;
440 int r = 0;
441
442 if (!(dir = sysfs_opendir(cxt, NULL)))
443 return 0;
444
445 while ((d = xreaddir(dir))) {
446 if (sysfs_is_partition_dirent(dir, d, devname))
447 r++;
448 }
449
450 closedir(dir);
451 return r;
452}
453
413993fc
KZ
454/*
455 * Returns slave name if there is only one slave, otherwise returns NULL.
456 * The result should be deallocated by free().
457 */
458char *sysfs_get_slave(struct sysfs_cxt *cxt)
459{
460 DIR *dir;
461 struct dirent *d;
462 char *name = NULL;
463
464 if (!(dir = sysfs_opendir(cxt, "slaves")))
465 return NULL;
466
467 while ((d = xreaddir(dir))) {
468 if (name)
469 goto err; /* more slaves */
470
471 name = strdup(d->d_name);
472 }
473
474 closedir(dir);
475 return name;
476err:
477 free(name);
5415374d 478 closedir(dir);
413993fc
KZ
479 return NULL;
480}
481
482/*
483 * Note that the @buf has to be large enough to store /sys/dev/block/<maj:min>
484 * symlinks.
485 */
486char *sysfs_get_devname(struct sysfs_cxt *cxt, char *buf, size_t bufsiz)
487{
488 char *name = NULL;
489 ssize_t sz;
490
491 sz = sysfs_readlink(cxt, NULL, buf, bufsiz - 1);
492 if (sz < 0)
493 return NULL;
494
495 buf[sz] = '\0';
496 name = strrchr(buf, '/');
497 if (!name)
498 return NULL;
499
500 name++;
501 sz = strlen(name);
502
503 memmove(buf, name, sz + 1);
504 return buf;
505}
7fe16fda 506
3b66f48e
ML
507/* returns basename and keeps dirname in the @path */
508static char *stripoff_last_component(char *path)
509{
510 char *p = strrchr(path, '/');
511
512 if (!p)
513 return NULL;
514 *p = '\0';
515 return ++p;
516}
517
518static int get_dm_wholedisk(struct sysfs_cxt *cxt, char *diskname,
519 size_t len, dev_t *diskdevno)
520{
521 int rc = 0;
522 char *name;
523
524 /* Note, sysfs_get_slave() returns the first slave only,
525 * if there is more slaves, then return NULL
526 */
527 name = sysfs_get_slave(cxt);
528 if (!name)
529 return -1;
530
531 if (diskname && len) {
532 strncpy(diskname, name, len);
533 diskname[len - 1] = '\0';
534 }
535
536 if (diskdevno) {
537 *diskdevno = sysfs_devname_to_devno(name, NULL);
538 if (!*diskdevno)
539 rc = -1;
540 }
541
542 free(name);
543 return rc;
544}
545
546int sysfs_devno_to_wholedisk(dev_t dev, char *diskname,
547 size_t len, dev_t *diskdevno)
548{
549 struct sysfs_cxt cxt;
550 int is_part = 0;
551
552 if (!dev || sysfs_init(&cxt, dev, NULL) != 0)
553 return -1;
554
555 is_part = sysfs_has_attribute(&cxt, "partition");
556 if (!is_part) {
557 /*
558 * Extra case for partitions mapped by device-mapper.
559 *
560 * All regualar partitions (added by BLKPG ioctl or kernel PT
561 * parser) have the /sys/.../partition file. The partitions
562 * mapped by DM don't have such file, but they have "part"
563 * prefix in DM UUID.
564 */
565 char *uuid = sysfs_strdup(&cxt, "dm/uuid");
566 char *tmp = uuid;
567 char *prefix = uuid ? strsep(&tmp, "-") : NULL;
568
569 if (prefix && strncasecmp(prefix, "part", 4) == 0)
570 is_part = 1;
571 free(uuid);
572
573 if (is_part &&
574 get_dm_wholedisk(&cxt, diskname, len, diskdevno) == 0)
575 /*
576 * partitioned device, mapped by DM
577 */
578 goto done;
579
580 is_part = 0;
581 }
582
583 if (!is_part) {
584 /*
585 * unpartitioned device
586 */
587 if (diskname && len) {
588 if (!sysfs_get_devname(&cxt, diskname, len))
589 goto err;
590 }
591 if (diskdevno)
592 *diskdevno = dev;
593
594 } else {
595 /*
596 * partitioned device
597 * - readlink /sys/dev/block/8:1 = ../../block/sda/sda1
598 * - dirname ../../block/sda/sda1 = ../../block/sda
599 * - basename ../../block/sda = sda
600 */
601 char linkpath[PATH_MAX];
602 char *name;
603 int linklen;
604
605 linklen = sysfs_readlink(&cxt, NULL,
606 linkpath, sizeof(linkpath) - 1);
607 if (linklen < 0)
608 goto err;
609 linkpath[linklen] = '\0';
610
611 stripoff_last_component(linkpath); /* dirname */
612 name = stripoff_last_component(linkpath); /* basename */
613 if (!name)
614 goto err;
615
616 if (diskname && len) {
617 strncpy(diskname, name, len);
618 diskname[len - 1] = '\0';
619 }
620
621 if (diskdevno) {
622 *diskdevno = sysfs_devname_to_devno(name, NULL);
623 if (!*diskdevno)
624 goto err;
625 }
626 }
627
628done:
629 sysfs_deinit(&cxt);
630 return 0;
631err:
632 sysfs_deinit(&cxt);
633 return -1;
634}
635
e918cca5 636#ifdef TEST_PROGRAM_SYSFS
7fe16fda
KZ
637#include <errno.h>
638#include <err.h>
639#include <stdlib.h>
640
641int main(int argc, char *argv[])
642{
4c5bbc5d 643 struct sysfs_cxt cxt = UL_SYSFSCXT_EMPTY;
7fe16fda
KZ
644 char *devname;
645 dev_t devno;
646 char path[PATH_MAX];
778ad369 647 int i, is_part;
90e9fcda 648 uint64_t u64;
413993fc 649 ssize_t len;
7fe16fda
KZ
650
651 if (argc != 2)
652 errx(EXIT_FAILURE, "usage: %s <devname>", argv[0]);
653
654 devname = argv[1];
655 devno = sysfs_devname_to_devno(devname, NULL);
656
657 if (!devno)
658 err(EXIT_FAILURE, "failed to read devno");
659
778ad369
KZ
660 is_part = sysfs_devno_has_attribute(devno, "partition");
661
7fe16fda 662 printf("NAME: %s\n", devname);
778ad369 663 printf("DEVNO: %u (%d:%d)\n", (unsigned int) devno, major(devno), minor(devno));
7fe16fda 664 printf("DEVNOPATH: %s\n", sysfs_devno_path(devno, path, sizeof(path)));
413993fc 665 printf("DEVPATH: %s\n", sysfs_devno_to_devpath(devno, path, sizeof(path)));
778ad369 666 printf("PARTITION: %s\n", is_part ? "YES" : "NOT");
7fe16fda 667
1aae31c0
KZ
668 if (sysfs_init(&cxt, devno, NULL))
669 return EXIT_FAILURE;
7fe16fda 670
413993fc
KZ
671 len = sysfs_readlink(&cxt, NULL, path, sizeof(path) - 1);
672 if (len > 0) {
673 path[len] = '\0';
674 printf("DEVNOLINK: %s\n", path);
675 }
676
778ad369
KZ
677 if (!is_part) {
678 printf("First 5 partitions:\n");
679 for (i = 1; i <= 5; i++) {
680 dev_t dev = sysfs_partno_to_devno(&cxt, i);
681 if (dev)
682 printf("\t#%d %d:%d\n", i, major(dev), minor(dev));
683 }
684 }
685
7fe16fda 686 printf("SLAVES: %d\n", sysfs_count_dirents(&cxt, "slaves"));
90e9fcda
KZ
687
688 if (sysfs_read_u64(&cxt, "size", &u64))
413993fc 689 printf("read SIZE failed\n");
90e9fcda
KZ
690 else
691 printf("SIZE: %jd\n", u64);
692
693 if (sysfs_read_int(&cxt, "queue/hw_sector_size", &i))
413993fc 694 printf("read SECTOR failed\n");
90e9fcda
KZ
695 else
696 printf("SECTOR: %d\n", i);
7fe16fda 697
413993fc
KZ
698 printf("DEVNAME: %s\n", sysfs_get_devname(&cxt, path, sizeof(path)));
699
fb4a9e54 700 sysfs_deinit(&cxt);
7fe16fda
KZ
701 return EXIT_SUCCESS;
702}
703#endif