]>
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 | ||
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 | ||
48a9b173 KS |
34 | #include "libudev.h" |
35 | #include "libudev-private.h" | |
d91b8841 | 36 | #include "macro.h" |
c7ae0d34 | 37 | |
912541b0 KS |
38 | #define BUFSIZE 16 |
39 | #define UDEV_ALARM_TIMEOUT 180 | |
c7ae0d34 HR |
40 | |
41 | enum collect_state { | |
912541b0 KS |
42 | STATE_NONE, |
43 | STATE_OLD, | |
44 | STATE_CONFIRMED, | |
c7ae0d34 HR |
45 | }; |
46 | ||
47 | struct _mate { | |
912541b0 KS |
48 | struct udev_list_node node; |
49 | char *name; | |
50 | enum collect_state state; | |
c7ae0d34 HR |
51 | }; |
52 | ||
002a9577 | 53 | static struct udev_list_node bunch; |
c7ae0d34 HR |
54 | static int debug; |
55 | ||
56 | /* This can increase dynamically */ | |
322fc7a6 | 57 | static size_t bufsize = BUFSIZE; |
c7ae0d34 | 58 | |
b27ee00b | 59 | static inline struct _mate *node_to_mate(struct udev_list_node *node) |
002a9577 | 60 | { |
b27ee00b | 61 | return container_of(node, struct _mate, node); |
002a9577 KS |
62 | } |
63 | ||
d91b8841 | 64 | _noreturn_ static void sig_alrm(int signo) |
c7ae0d34 | 65 | { |
912541b0 | 66 | exit(4); |
c7ae0d34 HR |
67 | } |
68 | ||
69 | static void usage(void) | |
70 | { | |
912541b0 KS |
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"); | |
c7ae0d34 HR |
80 | } |
81 | ||
82 | /* | |
83 | * prepare | |
84 | * | |
85 | * Prepares the database file | |
86 | */ | |
87 | static int prepare(char *dir, char *filename) | |
88 | { | |
912541b0 KS |
89 | struct stat statbuf; |
90 | char buf[512]; | |
91 | int fd; | |
92 | ||
93 | if (stat(dir, &statbuf) < 0) | |
94 | mkdir(dir, 0700); | |
95 | ||
96 | sprintf(buf, "%s/%s", dir, filename); | |
97 | ||
98 | fd = open(buf,O_RDWR|O_CREAT, S_IRUSR|S_IWUSR); | |
99 | if (fd < 0) | |
100 | fprintf(stderr, "Cannot open %s: %s\n", buf, strerror(errno)); | |
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) | |
112 | fprintf(stderr, "Could not get lock on %s: %s\n", buf, strerror(errno)); | |
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 KS |
141 | len = bufsize >> 1; |
142 | buf = calloc(1,bufsize + 1); | |
143 | if (!buf) { | |
669241a0 | 144 | fprintf(stderr, "Out of memory.\n"); |
912541b0 KS |
145 | return -1; |
146 | } | |
147 | memset(buf, ' ', bufsize); | |
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 | him->name = strdup(word); | |
171 | him->state = STATE_OLD; | |
172 | udev_list_node_append(&him->node, &bunch); | |
173 | word = NULL; | |
174 | } | |
175 | } | |
176 | memcpy(buf, buf + len, len); | |
177 | memset(buf + len, ' ', len); | |
178 | ||
179 | if (!ptr) | |
180 | ptr = word; | |
181 | if (!ptr) | |
182 | break; | |
183 | ptr -= len; | |
184 | } | |
185 | ||
186 | free(buf); | |
187 | return 0; | |
c7ae0d34 HR |
188 | } |
189 | ||
190 | /* | |
191 | * invite | |
192 | * | |
193 | * Adds a new ID 'us' to the internal list, | |
194 | * marks it as confirmed. | |
195 | */ | |
196 | static void invite(char *us) | |
197 | { | |
912541b0 KS |
198 | struct udev_list_node *him_node; |
199 | struct _mate *who = NULL; | |
c7ae0d34 | 200 | |
912541b0 KS |
201 | if (debug) |
202 | fprintf(stderr, "Adding ID '%s'\n", us); | |
c7ae0d34 | 203 | |
912541b0 KS |
204 | udev_list_node_foreach(him_node, &bunch) { |
205 | struct _mate *him = node_to_mate(him_node); | |
002a9577 | 206 | |
912541b0 KS |
207 | if (!strcmp(him->name, us)) { |
208 | him->state = STATE_CONFIRMED; | |
209 | who = him; | |
210 | } | |
211 | } | |
212 | if (debug && !who) | |
213 | fprintf(stderr, "ID '%s' not in database\n", us); | |
c7ae0d34 HR |
214 | |
215 | } | |
216 | ||
217 | /* | |
218 | * reject | |
219 | * | |
220 | * Marks the ID 'us' as invalid, | |
221 | * causing it to be removed when the | |
222 | * list is written out. | |
223 | */ | |
224 | static void reject(char *us) | |
225 | { | |
912541b0 KS |
226 | struct udev_list_node *him_node; |
227 | struct _mate *who = NULL; | |
228 | ||
229 | if (debug) | |
230 | fprintf(stderr, "Removing ID '%s'\n", us); | |
231 | ||
232 | udev_list_node_foreach(him_node, &bunch) { | |
233 | struct _mate *him = node_to_mate(him_node); | |
234 | ||
235 | if (!strcmp(him->name, us)) { | |
236 | him->state = STATE_NONE; | |
237 | who = him; | |
238 | } | |
239 | } | |
240 | if (debug && !who) | |
241 | fprintf(stderr, "ID '%s' not in database\n", us); | |
c7ae0d34 HR |
242 | } |
243 | ||
244 | /* | |
245 | * kickout | |
246 | * | |
247 | * Remove all IDs in the internal list which are not part | |
248 | * of the list passed via the commandline. | |
249 | */ | |
250 | static void kickout(void) | |
251 | { | |
912541b0 KS |
252 | struct udev_list_node *him_node; |
253 | struct udev_list_node *tmp; | |
254 | ||
255 | udev_list_node_foreach_safe(him_node, tmp, &bunch) { | |
256 | struct _mate *him = node_to_mate(him_node); | |
257 | ||
258 | if (him->state == STATE_OLD) { | |
259 | udev_list_node_remove(&him->node); | |
260 | free(him->name); | |
261 | free(him); | |
262 | } | |
263 | } | |
c7ae0d34 HR |
264 | } |
265 | ||
266 | /* | |
267 | * missing | |
268 | * | |
269 | * Counts all missing IDs in the internal list. | |
270 | */ | |
271 | static int missing(int fd) | |
272 | { | |
912541b0 KS |
273 | char *buf; |
274 | int ret = 0; | |
275 | struct udev_list_node *him_node; | |
276 | ||
277 | buf = malloc(bufsize); | |
278 | if (!buf) | |
279 | return -1; | |
280 | ||
281 | udev_list_node_foreach(him_node, &bunch) { | |
282 | struct _mate *him = node_to_mate(him_node); | |
283 | ||
284 | if (him->state == STATE_NONE) { | |
285 | ret++; | |
286 | } else { | |
287 | while (strlen(him->name)+1 >= bufsize) { | |
288 | char *tmpbuf; | |
289 | ||
290 | bufsize = bufsize << 1; | |
291 | tmpbuf = realloc(buf, bufsize); | |
292 | if (!tmpbuf) { | |
293 | free(buf); | |
294 | return -1; | |
295 | } | |
296 | buf = tmpbuf; | |
297 | } | |
298 | snprintf(buf, strlen(him->name)+2, "%s ", him->name); | |
299 | write(fd, buf, strlen(buf)); | |
300 | } | |
301 | } | |
302 | ||
303 | free(buf); | |
304 | return ret; | |
c7ae0d34 HR |
305 | } |
306 | ||
307 | /* | |
308 | * everybody | |
309 | * | |
310 | * Prints out the status of the internal list. | |
311 | */ | |
312 | static void everybody(void) | |
313 | { | |
912541b0 KS |
314 | struct udev_list_node *him_node; |
315 | const char *state = ""; | |
316 | ||
317 | udev_list_node_foreach(him_node, &bunch) { | |
318 | struct _mate *him = node_to_mate(him_node); | |
319 | ||
320 | switch (him->state) { | |
321 | case STATE_NONE: | |
322 | state = "none"; | |
323 | break; | |
324 | case STATE_OLD: | |
325 | state = "old"; | |
326 | break; | |
327 | case STATE_CONFIRMED: | |
328 | state = "confirmed"; | |
329 | break; | |
330 | } | |
331 | fprintf(stderr, "ID: %s=%s\n", him->name, state); | |
332 | } | |
c7ae0d34 HR |
333 | } |
334 | ||
335 | int main(int argc, char **argv) | |
336 | { | |
912541b0 KS |
337 | struct udev *udev; |
338 | static const struct option options[] = { | |
339 | { "add", no_argument, NULL, 'a' }, | |
340 | { "remove", no_argument, NULL, 'r' }, | |
341 | { "debug", no_argument, NULL, 'd' }, | |
342 | { "help", no_argument, NULL, 'h' }, | |
343 | {} | |
344 | }; | |
345 | int argi; | |
346 | char *checkpoint, *us; | |
347 | int fd; | |
348 | int i; | |
349 | int ret = EXIT_SUCCESS; | |
350 | int prune = 0; | |
351 | char tmpdir[UTIL_PATH_SIZE]; | |
352 | ||
353 | udev = udev_new(); | |
354 | if (udev == NULL) { | |
355 | ret = EXIT_FAILURE; | |
356 | goto exit; | |
357 | } | |
358 | ||
359 | while (1) { | |
360 | int option; | |
361 | ||
362 | option = getopt_long(argc, argv, "ardh", options, NULL); | |
363 | if (option == -1) | |
364 | break; | |
365 | ||
366 | switch (option) { | |
367 | case 'a': | |
368 | prune = 0; | |
369 | break; | |
370 | case 'r': | |
371 | prune = 1; | |
372 | break; | |
373 | case 'd': | |
374 | debug = 1; | |
375 | break; | |
376 | case 'h': | |
377 | usage(); | |
378 | goto exit; | |
379 | default: | |
380 | ret = 1; | |
381 | goto exit; | |
382 | } | |
383 | } | |
384 | ||
385 | argi = optind; | |
386 | if (argi + 2 > argc) { | |
387 | printf("Missing parameter(s)\n"); | |
388 | ret = 1; | |
389 | goto exit; | |
390 | } | |
391 | checkpoint = argv[argi++]; | |
392 | us = argv[argi++]; | |
393 | ||
394 | if (signal(SIGALRM, sig_alrm) == SIG_ERR) { | |
395 | fprintf(stderr, "Cannot set SIGALRM: %s\n", strerror(errno)); | |
396 | ret = 2; | |
397 | goto exit; | |
398 | } | |
399 | ||
400 | udev_list_node_init(&bunch); | |
401 | ||
402 | if (debug) | |
403 | fprintf(stderr, "Using checkpoint '%s'\n", checkpoint); | |
404 | ||
6ada823a | 405 | util_strscpyl(tmpdir, sizeof(tmpdir), "/run/udev/collect", NULL); |
912541b0 KS |
406 | fd = prepare(tmpdir, checkpoint); |
407 | if (fd < 0) { | |
408 | ret = 3; | |
409 | goto out; | |
410 | } | |
411 | ||
412 | if (checkout(fd) < 0) { | |
413 | ret = 2; | |
414 | goto out; | |
415 | } | |
416 | ||
417 | for (i = argi; i < argc; i++) { | |
418 | struct udev_list_node *him_node; | |
419 | struct _mate *who; | |
420 | ||
421 | who = NULL; | |
422 | udev_list_node_foreach(him_node, &bunch) { | |
423 | struct _mate *him = node_to_mate(him_node); | |
424 | ||
425 | if (!strcmp(him->name, argv[i])) | |
426 | who = him; | |
427 | } | |
428 | if (!who) { | |
429 | struct _mate *him; | |
430 | ||
431 | if (debug) | |
432 | fprintf(stderr, "ID %s: not in database\n", argv[i]); | |
433 | him = malloc(sizeof (struct _mate)); | |
434 | him->name = malloc(strlen(argv[i]) + 1); | |
435 | strcpy(him->name, argv[i]); | |
436 | him->state = STATE_NONE; | |
437 | udev_list_node_append(&him->node, &bunch); | |
438 | } else { | |
439 | if (debug) | |
440 | fprintf(stderr, "ID %s: found in database\n", argv[i]); | |
441 | who->state = STATE_CONFIRMED; | |
442 | } | |
443 | } | |
444 | ||
445 | if (prune) | |
446 | reject(us); | |
447 | else | |
448 | invite(us); | |
449 | ||
450 | if (debug) { | |
451 | everybody(); | |
452 | fprintf(stderr, "Prune lists\n"); | |
453 | } | |
454 | kickout(); | |
455 | ||
456 | lseek(fd, 0, SEEK_SET); | |
457 | ftruncate(fd, 0); | |
458 | ret = missing(fd); | |
459 | ||
460 | lockf(fd, F_ULOCK, 0); | |
461 | close(fd); | |
4ec9c3e7 | 462 | out: |
912541b0 KS |
463 | if (debug) |
464 | everybody(); | |
465 | if (ret >= 0) | |
466 | printf("COLLECT_%s=%d\n", checkpoint, ret); | |
4ec9c3e7 | 467 | exit: |
912541b0 KS |
468 | udev_unref(udev); |
469 | return ret; | |
c7ae0d34 | 470 | } |