]>
Commit | Line | Data |
---|---|---|
64ccdf82 KS |
1 | /* |
2 | * libudev - interface to udev device information | |
3 | * | |
4 | * Copyright (C) 2008 Kay Sievers <kay.sievers@vrfy.org> | |
f503f6b2 | 5 | * Copyright (C) 2009 Alan Jenkins <alan-jenkins@tuffmail.co.uk> |
64ccdf82 | 6 | * |
4061ab9f KS |
7 | * This library is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Lesser General Public | |
9 | * License as published by the Free Software Foundation; either | |
10 | * version 2.1 of the License, or (at your option) any later version. | |
64ccdf82 KS |
11 | */ |
12 | ||
13 | #include <stdio.h> | |
14 | #include <stdlib.h> | |
15 | #include <stddef.h> | |
16 | #include <unistd.h> | |
17 | #include <errno.h> | |
18 | #include <string.h> | |
19 | #include <dirent.h> | |
20 | #include <fcntl.h> | |
f503f6b2 | 21 | #include <limits.h> |
64ccdf82 KS |
22 | #include <sys/stat.h> |
23 | ||
24 | #include "libudev.h" | |
25 | #include "libudev-private.h" | |
26 | ||
ce1d6d7f KS |
27 | /** |
28 | * SECTION:libudev-queue | |
29 | * @short_description: access to currently active events | |
30 | * | |
a7d29d15 KS |
31 | * The udev daemon processes events asynchronously. All events which do not have |
32 | * interdependencies run in parallel. This exports the current state of the | |
33 | * event processing queue, and the current event sequence numbers from the kernel | |
ce1d6d7f KS |
34 | * and the udev daemon. |
35 | */ | |
36 | ||
37 | /** | |
38 | * udev_queue: | |
39 | * | |
40 | * Opaque object representing the current event queue in the udev daemon. | |
41 | */ | |
64ccdf82 KS |
42 | struct udev_queue { |
43 | struct udev *udev; | |
44 | int refcount; | |
869c9031 KS |
45 | struct udev_list queue_list; |
46 | struct udev_list failed_list; | |
64ccdf82 KS |
47 | }; |
48 | ||
8d6bc73a KS |
49 | /** |
50 | * udev_queue_new: | |
51 | * @udev: udev library context | |
52 | * | |
53 | * The initial refcount is 1, and needs to be decremented to | |
54 | * release the resources of the udev queue context. | |
55 | * | |
56 | * Returns: the udev queue context, or #NULL on error. | |
57 | **/ | |
666fcf03 | 58 | UDEV_EXPORT struct udev_queue *udev_queue_new(struct udev *udev) |
64ccdf82 KS |
59 | { |
60 | struct udev_queue *udev_queue; | |
61 | ||
62 | if (udev == NULL) | |
63 | return NULL; | |
64 | ||
b29a5e4a | 65 | udev_queue = calloc(1, sizeof(struct udev_queue)); |
64ccdf82 KS |
66 | if (udev_queue == NULL) |
67 | return NULL; | |
64ccdf82 KS |
68 | udev_queue->refcount = 1; |
69 | udev_queue->udev = udev; | |
869c9031 KS |
70 | udev_list_init(udev, &udev_queue->queue_list, false); |
71 | udev_list_init(udev, &udev_queue->failed_list, false); | |
64ccdf82 KS |
72 | return udev_queue; |
73 | } | |
74 | ||
8d6bc73a KS |
75 | /** |
76 | * udev_queue_ref: | |
77 | * @udev_queue: udev queue context | |
78 | * | |
79 | * Take a reference of a udev queue context. | |
80 | * | |
81 | * Returns: the same udev queue context. | |
82 | **/ | |
666fcf03 | 83 | UDEV_EXPORT struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue) |
64ccdf82 KS |
84 | { |
85 | if (udev_queue == NULL) | |
86 | return NULL; | |
87 | udev_queue->refcount++; | |
88 | return udev_queue; | |
89 | } | |
90 | ||
8d6bc73a KS |
91 | /** |
92 | * udev_queue_unref: | |
93 | * @udev_queue: udev queue context | |
94 | * | |
95 | * Drop a reference of a udev queue context. If the refcount reaches zero, | |
96 | * the resources of the queue context will be released. | |
97 | **/ | |
666fcf03 | 98 | UDEV_EXPORT void udev_queue_unref(struct udev_queue *udev_queue) |
64ccdf82 KS |
99 | { |
100 | if (udev_queue == NULL) | |
101 | return; | |
102 | udev_queue->refcount--; | |
103 | if (udev_queue->refcount > 0) | |
104 | return; | |
869c9031 KS |
105 | udev_list_cleanup(&udev_queue->queue_list); |
106 | udev_list_cleanup(&udev_queue->failed_list); | |
64ccdf82 KS |
107 | free(udev_queue); |
108 | } | |
109 | ||
8d6bc73a KS |
110 | /** |
111 | * udev_queue_get_udev: | |
112 | * @udev_queue: udev queue context | |
113 | * | |
114 | * Retrieve the udev library context the queue context was created with. | |
115 | * | |
116 | * Returns: the udev library context. | |
117 | **/ | |
666fcf03 | 118 | UDEV_EXPORT struct udev *udev_queue_get_udev(struct udev_queue *udev_queue) |
64ccdf82 KS |
119 | { |
120 | if (udev_queue == NULL) | |
121 | return NULL; | |
122 | return udev_queue->udev; | |
123 | } | |
124 | ||
f503f6b2 | 125 | unsigned long long int udev_get_kernel_seqnum(struct udev *udev) |
64ccdf82 KS |
126 | { |
127 | char filename[UTIL_PATH_SIZE]; | |
128 | unsigned long long int seqnum; | |
129 | int fd; | |
130 | char buf[32]; | |
131 | ssize_t len; | |
132 | ||
f503f6b2 | 133 | util_strscpyl(filename, sizeof(filename), udev_get_sys_path(udev), "/kernel/uevent_seqnum", NULL); |
0c5c4804 | 134 | fd = open(filename, O_RDONLY|O_CLOEXEC); |
64ccdf82 KS |
135 | if (fd < 0) |
136 | return 0; | |
137 | len = read(fd, buf, sizeof(buf)); | |
138 | close(fd); | |
139 | if (len <= 2) | |
140 | return 0; | |
141 | buf[len-1] = '\0'; | |
142 | seqnum = strtoull(buf, NULL, 10); | |
64ccdf82 KS |
143 | return seqnum; |
144 | } | |
145 | ||
8d6bc73a KS |
146 | /** |
147 | * udev_queue_get_kernel_seqnum: | |
148 | * @udev_queue: udev queue context | |
149 | * | |
150 | * Returns: the current kernel event sequence number. | |
151 | **/ | |
666fcf03 | 152 | UDEV_EXPORT unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue) |
64ccdf82 | 153 | { |
64ccdf82 | 154 | unsigned long long int seqnum; |
64ccdf82 KS |
155 | |
156 | if (udev_queue == NULL) | |
157 | return -EINVAL; | |
f503f6b2 AJ |
158 | |
159 | seqnum = udev_get_kernel_seqnum(udev_queue->udev); | |
86b57788 | 160 | dbg(udev_queue->udev, "seqnum=%llu\n", seqnum); |
64ccdf82 KS |
161 | return seqnum; |
162 | } | |
163 | ||
f503f6b2 AJ |
164 | int udev_queue_read_seqnum(FILE *queue_file, unsigned long long int *seqnum) |
165 | { | |
166 | if (fread(seqnum, sizeof(unsigned long long int), 1, queue_file) != 1) | |
167 | return -1; | |
168 | ||
169 | return 0; | |
170 | } | |
171 | ||
172 | ssize_t udev_queue_skip_devpath(FILE *queue_file) | |
173 | { | |
174 | unsigned short int len; | |
175 | ||
176 | if (fread(&len, sizeof(unsigned short int), 1, queue_file) == 1) { | |
177 | char devpath[len]; | |
178 | ||
179 | /* use fread to skip, fseek might drop buffered data */ | |
180 | if (fread(devpath, 1, len, queue_file) == len) | |
181 | return len; | |
182 | } | |
183 | ||
184 | return -1; | |
185 | } | |
186 | ||
187 | ssize_t udev_queue_read_devpath(FILE *queue_file, char *devpath, size_t size) | |
188 | { | |
189 | unsigned short int read_bytes = 0; | |
190 | unsigned short int len; | |
191 | ||
192 | if (fread(&len, sizeof(unsigned short int), 1, queue_file) != 1) | |
193 | return -1; | |
194 | ||
195 | read_bytes = (len < size - 1) ? len : size - 1; | |
196 | if (fread(devpath, 1, read_bytes, queue_file) != read_bytes) | |
197 | return -1; | |
198 | devpath[read_bytes] = '\0'; | |
199 | ||
200 | /* if devpath was too long, skip unread characters */ | |
201 | if (read_bytes != len) { | |
202 | unsigned short int skip_bytes = len - read_bytes; | |
203 | char buf[skip_bytes]; | |
204 | ||
205 | if (fread(buf, 1, skip_bytes, queue_file) != skip_bytes) | |
206 | return -1; | |
207 | } | |
208 | ||
209 | return read_bytes; | |
210 | } | |
211 | ||
212 | static FILE *open_queue_file(struct udev_queue *udev_queue, unsigned long long int *seqnum_start) | |
11d5eec2 KS |
213 | { |
214 | char filename[UTIL_PATH_SIZE]; | |
f503f6b2 | 215 | FILE *queue_file; |
11d5eec2 | 216 | |
4ec9c3e7 | 217 | util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev_queue->udev), "/queue.bin", NULL); |
0c5c4804 | 218 | queue_file = fopen(filename, "re"); |
f503f6b2 AJ |
219 | if (queue_file == NULL) |
220 | return NULL; | |
221 | ||
222 | if (udev_queue_read_seqnum(queue_file, seqnum_start) < 0) { | |
223 | err(udev_queue->udev, "corrupt queue file\n"); | |
224 | fclose(queue_file); | |
225 | return NULL; | |
226 | } | |
227 | ||
228 | return queue_file; | |
229 | } | |
230 | ||
8d6bc73a KS |
231 | /** |
232 | * udev_queue_get_udev_seqnum: | |
233 | * @udev_queue: udev queue context | |
234 | * | |
235 | * Returns: the last known udev event sequence number. | |
236 | **/ | |
666fcf03 | 237 | UDEV_EXPORT unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue) |
f503f6b2 AJ |
238 | { |
239 | unsigned long long int seqnum_udev; | |
240 | FILE *queue_file; | |
241 | ||
242 | queue_file = open_queue_file(udev_queue, &seqnum_udev); | |
243 | if (queue_file == NULL) | |
11d5eec2 | 244 | return 0; |
f503f6b2 | 245 | |
88cbfb09 | 246 | for (;;) { |
f503f6b2 AJ |
247 | unsigned long long int seqnum; |
248 | ssize_t devpath_len; | |
249 | ||
250 | if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) | |
251 | break; | |
252 | devpath_len = udev_queue_skip_devpath(queue_file); | |
253 | if (devpath_len < 0) | |
254 | break; | |
255 | if (devpath_len > 0) | |
256 | seqnum_udev = seqnum; | |
257 | } | |
258 | ||
259 | fclose(queue_file); | |
260 | return seqnum_udev; | |
261 | } | |
262 | ||
8d6bc73a KS |
263 | /** |
264 | * udev_queue_get_udev_is_active: | |
265 | * @udev_queue: udev queue context | |
266 | * | |
267 | * Returns: a flag indicating if udev is active. | |
268 | **/ | |
666fcf03 | 269 | UDEV_EXPORT int udev_queue_get_udev_is_active(struct udev_queue *udev_queue) |
f503f6b2 AJ |
270 | { |
271 | unsigned long long int seqnum_start; | |
272 | FILE *queue_file; | |
273 | ||
274 | queue_file = open_queue_file(udev_queue, &seqnum_start); | |
275 | if (queue_file == NULL) | |
276 | return 0; | |
277 | ||
278 | fclose(queue_file); | |
279 | return 1; | |
11d5eec2 KS |
280 | } |
281 | ||
8d6bc73a KS |
282 | /** |
283 | * udev_queue_get_queue_is_empty: | |
284 | * @udev_queue: udev queue context | |
285 | * | |
286 | * Returns: a flag indicating if udev is currently handling events. | |
287 | **/ | |
666fcf03 | 288 | UDEV_EXPORT int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue) |
64ccdf82 | 289 | { |
64ccdf82 | 290 | unsigned long long int seqnum_kernel; |
f503f6b2 AJ |
291 | unsigned long long int seqnum_udev = 0; |
292 | int queued = 0; | |
293 | int is_empty = 0; | |
294 | FILE *queue_file; | |
64ccdf82 KS |
295 | |
296 | if (udev_queue == NULL) | |
297 | return -EINVAL; | |
f503f6b2 AJ |
298 | queue_file = open_queue_file(udev_queue, &seqnum_udev); |
299 | if (queue_file == NULL) | |
300 | return 1; | |
301 | ||
e0d9c042 | 302 | for (;;) { |
f503f6b2 AJ |
303 | unsigned long long int seqnum; |
304 | ssize_t devpath_len; | |
305 | ||
306 | if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) | |
307 | break; | |
308 | devpath_len = udev_queue_skip_devpath(queue_file); | |
309 | if (devpath_len < 0) | |
310 | break; | |
311 | ||
312 | if (devpath_len > 0) { | |
313 | queued++; | |
314 | seqnum_udev = seqnum; | |
315 | } else { | |
316 | queued--; | |
317 | } | |
318 | } | |
319 | ||
320 | if (queued > 0) { | |
86b57788 | 321 | dbg(udev_queue->udev, "queue is not empty\n"); |
f503f6b2 | 322 | goto out; |
64ccdf82 | 323 | } |
f503f6b2 | 324 | |
64ccdf82 | 325 | seqnum_kernel = udev_queue_get_kernel_seqnum(udev_queue); |
f503f6b2 AJ |
326 | if (seqnum_udev < seqnum_kernel) { |
327 | dbg(udev_queue->udev, "queue is empty but kernel events still pending [%llu]<->[%llu]\n", | |
328 | seqnum_kernel, seqnum_udev); | |
329 | goto out; | |
64ccdf82 | 330 | } |
f503f6b2 AJ |
331 | |
332 | dbg(udev_queue->udev, "queue is empty\n"); | |
333 | is_empty = 1; | |
334 | ||
335 | out: | |
336 | fclose(queue_file); | |
337 | return is_empty; | |
64ccdf82 KS |
338 | } |
339 | ||
8d6bc73a KS |
340 | /** |
341 | * udev_queue_get_seqnum_sequence_is_finished: | |
342 | * @udev_queue: udev queue context | |
343 | * @start: first event sequence number | |
344 | * @end: last event sequence number | |
345 | * | |
2906cbba | 346 | * Returns: a flag indicating if any of the sequence numbers in the given range is currently active. |
8d6bc73a | 347 | **/ |
666fcf03 | 348 | UDEV_EXPORT int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue, |
f503f6b2 | 349 | unsigned long long int start, unsigned long long int end) |
64ccdf82 | 350 | { |
e0d9c042 | 351 | unsigned long long int seqnum; |
f503f6b2 AJ |
352 | ssize_t devpath_len; |
353 | int unfinished; | |
354 | FILE *queue_file; | |
64ccdf82 KS |
355 | |
356 | if (udev_queue == NULL) | |
357 | return -EINVAL; | |
f503f6b2 AJ |
358 | queue_file = open_queue_file(udev_queue, &seqnum); |
359 | if (queue_file == NULL) | |
360 | return 1; | |
361 | if (start < seqnum) | |
362 | start = seqnum; | |
9e6273c7 FZ |
363 | if (start > end) { |
364 | fclose(queue_file); | |
f503f6b2 | 365 | return 1; |
9e6273c7 FZ |
366 | } |
367 | if (end - start > INT_MAX - 1) { | |
368 | fclose(queue_file); | |
f503f6b2 | 369 | return -EOVERFLOW; |
9e6273c7 | 370 | } |
f503f6b2 | 371 | |
e0d9c042 KS |
372 | /* |
373 | * we might start with 0, and handle the initial seqnum | |
374 | * only when we find an entry in the queue file | |
375 | **/ | |
376 | unfinished = end - start; | |
377 | ||
378 | do { | |
f503f6b2 AJ |
379 | if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) |
380 | break; | |
381 | devpath_len = udev_queue_skip_devpath(queue_file); | |
382 | if (devpath_len < 0) | |
383 | break; | |
384 | ||
e0d9c042 KS |
385 | /* |
386 | * we might start with an empty or re-build queue file, where | |
387 | * the initial seqnum is not recorded as finished | |
388 | */ | |
389 | if (start == seqnum && devpath_len > 0) | |
390 | unfinished++; | |
391 | ||
f503f6b2 AJ |
392 | if (devpath_len == 0) { |
393 | if (seqnum >= start && seqnum <= end) | |
394 | unfinished--; | |
395 | } | |
e0d9c042 KS |
396 | } while (unfinished > 0); |
397 | ||
f503f6b2 AJ |
398 | fclose(queue_file); |
399 | ||
400 | return (unfinished == 0); | |
401 | } | |
402 | ||
8d6bc73a KS |
403 | /** |
404 | * udev_queue_get_seqnum_is_finished: | |
405 | * @udev_queue: udev queue context | |
406 | * @seqnum: sequence number | |
407 | * | |
2906cbba | 408 | * Returns: a flag indicating if the given sequence number is currently active. |
8d6bc73a | 409 | **/ |
666fcf03 | 410 | UDEV_EXPORT int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum) |
f503f6b2 AJ |
411 | { |
412 | if (!udev_queue_get_seqnum_sequence_is_finished(udev_queue, seqnum, seqnum)) | |
64ccdf82 | 413 | return 0; |
f503f6b2 | 414 | |
86b57788 | 415 | dbg(udev_queue->udev, "seqnum: %llu finished\n", seqnum); |
64ccdf82 KS |
416 | return 1; |
417 | } | |
418 | ||
8d6bc73a KS |
419 | /** |
420 | * udev_queue_get_queued_list_entry: | |
421 | * @udev_queue: udev queue context | |
422 | * | |
423 | * Returns: the first entry of the list of queued events. | |
424 | **/ | |
666fcf03 | 425 | UDEV_EXPORT struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue) |
64ccdf82 | 426 | { |
f503f6b2 AJ |
427 | unsigned long long int seqnum; |
428 | FILE *queue_file; | |
64ccdf82 KS |
429 | |
430 | if (udev_queue == NULL) | |
431 | return NULL; | |
869c9031 | 432 | udev_list_cleanup(&udev_queue->queue_list); |
f503f6b2 AJ |
433 | |
434 | queue_file = open_queue_file(udev_queue, &seqnum); | |
435 | if (queue_file == NULL) | |
64ccdf82 | 436 | return NULL; |
f503f6b2 | 437 | |
88cbfb09 | 438 | for (;;) { |
64ccdf82 | 439 | char syspath[UTIL_PATH_SIZE]; |
065db052 KS |
440 | char *s; |
441 | size_t l; | |
64ccdf82 | 442 | ssize_t len; |
f503f6b2 AJ |
443 | char seqnum_str[32]; |
444 | struct udev_list_entry *list_entry; | |
445 | ||
446 | if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) | |
447 | break; | |
448 | snprintf(seqnum_str, sizeof(seqnum_str), "%llu", seqnum); | |
64ccdf82 | 449 | |
065db052 KS |
450 | s = syspath; |
451 | l = util_strpcpyl(&s, sizeof(syspath), udev_get_sys_path(udev_queue->udev), NULL); | |
f503f6b2 AJ |
452 | len = udev_queue_read_devpath(queue_file, s, l); |
453 | if (len < 0) | |
454 | break; | |
455 | ||
456 | if (len > 0) { | |
869c9031 | 457 | udev_list_entry_add(&udev_queue->queue_list, syspath, seqnum_str); |
f503f6b2 AJ |
458 | } else { |
459 | udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_queue->queue_list)) { | |
460 | if (strcmp(seqnum_str, udev_list_entry_get_value(list_entry)) == 0) { | |
461 | udev_list_entry_delete(list_entry); | |
462 | break; | |
463 | } | |
464 | } | |
465 | } | |
64ccdf82 | 466 | } |
f503f6b2 AJ |
467 | fclose(queue_file); |
468 | ||
8cd2e972 | 469 | return udev_list_get_entry(&udev_queue->queue_list); |
64ccdf82 KS |
470 | } |
471 | ||
8d6bc73a KS |
472 | /** |
473 | * udev_queue_get_failed_list_entry: | |
474 | * @udev_queue: udev queue context | |
475 | * | |
476 | * Returns: the first entry of the list of recorded failed events. | |
477 | **/ | |
666fcf03 | 478 | UDEV_EXPORT struct udev_list_entry *udev_queue_get_failed_list_entry(struct udev_queue *udev_queue) |
64ccdf82 KS |
479 | { |
480 | char path[UTIL_PATH_SIZE]; | |
481 | DIR *dir; | |
482 | struct dirent *dent; | |
483 | ||
484 | if (udev_queue == NULL) | |
485 | return NULL; | |
869c9031 | 486 | udev_list_cleanup(&udev_queue->failed_list); |
4ec9c3e7 | 487 | util_strscpyl(path, sizeof(path), udev_get_run_path(udev_queue->udev), "/failed", NULL); |
64ccdf82 KS |
488 | dir = opendir(path); |
489 | if (dir == NULL) | |
490 | return NULL; | |
491 | for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { | |
492 | char filename[UTIL_PATH_SIZE]; | |
493 | char syspath[UTIL_PATH_SIZE]; | |
065db052 KS |
494 | char *s; |
495 | size_t l; | |
64ccdf82 | 496 | ssize_t len; |
065db052 | 497 | struct stat statbuf; |
64ccdf82 KS |
498 | |
499 | if (dent->d_name[0] == '.') | |
500 | continue; | |
065db052 KS |
501 | s = syspath; |
502 | l = util_strpcpyl(&s, sizeof(syspath), udev_get_sys_path(udev_queue->udev), NULL); | |
e6c1a2bd | 503 | len = readlinkat(dirfd(dir), dent->d_name, s, l); |
60067cc7 | 504 | if (len <= 0 || (size_t)len == l) |
64ccdf82 | 505 | continue; |
065db052 | 506 | s[len] = '\0'; |
86b57788 | 507 | dbg(udev_queue->udev, "found '%s' [%s]\n", syspath, dent->d_name); |
065db052 | 508 | util_strscpyl(filename, sizeof(filename), syspath, "/uevent", NULL); |
64ccdf82 KS |
509 | if (stat(filename, &statbuf) != 0) |
510 | continue; | |
869c9031 | 511 | udev_list_entry_add(&udev_queue->failed_list, syspath, NULL); |
64ccdf82 KS |
512 | } |
513 | closedir(dir); | |
8cd2e972 | 514 | return udev_list_get_entry(&udev_queue->failed_list); |
64ccdf82 | 515 | } |