]>
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 | ||
27 | struct udev_queue { | |
28 | struct udev *udev; | |
29 | int refcount; | |
8cd2e972 KS |
30 | struct udev_list_node queue_list; |
31 | struct udev_list_node failed_list; | |
64ccdf82 KS |
32 | }; |
33 | ||
34 | struct udev_queue *udev_queue_new(struct udev *udev) | |
35 | { | |
36 | struct udev_queue *udev_queue; | |
37 | ||
38 | if (udev == NULL) | |
39 | return NULL; | |
40 | ||
b29a5e4a | 41 | udev_queue = calloc(1, sizeof(struct udev_queue)); |
64ccdf82 KS |
42 | if (udev_queue == NULL) |
43 | return NULL; | |
64ccdf82 KS |
44 | udev_queue->refcount = 1; |
45 | udev_queue->udev = udev; | |
8cd2e972 KS |
46 | udev_list_init(&udev_queue->queue_list); |
47 | udev_list_init(&udev_queue->failed_list); | |
64ccdf82 KS |
48 | return udev_queue; |
49 | } | |
50 | ||
51 | struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue) | |
52 | { | |
53 | if (udev_queue == NULL) | |
54 | return NULL; | |
55 | udev_queue->refcount++; | |
56 | return udev_queue; | |
57 | } | |
58 | ||
59 | void udev_queue_unref(struct udev_queue *udev_queue) | |
60 | { | |
61 | if (udev_queue == NULL) | |
62 | return; | |
63 | udev_queue->refcount--; | |
64 | if (udev_queue->refcount > 0) | |
65 | return; | |
eb8837e1 KS |
66 | udev_list_cleanup_entries(udev_queue->udev, &udev_queue->queue_list); |
67 | udev_list_cleanup_entries(udev_queue->udev, &udev_queue->failed_list); | |
64ccdf82 KS |
68 | free(udev_queue); |
69 | } | |
70 | ||
71 | struct udev *udev_queue_get_udev(struct udev_queue *udev_queue) | |
72 | { | |
73 | if (udev_queue == NULL) | |
74 | return NULL; | |
75 | return udev_queue->udev; | |
76 | } | |
77 | ||
f503f6b2 | 78 | unsigned long long int udev_get_kernel_seqnum(struct udev *udev) |
64ccdf82 KS |
79 | { |
80 | char filename[UTIL_PATH_SIZE]; | |
81 | unsigned long long int seqnum; | |
82 | int fd; | |
83 | char buf[32]; | |
84 | ssize_t len; | |
85 | ||
f503f6b2 | 86 | util_strscpyl(filename, sizeof(filename), udev_get_sys_path(udev), "/kernel/uevent_seqnum", NULL); |
64ccdf82 KS |
87 | fd = open(filename, O_RDONLY); |
88 | if (fd < 0) | |
89 | return 0; | |
90 | len = read(fd, buf, sizeof(buf)); | |
91 | close(fd); | |
92 | if (len <= 2) | |
93 | return 0; | |
94 | buf[len-1] = '\0'; | |
95 | seqnum = strtoull(buf, NULL, 10); | |
64ccdf82 KS |
96 | return seqnum; |
97 | } | |
98 | ||
f503f6b2 | 99 | unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue) |
64ccdf82 | 100 | { |
64ccdf82 | 101 | unsigned long long int seqnum; |
64ccdf82 KS |
102 | |
103 | if (udev_queue == NULL) | |
104 | return -EINVAL; | |
f503f6b2 AJ |
105 | |
106 | seqnum = udev_get_kernel_seqnum(udev_queue->udev); | |
86b57788 | 107 | dbg(udev_queue->udev, "seqnum=%llu\n", seqnum); |
64ccdf82 KS |
108 | return seqnum; |
109 | } | |
110 | ||
f503f6b2 AJ |
111 | int udev_queue_read_seqnum(FILE *queue_file, unsigned long long int *seqnum) |
112 | { | |
113 | if (fread(seqnum, sizeof(unsigned long long int), 1, queue_file) != 1) | |
114 | return -1; | |
115 | ||
116 | return 0; | |
117 | } | |
118 | ||
119 | ssize_t udev_queue_skip_devpath(FILE *queue_file) | |
120 | { | |
121 | unsigned short int len; | |
122 | ||
123 | if (fread(&len, sizeof(unsigned short int), 1, queue_file) == 1) { | |
124 | char devpath[len]; | |
125 | ||
126 | /* use fread to skip, fseek might drop buffered data */ | |
127 | if (fread(devpath, 1, len, queue_file) == len) | |
128 | return len; | |
129 | } | |
130 | ||
131 | return -1; | |
132 | } | |
133 | ||
134 | ssize_t udev_queue_read_devpath(FILE *queue_file, char *devpath, size_t size) | |
135 | { | |
136 | unsigned short int read_bytes = 0; | |
137 | unsigned short int len; | |
138 | ||
139 | if (fread(&len, sizeof(unsigned short int), 1, queue_file) != 1) | |
140 | return -1; | |
141 | ||
142 | read_bytes = (len < size - 1) ? len : size - 1; | |
143 | if (fread(devpath, 1, read_bytes, queue_file) != read_bytes) | |
144 | return -1; | |
145 | devpath[read_bytes] = '\0'; | |
146 | ||
147 | /* if devpath was too long, skip unread characters */ | |
148 | if (read_bytes != len) { | |
149 | unsigned short int skip_bytes = len - read_bytes; | |
150 | char buf[skip_bytes]; | |
151 | ||
152 | if (fread(buf, 1, skip_bytes, queue_file) != skip_bytes) | |
153 | return -1; | |
154 | } | |
155 | ||
156 | return read_bytes; | |
157 | } | |
158 | ||
159 | static FILE *open_queue_file(struct udev_queue *udev_queue, unsigned long long int *seqnum_start) | |
11d5eec2 KS |
160 | { |
161 | char filename[UTIL_PATH_SIZE]; | |
f503f6b2 | 162 | FILE *queue_file; |
11d5eec2 | 163 | |
f503f6b2 AJ |
164 | util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev_queue->udev), "/.udev/queue.bin", NULL); |
165 | queue_file = fopen(filename, "r"); | |
166 | if (queue_file == NULL) | |
167 | return NULL; | |
168 | ||
169 | if (udev_queue_read_seqnum(queue_file, seqnum_start) < 0) { | |
170 | err(udev_queue->udev, "corrupt queue file\n"); | |
171 | fclose(queue_file); | |
172 | return NULL; | |
173 | } | |
174 | ||
175 | return queue_file; | |
176 | } | |
177 | ||
178 | ||
179 | unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue) | |
180 | { | |
181 | unsigned long long int seqnum_udev; | |
182 | FILE *queue_file; | |
183 | ||
184 | queue_file = open_queue_file(udev_queue, &seqnum_udev); | |
185 | if (queue_file == NULL) | |
11d5eec2 | 186 | return 0; |
f503f6b2 AJ |
187 | |
188 | while (1) { | |
189 | unsigned long long int seqnum; | |
190 | ssize_t devpath_len; | |
191 | ||
192 | if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) | |
193 | break; | |
194 | devpath_len = udev_queue_skip_devpath(queue_file); | |
195 | if (devpath_len < 0) | |
196 | break; | |
197 | if (devpath_len > 0) | |
198 | seqnum_udev = seqnum; | |
199 | } | |
200 | ||
201 | fclose(queue_file); | |
202 | return seqnum_udev; | |
203 | } | |
204 | ||
205 | int udev_queue_get_udev_is_active(struct udev_queue *udev_queue) | |
206 | { | |
207 | unsigned long long int seqnum_start; | |
208 | FILE *queue_file; | |
209 | ||
210 | queue_file = open_queue_file(udev_queue, &seqnum_start); | |
211 | if (queue_file == NULL) | |
212 | return 0; | |
213 | ||
214 | fclose(queue_file); | |
215 | return 1; | |
11d5eec2 KS |
216 | } |
217 | ||
64ccdf82 KS |
218 | int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue) |
219 | { | |
64ccdf82 | 220 | unsigned long long int seqnum_kernel; |
f503f6b2 AJ |
221 | unsigned long long int seqnum_udev = 0; |
222 | int queued = 0; | |
223 | int is_empty = 0; | |
224 | FILE *queue_file; | |
64ccdf82 KS |
225 | |
226 | if (udev_queue == NULL) | |
227 | return -EINVAL; | |
f503f6b2 AJ |
228 | queue_file = open_queue_file(udev_queue, &seqnum_udev); |
229 | if (queue_file == NULL) | |
230 | return 1; | |
231 | ||
232 | while (1) { | |
233 | unsigned long long int seqnum; | |
234 | ssize_t devpath_len; | |
235 | ||
236 | if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) | |
237 | break; | |
238 | devpath_len = udev_queue_skip_devpath(queue_file); | |
239 | if (devpath_len < 0) | |
240 | break; | |
241 | ||
242 | if (devpath_len > 0) { | |
243 | queued++; | |
244 | seqnum_udev = seqnum; | |
245 | } else { | |
246 | queued--; | |
247 | } | |
248 | } | |
249 | ||
250 | if (queued > 0) { | |
86b57788 | 251 | dbg(udev_queue->udev, "queue is not empty\n"); |
f503f6b2 | 252 | goto out; |
64ccdf82 | 253 | } |
f503f6b2 | 254 | |
64ccdf82 | 255 | seqnum_kernel = udev_queue_get_kernel_seqnum(udev_queue); |
f503f6b2 AJ |
256 | if (seqnum_udev < seqnum_kernel) { |
257 | dbg(udev_queue->udev, "queue is empty but kernel events still pending [%llu]<->[%llu]\n", | |
258 | seqnum_kernel, seqnum_udev); | |
259 | goto out; | |
64ccdf82 | 260 | } |
f503f6b2 AJ |
261 | |
262 | dbg(udev_queue->udev, "queue is empty\n"); | |
263 | is_empty = 1; | |
264 | ||
265 | out: | |
266 | fclose(queue_file); | |
267 | return is_empty; | |
64ccdf82 KS |
268 | } |
269 | ||
f503f6b2 AJ |
270 | int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue, |
271 | unsigned long long int start, unsigned long long int end) | |
64ccdf82 | 272 | { |
f503f6b2 AJ |
273 | unsigned long long int seqnum = 0; |
274 | ssize_t devpath_len; | |
275 | int unfinished; | |
276 | FILE *queue_file; | |
64ccdf82 KS |
277 | |
278 | if (udev_queue == NULL) | |
279 | return -EINVAL; | |
f503f6b2 AJ |
280 | queue_file = open_queue_file(udev_queue, &seqnum); |
281 | if (queue_file == NULL) | |
282 | return 1; | |
283 | if (start < seqnum) | |
284 | start = seqnum; | |
285 | if (start > end) | |
286 | return 1; | |
287 | if (end - start > INT_MAX - 1) | |
288 | return -EOVERFLOW; | |
289 | unfinished = (end - start) + 1; | |
290 | ||
291 | while (unfinished > 0) { | |
292 | if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) | |
293 | break; | |
294 | devpath_len = udev_queue_skip_devpath(queue_file); | |
295 | if (devpath_len < 0) | |
296 | break; | |
297 | ||
298 | if (devpath_len == 0) { | |
299 | if (seqnum >= start && seqnum <= end) | |
300 | unfinished--; | |
301 | } | |
302 | } | |
303 | fclose(queue_file); | |
304 | ||
305 | return (unfinished == 0); | |
306 | } | |
307 | ||
308 | int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum) | |
309 | { | |
310 | if (!udev_queue_get_seqnum_sequence_is_finished(udev_queue, seqnum, seqnum)) | |
64ccdf82 | 311 | return 0; |
f503f6b2 | 312 | |
86b57788 | 313 | dbg(udev_queue->udev, "seqnum: %llu finished\n", seqnum); |
64ccdf82 KS |
314 | return 1; |
315 | } | |
316 | ||
317 | struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue) | |
318 | { | |
f503f6b2 AJ |
319 | unsigned long long int seqnum; |
320 | FILE *queue_file; | |
64ccdf82 KS |
321 | |
322 | if (udev_queue == NULL) | |
323 | return NULL; | |
eb8837e1 | 324 | udev_list_cleanup_entries(udev_queue->udev, &udev_queue->queue_list); |
f503f6b2 AJ |
325 | |
326 | queue_file = open_queue_file(udev_queue, &seqnum); | |
327 | if (queue_file == NULL) | |
64ccdf82 | 328 | return NULL; |
f503f6b2 AJ |
329 | |
330 | while (1) { | |
64ccdf82 | 331 | char syspath[UTIL_PATH_SIZE]; |
065db052 KS |
332 | char *s; |
333 | size_t l; | |
64ccdf82 | 334 | ssize_t len; |
f503f6b2 AJ |
335 | char seqnum_str[32]; |
336 | struct udev_list_entry *list_entry; | |
337 | ||
338 | if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) | |
339 | break; | |
340 | snprintf(seqnum_str, sizeof(seqnum_str), "%llu", seqnum); | |
64ccdf82 | 341 | |
065db052 KS |
342 | s = syspath; |
343 | l = util_strpcpyl(&s, sizeof(syspath), udev_get_sys_path(udev_queue->udev), NULL); | |
f503f6b2 AJ |
344 | len = udev_queue_read_devpath(queue_file, s, l); |
345 | if (len < 0) | |
346 | break; | |
347 | ||
348 | if (len > 0) { | |
349 | udev_list_entry_add(udev_queue->udev, &udev_queue->queue_list, syspath, seqnum_str, 0, 0); | |
350 | } else { | |
351 | udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_queue->queue_list)) { | |
352 | if (strcmp(seqnum_str, udev_list_entry_get_value(list_entry)) == 0) { | |
353 | udev_list_entry_delete(list_entry); | |
354 | break; | |
355 | } | |
356 | } | |
357 | } | |
64ccdf82 | 358 | } |
f503f6b2 AJ |
359 | fclose(queue_file); |
360 | ||
8cd2e972 | 361 | return udev_list_get_entry(&udev_queue->queue_list); |
64ccdf82 KS |
362 | } |
363 | ||
364 | struct udev_list_entry *udev_queue_get_failed_list_entry(struct udev_queue *udev_queue) | |
365 | { | |
366 | char path[UTIL_PATH_SIZE]; | |
367 | DIR *dir; | |
368 | struct dirent *dent; | |
369 | ||
370 | if (udev_queue == NULL) | |
371 | return NULL; | |
eb8837e1 | 372 | udev_list_cleanup_entries(udev_queue->udev, &udev_queue->failed_list); |
065db052 | 373 | util_strscpyl(path, sizeof(path), udev_get_dev_path(udev_queue->udev), "/.udev/failed", NULL); |
64ccdf82 KS |
374 | dir = opendir(path); |
375 | if (dir == NULL) | |
376 | return NULL; | |
377 | for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { | |
378 | char filename[UTIL_PATH_SIZE]; | |
379 | char syspath[UTIL_PATH_SIZE]; | |
065db052 KS |
380 | char *s; |
381 | size_t l; | |
64ccdf82 | 382 | ssize_t len; |
065db052 | 383 | struct stat statbuf; |
64ccdf82 KS |
384 | |
385 | if (dent->d_name[0] == '.') | |
386 | continue; | |
065db052 KS |
387 | s = syspath; |
388 | l = util_strpcpyl(&s, sizeof(syspath), udev_get_sys_path(udev_queue->udev), NULL); | |
e6c1a2bd | 389 | len = readlinkat(dirfd(dir), dent->d_name, s, l); |
065db052 | 390 | if (len < 0 || (size_t)len >= l) |
64ccdf82 | 391 | continue; |
065db052 | 392 | s[len] = '\0'; |
86b57788 | 393 | dbg(udev_queue->udev, "found '%s' [%s]\n", syspath, dent->d_name); |
065db052 | 394 | util_strscpyl(filename, sizeof(filename), syspath, "/uevent", NULL); |
64ccdf82 KS |
395 | if (stat(filename, &statbuf) != 0) |
396 | continue; | |
8cd2e972 | 397 | udev_list_entry_add(udev_queue->udev, &udev_queue->failed_list, syspath, NULL, 0, 0); |
64ccdf82 KS |
398 | } |
399 | closedir(dir); | |
8cd2e972 | 400 | return udev_list_get_entry(&udev_queue->failed_list); |
64ccdf82 | 401 | } |