2bc605c44c511c889ccae33d77b6d028a57aa98e
[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 #include "debug.h"
37
38 char sysfs_path[PATH_SIZE];
39
40 /* list of sysfs devices */
41 static LIST_HEAD(sysfs_dev_list);
42 struct sysfs_dev {
43         struct list_head node;
44         struct sysfs_device dev;
45 };
46
47 int sysfs_init(char *path, size_t len)
48 {
49         if (path) {
50                 strlcpy(sysfs_path, path, len);
51                 remove_trailing_chars(sysfs_path, '/');
52         } else
53                 strlcpy(sysfs_path, "/sys", sizeof(sysfs_path));
54         dbg("sysfs_path='%s'", sysfs_path);
55
56         INIT_LIST_HEAD(&sysfs_dev_list);
57         return 0;
58 }
59
60 void sysfs_cleanup(void)
61 {
62         struct sysfs_dev *sysdev_loop;
63         struct sysfs_dev *sysdev_temp;
64
65         list_for_each_entry_safe(sysdev_loop, sysdev_temp, &sysfs_dev_list, node) {
66                 list_del(&sysdev_loop->node);
67                 free(sysdev_loop);
68         }
69 }
70
71 void sysfs_device_set_values(struct sysfs_device *dev, const char *devpath,
72                              const char *subsystem, const char *driver)
73 {
74         char *pos;
75
76         strlcpy(dev->devpath, devpath, sizeof(dev->devpath));
77         if (subsystem != NULL)
78                 strlcpy(dev->subsystem, subsystem, sizeof(dev->subsystem));
79         if (driver != NULL)
80                 strlcpy(dev->driver, driver, sizeof(dev->driver));
81
82         /* set kernel name */
83         pos = strrchr(dev->devpath, '/');
84         if (pos == NULL)
85                 return;
86         strlcpy(dev->kernel, &pos[1], sizeof(dev->kernel));
87         dbg("kernel='%s'", dev->kernel);
88
89         /* some devices have '!' in their name, change that to '/' */
90         pos = dev->kernel;
91         while (pos[0] != '\0') {
92                 if (pos[0] == '!')
93                         pos[0] = '/';
94                 pos++;
95         }
96
97         /* get kernel number */
98         pos = &dev->kernel[strlen(dev->kernel)];
99         while (isdigit(pos[-1]))
100                 pos--;
101         strlcpy(dev->kernel_number, pos, sizeof(dev->kernel_number));
102         dbg("kernel_number='%s'", dev->kernel_number);
103 }
104
105 int sysfs_resolve_link(char *devpath, size_t size)
106 {
107         char link_path[PATH_SIZE];
108         char link_target[PATH_SIZE];
109         int len;
110         int i;
111         int back;
112
113         strlcpy(link_path, sysfs_path, sizeof(link_path));
114         strlcat(link_path, devpath, sizeof(link_path));
115         len = readlink(link_path, link_target, sizeof(link_target));
116         if (len <= 0)
117                 return -1;
118         link_target[len] = '\0';
119         dbg("path link '%s' points to '%s'", devpath, link_target);
120
121         for (back = 0; strncmp(&link_target[back * 3], "../", 3) == 0; back++)
122                 ;
123         dbg("base '%s', tail '%s', back %i", devpath, &link_target[back * 3], back);
124         for (i = 0; i <= back; i++) {
125                 char *pos = strrchr(devpath, '/');
126
127                 if (pos == NULL)
128                         return -1;
129                 pos[0] = '\0';
130         }
131         dbg("after moving back '%s'", devpath);
132         strlcat(devpath, "/", size);
133         strlcat(devpath, &link_target[back * 3], size);
134         return 0;
135 }
136
137 struct sysfs_device *sysfs_device_get(const char *devpath)
138 {
139         char path[PATH_SIZE];
140         char devpath_real[PATH_SIZE];
141         struct sysfs_device *dev = NULL;
142         struct sysfs_dev *sysdev_loop, *sysdev;
143         struct stat statbuf;
144         char link_path[PATH_SIZE];
145         char link_target[PATH_SIZE];
146         int len;
147         char *pos;
148
149         /* we handle only these devpathes */
150         if (devpath != NULL &&
151             strncmp(devpath, "/devices/", 9) != 0 &&
152             strncmp(devpath, "/subsystem/", 11) != 0 &&
153             strncmp(devpath, "/module/", 8) != 0 &&
154             strncmp(devpath, "/bus/", 5) != 0 &&
155             strncmp(devpath, "/class/", 7) != 0 &&
156             strncmp(devpath, "/block/", 7) != 0) {
157                 dbg("invalid devpath '%s'", devpath);
158                 return NULL;
159         }
160
161         dbg("open '%s'", devpath);
162         strlcpy(devpath_real, devpath, sizeof(devpath_real));
163         remove_trailing_chars(devpath_real, '/');
164         if (devpath[0] == '\0' )
165                 return NULL;
166
167         /* if we got a link, resolve it to the real device */
168         strlcpy(path, sysfs_path, sizeof(path));
169         strlcat(path, devpath_real, sizeof(path));
170         if (lstat(path, &statbuf) != 0) {
171                 /* if stat fails look in the cache */
172                 dbg("stat '%s' failed: %s", path, strerror(errno));
173                 list_for_each_entry(sysdev_loop, &sysfs_dev_list, node) {
174                         if (strcmp(sysdev_loop->dev.devpath, devpath_real) == 0) {
175                                 dbg("found vanished dev in cache '%s'",
176                                     sysdev_loop->dev.devpath);
177                                 return &sysdev_loop->dev;
178                         }
179                 }
180                 return NULL;
181         }
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         list_for_each_entry(sysdev_loop, &sysfs_dev_list, node) {
189                 if (strcmp(sysdev_loop->dev.devpath, devpath_real) == 0) {
190                         dbg("found dev in cache '%s'", sysdev_loop->dev.devpath);
191                         dev = &sysdev_loop->dev;
192                 }
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 (strstr(dev->devpath, "/drivers/") != NULL) {
221                 strlcpy(dev->subsystem, "drivers", sizeof(dev->subsystem));
222         } else if (strncmp(dev->devpath, "/module/", 8) == 0) {
223                 strlcpy(dev->subsystem, "module", sizeof(dev->subsystem));
224         } else if (strncmp(dev->devpath, "/subsystem/", 11) == 0) {
225                 pos = strrchr(dev->devpath, '/');
226                 if (pos == &dev->devpath[10])
227                         strlcpy(dev->subsystem, "subsystem",
228                                 sizeof(dev->subsystem));
229         } else if (strncmp(dev->devpath, "/class/", 7) == 0) {
230                 pos = strrchr(dev->devpath, '/');
231                 if (pos == &dev->devpath[6])
232                         strlcpy(dev->subsystem, "subsystem",
233                                 sizeof(dev->subsystem));
234         } else if (strncmp(dev->devpath, "/bus/", 5) == 0) {
235                 pos = strrchr(dev->devpath, '/');
236                 if (pos == &dev->devpath[4])
237                         strlcpy(dev->subsystem, "subsystem",
238                                 sizeof(dev->subsystem));
239         }
240
241         /* get driver name */
242         strlcpy(link_path, sysfs_path, sizeof(link_path));
243         strlcat(link_path, dev->devpath, sizeof(link_path));
244         strlcat(link_path, "/driver", sizeof(link_path));
245         len = readlink(link_path, link_target, sizeof(link_target));
246         if (len > 0) {
247                 link_target[len] = '\0';
248                 dbg("driver link '%s' points to '%s'", link_path, link_target);
249                 pos = strrchr(link_target, '/');
250                 if (pos != NULL)
251                         strlcpy(dev->driver, &pos[1], sizeof(dev->driver));
252         }
253
254         return dev;
255 }
256
257 struct sysfs_device *sysfs_device_get_parent(struct sysfs_device *dev)
258 {
259         char parent_devpath[PATH_SIZE];
260         char *pos;
261
262         dbg("open '%s'", dev->devpath);
263
264         /* look if we already know the parent */
265         if (dev->parent != NULL)
266                 return dev->parent;
267
268         strlcpy(parent_devpath, dev->devpath, sizeof(parent_devpath));
269         dbg("'%s'", parent_devpath);
270
271         /* strip last element */
272         pos = strrchr(parent_devpath, '/');
273         if (pos == NULL || pos == parent_devpath)
274                 return NULL;
275         pos[0] = '\0';
276
277         if (strncmp(parent_devpath, "/class", 6) == 0) {
278                 pos = strrchr(parent_devpath, '/');
279                 if (pos == &parent_devpath[6] || pos == parent_devpath) {
280                         dbg("/class top level, look for device link");
281                         goto device_link;
282                 }
283         }
284         if (strcmp(parent_devpath, "/block") == 0) {
285                 dbg("/block top level, look for device link");
286                 goto device_link;
287         }
288
289         /* are we at the top level? */
290         pos = strrchr(parent_devpath, '/');
291         if (pos == NULL || pos == parent_devpath)
292                 return NULL;
293
294         /* get parent and remember it */
295         dev->parent = sysfs_device_get(parent_devpath);
296         return dev->parent;
297
298 device_link:
299         strlcpy(parent_devpath, dev->devpath, sizeof(parent_devpath));
300         strlcat(parent_devpath, "/device", sizeof(parent_devpath));
301         if (sysfs_resolve_link(parent_devpath, sizeof(parent_devpath)) != 0)
302                 return NULL;
303
304         /* get parent and remember it */
305         dev->parent = sysfs_device_get(parent_devpath);
306         return dev->parent;
307 }
308
309 struct sysfs_device *sysfs_device_get_parent_with_subsystem(struct sysfs_device *dev, const char *subsystem)
310 {
311         struct sysfs_device *dev_parent;
312
313         dev_parent = sysfs_device_get_parent(dev);
314         while (dev_parent != NULL) {
315                 if (strcmp(dev_parent->subsystem, subsystem) == 0)
316                         return dev_parent;
317                 dev_parent = sysfs_device_get_parent(dev_parent);
318         }
319         return NULL;
320 }
321
322 void sysfs_device_put(struct sysfs_device *dev)
323 {
324         struct sysfs_dev *sysdev_loop;
325
326         list_for_each_entry(sysdev_loop, &sysfs_dev_list, node) {
327                 if (&sysdev_loop->dev == dev) {
328                         dbg("removed dev '%s' from cache",
329                             sysdev_loop->dev.devpath);
330                         list_del(&sysdev_loop->node);
331                         free(sysdev_loop);
332                         return;
333                 }
334         }
335         dbg("dev '%s' not found in cache",
336             sysdev_loop->dev.devpath);
337
338         return;
339 }
340
341 size_t sysfs_attr_get_value(const char *devpath, const char *attr_name,
342                             char *attr_value, int attr_len)
343 {
344         char path_full[PATH_SIZE];
345         const char *path;
346         struct stat statbuf;
347         int fd;
348         ssize_t size;
349         size_t sysfs_len;
350
351         if (!attr_value || !attr_len)
352                 return 0;
353
354         attr_value[0] = '\0';
355         size = 0;
356
357         dbg("open '%s'/'%s'", devpath, attr_name);
358         sysfs_len = strlcpy(path_full, sysfs_path, sizeof(path_full));
359         if(sysfs_len >= sizeof(path_full))
360                 sysfs_len = sizeof(path_full) - 1;
361         path = &path_full[sysfs_len];
362         strlcat(path_full, devpath, sizeof(path_full));
363         strlcat(path_full, "/", sizeof(path_full));
364         strlcat(path_full, attr_name, sizeof(path_full));
365
366         if (stat(path_full, &statbuf) != 0) {
367                 dbg("stat '%s' failed: %s", path_full, strerror(errno));
368                 goto out;
369         }
370
371         /* skip directories */
372         if (S_ISDIR(statbuf.st_mode))
373                 goto out;
374
375         /* skip non-readable files */
376         if ((statbuf.st_mode & S_IRUSR) == 0)
377                 goto out;
378
379         /* read attribute value */
380         fd = open(path_full, O_RDONLY);
381         if (fd < 0) {
382                 dbg("attribute '%s' can not be opened: %s",
383                     path_full, strerror(errno));
384                 goto out;
385         }
386         size = read(fd, attr_value, attr_len);
387         close(fd);
388         if (size < 0)
389                 goto out;
390         if (size == attr_len) {
391                 dbg("overflow in attribute '%s', truncating", path_full);
392                 size--;
393         }
394
395         /* got a valid value, store and return it */
396         attr_value[size] = '\0';
397         remove_trailing_chars(attr_value, '\n');
398
399 out:
400         return size;
401 }
402
403 ssize_t sysfs_attr_set_value(const char *devpath, const char *attr_name,
404                              const char *value, int value_len)
405 {
406         char path_full[PATH_SIZE];
407         struct stat statbuf;
408         int fd;
409         ssize_t size = 0;
410         size_t sysfs_len;
411
412         if (!attr_name || !value || !value_len)
413                 return 0;
414
415         dbg("open '%s'/'%s'", devpath, attr_name);
416         sysfs_len = snprintf(path_full, PATH_SIZE, "%s%s/%s", sysfs_path,
417                              devpath, attr_name);
418         if (sysfs_len >= PATH_SIZE || sysfs_len < 0) {
419                 if (sysfs_len < 0)
420                         dbg("cannot copy sysfs path %s%s/%s : %s", sysfs_path,
421                             devpath, attr_name, strerror(errno));
422                 else
423                         dbg("sysfs_path %s%s/%s too large", sysfs_path,
424                             devpath, attr_name);
425                 goto out;
426         }
427
428         if (stat(path_full, &statbuf) != 0) {
429                 dbg("stat '%s' failed: %s", path_full, strerror(errno));
430                 goto out;
431         }
432
433         /* skip directories */
434         if (S_ISDIR(statbuf.st_mode))
435                 goto out;
436
437         /* skip non-writeable files */
438         if ((statbuf.st_mode & S_IWUSR) == 0)
439                 goto out;
440
441         /* read attribute value */
442         fd = open(path_full, O_WRONLY);
443         if (fd < 0) {
444                 dbg("attribute '%s' can not be opened: %s",
445                     path_full, strerror(errno));
446                 goto out;
447         }
448         size = write(fd, value, value_len);
449         if (size < 0)
450                 dbg("write to %s failed: %s", path_full, strerror(errno));
451         else if (size < value_len) {
452                 dbg("tried to write %d to %s. Wrote %d\n", value_len,
453                     path_full, size);
454                 size = -1;
455         }
456         close(fd);
457
458 out:
459         return size;
460 }