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