]>
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 | 42 | struct udev_queue { |
912541b0 KS |
43 | struct udev *udev; |
44 | int refcount; | |
45 | struct udev_list queue_list; | |
64ccdf82 KS |
46 | }; |
47 | ||
8d6bc73a KS |
48 | /** |
49 | * udev_queue_new: | |
50 | * @udev: udev library context | |
51 | * | |
52 | * The initial refcount is 1, and needs to be decremented to | |
53 | * release the resources of the udev queue context. | |
54 | * | |
55 | * Returns: the udev queue context, or #NULL on error. | |
56 | **/ | |
54cf0b7f | 57 | _public_ struct udev_queue *udev_queue_new(struct udev *udev) |
64ccdf82 | 58 | { |
912541b0 KS |
59 | struct udev_queue *udev_queue; |
60 | ||
61 | if (udev == NULL) | |
62 | return NULL; | |
63 | ||
64 | udev_queue = calloc(1, sizeof(struct udev_queue)); | |
65 | if (udev_queue == NULL) | |
66 | return NULL; | |
67 | udev_queue->refcount = 1; | |
68 | udev_queue->udev = udev; | |
69 | udev_list_init(udev, &udev_queue->queue_list, false); | |
70 | return udev_queue; | |
64ccdf82 KS |
71 | } |
72 | ||
8d6bc73a KS |
73 | /** |
74 | * udev_queue_ref: | |
75 | * @udev_queue: udev queue context | |
76 | * | |
77 | * Take a reference of a udev queue context. | |
78 | * | |
79 | * Returns: the same udev queue context. | |
80 | **/ | |
54cf0b7f | 81 | _public_ struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue) |
64ccdf82 | 82 | { |
912541b0 KS |
83 | if (udev_queue == NULL) |
84 | return NULL; | |
85 | udev_queue->refcount++; | |
86 | return udev_queue; | |
64ccdf82 KS |
87 | } |
88 | ||
8d6bc73a KS |
89 | /** |
90 | * udev_queue_unref: | |
91 | * @udev_queue: udev queue context | |
92 | * | |
93 | * Drop a reference of a udev queue context. If the refcount reaches zero, | |
94 | * the resources of the queue context will be released. | |
c1959569 KS |
95 | * |
96 | * Returns: the passed queue context if it has still an active reference, or #NULL otherwise. | |
8d6bc73a | 97 | **/ |
20bbd54f | 98 | _public_ struct udev_queue *udev_queue_unref(struct udev_queue *udev_queue) |
64ccdf82 | 99 | { |
912541b0 | 100 | if (udev_queue == NULL) |
20bbd54f | 101 | return NULL; |
912541b0 KS |
102 | udev_queue->refcount--; |
103 | if (udev_queue->refcount > 0) | |
20bbd54f | 104 | return udev_queue; |
912541b0 KS |
105 | udev_list_cleanup(&udev_queue->queue_list); |
106 | free(udev_queue); | |
20bbd54f | 107 | return NULL; |
64ccdf82 KS |
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 | **/ | |
54cf0b7f | 118 | _public_ struct udev *udev_queue_get_udev(struct udev_queue *udev_queue) |
64ccdf82 | 119 | { |
912541b0 KS |
120 | if (udev_queue == NULL) |
121 | return NULL; | |
122 | return udev_queue->udev; | |
64ccdf82 KS |
123 | } |
124 | ||
f503f6b2 | 125 | unsigned long long int udev_get_kernel_seqnum(struct udev *udev) |
64ccdf82 | 126 | { |
912541b0 KS |
127 | unsigned long long int seqnum; |
128 | int fd; | |
129 | char buf[32]; | |
130 | ssize_t len; | |
131 | ||
6ada823a | 132 | fd = open("/sys/kernel/uevent_seqnum", O_RDONLY|O_CLOEXEC); |
912541b0 KS |
133 | if (fd < 0) |
134 | return 0; | |
135 | len = read(fd, buf, sizeof(buf)); | |
136 | close(fd); | |
137 | if (len <= 2) | |
138 | return 0; | |
139 | buf[len-1] = '\0'; | |
140 | seqnum = strtoull(buf, NULL, 10); | |
141 | return seqnum; | |
64ccdf82 KS |
142 | } |
143 | ||
8d6bc73a KS |
144 | /** |
145 | * udev_queue_get_kernel_seqnum: | |
146 | * @udev_queue: udev queue context | |
147 | * | |
21dbe43a KS |
148 | * Get the current kernel event sequence number. |
149 | * | |
150 | * Returns: the sequence number. | |
8d6bc73a | 151 | **/ |
54cf0b7f | 152 | _public_ unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue) |
64ccdf82 | 153 | { |
912541b0 | 154 | unsigned long long int seqnum; |
64ccdf82 | 155 | |
912541b0 KS |
156 | if (udev_queue == NULL) |
157 | return -EINVAL; | |
f503f6b2 | 158 | |
912541b0 | 159 | seqnum = udev_get_kernel_seqnum(udev_queue->udev); |
912541b0 | 160 | return seqnum; |
64ccdf82 KS |
161 | } |
162 | ||
f503f6b2 AJ |
163 | int udev_queue_read_seqnum(FILE *queue_file, unsigned long long int *seqnum) |
164 | { | |
912541b0 KS |
165 | if (fread(seqnum, sizeof(unsigned long long int), 1, queue_file) != 1) |
166 | return -1; | |
f503f6b2 | 167 | |
912541b0 | 168 | return 0; |
f503f6b2 AJ |
169 | } |
170 | ||
171 | ssize_t udev_queue_skip_devpath(FILE *queue_file) | |
172 | { | |
912541b0 | 173 | unsigned short int len; |
f503f6b2 | 174 | |
912541b0 | 175 | if (fread(&len, sizeof(unsigned short int), 1, queue_file) == 1) { |
fc863dea | 176 | char *devpath = alloca(len); |
f503f6b2 | 177 | |
912541b0 KS |
178 | /* use fread to skip, fseek might drop buffered data */ |
179 | if (fread(devpath, 1, len, queue_file) == len) | |
180 | return len; | |
181 | } | |
f503f6b2 | 182 | |
912541b0 | 183 | return -1; |
f503f6b2 AJ |
184 | } |
185 | ||
186 | ssize_t udev_queue_read_devpath(FILE *queue_file, char *devpath, size_t size) | |
187 | { | |
912541b0 KS |
188 | unsigned short int read_bytes = 0; |
189 | unsigned short int len; | |
f503f6b2 | 190 | |
912541b0 KS |
191 | if (fread(&len, sizeof(unsigned short int), 1, queue_file) != 1) |
192 | return -1; | |
f503f6b2 | 193 | |
912541b0 KS |
194 | read_bytes = (len < size - 1) ? len : size - 1; |
195 | if (fread(devpath, 1, read_bytes, queue_file) != read_bytes) | |
196 | return -1; | |
197 | devpath[read_bytes] = '\0'; | |
f503f6b2 | 198 | |
912541b0 KS |
199 | /* if devpath was too long, skip unread characters */ |
200 | if (read_bytes != len) { | |
201 | unsigned short int skip_bytes = len - read_bytes; | |
fc863dea | 202 | char *buf = alloca(skip_bytes); |
f503f6b2 | 203 | |
912541b0 KS |
204 | if (fread(buf, 1, skip_bytes, queue_file) != skip_bytes) |
205 | return -1; | |
206 | } | |
f503f6b2 | 207 | |
912541b0 | 208 | return read_bytes; |
f503f6b2 AJ |
209 | } |
210 | ||
211 | static FILE *open_queue_file(struct udev_queue *udev_queue, unsigned long long int *seqnum_start) | |
11d5eec2 | 212 | { |
912541b0 | 213 | FILE *queue_file; |
11d5eec2 | 214 | |
a267bebf | 215 | queue_file = fopen("/run/udev/queue.bin", "re"); |
912541b0 KS |
216 | if (queue_file == NULL) |
217 | return NULL; | |
f503f6b2 | 218 | |
912541b0 | 219 | if (udev_queue_read_seqnum(queue_file, seqnum_start) < 0) { |
c8f8394a | 220 | udev_err(udev_queue->udev, "corrupt queue file\n"); |
912541b0 KS |
221 | fclose(queue_file); |
222 | return NULL; | |
223 | } | |
f503f6b2 | 224 | |
912541b0 | 225 | return queue_file; |
f503f6b2 AJ |
226 | } |
227 | ||
8d6bc73a KS |
228 | /** |
229 | * udev_queue_get_udev_seqnum: | |
230 | * @udev_queue: udev queue context | |
231 | * | |
21dbe43a KS |
232 | * Get the last known udev event sequence number. |
233 | * | |
234 | * Returns: the sequence number. | |
8d6bc73a | 235 | **/ |
54cf0b7f | 236 | _public_ unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue) |
f503f6b2 | 237 | { |
912541b0 KS |
238 | unsigned long long int seqnum_udev; |
239 | FILE *queue_file; | |
240 | ||
241 | queue_file = open_queue_file(udev_queue, &seqnum_udev); | |
242 | if (queue_file == NULL) | |
243 | return 0; | |
244 | ||
245 | for (;;) { | |
246 | unsigned long long int seqnum; | |
247 | ssize_t devpath_len; | |
248 | ||
249 | if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) | |
250 | break; | |
251 | devpath_len = udev_queue_skip_devpath(queue_file); | |
252 | if (devpath_len < 0) | |
253 | break; | |
254 | if (devpath_len > 0) | |
255 | seqnum_udev = seqnum; | |
256 | } | |
257 | ||
258 | fclose(queue_file); | |
259 | return seqnum_udev; | |
f503f6b2 AJ |
260 | } |
261 | ||
8d6bc73a KS |
262 | /** |
263 | * udev_queue_get_udev_is_active: | |
264 | * @udev_queue: udev queue context | |
265 | * | |
21dbe43a KS |
266 | * Check if udev is active on the system. |
267 | * | |
8d6bc73a KS |
268 | * Returns: a flag indicating if udev is active. |
269 | **/ | |
54cf0b7f | 270 | _public_ int udev_queue_get_udev_is_active(struct udev_queue *udev_queue) |
f503f6b2 | 271 | { |
912541b0 KS |
272 | unsigned long long int seqnum_start; |
273 | FILE *queue_file; | |
f503f6b2 | 274 | |
912541b0 KS |
275 | queue_file = open_queue_file(udev_queue, &seqnum_start); |
276 | if (queue_file == NULL) | |
277 | return 0; | |
f503f6b2 | 278 | |
912541b0 KS |
279 | fclose(queue_file); |
280 | return 1; | |
11d5eec2 KS |
281 | } |
282 | ||
8d6bc73a KS |
283 | /** |
284 | * udev_queue_get_queue_is_empty: | |
285 | * @udev_queue: udev queue context | |
286 | * | |
21dbe43a KS |
287 | * Check if udev is currently processing any events. |
288 | * | |
8d6bc73a KS |
289 | * Returns: a flag indicating if udev is currently handling events. |
290 | **/ | |
54cf0b7f | 291 | _public_ int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue) |
64ccdf82 | 292 | { |
912541b0 KS |
293 | unsigned long long int seqnum_kernel; |
294 | unsigned long long int seqnum_udev = 0; | |
295 | int queued = 0; | |
296 | int is_empty = 0; | |
297 | FILE *queue_file; | |
298 | ||
299 | if (udev_queue == NULL) | |
300 | return -EINVAL; | |
301 | queue_file = open_queue_file(udev_queue, &seqnum_udev); | |
302 | if (queue_file == NULL) | |
303 | return 1; | |
304 | ||
305 | for (;;) { | |
306 | unsigned long long int seqnum; | |
307 | ssize_t devpath_len; | |
308 | ||
309 | if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) | |
310 | break; | |
311 | devpath_len = udev_queue_skip_devpath(queue_file); | |
312 | if (devpath_len < 0) | |
313 | break; | |
314 | ||
315 | if (devpath_len > 0) { | |
316 | queued++; | |
317 | seqnum_udev = seqnum; | |
318 | } else { | |
319 | queued--; | |
320 | } | |
321 | } | |
322 | ||
baa30fbc | 323 | if (queued > 0) |
912541b0 | 324 | goto out; |
912541b0 KS |
325 | |
326 | seqnum_kernel = udev_queue_get_kernel_seqnum(udev_queue); | |
baa30fbc | 327 | if (seqnum_udev < seqnum_kernel) |
912541b0 | 328 | goto out; |
912541b0 | 329 | |
912541b0 | 330 | is_empty = 1; |
f503f6b2 AJ |
331 | |
332 | out: | |
912541b0 KS |
333 | fclose(queue_file); |
334 | return is_empty; | |
64ccdf82 KS |
335 | } |
336 | ||
8d6bc73a KS |
337 | /** |
338 | * udev_queue_get_seqnum_sequence_is_finished: | |
339 | * @udev_queue: udev queue context | |
340 | * @start: first event sequence number | |
341 | * @end: last event sequence number | |
342 | * | |
21dbe43a KS |
343 | * Check if udev is currently processing any events in a given sequence number range. |
344 | * | |
2906cbba | 345 | * Returns: a flag indicating if any of the sequence numbers in the given range is currently active. |
8d6bc73a | 346 | **/ |
54cf0b7f | 347 | _public_ int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue, |
912541b0 | 348 | unsigned long long int start, unsigned long long int end) |
64ccdf82 | 349 | { |
912541b0 KS |
350 | unsigned long long int seqnum; |
351 | ssize_t devpath_len; | |
352 | int unfinished; | |
353 | FILE *queue_file; | |
354 | ||
355 | if (udev_queue == NULL) | |
356 | return -EINVAL; | |
357 | queue_file = open_queue_file(udev_queue, &seqnum); | |
358 | if (queue_file == NULL) | |
359 | return 1; | |
360 | if (start < seqnum) | |
361 | start = seqnum; | |
362 | if (start > end) { | |
363 | fclose(queue_file); | |
364 | return 1; | |
365 | } | |
366 | if (end - start > INT_MAX - 1) { | |
367 | fclose(queue_file); | |
368 | return -EOVERFLOW; | |
369 | } | |
370 | ||
371 | /* | |
372 | * we might start with 0, and handle the initial seqnum | |
373 | * only when we find an entry in the queue file | |
374 | **/ | |
375 | unfinished = end - start; | |
376 | ||
377 | do { | |
378 | if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) | |
379 | break; | |
380 | devpath_len = udev_queue_skip_devpath(queue_file); | |
381 | if (devpath_len < 0) | |
382 | break; | |
383 | ||
384 | /* | |
385 | * we might start with an empty or re-build queue file, where | |
386 | * the initial seqnum is not recorded as finished | |
387 | */ | |
388 | if (start == seqnum && devpath_len > 0) | |
389 | unfinished++; | |
390 | ||
391 | if (devpath_len == 0) { | |
392 | if (seqnum >= start && seqnum <= end) | |
393 | unfinished--; | |
394 | } | |
395 | } while (unfinished > 0); | |
396 | ||
397 | fclose(queue_file); | |
398 | ||
399 | return (unfinished == 0); | |
f503f6b2 AJ |
400 | } |
401 | ||
8d6bc73a KS |
402 | /** |
403 | * udev_queue_get_seqnum_is_finished: | |
404 | * @udev_queue: udev queue context | |
405 | * @seqnum: sequence number | |
406 | * | |
21dbe43a KS |
407 | * Check if udev is currently processing a given sequence number. |
408 | * | |
2906cbba | 409 | * Returns: a flag indicating if the given sequence number is currently active. |
8d6bc73a | 410 | **/ |
54cf0b7f | 411 | _public_ int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum) |
f503f6b2 | 412 | { |
912541b0 KS |
413 | if (!udev_queue_get_seqnum_sequence_is_finished(udev_queue, seqnum, seqnum)) |
414 | return 0; | |
f503f6b2 | 415 | |
912541b0 | 416 | return 1; |
64ccdf82 KS |
417 | } |
418 | ||
8d6bc73a KS |
419 | /** |
420 | * udev_queue_get_queued_list_entry: | |
421 | * @udev_queue: udev queue context | |
422 | * | |
21dbe43a KS |
423 | * Get the first entry of the list of queued events. |
424 | * | |
425 | * Returns: a udev_list_entry. | |
8d6bc73a | 426 | **/ |
54cf0b7f | 427 | _public_ struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue) |
64ccdf82 | 428 | { |
912541b0 KS |
429 | unsigned long long int seqnum; |
430 | FILE *queue_file; | |
431 | ||
432 | if (udev_queue == NULL) | |
433 | return NULL; | |
434 | udev_list_cleanup(&udev_queue->queue_list); | |
435 | ||
436 | queue_file = open_queue_file(udev_queue, &seqnum); | |
437 | if (queue_file == NULL) | |
438 | return NULL; | |
439 | ||
440 | for (;;) { | |
441 | char syspath[UTIL_PATH_SIZE]; | |
442 | char *s; | |
443 | size_t l; | |
444 | ssize_t len; | |
445 | char seqnum_str[32]; | |
446 | struct udev_list_entry *list_entry; | |
447 | ||
448 | if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) | |
449 | break; | |
450 | snprintf(seqnum_str, sizeof(seqnum_str), "%llu", seqnum); | |
451 | ||
452 | s = syspath; | |
6ada823a | 453 | l = util_strpcpy(&s, sizeof(syspath), "/sys"); |
912541b0 KS |
454 | len = udev_queue_read_devpath(queue_file, s, l); |
455 | if (len < 0) | |
456 | break; | |
457 | ||
458 | if (len > 0) { | |
459 | udev_list_entry_add(&udev_queue->queue_list, syspath, seqnum_str); | |
460 | } else { | |
461 | udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_queue->queue_list)) { | |
462 | if (strcmp(seqnum_str, udev_list_entry_get_value(list_entry)) == 0) { | |
463 | udev_list_entry_delete(list_entry); | |
464 | break; | |
465 | } | |
466 | } | |
467 | } | |
468 | } | |
469 | fclose(queue_file); | |
470 | ||
471 | return udev_list_get_entry(&udev_queue->queue_list); | |
64ccdf82 | 472 | } |