chiark / gitweb /
[PATCH] add symlink for video rule.
[elogind.git] / udevruler.c
1 /*
2  * udevruler.c - simple udev-rule composer
3  *
4  * Reads the udev-db to get all currently known devices and
5  * scans the sysfs device chain for the choosen device to select attributes
6  * to compose a rule for the udev.rules file to uniquely name this device.
7  *
8  * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
9  *
10  *      This program is free software; you can redistribute it and/or modify it
11  *      under the terms of the GNU General Public License as published by the
12  *      Free Software Foundation version 2 of the License.
13  * 
14  *      This program is distributed in the hope that it will be useful, but
15  *      WITHOUT ANY WARRANTY; without even the implied warranty of
16  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  *      General Public License for more details.
18  * 
19  *      You should have received a copy of the GNU General Public License along
20  *      with this program; if not, write to the Free Software Foundation, Inc.,
21  *      675 Mass Ave, Cambridge, MA 02139, USA.
22  *
23  */
24
25 #include <newt.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <stddef.h>
29 #include <ctype.h>
30 #include <time.h>
31 #include <unistd.h>
32
33 #include "udev.h"
34 #include "udev_lib.h"
35 #include "udev_version.h"
36 #include "udevdb.h"
37 #include "logging.h"
38 #include "libsysfs/sysfs.h"
39 #include "list.h"
40
41 #ifdef LOG
42 unsigned char logname[LOGNAME_SIZE];
43 void log_message(int level, const char *format, ...)
44 {
45         va_list args;
46
47         if (!udev_log)
48                 return;
49
50         va_start(args, format);
51         vsyslog(level, format, args);
52         va_end(args);
53 }
54 #endif
55
56 static char *dev_blacklist[] = {
57         "tty",
58         "pty",
59         "zero",
60         "null",
61         "kmsg",
62         "rtc",
63         "timer",
64         "full",
65         "kmem",
66         "mem",
67         "random",
68         "urandom",
69         "console",
70         "port",
71         ""
72 };
73
74 struct device {
75         struct list_head list;
76         char name[NAME_SIZE];
77         char devpath[DEVPATH_SIZE];
78         int config_line;
79         char config_file[NAME_SIZE];
80         long config_uptime;
81         int added;
82 };
83
84 static LIST_HEAD(device_list);
85 static int device_count;
86
87 /* callback for database dump */
88 static int add_record(char *path, struct udevice *udev)
89 {
90         struct device *dev;
91         struct device *loop_dev;
92         int i = 0;
93
94         while (dev_blacklist[i][0] != '\0') {
95                 if (strncmp(udev->name, dev_blacklist[i], strlen(dev_blacklist[i])) == 0)
96                         goto exit;
97                 i++;
98         }
99
100         dev = malloc(sizeof(struct device));
101         if (dev == NULL) {
102                 printf("error malloc\n");
103                 exit(2);
104         }
105         strfieldcpy(dev->name, udev->name);
106         strfieldcpy(dev->devpath, path);
107         dev->config_line = udev->config_line;
108         strfieldcpy(dev->config_file, udev->config_file);
109         dev->config_uptime = udev->config_uptime;
110         dev->added = 0;
111
112         /* sort in lexical order */
113         list_for_each_entry(loop_dev, &device_list, list) {
114                 if (strcmp(loop_dev->name, dev->name) > 0) {
115                         break;
116                 }
117         }
118
119         list_add_tail(&dev->list, &loop_dev->list);
120         device_count++;
121
122 exit:
123         return 0;
124 }
125
126 /* get all devices from udev database */
127 static int get_all_devices(void)
128 {
129         int retval;
130
131         device_count = 0;
132         INIT_LIST_HEAD(&device_list);
133
134         retval = udevdb_open_ro();
135         if (retval != 0) {
136                 printf("unable to open udev database\n");
137                 exit(1);
138         }
139
140         udevdb_call_foreach(add_record);
141         udevdb_exit();
142
143         return 0;
144 }
145
146 struct attribute {
147         struct list_head list;
148         int level;
149         char key[NAME_SIZE];
150 };
151
152 static LIST_HEAD(attribute_list);
153 static int attribute_count;
154
155 static int add_attribute(const char *key, int level)
156 {
157         struct attribute *attr;
158
159         dbg("add attribute '%s'", key);
160         attr = malloc(sizeof(struct attribute));
161         if (attr == NULL) {
162                 printf("error malloc\n");
163                 exit(2);
164         }
165
166         strfieldcpy(attr->key, key);
167         attr->level = level;
168         list_add_tail(&attr->list, &attribute_list);
169         attribute_count++;
170         return 0;
171 }
172
173 static int add_all_attributes(const char *path, int level)
174 {
175         struct dlist *attributes;
176         struct sysfs_attribute *attr;
177         struct sysfs_directory *sysfs_dir;
178         char value[NAME_SIZE];
179         char key[NAME_SIZE];
180         int len;
181         int retval = 0;
182
183         dbg("look at '%s', level %i", path, level);
184
185         sysfs_dir = sysfs_open_directory(path);
186         if (sysfs_dir == NULL)
187                 return -1;
188
189         attributes = sysfs_get_dir_attributes(sysfs_dir);
190         if (attributes == NULL) {
191                 retval = -1;
192                 return 0;
193         }
194
195         dlist_for_each_data(attributes, attr, struct sysfs_attribute)
196                 if (attr->value != NULL) {
197                         dbg("found attribute '%s'", attr->name);
198                         strfieldcpy(value, attr->value);
199                         len = strlen(value);
200                         if (len == 0)
201                                 continue;
202
203                         /* skip very long attributes */
204                         if (len > 40)
205                                 continue;
206
207                         /* remove trailing newline */
208                         if (value[len-1] == '\n') {
209                                 value[len-1] = '\0';
210                                 len--;
211                         }
212
213                         /* skip nonprintable values */
214                         while (len) {
215                                 if (!isprint(value[len-1]))
216                                         break;
217                                 len--;
218                         }
219                         if (len == 0) {
220                                 sprintf(key, "SYSFS{%s}=\"%s\"", attr->name, value);
221                                 add_attribute(key, level);
222                         }
223                 }
224
225         sysfs_close_directory(sysfs_dir);
226         return 0;
227 }
228
229 static int get_all_attributes(char *path)
230 {
231         struct sysfs_class_device *class_dev;
232         struct sysfs_class_device *class_dev_parent;
233         struct sysfs_device *sysfs_dev;
234         struct sysfs_device *sysfs_dev_parent;
235         char key[NAME_SIZE];
236         int retval = 0;
237         int level = 0;
238
239         attribute_count = 0;
240         INIT_LIST_HEAD(&attribute_list);
241
242         /*  get the class dev */
243         class_dev = sysfs_open_class_device_path(path);
244         if (class_dev == NULL) {
245                 dbg("couldn't get the class device");
246                 return -1;
247         }
248
249         /* open sysfs class device directory and get all attributes */
250         if (add_all_attributes(class_dev->path, level) != 0) {
251                 dbg("couldn't open class device directory");
252                 retval = -1;
253                 goto exit;
254         }
255         level++;
256
257         /* get the device link (if parent exists look here) */
258         class_dev_parent = sysfs_get_classdev_parent(class_dev);
259         if (class_dev_parent != NULL)
260                 sysfs_dev = sysfs_get_classdev_device(class_dev_parent);
261         else
262                 sysfs_dev = sysfs_get_classdev_device(class_dev);
263
264         /* look the device chain upwards */
265         while (sysfs_dev != NULL) {
266                 if (sysfs_dev->bus[0] != '\0') {
267                         add_attribute("", level);
268                         sprintf(key, "BUS=\"%s\"", sysfs_dev->bus);
269                         add_attribute(key, level);
270                         sprintf(key, "ID=\"%s\"", sysfs_dev->bus_id);
271                         add_attribute(key, level);
272
273                         /* open sysfs device directory and print all attributes */
274                         add_all_attributes(sysfs_dev->path, level);
275                 }
276                 level++;
277
278                 sysfs_dev_parent = sysfs_get_device_parent(sysfs_dev);
279                 if (sysfs_dev_parent == NULL)
280                         break;
281
282                 sysfs_dev = sysfs_dev_parent;
283         }
284
285 exit:
286         sysfs_close_class_device(class_dev);
287         return retval;
288 }
289
290
291 int main(int argc, char *argv[]) {
292         newtComponent lbox, run, lattr;
293         newtComponent quit, form, answer;
294         newtGrid grid, grid2;
295         char roottext[81];
296         char path[NAME_SIZE];
297         struct device *dev;
298         long time_last;
299         int count_last;
300
301         newtInit();
302         newtCls();
303
304         newtWinMessage("udevruler", "Ok",
305                        "This program lets you select a device currently present "
306                        "on the system and you may choose the attributes to uniquely "
307                        "name the device with a udev rule.\n"
308                        "No configuration will be changed, it just prints the rule "
309                        "to place in a udev.rules configuration file. The \"%k\" in the "
310                        "NAME key of the printed rule may be replaced by the name the "
311                        "node should have.");
312
313         init_logging("udevruler");
314         udev_init_config();
315         get_all_devices();
316
317         lbox = newtListbox(2, 1, 10, NEWT_FLAG_SCROLL | NEWT_FLAG_RETURNEXIT);
318
319         /* look for last discovered device */
320         time_last = 0;
321         list_for_each_entry(dev, &device_list, list)
322                 if (dev->config_uptime > time_last)
323                         time_last = dev->config_uptime;
324
325         /* skip if more than 16 recent devices */
326         count_last = 0;
327         list_for_each_entry(dev, &device_list, list) {
328                 if (dev->config_uptime < time_last - 10)
329                         continue;
330                 count_last++;
331         }
332
333         /* add devices up to 10 seconds older than the last one */
334         if (count_last < 16) {
335                 newtListboxAppendEntry(lbox, "--- last dicovered ---", NULL);
336                 list_for_each_entry(dev, &device_list, list) {
337                         if (dev->config_uptime < time_last - 10)
338                                 continue;
339
340                         dbg("%s %i", dev->name, dev->config_line);
341                         newtListboxAppendEntry(lbox, dev->name, (void*) dev);
342                         dev->added = 1;
343                 }
344                 newtListboxAppendEntry(lbox, "", NULL);
345         }
346
347         /* add devices not catched by a rule */
348         newtListboxAppendEntry(lbox, "--- not configured by a rule ---", NULL);
349         list_for_each_entry(dev, &device_list, list) {
350                 if (dev->added)
351                         continue;
352
353                 if (dev->config_line != 0)
354                         continue;
355
356                 dbg("%s %i", dev->name, dev->config_line);
357                 newtListboxAppendEntry(lbox, dev->name, (void*) dev);
358                 dev->added = 1;
359         }
360         newtListboxAppendEntry(lbox, "", NULL);
361
362         /* add remaining devices */
363         newtListboxAppendEntry(lbox, "--- configured by a rule ---", NULL);
364         list_for_each_entry(dev, &device_list, list) {
365                 if (dev->added)
366                         continue;
367
368                 dbg("%s %i", dev->name, dev->config_line);
369                 newtListboxAppendEntry(lbox, dev->name, (void*) dev);
370         }
371
372         newtPushHelpLine("    <Tab>/<Alt-Tab> between elements  |    Use <Enter> to select a device");
373         snprintf(roottext, sizeof(roottext), "simple udev rule composer, version %s, (c) 2004 can't sleep team", UDEV_VERSION);
374         roottext[sizeof(roottext)-1] = '\0';
375         newtDrawRootText(0, 0, roottext);
376
377         form = newtForm(NULL, NULL, 0);
378         grid = newtCreateGrid(1, 2);
379         grid2 = newtButtonBar("Select device", &run, "Quit", &quit, NULL);
380         newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, lbox, 1, 0, 1, 0, NEWT_ANCHOR_TOP, 0);
381         newtGridSetField(grid, 0, 1, NEWT_GRID_SUBGRID, grid2, 0, 1, 0, 0, NEWT_ANCHOR_TOP, 0);
382         newtFormAddComponents(form, lbox, run, quit, NULL);
383         newtGridWrappedWindow(grid,"Choose the device for which to compose a rule");
384         newtGridFree(grid, 1);
385
386         while (1) {
387                 struct attribute *attr;
388                 newtComponent ok, back, form2, answer2, text;
389                 newtGrid grid3, grid4;
390                 int i;
391                 int numitems;
392                 struct attribute **selattr;
393                 char text_rule[255];
394
395                 answer = newtRunForm(form);
396                 if (answer == quit)
397                         break;
398
399                 dev = (struct device *) newtListboxGetCurrent(lbox);
400                 if (dev == NULL)
401                         continue;
402
403                 if (dev->config_line > 0)
404                         snprintf(text_rule, sizeof(text_rule),
405                                  "The device is handled by a rule in the file '%s', line %i.",
406                                  dev->config_file, dev->config_line);
407                 else
408                         strcpy(text_rule, "The device was not handled by a rule.");
409
410                 strfieldcpy(path, sysfs_path);
411                 strfieldcat(path, dev->devpath);
412                 dbg("look at sysfs device '%s'", path);
413                 get_all_attributes(path);
414
415                 grid3 = newtCreateGrid(1, 3);
416                 form2 = newtForm(NULL, NULL, 0);
417                 grid4 = newtButtonBar("Ok", &ok, "Back", &back, NULL);
418
419                 lattr = newtListbox(-1, -1, 10, NEWT_FLAG_MULTIPLE | NEWT_FLAG_BORDER | NEWT_FLAG_RETURNEXIT);
420                 list_for_each_entry(attr, &attribute_list, list)
421                         newtListboxAddEntry(lattr, attr->key, (void *) attr);
422
423                 text = newtTextbox(-1, -1, 50, 2, NEWT_FLAG_WRAP);
424                 newtTextboxSetText(text, text_rule);
425
426                 newtGridSetField(grid3, 0, 0, NEWT_GRID_COMPONENT, lattr, 0, 0, 0, 1, 0, 0);
427                 newtGridSetField(grid3, 0, 1, NEWT_GRID_COMPONENT, text, 0, 0, 0, 1, 0, 0);
428                 newtGridSetField(grid3, 0, 2, NEWT_GRID_SUBGRID, grid4, 0, 1, 0, 0, NEWT_ANCHOR_TOP, 0);
429
430                 newtFormAddComponents(form2, text, lattr, ok, back, NULL);
431                 newtGridWrappedWindow(grid3, "Select one ore more attributes within one section with the space bar");
432                 newtGridFree(grid3, 1);
433
434                 while (1) {
435                         char rule[255];
436                         int onelevel;
437                         int skipped;
438
439                         answer2 = newtRunForm(form2);
440                         if (answer2 == back)
441                                 break;
442
443                         selattr = (struct attribute **) newtListboxGetSelection(lattr, &numitems);
444                         if (selattr == NULL)
445                                 continue;
446
447                         rule[0] = '\0';
448                         onelevel = -1;
449                         skipped = 0;
450                         for (i = 0; i < numitems; i++) {
451                                 if (selattr[i]->key[0] == '\0')
452                                         continue;
453
454                                 if (onelevel != -1) {
455                                         if (onelevel != selattr[i]->level) {
456                                                 skipped = 1;
457                                                 continue;
458                                         }
459                                 } else {
460                                         onelevel = selattr[i]->level;
461                                 }
462
463                                 dbg("'%s'\n", selattr[i]->key);
464                                 strfieldcat(rule, selattr[i]->key);
465                                 strfieldcat(rule, ", ");
466                         }
467                         if (skipped) {
468                                 newtWinMessage("error", "Ok", "Please select only attributes within one section");
469                                 continue;
470                         }
471
472                         if (strlen(rule) > 200) {
473                                 newtWinMessage("error", "Ok", "The line is too long, please select fewer attributes.");
474                         } else {
475                                 if (rule[0] == '\0')
476                                         continue;
477
478                                 strfieldcat(rule, "NAME=\"%k\"");
479                                 newtWinMessage("the rule to place in config file", "Ok", rule);
480                         }
481                 }
482
483                 newtPopWindow();
484         }
485
486         newtPopWindow();
487         newtFormDestroy(form);
488         newtFinished();
489         return 0;
490 }