chiark / gitweb /
[PATCH] klibc specific tweaks
[elogind.git] / libsysfs / sysfs_device.c
1 /*
2  * sysfs_device.c
3  *
4  * Generic device utility functions for libsysfs
5  *
6  * Copyright (C) IBM Corp. 2003
7  *
8  *  This library is free software; you can redistribute it and/or
9  *  modify it under the terms of the GNU Lesser General Public
10  *  License as published by the Free Software Foundation; either
11  *  version 2.1 of the License, or (at your option) any later version.
12  *
13  *  This library is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  *  Lesser General Public License for more details.
17  *
18  *  You should have received a copy of the GNU Lesser General Public
19  *  License along with this library; if not, write to the Free Software
20  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21  *
22  */
23 #include "libsysfs.h"
24 #include "sysfs.h"
25
26 /**
27  * sysfs_close_device_tree: closes every device in the supplied tree, 
28  *      closing children only.
29  * @devroot: device root of tree.
30  */
31 static void sysfs_close_device_tree(struct sysfs_device *devroot)
32 {
33         if (devroot != NULL) {
34                 if (devroot->children != NULL) {
35                         struct sysfs_device *child = NULL;
36
37                         dlist_for_each_data(devroot->children, child,
38                                         struct sysfs_device) {
39                                 sysfs_close_device_tree(child);
40                         }
41                 }
42                 sysfs_close_device(devroot);
43         }
44 }
45
46 /**
47  * sysfs_del_device: routine for dlist integration
48  */
49 static void sysfs_del_device(void *dev)
50 {
51         sysfs_close_device((struct sysfs_device *)dev);
52 }
53
54 /**
55  * sysfs_close_dev_tree: routine for dlist integration
56  */
57 static void sysfs_close_dev_tree(void *dev)
58 {
59         sysfs_close_device_tree((struct sysfs_device *)dev);
60 }
61
62 /**
63  * sysfs_close_device: closes and cleans up a device
64  * @dev = device to clean up
65  */
66 void sysfs_close_device(struct sysfs_device *dev)
67 {
68         if (dev != NULL) {
69                 if (dev->directory != NULL)
70                         sysfs_close_directory(dev->directory);
71                 if (dev->children != NULL && dev->children->count == 0)
72                         dlist_destroy(dev->children);
73                 free(dev);
74         }
75 }
76
77 /**
78  * alloc_device: allocates and initializes device structure
79  * returns struct sysfs_device
80  */
81 static struct sysfs_device *alloc_device(void)
82 {
83         return (struct sysfs_device *)calloc(1, sizeof(struct sysfs_device));
84 }
85
86 /**
87  * sysfs_get_device_attr: searches dev's attributes by name
88  * @dev: device to look through
89  * @name: attribute name to get
90  * returns sysfs_attribute reference with success or NULL with error.
91  */
92 struct sysfs_attribute *sysfs_get_device_attr(struct sysfs_device *dev,
93                                                 const unsigned char *name)
94 {
95         struct sysfs_attribute *cur = NULL;
96
97         if (dev == NULL || dev->directory == NULL 
98             || dev->directory->attributes == NULL || name == NULL) {
99                 errno = EINVAL;
100                 return NULL;
101         }
102         
103         cur = sysfs_get_directory_attribute(dev->directory, 
104                         (unsigned char *)name);
105         if (cur != NULL)
106                 return cur;
107
108         return NULL;
109 }
110
111 /**
112  * sysfs_open_device: opens and populates device structure
113  * @path: path to device, this is the /sys/devices/ path
114  * returns sysfs_device structure with success or NULL with error
115  */
116 struct sysfs_device *sysfs_open_device(const unsigned char *path)
117 {
118         struct sysfs_device *dev = NULL;
119         struct sysfs_directory *sdir = NULL;
120
121         if (path == NULL) {
122                 errno = EINVAL;
123                 return NULL;
124         }
125         dev = alloc_device();   
126         if (dev == NULL) {
127                 dprintf("Error allocating device at %s\n", path);
128                 return NULL;
129         }
130         sdir = sysfs_open_directory(path);
131         if (sdir == NULL) {
132                 dprintf("Invalid device at %s\n", path);
133                 errno = EINVAL;
134                 sysfs_close_device(dev);
135                 return NULL;
136         }
137         if ((sysfs_read_directory(sdir)) != 0) {
138                 dprintf("Error reading device directory at %s\n", path);
139                 sysfs_close_directory(sdir);
140                 sysfs_close_device(dev);
141                 return NULL;
142         }
143         dev->directory = sdir;
144         strcpy(dev->bus_id, sdir->name);
145         strcpy(dev->path, sdir->path);
146
147         /* 
148          * The "name" attribute no longer exists... return the device's
149          * sysfs representation instead, in the "dev->name" field, which
150          * implies that the dev->name and dev->bus_id contain same data.
151          */
152         strncpy(dev->name, sdir->name, SYSFS_NAME_LEN);
153
154         return dev;
155 }
156
157 /**
158  * sysfs_open_device_tree: opens root device and all of its children,
159  *      creating a tree of devices. Only opens children.
160  * @path: sysfs path to devices
161  * returns struct sysfs_device and its children with success or NULL with
162  *      error.
163  */
164 static struct sysfs_device *sysfs_open_device_tree(const unsigned char *path)
165 {
166         struct sysfs_device *rootdev = NULL, *new = NULL;
167         struct sysfs_directory *cur = NULL;
168
169         if (path == NULL) {
170                 errno = EINVAL;
171                 return NULL;
172         }
173         rootdev = sysfs_open_device(path);
174         if (rootdev == NULL) {
175                 dprintf("Error opening root device at %s\n", path);
176                 return NULL;
177         }
178         if (rootdev->directory->subdirs != NULL) {
179                 dlist_for_each_data(rootdev->directory->subdirs, cur,
180                                 struct sysfs_directory) {
181                         new = sysfs_open_device_tree(cur->path);
182                         if (new == NULL) {
183                                 dprintf("Error opening device tree at %s\n",
184                                         cur->path);
185                                 sysfs_close_device_tree(rootdev);
186                                 return NULL;
187                         }
188                         if (rootdev->children == NULL)
189                                 rootdev->children = dlist_new_with_delete
190                                         (sizeof(struct sysfs_device),
191                                         sysfs_del_device);
192                         dlist_unshift(rootdev->children, new);
193                 }
194         }
195
196         return rootdev;
197 }
198
199 /**
200  * sysfs_close_root_device: closes root and all devices
201  * @root: root device to close
202  */
203 void sysfs_close_root_device(struct sysfs_root_device *root)
204 {
205         if (root != NULL) {
206                 if (root->devices != NULL)
207                         dlist_destroy(root->devices);
208                 if (root->directory != NULL)
209                         sysfs_close_directory(root->directory);
210                 free(root);
211         }
212 }
213
214 /**
215  * open_root_device_dir: opens up sysfs_directory for specific root dev
216  * @name: name of root
217  * returns struct sysfs_directory with success and NULL with error
218  */
219 static struct sysfs_directory *open_root_device_dir(const unsigned char *name)
220 {
221         struct sysfs_directory *rdir = NULL;
222         unsigned char rootpath[SYSFS_PATH_MAX];
223
224         if (name == NULL) {
225                 errno = EINVAL;
226                 return NULL;
227         }
228
229         memset(rootpath, 0, SYSFS_PATH_MAX);
230         if (sysfs_get_mnt_path(rootpath, SYSFS_PATH_MAX) != 0) {
231                 dprintf ("Sysfs not supported on this system\n");
232                 return NULL;
233         }
234
235         strcat(rootpath, SYSFS_DEVICES_DIR);
236         strcat(rootpath, "/");
237         strcat(rootpath, name);
238         rdir = sysfs_open_directory(rootpath);
239         if (rdir == NULL) {
240                 errno = EINVAL;
241                 dprintf ("Root device %s not supported on this system\n",
242                         name);
243                 return NULL;
244         }
245         if (sysfs_read_directory(rdir) != 0) {
246                 dprintf ("Error reading %s root device at dir %s\n", name,
247                         rootpath);
248                 sysfs_close_directory(rdir);
249                 return NULL;
250         }
251         
252         return rdir;
253 }
254
255 /**
256  * get_all_root_devices: opens up all the devices under this root device
257  * @root: root device to open devices for
258  * returns 0 with success and -1 with error
259  */
260 static int get_all_root_devices(struct sysfs_root_device *root)
261 {
262         struct sysfs_device *dev = NULL;
263         struct sysfs_directory *cur = NULL;
264
265         if (root == NULL || root->directory == NULL) {
266                 errno = EINVAL;
267                 return -1;
268         }
269         if (root->directory->subdirs == NULL)
270                 return 0;
271
272         dlist_for_each_data(root->directory->subdirs, cur,
273                         struct sysfs_directory) {
274                 dev = sysfs_open_device_tree(cur->path);
275                 if (dev == NULL) {
276                         dprintf ("Error opening device at %s\n", cur->path);
277                         continue;
278                 }
279                 if (root->devices == NULL)
280                         root->devices = dlist_new_with_delete
281                                 (sizeof(struct sysfs_device), 
282                                 sysfs_close_dev_tree);
283                 dlist_unshift(root->devices, dev);
284         }
285
286         return 0;
287 }
288
289 /**
290  * sysfs_open_root_device: opens sysfs devices root and all of its
291  *      devices.
292  * @name: name of /sys/devices/root to open
293  * returns struct sysfs_root_device if success and NULL with error
294  */
295 struct sysfs_root_device *sysfs_open_root_device(const unsigned char *name)
296 {
297         struct sysfs_root_device *root = NULL;
298         struct sysfs_directory *rootdir = NULL;
299
300         if (name == NULL) {
301                 errno = EINVAL;
302                 return NULL;
303         }
304
305         root = (struct sysfs_root_device *)calloc
306                                         (1, sizeof(struct sysfs_root_device));
307         if (root == NULL) {
308                 dprintf("calloc failure\n");
309                 return NULL;
310         }
311         rootdir = open_root_device_dir(name);
312         if (rootdir == NULL) {
313                 dprintf ("Invalid root device, %s not supported\n", name);
314                 sysfs_close_root_device(root);
315                 return NULL;
316         }
317         strcpy(root->path, rootdir->path);
318         root->directory = rootdir;
319         if (get_all_root_devices(root) != 0) {
320                 dprintf ("Error retrieving devices for root %s\n", name);
321                 sysfs_close_root_device(root);
322                 return NULL;
323         }
324
325         return root;
326 }
327
328 /**
329  * sysfs_get_device_attributes: returns a dlist of attributes corresponding to
330  *      the specific device
331  * @device: struct sysfs_device * for which attributes are to be returned
332  */
333 struct dlist *sysfs_get_device_attributes(struct sysfs_device *device)
334 {
335         if (device == NULL || device->directory == NULL) 
336                 return NULL;
337
338         return (device->directory->attributes);
339 }
340
341 /**
342  * sysfs_open_device_by_id: open a device by id (use the "bus" subsystem)
343  * @bus_id: bus_id of the device to open - has to be the "bus_id" in 
344  *              /sys/bus/xxx/devices
345  * @bus: bus the device belongs to
346  * @bsize: size of the bus buffer
347  * returns struct sysfs_device if found, NULL otherwise
348  * NOTE: 
349  * 1. Use sysfs_close_device to close the device
350  * 2. Bus the device is on must be supplied
351  *      Use sysfs_find_device_bus to get the bus name
352  */
353 struct sysfs_device *sysfs_open_device_by_id(const unsigned char *bus_id, 
354                 const unsigned char *bus, size_t bsize)
355 {
356         char sysfs_path[SYSFS_PATH_MAX], device_path[SYSFS_PATH_MAX];
357         struct sysfs_device *device = NULL;
358
359         if (bus_id == NULL || bus == NULL) {
360                 errno = EINVAL;
361                 return NULL;
362         }
363         memset(sysfs_path, 0, SYSFS_PATH_MAX);
364         if ((sysfs_get_mnt_path(sysfs_path, SYSFS_PATH_MAX)) != 0) {
365                 dprintf("Error getting sysfs mount path\n");
366                 return NULL;
367         }
368         strcat(sysfs_path, SYSFS_BUS_DIR);
369         strcat(sysfs_path, "/");
370         strncat(sysfs_path, bus, bsize);
371         strcat(sysfs_path, SYSFS_DEVICES_DIR);
372         strcat(sysfs_path, "/");
373         strcat(sysfs_path, bus_id);
374
375         /* devices under /sys/bus/xxx/devices are links to devices subsystem */
376         if ((sysfs_get_link(sysfs_path, device_path, SYSFS_PATH_MAX)) < 0) {
377                 dprintf("Error getting device path\n");
378                 return NULL;
379         }
380         
381         device = sysfs_open_device(device_path);
382         if (device == NULL) {
383                 dprintf("Error opening device %s\n", bus_id);
384                 return NULL;
385         }
386
387         return device;
388 }
389
390 /**
391  * get_device_absolute_path: looks up the bus the device is on, gets 
392  *              absolute path to the device
393  * @device: device for which path is needed
394  * @path: buffer to store absolute path
395  * @psize: size of "path"
396  * Returns 0 on success -1 on failure
397  */
398 static int get_device_absolute_path(const unsigned char *device, 
399                                         unsigned char *path, size_t psize)
400 {
401         unsigned char bus_name[SYSFS_NAME_LEN], bus_path[SYSFS_PATH_MAX];
402
403         if (device == NULL || path == NULL) {
404                 errno = EINVAL;
405                 return -1;
406         }
407
408         memset(bus_name, 0, SYSFS_NAME_LEN);
409         memset(bus_path, 0, SYSFS_NAME_LEN);
410         if ((sysfs_find_device_bus(device, bus_name, SYSFS_NAME_LEN)) != 0) {
411                 dprintf("Device %s not found\n", device);
412                 return -1;
413         }
414         if (sysfs_get_mnt_path(bus_path, SYSFS_PATH_MAX) != 0) {
415                 dprintf ("Sysfs not supported on this system\n");
416                 return -1;
417         }
418         strcat(bus_path, SYSFS_BUS_DIR);
419         strcat(bus_path, "/");
420         strcat(bus_path, bus_name);
421         strcat(bus_path, SYSFS_DEVICES_DIR);
422         strcat(bus_path, "/");
423         strcat(bus_path, device);
424         /*
425          * We now are at /sys/bus/"bus_name"/devices/"device" which is a link.
426          * Now read this link to reach to the device.
427          */ 
428         if ((sysfs_get_link(bus_path, path, SYSFS_PATH_MAX)) != 0) {
429                 dprintf("Error getting to device %s\n", device);
430                 return -1;
431         }
432         return 0;
433 }
434
435 /**
436  * sysfs_write_device_attr: modify a "writable" attribute for the given device
437  * @dev: device bus_id for which attribute has to be changed
438  * @attrib: attribute to change
439  * @value: value to change to
440  * @len: "value" length to write
441  * Returns 0 on success -1 on error
442  */ 
443 int sysfs_write_device_attr(unsigned char *dev, unsigned char *attrib,
444                                         unsigned char *value, size_t len)
445 {
446         struct sysfs_attribute *attribute = NULL;
447         unsigned char devpath[SYSFS_PATH_MAX];
448
449         if (dev == NULL || attrib == NULL || value == NULL) {
450                 errno = EINVAL;
451                 return -1;
452         }
453         
454         memset(devpath, 0, SYSFS_PATH_MAX);
455         if ((get_device_absolute_path(dev, devpath, SYSFS_PATH_MAX)) != 0) {
456                 dprintf("Error finding absolute path to device %s\n", dev);
457                 return -1;
458         }
459         strcat(devpath, "/");
460         strcat(devpath, attrib);
461         attribute = sysfs_open_attribute(devpath);
462         if (attribute == NULL) {
463                 dprintf("Attribute %s could not be retrieved for device %s\n",
464                                 attrib, dev);
465                 return -1;
466         }
467         if (attribute->method & SYSFS_METHOD_SHOW) {
468                 if ((sysfs_read_attribute(attribute)) != 0) {
469                         dprintf("Error reading attribute %s for device %s\n",
470                                         attrib, dev);
471                         sysfs_close_attribute(attribute);
472                         return -1;
473                 }
474         }
475         if ((sysfs_write_attribute(attribute, value, len)) < 0) {
476                 dprintf("Error setting %s to %s\n", attrib, value);
477                 sysfs_close_attribute(attribute);
478                 return -1;
479         }
480         sysfs_close_attribute(attribute);
481         return 0;
482 }
483
484 /**
485  * sysfs_read_device_attr: read an attribute of the given device
486  * @dev: device bus_id for which attribute has to be changed
487  * @attrib: attribute to read
488  * @value: buffer to return value in
489  * @len: size of buffer available
490  * Returns 0 on success -1 on error
491  */ 
492 int sysfs_read_device_attr(unsigned char *dev, unsigned char *attrib,
493                                         unsigned char *value, size_t len)
494 {
495         struct sysfs_attribute *attribute = NULL;
496         unsigned char devpath[SYSFS_PATH_MAX];
497
498         if (dev == NULL || attrib == NULL || value == NULL) {
499                 errno = EINVAL;
500                 return -1;
501         }
502         
503         memset(devpath, 0, SYSFS_PATH_MAX);
504         if ((get_device_absolute_path(dev, devpath, SYSFS_PATH_MAX)) != 0) {
505                 dprintf("Error finding absolute path to device %s\n", dev);
506                 return -1;
507         }
508         strcat(devpath, "/");
509         strcat(devpath, attrib);
510         attribute = sysfs_open_attribute(devpath);
511         if (attribute == NULL) {
512                 dprintf("Error opening attribute %s for device %s\n",
513                                 attrib, dev);
514                 return -1;
515         }
516         if (!(attribute->method & SYSFS_METHOD_SHOW)) {
517                 dprintf("Show method not supported for attribute %s\n",
518                                 attrib);
519                 sysfs_close_attribute(attribute);
520                 return -1;
521         }
522         if ((sysfs_read_attribute(attribute)) != 0) {
523                 dprintf("Error reading attribute %s for device %s\n",
524                                 attrib, dev);
525                 sysfs_close_attribute(attribute);
526                 return -1;
527         }
528         if (attribute->len > len) {
529                 dprintf("Value length %d is larger than supplied buffer %d\n",
530                                 attribute->len, len);
531                 sysfs_close_attribute(attribute);
532                 return -1;
533         }
534         strncpy(value, attribute->value, attribute->len);
535         value[(attribute->len)+1] = 0;
536         sysfs_close_attribute(attribute);
537         return 0;
538 }