]> git.proxmox.com Git - systemd.git/blame - src/udev/collect/collect.c
Merge tag 'upstream/229'
[systemd.git] / src / udev / collect / collect.c
CommitLineData
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
36enum collect_state {
37 STATE_NONE,
38 STATE_OLD,
39 STATE_CONFIRMED,
40};
41
42struct _mate {
43 struct udev_list_node node;
44 char *name;
45 enum collect_state state;
46};
47
48static struct udev_list_node bunch;
49static int debug;
50
51/* This can increase dynamically */
52static size_t bufsize = BUFSIZE;
53
54static inline struct _mate *node_to_mate(struct udev_list_node *node)
55{
56 return container_of(node, struct _mate, node);
57}
58
60f067b4 59noreturn static void sig_alrm(int signo)
663996b3
MS
60{
61 exit(4);
62}
63
64static 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 */
86static 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 */
133static 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 */
204static 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 */
232static 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 */
258static 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 */
279static 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 */
323static 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
346int 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);
483out:
484 if (debug)
485 everybody();
486 if (ret >= 0)
487 printf("COLLECT_%s=%d\n", checkpoint, ret);
488exit:
489 udev_unref(udev);
490 return ret;
491}