libmultipath: rework sysfs handling
[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 {
73         char *pos;
74
75         strlcpy(dev->devpath, devpath, sizeof(dev->devpath));
76
77         /* set kernel name */
78         pos = strrchr(dev->devpath, '/');
79         if (pos == NULL)
80                 return;
81         strlcpy(dev->kernel, &pos[1], sizeof(dev->kernel));
82         dbg("kernel='%s'", dev->kernel);
83
84         /* some devices have '!' in their name, change that to '/' */
85         pos = dev->kernel;
86         while (pos[0] != '\0') {
87                 if (pos[0] == '!')
88                         pos[0] = '/';
89                 pos++;
90         }
91 }
92
93 int sysfs_resolve_link(char *devpath, size_t size)
94 {
95         char link_path[PATH_SIZE];
96         char link_target[PATH_SIZE];
97         int len;
98         int i;
99         int back;
100
101         strlcpy(link_path, sysfs_path, sizeof(link_path));
102         strlcat(link_path, devpath, sizeof(link_path));
103         len = readlink(link_path, link_target, sizeof(link_target));
104         if (len <= 0)
105                 return -1;
106         link_target[len] = '\0';
107         dbg("path link '%s' points to '%s'", devpath, link_target);
108
109         for (back = 0; strncmp(&link_target[back * 3], "../", 3) == 0; back++)
110                 ;
111         dbg("base '%s', tail '%s', back %i", devpath, &link_target[back * 3], back);
112         for (i = 0; i <= back; i++) {
113                 char *pos = strrchr(devpath, '/');
114
115                 if (pos == NULL)
116                         return -1;
117                 pos[0] = '\0';
118         }
119         dbg("after moving back '%s'", devpath);
120         strlcat(devpath, "/", size);
121         strlcat(devpath, &link_target[back * 3], size);
122         return 0;
123 }
124
125 /*
126  * Caution: this routine is called extremely often.
127  * Should be as efficient as possible.
128  */
129 struct sysfs_device *sysfs_device_get(const char *devpath)
130 {
131         char path[PATH_SIZE];
132         char devpath_real[PATH_SIZE];
133         struct sysfs_device *dev = NULL;
134         struct sysfs_dev *sysdev_loop, *sysdev;
135         struct stat statbuf;
136
137         dbg("open '%s'", devpath);
138         strlcpy(devpath_real, devpath, sizeof(devpath_real));
139         remove_trailing_chars(devpath_real, '/');
140         if (devpath[0] == '\0' )
141                 return NULL;
142
143         /* if we got a link, resolve it to the real device */
144         strlcpy(path, sysfs_path, sizeof(path));
145         strlcat(path, devpath_real, sizeof(path));
146         if (lstat(path, &statbuf) != 0) {
147                 /* if stat fails look in the cache */
148                 dbg("stat '%s' failed: %s", path, strerror(errno));
149                 list_for_each_entry(sysdev_loop, &sysfs_dev_list, node) {
150                         if (strcmp(sysdev_loop->dev.devpath, devpath_real) == 0) {
151                                 dbg("found vanished dev in cache '%s'",
152                                     sysdev_loop->dev.devpath);
153                                 return &sysdev_loop->dev;
154                         }
155                 }
156                 return NULL;
157         }
158
159         if (S_ISLNK(statbuf.st_mode)) {
160                 if (sysfs_resolve_link(devpath_real, sizeof(devpath_real)) != 0)
161                         return NULL;
162         }
163
164         list_for_each_entry(sysdev_loop, &sysfs_dev_list, node) {
165                 if (strcmp(sysdev_loop->dev.devpath, devpath_real) == 0) {
166                         dbg("found dev in cache '%s'", sysdev_loop->dev.devpath);
167                         dev = &sysdev_loop->dev;
168                 }
169         }
170
171         if(!dev) {
172                 /* it is a new device */
173                 dbg("new device '%s'", devpath_real);
174                 sysdev = malloc(sizeof(struct sysfs_dev));
175                 if (sysdev == NULL)
176                         return NULL;
177                 memset(sysdev, 0x00, sizeof(struct sysfs_dev));
178                 list_add(&sysdev->node, &sysfs_dev_list);
179                 dev = &sysdev->dev;
180         }
181
182         sysfs_device_set_values(dev, devpath_real);
183
184         return dev;
185 }
186
187 struct sysfs_device *sysfs_device_get_parent(struct sysfs_device *dev)
188 {
189         char parent_devpath[PATH_SIZE];
190         char *pos;
191
192         dbg("open '%s'", dev->devpath);
193
194         /* look if we already know the parent */
195         if (dev->parent != NULL)
196                 return dev->parent;
197
198         strlcpy(parent_devpath, dev->devpath, sizeof(parent_devpath));
199         dbg("'%s'", parent_devpath);
200
201         /* strip last element */
202         pos = strrchr(parent_devpath, '/');
203         if (pos == NULL || pos == parent_devpath)
204                 return NULL;
205         pos[0] = '\0';
206
207         if (strncmp(parent_devpath, "/class", 6) == 0) {
208                 pos = strrchr(parent_devpath, '/');
209                 if (pos == &parent_devpath[6] || pos == parent_devpath) {
210                         dbg("/class top level, look for device link");
211                         goto device_link;
212                 }
213         }
214         if (strcmp(parent_devpath, "/block") == 0) {
215                 dbg("/block top level, look for device link");
216                 goto device_link;
217         }
218
219         /* are we at the top level? */
220         pos = strrchr(parent_devpath, '/');
221         if (pos == NULL || pos == parent_devpath)
222                 return NULL;
223
224         /* get parent and remember it */
225         dev->parent = sysfs_device_get(parent_devpath);
226         return dev->parent;
227
228 device_link:
229         strlcpy(parent_devpath, dev->devpath, sizeof(parent_devpath));
230         strlcat(parent_devpath, "/device", sizeof(parent_devpath));
231         if (sysfs_resolve_link(parent_devpath, sizeof(parent_devpath)) != 0)
232                 return NULL;
233
234         /* get parent and remember it */
235         dev->parent = sysfs_device_get(parent_devpath);
236         return dev->parent;
237 }
238
239 struct sysfs_device *sysfs_device_verify(struct sysfs_device *dev)
240 {
241         char path[PATH_SIZE];
242         struct stat statbuf;
243
244         strlcpy(path, sysfs_path, sizeof(path));
245         strlcat(path, dev->devpath, sizeof(path));
246         if (stat(dev->devpath, &statbuf) == 0 &&
247             S_ISDIR(statbuf.st_mode))
248                 return dev;
249
250         return NULL;
251 }
252
253 void sysfs_device_put(struct sysfs_device *dev)
254 {
255         struct sysfs_dev *sysdev_loop;
256
257         list_for_each_entry(sysdev_loop, &sysfs_dev_list, node) {
258                 if (&sysdev_loop->dev == dev) {
259                         dbg("removed dev '%s' from cache",
260                             sysdev_loop->dev.devpath);
261                         list_del(&sysdev_loop->node);
262                         free(sysdev_loop);
263                         return;
264                 }
265         }
266         dbg("dev '%s' not found in cache",
267             sysdev_loop->dev.devpath);
268
269         return;
270 }
271
272 size_t sysfs_attr_get_value(const char *devpath, const char *attr_name,
273                             char *attr_value, int attr_len)
274 {
275         char path_full[PATH_SIZE];
276         const char *path;
277         struct stat statbuf;
278         int fd;
279         ssize_t size;
280         size_t sysfs_len;
281
282         if (!attr_value || !attr_len)
283                 return 0;
284
285         attr_value[0] = '\0';
286         size = 0;
287
288         dbg("open '%s'/'%s'", devpath, attr_name);
289         sysfs_len = strlcpy(path_full, sysfs_path, sizeof(path_full));
290         if(sysfs_len >= sizeof(path_full))
291                 sysfs_len = sizeof(path_full) - 1;
292         path = &path_full[sysfs_len];
293         strlcat(path_full, devpath, sizeof(path_full));
294         strlcat(path_full, "/", sizeof(path_full));
295         strlcat(path_full, attr_name, sizeof(path_full));
296
297         if (stat(path_full, &statbuf) != 0) {
298                 dbg("stat '%s' failed: %s", path_full, strerror(errno));
299                 goto out;
300         }
301
302         /* skip directories */
303         if (S_ISDIR(statbuf.st_mode))
304                 goto out;
305
306         /* skip non-readable files */
307         if ((statbuf.st_mode & S_IRUSR) == 0)
308                 goto out;
309
310         /* read attribute value */
311         fd = open(path_full, O_RDONLY);
312         if (fd < 0) {
313                 dbg("attribute '%s' can not be opened: %s",
314                     path_full, strerror(errno));
315                 goto out;
316         }
317         size = read(fd, attr_value, attr_len);
318         close(fd);
319         if (size < 0)
320                 goto out;
321         if (size == attr_len) {
322                 dbg("overflow in attribute '%s', truncating", path_full);
323                 size--;
324         }
325
326         /* got a valid value, store and return it */
327         attr_value[size] = '\0';
328         remove_trailing_chars(attr_value, '\n');
329
330 out:
331         return size;
332 }
333
334 ssize_t sysfs_attr_set_value(const char *devpath, const char *attr_name,
335                              const char *value, int value_len)
336 {
337         char path_full[PATH_SIZE];
338         struct stat statbuf;
339         int fd;
340         ssize_t size = 0;
341         size_t sysfs_len;
342
343         if (!attr_name || !value || !value_len)
344                 return 0;
345
346         dbg("open '%s'/'%s'", devpath, attr_name);
347         sysfs_len = snprintf(path_full, PATH_SIZE, "%s%s/%s", sysfs_path,
348                              devpath, attr_name);
349         if (sysfs_len >= PATH_SIZE || sysfs_len < 0) {
350                 if (sysfs_len < 0)
351                         dbg("cannot copy sysfs path %s%s/%s : %s", sysfs_path,
352                             devpath, attr_name, strerror(errno));
353                 else
354                         dbg("sysfs_path %s%s/%s too large", sysfs_path,
355                             devpath, attr_name);
356                 goto out;
357         }
358
359         if (stat(path_full, &statbuf) != 0) {
360                 dbg("stat '%s' failed: %s", path_full, strerror(errno));
361                 goto out;
362         }
363
364         /* skip directories */
365         if (S_ISDIR(statbuf.st_mode))
366                 goto out;
367
368         /* skip non-writeable files */
369         if ((statbuf.st_mode & S_IWUSR) == 0)
370                 goto out;
371
372         /* write attribute value */
373         fd = open(path_full, O_WRONLY);
374         if (fd < 0) {
375                 dbg("attribute '%s' can not be opened: %s",
376                     path_full, strerror(errno));
377                 goto out;
378         }
379         size = write(fd, value, value_len);
380         if (size < 0)
381                 dbg("write to %s failed: %s", path_full, strerror(errno));
382         else if (size < value_len) {
383                 dbg("tried to write %d to %s. Wrote %d\n", value_len,
384                     path_full, size);
385                 size = -1;
386         }
387
388         close(fd);
389 out:
390
391         return size;
392 }