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