]>
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 KS |
213 | char filename[UTIL_PATH_SIZE]; |
214 | FILE *queue_file; | |
11d5eec2 | 215 | |
6ada823a | 216 | util_strscpyl(filename, sizeof(filename), "/run/udev/queue.bin", NULL); |
912541b0 KS |
217 | queue_file = fopen(filename, "re"); |
218 | if (queue_file == NULL) | |
219 | return NULL; | |
f503f6b2 | 220 | |
912541b0 | 221 | if (udev_queue_read_seqnum(queue_file, seqnum_start) < 0) { |
c8f8394a | 222 | udev_err(udev_queue->udev, "corrupt queue file\n"); |
912541b0 KS |
223 | fclose(queue_file); |
224 | return NULL; | |
225 | } | |
f503f6b2 | 226 | |
912541b0 | 227 | return queue_file; |
f503f6b2 AJ |
228 | } |
229 | ||
8d6bc73a KS |
230 | /** |
231 | * udev_queue_get_udev_seqnum: | |
232 | * @udev_queue: udev queue context | |
233 | * | |
21dbe43a KS |
234 | * Get the last known udev event sequence number. |
235 | * | |
236 | * Returns: the sequence number. | |
8d6bc73a | 237 | **/ |
54cf0b7f | 238 | _public_ unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue) |
f503f6b2 | 239 | { |
912541b0 KS |
240 | unsigned long long int seqnum_udev; |
241 | FILE *queue_file; | |
242 | ||
243 | queue_file = open_queue_file(udev_queue, &seqnum_udev); | |
244 | if (queue_file == NULL) | |
245 | return 0; | |
246 | ||
247 | for (;;) { | |
248 | unsigned long long int seqnum; | |
249 | ssize_t devpath_len; | |
250 | ||
251 | if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) | |
252 | break; | |
253 | devpath_len = udev_queue_skip_devpath(queue_file); | |
254 | if (devpath_len < 0) | |
255 | break; | |
256 | if (devpath_len > 0) | |
257 | seqnum_udev = seqnum; | |
258 | } | |
259 | ||
260 | fclose(queue_file); | |
261 | return seqnum_udev; | |
f503f6b2 AJ |
262 | } |
263 | ||
8d6bc73a KS |
264 | /** |
265 | * udev_queue_get_udev_is_active: | |
266 | * @udev_queue: udev queue context | |
267 | * | |
21dbe43a KS |
268 | * Check if udev is active on the system. |
269 | * | |
8d6bc73a KS |
270 | * Returns: a flag indicating if udev is active. |
271 | **/ | |
54cf0b7f | 272 | _public_ int udev_queue_get_udev_is_active(struct udev_queue *udev_queue) |
f503f6b2 | 273 | { |
912541b0 KS |
274 | unsigned long long int seqnum_start; |
275 | FILE *queue_file; | |
f503f6b2 | 276 | |
912541b0 KS |
277 | queue_file = open_queue_file(udev_queue, &seqnum_start); |
278 | if (queue_file == NULL) | |
279 | return 0; | |
f503f6b2 | 280 | |
912541b0 KS |
281 | fclose(queue_file); |
282 | return 1; | |
11d5eec2 KS |
283 | } |
284 | ||
8d6bc73a KS |
285 | /** |
286 | * udev_queue_get_queue_is_empty: | |
287 | * @udev_queue: udev queue context | |
288 | * | |
21dbe43a KS |
289 | * Check if udev is currently processing any events. |
290 | * | |
8d6bc73a KS |
291 | * Returns: a flag indicating if udev is currently handling events. |
292 | **/ | |
54cf0b7f | 293 | _public_ int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue) |
64ccdf82 | 294 | { |
912541b0 KS |
295 | unsigned long long int seqnum_kernel; |
296 | unsigned long long int seqnum_udev = 0; | |
297 | int queued = 0; | |
298 | int is_empty = 0; | |
299 | FILE *queue_file; | |
300 | ||
301 | if (udev_queue == NULL) | |
302 | return -EINVAL; | |
303 | queue_file = open_queue_file(udev_queue, &seqnum_udev); | |
304 | if (queue_file == NULL) | |
305 | return 1; | |
306 | ||
307 | for (;;) { | |
308 | unsigned long long int seqnum; | |
309 | ssize_t devpath_len; | |
310 | ||
311 | if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) | |
312 | break; | |
313 | devpath_len = udev_queue_skip_devpath(queue_file); | |
314 | if (devpath_len < 0) | |
315 | break; | |
316 | ||
317 | if (devpath_len > 0) { | |
318 | queued++; | |
319 | seqnum_udev = seqnum; | |
320 | } else { | |
321 | queued--; | |
322 | } | |
323 | } | |
324 | ||
baa30fbc | 325 | if (queued > 0) |
912541b0 | 326 | goto out; |
912541b0 KS |
327 | |
328 | seqnum_kernel = udev_queue_get_kernel_seqnum(udev_queue); | |
baa30fbc | 329 | if (seqnum_udev < seqnum_kernel) |
912541b0 | 330 | goto out; |
912541b0 | 331 | |
912541b0 | 332 | is_empty = 1; |
f503f6b2 AJ |
333 | |
334 | out: | |
912541b0 KS |
335 | fclose(queue_file); |
336 | return is_empty; | |
64ccdf82 KS |
337 | } |
338 | ||
8d6bc73a KS |
339 | /** |
340 | * udev_queue_get_seqnum_sequence_is_finished: | |
341 | * @udev_queue: udev queue context | |
342 | * @start: first event sequence number | |
343 | * @end: last event sequence number | |
344 | * | |
21dbe43a KS |
345 | * Check if udev is currently processing any events in a given sequence number range. |
346 | * | |
2906cbba | 347 | * Returns: a flag indicating if any of the sequence numbers in the given range is currently active. |
8d6bc73a | 348 | **/ |
54cf0b7f | 349 | _public_ int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue, |
912541b0 | 350 | unsigned long long int start, unsigned long long int end) |
64ccdf82 | 351 | { |
912541b0 KS |
352 | unsigned long long int seqnum; |
353 | ssize_t devpath_len; | |
354 | int unfinished; | |
355 | FILE *queue_file; | |
356 | ||
357 | if (udev_queue == NULL) | |
358 | return -EINVAL; | |
359 | queue_file = open_queue_file(udev_queue, &seqnum); | |
360 | if (queue_file == NULL) | |
361 | return 1; | |
362 | if (start < seqnum) | |
363 | start = seqnum; | |
364 | if (start > end) { | |
365 | fclose(queue_file); | |
366 | return 1; | |
367 | } | |
368 | if (end - start > INT_MAX - 1) { | |
369 | fclose(queue_file); | |
370 | return -EOVERFLOW; | |
371 | } | |
372 | ||
373 | /* | |
374 | * we might start with 0, and handle the initial seqnum | |
375 | * only when we find an entry in the queue file | |
376 | **/ | |
377 | unfinished = end - start; | |
378 | ||
379 | do { | |
380 | if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) | |
381 | break; | |
382 | devpath_len = udev_queue_skip_devpath(queue_file); | |
383 | if (devpath_len < 0) | |
384 | break; | |
385 | ||
386 | /* | |
387 | * we might start with an empty or re-build queue file, where | |
388 | * the initial seqnum is not recorded as finished | |
389 | */ | |
390 | if (start == seqnum && devpath_len > 0) | |
391 | unfinished++; | |
392 | ||
393 | if (devpath_len == 0) { | |
394 | if (seqnum >= start && seqnum <= end) | |
395 | unfinished--; | |
396 | } | |
397 | } while (unfinished > 0); | |
398 | ||
399 | fclose(queue_file); | |
400 | ||
401 | return (unfinished == 0); | |
f503f6b2 AJ |
402 | } |
403 | ||
8d6bc73a KS |
404 | /** |
405 | * udev_queue_get_seqnum_is_finished: | |
406 | * @udev_queue: udev queue context | |
407 | * @seqnum: sequence number | |
408 | * | |
21dbe43a KS |
409 | * Check if udev is currently processing a given sequence number. |
410 | * | |
2906cbba | 411 | * Returns: a flag indicating if the given sequence number is currently active. |
8d6bc73a | 412 | **/ |
54cf0b7f | 413 | _public_ int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum) |
f503f6b2 | 414 | { |
912541b0 KS |
415 | if (!udev_queue_get_seqnum_sequence_is_finished(udev_queue, seqnum, seqnum)) |
416 | return 0; | |
f503f6b2 | 417 | |
912541b0 | 418 | return 1; |
64ccdf82 KS |
419 | } |
420 | ||
8d6bc73a KS |
421 | /** |
422 | * udev_queue_get_queued_list_entry: | |
423 | * @udev_queue: udev queue context | |
424 | * | |
21dbe43a KS |
425 | * Get the first entry of the list of queued events. |
426 | * | |
427 | * Returns: a udev_list_entry. | |
8d6bc73a | 428 | **/ |
54cf0b7f | 429 | _public_ struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue) |
64ccdf82 | 430 | { |
912541b0 KS |
431 | unsigned long long int seqnum; |
432 | FILE *queue_file; | |
433 | ||
434 | if (udev_queue == NULL) | |
435 | return NULL; | |
436 | udev_list_cleanup(&udev_queue->queue_list); | |
437 | ||
438 | queue_file = open_queue_file(udev_queue, &seqnum); | |
439 | if (queue_file == NULL) | |
440 | return NULL; | |
441 | ||
442 | for (;;) { | |
443 | char syspath[UTIL_PATH_SIZE]; | |
444 | char *s; | |
445 | size_t l; | |
446 | ssize_t len; | |
447 | char seqnum_str[32]; | |
448 | struct udev_list_entry *list_entry; | |
449 | ||
450 | if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) | |
451 | break; | |
452 | snprintf(seqnum_str, sizeof(seqnum_str), "%llu", seqnum); | |
453 | ||
454 | s = syspath; | |
6ada823a | 455 | l = util_strpcpy(&s, sizeof(syspath), "/sys"); |
912541b0 KS |
456 | len = udev_queue_read_devpath(queue_file, s, l); |
457 | if (len < 0) | |
458 | break; | |
459 | ||
460 | if (len > 0) { | |
461 | udev_list_entry_add(&udev_queue->queue_list, syspath, seqnum_str); | |
462 | } else { | |
463 | udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_queue->queue_list)) { | |
464 | if (strcmp(seqnum_str, udev_list_entry_get_value(list_entry)) == 0) { | |
465 | udev_list_entry_delete(list_entry); | |
466 | break; | |
467 | } | |
468 | } | |
469 | } | |
470 | } | |
471 | fclose(queue_file); | |
472 | ||
473 | return udev_list_get_entry(&udev_queue->queue_list); | |
64ccdf82 | 474 | } |