]>
Commit | Line | Data |
---|---|---|
7fe16fda | 1 | /* |
0f23ee0c KZ |
2 | * No copyright is claimed. This code is in the public domain; do with |
3 | * it what you wish. | |
4 | * | |
5 | * Written by Karel Zak <kzak@redhat.com> | |
7fe16fda | 6 | */ |
093b20ba | 7 | #include <ctype.h> |
948d1f31 | 8 | #include <libgen.h> |
2208b3cc RM |
9 | #include <fcntl.h> |
10 | #include <sys/stat.h> | |
11 | #include <unistd.h> | |
093b20ba | 12 | |
7fe16fda | 13 | #include "c.h" |
7fe16fda KZ |
14 | #include "pathnames.h" |
15 | #include "sysfs.h" | |
d4eaabc8 | 16 | #include "fileutils.h" |
6c987ca9 | 17 | #include "all-io.h" |
7fe16fda KZ |
18 | |
19 | char *sysfs_devno_attribute_path(dev_t devno, char *buf, | |
413993fc | 20 | size_t bufsiz, const char *attr) |
7fe16fda KZ |
21 | { |
22 | int len; | |
23 | ||
24 | if (attr) | |
413993fc | 25 | len = snprintf(buf, bufsiz, _PATH_SYS_DEVBLOCK "/%d:%d/%s", |
7fe16fda KZ |
26 | major(devno), minor(devno), attr); |
27 | else | |
413993fc | 28 | len = snprintf(buf, bufsiz, _PATH_SYS_DEVBLOCK "/%d:%d", |
7fe16fda KZ |
29 | major(devno), minor(devno)); |
30 | ||
06fa5817 | 31 | return (len < 0 || (size_t) len >= bufsiz) ? NULL : buf; |
7fe16fda KZ |
32 | } |
33 | ||
34 | int sysfs_devno_has_attribute(dev_t devno, const char *attr) | |
35 | { | |
36 | char path[PATH_MAX]; | |
37 | struct stat info; | |
38 | ||
39 | if (!sysfs_devno_attribute_path(devno, path, sizeof(path), attr)) | |
40 | return 0; | |
41 | if (stat(path, &info) == 0) | |
42 | return 1; | |
43 | return 0; | |
44 | } | |
45 | ||
413993fc | 46 | char *sysfs_devno_path(dev_t devno, char *buf, size_t bufsiz) |
7fe16fda | 47 | { |
413993fc | 48 | return sysfs_devno_attribute_path(devno, buf, bufsiz, NULL); |
7fe16fda KZ |
49 | } |
50 | ||
51 | dev_t sysfs_devname_to_devno(const char *name, const char *parent) | |
52 | { | |
53 | char buf[PATH_MAX], *path = NULL; | |
54 | dev_t dev = 0; | |
55 | ||
56 | if (strncmp("/dev/", name, 5) == 0) { | |
57 | /* | |
58 | * Read from /dev | |
59 | */ | |
60 | struct stat st; | |
61 | ||
62 | if (stat(name, &st) == 0) | |
63 | dev = st.st_rdev; | |
64 | else | |
65 | name += 5; /* unaccesible, or not node in /dev */ | |
66 | } | |
67 | ||
857db7f5 | 68 | if (!dev && parent && strncmp("dm-", name, 3)) { |
7fe16fda KZ |
69 | /* |
70 | * Create path to /sys/block/<parent>/<name>/dev | |
71 | */ | |
6c62abc4 KZ |
72 | char *_name = strdup(name), *_parent = strdup(parent); |
73 | int len; | |
74 | ||
92441d5c KZ |
75 | if (!_name || !_parent) { |
76 | free(_name); | |
77 | free(_parent); | |
6c62abc4 | 78 | return 0; |
92441d5c | 79 | } |
6c62abc4 KZ |
80 | sysfs_devname_dev_to_sys(_name); |
81 | sysfs_devname_dev_to_sys(_parent); | |
82 | ||
83 | len = snprintf(buf, sizeof(buf), | |
84 | _PATH_SYS_BLOCK "/%s/%s/dev", _parent, _name); | |
85 | free(_name); | |
86 | free(_parent); | |
06fa5817 | 87 | if (len < 0 || (size_t) len >= sizeof(buf)) |
7fe16fda KZ |
88 | return 0; |
89 | path = buf; | |
90 | ||
91 | } else if (!dev) { | |
92 | /* | |
759b120d | 93 | * Create path to /sys/block/<sysname>/dev |
7fe16fda | 94 | */ |
6c62abc4 KZ |
95 | char *_name = strdup(name); |
96 | int len; | |
759b120d | 97 | |
6c62abc4 KZ |
98 | if (!_name) |
99 | return 0; | |
100 | ||
101 | sysfs_devname_dev_to_sys(_name); | |
102 | len = snprintf(buf, sizeof(buf), | |
103 | _PATH_SYS_BLOCK "/%s/dev", _name); | |
104 | free(_name); | |
06fa5817 | 105 | if (len < 0 || (size_t) len >= sizeof(buf)) |
7fe16fda KZ |
106 | return 0; |
107 | path = buf; | |
108 | } | |
109 | ||
110 | if (path) { | |
111 | /* | |
112 | * read devno from sysfs | |
113 | */ | |
114 | FILE *f; | |
115 | int maj = 0, min = 0; | |
116 | ||
b1fa3e22 | 117 | f = fopen(path, "r" UL_CLOEXECSTR); |
7fe16fda KZ |
118 | if (!f) |
119 | return 0; | |
120 | ||
9d72cbc1 | 121 | if (fscanf(f, "%d:%d", &maj, &min) == 2) |
7fe16fda KZ |
122 | dev = makedev(maj, min); |
123 | fclose(f); | |
124 | } | |
125 | return dev; | |
126 | } | |
127 | ||
413993fc KZ |
128 | /* |
129 | * Returns devname (e.g. "/dev/sda1") for the given devno. | |
130 | * | |
413993fc KZ |
131 | * Please, use more robust blkid_devno_to_devname() in your applications. |
132 | */ | |
133 | char *sysfs_devno_to_devpath(dev_t devno, char *buf, size_t bufsiz) | |
134 | { | |
135 | struct sysfs_cxt cxt; | |
136 | char *name; | |
137 | size_t sz; | |
138 | struct stat st; | |
139 | ||
140 | if (sysfs_init(&cxt, devno, NULL)) | |
141 | return NULL; | |
142 | ||
143 | name = sysfs_get_devname(&cxt, buf, bufsiz); | |
144 | sysfs_deinit(&cxt); | |
145 | ||
146 | if (!name) | |
147 | return NULL; | |
148 | ||
149 | sz = strlen(name); | |
150 | ||
151 | if (sz + sizeof("/dev/") > bufsiz) | |
152 | return NULL; | |
153 | ||
154 | /* create the final "/dev/<name>" string */ | |
155 | memmove(buf + 5, name, sz + 1); | |
156 | memcpy(buf, "/dev/", 5); | |
157 | ||
158 | if (!stat(buf, &st) && S_ISBLK(st.st_mode) && st.st_rdev == devno) | |
159 | return buf; | |
160 | ||
161 | return NULL; | |
162 | } | |
163 | ||
7fe16fda KZ |
164 | int sysfs_init(struct sysfs_cxt *cxt, dev_t devno, struct sysfs_cxt *parent) |
165 | { | |
166 | char path[PATH_MAX]; | |
4c5bbc5d | 167 | int fd, rc; |
7fe16fda | 168 | |
413993fc | 169 | memset(cxt, 0, sizeof(*cxt)); |
3a18db62 | 170 | cxt->dir_fd = -1; |
413993fc | 171 | |
7fe16fda KZ |
172 | if (!sysfs_devno_path(devno, path, sizeof(path))) |
173 | goto err; | |
174 | ||
b1fa3e22 | 175 | fd = open(path, O_RDONLY|O_CLOEXEC); |
7fe16fda KZ |
176 | if (fd < 0) |
177 | goto err; | |
06ae069b KZ |
178 | cxt->dir_fd = fd; |
179 | ||
7fe16fda KZ |
180 | cxt->dir_path = strdup(path); |
181 | if (!cxt->dir_path) | |
182 | goto err; | |
7fe16fda | 183 | cxt->devno = devno; |
7fe16fda KZ |
184 | cxt->parent = parent; |
185 | return 0; | |
186 | err: | |
4c5bbc5d | 187 | rc = errno > 0 ? -errno : -1; |
7fe16fda KZ |
188 | sysfs_deinit(cxt); |
189 | return rc; | |
190 | } | |
191 | ||
192 | void sysfs_deinit(struct sysfs_cxt *cxt) | |
193 | { | |
194 | if (!cxt) | |
195 | return; | |
196 | ||
197 | if (cxt->dir_fd >= 0) | |
198 | close(cxt->dir_fd); | |
87e77645 KZ |
199 | free(cxt->dir_path); |
200 | ||
d0f7e5b4 KZ |
201 | memset(cxt, 0, sizeof(*cxt)); |
202 | ||
7fe16fda | 203 | cxt->dir_fd = -1; |
7fe16fda KZ |
204 | } |
205 | ||
206 | int sysfs_stat(struct sysfs_cxt *cxt, const char *attr, struct stat *st) | |
207 | { | |
2208b3cc | 208 | int rc = fstatat(cxt->dir_fd, attr, st, 0); |
7fe16fda KZ |
209 | |
210 | if (rc != 0 && errno == ENOENT && | |
211 | strncmp(attr, "queue/", 6) == 0 && cxt->parent) { | |
212 | ||
213 | /* Exception for "queue/<attr>". These attributes are available | |
214 | * for parental devices only | |
215 | */ | |
2208b3cc | 216 | return fstatat(cxt->parent->dir_fd, attr, st, 0); |
7fe16fda KZ |
217 | } |
218 | return rc; | |
219 | } | |
220 | ||
d8a84552 KZ |
221 | int sysfs_has_attribute(struct sysfs_cxt *cxt, const char *attr) |
222 | { | |
223 | struct stat st; | |
224 | ||
225 | return sysfs_stat(cxt, attr, &st) == 0; | |
226 | } | |
227 | ||
6c987ca9 | 228 | static int sysfs_open(struct sysfs_cxt *cxt, const char *attr, int flags) |
7fe16fda | 229 | { |
2208b3cc | 230 | int fd = openat(cxt->dir_fd, attr, flags); |
7fe16fda KZ |
231 | |
232 | if (fd == -1 && errno == ENOENT && | |
233 | strncmp(attr, "queue/", 6) == 0 && cxt->parent) { | |
234 | ||
235 | /* Exception for "queue/<attr>". These attributes are available | |
236 | * for parental devices only | |
237 | */ | |
2208b3cc | 238 | fd = openat(cxt->parent->dir_fd, attr, flags); |
7fe16fda KZ |
239 | } |
240 | return fd; | |
241 | } | |
242 | ||
413993fc KZ |
243 | ssize_t sysfs_readlink(struct sysfs_cxt *cxt, const char *attr, |
244 | char *buf, size_t bufsiz) | |
245 | { | |
cffee0de CW |
246 | if (!cxt->dir_path) |
247 | return -1; | |
248 | ||
413993fc | 249 | if (attr) |
2208b3cc | 250 | return readlinkat(cxt->dir_fd, attr, buf, bufsiz); |
413993fc KZ |
251 | |
252 | /* read /sys/dev/block/<maj:min> link */ | |
253 | return readlink(cxt->dir_path, buf, bufsiz); | |
254 | } | |
255 | ||
7fe16fda KZ |
256 | DIR *sysfs_opendir(struct sysfs_cxt *cxt, const char *attr) |
257 | { | |
258 | DIR *dir; | |
fee9431f | 259 | int fd = -1; |
7fe16fda KZ |
260 | |
261 | if (attr) | |
6c987ca9 | 262 | fd = sysfs_open(cxt, attr, O_RDONLY|O_CLOEXEC); |
fee9431f KZ |
263 | |
264 | else if (cxt->dir_fd >= 0) | |
7fe16fda KZ |
265 | /* request to open root of device in sysfs (/sys/block/<dev>) |
266 | * -- we cannot use cxt->sysfs_fd directly, because closedir() | |
267 | * will close this our persistent file descriptor. | |
268 | */ | |
fef6f84a | 269 | fd = dup_fd_cloexec(cxt->dir_fd, STDERR_FILENO + 1); |
7fe16fda KZ |
270 | |
271 | if (fd < 0) | |
272 | return NULL; | |
273 | ||
274 | dir = fdopendir(fd); | |
275 | if (!dir) { | |
276 | close(fd); | |
277 | return NULL; | |
278 | } | |
279 | if (!attr) | |
280 | rewinddir(dir); | |
281 | return dir; | |
282 | } | |
283 | ||
284 | ||
285 | static FILE *sysfs_fopen(struct sysfs_cxt *cxt, const char *attr) | |
286 | { | |
6c987ca9 | 287 | int fd = sysfs_open(cxt, attr, O_RDONLY|O_CLOEXEC); |
7fe16fda | 288 | |
b1fa3e22 | 289 | return fd < 0 ? NULL : fdopen(fd, "r" UL_CLOEXECSTR); |
7fe16fda KZ |
290 | } |
291 | ||
292 | ||
293 | static struct dirent *xreaddir(DIR *dp) | |
294 | { | |
295 | struct dirent *d; | |
296 | ||
297 | while ((d = readdir(dp))) { | |
298 | if (!strcmp(d->d_name, ".") || | |
299 | !strcmp(d->d_name, "..")) | |
300 | continue; | |
301 | ||
302 | /* blacklist here? */ | |
303 | break; | |
304 | } | |
305 | return d; | |
306 | } | |
307 | ||
308 | int sysfs_is_partition_dirent(DIR *dir, struct dirent *d, const char *parent_name) | |
309 | { | |
310 | char path[256]; | |
311 | ||
312 | #ifdef _DIRENT_HAVE_D_TYPE | |
09a71aa1 | 313 | if (d->d_type != DT_DIR && |
ea7f012b KZ |
314 | d->d_type != DT_LNK && |
315 | d->d_type != DT_UNKNOWN) | |
7fe16fda KZ |
316 | return 0; |
317 | #endif | |
093b20ba KZ |
318 | if (parent_name) { |
319 | const char *p = parent_name; | |
320 | size_t len; | |
321 | ||
322 | /* /dev/sda --> "sda" */ | |
323 | if (*parent_name == '/') { | |
324 | p = strrchr(parent_name, '/'); | |
325 | if (!p) | |
326 | return 0; | |
327 | p++; | |
328 | } | |
329 | ||
330 | len = strlen(p); | |
331 | if (strlen(d->d_name) <= len) | |
332 | return 0; | |
333 | ||
a3b78053 KZ |
334 | /* partitions subdir name is |
335 | * "<parent>[:digit:]" or "<parent>p[:digit:]" | |
336 | */ | |
337 | return strncmp(p, d->d_name, len) == 0 && | |
338 | ((*(d->d_name + len) == 'p' && isdigit(*(d->d_name + len + 1))) | |
339 | || isdigit(*(d->d_name + len))); | |
093b20ba | 340 | } |
7fe16fda KZ |
341 | |
342 | /* Cannot use /partition file, not supported on old sysfs */ | |
343 | snprintf(path, sizeof(path), "%s/start", d->d_name); | |
344 | ||
345 | return faccessat(dirfd(dir), path, R_OK, 0) == 0; | |
346 | } | |
347 | ||
778ad369 | 348 | /* |
f31322a2 BV |
349 | * Converts @partno (partition number) to devno of the partition. |
350 | * The @cxt handles wholedisk device. | |
778ad369 KZ |
351 | * |
352 | * Note that this code does not expect any special format of the | |
353 | * partitions devnames. | |
354 | */ | |
355 | dev_t sysfs_partno_to_devno(struct sysfs_cxt *cxt, int partno) | |
356 | { | |
357 | DIR *dir; | |
358 | struct dirent *d; | |
359 | char path[256]; | |
360 | dev_t devno = 0; | |
361 | ||
362 | dir = sysfs_opendir(cxt, NULL); | |
363 | if (!dir) | |
364 | return 0; | |
365 | ||
366 | while ((d = xreaddir(dir))) { | |
367 | int n, maj, min; | |
368 | ||
369 | if (!sysfs_is_partition_dirent(dir, d, NULL)) | |
370 | continue; | |
371 | ||
372 | snprintf(path, sizeof(path), "%s/partition", d->d_name); | |
f31322a2 | 373 | if (sysfs_read_int(cxt, path, &n)) |
778ad369 KZ |
374 | continue; |
375 | ||
f31322a2 BV |
376 | if (n == partno) { |
377 | snprintf(path, sizeof(path), "%s/dev", d->d_name); | |
378 | if (sysfs_scanf(cxt, path, "%d:%d", &maj, &min) == 2) | |
379 | devno = makedev(maj, min); | |
380 | break; | |
381 | } | |
778ad369 KZ |
382 | } |
383 | ||
384 | closedir(dir); | |
385 | return devno; | |
386 | } | |
387 | ||
388 | ||
7fe16fda KZ |
389 | int sysfs_scanf(struct sysfs_cxt *cxt, const char *attr, const char *fmt, ...) |
390 | { | |
391 | FILE *f = sysfs_fopen(cxt, attr); | |
392 | va_list ap; | |
393 | int rc; | |
394 | ||
395 | if (!f) | |
396 | return -EINVAL; | |
397 | va_start(ap, fmt); | |
398 | rc = vfscanf(f, fmt, ap); | |
399 | va_end(ap); | |
400 | ||
401 | fclose(f); | |
402 | return rc; | |
403 | } | |
404 | ||
413993fc | 405 | |
90e9fcda | 406 | int sysfs_read_s64(struct sysfs_cxt *cxt, const char *attr, int64_t *res) |
7fe16fda | 407 | { |
90e9fcda KZ |
408 | int64_t x = 0; |
409 | ||
410 | if (sysfs_scanf(cxt, attr, "%"SCNd64, &x) == 1) { | |
411 | if (res) | |
412 | *res = x; | |
413 | return 0; | |
414 | } | |
415 | return -1; | |
7fe16fda KZ |
416 | } |
417 | ||
90e9fcda | 418 | int sysfs_read_u64(struct sysfs_cxt *cxt, const char *attr, uint64_t *res) |
7fe16fda | 419 | { |
90e9fcda KZ |
420 | uint64_t x = 0; |
421 | ||
422 | if (sysfs_scanf(cxt, attr, "%"SCNu64, &x) == 1) { | |
423 | if (res) | |
424 | *res = x; | |
425 | return 0; | |
426 | } | |
427 | return -1; | |
7fe16fda KZ |
428 | } |
429 | ||
90e9fcda | 430 | int sysfs_read_int(struct sysfs_cxt *cxt, const char *attr, int *res) |
7fe16fda | 431 | { |
90e9fcda KZ |
432 | int x = 0; |
433 | ||
434 | if (sysfs_scanf(cxt, attr, "%d", &x) == 1) { | |
435 | if (res) | |
436 | *res = x; | |
437 | return 0; | |
438 | } | |
439 | return -1; | |
7fe16fda KZ |
440 | } |
441 | ||
6c987ca9 KZ |
442 | int sysfs_write_string(struct sysfs_cxt *cxt, const char *attr, const char *str) |
443 | { | |
444 | int fd = sysfs_open(cxt, attr, O_WRONLY|O_CLOEXEC); | |
445 | int rc, errsv; | |
446 | ||
447 | if (fd < 0) | |
448 | return -errno; | |
449 | rc = write_all(fd, str, strlen(str)); | |
450 | ||
451 | errsv = errno; | |
452 | close(fd); | |
453 | errno = errsv; | |
454 | return rc; | |
455 | } | |
456 | ||
457 | int sysfs_write_u64(struct sysfs_cxt *cxt, const char *attr, uint64_t num) | |
458 | { | |
459 | char buf[sizeof(stringify_value(ULLONG_MAX))]; | |
460 | int fd, rc = 0, len, errsv; | |
461 | ||
462 | fd = sysfs_open(cxt, attr, O_WRONLY|O_CLOEXEC); | |
463 | if (fd < 0) | |
464 | return -errno; | |
465 | ||
7231fb2a | 466 | len = snprintf(buf, sizeof(buf), "%" PRIu64, num); |
06fa5817 | 467 | if (len < 0 || (size_t) len >= sizeof(buf)) |
ff27b20f | 468 | rc = len < 0 ? -errno : -E2BIG; |
6c987ca9 KZ |
469 | else |
470 | rc = write_all(fd, buf, len); | |
471 | ||
472 | errsv = errno; | |
473 | close(fd); | |
474 | errno = errsv; | |
475 | return rc; | |
476 | } | |
477 | ||
7fe16fda KZ |
478 | char *sysfs_strdup(struct sysfs_cxt *cxt, const char *attr) |
479 | { | |
548b9714 | 480 | char buf[BUFSIZ]; |
657d9adb | 481 | return sysfs_scanf(cxt, attr, "%1023[^\n]", buf) == 1 ? |
7fe16fda KZ |
482 | strdup(buf) : NULL; |
483 | } | |
484 | ||
548b9714 | 485 | |
7fe16fda KZ |
486 | int sysfs_count_dirents(struct sysfs_cxt *cxt, const char *attr) |
487 | { | |
488 | DIR *dir; | |
489 | int r = 0; | |
490 | ||
491 | if (!(dir = sysfs_opendir(cxt, attr))) | |
492 | return 0; | |
493 | ||
494 | while (xreaddir(dir)) r++; | |
495 | ||
496 | closedir(dir); | |
497 | return r; | |
498 | } | |
499 | ||
500 | int sysfs_count_partitions(struct sysfs_cxt *cxt, const char *devname) | |
501 | { | |
502 | DIR *dir; | |
503 | struct dirent *d; | |
504 | int r = 0; | |
505 | ||
506 | if (!(dir = sysfs_opendir(cxt, NULL))) | |
507 | return 0; | |
508 | ||
509 | while ((d = xreaddir(dir))) { | |
510 | if (sysfs_is_partition_dirent(dir, d, devname)) | |
511 | r++; | |
512 | } | |
513 | ||
514 | closedir(dir); | |
515 | return r; | |
516 | } | |
517 | ||
413993fc KZ |
518 | /* |
519 | * Returns slave name if there is only one slave, otherwise returns NULL. | |
520 | * The result should be deallocated by free(). | |
521 | */ | |
522 | char *sysfs_get_slave(struct sysfs_cxt *cxt) | |
523 | { | |
524 | DIR *dir; | |
525 | struct dirent *d; | |
526 | char *name = NULL; | |
527 | ||
528 | if (!(dir = sysfs_opendir(cxt, "slaves"))) | |
529 | return NULL; | |
530 | ||
531 | while ((d = xreaddir(dir))) { | |
532 | if (name) | |
533 | goto err; /* more slaves */ | |
534 | ||
535 | name = strdup(d->d_name); | |
536 | } | |
537 | ||
538 | closedir(dir); | |
539 | return name; | |
540 | err: | |
541 | free(name); | |
5415374d | 542 | closedir(dir); |
413993fc KZ |
543 | return NULL; |
544 | } | |
545 | ||
413993fc KZ |
546 | char *sysfs_get_devname(struct sysfs_cxt *cxt, char *buf, size_t bufsiz) |
547 | { | |
4419ffb9 KZ |
548 | char linkpath[PATH_MAX]; |
549 | char *name; | |
550 | ssize_t sz; | |
413993fc | 551 | |
4419ffb9 | 552 | sz = sysfs_readlink(cxt, NULL, linkpath, sizeof(linkpath) - 1); |
413993fc KZ |
553 | if (sz < 0) |
554 | return NULL; | |
4419ffb9 | 555 | linkpath[sz] = '\0'; |
413993fc | 556 | |
4419ffb9 | 557 | name = strrchr(linkpath, '/'); |
413993fc KZ |
558 | if (!name) |
559 | return NULL; | |
560 | ||
561 | name++; | |
562 | sz = strlen(name); | |
563 | ||
4419ffb9 KZ |
564 | if ((size_t) sz + 1 > bufsiz) |
565 | return NULL; | |
566 | ||
567 | memcpy(buf, name, sz + 1); | |
6c62abc4 KZ |
568 | sysfs_devname_sys_to_dev(buf); |
569 | ||
413993fc KZ |
570 | return buf; |
571 | } | |
7fe16fda | 572 | |
e017ef8b KZ |
573 | #define SUBSYSTEM_LINKNAME "/subsystem" |
574 | ||
575 | /* | |
576 | * For example: | |
577 | * | |
578 | * chain: /sys/dev/block/../../devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/ \ | |
579 | * 1-1.2:1.0/host65/target65:0:0/65:0:0:0/block/sdb | |
580 | * | |
581 | * The function check if <chain>/subsystem symlink exists, if yes then returns | |
582 | * basename of the readlink result, and remove the last subdirectory from the | |
583 | * <chain> path. | |
584 | */ | |
585 | static char *get_subsystem(char *chain, char *buf, size_t bufsz) | |
586 | { | |
587 | size_t len; | |
588 | char *p; | |
589 | ||
590 | if (!chain || !*chain) | |
591 | return NULL; | |
592 | ||
593 | len = strlen(chain); | |
594 | if (len + sizeof(SUBSYSTEM_LINKNAME) > PATH_MAX) | |
595 | return NULL; | |
596 | ||
597 | do { | |
598 | ssize_t sz; | |
599 | ||
600 | /* append "/subsystem" to the path */ | |
601 | memcpy(chain + len, SUBSYSTEM_LINKNAME, sizeof(SUBSYSTEM_LINKNAME)); | |
602 | ||
603 | /* try if subsystem symlink exists */ | |
604 | sz = readlink(chain, buf, bufsz - 1); | |
605 | ||
606 | /* remove last subsystem from chain */ | |
607 | chain[len] = '\0'; | |
608 | p = strrchr(chain, '/'); | |
609 | if (p) { | |
610 | *p = '\0'; | |
611 | len = p - chain; | |
612 | } | |
613 | ||
614 | if (sz > 0) { | |
615 | /* we found symlink to subsystem, return basename */ | |
616 | buf[sz] = '\0'; | |
617 | return basename(buf); | |
618 | } | |
619 | ||
620 | } while (p); | |
621 | ||
622 | return NULL; | |
623 | } | |
624 | ||
625 | /* | |
a7349ee3 | 626 | * Returns complete path to the device, the patch contains all subsystems |
e017ef8b KZ |
627 | * used for the device. |
628 | */ | |
629 | char *sysfs_get_devchain(struct sysfs_cxt *cxt, char *buf, size_t bufsz) | |
630 | { | |
631 | /* read /sys/dev/block/<maj>:<min> symlink */ | |
e8b150e9 | 632 | ssize_t sz = sysfs_readlink(cxt, NULL, buf, bufsz); |
e017ef8b KZ |
633 | if (sz <= 0 || sz + sizeof(_PATH_SYS_DEVBLOCK "/") > bufsz) |
634 | return NULL; | |
635 | ||
636 | buf[sz++] = '\0'; | |
637 | ||
638 | /* create absolute patch from the link */ | |
639 | memmove(buf + sizeof(_PATH_SYS_DEVBLOCK "/") - 1, buf, sz); | |
640 | memcpy(buf, _PATH_SYS_DEVBLOCK "/", sizeof(_PATH_SYS_DEVBLOCK "/") - 1); | |
641 | ||
642 | return buf; | |
643 | } | |
644 | ||
645 | /* | |
646 | * The @subsys returns the next subsystem in the chain. Function modifies | |
647 | * @devchain string. | |
648 | * | |
649 | * Returns: 0 in success, <0 on error, 1 on end of chain | |
650 | */ | |
651 | int sysfs_next_subsystem(struct sysfs_cxt *cxt __attribute__((unused)), | |
652 | char *devchain, char **subsys) | |
653 | { | |
654 | char subbuf[PATH_MAX]; | |
655 | char *sub; | |
656 | ||
657 | if (!subsys || !devchain) | |
658 | return -EINVAL; | |
659 | ||
32c9ce4b KZ |
660 | *subsys = NULL; |
661 | ||
e017ef8b KZ |
662 | while ((sub = get_subsystem(devchain, subbuf, sizeof(subbuf)))) { |
663 | *subsys = strdup(sub); | |
664 | if (!*subsys) | |
665 | return -ENOMEM; | |
666 | return 0; | |
667 | } | |
668 | ||
669 | return 1; | |
670 | } | |
671 | ||
672 | ||
673 | static int is_hotpluggable_subsystem(const char *name) | |
674 | { | |
675 | static const char * const hotplug_subsystems[] = { | |
676 | "usb", | |
677 | "ieee1394", | |
678 | "pcmcia", | |
679 | "mmc", | |
680 | "ccw" | |
681 | }; | |
682 | size_t i; | |
683 | ||
684 | for (i = 0; i < ARRAY_SIZE(hotplug_subsystems); i++) | |
685 | if (strcmp(name, hotplug_subsystems[i]) == 0) | |
686 | return 1; | |
687 | ||
688 | return 0; | |
689 | } | |
690 | ||
691 | int sysfs_is_hotpluggable(struct sysfs_cxt *cxt) | |
692 | { | |
693 | char buf[PATH_MAX], *chain, *sub; | |
694 | int rc = 0; | |
695 | ||
696 | ||
697 | /* check /sys/dev/block/<maj>:<min>/removable attribute */ | |
698 | if (sysfs_read_int(cxt, "removable", &rc) == 0 && rc == 1) | |
699 | return 1; | |
700 | ||
701 | chain = sysfs_get_devchain(cxt, buf, sizeof(buf)); | |
702 | ||
703 | while (chain && sysfs_next_subsystem(cxt, chain, &sub) == 0) { | |
704 | rc = is_hotpluggable_subsystem(sub); | |
705 | if (rc) { | |
706 | free(sub); | |
707 | break; | |
708 | } | |
709 | free(sub); | |
710 | } | |
711 | ||
712 | return rc; | |
713 | } | |
714 | ||
3b66f48e ML |
715 | static int get_dm_wholedisk(struct sysfs_cxt *cxt, char *diskname, |
716 | size_t len, dev_t *diskdevno) | |
717 | { | |
718 | int rc = 0; | |
719 | char *name; | |
720 | ||
721 | /* Note, sysfs_get_slave() returns the first slave only, | |
722 | * if there is more slaves, then return NULL | |
723 | */ | |
724 | name = sysfs_get_slave(cxt); | |
725 | if (!name) | |
726 | return -1; | |
727 | ||
728 | if (diskname && len) { | |
729 | strncpy(diskname, name, len); | |
730 | diskname[len - 1] = '\0'; | |
731 | } | |
732 | ||
733 | if (diskdevno) { | |
734 | *diskdevno = sysfs_devname_to_devno(name, NULL); | |
735 | if (!*diskdevno) | |
736 | rc = -1; | |
737 | } | |
738 | ||
739 | free(name); | |
740 | return rc; | |
741 | } | |
742 | ||
b55e5886 | 743 | /* |
9e930041 | 744 | * Returns by @diskdevno whole disk device devno and (optionally) by |
b55e5886 KZ |
745 | * @diskname the whole disk device name. |
746 | */ | |
3b66f48e ML |
747 | int sysfs_devno_to_wholedisk(dev_t dev, char *diskname, |
748 | size_t len, dev_t *diskdevno) | |
749 | { | |
750 | struct sysfs_cxt cxt; | |
751 | int is_part = 0; | |
752 | ||
753 | if (!dev || sysfs_init(&cxt, dev, NULL) != 0) | |
754 | return -1; | |
755 | ||
756 | is_part = sysfs_has_attribute(&cxt, "partition"); | |
757 | if (!is_part) { | |
758 | /* | |
759 | * Extra case for partitions mapped by device-mapper. | |
760 | * | |
9e930041 | 761 | * All regular partitions (added by BLKPG ioctl or kernel PT |
3b66f48e ML |
762 | * parser) have the /sys/.../partition file. The partitions |
763 | * mapped by DM don't have such file, but they have "part" | |
764 | * prefix in DM UUID. | |
765 | */ | |
766 | char *uuid = sysfs_strdup(&cxt, "dm/uuid"); | |
767 | char *tmp = uuid; | |
768 | char *prefix = uuid ? strsep(&tmp, "-") : NULL; | |
769 | ||
770 | if (prefix && strncasecmp(prefix, "part", 4) == 0) | |
771 | is_part = 1; | |
772 | free(uuid); | |
773 | ||
774 | if (is_part && | |
775 | get_dm_wholedisk(&cxt, diskname, len, diskdevno) == 0) | |
776 | /* | |
777 | * partitioned device, mapped by DM | |
778 | */ | |
779 | goto done; | |
780 | ||
781 | is_part = 0; | |
782 | } | |
783 | ||
784 | if (!is_part) { | |
785 | /* | |
786 | * unpartitioned device | |
787 | */ | |
74ce680a SK |
788 | if (diskname && len && !sysfs_get_devname(&cxt, diskname, len)) |
789 | goto err; | |
3b66f48e ML |
790 | if (diskdevno) |
791 | *diskdevno = dev; | |
792 | ||
793 | } else { | |
794 | /* | |
795 | * partitioned device | |
796 | * - readlink /sys/dev/block/8:1 = ../../block/sda/sda1 | |
797 | * - dirname ../../block/sda/sda1 = ../../block/sda | |
798 | * - basename ../../block/sda = sda | |
799 | */ | |
800 | char linkpath[PATH_MAX]; | |
801 | char *name; | |
e8b150e9 | 802 | ssize_t linklen; |
3b66f48e | 803 | |
e8b150e9 | 804 | linklen = sysfs_readlink(&cxt, NULL, linkpath, sizeof(linkpath) - 1); |
3b66f48e ML |
805 | if (linklen < 0) |
806 | goto err; | |
807 | linkpath[linklen] = '\0'; | |
808 | ||
809 | stripoff_last_component(linkpath); /* dirname */ | |
810 | name = stripoff_last_component(linkpath); /* basename */ | |
811 | if (!name) | |
812 | goto err; | |
813 | ||
6c62abc4 | 814 | sysfs_devname_sys_to_dev(name); |
3b66f48e ML |
815 | if (diskname && len) { |
816 | strncpy(diskname, name, len); | |
817 | diskname[len - 1] = '\0'; | |
818 | } | |
819 | ||
820 | if (diskdevno) { | |
821 | *diskdevno = sysfs_devname_to_devno(name, NULL); | |
822 | if (!*diskdevno) | |
823 | goto err; | |
824 | } | |
825 | } | |
826 | ||
827 | done: | |
828 | sysfs_deinit(&cxt); | |
829 | return 0; | |
830 | err: | |
831 | sysfs_deinit(&cxt); | |
832 | return -1; | |
833 | } | |
834 | ||
39866431 KZ |
835 | /* |
836 | * Returns 1 if the device is private LVM device. | |
837 | */ | |
838 | int sysfs_devno_is_lvm_private(dev_t devno) | |
839 | { | |
840 | struct sysfs_cxt cxt = UL_SYSFSCXT_EMPTY; | |
841 | char *uuid = NULL; | |
842 | int rc = 0; | |
843 | ||
844 | if (sysfs_init(&cxt, devno, NULL) != 0) | |
845 | return 0; | |
846 | ||
847 | uuid = sysfs_strdup(&cxt, "dm/uuid"); | |
848 | ||
849 | /* Private LVM devices use "LVM-<uuid>-<name>" uuid format (important | |
850 | * is the "LVM" prefix and "-<name>" postfix). | |
851 | */ | |
852 | if (uuid && strncmp(uuid, "LVM-", 4) == 0) { | |
853 | char *p = strrchr(uuid + 4, '-'); | |
854 | ||
855 | if (p && *(p + 1)) | |
856 | rc = 1; | |
857 | } | |
858 | ||
859 | sysfs_deinit(&cxt); | |
860 | free(uuid); | |
861 | return rc; | |
862 | } | |
d0f7e5b4 | 863 | |
b55e5886 KZ |
864 | /* |
865 | * Return 0 or 1, or < 0 in case of error | |
866 | */ | |
867 | int sysfs_devno_is_wholedisk(dev_t devno) | |
868 | { | |
869 | dev_t disk; | |
b55e5886 KZ |
870 | |
871 | if (sysfs_devno_to_wholedisk(devno, NULL, 0, &disk) != 0) | |
872 | return -1; | |
873 | ||
874 | return devno == disk; | |
875 | } | |
876 | ||
e017ef8b | 877 | |
d0f7e5b4 KZ |
878 | int sysfs_scsi_get_hctl(struct sysfs_cxt *cxt, int *h, int *c, int *t, int *l) |
879 | { | |
880 | char buf[PATH_MAX], *hctl; | |
881 | ssize_t len; | |
882 | ||
638402ed | 883 | if (!cxt || cxt->hctl_error) |
d0f7e5b4 KZ |
884 | return -EINVAL; |
885 | if (cxt->has_hctl) | |
886 | goto done; | |
887 | ||
638402ed | 888 | cxt->hctl_error = 1; |
9f51089e | 889 | len = sysfs_readlink(cxt, "device", buf, sizeof(buf) - 1); |
d0f7e5b4 KZ |
890 | if (len < 0) |
891 | return len; | |
892 | ||
893 | buf[len] = '\0'; | |
88a6006b | 894 | hctl = strrchr(buf, '/'); |
d0f7e5b4 KZ |
895 | if (!hctl) |
896 | return -1; | |
88a6006b | 897 | hctl++; |
d0f7e5b4 | 898 | |
a6d85ee0 | 899 | if (sscanf(hctl, "%u:%u:%u:%u", &cxt->scsi_host, &cxt->scsi_channel, |
d0f7e5b4 KZ |
900 | &cxt->scsi_target, &cxt->scsi_lun) != 4) |
901 | return -1; | |
902 | ||
903 | cxt->has_hctl = 1; | |
904 | done: | |
905 | if (h) | |
906 | *h = cxt->scsi_host; | |
907 | if (c) | |
908 | *c = cxt->scsi_channel; | |
909 | if (t) | |
910 | *t = cxt->scsi_target; | |
911 | if (l) | |
912 | *l = cxt->scsi_lun; | |
638402ed KZ |
913 | |
914 | cxt->hctl_error = 0; | |
d0f7e5b4 KZ |
915 | return 0; |
916 | } | |
917 | ||
23a11c74 KZ |
918 | |
919 | static char *sysfs_scsi_host_attribute_path(struct sysfs_cxt *cxt, | |
920 | const char *type, char *buf, size_t bufsz, const char *attr) | |
921 | { | |
922 | int len; | |
923 | int host; | |
924 | ||
925 | if (sysfs_scsi_get_hctl(cxt, &host, NULL, NULL, NULL)) | |
926 | return NULL; | |
927 | ||
928 | if (attr) | |
a5dc4d2d | 929 | len = snprintf(buf, bufsz, _PATH_SYS_CLASS "/%s_host/host%d/%s", |
23a11c74 KZ |
930 | type, host, attr); |
931 | else | |
a5dc4d2d | 932 | len = snprintf(buf, bufsz, _PATH_SYS_CLASS "/%s_host/host%d", |
23a11c74 KZ |
933 | type, host); |
934 | ||
06fa5817 | 935 | return (len < 0 || (size_t) len >= bufsz) ? NULL : buf; |
23a11c74 KZ |
936 | } |
937 | ||
938 | char *sysfs_scsi_host_strdup_attribute(struct sysfs_cxt *cxt, | |
939 | const char *type, const char *attr) | |
940 | { | |
941 | char buf[1024]; | |
942 | int rc; | |
943 | FILE *f; | |
944 | ||
945 | if (!attr || !type || | |
946 | !sysfs_scsi_host_attribute_path(cxt, type, buf, sizeof(buf), attr)) | |
947 | return NULL; | |
948 | ||
b1fa3e22 | 949 | if (!(f = fopen(buf, "r" UL_CLOEXECSTR))) |
23a11c74 KZ |
950 | return NULL; |
951 | ||
952 | rc = fscanf(f, "%1023[^\n]", buf); | |
953 | fclose(f); | |
954 | ||
955 | return rc == 1 ? strdup(buf) : NULL; | |
956 | } | |
957 | ||
958 | int sysfs_scsi_host_is(struct sysfs_cxt *cxt, const char *type) | |
959 | { | |
960 | char buf[PATH_MAX]; | |
961 | struct stat st; | |
962 | ||
963 | if (!type || !sysfs_scsi_host_attribute_path(cxt, type, | |
964 | buf, sizeof(buf), NULL)) | |
965 | return 0; | |
966 | ||
967 | return stat(buf, &st) == 0 && S_ISDIR(st.st_mode); | |
968 | } | |
969 | ||
a5dc4d2d KZ |
970 | static char *sysfs_scsi_attribute_path(struct sysfs_cxt *cxt, |
971 | char *buf, size_t bufsz, const char *attr) | |
972 | { | |
973 | int len, h, c, t, l; | |
974 | ||
975 | if (sysfs_scsi_get_hctl(cxt, &h, &c, &t, &l) != 0) | |
976 | return NULL; | |
977 | ||
978 | if (attr) | |
979 | len = snprintf(buf, bufsz, _PATH_SYS_SCSI "/devices/%d:%d:%d:%d/%s", | |
980 | h,c,t,l, attr); | |
981 | else | |
982 | len = snprintf(buf, bufsz, _PATH_SYS_SCSI "/devices/%d:%d:%d:%d", | |
983 | h,c,t,l); | |
06fa5817 | 984 | return (len < 0 || (size_t) len >= bufsz) ? NULL : buf; |
a5dc4d2d KZ |
985 | } |
986 | ||
987 | int sysfs_scsi_has_attribute(struct sysfs_cxt *cxt, const char *attr) | |
988 | { | |
989 | char path[PATH_MAX]; | |
990 | struct stat st; | |
991 | ||
992 | if (!sysfs_scsi_attribute_path(cxt, path, sizeof(path), attr)) | |
993 | return 0; | |
994 | ||
995 | return stat(path, &st) == 0; | |
996 | } | |
997 | ||
998 | int sysfs_scsi_path_contains(struct sysfs_cxt *cxt, const char *pattern) | |
999 | { | |
1000 | char path[PATH_MAX], linkc[PATH_MAX]; | |
1001 | struct stat st; | |
1002 | ssize_t len; | |
1003 | ||
1004 | if (!sysfs_scsi_attribute_path(cxt, path, sizeof(path), NULL)) | |
1005 | return 0; | |
1006 | ||
1007 | if (stat(path, &st) != 0) | |
1008 | return 0; | |
1009 | ||
1010 | len = readlink(path, linkc, sizeof(linkc) - 1); | |
1011 | if (len < 0) | |
1012 | return 0; | |
1013 | ||
1014 | linkc[len] = '\0'; | |
1015 | return strstr(linkc, pattern) != NULL; | |
1016 | } | |
1017 | ||
e918cca5 | 1018 | #ifdef TEST_PROGRAM_SYSFS |
7fe16fda KZ |
1019 | #include <errno.h> |
1020 | #include <err.h> | |
1021 | #include <stdlib.h> | |
1022 | ||
1023 | int main(int argc, char *argv[]) | |
1024 | { | |
4c5bbc5d | 1025 | struct sysfs_cxt cxt = UL_SYSFSCXT_EMPTY; |
7fe16fda | 1026 | char *devname; |
4419ffb9 | 1027 | dev_t devno, disk_devno; |
e017ef8b | 1028 | char path[PATH_MAX], *sub, *chain; |
4419ffb9 | 1029 | char diskname[32]; |
778ad369 | 1030 | int i, is_part; |
90e9fcda | 1031 | uint64_t u64; |
413993fc | 1032 | ssize_t len; |
7fe16fda KZ |
1033 | |
1034 | if (argc != 2) | |
1035 | errx(EXIT_FAILURE, "usage: %s <devname>", argv[0]); | |
1036 | ||
1037 | devname = argv[1]; | |
1038 | devno = sysfs_devname_to_devno(devname, NULL); | |
1039 | ||
1040 | if (!devno) | |
1041 | err(EXIT_FAILURE, "failed to read devno"); | |
1042 | ||
4419ffb9 KZ |
1043 | if (sysfs_init(&cxt, devno, NULL)) |
1044 | return EXIT_FAILURE; | |
778ad369 | 1045 | |
7fe16fda | 1046 | printf("NAME: %s\n", devname); |
4419ffb9 | 1047 | printf("DEVNAME: %s\n", sysfs_get_devname(&cxt, path, sizeof(path))); |
413993fc | 1048 | printf("DEVPATH: %s\n", sysfs_devno_to_devpath(devno, path, sizeof(path))); |
4419ffb9 KZ |
1049 | printf("DEVNO: %u (%d:%d)\n", (unsigned int) devno, major(devno), minor(devno)); |
1050 | printf("DEVNO-PATH: %s\n", sysfs_devno_path(devno, path, sizeof(path))); | |
1051 | ||
1052 | sysfs_devno_to_wholedisk(devno, diskname, sizeof(diskname), &disk_devno); | |
1053 | printf("WHOLEDISK-DEVNO: %u (%d:%d)\n", (unsigned int) disk_devno, major(disk_devno), minor(disk_devno)); | |
1054 | printf("WHOLEDISK-DEVNAME: %s\n", diskname); | |
1055 | ||
1056 | is_part = sysfs_devno_has_attribute(devno, "partition"); | |
778ad369 | 1057 | printf("PARTITION: %s\n", is_part ? "YES" : "NOT"); |
7fe16fda | 1058 | |
4419ffb9 KZ |
1059 | printf("HOTPLUG: %s\n", sysfs_is_hotpluggable(&cxt) ? "yes" : "no"); |
1060 | printf("SLAVES: %d\n", sysfs_count_dirents(&cxt, "slaves")); | |
7fe16fda | 1061 | |
413993fc KZ |
1062 | len = sysfs_readlink(&cxt, NULL, path, sizeof(path) - 1); |
1063 | if (len > 0) { | |
1064 | path[len] = '\0'; | |
1065 | printf("DEVNOLINK: %s\n", path); | |
1066 | } | |
1067 | ||
778ad369 KZ |
1068 | if (!is_part) { |
1069 | printf("First 5 partitions:\n"); | |
1070 | for (i = 1; i <= 5; i++) { | |
1071 | dev_t dev = sysfs_partno_to_devno(&cxt, i); | |
1072 | if (dev) | |
1073 | printf("\t#%d %d:%d\n", i, major(dev), minor(dev)); | |
1074 | } | |
1075 | } | |
1076 | ||
90e9fcda | 1077 | if (sysfs_read_u64(&cxt, "size", &u64)) |
413993fc | 1078 | printf("read SIZE failed\n"); |
90e9fcda KZ |
1079 | else |
1080 | printf("SIZE: %jd\n", u64); | |
1081 | ||
1082 | if (sysfs_read_int(&cxt, "queue/hw_sector_size", &i)) | |
413993fc | 1083 | printf("read SECTOR failed\n"); |
90e9fcda KZ |
1084 | else |
1085 | printf("SECTOR: %d\n", i); | |
7fe16fda | 1086 | |
e017ef8b KZ |
1087 | |
1088 | chain = sysfs_get_devchain(&cxt, path, sizeof(path)); | |
1089 | printf("SUBSUSTEMS:\n"); | |
1090 | ||
1091 | while (chain && sysfs_next_subsystem(&cxt, chain, &sub) == 0) { | |
1092 | printf("\t%s\n", sub); | |
1093 | free(sub); | |
1094 | } | |
1095 | ||
413993fc | 1096 | |
fb4a9e54 | 1097 | sysfs_deinit(&cxt); |
7fe16fda KZ |
1098 | return EXIT_SUCCESS; |
1099 | } | |
e8f7acb0 | 1100 | #endif /* TEST_PROGRAM_SYSFS */ |