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