]>
Commit | Line | Data |
---|---|---|
7fe16fda KZ |
1 | /* |
2 | * Copyright (C) 2011 Karel Zak <kzak@redhat.com> | |
3 | */ | |
4 | ||
5 | #include "c.h" | |
6 | #include "at.h" | |
7 | #include "pathnames.h" | |
8 | #include "sysfs.h" | |
9 | ||
10 | char *sysfs_devno_attribute_path(dev_t devno, char *buf, | |
413993fc | 11 | size_t bufsiz, const char *attr) |
7fe16fda KZ |
12 | { |
13 | int len; | |
14 | ||
15 | if (attr) | |
413993fc | 16 | len = snprintf(buf, bufsiz, _PATH_SYS_DEVBLOCK "/%d:%d/%s", |
7fe16fda KZ |
17 | major(devno), minor(devno), attr); |
18 | else | |
413993fc | 19 | len = snprintf(buf, bufsiz, _PATH_SYS_DEVBLOCK "/%d:%d", |
7fe16fda KZ |
20 | major(devno), minor(devno)); |
21 | ||
413993fc | 22 | return (len < 0 || len + 1 > bufsiz) ? NULL : buf; |
7fe16fda KZ |
23 | } |
24 | ||
25 | int sysfs_devno_has_attribute(dev_t devno, const char *attr) | |
26 | { | |
27 | char path[PATH_MAX]; | |
28 | struct stat info; | |
29 | ||
30 | if (!sysfs_devno_attribute_path(devno, path, sizeof(path), attr)) | |
31 | return 0; | |
32 | if (stat(path, &info) == 0) | |
33 | return 1; | |
34 | return 0; | |
35 | } | |
36 | ||
413993fc | 37 | char *sysfs_devno_path(dev_t devno, char *buf, size_t bufsiz) |
7fe16fda | 38 | { |
413993fc | 39 | return sysfs_devno_attribute_path(devno, buf, bufsiz, NULL); |
7fe16fda KZ |
40 | } |
41 | ||
42 | dev_t sysfs_devname_to_devno(const char *name, const char *parent) | |
43 | { | |
44 | char buf[PATH_MAX], *path = NULL; | |
45 | dev_t dev = 0; | |
46 | ||
47 | if (strncmp("/dev/", name, 5) == 0) { | |
48 | /* | |
49 | * Read from /dev | |
50 | */ | |
51 | struct stat st; | |
52 | ||
53 | if (stat(name, &st) == 0) | |
54 | dev = st.st_rdev; | |
55 | else | |
56 | name += 5; /* unaccesible, or not node in /dev */ | |
57 | } | |
58 | ||
59 | if (!dev && parent) { | |
60 | /* | |
61 | * Create path to /sys/block/<parent>/<name>/dev | |
62 | */ | |
63 | int len = snprintf(buf, sizeof(buf), | |
64 | _PATH_SYS_BLOCK "/%s/%s/dev", parent, name); | |
65 | if (len < 0 || len + 1 > sizeof(buf)) | |
66 | return 0; | |
67 | path = buf; | |
68 | ||
69 | } else if (!dev) { | |
70 | /* | |
71 | * Create path to /sys/block/<name>/dev | |
72 | */ | |
73 | int len = snprintf(buf, sizeof(buf), | |
74 | _PATH_SYS_BLOCK "/%s/dev", name); | |
75 | if (len < 0 || len + 1 > sizeof(buf)) | |
76 | return 0; | |
77 | path = buf; | |
78 | } | |
79 | ||
80 | if (path) { | |
81 | /* | |
82 | * read devno from sysfs | |
83 | */ | |
84 | FILE *f; | |
85 | int maj = 0, min = 0; | |
86 | ||
87 | f = fopen(path, "r"); | |
88 | if (!f) | |
89 | return 0; | |
90 | ||
91 | if (fscanf(f, "%u:%u", &maj, &min) == 2) | |
92 | dev = makedev(maj, min); | |
93 | fclose(f); | |
94 | } | |
95 | return dev; | |
96 | } | |
97 | ||
413993fc KZ |
98 | /* |
99 | * Returns devname (e.g. "/dev/sda1") for the given devno. | |
100 | * | |
101 | * Note that the @buf has to be large enough to store /sys/dev/block/<maj:min> | |
102 | * symlinks. | |
103 | * | |
104 | * Please, use more robust blkid_devno_to_devname() in your applications. | |
105 | */ | |
106 | char *sysfs_devno_to_devpath(dev_t devno, char *buf, size_t bufsiz) | |
107 | { | |
108 | struct sysfs_cxt cxt; | |
109 | char *name; | |
110 | size_t sz; | |
111 | struct stat st; | |
112 | ||
113 | if (sysfs_init(&cxt, devno, NULL)) | |
114 | return NULL; | |
115 | ||
116 | name = sysfs_get_devname(&cxt, buf, bufsiz); | |
117 | sysfs_deinit(&cxt); | |
118 | ||
119 | if (!name) | |
120 | return NULL; | |
121 | ||
122 | sz = strlen(name); | |
123 | ||
124 | if (sz + sizeof("/dev/") > bufsiz) | |
125 | return NULL; | |
126 | ||
127 | /* create the final "/dev/<name>" string */ | |
128 | memmove(buf + 5, name, sz + 1); | |
129 | memcpy(buf, "/dev/", 5); | |
130 | ||
131 | if (!stat(buf, &st) && S_ISBLK(st.st_mode) && st.st_rdev == devno) | |
132 | return buf; | |
133 | ||
134 | return NULL; | |
135 | } | |
136 | ||
7fe16fda KZ |
137 | int sysfs_init(struct sysfs_cxt *cxt, dev_t devno, struct sysfs_cxt *parent) |
138 | { | |
139 | char path[PATH_MAX]; | |
140 | int fd, rc = 0; | |
141 | ||
413993fc KZ |
142 | memset(cxt, 0, sizeof(*cxt)); |
143 | ||
7fe16fda KZ |
144 | if (!sysfs_devno_path(devno, path, sizeof(path))) |
145 | goto err; | |
146 | ||
147 | fd = open(path, O_RDONLY); | |
148 | if (fd < 0) | |
149 | goto err; | |
7fe16fda KZ |
150 | cxt->dir_path = strdup(path); |
151 | if (!cxt->dir_path) | |
152 | goto err; | |
7fe16fda KZ |
153 | cxt->devno = devno; |
154 | cxt->dir_fd = fd; | |
155 | cxt->parent = parent; | |
156 | return 0; | |
157 | err: | |
158 | rc = -errno; | |
159 | sysfs_deinit(cxt); | |
160 | return rc; | |
161 | } | |
162 | ||
163 | void sysfs_deinit(struct sysfs_cxt *cxt) | |
164 | { | |
165 | if (!cxt) | |
166 | return; | |
167 | ||
168 | if (cxt->dir_fd >= 0) | |
169 | close(cxt->dir_fd); | |
87e77645 KZ |
170 | free(cxt->dir_path); |
171 | ||
7fe16fda KZ |
172 | cxt->devno = 0; |
173 | cxt->dir_fd = -1; | |
174 | cxt->parent = NULL; | |
87e77645 | 175 | cxt->dir_path = NULL; |
7fe16fda KZ |
176 | } |
177 | ||
178 | int sysfs_stat(struct sysfs_cxt *cxt, const char *attr, struct stat *st) | |
179 | { | |
180 | int rc = fstat_at(cxt->dir_fd, cxt->dir_path, attr, st, 0); | |
181 | ||
182 | if (rc != 0 && errno == ENOENT && | |
183 | strncmp(attr, "queue/", 6) == 0 && cxt->parent) { | |
184 | ||
185 | /* Exception for "queue/<attr>". These attributes are available | |
186 | * for parental devices only | |
187 | */ | |
188 | return fstat_at(cxt->parent->dir_fd, | |
189 | cxt->parent->dir_path, attr, st, 0); | |
190 | } | |
191 | return rc; | |
192 | } | |
193 | ||
d8a84552 KZ |
194 | int sysfs_has_attribute(struct sysfs_cxt *cxt, const char *attr) |
195 | { | |
196 | struct stat st; | |
197 | ||
198 | return sysfs_stat(cxt, attr, &st) == 0; | |
199 | } | |
200 | ||
7fe16fda KZ |
201 | static int sysfs_open(struct sysfs_cxt *cxt, const char *attr) |
202 | { | |
203 | int fd = open_at(cxt->dir_fd, cxt->dir_path, attr, O_RDONLY); | |
204 | ||
205 | if (fd == -1 && errno == ENOENT && | |
206 | strncmp(attr, "queue/", 6) == 0 && cxt->parent) { | |
207 | ||
208 | /* Exception for "queue/<attr>". These attributes are available | |
209 | * for parental devices only | |
210 | */ | |
211 | fd = open_at(cxt->parent->dir_fd, cxt->dir_path, attr, O_RDONLY); | |
212 | } | |
213 | return fd; | |
214 | } | |
215 | ||
413993fc KZ |
216 | ssize_t sysfs_readlink(struct sysfs_cxt *cxt, const char *attr, |
217 | char *buf, size_t bufsiz) | |
218 | { | |
219 | if (attr) | |
220 | return readlink_at(cxt->dir_fd, cxt->dir_path, attr, buf, bufsiz); | |
221 | ||
222 | /* read /sys/dev/block/<maj:min> link */ | |
223 | return readlink(cxt->dir_path, buf, bufsiz); | |
224 | } | |
225 | ||
7fe16fda KZ |
226 | DIR *sysfs_opendir(struct sysfs_cxt *cxt, const char *attr) |
227 | { | |
228 | DIR *dir; | |
229 | int fd; | |
230 | ||
231 | if (attr) | |
232 | fd = sysfs_open(cxt, attr); | |
413993fc | 233 | else |
7fe16fda KZ |
234 | /* request to open root of device in sysfs (/sys/block/<dev>) |
235 | * -- we cannot use cxt->sysfs_fd directly, because closedir() | |
236 | * will close this our persistent file descriptor. | |
237 | */ | |
238 | fd = dup(cxt->dir_fd); | |
7fe16fda KZ |
239 | |
240 | if (fd < 0) | |
241 | return NULL; | |
242 | ||
243 | dir = fdopendir(fd); | |
244 | if (!dir) { | |
245 | close(fd); | |
246 | return NULL; | |
247 | } | |
248 | if (!attr) | |
249 | rewinddir(dir); | |
250 | return dir; | |
251 | } | |
252 | ||
253 | ||
254 | static FILE *sysfs_fopen(struct sysfs_cxt *cxt, const char *attr) | |
255 | { | |
256 | int fd = sysfs_open(cxt, attr); | |
257 | ||
258 | return fd < 0 ? NULL : fdopen(fd, "r"); | |
259 | } | |
260 | ||
261 | ||
262 | static struct dirent *xreaddir(DIR *dp) | |
263 | { | |
264 | struct dirent *d; | |
265 | ||
266 | while ((d = readdir(dp))) { | |
267 | if (!strcmp(d->d_name, ".") || | |
268 | !strcmp(d->d_name, "..")) | |
269 | continue; | |
270 | ||
271 | /* blacklist here? */ | |
272 | break; | |
273 | } | |
274 | return d; | |
275 | } | |
276 | ||
277 | int sysfs_is_partition_dirent(DIR *dir, struct dirent *d, const char *parent_name) | |
278 | { | |
279 | char path[256]; | |
280 | ||
281 | #ifdef _DIRENT_HAVE_D_TYPE | |
282 | if (d->d_type != DT_DIR) | |
283 | return 0; | |
284 | #endif | |
285 | if (strncmp(parent_name, d->d_name, strlen(parent_name))) | |
286 | return 0; | |
287 | ||
288 | /* Cannot use /partition file, not supported on old sysfs */ | |
289 | snprintf(path, sizeof(path), "%s/start", d->d_name); | |
290 | ||
291 | return faccessat(dirfd(dir), path, R_OK, 0) == 0; | |
292 | } | |
293 | ||
294 | int sysfs_scanf(struct sysfs_cxt *cxt, const char *attr, const char *fmt, ...) | |
295 | { | |
296 | FILE *f = sysfs_fopen(cxt, attr); | |
297 | va_list ap; | |
298 | int rc; | |
299 | ||
300 | if (!f) | |
301 | return -EINVAL; | |
302 | va_start(ap, fmt); | |
303 | rc = vfscanf(f, fmt, ap); | |
304 | va_end(ap); | |
305 | ||
306 | fclose(f); | |
307 | return rc; | |
308 | } | |
309 | ||
413993fc | 310 | |
90e9fcda | 311 | int sysfs_read_s64(struct sysfs_cxt *cxt, const char *attr, int64_t *res) |
7fe16fda | 312 | { |
90e9fcda KZ |
313 | int64_t x = 0; |
314 | ||
315 | if (sysfs_scanf(cxt, attr, "%"SCNd64, &x) == 1) { | |
316 | if (res) | |
317 | *res = x; | |
318 | return 0; | |
319 | } | |
320 | return -1; | |
7fe16fda KZ |
321 | } |
322 | ||
90e9fcda | 323 | int sysfs_read_u64(struct sysfs_cxt *cxt, const char *attr, uint64_t *res) |
7fe16fda | 324 | { |
90e9fcda KZ |
325 | uint64_t x = 0; |
326 | ||
327 | if (sysfs_scanf(cxt, attr, "%"SCNu64, &x) == 1) { | |
328 | if (res) | |
329 | *res = x; | |
330 | return 0; | |
331 | } | |
332 | return -1; | |
7fe16fda KZ |
333 | } |
334 | ||
90e9fcda | 335 | int sysfs_read_int(struct sysfs_cxt *cxt, const char *attr, int *res) |
7fe16fda | 336 | { |
90e9fcda KZ |
337 | int x = 0; |
338 | ||
339 | if (sysfs_scanf(cxt, attr, "%d", &x) == 1) { | |
340 | if (res) | |
341 | *res = x; | |
342 | return 0; | |
343 | } | |
344 | return -1; | |
7fe16fda KZ |
345 | } |
346 | ||
347 | char *sysfs_strdup(struct sysfs_cxt *cxt, const char *attr) | |
348 | { | |
349 | char buf[1024]; | |
350 | return sysfs_scanf(cxt, attr, "%1024[^\n]", buf) == 1 ? | |
351 | strdup(buf) : NULL; | |
352 | } | |
353 | ||
354 | int sysfs_count_dirents(struct sysfs_cxt *cxt, const char *attr) | |
355 | { | |
356 | DIR *dir; | |
357 | int r = 0; | |
358 | ||
359 | if (!(dir = sysfs_opendir(cxt, attr))) | |
360 | return 0; | |
361 | ||
362 | while (xreaddir(dir)) r++; | |
363 | ||
364 | closedir(dir); | |
365 | return r; | |
366 | } | |
367 | ||
368 | int sysfs_count_partitions(struct sysfs_cxt *cxt, const char *devname) | |
369 | { | |
370 | DIR *dir; | |
371 | struct dirent *d; | |
372 | int r = 0; | |
373 | ||
374 | if (!(dir = sysfs_opendir(cxt, NULL))) | |
375 | return 0; | |
376 | ||
377 | while ((d = xreaddir(dir))) { | |
378 | if (sysfs_is_partition_dirent(dir, d, devname)) | |
379 | r++; | |
380 | } | |
381 | ||
382 | closedir(dir); | |
383 | return r; | |
384 | } | |
385 | ||
413993fc KZ |
386 | /* |
387 | * Returns slave name if there is only one slave, otherwise returns NULL. | |
388 | * The result should be deallocated by free(). | |
389 | */ | |
390 | char *sysfs_get_slave(struct sysfs_cxt *cxt) | |
391 | { | |
392 | DIR *dir; | |
393 | struct dirent *d; | |
394 | char *name = NULL; | |
395 | ||
396 | if (!(dir = sysfs_opendir(cxt, "slaves"))) | |
397 | return NULL; | |
398 | ||
399 | while ((d = xreaddir(dir))) { | |
400 | if (name) | |
401 | goto err; /* more slaves */ | |
402 | ||
403 | name = strdup(d->d_name); | |
404 | } | |
405 | ||
406 | closedir(dir); | |
407 | return name; | |
408 | err: | |
409 | free(name); | |
410 | return NULL; | |
411 | } | |
412 | ||
413 | /* | |
414 | * Note that the @buf has to be large enough to store /sys/dev/block/<maj:min> | |
415 | * symlinks. | |
416 | */ | |
417 | char *sysfs_get_devname(struct sysfs_cxt *cxt, char *buf, size_t bufsiz) | |
418 | { | |
419 | char *name = NULL; | |
420 | ssize_t sz; | |
421 | ||
422 | sz = sysfs_readlink(cxt, NULL, buf, bufsiz - 1); | |
423 | if (sz < 0) | |
424 | return NULL; | |
425 | ||
426 | buf[sz] = '\0'; | |
427 | name = strrchr(buf, '/'); | |
428 | if (!name) | |
429 | return NULL; | |
430 | ||
431 | name++; | |
432 | sz = strlen(name); | |
433 | ||
434 | memmove(buf, name, sz + 1); | |
435 | return buf; | |
436 | } | |
7fe16fda | 437 | |
e918cca5 | 438 | #ifdef TEST_PROGRAM_SYSFS |
7fe16fda KZ |
439 | #include <errno.h> |
440 | #include <err.h> | |
441 | #include <stdlib.h> | |
442 | ||
443 | int main(int argc, char *argv[]) | |
444 | { | |
445 | struct sysfs_cxt cxt; | |
446 | char *devname; | |
447 | dev_t devno; | |
448 | char path[PATH_MAX]; | |
90e9fcda KZ |
449 | int i; |
450 | uint64_t u64; | |
413993fc | 451 | ssize_t len; |
7fe16fda KZ |
452 | |
453 | if (argc != 2) | |
454 | errx(EXIT_FAILURE, "usage: %s <devname>", argv[0]); | |
455 | ||
456 | devname = argv[1]; | |
457 | devno = sysfs_devname_to_devno(devname, NULL); | |
458 | ||
459 | if (!devno) | |
460 | err(EXIT_FAILURE, "failed to read devno"); | |
461 | ||
462 | printf("NAME: %s\n", devname); | |
463 | printf("DEVNO: %u\n", (unsigned int) devno); | |
464 | printf("DEVNOPATH: %s\n", sysfs_devno_path(devno, path, sizeof(path))); | |
413993fc | 465 | printf("DEVPATH: %s\n", sysfs_devno_to_devpath(devno, path, sizeof(path))); |
7fe16fda KZ |
466 | printf("PARTITION: %s\n", |
467 | sysfs_devno_has_attribute(devno, "partition") ? "YES" : "NOT"); | |
468 | ||
469 | sysfs_init(&cxt, devno, NULL); | |
470 | ||
413993fc KZ |
471 | len = sysfs_readlink(&cxt, NULL, path, sizeof(path) - 1); |
472 | if (len > 0) { | |
473 | path[len] = '\0'; | |
474 | printf("DEVNOLINK: %s\n", path); | |
475 | } | |
476 | ||
7fe16fda | 477 | printf("SLAVES: %d\n", sysfs_count_dirents(&cxt, "slaves")); |
90e9fcda KZ |
478 | |
479 | if (sysfs_read_u64(&cxt, "size", &u64)) | |
413993fc | 480 | printf("read SIZE failed\n"); |
90e9fcda KZ |
481 | else |
482 | printf("SIZE: %jd\n", u64); | |
483 | ||
484 | if (sysfs_read_int(&cxt, "queue/hw_sector_size", &i)) | |
413993fc | 485 | printf("read SECTOR failed\n"); |
90e9fcda KZ |
486 | else |
487 | printf("SECTOR: %d\n", i); | |
7fe16fda | 488 | |
413993fc KZ |
489 | printf("DEVNAME: %s\n", sysfs_get_devname(&cxt, path, sizeof(path))); |
490 | ||
fb4a9e54 | 491 | sysfs_deinit(&cxt); |
7fe16fda KZ |
492 | return EXIT_SUCCESS; |
493 | } | |
494 | #endif |