chiark / gitweb /
af2cf7bb815372c2da530ffe3be5eff2b6fb9eac
[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         time_t config_time;
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_time = udev->config_time;
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         return 0;
226 }
227
228 static int get_all_attributes(char *path)
229 {
230         struct sysfs_class_device *class_dev;
231         struct sysfs_class_device *class_dev_parent;
232         struct sysfs_attribute *attr;
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         /* read the 'dev' file for major/minor*/
250         attr = sysfs_get_classdev_attr(class_dev, "dev");
251         if (attr == NULL) {
252                 dbg("couldn't get the \"dev\" file");
253                 retval = -1;
254                 goto exit;
255         }
256
257         sysfs_close_attribute(attr);
258
259         /* open sysfs class device directory and get all attributes */
260         if (add_all_attributes(class_dev->path, level) != 0) {
261                 dbg("couldn't open class device directory");
262                 retval = -1;
263                 goto exit;
264         }
265         level++;
266
267         /* get the device link (if parent exists look here) */
268         class_dev_parent = sysfs_get_classdev_parent(class_dev);
269         if (class_dev_parent != NULL) {
270                 //sysfs_close_class_device(class_dev);
271                 class_dev = class_dev_parent;
272         }
273         sysfs_dev = sysfs_get_classdev_device(class_dev);
274
275         /* look the device chain upwards */
276         while (sysfs_dev != NULL) {
277                 if (sysfs_dev->bus[0] != '\0') {
278                         add_attribute("", level);
279                         sprintf(key, "BUS=\"%s\"", sysfs_dev->bus);
280                         add_attribute(key, level);
281                         sprintf(key, "ID=\"%s\"", sysfs_dev->bus_id);
282                         add_attribute(key, level);
283
284                         /* open sysfs device directory and print all attributes */
285                         add_all_attributes(sysfs_dev->path, level);
286                 }
287                 level++;
288
289                 sysfs_dev_parent = sysfs_get_device_parent(sysfs_dev);
290                 if (sysfs_dev_parent == NULL)
291                         break;
292
293                 //sysfs_close_device(sysfs_dev);
294                 sysfs_dev = sysfs_dev_parent;
295         }
296         sysfs_close_device(sysfs_dev);
297
298 exit:
299         //sysfs_close_class_device(class_dev);
300         return retval;
301 }
302
303
304 int main(int argc, char *argv[]) {
305         newtComponent lbox, run, lattr;
306         newtComponent quit, form, answer;
307         newtGrid grid, grid2;
308         char roottext[81];
309         char path[NAME_SIZE];
310         struct device *dev;
311         time_t time_last;
312         int count_last;
313
314         newtInit();
315         newtCls();
316
317         newtWinMessage("udevruler", "Ok",
318                        "This program lets you select a device currently present "
319                        "on the system and you may choose the attributes to uniquely "
320                        "name the device with a udev rule.\n"
321                        "No configuration will be changed, it just prints the rule "
322                        "to place in a udev.rules configuration file. The \"%k\" in the "
323                        "NAME key of the printed rule may be replaced by the name the "
324                        "node should have.");
325
326         init_logging("udevruler");
327         udev_init_config();
328         get_all_devices();
329
330         lbox = newtListbox(2, 1, 10, NEWT_FLAG_SCROLL | NEWT_FLAG_RETURNEXIT);
331
332         /* look for last discovered device */
333         time_last = 0;
334         list_for_each_entry(dev, &device_list, list)
335                 if (dev->config_time > time_last)
336                         time_last = dev->config_time;
337
338         /* skip if more than 16 recent devices */
339         count_last = 0;
340         list_for_each_entry(dev, &device_list, list) {
341                 if (dev->config_time < time_last - 10)
342                         continue;
343                 count_last++;
344         }
345
346         /* add devices up to 10 seconds older than the last one */
347         if (count_last < 16) {
348                 newtListboxAppendEntry(lbox, "--- last dicovered ---", NULL);
349                 list_for_each_entry(dev, &device_list, list) {
350                         if (dev->config_time < time_last - 10)
351                                 continue;
352
353                         dbg("%s %i", dev->name, dev->config_line);
354                         newtListboxAppendEntry(lbox, dev->name, (void*) dev);
355                         dev->added = 1;
356                 }
357                 newtListboxAppendEntry(lbox, "", NULL);
358         }
359
360         /* add devices not catched by a rule */
361         newtListboxAppendEntry(lbox, "--- not configured by a rule ---", NULL);
362         list_for_each_entry(dev, &device_list, list) {
363                 if (dev->added)
364                         continue;
365
366                 if (dev->config_line != 0)
367                         continue;
368
369                 dbg("%s %i", dev->name, dev->config_line);
370                 newtListboxAppendEntry(lbox, dev->name, (void*) dev);
371                 dev->added = 1;
372         }
373         newtListboxAppendEntry(lbox, "", NULL);
374
375         /* add remaining devices */
376         newtListboxAppendEntry(lbox, "--- configured by a rule ---", NULL);
377         list_for_each_entry(dev, &device_list, list) {
378                 if (dev->added)
379                         continue;
380
381                 dbg("%s %i", dev->name, dev->config_line);
382                 newtListboxAppendEntry(lbox, dev->name, (void*) dev);
383         }
384
385         newtPushHelpLine("    <Tab>/<Alt-Tab> between elements  |    Use <Enter> to select a device");
386         snprintf(roottext, sizeof(roottext), "simple udev rule composer, version %s, (c) 2004 can't sleep team", UDEV_VERSION);
387         roottext[sizeof(roottext)-1] = '\0';
388         newtDrawRootText(0, 0, roottext);
389
390         form = newtForm(NULL, NULL, 0);
391         grid = newtCreateGrid(1, 2);
392         grid2 = newtButtonBar("Select device", &run, "Quit", &quit, NULL);
393         newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, lbox, 1, 0, 1, 0, NEWT_ANCHOR_TOP, 0);
394         newtGridSetField(grid, 0, 1, NEWT_GRID_SUBGRID, grid2, 0, 1, 0, 0, NEWT_ANCHOR_TOP, 0);
395         newtFormAddComponents(form, lbox, run, quit, NULL);
396         newtGridWrappedWindow(grid,"Choose the device for which to compose a rule");
397         newtGridFree(grid, 1);
398
399         while (1) {
400                 struct attribute *attr;
401                 newtComponent ok, back, form2, answer2, text;
402                 newtGrid grid3, grid4;
403                 int i;
404                 int numitems;
405                 struct attribute **selattr;
406                 char text_rule[80];
407
408                 answer = newtRunForm(form);
409                 if (answer == quit)
410                         break;
411
412                 dev = (struct device *) newtListboxGetCurrent(lbox);
413                 if (dev == NULL)
414                         continue;
415
416                 if (dev->config_line > 0)
417                         snprintf(text_rule, sizeof(text_rule),
418                                  "The device is handled by a rule in the file '%s', line %i.",
419                                  dev->config_file, dev->config_line);
420                 else
421                         strcpy(text_rule, "The device was not handled by a rule.");
422
423                 strfieldcpy(path, sysfs_path);
424                 strfieldcat(path, dev->devpath);
425                 dbg("look at sysfs device '%s'", path);
426                 get_all_attributes(path);
427
428                 grid3 = newtCreateGrid(1, 3);
429                 form2 = newtForm(NULL, NULL, 0);
430                 grid4 = newtButtonBar("Ok", &ok, "Back", &back, NULL);
431
432                 lattr = newtListbox(-1, -1, 10, NEWT_FLAG_MULTIPLE | NEWT_FLAG_BORDER | NEWT_FLAG_RETURNEXIT);
433                 list_for_each_entry(attr, &attribute_list, list)
434                         newtListboxAddEntry(lattr, attr->key, (void *) attr);
435
436                 text = newtTextbox(-1, -1, 50, 2, NEWT_FLAG_WRAP);
437                 newtTextboxSetText(text, text_rule);
438
439                 newtGridSetField(grid3, 0, 0, NEWT_GRID_COMPONENT, lattr, 0, 0, 0, 1, 0, 0);
440                 newtGridSetField(grid3, 0, 1, NEWT_GRID_COMPONENT, text, 0, 0, 0, 1, 0, 0);
441                 newtGridSetField(grid3, 0, 2, NEWT_GRID_SUBGRID, grid4, 0, 1, 0, 0, NEWT_ANCHOR_TOP, 0);
442
443                 newtFormAddComponents(form2, text, lattr, ok, back, NULL);
444                 newtGridWrappedWindow(grid3, "Select one ore more attributes within one section with the space bar");
445                 newtGridFree(grid3, 1);
446
447                 while (1) {
448                         char rule[255];
449                         int onelevel;
450                         int skipped;
451
452                         answer2 = newtRunForm(form2);
453                         if (answer2 == back)
454                                 break;
455
456                         selattr = (struct attribute **) newtListboxGetSelection(lattr, &numitems);
457                         if (selattr == NULL)
458                                 continue;
459
460                         rule[0] = '\0';
461                         onelevel = -1;
462                         skipped = 0;
463                         for (i = 0; i < numitems; i++) {
464                                 if (selattr[i]->key[0] == '\0')
465                                         continue;
466
467                                 if (onelevel != -1) {
468                                         if (onelevel != selattr[i]->level) {
469                                                 skipped = 1;
470                                                 continue;
471                                         }
472                                 } else {
473                                         onelevel = selattr[i]->level;
474                                 }
475
476                                 dbg("'%s'\n", selattr[i]->key);
477                                 strfieldcat(rule, selattr[i]->key);
478                                 strfieldcat(rule, ", ");
479                         }
480                         if (skipped) {
481                                 newtWinMessage("error", "Ok", "Please select only attributes within one section");
482                                 continue;
483                         }
484
485                         if (strlen(rule) > 200) {
486                                 newtWinMessage("error", "Ok", "The line is too long, please select fewer attributes.");
487                         } else {
488                                 if (rule[0] == '\0')
489                                         continue;
490
491                                 strfieldcat(rule, "NAME=\"%k\"");
492                                 newtWinMessage("the rule to place in config file", "Ok", rule);
493                         }
494                 }
495
496                 newtPopWindow();
497         }
498
499         newtPopWindow();
500         newtFormDestroy(form);
501         newtFinished();
502         return 0;
503 }