]>
Commit | Line | Data |
---|---|---|
c7ae0d34 HR |
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 | ||
c7ae0d34 | 22 | #include <errno.h> |
c7ae0d34 | 23 | #include <getopt.h> |
07630cea LP |
24 | #include <stddef.h> |
25 | #include <stdio.h> | |
c7ae0d34 | 26 | |
b5efdb8a | 27 | #include "alloc-util.h" |
48a9b173 | 28 | #include "libudev-private.h" |
d91b8841 | 29 | #include "macro.h" |
d054f0a4 | 30 | #include "stdio-util.h" |
07630cea | 31 | #include "string-util.h" |
b237a168 | 32 | #include "udev-util.h" |
c7ae0d34 | 33 | |
2852ae21 LP |
34 | #define BUFSIZE 16 |
35 | #define UDEV_ALARM_TIMEOUT 180 | |
c7ae0d34 HR |
36 | |
37 | enum collect_state { | |
912541b0 KS |
38 | STATE_NONE, |
39 | STATE_OLD, | |
40 | STATE_CONFIRMED, | |
c7ae0d34 HR |
41 | }; |
42 | ||
43 | struct _mate { | |
912541b0 KS |
44 | struct udev_list_node node; |
45 | char *name; | |
46 | enum collect_state state; | |
c7ae0d34 HR |
47 | }; |
48 | ||
002a9577 | 49 | static struct udev_list_node bunch; |
c7ae0d34 HR |
50 | static int debug; |
51 | ||
52 | /* This can increase dynamically */ | |
322fc7a6 | 53 | static size_t bufsize = BUFSIZE; |
c7ae0d34 | 54 | |
b27ee00b | 55 | static inline struct _mate *node_to_mate(struct udev_list_node *node) |
002a9577 | 56 | { |
b27ee00b | 57 | return container_of(node, struct _mate, node); |
002a9577 KS |
58 | } |
59 | ||
919ce0b7 | 60 | noreturn static void sig_alrm(int signo) |
c7ae0d34 | 61 | { |
912541b0 | 62 | exit(4); |
c7ae0d34 HR |
63 | } |
64 | ||
65 | static void usage(void) | |
66 | { | |
5ac0162c LP |
67 | printf("%s [options] <checkpoint> <id> <idlist>\n\n" |
68 | "Collect variables across events.\n\n" | |
69 | " -h --help Print this message\n" | |
70 | " -a --add Add ID <id> to the list <idlist>\n" | |
71 | " -r --remove Remove ID <id> from the list <idlist>\n" | |
72 | " -d --debug Debug to stderr\n\n" | |
912541b0 KS |
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" | |
5ac0162c LP |
78 | " On error a negative number is returned.\n\n" |
79 | , program_invocation_short_name); | |
c7ae0d34 HR |
80 | } |
81 | ||
82 | /* | |
83 | * prepare | |
84 | * | |
85 | * Prepares the database file | |
86 | */ | |
87 | static int prepare(char *dir, char *filename) | |
88 | { | |
e68eedbb | 89 | char buf[PATH_MAX]; |
c9732bae | 90 | int r, fd; |
912541b0 | 91 | |
c9732bae RC |
92 | r = mkdir(dir, 0700); |
93 | if (r < 0 && errno != EEXIST) | |
94 | return -errno; | |
912541b0 | 95 | |
e68eedbb | 96 | snprintf(buf, sizeof buf, "%s/%s", dir, filename); |
912541b0 | 97 | |
e68eedbb | 98 | fd = open(buf, O_RDWR|O_CREAT|O_CLOEXEC, S_IRUSR|S_IWUSR); |
912541b0 | 99 | if (fd < 0) |
f5f6d0e2 | 100 | fprintf(stderr, "Cannot open %s: %m\n", buf); |
912541b0 KS |
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) | |
f5f6d0e2 | 112 | fprintf(stderr, "Could not get lock on %s: %m\n", buf); |
912541b0 KS |
113 | } |
114 | } | |
115 | ||
116 | return fd; | |
c7ae0d34 HR |
117 | } |
118 | ||
119 | /* | |
120 | * Read checkpoint file | |
121 | * | |
122 | * Tricky reading this. We allocate a buffer twice as large | |
214a6c79 | 123 | * as we're going to read. Then we read into the upper half |
c7ae0d34 HR |
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 | { | |
912541b0 KS |
136 | int len; |
137 | char *buf, *ptr, *word = NULL; | |
138 | struct _mate *him; | |
c7ae0d34 HR |
139 | |
140 | restart: | |
912541b0 | 141 | len = bufsize >> 1; |
7d566799 | 142 | buf = malloc(bufsize + 1); |
2852ae21 | 143 | if (!buf) |
8dc8ef59 | 144 | return log_oom(); |
912541b0 | 145 | memset(buf, ' ', bufsize); |
7d566799 ZJS |
146 | buf[bufsize] = '\0'; |
147 | ||
912541b0 KS |
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) | |
c4ef0548 | 156 | fprintf(stderr, "ID overflow, restarting with size %zu\n", bufsize); |
912541b0 KS |
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)); | |
8dc8ef59 VP |
170 | if (!him) { |
171 | free(buf); | |
172 | return log_oom(); | |
173 | } | |
912541b0 | 174 | him->name = strdup(word); |
8dc8ef59 VP |
175 | if (!him->name) { |
176 | free(buf); | |
177 | free(him); | |
178 | return log_oom(); | |
179 | } | |
912541b0 KS |
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; | |
c7ae0d34 HR |
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 | { | |
912541b0 KS |
207 | struct udev_list_node *him_node; |
208 | struct _mate *who = NULL; | |
c7ae0d34 | 209 | |
912541b0 KS |
210 | if (debug) |
211 | fprintf(stderr, "Adding ID '%s'\n", us); | |
c7ae0d34 | 212 | |
912541b0 KS |
213 | udev_list_node_foreach(him_node, &bunch) { |
214 | struct _mate *him = node_to_mate(him_node); | |
002a9577 | 215 | |
090be865 | 216 | if (streq(him->name, us)) { |
912541b0 KS |
217 | him->state = STATE_CONFIRMED; |
218 | who = him; | |
219 | } | |
220 | } | |
221 | if (debug && !who) | |
222 | fprintf(stderr, "ID '%s' not in database\n", us); | |
c7ae0d34 HR |
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 | { | |
912541b0 KS |
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 | ||
090be865 | 244 | if (streq(him->name, us)) { |
912541b0 KS |
245 | him->state = STATE_NONE; |
246 | who = him; | |
247 | } | |
248 | } | |
249 | if (debug && !who) | |
250 | fprintf(stderr, "ID '%s' not in database\n", us); | |
c7ae0d34 HR |
251 | } |
252 | ||
253 | /* | |
254 | * kickout | |
255 | * | |
256 | * Remove all IDs in the internal list which are not part | |
3f85ef0f | 257 | * of the list passed via the command line. |
c7ae0d34 HR |
258 | */ |
259 | static void kickout(void) | |
260 | { | |
912541b0 KS |
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 | } | |
c7ae0d34 HR |
273 | } |
274 | ||
275 | /* | |
276 | * missing | |
277 | * | |
278 | * Counts all missing IDs in the internal list. | |
279 | */ | |
280 | static int missing(int fd) | |
281 | { | |
912541b0 KS |
282 | char *buf; |
283 | int ret = 0; | |
284 | struct udev_list_node *him_node; | |
285 | ||
286 | buf = malloc(bufsize); | |
287 | if (!buf) | |
8dc8ef59 | 288 | return log_oom(); |
912541b0 KS |
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); | |
8dc8ef59 | 303 | return log_oom(); |
912541b0 KS |
304 | } |
305 | buf = tmpbuf; | |
306 | } | |
307 | snprintf(buf, strlen(him->name)+2, "%s ", him->name); | |
cb32f014 VP |
308 | if (write(fd, buf, strlen(buf)) < 0) { |
309 | free(buf); | |
310 | return -1; | |
311 | } | |
912541b0 KS |
312 | } |
313 | } | |
314 | ||
315 | free(buf); | |
316 | return ret; | |
c7ae0d34 HR |
317 | } |
318 | ||
319 | /* | |
320 | * everybody | |
321 | * | |
322 | * Prints out the status of the internal list. | |
323 | */ | |
324 | static void everybody(void) | |
325 | { | |
912541b0 KS |
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 | } | |
c7ae0d34 HR |
345 | } |
346 | ||
347 | int main(int argc, char **argv) | |
348 | { | |
912541b0 KS |
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 | ||
b237a168 ZJS |
365 | log_set_target(LOG_TARGET_AUTO); |
366 | udev_parse_config(); | |
367 | log_parse_environment(); | |
368 | log_open(); | |
369 | ||
912541b0 KS |
370 | udev = udev_new(); |
371 | if (udev == NULL) { | |
372 | ret = EXIT_FAILURE; | |
373 | goto exit; | |
374 | } | |
375 | ||
57255510 | 376 | for (;;) { |
912541b0 KS |
377 | int option; |
378 | ||
379 | option = getopt_long(argc, argv, "ardh", options, NULL); | |
380 | if (option == -1) | |
381 | break; | |
382 | ||
383 | switch (option) { | |
384 | case 'a': | |
385 | prune = 0; | |
386 | break; | |
387 | case 'r': | |
388 | prune = 1; | |
389 | break; | |
390 | case 'd': | |
391 | debug = 1; | |
392 | break; | |
393 | case 'h': | |
394 | usage(); | |
395 | goto exit; | |
396 | default: | |
397 | ret = 1; | |
398 | goto exit; | |
399 | } | |
400 | } | |
401 | ||
402 | argi = optind; | |
403 | if (argi + 2 > argc) { | |
404 | printf("Missing parameter(s)\n"); | |
405 | ret = 1; | |
406 | goto exit; | |
407 | } | |
408 | checkpoint = argv[argi++]; | |
409 | us = argv[argi++]; | |
410 | ||
411 | if (signal(SIGALRM, sig_alrm) == SIG_ERR) { | |
f5f6d0e2 | 412 | fprintf(stderr, "Cannot set SIGALRM: %m\n"); |
912541b0 KS |
413 | ret = 2; |
414 | goto exit; | |
415 | } | |
416 | ||
417 | udev_list_node_init(&bunch); | |
418 | ||
419 | if (debug) | |
420 | fprintf(stderr, "Using checkpoint '%s'\n", checkpoint); | |
421 | ||
d5a89d7d | 422 | strscpyl(tmpdir, sizeof(tmpdir), "/run/udev/collect", NULL); |
912541b0 KS |
423 | fd = prepare(tmpdir, checkpoint); |
424 | if (fd < 0) { | |
425 | ret = 3; | |
426 | goto out; | |
427 | } | |
428 | ||
429 | if (checkout(fd) < 0) { | |
430 | ret = 2; | |
431 | goto out; | |
432 | } | |
433 | ||
434 | for (i = argi; i < argc; i++) { | |
435 | struct udev_list_node *him_node; | |
436 | struct _mate *who; | |
437 | ||
438 | who = NULL; | |
439 | udev_list_node_foreach(him_node, &bunch) { | |
440 | struct _mate *him = node_to_mate(him_node); | |
441 | ||
090be865 | 442 | if (streq(him->name, argv[i])) |
912541b0 KS |
443 | who = him; |
444 | } | |
445 | if (!who) { | |
446 | struct _mate *him; | |
447 | ||
448 | if (debug) | |
449 | fprintf(stderr, "ID %s: not in database\n", argv[i]); | |
ef89eef7 | 450 | him = new(struct _mate, 1); |
8dc8ef59 VP |
451 | if (!him) { |
452 | ret = ENOMEM; | |
453 | goto out; | |
454 | } | |
455 | ||
ef89eef7 | 456 | him->name = strdup(argv[i]); |
8dc8ef59 | 457 | if (!him->name) { |
ef89eef7 | 458 | free(him); |
8dc8ef59 VP |
459 | ret = ENOMEM; |
460 | goto out; | |
461 | } | |
462 | ||
912541b0 KS |
463 | him->state = STATE_NONE; |
464 | udev_list_node_append(&him->node, &bunch); | |
465 | } else { | |
466 | if (debug) | |
467 | fprintf(stderr, "ID %s: found in database\n", argv[i]); | |
468 | who->state = STATE_CONFIRMED; | |
469 | } | |
470 | } | |
471 | ||
472 | if (prune) | |
473 | reject(us); | |
474 | else | |
475 | invite(us); | |
476 | ||
477 | if (debug) { | |
478 | everybody(); | |
479 | fprintf(stderr, "Prune lists\n"); | |
480 | } | |
481 | kickout(); | |
482 | ||
483 | lseek(fd, 0, SEEK_SET); | |
484 | ftruncate(fd, 0); | |
485 | ret = missing(fd); | |
486 | ||
487 | lockf(fd, F_ULOCK, 0); | |
488 | close(fd); | |
4ec9c3e7 | 489 | out: |
912541b0 KS |
490 | if (debug) |
491 | everybody(); | |
492 | if (ret >= 0) | |
493 | printf("COLLECT_%s=%d\n", checkpoint, ret); | |
4ec9c3e7 | 494 | exit: |
912541b0 KS |
495 | udev_unref(udev); |
496 | return ret; | |
c7ae0d34 | 497 | } |