]>
Commit | Line | Data |
---|---|---|
663996b3 MS |
1 | /* |
2 | * Collect variables across events. | |
3 | * | |
4 | * usage: collect [--add|--remove] <checkpoint> <id> <idlist> | |
5 | * | |
6 | * Adds ID <id> to the list governed by <checkpoint>. | |
7 | * <id> must be part of the ID list <idlist>. | |
8 | * If all IDs given by <idlist> are listed (ie collect has been | |
9 | * invoked for each ID in <idlist>) collect returns 0, the | |
10 | * number of missing IDs otherwise. | |
11 | * A negative number is returned on error. | |
12 | * | |
13 | * Copyright(C) 2007, Hannes Reinecke <hare@suse.de> | |
14 | * | |
15 | * This program is free software: you can redistribute it and/or modify | |
16 | * it under the terms of the GNU General Public License as published by | |
17 | * the Free Software Foundation, either version 2 of the License, or | |
18 | * (at your option) any later version. | |
19 | * | |
20 | */ | |
21 | ||
22 | #include <stdio.h> | |
23 | #include <stdlib.h> | |
24 | #include <stddef.h> | |
25 | #include <unistd.h> | |
26 | #include <signal.h> | |
27 | #include <fcntl.h> | |
28 | #include <errno.h> | |
29 | #include <string.h> | |
30 | #include <getopt.h> | |
31 | #include <sys/types.h> | |
32 | #include <sys/stat.h> | |
33 | ||
34 | #include "libudev.h" | |
35 | #include "libudev-private.h" | |
36 | #include "macro.h" | |
37 | ||
38 | #define BUFSIZE 16 | |
39 | #define UDEV_ALARM_TIMEOUT 180 | |
40 | ||
41 | enum collect_state { | |
42 | STATE_NONE, | |
43 | STATE_OLD, | |
44 | STATE_CONFIRMED, | |
45 | }; | |
46 | ||
47 | struct _mate { | |
48 | struct udev_list_node node; | |
49 | char *name; | |
50 | enum collect_state state; | |
51 | }; | |
52 | ||
53 | static struct udev_list_node bunch; | |
54 | static int debug; | |
55 | ||
56 | /* This can increase dynamically */ | |
57 | static size_t bufsize = BUFSIZE; | |
58 | ||
59 | static inline struct _mate *node_to_mate(struct udev_list_node *node) | |
60 | { | |
61 | return container_of(node, struct _mate, node); | |
62 | } | |
63 | ||
60f067b4 | 64 | noreturn static void sig_alrm(int signo) |
663996b3 MS |
65 | { |
66 | exit(4); | |
67 | } | |
68 | ||
69 | static void usage(void) | |
70 | { | |
71 | printf("usage: collect [--add|--remove] [--debug] <checkpoint> <id> <idlist>\n" | |
72 | "\n" | |
73 | " Adds ID <id> to the list governed by <checkpoint>.\n" | |
74 | " <id> must be part of the list <idlist>.\n" | |
75 | " If all IDs given by <idlist> are listed (ie collect has been\n" | |
76 | " invoked for each ID in <idlist>) collect returns 0, the\n" | |
77 | " number of missing IDs otherwise.\n" | |
78 | " On error a negative number is returned.\n" | |
79 | "\n"); | |
80 | } | |
81 | ||
82 | /* | |
83 | * prepare | |
84 | * | |
85 | * Prepares the database file | |
86 | */ | |
87 | static int prepare(char *dir, char *filename) | |
88 | { | |
663996b3 | 89 | char buf[512]; |
f47781d8 | 90 | int r, fd; |
663996b3 | 91 | |
f47781d8 MP |
92 | r = mkdir(dir, 0700); |
93 | if (r < 0 && errno != EEXIST) | |
94 | return -errno; | |
663996b3 MS |
95 | |
96 | snprintf(buf, sizeof(buf), "%s/%s", dir, filename); | |
97 | ||
60f067b4 | 98 | fd = open(buf,O_RDWR|O_CREAT|O_CLOEXEC, S_IRUSR|S_IWUSR); |
663996b3 | 99 | if (fd < 0) |
60f067b4 | 100 | fprintf(stderr, "Cannot open %s: %m\n", buf); |
663996b3 MS |
101 | |
102 | if (lockf(fd,F_TLOCK,0) < 0) { | |
103 | if (debug) | |
104 | fprintf(stderr, "Lock taken, wait for %d seconds\n", UDEV_ALARM_TIMEOUT); | |
105 | if (errno == EAGAIN || errno == EACCES) { | |
106 | alarm(UDEV_ALARM_TIMEOUT); | |
107 | lockf(fd, F_LOCK, 0); | |
108 | if (debug) | |
109 | fprintf(stderr, "Acquired lock on %s\n", buf); | |
110 | } else { | |
111 | if (debug) | |
60f067b4 | 112 | fprintf(stderr, "Could not get lock on %s: %m\n", buf); |
663996b3 MS |
113 | } |
114 | } | |
115 | ||
116 | return fd; | |
117 | } | |
118 | ||
119 | /* | |
120 | * Read checkpoint file | |
121 | * | |
122 | * Tricky reading this. We allocate a buffer twice as large | |
123 | * as we're going to read. Then we read into the upper half | |
124 | * of that buffer and start parsing. | |
125 | * Once we do _not_ find end-of-work terminator (whitespace | |
126 | * character) we move the upper half to the lower half, | |
127 | * adjust the read pointer and read the next bit. | |
128 | * Quite clever methinks :-) | |
129 | * I should become a programmer ... | |
130 | * | |
131 | * Yes, one could have used fgets() for this. But then we'd | |
132 | * have to use freopen etc which I found quite tedious. | |
133 | */ | |
134 | static int checkout(int fd) | |
135 | { | |
136 | int len; | |
137 | char *buf, *ptr, *word = NULL; | |
138 | struct _mate *him; | |
139 | ||
140 | restart: | |
141 | len = bufsize >> 1; | |
142 | buf = malloc(bufsize + 1); | |
143 | if (!buf) | |
144 | return log_oom(); | |
145 | memset(buf, ' ', bufsize); | |
146 | buf[bufsize] = '\0'; | |
147 | ||
148 | ptr = buf + len; | |
149 | while ((read(fd, buf + len, len)) > 0) { | |
150 | while (ptr && *ptr) { | |
151 | word = ptr; | |
152 | ptr = strpbrk(word," \n\t\r"); | |
153 | if (!ptr && word < (buf + len)) { | |
154 | bufsize = bufsize << 1; | |
155 | if (debug) | |
156 | fprintf(stderr, "ID overflow, restarting with size %zi\n", bufsize); | |
157 | free(buf); | |
158 | lseek(fd, 0, SEEK_SET); | |
159 | goto restart; | |
160 | } | |
161 | if (ptr) { | |
162 | *ptr = '\0'; | |
163 | ptr++; | |
164 | if (!strlen(word)) | |
165 | continue; | |
166 | ||
167 | if (debug) | |
168 | fprintf(stderr, "Found word %s\n", word); | |
169 | him = malloc(sizeof (struct _mate)); | |
170 | if (!him) { | |
171 | free(buf); | |
172 | return log_oom(); | |
173 | } | |
174 | him->name = strdup(word); | |
175 | if (!him->name) { | |
176 | free(buf); | |
177 | free(him); | |
178 | return log_oom(); | |
179 | } | |
180 | him->state = STATE_OLD; | |
181 | udev_list_node_append(&him->node, &bunch); | |
182 | word = NULL; | |
183 | } | |
184 | } | |
185 | memcpy(buf, buf + len, len); | |
186 | memset(buf + len, ' ', len); | |
187 | ||
188 | if (!ptr) | |
189 | ptr = word; | |
190 | if (!ptr) | |
191 | break; | |
192 | ptr -= len; | |
193 | } | |
194 | ||
195 | free(buf); | |
196 | return 0; | |
197 | } | |
198 | ||
199 | /* | |
200 | * invite | |
201 | * | |
202 | * Adds a new ID 'us' to the internal list, | |
203 | * marks it as confirmed. | |
204 | */ | |
205 | static void invite(char *us) | |
206 | { | |
207 | struct udev_list_node *him_node; | |
208 | struct _mate *who = NULL; | |
209 | ||
210 | if (debug) | |
211 | fprintf(stderr, "Adding ID '%s'\n", us); | |
212 | ||
213 | udev_list_node_foreach(him_node, &bunch) { | |
214 | struct _mate *him = node_to_mate(him_node); | |
215 | ||
216 | if (streq(him->name, us)) { | |
217 | him->state = STATE_CONFIRMED; | |
218 | who = him; | |
219 | } | |
220 | } | |
221 | if (debug && !who) | |
222 | fprintf(stderr, "ID '%s' not in database\n", us); | |
223 | ||
224 | } | |
225 | ||
226 | /* | |
227 | * reject | |
228 | * | |
229 | * Marks the ID 'us' as invalid, | |
230 | * causing it to be removed when the | |
231 | * list is written out. | |
232 | */ | |
233 | static void reject(char *us) | |
234 | { | |
235 | struct udev_list_node *him_node; | |
236 | struct _mate *who = NULL; | |
237 | ||
238 | if (debug) | |
239 | fprintf(stderr, "Removing ID '%s'\n", us); | |
240 | ||
241 | udev_list_node_foreach(him_node, &bunch) { | |
242 | struct _mate *him = node_to_mate(him_node); | |
243 | ||
244 | if (streq(him->name, us)) { | |
245 | him->state = STATE_NONE; | |
246 | who = him; | |
247 | } | |
248 | } | |
249 | if (debug && !who) | |
250 | fprintf(stderr, "ID '%s' not in database\n", us); | |
251 | } | |
252 | ||
253 | /* | |
254 | * kickout | |
255 | * | |
256 | * Remove all IDs in the internal list which are not part | |
f47781d8 | 257 | * of the list passed via the command line. |
663996b3 MS |
258 | */ |
259 | static void kickout(void) | |
260 | { | |
261 | struct udev_list_node *him_node; | |
262 | struct udev_list_node *tmp; | |
263 | ||
264 | udev_list_node_foreach_safe(him_node, tmp, &bunch) { | |
265 | struct _mate *him = node_to_mate(him_node); | |
266 | ||
267 | if (him->state == STATE_OLD) { | |
268 | udev_list_node_remove(&him->node); | |
269 | free(him->name); | |
270 | free(him); | |
271 | } | |
272 | } | |
273 | } | |
274 | ||
275 | /* | |
276 | * missing | |
277 | * | |
278 | * Counts all missing IDs in the internal list. | |
279 | */ | |
280 | static int missing(int fd) | |
281 | { | |
282 | char *buf; | |
283 | int ret = 0; | |
284 | struct udev_list_node *him_node; | |
285 | ||
286 | buf = malloc(bufsize); | |
287 | if (!buf) | |
288 | return log_oom(); | |
289 | ||
290 | udev_list_node_foreach(him_node, &bunch) { | |
291 | struct _mate *him = node_to_mate(him_node); | |
292 | ||
293 | if (him->state == STATE_NONE) { | |
294 | ret++; | |
295 | } else { | |
296 | while (strlen(him->name)+1 >= bufsize) { | |
297 | char *tmpbuf; | |
298 | ||
299 | bufsize = bufsize << 1; | |
300 | tmpbuf = realloc(buf, bufsize); | |
301 | if (!tmpbuf) { | |
302 | free(buf); | |
303 | return log_oom(); | |
304 | } | |
305 | buf = tmpbuf; | |
306 | } | |
307 | snprintf(buf, strlen(him->name)+2, "%s ", him->name); | |
308 | if (write(fd, buf, strlen(buf)) < 0) { | |
309 | free(buf); | |
310 | return -1; | |
311 | } | |
312 | } | |
313 | } | |
314 | ||
315 | free(buf); | |
316 | return ret; | |
317 | } | |
318 | ||
319 | /* | |
320 | * everybody | |
321 | * | |
322 | * Prints out the status of the internal list. | |
323 | */ | |
324 | static void everybody(void) | |
325 | { | |
326 | struct udev_list_node *him_node; | |
327 | const char *state = ""; | |
328 | ||
329 | udev_list_node_foreach(him_node, &bunch) { | |
330 | struct _mate *him = node_to_mate(him_node); | |
331 | ||
332 | switch (him->state) { | |
333 | case STATE_NONE: | |
334 | state = "none"; | |
335 | break; | |
336 | case STATE_OLD: | |
337 | state = "old"; | |
338 | break; | |
339 | case STATE_CONFIRMED: | |
340 | state = "confirmed"; | |
341 | break; | |
342 | } | |
343 | fprintf(stderr, "ID: %s=%s\n", him->name, state); | |
344 | } | |
345 | } | |
346 | ||
347 | int main(int argc, char **argv) | |
348 | { | |
349 | struct udev *udev; | |
350 | static const struct option options[] = { | |
351 | { "add", no_argument, NULL, 'a' }, | |
352 | { "remove", no_argument, NULL, 'r' }, | |
353 | { "debug", no_argument, NULL, 'd' }, | |
354 | { "help", no_argument, NULL, 'h' }, | |
355 | {} | |
356 | }; | |
357 | int argi; | |
358 | char *checkpoint, *us; | |
359 | int fd; | |
360 | int i; | |
361 | int ret = EXIT_SUCCESS; | |
362 | int prune = 0; | |
363 | char tmpdir[UTIL_PATH_SIZE]; | |
364 | ||
365 | udev = udev_new(); | |
366 | if (udev == NULL) { | |
367 | ret = EXIT_FAILURE; | |
368 | goto exit; | |
369 | } | |
370 | ||
371 | while (1) { | |
372 | int option; | |
373 | ||
374 | option = getopt_long(argc, argv, "ardh", options, NULL); | |
375 | if (option == -1) | |
376 | break; | |
377 | ||
378 | switch (option) { | |
379 | case 'a': | |
380 | prune = 0; | |
381 | break; | |
382 | case 'r': | |
383 | prune = 1; | |
384 | break; | |
385 | case 'd': | |
386 | debug = 1; | |
387 | break; | |
388 | case 'h': | |
389 | usage(); | |
390 | goto exit; | |
391 | default: | |
392 | ret = 1; | |
393 | goto exit; | |
394 | } | |
395 | } | |
396 | ||
397 | argi = optind; | |
398 | if (argi + 2 > argc) { | |
399 | printf("Missing parameter(s)\n"); | |
400 | ret = 1; | |
401 | goto exit; | |
402 | } | |
403 | checkpoint = argv[argi++]; | |
404 | us = argv[argi++]; | |
405 | ||
406 | if (signal(SIGALRM, sig_alrm) == SIG_ERR) { | |
60f067b4 | 407 | fprintf(stderr, "Cannot set SIGALRM: %m\n"); |
663996b3 MS |
408 | ret = 2; |
409 | goto exit; | |
410 | } | |
411 | ||
412 | udev_list_node_init(&bunch); | |
413 | ||
414 | if (debug) | |
415 | fprintf(stderr, "Using checkpoint '%s'\n", checkpoint); | |
416 | ||
417 | strscpyl(tmpdir, sizeof(tmpdir), "/run/udev/collect", NULL); | |
418 | fd = prepare(tmpdir, checkpoint); | |
419 | if (fd < 0) { | |
420 | ret = 3; | |
421 | goto out; | |
422 | } | |
423 | ||
424 | if (checkout(fd) < 0) { | |
425 | ret = 2; | |
426 | goto out; | |
427 | } | |
428 | ||
429 | for (i = argi; i < argc; i++) { | |
430 | struct udev_list_node *him_node; | |
431 | struct _mate *who; | |
432 | ||
433 | who = NULL; | |
434 | udev_list_node_foreach(him_node, &bunch) { | |
435 | struct _mate *him = node_to_mate(him_node); | |
436 | ||
437 | if (streq(him->name, argv[i])) | |
438 | who = him; | |
439 | } | |
440 | if (!who) { | |
441 | struct _mate *him; | |
442 | ||
443 | if (debug) | |
444 | fprintf(stderr, "ID %s: not in database\n", argv[i]); | |
14228c0d | 445 | him = new(struct _mate, 1); |
663996b3 MS |
446 | if (!him) { |
447 | ret = ENOMEM; | |
448 | goto out; | |
449 | } | |
450 | ||
14228c0d | 451 | him->name = strdup(argv[i]); |
663996b3 | 452 | if (!him->name) { |
14228c0d | 453 | free(him); |
663996b3 MS |
454 | ret = ENOMEM; |
455 | goto out; | |
456 | } | |
457 | ||
663996b3 MS |
458 | him->state = STATE_NONE; |
459 | udev_list_node_append(&him->node, &bunch); | |
460 | } else { | |
461 | if (debug) | |
462 | fprintf(stderr, "ID %s: found in database\n", argv[i]); | |
463 | who->state = STATE_CONFIRMED; | |
464 | } | |
465 | } | |
466 | ||
467 | if (prune) | |
468 | reject(us); | |
469 | else | |
470 | invite(us); | |
471 | ||
472 | if (debug) { | |
473 | everybody(); | |
474 | fprintf(stderr, "Prune lists\n"); | |
475 | } | |
476 | kickout(); | |
477 | ||
478 | lseek(fd, 0, SEEK_SET); | |
479 | ftruncate(fd, 0); | |
480 | ret = missing(fd); | |
481 | ||
482 | lockf(fd, F_ULOCK, 0); | |
483 | close(fd); | |
484 | out: | |
485 | if (debug) | |
486 | everybody(); | |
487 | if (ret >= 0) | |
488 | printf("COLLECT_%s=%d\n", checkpoint, ret); | |
489 | exit: | |
490 | udev_unref(udev); | |
491 | return ret; | |
492 | } |