]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
96df036f | 2 | |
ca78ad1d ZJS |
3 | #include <fcntl.h> |
4 | #include <unistd.h> | |
5 | ||
96df036f TG |
6 | #include "sd-device.h" |
7 | ||
b5efdb8a | 8 | #include "alloc-util.h" |
96df036f | 9 | #include "device-enumerator-private.h" |
07630cea | 10 | #include "device-util.h" |
a0956174 | 11 | #include "dirent-util.h" |
3ffd4af2 | 12 | #include "fd-util.h" |
07630cea | 13 | #include "set.h" |
760877e9 | 14 | #include "sort-util.h" |
07630cea LP |
15 | #include "string-util.h" |
16 | #include "strv.h" | |
96df036f TG |
17 | |
18 | #define DEVICE_ENUMERATE_MAX_DEPTH 256 | |
19 | ||
20 | typedef enum DeviceEnumerationType { | |
21 | DEVICE_ENUMERATION_TYPE_DEVICES, | |
22 | DEVICE_ENUMERATION_TYPE_SUBSYSTEMS, | |
3c5cc23a | 23 | DEVICE_ENUMERATION_TYPE_ALL, |
96df036f | 24 | _DEVICE_ENUMERATION_TYPE_MAX, |
2d93c20e | 25 | _DEVICE_ENUMERATION_TYPE_INVALID = -EINVAL, |
96df036f TG |
26 | } DeviceEnumerationType; |
27 | ||
28 | struct sd_device_enumerator { | |
29 | unsigned n_ref; | |
30 | ||
31 | DeviceEnumerationType type; | |
87c3a0f9 | 32 | Hashmap *devices_by_syspath; |
0a166589 | 33 | sd_device **devices; |
319a4f4b | 34 | size_t n_devices, current_device_index; |
96df036f | 35 | bool scan_uptodate; |
87c3a0f9 | 36 | bool sorted; |
96df036f | 37 | |
95a6f969 | 38 | char **prioritized_subsystems; |
96df036f TG |
39 | Set *match_subsystem; |
40 | Set *nomatch_subsystem; | |
41 | Hashmap *match_sysattr; | |
42 | Hashmap *nomatch_sysattr; | |
43 | Hashmap *match_property; | |
44 | Set *match_sysname; | |
45 | Set *match_tag; | |
e022bf66 | 46 | Set *match_parent; |
d8b50e5d | 47 | MatchInitializedType match_initialized; |
96df036f TG |
48 | }; |
49 | ||
50 | _public_ int sd_device_enumerator_new(sd_device_enumerator **ret) { | |
4afd3348 | 51 | _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *enumerator = NULL; |
96df036f TG |
52 | |
53 | assert(ret); | |
54 | ||
6116d2b2 | 55 | enumerator = new(sd_device_enumerator, 1); |
96df036f TG |
56 | if (!enumerator) |
57 | return -ENOMEM; | |
58 | ||
6116d2b2 YW |
59 | *enumerator = (sd_device_enumerator) { |
60 | .n_ref = 1, | |
61 | .type = _DEVICE_ENUMERATION_TYPE_INVALID, | |
d8b50e5d | 62 | .match_initialized = MATCH_INITIALIZED_COMPAT, |
6116d2b2 | 63 | }; |
96df036f | 64 | |
1cc6c93a | 65 | *ret = TAKE_PTR(enumerator); |
96df036f TG |
66 | |
67 | return 0; | |
68 | } | |
69 | ||
deac0c9c YW |
70 | static void device_unref_many(sd_device **devices, size_t n) { |
71 | assert(devices || n == 0); | |
72 | ||
73 | for (size_t i = 0; i < n; i++) | |
74 | sd_device_unref(devices[i]); | |
75 | } | |
76 | ||
77 | static void device_enumerator_unref_devices(sd_device_enumerator *enumerator) { | |
78 | assert(enumerator); | |
79 | ||
87c3a0f9 | 80 | hashmap_clear_with_destructor(enumerator->devices_by_syspath, sd_device_unref); |
deac0c9c | 81 | device_unref_many(enumerator->devices, enumerator->n_devices); |
87c3a0f9 | 82 | enumerator->devices = mfree(enumerator->devices); |
deac0c9c YW |
83 | enumerator->n_devices = 0; |
84 | } | |
85 | ||
8301aa0b | 86 | static sd_device_enumerator *device_enumerator_free(sd_device_enumerator *enumerator) { |
8301aa0b | 87 | assert(enumerator); |
96df036f | 88 | |
deac0c9c | 89 | device_enumerator_unref_devices(enumerator); |
96df036f | 90 | |
87c3a0f9 | 91 | hashmap_free(enumerator->devices_by_syspath); |
95a6f969 | 92 | strv_free(enumerator->prioritized_subsystems); |
c73bb513 ZJS |
93 | set_free(enumerator->match_subsystem); |
94 | set_free(enumerator->nomatch_subsystem); | |
eb1c1dc0 ZJS |
95 | hashmap_free(enumerator->match_sysattr); |
96 | hashmap_free(enumerator->nomatch_sysattr); | |
97 | hashmap_free(enumerator->match_property); | |
c73bb513 ZJS |
98 | set_free(enumerator->match_sysname); |
99 | set_free(enumerator->match_tag); | |
100 | set_free(enumerator->match_parent); | |
96df036f | 101 | |
8301aa0b | 102 | return mfree(enumerator); |
96df036f TG |
103 | } |
104 | ||
8301aa0b YW |
105 | DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_device_enumerator, sd_device_enumerator, device_enumerator_free); |
106 | ||
95a6f969 YW |
107 | int device_enumerator_add_prioritized_subsystem(sd_device_enumerator *enumerator, const char *subsystem) { |
108 | int r; | |
109 | ||
110 | assert(enumerator); | |
111 | assert(subsystem); | |
112 | ||
113 | if (strv_contains(enumerator->prioritized_subsystems, subsystem)) | |
114 | return 0; | |
115 | ||
116 | r = strv_extend(&enumerator->prioritized_subsystems, subsystem); | |
117 | if (r < 0) | |
118 | return r; | |
119 | ||
120 | enumerator->scan_uptodate = false; | |
121 | ||
122 | return 1; | |
123 | } | |
124 | ||
96df036f TG |
125 | _public_ int sd_device_enumerator_add_match_subsystem(sd_device_enumerator *enumerator, const char *subsystem, int match) { |
126 | Set **set; | |
127 | int r; | |
128 | ||
129 | assert_return(enumerator, -EINVAL); | |
130 | assert_return(subsystem, -EINVAL); | |
131 | ||
132 | if (match) | |
133 | set = &enumerator->match_subsystem; | |
134 | else | |
135 | set = &enumerator->nomatch_subsystem; | |
136 | ||
be327321 | 137 | r = set_put_strdup(set, subsystem); |
2204f018 | 138 | if (r <= 0) |
96df036f TG |
139 | return r; |
140 | ||
141 | enumerator->scan_uptodate = false; | |
142 | ||
2204f018 | 143 | return 1; |
96df036f TG |
144 | } |
145 | ||
eb1c1dc0 | 146 | _public_ int sd_device_enumerator_add_match_sysattr(sd_device_enumerator *enumerator, const char *sysattr, const char *value, int match) { |
96df036f TG |
147 | Hashmap **hashmap; |
148 | int r; | |
149 | ||
150 | assert_return(enumerator, -EINVAL); | |
eb1c1dc0 | 151 | assert_return(sysattr, -EINVAL); |
96df036f TG |
152 | |
153 | if (match) | |
154 | hashmap = &enumerator->match_sysattr; | |
155 | else | |
156 | hashmap = &enumerator->nomatch_sysattr; | |
157 | ||
30e2c8c7 YW |
158 | /* Do not use string_has_ops_free_free or hashmap_put_strdup() here, as this may be called |
159 | * multiple times with the same sysattr but different value. */ | |
a0887abb | 160 | r = hashmap_put_strdup_full(hashmap, &trivial_hash_ops_free_free, sysattr, value); |
2204f018 | 161 | if (r <= 0) |
96df036f TG |
162 | return r; |
163 | ||
96df036f TG |
164 | enumerator->scan_uptodate = false; |
165 | ||
2204f018 | 166 | return 1; |
96df036f TG |
167 | } |
168 | ||
eb1c1dc0 | 169 | _public_ int sd_device_enumerator_add_match_property(sd_device_enumerator *enumerator, const char *property, const char *value) { |
96df036f TG |
170 | int r; |
171 | ||
172 | assert_return(enumerator, -EINVAL); | |
eb1c1dc0 | 173 | assert_return(property, -EINVAL); |
96df036f | 174 | |
30e2c8c7 YW |
175 | /* Do not use string_has_ops_free_free or hashmap_put_strdup() here, as this may be called |
176 | * multiple times with the same property but different value. */ | |
a0887abb | 177 | r = hashmap_put_strdup_full(&enumerator->match_property, &trivial_hash_ops_free_free, property, value); |
2204f018 | 178 | if (r <= 0) |
96df036f TG |
179 | return r; |
180 | ||
96df036f TG |
181 | enumerator->scan_uptodate = false; |
182 | ||
2204f018 | 183 | return 1; |
96df036f TG |
184 | } |
185 | ||
186 | _public_ int sd_device_enumerator_add_match_sysname(sd_device_enumerator *enumerator, const char *sysname) { | |
187 | int r; | |
188 | ||
189 | assert_return(enumerator, -EINVAL); | |
190 | assert_return(sysname, -EINVAL); | |
191 | ||
be327321 | 192 | r = set_put_strdup(&enumerator->match_sysname, sysname); |
2204f018 | 193 | if (r <= 0) |
96df036f TG |
194 | return r; |
195 | ||
196 | enumerator->scan_uptodate = false; | |
197 | ||
2204f018 | 198 | return 1; |
96df036f TG |
199 | } |
200 | ||
201 | _public_ int sd_device_enumerator_add_match_tag(sd_device_enumerator *enumerator, const char *tag) { | |
202 | int r; | |
203 | ||
204 | assert_return(enumerator, -EINVAL); | |
205 | assert_return(tag, -EINVAL); | |
206 | ||
be327321 | 207 | r = set_put_strdup(&enumerator->match_tag, tag); |
2204f018 | 208 | if (r <= 0) |
96df036f TG |
209 | return r; |
210 | ||
211 | enumerator->scan_uptodate = false; | |
212 | ||
2204f018 | 213 | return 1; |
96df036f TG |
214 | } |
215 | ||
e022bf66 YW |
216 | int device_enumerator_add_match_parent_incremental(sd_device_enumerator *enumerator, sd_device *parent) { |
217 | const char *path; | |
218 | int r; | |
219 | ||
476a63e9 ZJS |
220 | assert(enumerator); |
221 | assert(parent); | |
96df036f | 222 | |
e022bf66 YW |
223 | r = sd_device_get_syspath(parent, &path); |
224 | if (r < 0) | |
225 | return r; | |
226 | ||
be327321 | 227 | r = set_put_strdup(&enumerator->match_parent, path); |
2204f018 | 228 | if (r <= 0) |
e022bf66 | 229 | return r; |
96df036f TG |
230 | |
231 | enumerator->scan_uptodate = false; | |
232 | ||
2204f018 | 233 | return 1; |
96df036f TG |
234 | } |
235 | ||
e022bf66 | 236 | _public_ int sd_device_enumerator_add_match_parent(sd_device_enumerator *enumerator, sd_device *parent) { |
476a63e9 ZJS |
237 | assert_return(enumerator, -EINVAL); |
238 | assert_return(parent, -EINVAL); | |
239 | ||
240 | set_clear(enumerator->match_parent); | |
241 | ||
e022bf66 YW |
242 | return device_enumerator_add_match_parent_incremental(enumerator, parent); |
243 | } | |
244 | ||
dee5e0b6 | 245 | _public_ int sd_device_enumerator_allow_uninitialized(sd_device_enumerator *enumerator) { |
96df036f TG |
246 | assert_return(enumerator, -EINVAL); |
247 | ||
d8b50e5d | 248 | enumerator->match_initialized = MATCH_INITIALIZED_ALL; |
dee5e0b6 TG |
249 | |
250 | enumerator->scan_uptodate = false; | |
251 | ||
2204f018 | 252 | return 1; |
dee5e0b6 TG |
253 | } |
254 | ||
d8b50e5d | 255 | int device_enumerator_add_match_is_initialized(sd_device_enumerator *enumerator, MatchInitializedType type) { |
dee5e0b6 | 256 | assert_return(enumerator, -EINVAL); |
d8b50e5d | 257 | assert_return(type >= 0 && type < _MATCH_INITIALIZED_MAX, -EINVAL); |
dee5e0b6 | 258 | |
d8b50e5d | 259 | enumerator->match_initialized = type; |
96df036f TG |
260 | |
261 | enumerator->scan_uptodate = false; | |
262 | ||
2204f018 | 263 | return 1; |
96df036f TG |
264 | } |
265 | ||
9e871965 YW |
266 | static int sound_device_compare(const char *devpath_a, const char *devpath_b) { |
267 | const char *sound_a, *sound_b; | |
268 | size_t prefix_len; | |
269 | ||
270 | assert(devpath_a); | |
271 | assert(devpath_b); | |
96df036f | 272 | |
9e871965 YW |
273 | /* For sound cards the control device must be enumerated last to make sure it's the final |
274 | * device node that gets ACLs applied. Applications rely on this fact and use ACL changes on | |
275 | * the control node as an indicator that the ACL change of the entire sound card completed. The | |
276 | * kernel makes this guarantee when creating those devices, and hence we should too when | |
277 | * enumerating them. */ | |
96df036f TG |
278 | |
279 | sound_a = strstr(devpath_a, "/sound/card"); | |
9e871965 YW |
280 | if (!sound_a) |
281 | return 0; | |
282 | ||
283 | sound_a += STRLEN("/sound/card"); | |
284 | sound_a = strchr(devpath_a, '/'); | |
285 | if (!sound_a) | |
286 | return 0; | |
287 | ||
288 | prefix_len = sound_a - devpath_a; | |
289 | ||
290 | if (!strneq(devpath_a, devpath_b, prefix_len)) | |
291 | return 0; | |
292 | ||
293 | sound_b = devpath_b + prefix_len; | |
294 | ||
295 | return CMP(!!startswith(sound_a, "/controlC"), | |
296 | !!startswith(sound_b, "/controlC")); | |
297 | } | |
298 | ||
299 | static bool devpath_is_late_block(const char *devpath) { | |
300 | assert(devpath); | |
301 | ||
302 | return strstr(devpath, "/block/md") || strstr(devpath, "/block/dm-"); | |
303 | } | |
304 | ||
305 | static int device_compare(sd_device * const *a, sd_device * const *b) { | |
306 | const char *devpath_a, *devpath_b; | |
307 | int r; | |
308 | ||
309 | assert(a); | |
310 | assert(b); | |
311 | assert(*a); | |
312 | assert(*b); | |
313 | ||
314 | assert_se(sd_device_get_devpath(*(sd_device**) a, &devpath_a) >= 0); | |
315 | assert_se(sd_device_get_devpath(*(sd_device**) b, &devpath_b) >= 0); | |
316 | ||
317 | r = sound_device_compare(devpath_a, devpath_b); | |
318 | if (r != 0) | |
319 | return r; | |
96df036f TG |
320 | |
321 | /* md and dm devices are enumerated after all other devices */ | |
9e871965 | 322 | r = CMP(devpath_is_late_block(devpath_a), devpath_is_late_block(devpath_b)); |
93bab288 YW |
323 | if (r != 0) |
324 | return r; | |
96df036f | 325 | |
63aac21c | 326 | return path_compare(devpath_a, devpath_b); |
96df036f TG |
327 | } |
328 | ||
87c3a0f9 | 329 | static int enumerator_sort_devices(sd_device_enumerator *enumerator) { |
95a6f969 | 330 | size_t n_sorted = 0, n = 0; |
87c3a0f9 YW |
331 | sd_device **devices; |
332 | sd_device *device; | |
95a6f969 | 333 | int r; |
87c3a0f9 YW |
334 | |
335 | assert(enumerator); | |
336 | ||
337 | if (enumerator->sorted) | |
338 | return 0; | |
339 | ||
340 | devices = new(sd_device*, hashmap_size(enumerator->devices_by_syspath)); | |
341 | if (!devices) | |
342 | return -ENOMEM; | |
343 | ||
95a6f969 YW |
344 | STRV_FOREACH(prioritized_subsystem, enumerator->prioritized_subsystems) { |
345 | ||
346 | for (;;) { | |
347 | const char *syspath; | |
348 | size_t m = n; | |
349 | ||
350 | HASHMAP_FOREACH_KEY(device, syspath, enumerator->devices_by_syspath) { | |
351 | _cleanup_free_ char *p = NULL; | |
352 | const char *subsys; | |
353 | ||
354 | if (sd_device_get_subsystem(device, &subsys) < 0) | |
355 | continue; | |
356 | ||
357 | if (!streq(subsys, *prioritized_subsystem)) | |
358 | continue; | |
359 | ||
360 | devices[n++] = sd_device_ref(device); | |
361 | ||
362 | for (;;) { | |
363 | _cleanup_free_ char *q = NULL; | |
364 | ||
365 | r = path_extract_directory(p ?: syspath, &q); | |
366 | if (r == -EADDRNOTAVAIL) | |
367 | break; | |
368 | if (r < 0) | |
369 | goto failed; | |
370 | ||
371 | device = hashmap_get(enumerator->devices_by_syspath, q); | |
372 | if (device) | |
373 | devices[n++] = sd_device_ref(device); | |
374 | ||
375 | free_and_replace(p, q); | |
376 | } | |
377 | ||
378 | break; | |
379 | } | |
380 | ||
381 | /* We cannot remove multiple entries in the loop HASHMAP_FOREACH_KEY() above. */ | |
382 | for (size_t i = m; i < n; i++) { | |
383 | r = sd_device_get_syspath(devices[i], &syspath); | |
384 | if (r < 0) | |
385 | goto failed; | |
386 | ||
387 | assert_se(hashmap_remove(enumerator->devices_by_syspath, syspath) == devices[i]); | |
388 | sd_device_unref(devices[i]); | |
389 | } | |
390 | ||
391 | if (m == n) | |
392 | break; | |
393 | } | |
394 | ||
395 | typesafe_qsort(devices + n_sorted, n - n_sorted, device_compare); | |
396 | n_sorted = n; | |
397 | } | |
398 | ||
87c3a0f9 YW |
399 | HASHMAP_FOREACH(device, enumerator->devices_by_syspath) |
400 | devices[n++] = sd_device_ref(device); | |
401 | ||
95a6f969 YW |
402 | /* Move all devices back to the hashmap. Otherwise, devices added by |
403 | * udev_enumerate_add_syspath() -> device_enumerator_add_device() may not be listed. */ | |
404 | for (size_t i = 0; i < n_sorted; i++) { | |
405 | const char *syspath; | |
406 | ||
407 | r = sd_device_get_syspath(devices[i], &syspath); | |
408 | if (r < 0) | |
409 | goto failed; | |
410 | ||
411 | r = hashmap_put(enumerator->devices_by_syspath, syspath, devices[i]); | |
412 | if (r < 0) | |
413 | goto failed; | |
414 | assert(r > 0); | |
415 | ||
416 | sd_device_ref(devices[i]); | |
417 | } | |
418 | ||
419 | typesafe_qsort(devices + n_sorted, n - n_sorted, device_compare); | |
87c3a0f9 YW |
420 | |
421 | device_unref_many(enumerator->devices, enumerator->n_devices); | |
422 | ||
423 | enumerator->n_devices = n; | |
424 | free_and_replace(enumerator->devices, devices); | |
425 | ||
426 | enumerator->sorted = true; | |
427 | return 0; | |
95a6f969 YW |
428 | |
429 | failed: | |
430 | device_unref_many(devices, n); | |
431 | free(devices); | |
432 | return r; | |
87c3a0f9 YW |
433 | } |
434 | ||
19c9df44 | 435 | int device_enumerator_add_device(sd_device_enumerator *enumerator, sd_device *device) { |
87c3a0f9 YW |
436 | const char *syspath; |
437 | int r; | |
438 | ||
96df036f TG |
439 | assert_return(enumerator, -EINVAL); |
440 | assert_return(device, -EINVAL); | |
441 | ||
87c3a0f9 YW |
442 | r = sd_device_get_syspath(device, &syspath); |
443 | if (r < 0) | |
444 | return r; | |
96df036f | 445 | |
87c3a0f9 YW |
446 | r = hashmap_ensure_put(&enumerator->devices_by_syspath, &string_hash_ops, syspath, device); |
447 | if (IN_SET(r, -EEXIST, 0)) | |
448 | return 0; | |
449 | if (r < 0) | |
450 | return r; | |
96df036f | 451 | |
87c3a0f9 YW |
452 | sd_device_ref(device); |
453 | ||
454 | enumerator->sorted = false; | |
455 | return 1; | |
96df036f TG |
456 | } |
457 | ||
96df036f TG |
458 | static bool match_property(sd_device_enumerator *enumerator, sd_device *device) { |
459 | const char *property; | |
460 | const char *value; | |
96df036f TG |
461 | |
462 | assert(enumerator); | |
463 | assert(device); | |
464 | ||
465 | if (hashmap_isempty(enumerator->match_property)) | |
466 | return true; | |
467 | ||
90e74a66 | 468 | HASHMAP_FOREACH_KEY(value, property, enumerator->match_property) { |
96df036f TG |
469 | const char *property_dev, *value_dev; |
470 | ||
471 | FOREACH_DEVICE_PROPERTY(device, property_dev, value_dev) { | |
472 | if (fnmatch(property, property_dev, 0) != 0) | |
473 | continue; | |
474 | ||
475 | if (!value && !value_dev) | |
476 | return true; | |
477 | ||
478 | if (!value || !value_dev) | |
479 | continue; | |
480 | ||
481 | if (fnmatch(value, value_dev, 0) == 0) | |
482 | return true; | |
483 | } | |
484 | } | |
485 | ||
486 | return false; | |
487 | } | |
488 | ||
489 | static bool match_tag(sd_device_enumerator *enumerator, sd_device *device) { | |
490 | const char *tag; | |
96df036f TG |
491 | |
492 | assert(enumerator); | |
493 | assert(device); | |
494 | ||
90e74a66 | 495 | SET_FOREACH(tag, enumerator->match_tag) |
96df036f TG |
496 | if (!sd_device_has_tag(device, tag)) |
497 | return false; | |
498 | ||
499 | return true; | |
500 | } | |
501 | ||
96df036f TG |
502 | static bool match_sysname(sd_device_enumerator *enumerator, const char *sysname) { |
503 | const char *sysname_match; | |
96df036f TG |
504 | |
505 | assert(enumerator); | |
506 | assert(sysname); | |
507 | ||
508 | if (set_isempty(enumerator->match_sysname)) | |
509 | return true; | |
510 | ||
90e74a66 | 511 | SET_FOREACH(sysname_match, enumerator->match_sysname) |
96df036f TG |
512 | if (fnmatch(sysname_match, sysname, 0) == 0) |
513 | return true; | |
514 | ||
515 | return false; | |
516 | } | |
517 | ||
d8b50e5d YW |
518 | static int match_initialized(sd_device_enumerator *enumerator, sd_device *device) { |
519 | int r; | |
520 | ||
521 | assert(enumerator); | |
522 | assert(device); | |
523 | ||
524 | if (enumerator->match_initialized == MATCH_INITIALIZED_ALL) | |
525 | return true; | |
526 | ||
527 | r = sd_device_get_is_initialized(device); | |
528 | if (r == -ENOENT) /* this is necessarily racey, so ignore missing devices */ | |
529 | return false; | |
530 | if (r < 0) | |
531 | return r; | |
532 | ||
533 | if (enumerator->match_initialized == MATCH_INITIALIZED_COMPAT) { | |
534 | /* only devices that have no devnode/ifindex or have a db entry are accepted. */ | |
535 | if (r > 0) | |
536 | return true; | |
537 | ||
538 | if (sd_device_get_devnum(device, NULL) >= 0) | |
539 | return true; | |
540 | ||
541 | if (sd_device_get_ifindex(device, NULL) >= 0) | |
542 | return true; | |
543 | ||
544 | return false; | |
545 | } | |
546 | ||
547 | return (enumerator->match_initialized == MATCH_INITIALIZED_NO) == (r == 0); | |
548 | } | |
549 | ||
96df036f TG |
550 | static int enumerator_scan_dir_and_add_devices(sd_device_enumerator *enumerator, const char *basedir, const char *subdir1, const char *subdir2) { |
551 | _cleanup_closedir_ DIR *dir = NULL; | |
552 | char *path; | |
d8b50e5d | 553 | int k, r = 0; |
96df036f TG |
554 | |
555 | assert(enumerator); | |
556 | assert(basedir); | |
557 | ||
558 | path = strjoina("/sys/", basedir, "/"); | |
559 | ||
560 | if (subdir1) | |
561 | path = strjoina(path, subdir1, "/"); | |
562 | ||
563 | if (subdir2) | |
564 | path = strjoina(path, subdir2, "/"); | |
565 | ||
566 | dir = opendir(path); | |
567 | if (!dir) | |
cfb6197b IP |
568 | /* this is necessarily racey, so ignore missing directories */ |
569 | return (errno == ENOENT && (subdir1 || subdir2)) ? 0 : -errno; | |
96df036f | 570 | |
c7f0d9e5 | 571 | FOREACH_DIRENT_ALL(de, dir, return -errno) { |
4afd3348 | 572 | _cleanup_(sd_device_unrefp) sd_device *device = NULL; |
c7f0d9e5 | 573 | char syspath[strlen(path) + 1 + strlen(de->d_name) + 1]; |
96df036f | 574 | |
c7f0d9e5 | 575 | if (de->d_name[0] == '.') |
96df036f TG |
576 | continue; |
577 | ||
c7f0d9e5 | 578 | if (!match_sysname(enumerator, de->d_name)) |
96df036f TG |
579 | continue; |
580 | ||
c7f0d9e5 | 581 | (void) sprintf(syspath, "%s%s", path, de->d_name); |
96df036f TG |
582 | |
583 | k = sd_device_new_from_syspath(&device, syspath); | |
584 | if (k < 0) { | |
08232a02 TG |
585 | if (k != -ENODEV) |
586 | /* this is necessarily racey, so ignore missing devices */ | |
587 | r = k; | |
588 | ||
96df036f TG |
589 | continue; |
590 | } | |
591 | ||
d8b50e5d YW |
592 | k = match_initialized(enumerator, device); |
593 | if (k <= 0) { | |
594 | if (k < 0) | |
595 | r = k; | |
96df036f TG |
596 | continue; |
597 | } | |
598 | ||
bcfe746b | 599 | if (!device_match_parent(device, enumerator->match_parent, NULL)) |
96df036f TG |
600 | continue; |
601 | ||
602 | if (!match_tag(enumerator, device)) | |
603 | continue; | |
604 | ||
605 | if (!match_property(enumerator, device)) | |
606 | continue; | |
607 | ||
ac790e8b | 608 | if (!device_match_sysattr(device, enumerator->match_sysattr, enumerator->nomatch_sysattr)) |
96df036f TG |
609 | continue; |
610 | ||
19c9df44 | 611 | k = device_enumerator_add_device(enumerator, device); |
96df036f TG |
612 | if (k < 0) |
613 | r = k; | |
614 | } | |
615 | ||
616 | return r; | |
617 | } | |
618 | ||
619 | static bool match_subsystem(sd_device_enumerator *enumerator, const char *subsystem) { | |
620 | const char *subsystem_match; | |
96df036f TG |
621 | |
622 | assert(enumerator); | |
623 | ||
624 | if (!subsystem) | |
625 | return false; | |
626 | ||
90e74a66 | 627 | SET_FOREACH(subsystem_match, enumerator->nomatch_subsystem) |
96df036f TG |
628 | if (fnmatch(subsystem_match, subsystem, 0) == 0) |
629 | return false; | |
630 | ||
631 | if (set_isempty(enumerator->match_subsystem)) | |
632 | return true; | |
633 | ||
90e74a66 | 634 | SET_FOREACH(subsystem_match, enumerator->match_subsystem) |
96df036f TG |
635 | if (fnmatch(subsystem_match, subsystem, 0) == 0) |
636 | return true; | |
637 | ||
638 | return false; | |
639 | } | |
640 | ||
641 | static int enumerator_scan_dir(sd_device_enumerator *enumerator, const char *basedir, const char *subdir, const char *subsystem) { | |
642 | _cleanup_closedir_ DIR *dir = NULL; | |
643 | char *path; | |
96df036f TG |
644 | int r = 0; |
645 | ||
646 | path = strjoina("/sys/", basedir); | |
647 | ||
648 | dir = opendir(path); | |
649 | if (!dir) | |
650 | return -errno; | |
651 | ||
c7d54dae | 652 | log_debug("sd-device-enumerator: Scanning %s", path); |
96df036f | 653 | |
c7f0d9e5 | 654 | FOREACH_DIRENT_ALL(de, dir, return -errno) { |
96df036f TG |
655 | int k; |
656 | ||
c7f0d9e5 | 657 | if (de->d_name[0] == '.') |
96df036f TG |
658 | continue; |
659 | ||
c7f0d9e5 | 660 | if (!match_subsystem(enumerator, subsystem ? : de->d_name)) |
96df036f TG |
661 | continue; |
662 | ||
c7f0d9e5 | 663 | k = enumerator_scan_dir_and_add_devices(enumerator, basedir, de->d_name, subdir); |
96df036f TG |
664 | if (k < 0) |
665 | r = k; | |
666 | } | |
667 | ||
668 | return r; | |
669 | } | |
670 | ||
671 | static int enumerator_scan_devices_tag(sd_device_enumerator *enumerator, const char *tag) { | |
672 | _cleanup_closedir_ DIR *dir = NULL; | |
673 | char *path; | |
96df036f TG |
674 | int r = 0; |
675 | ||
676 | assert(enumerator); | |
677 | assert(tag); | |
678 | ||
679 | path = strjoina("/run/udev/tags/", tag); | |
680 | ||
681 | dir = opendir(path); | |
682 | if (!dir) { | |
c7d54dae YW |
683 | if (errno != ENOENT) |
684 | return log_debug_errno(errno, "sd-device-enumerator: Failed to open tags directory %s: %m", path); | |
685 | return 0; | |
96df036f TG |
686 | } |
687 | ||
688 | /* TODO: filter away subsystems? */ | |
689 | ||
c7f0d9e5 | 690 | FOREACH_DIRENT_ALL(de, dir, return -errno) { |
4afd3348 | 691 | _cleanup_(sd_device_unrefp) sd_device *device = NULL; |
96df036f TG |
692 | const char *subsystem, *sysname; |
693 | int k; | |
694 | ||
c7f0d9e5 | 695 | if (de->d_name[0] == '.') |
96df036f TG |
696 | continue; |
697 | ||
c7f0d9e5 | 698 | k = sd_device_new_from_device_id(&device, de->d_name); |
96df036f | 699 | if (k < 0) { |
08232a02 TG |
700 | if (k != -ENODEV) |
701 | /* this is necessarily racy, so ignore missing devices */ | |
702 | r = k; | |
703 | ||
96df036f TG |
704 | continue; |
705 | } | |
706 | ||
707 | k = sd_device_get_subsystem(device, &subsystem); | |
708 | if (k < 0) { | |
3126d64e YW |
709 | if (k != -ENOENT) |
710 | /* this is necessarily racy, so ignore missing devices */ | |
711 | r = k; | |
96df036f TG |
712 | continue; |
713 | } | |
714 | ||
715 | if (!match_subsystem(enumerator, subsystem)) | |
716 | continue; | |
717 | ||
718 | k = sd_device_get_sysname(device, &sysname); | |
719 | if (k < 0) { | |
720 | r = k; | |
721 | continue; | |
722 | } | |
723 | ||
724 | if (!match_sysname(enumerator, sysname)) | |
725 | continue; | |
726 | ||
bcfe746b | 727 | if (!device_match_parent(device, enumerator->match_parent, NULL)) |
96df036f TG |
728 | continue; |
729 | ||
730 | if (!match_property(enumerator, device)) | |
731 | continue; | |
732 | ||
ac790e8b | 733 | if (!device_match_sysattr(device, enumerator->match_sysattr, enumerator->nomatch_sysattr)) |
96df036f TG |
734 | continue; |
735 | ||
19c9df44 | 736 | k = device_enumerator_add_device(enumerator, device); |
96df036f TG |
737 | if (k < 0) { |
738 | r = k; | |
739 | continue; | |
740 | } | |
741 | } | |
742 | ||
743 | return r; | |
744 | } | |
745 | ||
746 | static int enumerator_scan_devices_tags(sd_device_enumerator *enumerator) { | |
747 | const char *tag; | |
3172836b | 748 | int r = 0; |
96df036f TG |
749 | |
750 | assert(enumerator); | |
751 | ||
90e74a66 | 752 | SET_FOREACH(tag, enumerator->match_tag) { |
3172836b TG |
753 | int k; |
754 | ||
755 | k = enumerator_scan_devices_tag(enumerator, tag); | |
756 | if (k < 0) | |
757 | r = k; | |
96df036f TG |
758 | } |
759 | ||
3172836b | 760 | return r; |
96df036f TG |
761 | } |
762 | ||
763 | static int parent_add_child(sd_device_enumerator *enumerator, const char *path) { | |
4afd3348 | 764 | _cleanup_(sd_device_unrefp) sd_device *device = NULL; |
96df036f TG |
765 | const char *subsystem, *sysname; |
766 | int r; | |
767 | ||
768 | r = sd_device_new_from_syspath(&device, path); | |
08232a02 TG |
769 | if (r == -ENODEV) |
770 | /* this is necessarily racy, so ignore missing devices */ | |
96df036f TG |
771 | return 0; |
772 | else if (r < 0) | |
773 | return r; | |
774 | ||
775 | r = sd_device_get_subsystem(device, &subsystem); | |
9a9c7dc2 MM |
776 | if (r == -ENOENT) |
777 | return 0; | |
96df036f TG |
778 | if (r < 0) |
779 | return r; | |
780 | ||
781 | if (!match_subsystem(enumerator, subsystem)) | |
782 | return 0; | |
783 | ||
784 | r = sd_device_get_sysname(device, &sysname); | |
785 | if (r < 0) | |
786 | return r; | |
787 | ||
788 | if (!match_sysname(enumerator, sysname)) | |
789 | return 0; | |
790 | ||
791 | if (!match_property(enumerator, device)) | |
792 | return 0; | |
793 | ||
ac790e8b | 794 | if (!device_match_sysattr(device, enumerator->match_sysattr, enumerator->nomatch_sysattr)) |
96df036f TG |
795 | return 0; |
796 | ||
19c9df44 | 797 | r = device_enumerator_add_device(enumerator, device); |
96df036f TG |
798 | if (r < 0) |
799 | return r; | |
800 | ||
801 | return 1; | |
802 | } | |
803 | ||
804 | static int parent_crawl_children(sd_device_enumerator *enumerator, const char *path, unsigned maxdepth) { | |
805 | _cleanup_closedir_ DIR *dir = NULL; | |
96df036f TG |
806 | int r = 0; |
807 | ||
808 | dir = opendir(path); | |
25f027c5 | 809 | if (!dir) |
c7d54dae | 810 | return log_debug_errno(errno, "sd-device-enumerator: Failed to open parent directory %s: %m", path); |
96df036f | 811 | |
c7f0d9e5 | 812 | FOREACH_DIRENT_ALL(de, dir, return -errno) { |
96df036f TG |
813 | _cleanup_free_ char *child = NULL; |
814 | int k; | |
815 | ||
c7f0d9e5 | 816 | if (de->d_name[0] == '.') |
96df036f TG |
817 | continue; |
818 | ||
c7f0d9e5 | 819 | if (de->d_type != DT_DIR) |
96df036f TG |
820 | continue; |
821 | ||
c7f0d9e5 | 822 | child = path_join(path, de->d_name); |
53fae771 ZJS |
823 | if (!child) |
824 | return -ENOMEM; | |
96df036f TG |
825 | |
826 | k = parent_add_child(enumerator, child); | |
827 | if (k < 0) | |
828 | r = k; | |
829 | ||
830 | if (maxdepth > 0) | |
831 | parent_crawl_children(enumerator, child, maxdepth - 1); | |
832 | else | |
c7d54dae | 833 | log_debug("sd-device-enumerator: Max depth reached, %s: ignoring devices", child); |
96df036f TG |
834 | } |
835 | ||
836 | return r; | |
837 | } | |
838 | ||
839 | static int enumerator_scan_devices_children(sd_device_enumerator *enumerator) { | |
840 | const char *path; | |
841 | int r = 0, k; | |
842 | ||
90e74a66 | 843 | SET_FOREACH(path, enumerator->match_parent) { |
e022bf66 YW |
844 | k = parent_add_child(enumerator, path); |
845 | if (k < 0) | |
846 | r = k; | |
96df036f | 847 | |
e022bf66 YW |
848 | k = parent_crawl_children(enumerator, path, DEVICE_ENUMERATE_MAX_DEPTH); |
849 | if (k < 0) | |
850 | r = k; | |
851 | } | |
96df036f TG |
852 | |
853 | return r; | |
854 | } | |
855 | ||
856 | static int enumerator_scan_devices_all(sd_device_enumerator *enumerator) { | |
37cf83d9 | 857 | int k, r = 0; |
96df036f | 858 | |
c7d54dae | 859 | log_debug("sd-device-enumerator: Scan all dirs"); |
96df036f | 860 | |
37cf83d9 YW |
861 | k = enumerator_scan_dir(enumerator, "bus", "devices", NULL); |
862 | if (k < 0) | |
863 | r = log_debug_errno(k, "sd-device-enumerator: Failed to scan /sys/bus: %m"); | |
96df036f | 864 | |
37cf83d9 YW |
865 | k = enumerator_scan_dir(enumerator, "class", NULL, NULL); |
866 | if (k < 0) | |
867 | r = log_debug_errno(k, "sd-device-enumerator: Failed to scan /sys/class: %m"); | |
96df036f TG |
868 | |
869 | return r; | |
870 | } | |
871 | ||
872 | int device_enumerator_scan_devices(sd_device_enumerator *enumerator) { | |
3172836b | 873 | int r = 0, k; |
96df036f TG |
874 | |
875 | assert(enumerator); | |
876 | ||
877 | if (enumerator->scan_uptodate && | |
878 | enumerator->type == DEVICE_ENUMERATION_TYPE_DEVICES) | |
879 | return 0; | |
880 | ||
deac0c9c | 881 | device_enumerator_unref_devices(enumerator); |
96df036f TG |
882 | |
883 | if (!set_isempty(enumerator->match_tag)) { | |
3172836b TG |
884 | k = enumerator_scan_devices_tags(enumerator); |
885 | if (k < 0) | |
886 | r = k; | |
96df036f | 887 | } else if (enumerator->match_parent) { |
3172836b TG |
888 | k = enumerator_scan_devices_children(enumerator); |
889 | if (k < 0) | |
890 | r = k; | |
96df036f | 891 | } else { |
3172836b TG |
892 | k = enumerator_scan_devices_all(enumerator); |
893 | if (k < 0) | |
894 | r = k; | |
96df036f TG |
895 | } |
896 | ||
897 | enumerator->scan_uptodate = true; | |
0a166589 | 898 | enumerator->type = DEVICE_ENUMERATION_TYPE_DEVICES; |
96df036f | 899 | |
3172836b | 900 | return r; |
96df036f TG |
901 | } |
902 | ||
903 | _public_ sd_device *sd_device_enumerator_get_device_first(sd_device_enumerator *enumerator) { | |
96df036f TG |
904 | assert_return(enumerator, NULL); |
905 | ||
87c3a0f9 YW |
906 | if (device_enumerator_scan_devices(enumerator) < 0) |
907 | return NULL; | |
908 | ||
909 | if (enumerator_sort_devices(enumerator) < 0) | |
96df036f TG |
910 | return NULL; |
911 | ||
0a166589 YW |
912 | enumerator->current_device_index = 0; |
913 | ||
914 | if (enumerator->n_devices == 0) | |
915 | return NULL; | |
96df036f | 916 | |
0a166589 | 917 | return enumerator->devices[0]; |
96df036f TG |
918 | } |
919 | ||
920 | _public_ sd_device *sd_device_enumerator_get_device_next(sd_device_enumerator *enumerator) { | |
921 | assert_return(enumerator, NULL); | |
922 | ||
923 | if (!enumerator->scan_uptodate || | |
87c3a0f9 | 924 | !enumerator->sorted || |
0a166589 YW |
925 | enumerator->type != DEVICE_ENUMERATION_TYPE_DEVICES || |
926 | enumerator->current_device_index + 1 >= enumerator->n_devices) | |
96df036f TG |
927 | return NULL; |
928 | ||
0a166589 | 929 | return enumerator->devices[++enumerator->current_device_index]; |
96df036f TG |
930 | } |
931 | ||
932 | int device_enumerator_scan_subsystems(sd_device_enumerator *enumerator) { | |
96df036f TG |
933 | int r = 0, k; |
934 | ||
935 | assert(enumerator); | |
936 | ||
937 | if (enumerator->scan_uptodate && | |
938 | enumerator->type == DEVICE_ENUMERATION_TYPE_SUBSYSTEMS) | |
939 | return 0; | |
940 | ||
deac0c9c | 941 | device_enumerator_unref_devices(enumerator); |
96df036f TG |
942 | |
943 | /* modules */ | |
944 | if (match_subsystem(enumerator, "module")) { | |
945 | k = enumerator_scan_dir_and_add_devices(enumerator, "module", NULL, NULL); | |
fed66db0 YW |
946 | if (k < 0) |
947 | r = log_debug_errno(k, "sd-device-enumerator: Failed to scan modules: %m"); | |
96df036f TG |
948 | } |
949 | ||
96df036f TG |
950 | /* subsystems (only buses support coldplug) */ |
951 | if (match_subsystem(enumerator, "subsystem")) { | |
37cf83d9 | 952 | k = enumerator_scan_dir_and_add_devices(enumerator, "bus", NULL, NULL); |
fed66db0 YW |
953 | if (k < 0) |
954 | r = log_debug_errno(k, "sd-device-enumerator: Failed to scan subsystems: %m"); | |
96df036f TG |
955 | } |
956 | ||
957 | /* subsystem drivers */ | |
958 | if (match_subsystem(enumerator, "drivers")) { | |
37cf83d9 | 959 | k = enumerator_scan_dir(enumerator, "bus", "drivers", "drivers"); |
fed66db0 YW |
960 | if (k < 0) |
961 | r = log_debug_errno(k, "sd-device-enumerator: Failed to scan drivers: %m"); | |
96df036f TG |
962 | } |
963 | ||
964 | enumerator->scan_uptodate = true; | |
0a166589 | 965 | enumerator->type = DEVICE_ENUMERATION_TYPE_SUBSYSTEMS; |
96df036f TG |
966 | |
967 | return r; | |
968 | } | |
969 | ||
970 | _public_ sd_device *sd_device_enumerator_get_subsystem_first(sd_device_enumerator *enumerator) { | |
96df036f TG |
971 | assert_return(enumerator, NULL); |
972 | ||
87c3a0f9 YW |
973 | if (device_enumerator_scan_subsystems(enumerator) < 0) |
974 | return NULL; | |
975 | ||
976 | if (enumerator_sort_devices(enumerator) < 0) | |
96df036f TG |
977 | return NULL; |
978 | ||
0a166589 YW |
979 | enumerator->current_device_index = 0; |
980 | ||
981 | if (enumerator->n_devices == 0) | |
982 | return NULL; | |
96df036f | 983 | |
0a166589 | 984 | return enumerator->devices[0]; |
96df036f TG |
985 | } |
986 | ||
987 | _public_ sd_device *sd_device_enumerator_get_subsystem_next(sd_device_enumerator *enumerator) { | |
988 | assert_return(enumerator, NULL); | |
989 | ||
4005d321 | 990 | if (!enumerator->scan_uptodate || |
87c3a0f9 | 991 | !enumerator->sorted || |
0a166589 YW |
992 | enumerator->type != DEVICE_ENUMERATION_TYPE_SUBSYSTEMS || |
993 | enumerator->current_device_index + 1 >= enumerator->n_devices) | |
96df036f TG |
994 | return NULL; |
995 | ||
0a166589 | 996 | return enumerator->devices[++enumerator->current_device_index]; |
96df036f TG |
997 | } |
998 | ||
3c5cc23a YW |
999 | int device_enumerator_scan_devices_and_subsystems(sd_device_enumerator *enumerator) { |
1000 | int r = 0, k; | |
1001 | ||
1002 | assert(enumerator); | |
1003 | ||
1004 | if (enumerator->scan_uptodate && | |
1005 | enumerator->type == DEVICE_ENUMERATION_TYPE_ALL) | |
1006 | return 0; | |
1007 | ||
1008 | device_enumerator_unref_devices(enumerator); | |
1009 | ||
1010 | if (!set_isempty(enumerator->match_tag)) { | |
1011 | k = enumerator_scan_devices_tags(enumerator); | |
1012 | if (k < 0) | |
1013 | r = k; | |
1014 | } else if (enumerator->match_parent) { | |
1015 | k = enumerator_scan_devices_children(enumerator); | |
1016 | if (k < 0) | |
1017 | r = k; | |
1018 | } else { | |
1019 | k = enumerator_scan_dir(enumerator, "class", NULL, NULL); | |
1020 | if (k < 0) | |
1021 | r = log_debug_errno(k, "sd-device-enumerator: Failed to scan /sys/class: %m"); | |
1022 | ||
1023 | k = enumerator_scan_dir(enumerator, "bus", "devices", NULL); | |
1024 | if (k < 0) | |
1025 | r = log_debug_errno(k, "sd-device-enumerator: Failed to scan /sys/bus: %m"); | |
1026 | ||
1027 | if (match_subsystem(enumerator, "module")) { | |
1028 | k = enumerator_scan_dir_and_add_devices(enumerator, "module", NULL, NULL); | |
1029 | if (k < 0) | |
1030 | r = log_debug_errno(k, "sd-device-enumerator: Failed to scan modules: %m"); | |
1031 | } | |
1032 | if (match_subsystem(enumerator, "subsystem")) { | |
1033 | k = enumerator_scan_dir_and_add_devices(enumerator, "bus", NULL, NULL); | |
1034 | if (k < 0) | |
1035 | r = log_debug_errno(k, "sd-device-enumerator: Failed to scan subsystems: %m"); | |
1036 | } | |
1037 | ||
1038 | if (match_subsystem(enumerator, "drivers")) { | |
1039 | k = enumerator_scan_dir(enumerator, "bus", "drivers", "drivers"); | |
1040 | if (k < 0) | |
1041 | r = log_debug_errno(k, "sd-device-enumerator: Failed to scan drivers: %m"); | |
1042 | } | |
1043 | } | |
1044 | ||
1045 | enumerator->scan_uptodate = true; | |
1046 | enumerator->type = DEVICE_ENUMERATION_TYPE_ALL; | |
1047 | ||
1048 | return r; | |
1049 | } | |
1050 | ||
96df036f TG |
1051 | sd_device *device_enumerator_get_first(sd_device_enumerator *enumerator) { |
1052 | assert_return(enumerator, NULL); | |
1053 | ||
0a166589 YW |
1054 | if (!enumerator->scan_uptodate) |
1055 | return NULL; | |
1056 | ||
87c3a0f9 YW |
1057 | if (enumerator_sort_devices(enumerator) < 0) |
1058 | return NULL; | |
1059 | ||
0a166589 YW |
1060 | enumerator->current_device_index = 0; |
1061 | ||
1062 | if (enumerator->n_devices == 0) | |
1063 | return NULL; | |
1064 | ||
1065 | return enumerator->devices[0]; | |
96df036f TG |
1066 | } |
1067 | ||
1068 | sd_device *device_enumerator_get_next(sd_device_enumerator *enumerator) { | |
1069 | assert_return(enumerator, NULL); | |
1070 | ||
0a166589 | 1071 | if (!enumerator->scan_uptodate || |
87c3a0f9 | 1072 | !enumerator->sorted || |
0a166589 YW |
1073 | enumerator->current_device_index + 1 >= enumerator->n_devices) |
1074 | return NULL; | |
96df036f | 1075 | |
0a166589 | 1076 | return enumerator->devices[++enumerator->current_device_index]; |
96df036f | 1077 | } |
708474c5 YW |
1078 | |
1079 | sd_device **device_enumerator_get_devices(sd_device_enumerator *enumerator, size_t *ret_n_devices) { | |
1080 | assert(enumerator); | |
1081 | assert(ret_n_devices); | |
1082 | ||
1083 | if (!enumerator->scan_uptodate) | |
1084 | return NULL; | |
1085 | ||
87c3a0f9 YW |
1086 | if (enumerator_sort_devices(enumerator) < 0) |
1087 | return NULL; | |
1088 | ||
708474c5 YW |
1089 | *ret_n_devices = enumerator->n_devices; |
1090 | return enumerator->devices; | |
1091 | } |