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