]>
Commit | Line | Data |
---|---|---|
0d5be398 | 1 | /* |
0d5be398 KS |
2 | * Copyright (C) 2004-2006 Kay Sievers <kay@vrfy.org> |
3 | * Copyright (C) 2006 Hannes Reinecke <hare@suse.de> | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify it | |
6 | * under the terms of the GNU General Public License as published by the | |
7 | * Free Software Foundation version 2 of the License. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, but | |
10 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | * General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License along | |
15 | * with this program; if not, write to the Free Software Foundation, Inc., | |
27b77df4 | 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
0d5be398 KS |
17 | * |
18 | */ | |
19 | ||
20 | #include <stdlib.h> | |
21 | #include <stddef.h> | |
22 | #include <string.h> | |
23 | #include <stdio.h> | |
24 | #include <unistd.h> | |
fc89fe7e | 25 | #include <getopt.h> |
0d5be398 KS |
26 | #include <errno.h> |
27 | #include <dirent.h> | |
28 | #include <fcntl.h> | |
29 | #include <syslog.h> | |
fc89fe7e | 30 | #include <fnmatch.h> |
0d5be398 KS |
31 | #include <sys/stat.h> |
32 | #include <sys/types.h> | |
33 | ||
34 | #include "udev.h" | |
04cc3a81 | 35 | #include "udevd.h" |
0d5be398 | 36 | |
c48622cc KS |
37 | static int verbose; |
38 | static int dry_run; | |
39 | LIST_HEAD(device_list); | |
40 | LIST_HEAD(filter_subsystem_match_list); | |
41 | LIST_HEAD(filter_subsystem_nomatch_list); | |
42 | LIST_HEAD(filter_attr_match_list); | |
43 | LIST_HEAD(filter_attr_nomatch_list); | |
44 | ||
0d5be398 KS |
45 | #ifdef USE_LOG |
46 | void log_message(int priority, const char *format, ...) | |
47 | { | |
48 | va_list args; | |
49 | ||
50 | if (priority > udev_log_priority) | |
51 | return; | |
52 | ||
53 | va_start(args, format); | |
54 | vsyslog(priority, format, args); | |
55 | va_end(args); | |
56 | } | |
57 | #endif | |
58 | ||
c48622cc KS |
59 | /* devices that should run last cause of their dependencies */ |
60 | static int delay_device(const char *devpath) | |
61 | { | |
62 | static const char *delay_device_list[] = { | |
63 | "*/md*", | |
64 | "*/dm-*", | |
65 | NULL | |
66 | }; | |
67 | int i; | |
0d5be398 | 68 | |
c48622cc KS |
69 | for (i = 0; delay_device_list[i] != NULL; i++) |
70 | if (fnmatch(delay_device_list[i], devpath, 0) == 0) | |
71 | return 1; | |
72 | return 0; | |
73 | } | |
fc89fe7e | 74 | |
0d5be398 KS |
75 | static int device_list_insert(const char *path) |
76 | { | |
c48622cc KS |
77 | char filename[PATH_SIZE]; |
78 | char devpath[PATH_SIZE]; | |
79 | struct stat statbuf; | |
0d5be398 KS |
80 | |
81 | dbg("add '%s'" , path); | |
c48622cc KS |
82 | |
83 | /* we only have a device, if we have an uevent file */ | |
84 | strlcpy(filename, path, sizeof(filename)); | |
85 | strlcat(filename, "/uevent", sizeof(filename)); | |
86 | if (stat(filename, &statbuf) < 0) | |
87 | return -1; | |
88 | if (!(statbuf.st_mode & S_IWUSR)) | |
89 | return -1; | |
90 | ||
91 | strlcpy(devpath, &path[strlen(sysfs_path)], sizeof(devpath)); | |
92 | ||
93 | /* resolve possible link to real target */ | |
94 | if (lstat(path, &statbuf) < 0) | |
95 | return -1; | |
96 | if (S_ISLNK(statbuf.st_mode)) | |
97 | if (sysfs_resolve_link(devpath, sizeof(devpath)) != 0) | |
98 | return -1; | |
99 | ||
100 | name_list_add(&device_list, devpath, 1); | |
0d5be398 KS |
101 | return 0; |
102 | } | |
103 | ||
c48622cc | 104 | static void trigger_uevent(const char *devpath) |
0d5be398 KS |
105 | { |
106 | char filename[PATH_SIZE]; | |
107 | int fd; | |
108 | ||
c48622cc KS |
109 | strlcpy(filename, sysfs_path, sizeof(filename)); |
110 | strlcat(filename, devpath, sizeof(filename)); | |
0d5be398 KS |
111 | strlcat(filename, "/uevent", sizeof(filename)); |
112 | ||
113 | if (verbose) | |
c48622cc | 114 | printf("%s\n", devpath); |
0d5be398 KS |
115 | |
116 | if (dry_run) | |
117 | return; | |
118 | ||
119 | fd = open(filename, O_WRONLY); | |
120 | if (fd < 0) { | |
121 | dbg("error on opening %s: %s\n", filename, strerror(errno)); | |
122 | return; | |
123 | } | |
124 | ||
125 | if (write(fd, "add", 3) < 0) | |
126 | info("error on triggering %s: %s\n", filename, strerror(errno)); | |
127 | ||
128 | close(fd); | |
129 | } | |
130 | ||
c48622cc | 131 | static void exec_list(void) |
0d5be398 KS |
132 | { |
133 | struct name_entry *loop_device; | |
134 | struct name_entry *tmp_device; | |
135 | ||
c48622cc KS |
136 | list_for_each_entry_safe(loop_device, tmp_device, &device_list, node) { |
137 | if (delay_device(loop_device->name)) | |
138 | continue; | |
0d5be398 | 139 | |
0d5be398 KS |
140 | trigger_uevent(loop_device->name); |
141 | list_del(&loop_device->node); | |
142 | free(loop_device); | |
143 | } | |
144 | ||
c48622cc KS |
145 | /* trigger remaining delayed devices */ |
146 | list_for_each_entry_safe(loop_device, tmp_device, &device_list, node) { | |
0d5be398 KS |
147 | trigger_uevent(loop_device->name); |
148 | list_del(&loop_device->node); | |
149 | free(loop_device); | |
150 | } | |
151 | } | |
152 | ||
fc89fe7e KS |
153 | static int subsystem_filtered(const char *subsystem) |
154 | { | |
155 | struct name_entry *loop_name; | |
156 | ||
157 | /* skip devices matching the listed subsystems */ | |
f14326ad | 158 | list_for_each_entry(loop_name, &filter_subsystem_nomatch_list, node) |
7c27c752 | 159 | if (fnmatch(loop_name->name, subsystem, 0) == 0) |
fc89fe7e KS |
160 | return 1; |
161 | ||
162 | /* skip devices not matching the listed subsystems */ | |
f14326ad SV |
163 | if (!list_empty(&filter_subsystem_match_list)) { |
164 | list_for_each_entry(loop_name, &filter_subsystem_match_list, node) | |
7c27c752 | 165 | if (fnmatch(loop_name->name, subsystem, 0) == 0) |
fc89fe7e KS |
166 | return 0; |
167 | return 1; | |
168 | } | |
169 | ||
170 | return 0; | |
171 | } | |
172 | ||
173 | static int attr_match(const char *path, const char *attr_value) | |
174 | { | |
175 | char attr[NAME_SIZE]; | |
176 | char file[PATH_SIZE]; | |
177 | char *match_value; | |
178 | ||
179 | strlcpy(attr, attr_value, sizeof(attr)); | |
180 | ||
181 | /* separate attr and match value */ | |
182 | match_value = strchr(attr, '='); | |
183 | if (match_value != NULL) { | |
184 | match_value[0] = '\0'; | |
185 | match_value = &match_value[1]; | |
186 | } | |
187 | ||
188 | strlcpy(file, path, sizeof(file)); | |
189 | strlcat(file, "/", sizeof(file)); | |
190 | strlcat(file, attr, sizeof(file)); | |
191 | ||
192 | if (match_value != NULL) { | |
193 | /* match file content */ | |
194 | char value[NAME_SIZE]; | |
195 | int fd; | |
196 | ssize_t size; | |
197 | ||
198 | fd = open(file, O_RDONLY); | |
199 | if (fd < 0) | |
200 | return 0; | |
201 | size = read(fd, value, sizeof(value)); | |
202 | close(fd); | |
203 | if (size < 0) | |
204 | return 0; | |
205 | value[size] = '\0'; | |
206 | remove_trailing_chars(value, '\n'); | |
207 | ||
208 | /* match if attribute value matches */ | |
209 | if (fnmatch(match_value, value, 0) == 0) | |
210 | return 1; | |
211 | } else { | |
212 | /* match if attribute exists */ | |
213 | struct stat statbuf; | |
214 | ||
215 | if (stat(file, &statbuf) == 0) | |
216 | return 1; | |
217 | } | |
218 | return 0; | |
219 | } | |
220 | ||
221 | static int attr_filtered(const char *path) | |
222 | { | |
223 | struct name_entry *loop_name; | |
224 | ||
225 | /* skip devices matching the listed sysfs attributes */ | |
226 | list_for_each_entry(loop_name, &filter_attr_nomatch_list, node) | |
227 | if (attr_match(path, loop_name->name)) | |
228 | return 1; | |
229 | ||
230 | /* skip devices not matching the listed sysfs attributes */ | |
231 | if (!list_empty(&filter_attr_match_list)) { | |
232 | list_for_each_entry(loop_name, &filter_attr_match_list, node) | |
233 | if (attr_match(path, loop_name->name)) | |
234 | return 0; | |
235 | return 1; | |
236 | } | |
237 | return 0; | |
238 | } | |
239 | ||
5ac28543 | 240 | static void scan_subsystem(const char *subsys) |
0d5be398 KS |
241 | { |
242 | char base[PATH_SIZE]; | |
243 | DIR *dir; | |
244 | struct dirent *dent; | |
245 | ||
246 | strlcpy(base, sysfs_path, sizeof(base)); | |
5ac28543 KS |
247 | strlcat(base, "/", sizeof(base)); |
248 | strlcat(base, subsys, sizeof(base)); | |
0d5be398 KS |
249 | |
250 | dir = opendir(base); | |
251 | if (dir != NULL) { | |
252 | for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { | |
253 | char dirname[PATH_SIZE]; | |
254 | DIR *dir2; | |
255 | struct dirent *dent2; | |
256 | ||
257 | if (dent->d_name[0] == '.') | |
258 | continue; | |
259 | ||
fc89fe7e KS |
260 | if (subsystem_filtered(dent->d_name)) |
261 | continue; | |
262 | ||
0d5be398 KS |
263 | strlcpy(dirname, base, sizeof(dirname)); |
264 | strlcat(dirname, "/", sizeof(dirname)); | |
265 | strlcat(dirname, dent->d_name, sizeof(dirname)); | |
266 | strlcat(dirname, "/devices", sizeof(dirname)); | |
267 | ||
268 | /* look for devices */ | |
269 | dir2 = opendir(dirname); | |
270 | if (dir2 != NULL) { | |
271 | for (dent2 = readdir(dir2); dent2 != NULL; dent2 = readdir(dir2)) { | |
272 | char dirname2[PATH_SIZE]; | |
273 | ||
274 | if (dent2->d_name[0] == '.') | |
275 | continue; | |
276 | ||
277 | strlcpy(dirname2, dirname, sizeof(dirname2)); | |
278 | strlcat(dirname2, "/", sizeof(dirname2)); | |
279 | strlcat(dirname2, dent2->d_name, sizeof(dirname2)); | |
fc89fe7e KS |
280 | if (attr_filtered(dirname2)) |
281 | continue; | |
c48622cc | 282 | device_list_insert(dirname2); |
0d5be398 KS |
283 | } |
284 | closedir(dir2); | |
285 | } | |
286 | } | |
287 | closedir(dir); | |
288 | } | |
289 | } | |
290 | ||
04cc3a81 | 291 | static void scan_block(void) |
0d5be398 KS |
292 | { |
293 | char base[PATH_SIZE]; | |
294 | DIR *dir; | |
295 | struct dirent *dent; | |
0d5be398 | 296 | |
fc89fe7e KS |
297 | if (subsystem_filtered("block")) |
298 | return; | |
299 | ||
0d5be398 KS |
300 | strlcpy(base, sysfs_path, sizeof(base)); |
301 | strlcat(base, "/block", sizeof(base)); | |
302 | ||
303 | dir = opendir(base); | |
304 | if (dir != NULL) { | |
305 | for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { | |
306 | char dirname[PATH_SIZE]; | |
307 | DIR *dir2; | |
308 | struct dirent *dent2; | |
309 | ||
310 | if (dent->d_name[0] == '.') | |
311 | continue; | |
312 | ||
313 | strlcpy(dirname, base, sizeof(dirname)); | |
314 | strlcat(dirname, "/", sizeof(dirname)); | |
315 | strlcat(dirname, dent->d_name, sizeof(dirname)); | |
fc89fe7e KS |
316 | if (attr_filtered(dirname)) |
317 | continue; | |
d6e39538 | 318 | if (device_list_insert(dirname) != 0) |
0d5be398 KS |
319 | continue; |
320 | ||
321 | /* look for partitions */ | |
322 | dir2 = opendir(dirname); | |
323 | if (dir2 != NULL) { | |
324 | for (dent2 = readdir(dir2); dent2 != NULL; dent2 = readdir(dir2)) { | |
325 | char dirname2[PATH_SIZE]; | |
326 | ||
327 | if (dent2->d_name[0] == '.') | |
328 | continue; | |
329 | ||
330 | if (!strcmp(dent2->d_name,"device")) | |
331 | continue; | |
332 | ||
333 | strlcpy(dirname2, dirname, sizeof(dirname2)); | |
334 | strlcat(dirname2, "/", sizeof(dirname2)); | |
335 | strlcat(dirname2, dent2->d_name, sizeof(dirname2)); | |
fc89fe7e KS |
336 | if (attr_filtered(dirname2)) |
337 | continue; | |
c48622cc | 338 | device_list_insert(dirname2); |
0d5be398 KS |
339 | } |
340 | closedir(dir2); | |
341 | } | |
342 | } | |
343 | closedir(dir); | |
344 | } | |
345 | } | |
346 | ||
04cc3a81 | 347 | static void scan_class(void) |
0d5be398 KS |
348 | { |
349 | char base[PATH_SIZE]; | |
350 | DIR *dir; | |
351 | struct dirent *dent; | |
352 | ||
353 | strlcpy(base, sysfs_path, sizeof(base)); | |
354 | strlcat(base, "/class", sizeof(base)); | |
355 | ||
356 | dir = opendir(base); | |
357 | if (dir != NULL) { | |
358 | for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { | |
359 | char dirname[PATH_SIZE]; | |
360 | DIR *dir2; | |
361 | struct dirent *dent2; | |
362 | ||
363 | if (dent->d_name[0] == '.') | |
364 | continue; | |
365 | ||
fc89fe7e KS |
366 | if (subsystem_filtered(dent->d_name)) |
367 | continue; | |
368 | ||
0d5be398 KS |
369 | strlcpy(dirname, base, sizeof(dirname)); |
370 | strlcat(dirname, "/", sizeof(dirname)); | |
371 | strlcat(dirname, dent->d_name, sizeof(dirname)); | |
372 | dir2 = opendir(dirname); | |
373 | if (dir2 != NULL) { | |
374 | for (dent2 = readdir(dir2); dent2 != NULL; dent2 = readdir(dir2)) { | |
375 | char dirname2[PATH_SIZE]; | |
376 | ||
377 | if (dent2->d_name[0] == '.') | |
378 | continue; | |
379 | ||
380 | if (!strcmp(dent2->d_name, "device")) | |
381 | continue; | |
382 | ||
383 | strlcpy(dirname2, dirname, sizeof(dirname2)); | |
384 | strlcat(dirname2, "/", sizeof(dirname2)); | |
385 | strlcat(dirname2, dent2->d_name, sizeof(dirname2)); | |
fc89fe7e KS |
386 | if (attr_filtered(dirname2)) |
387 | continue; | |
c48622cc | 388 | device_list_insert(dirname2); |
0d5be398 KS |
389 | } |
390 | closedir(dir2); | |
391 | } | |
392 | } | |
393 | closedir(dir); | |
394 | } | |
395 | } | |
396 | ||
04cc3a81 KS |
397 | static void scan_failed(void) |
398 | { | |
399 | char base[PATH_SIZE]; | |
400 | DIR *dir; | |
401 | struct dirent *dent; | |
402 | ||
403 | strlcpy(base, udev_root, sizeof(base)); | |
051445e0 | 404 | strlcat(base, "/" EVENT_FAILED_DIR, sizeof(base)); |
04cc3a81 KS |
405 | |
406 | dir = opendir(base); | |
407 | if (dir != NULL) { | |
408 | for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { | |
051445e0 KS |
409 | char device[PATH_SIZE]; |
410 | size_t start, end, i; | |
04cc3a81 KS |
411 | |
412 | if (dent->d_name[0] == '.') | |
413 | continue; | |
414 | ||
051445e0 KS |
415 | strlcpy(device, sysfs_path, sizeof(device)); |
416 | start = strlcat(device, "/", sizeof(device)); | |
417 | end = strlcat(device, dent->d_name, sizeof(device)); | |
418 | if (end > sizeof(device)) | |
419 | end = sizeof(device); | |
04cc3a81 | 420 | |
051445e0 KS |
421 | /* replace PATH_TO_NAME_CHAR with '/' */ |
422 | for (i = start; i < end; i++) | |
423 | if (device[i] == PATH_TO_NAME_CHAR) | |
424 | device[i] = '/'; | |
04cc3a81 | 425 | |
c48622cc | 426 | device_list_insert(device); |
04cc3a81 KS |
427 | } |
428 | closedir(dir); | |
429 | } | |
430 | } | |
431 | ||
0d5be398 KS |
432 | int main(int argc, char *argv[], char *envp[]) |
433 | { | |
e3396a2d | 434 | int failed = 0; |
fc89fe7e | 435 | int option; |
e97717ba | 436 | static const struct option options[] = { |
fc89fe7e KS |
437 | { "verbose", 0, NULL, 'v' }, |
438 | { "dry-run", 0, NULL, 'n' }, | |
439 | { "retry-failed", 0, NULL, 'F' }, | |
440 | { "help", 0, NULL, 'h' }, | |
441 | { "subsystem-match", 1, NULL, 's' }, | |
442 | { "subsystem-nomatch", 1, NULL, 'S' }, | |
443 | { "attr-match", 1, NULL, 'a' }, | |
444 | { "attr-nomatch", 1, NULL, 'A' }, | |
445 | {} | |
446 | }; | |
0d5be398 KS |
447 | |
448 | logging_init("udevtrigger"); | |
449 | udev_config_init(); | |
450 | dbg("version %s", UDEV_VERSION); | |
04cc3a81 | 451 | sysfs_init(); |
0d5be398 | 452 | |
fc89fe7e | 453 | while (1) { |
a44a30f6 | 454 | option = getopt_long(argc, argv, "vnFhs:S:a:A:", options, NULL); |
fc89fe7e KS |
455 | if (option == -1) |
456 | break; | |
0d5be398 | 457 | |
fc89fe7e KS |
458 | switch (option) { |
459 | case 'v': | |
0d5be398 | 460 | verbose = 1; |
fc89fe7e KS |
461 | break; |
462 | case 'n': | |
0d5be398 | 463 | dry_run = 1; |
fc89fe7e KS |
464 | break; |
465 | case 'F': | |
e3396a2d | 466 | failed = 1; |
fc89fe7e KS |
467 | break; |
468 | case 's': | |
f14326ad | 469 | name_list_add(&filter_subsystem_match_list, optarg, 0); |
fc89fe7e KS |
470 | break; |
471 | case 'S': | |
f14326ad | 472 | name_list_add(&filter_subsystem_nomatch_list, optarg, 0); |
fc89fe7e KS |
473 | break; |
474 | case 'a': | |
475 | name_list_add(&filter_attr_match_list, optarg, 0); | |
476 | break; | |
477 | case 'A': | |
478 | name_list_add(&filter_attr_nomatch_list, optarg, 0); | |
479 | break; | |
480 | case 'h': | |
481 | printf("Usage: udevtrigger OPTIONS\n" | |
7c27c752 KS |
482 | " --verbose print the list of devices while running\n" |
483 | " --dry-run do not actually trigger the events\n" | |
484 | " --retry-failed trigger only the events which have been\n" | |
485 | " marked as failed during a previous run\n" | |
486 | " --subsystem-match=<subsystem> trigger devices from a matching subystem\n" | |
487 | " --subsystem-nomatch=<subsystem> exclude devices from a matching subystem\n" | |
488 | " --attr-match=<file[=<value>]> trigger devices with a matching sysfs\n" | |
489 | " attribute\n" | |
490 | " --attr-nomatch=<file[=<value>]> exclude devices with a matching sysfs\n" | |
491 | " attribute\n" | |
492 | " --help print this text\n" | |
fc89fe7e KS |
493 | "\n"); |
494 | goto exit; | |
495 | default: | |
04cc3a81 | 496 | goto exit; |
0d5be398 KS |
497 | } |
498 | } | |
499 | ||
e3396a2d KS |
500 | if (failed) |
501 | scan_failed(); | |
502 | else { | |
5ac28543 KS |
503 | char base[PATH_SIZE]; |
504 | struct stat statbuf; | |
505 | ||
506 | /* if we have /sys/subsystem, forget all the old stuff */ | |
507 | strlcpy(base, sysfs_path, sizeof(base)); | |
508 | strlcat(base, "/subsystem", sizeof(base)); | |
509 | if (stat(base, &statbuf) == 0) | |
510 | scan_subsystem("subsystem"); | |
511 | else { | |
512 | scan_subsystem("bus"); | |
513 | scan_class(); | |
514 | ||
515 | /* scan "block" if it isn't a "class" */ | |
516 | strlcpy(base, sysfs_path, sizeof(base)); | |
517 | strlcat(base, "/class/block", sizeof(base)); | |
518 | if (stat(base, &statbuf) != 0) | |
519 | scan_block(); | |
520 | } | |
e3396a2d | 521 | } |
c48622cc | 522 | exec_list(); |
0d5be398 | 523 | |
0d5be398 | 524 | exit: |
f14326ad SV |
525 | name_list_cleanup(&filter_subsystem_match_list); |
526 | name_list_cleanup(&filter_subsystem_nomatch_list); | |
fc89fe7e KS |
527 | name_list_cleanup(&filter_attr_match_list); |
528 | name_list_cleanup(&filter_attr_nomatch_list); | |
529 | ||
04cc3a81 | 530 | sysfs_cleanup(); |
0d5be398 KS |
531 | logging_close(); |
532 | return 0; | |
533 | } |