b9621ac24ef1859bf87bb69b860f1d7289847ae6
[platform/upstream/multipath-tools.git] / libmultipath / sysfs.c
1 /*
2  * Copyright (C) 2005-2006 Kay Sievers <kay.sievers@vrfy.org>
3  *
4  *      This program is free software; you can redistribute it and/or modify it
5  *      under the terms of the GNU General Public License as published by the
6  *      Free Software Foundation version 2 of the License.
7  * 
8  *      This program is distributed in the hope that it will be useful, but
9  *      WITHOUT ANY WARRANTY; without even the implied warranty of
10  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  *      General Public License for more details.
12  * 
13  *      You should have received a copy of the GNU General Public License along
14  *      with this program; if not, write to the Free Software Foundation, Inc.,
15  *      51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16  *
17  */
18
19
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <stddef.h>
23 #include <unistd.h>
24 #include <fcntl.h>
25 #include <ctype.h>
26 #include <errno.h>
27 #include <sys/stat.h>
28 #include <string.h>
29
30 #include "checkers.h"
31 #include "vector.h"
32 #include "structs.h"
33 #include "sysfs.h"
34 #include "list.h"
35 #include "util.h"
36
37 char sysfs_path[PATH_SIZE];
38
39 /* attribute value cache */
40 static LIST_HEAD(attr_list);
41 struct sysfs_attr {
42         struct list_head node;
43         char path[PATH_SIZE];
44         char *value;                    /* points to value_local if value is cached */
45         char value_local[NAME_SIZE];
46 };
47
48 /* list of sysfs devices */
49 static LIST_HEAD(sysfs_dev_list);
50 struct sysfs_dev {
51         struct list_head node;
52         struct sysfs_device dev;
53 };
54
55 int sysfs_init(char *path, size_t len)
56 {
57         if (path) {
58                 strlcpy(sysfs_path, path, len);
59                 remove_trailing_chars(sysfs_path, '/');
60         } else
61                 strlcpy(sysfs_path, "/sys", len);
62         dbg("sysfs_path='%s'", sysfs_path);
63
64         INIT_LIST_HEAD(&attr_list);
65         INIT_LIST_HEAD(&sysfs_dev_list);
66         return 0;
67 }
68
69 void sysfs_cleanup(void)
70 {
71         struct sysfs_attr *attr_loop;
72         struct sysfs_attr *attr_temp;
73
74         struct sysfs_dev *sysdev_loop;
75         struct sysfs_dev *sysdev_temp;
76
77         list_for_each_entry_safe(attr_loop, attr_temp, &attr_list, node) {
78                 list_del(&attr_loop->node);
79                 free(attr_loop);
80         }
81
82         list_for_each_entry_safe(sysdev_loop, sysdev_temp, &sysfs_dev_list, node) {
83                 free(sysdev_loop);
84         }
85 }
86
87 void sysfs_device_set_values(struct sysfs_device *dev, const char *devpath,
88                              const char *subsystem, const char *driver)
89 {
90         char *pos;
91
92         strlcpy(dev->devpath, devpath, sizeof(dev->devpath));
93         if (subsystem != NULL)
94                 strlcpy(dev->subsystem, subsystem, sizeof(dev->subsystem));
95         if (driver != NULL)
96                 strlcpy(dev->driver, driver, sizeof(dev->driver));
97
98         /* set kernel name */
99         pos = strrchr(dev->devpath, '/');
100         if (pos == NULL)
101                 return;
102         strlcpy(dev->kernel, &pos[1], sizeof(dev->kernel));
103         dbg("kernel='%s'", dev->kernel);
104
105         /* some devices have '!' in their name, change that to '/' */
106         pos = dev->kernel;
107         while (pos[0] != '\0') {
108                 if (pos[0] == '!')
109                         pos[0] = '/';
110                 pos++;
111         }
112
113         /* get kernel number */
114         pos = &dev->kernel[strlen(dev->kernel)];
115         while (isdigit(pos[-1]))
116                 pos--;
117         strlcpy(dev->kernel_number, pos, sizeof(dev->kernel_number));
118         dbg("kernel_number='%s'", dev->kernel_number);
119 }
120
121 int sysfs_resolve_link(char *devpath, size_t size)
122 {
123         char link_path[PATH_SIZE];
124         char link_target[PATH_SIZE];
125         int len;
126         int i;
127         int back;
128
129         strlcpy(link_path, sysfs_path, sizeof(link_path));
130         strlcat(link_path, devpath, sizeof(link_path));
131         len = readlink(link_path, link_target, sizeof(link_target));
132         if (len <= 0)
133                 return -1;
134         link_target[len] = '\0';
135         dbg("path link '%s' points to '%s'", devpath, link_target);
136
137         for (back = 0; strncmp(&link_target[back * 3], "../", 3) == 0; back++)
138                 ;
139         dbg("base '%s', tail '%s', back %i", devpath, &link_target[back * 3], back);
140         for (i = 0; i <= back; i++) {
141                 char *pos = strrchr(devpath, '/');
142
143                 if (pos == NULL)
144                         return -1;
145                 pos[0] = '\0';
146         }
147         dbg("after moving back '%s'", devpath);
148         strlcat(devpath, "/", size);
149         strlcat(devpath, &link_target[back * 3], size);
150         return 0;
151 }
152
153 struct sysfs_device *sysfs_device_get(const char *devpath)
154 {
155         char path[PATH_SIZE];
156         char devpath_real[PATH_SIZE];
157         struct sysfs_device *dev = NULL;
158         struct sysfs_dev *sysdev_loop, *sysdev;
159         struct stat statbuf;
160         char link_path[PATH_SIZE];
161         char link_target[PATH_SIZE];
162         int len;
163         char *pos;
164
165         dbg("open '%s'", devpath);
166         strlcpy(devpath_real, devpath, sizeof(devpath_real));
167         remove_trailing_chars(devpath_real, '/');
168
169         /* if we got a link, resolve it to the real device */
170         strlcpy(path, sysfs_path, sizeof(path));
171         strlcat(path, devpath_real, sizeof(path));
172         if (lstat(path, &statbuf) != 0) {
173                 /* if stat fails look in the cache */
174                 dbg("stat '%s' failed: %s", path, strerror(errno));
175                 list_for_each_entry(sysdev_loop, &sysfs_dev_list, node) {
176                         if (strcmp(sysdev_loop->dev.devpath, devpath_real) == 0) {
177                                 dbg("found vanished dev in cache '%s'", sysdev_loop->dev.devpath);
178                                 return &sysdev_loop->dev;
179                         }
180                 }
181                 return NULL;
182         }
183         if (S_ISLNK(statbuf.st_mode)) {
184                 if (sysfs_resolve_link(devpath_real, sizeof(devpath_real)) != 0)
185                         return NULL;
186
187         }
188
189         list_for_each_entry(sysdev_loop, &sysfs_dev_list, node) {
190                 if (strcmp(sysdev_loop->dev.devpath, devpath_real) == 0) {
191                         dbg("found dev in cache '%s'", sysdev_loop->dev.devpath);
192                                 dev = &sysdev_loop->dev;
193                 }
194         }
195         if(!dev) {
196                 /* it is a new device */
197                 dbg("new device '%s'", devpath_real);
198                 sysdev = malloc(sizeof(struct sysfs_dev));
199                 if (sysdev == NULL)
200                         return NULL;
201                 memset(sysdev, 0x00, sizeof(struct sysfs_dev));
202                 list_add(&sysdev->node, &sysfs_dev_list);
203                 dev = &sysdev->dev;
204         }
205
206         sysfs_device_set_values(dev, devpath_real, NULL, NULL);
207
208         /* get subsystem name */
209         strlcpy(link_path, sysfs_path, sizeof(link_path));
210         strlcat(link_path, dev->devpath, sizeof(link_path));
211         strlcat(link_path, "/subsystem", sizeof(link_path));
212         len = readlink(link_path, link_target, sizeof(link_target));
213         if (len > 0) {
214                 /* get subsystem from "subsystem" link */
215                 link_target[len] = '\0';
216                 dbg("subsystem link '%s' points to '%s'", link_path, link_target);
217                 pos = strrchr(link_target, '/');
218                 if (pos != NULL)
219                         strlcpy(dev->subsystem, &pos[1], sizeof(dev->subsystem));
220         } else if (strncmp(dev->devpath, "/class/", 7) == 0) {
221                 /* get subsystem from class dir */
222                 strlcpy(dev->subsystem, &dev->devpath[7], sizeof(dev->subsystem));
223                 pos = strchr(dev->subsystem, '/');
224                 if (pos != NULL)
225                         pos[0] = '\0';
226                 else
227                         dev->subsystem[0] = '\0';
228         } else if (strncmp(dev->devpath, "/block/", 7) == 0) {
229                 strlcpy(dev->subsystem, "block", sizeof(dev->subsystem));
230         } else if (strncmp(dev->devpath, "/devices/", 9) == 0) {
231                 /* get subsystem from "bus" link */
232                 strlcpy(link_path, sysfs_path, sizeof(link_path));
233                 strlcat(link_path, dev->devpath, sizeof(link_path));
234                 strlcat(link_path, "/bus", sizeof(link_path));
235                 len = readlink(link_path, link_target, sizeof(link_target));
236                 if (len > 0) {
237                         link_target[len] = '\0';
238                         dbg("bus link '%s' points to '%s'", link_path, link_target);
239                         pos = strrchr(link_target, '/');
240                         if (pos != NULL)
241                                 strlcpy(dev->subsystem, &pos[1], sizeof(dev->subsystem));
242                 }
243         } else if (strstr(dev->devpath, "/drivers/") != NULL) {
244                 strlcpy(dev->subsystem, "drivers", sizeof(dev->subsystem));
245         } else if (strncmp(dev->devpath, "/module/", 8) == 0) {
246                 strlcpy(dev->subsystem, "module", sizeof(dev->subsystem));
247         }
248
249         /* get driver name */
250         strlcpy(link_path, sysfs_path, sizeof(link_path));
251         strlcat(link_path, dev->devpath, sizeof(link_path));
252         strlcat(link_path, "/driver", sizeof(link_path));
253         len = readlink(link_path, link_target, sizeof(link_target));
254         if (len > 0) {
255                 link_target[len] = '\0';
256                 dbg("driver link '%s' points to '%s'", link_path, link_target);
257                 pos = strrchr(link_target, '/');
258                 if (pos != NULL)
259                         strlcpy(dev->driver, &pos[1], sizeof(dev->driver));
260         }
261         return dev;
262 }
263
264 struct sysfs_device *sysfs_device_get_parent(struct sysfs_device *dev)
265 {
266         char parent_devpath[PATH_SIZE];
267         char *pos;
268
269         dbg("open '%s'", dev->devpath);
270
271         /* look if we already know the parent */
272         if (dev->parent != NULL)
273                 return dev->parent;
274
275         /* requesting a parent is only valid for devices */
276         if ((strncmp(dev->devpath, "/devices/", 9) != 0) &&
277             (strncmp(dev->devpath, "/subsystem/", 11) != 0) &&
278             (strncmp(dev->devpath, "/class/", 7) != 0) &&
279             (strncmp(dev->devpath, "/block/", 7) != 0))
280                 return NULL;
281
282         strlcpy(parent_devpath, dev->devpath, sizeof(parent_devpath));
283         dbg("'%s'", parent_devpath);
284
285         /* strip last element */
286         pos = strrchr(parent_devpath, '/');
287         if (pos == NULL || pos == parent_devpath)
288                 return NULL;
289         pos[0] = '\0';
290
291         /* are we at the top level of /devices */
292         if (strcmp(parent_devpath, "/devices") == 0) {
293                 dbg("/devices top level");
294                 return NULL;
295         }
296
297         /* at the subsystems top level we want to follow the old-style "device" link */
298         if (strncmp(parent_devpath, "/subsystem", 10) == 0) {
299                 pos = strrchr(parent_devpath, '/');
300                 if (pos == &parent_devpath[10] || pos == parent_devpath || strcmp(pos, "/devices") == 0) {
301                         dbg("/subsystem top level, look for device link");
302                         goto device_link;
303                 }
304         }
305         if (strncmp(parent_devpath, "/class", 6) == 0) {
306                 pos = strrchr(parent_devpath, '/');
307                 if (pos == &parent_devpath[6] || pos == parent_devpath) {
308                         dbg("/class top level, look for device link");
309                         goto device_link;
310                 }
311         }
312         if (strcmp(parent_devpath, "/block") == 0) {
313                 dbg("/block top level, look for device link");
314                 goto device_link;
315         }
316
317         /* get parent and remember it */
318         dev->parent = sysfs_device_get(parent_devpath);
319         return dev->parent;
320
321 device_link:
322         strlcpy(parent_devpath, dev->devpath, sizeof(parent_devpath));
323         strlcat(parent_devpath, "/device", sizeof(parent_devpath));
324         if (sysfs_resolve_link(parent_devpath, sizeof(parent_devpath)) != 0)
325                 return NULL;
326
327         /* get parent and remember it */
328         dev->parent = sysfs_device_get(parent_devpath);
329         return dev->parent;
330 }
331
332 struct sysfs_device *sysfs_device_get_parent_with_subsystem(struct sysfs_device *dev, const char *subsystem)
333 {
334         struct sysfs_device *dev_parent;
335
336         dev_parent = sysfs_device_get_parent(dev);
337         while (dev_parent != NULL) {
338                 if (strcmp(dev_parent->subsystem, subsystem) == 0)
339                         return dev_parent;
340                 dev_parent = sysfs_device_get_parent(dev_parent);
341         }
342         return NULL;
343 }
344
345 void sysfs_device_put(struct sysfs_device *dev)
346 {
347         struct sysfs_dev *sysdev_loop;
348
349         list_for_each_entry(sysdev_loop, &sysfs_dev_list, node) {
350                 if (&sysdev_loop->dev == dev) {
351                         dbg("removed dev '%s' from cache",
352                             sysdev_loop->dev.devpath);
353                         list_del(&sysdev_loop->node);
354                         free(sysdev_loop);
355                         return;
356                 }
357         }
358         dbg("dev '%s' not found in cache",
359             sysdev_loop->dev.devpath);
360
361         return;
362 }
363
364 char *sysfs_attr_get_value(const char *devpath, const char *attr_name)
365 {
366         char path_full[PATH_SIZE];
367         const char *path;
368         char value[NAME_SIZE];
369         struct sysfs_attr *attr_loop;
370         struct sysfs_attr *attr = NULL;
371         struct stat statbuf;
372         int fd;
373         ssize_t size;
374         size_t sysfs_len;
375
376         dbg("open '%s'/'%s'", devpath, attr_name);
377         sysfs_len = strlcpy(path_full, sysfs_path, sizeof(path_full));
378         path = &path_full[sysfs_len];
379         strlcat(path_full, devpath, sizeof(path_full));
380         strlcat(path_full, "/", sizeof(path_full));
381         strlcat(path_full, attr_name, sizeof(path_full));
382
383         /* look for attribute in cache */
384         list_for_each_entry(attr_loop, &attr_list, node) {
385                 if (strcmp(attr_loop->path, path) == 0) {
386                         dbg("found in cache '%s'", attr_loop->path);
387                         attr = attr_loop;
388                 }
389         }
390         if (!attr) {
391                 /* store attribute in cache */
392                 dbg("new uncached attribute '%s'", path_full);
393                 attr = malloc(sizeof(struct sysfs_attr));
394                 if (attr == NULL)
395                         return NULL;
396                 memset(attr, 0x00, sizeof(struct sysfs_attr));
397                 strlcpy(attr->path, path, sizeof(attr->path));
398                 dbg("add to cache '%s'", path_full);
399                 list_add(&attr->node, &attr_list);
400         } else {
401                 /* clear old value */
402                 if(attr->value)
403                         memset(attr->value, 0x00, sizeof(attr->value));
404         }
405
406         if (lstat(path_full, &statbuf) != 0) {
407                 dbg("stat '%s' failed: %s", path_full, strerror(errno));
408                 goto out;
409         }
410
411         if (S_ISLNK(statbuf.st_mode)) {
412                 /* links return the last element of the target path */
413                 char link_target[PATH_SIZE];
414                 int len;
415                 const char *pos;
416
417                 len = readlink(path_full, link_target, sizeof(link_target));
418                 if (len > 0) {
419                         link_target[len] = '\0';
420                         pos = strrchr(link_target, '/');
421                         if (pos != NULL) {
422                                 dbg("cache '%s' with link value '%s'", path_full, value);
423                                 strlcpy(attr->value_local, &pos[1], sizeof(attr->value_local));
424                                 attr->value = attr->value_local;
425                         }
426                 }
427                 goto out;
428         }
429
430         /* skip directories */
431         if (S_ISDIR(statbuf.st_mode))
432                 goto out;
433
434         /* skip non-readable files */
435         if ((statbuf.st_mode & S_IRUSR) == 0)
436                 goto out;
437
438         /* read attribute value */
439         fd = open(path_full, O_RDONLY);
440         if (fd < 0) {
441                 dbg("attribute '%s' does not exist", path_full);
442                 goto out;
443         }
444         size = read(fd, value, sizeof(value));
445         close(fd);
446         if (size < 0)
447                 goto out;
448         if (size == sizeof(value))
449                 goto out;
450
451         /* got a valid value, store and return it */
452         value[size] = '\0';
453         remove_trailing_chars(value, '\n');
454         dbg("cache '%s' with attribute value '%s'", path_full, value);
455         strlcpy(attr->value_local, value, sizeof(attr->value_local));
456         attr->value = attr->value_local;
457
458 out:
459         return attr->value;
460 }