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