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