]>
Commit | Line | Data |
---|---|---|
b608ade8 KS |
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]; | |
bbbe503e | 80 | long config_uptime; |
b608ade8 KS |
81 | int added; |
82 | }; | |
83 | ||
4a539daf KS |
84 | static LIST_HEAD(device_list); |
85 | static int device_count; | |
b608ade8 KS |
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); | |
bbbe503e | 109 | dev->config_uptime = udev->config_uptime; |
b608ade8 KS |
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 | ||
4a539daf KS |
152 | static LIST_HEAD(attribute_list); |
153 | static int attribute_count; | |
b608ade8 KS |
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 | ||
dd785ba6 | 225 | sysfs_close_directory(sysfs_dir); |
b608ade8 KS |
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; | |
b608ade8 KS |
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 | ||
b608ade8 KS |
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); | |
dd785ba6 KS |
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); | |
b608ade8 KS |
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 | ||
b608ade8 KS |
282 | sysfs_dev = sysfs_dev_parent; |
283 | } | |
b608ade8 KS |
284 | |
285 | exit: | |
dd785ba6 | 286 | sysfs_close_class_device(class_dev); |
b608ade8 KS |
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; | |
bbbe503e | 298 | long time_last; |
b608ade8 KS |
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) | |
bbbe503e KS |
322 | if (dev->config_uptime > time_last) |
323 | time_last = dev->config_uptime; | |
b608ade8 KS |
324 | |
325 | /* skip if more than 16 recent devices */ | |
326 | count_last = 0; | |
327 | list_for_each_entry(dev, &device_list, list) { | |
bbbe503e | 328 | if (dev->config_uptime < time_last - 10) |
b608ade8 KS |
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) { | |
bbbe503e | 337 | if (dev->config_uptime < time_last - 10) |
b608ade8 KS |
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; | |
dd785ba6 | 393 | char text_rule[255]; |
b608ade8 KS |
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 | } |