]> git.ipfire.org Git - thirdparty/util-linux.git/blob - lib/sysfs.c
lib/sysfs: fix handle leak
[thirdparty/util-linux.git] / lib / sysfs.c
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,
11 size_t bufsiz, const char *attr)
12 {
13 int len;
14
15 if (attr)
16 len = snprintf(buf, bufsiz, _PATH_SYS_DEVBLOCK "/%d:%d/%s",
17 major(devno), minor(devno), attr);
18 else
19 len = snprintf(buf, bufsiz, _PATH_SYS_DEVBLOCK "/%d:%d",
20 major(devno), minor(devno));
21
22 return (len < 0 || (size_t) len + 1 > bufsiz) ? NULL : buf;
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
37 char *sysfs_devno_path(dev_t devno, char *buf, size_t bufsiz)
38 {
39 return sysfs_devno_attribute_path(devno, buf, bufsiz, NULL);
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 || (size_t) 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 || (size_t) 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
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
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
142 memset(cxt, 0, sizeof(*cxt));
143 cxt->dir_fd = -1;
144
145 if (!sysfs_devno_path(devno, path, sizeof(path)))
146 goto err;
147
148 fd = open(path, O_RDONLY);
149 if (fd < 0)
150 goto err;
151 cxt->dir_fd = fd;
152
153 cxt->dir_path = strdup(path);
154 if (!cxt->dir_path)
155 goto err;
156 cxt->devno = devno;
157 cxt->parent = parent;
158 return 0;
159 err:
160 rc = -errno;
161 sysfs_deinit(cxt);
162 return rc;
163 }
164
165 void sysfs_deinit(struct sysfs_cxt *cxt)
166 {
167 if (!cxt)
168 return;
169
170 if (cxt->dir_fd >= 0)
171 close(cxt->dir_fd);
172 free(cxt->dir_path);
173
174 cxt->devno = 0;
175 cxt->dir_fd = -1;
176 cxt->parent = NULL;
177 cxt->dir_path = NULL;
178 }
179
180 int sysfs_stat(struct sysfs_cxt *cxt, const char *attr, struct stat *st)
181 {
182 int rc = fstat_at(cxt->dir_fd, cxt->dir_path, attr, st, 0);
183
184 if (rc != 0 && errno == ENOENT &&
185 strncmp(attr, "queue/", 6) == 0 && cxt->parent) {
186
187 /* Exception for "queue/<attr>". These attributes are available
188 * for parental devices only
189 */
190 return fstat_at(cxt->parent->dir_fd,
191 cxt->parent->dir_path, attr, st, 0);
192 }
193 return rc;
194 }
195
196 int sysfs_has_attribute(struct sysfs_cxt *cxt, const char *attr)
197 {
198 struct stat st;
199
200 return sysfs_stat(cxt, attr, &st) == 0;
201 }
202
203 static int sysfs_open(struct sysfs_cxt *cxt, const char *attr)
204 {
205 int fd = open_at(cxt->dir_fd, cxt->dir_path, attr, O_RDONLY);
206
207 if (fd == -1 && errno == ENOENT &&
208 strncmp(attr, "queue/", 6) == 0 && cxt->parent) {
209
210 /* Exception for "queue/<attr>". These attributes are available
211 * for parental devices only
212 */
213 fd = open_at(cxt->parent->dir_fd, cxt->dir_path, attr, O_RDONLY);
214 }
215 return fd;
216 }
217
218 ssize_t sysfs_readlink(struct sysfs_cxt *cxt, const char *attr,
219 char *buf, size_t bufsiz)
220 {
221 if (attr)
222 return readlink_at(cxt->dir_fd, cxt->dir_path, attr, buf, bufsiz);
223
224 /* read /sys/dev/block/<maj:min> link */
225 return readlink(cxt->dir_path, buf, bufsiz);
226 }
227
228 DIR *sysfs_opendir(struct sysfs_cxt *cxt, const char *attr)
229 {
230 DIR *dir;
231 int fd;
232
233 if (attr)
234 fd = sysfs_open(cxt, attr);
235 else
236 /* request to open root of device in sysfs (/sys/block/<dev>)
237 * -- we cannot use cxt->sysfs_fd directly, because closedir()
238 * will close this our persistent file descriptor.
239 */
240 fd = dup(cxt->dir_fd);
241
242 if (fd < 0)
243 return NULL;
244
245 dir = fdopendir(fd);
246 if (!dir) {
247 close(fd);
248 return NULL;
249 }
250 if (!attr)
251 rewinddir(dir);
252 return dir;
253 }
254
255
256 static FILE *sysfs_fopen(struct sysfs_cxt *cxt, const char *attr)
257 {
258 int fd = sysfs_open(cxt, attr);
259
260 return fd < 0 ? NULL : fdopen(fd, "r");
261 }
262
263
264 static struct dirent *xreaddir(DIR *dp)
265 {
266 struct dirent *d;
267
268 while ((d = readdir(dp))) {
269 if (!strcmp(d->d_name, ".") ||
270 !strcmp(d->d_name, ".."))
271 continue;
272
273 /* blacklist here? */
274 break;
275 }
276 return d;
277 }
278
279 int sysfs_is_partition_dirent(DIR *dir, struct dirent *d, const char *parent_name)
280 {
281 char path[256];
282
283 #ifdef _DIRENT_HAVE_D_TYPE
284 if (d->d_type != DT_DIR)
285 return 0;
286 #endif
287 if (strncmp(parent_name, d->d_name, strlen(parent_name)))
288 return 0;
289
290 /* Cannot use /partition file, not supported on old sysfs */
291 snprintf(path, sizeof(path), "%s/start", d->d_name);
292
293 return faccessat(dirfd(dir), path, R_OK, 0) == 0;
294 }
295
296 int sysfs_scanf(struct sysfs_cxt *cxt, const char *attr, const char *fmt, ...)
297 {
298 FILE *f = sysfs_fopen(cxt, attr);
299 va_list ap;
300 int rc;
301
302 if (!f)
303 return -EINVAL;
304 va_start(ap, fmt);
305 rc = vfscanf(f, fmt, ap);
306 va_end(ap);
307
308 fclose(f);
309 return rc;
310 }
311
312
313 int sysfs_read_s64(struct sysfs_cxt *cxt, const char *attr, int64_t *res)
314 {
315 int64_t x = 0;
316
317 if (sysfs_scanf(cxt, attr, "%"SCNd64, &x) == 1) {
318 if (res)
319 *res = x;
320 return 0;
321 }
322 return -1;
323 }
324
325 int sysfs_read_u64(struct sysfs_cxt *cxt, const char *attr, uint64_t *res)
326 {
327 uint64_t x = 0;
328
329 if (sysfs_scanf(cxt, attr, "%"SCNu64, &x) == 1) {
330 if (res)
331 *res = x;
332 return 0;
333 }
334 return -1;
335 }
336
337 int sysfs_read_int(struct sysfs_cxt *cxt, const char *attr, int *res)
338 {
339 int x = 0;
340
341 if (sysfs_scanf(cxt, attr, "%d", &x) == 1) {
342 if (res)
343 *res = x;
344 return 0;
345 }
346 return -1;
347 }
348
349 char *sysfs_strdup(struct sysfs_cxt *cxt, const char *attr)
350 {
351 char buf[1024];
352 return sysfs_scanf(cxt, attr, "%1024[^\n]", buf) == 1 ?
353 strdup(buf) : NULL;
354 }
355
356 int sysfs_count_dirents(struct sysfs_cxt *cxt, const char *attr)
357 {
358 DIR *dir;
359 int r = 0;
360
361 if (!(dir = sysfs_opendir(cxt, attr)))
362 return 0;
363
364 while (xreaddir(dir)) r++;
365
366 closedir(dir);
367 return r;
368 }
369
370 int sysfs_count_partitions(struct sysfs_cxt *cxt, const char *devname)
371 {
372 DIR *dir;
373 struct dirent *d;
374 int r = 0;
375
376 if (!(dir = sysfs_opendir(cxt, NULL)))
377 return 0;
378
379 while ((d = xreaddir(dir))) {
380 if (sysfs_is_partition_dirent(dir, d, devname))
381 r++;
382 }
383
384 closedir(dir);
385 return r;
386 }
387
388 /*
389 * Returns slave name if there is only one slave, otherwise returns NULL.
390 * The result should be deallocated by free().
391 */
392 char *sysfs_get_slave(struct sysfs_cxt *cxt)
393 {
394 DIR *dir;
395 struct dirent *d;
396 char *name = NULL;
397
398 if (!(dir = sysfs_opendir(cxt, "slaves")))
399 return NULL;
400
401 while ((d = xreaddir(dir))) {
402 if (name)
403 goto err; /* more slaves */
404
405 name = strdup(d->d_name);
406 }
407
408 closedir(dir);
409 return name;
410 err:
411 free(name);
412 closedir(dir);
413 return NULL;
414 }
415
416 /*
417 * Note that the @buf has to be large enough to store /sys/dev/block/<maj:min>
418 * symlinks.
419 */
420 char *sysfs_get_devname(struct sysfs_cxt *cxt, char *buf, size_t bufsiz)
421 {
422 char *name = NULL;
423 ssize_t sz;
424
425 sz = sysfs_readlink(cxt, NULL, buf, bufsiz - 1);
426 if (sz < 0)
427 return NULL;
428
429 buf[sz] = '\0';
430 name = strrchr(buf, '/');
431 if (!name)
432 return NULL;
433
434 name++;
435 sz = strlen(name);
436
437 memmove(buf, name, sz + 1);
438 return buf;
439 }
440
441 #ifdef TEST_PROGRAM_SYSFS
442 #include <errno.h>
443 #include <err.h>
444 #include <stdlib.h>
445
446 int main(int argc, char *argv[])
447 {
448 struct sysfs_cxt cxt;
449 char *devname;
450 dev_t devno;
451 char path[PATH_MAX];
452 int i;
453 uint64_t u64;
454 ssize_t len;
455
456 if (argc != 2)
457 errx(EXIT_FAILURE, "usage: %s <devname>", argv[0]);
458
459 devname = argv[1];
460 devno = sysfs_devname_to_devno(devname, NULL);
461
462 if (!devno)
463 err(EXIT_FAILURE, "failed to read devno");
464
465 printf("NAME: %s\n", devname);
466 printf("DEVNO: %u\n", (unsigned int) devno);
467 printf("DEVNOPATH: %s\n", sysfs_devno_path(devno, path, sizeof(path)));
468 printf("DEVPATH: %s\n", sysfs_devno_to_devpath(devno, path, sizeof(path)));
469 printf("PARTITION: %s\n",
470 sysfs_devno_has_attribute(devno, "partition") ? "YES" : "NOT");
471
472 sysfs_init(&cxt, devno, NULL);
473
474 len = sysfs_readlink(&cxt, NULL, path, sizeof(path) - 1);
475 if (len > 0) {
476 path[len] = '\0';
477 printf("DEVNOLINK: %s\n", path);
478 }
479
480 printf("SLAVES: %d\n", sysfs_count_dirents(&cxt, "slaves"));
481
482 if (sysfs_read_u64(&cxt, "size", &u64))
483 printf("read SIZE failed\n");
484 else
485 printf("SIZE: %jd\n", u64);
486
487 if (sysfs_read_int(&cxt, "queue/hw_sector_size", &i))
488 printf("read SECTOR failed\n");
489 else
490 printf("SECTOR: %d\n", i);
491
492 printf("DEVNAME: %s\n", sysfs_get_devname(&cxt, path, sizeof(path)));
493
494 sysfs_deinit(&cxt);
495 return EXIT_SUCCESS;
496 }
497 #endif