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