]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/udev/collect/collect.c
tree-wide: drop empty lines in comments
[thirdparty/systemd.git] / src / udev / collect / collect.c
1 /* SPDX-License-Identifier: GPL-2.0+ */
2 /*
3 * Collect variables across events.
4 *
5 * usage: collect [--add|--remove] <checkpoint> <id> <idlist>
6 *
7 * Adds ID <id> to the list governed by <checkpoint>.
8 * <id> must be part of the ID list <idlist>.
9 * If all IDs given by <idlist> are listed (ie collect has been
10 * invoked for each ID in <idlist>) collect returns 0, the
11 * number of missing IDs otherwise.
12 * A negative number is returned on error.
13 *
14 * Copyright © 2007, Hannes Reinecke <hare@suse.de>
15 *
16 * This program is free software: you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation, either version 2 of the License, or
19 * (at your option) any later version.
20 */
21
22 #include <errno.h>
23 #include <getopt.h>
24 #include <stddef.h>
25 #include <stdio.h>
26
27 #include "alloc-util.h"
28 #include "libudev-private.h"
29 #include "macro.h"
30 #include "stdio-util.h"
31 #include "string-util.h"
32 #include "udev-util.h"
33
34 #define BUFSIZE 16
35 #define UDEV_ALARM_TIMEOUT 180
36
37 enum collect_state {
38 STATE_NONE,
39 STATE_OLD,
40 STATE_CONFIRMED,
41 };
42
43 struct _mate {
44 struct udev_list_node node;
45 char *name;
46 enum collect_state state;
47 };
48
49 static struct udev_list_node bunch;
50 static int debug;
51
52 /* This can increase dynamically */
53 static size_t bufsize = BUFSIZE;
54
55 static inline struct _mate *node_to_mate(struct udev_list_node *node)
56 {
57 return container_of(node, struct _mate, node);
58 }
59
60 _noreturn_ static void sig_alrm(int signo)
61 {
62 exit(4);
63 }
64
65 static void usage(void)
66 {
67 printf("%s [options] <checkpoint> <id> <idlist>\n\n"
68 "Collect variables across events.\n\n"
69 " -h --help Print this message\n"
70 " -a --add Add ID <id> to the list <idlist>\n"
71 " -r --remove Remove ID <id> from the list <idlist>\n"
72 " -d --debug Debug to stderr\n\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\n"
79 , program_invocation_short_name);
80 }
81
82 /*
83 * prepare
84 *
85 * Prepares the database file
86 */
87 static int prepare(char *dir, char *filename)
88 {
89 char buf[PATH_MAX];
90 int r, fd;
91
92 r = mkdir(dir, 0700);
93 if (r < 0 && errno != EEXIST)
94 return -errno;
95
96 snprintf(buf, sizeof buf, "%s/%s", dir, filename);
97
98 fd = open(buf, O_RDWR|O_CREAT|O_CLOEXEC, S_IRUSR|S_IWUSR);
99 if (fd < 0)
100 fprintf(stderr, "Cannot open %s: %m\n", buf);
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 (IN_SET(errno, EAGAIN, 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: %m\n", buf);
113 }
114 }
115
116 return fd;
117 }
118
119 /*
120 * Read checkpoint file
121 *
122 * Tricky reading this. We allocate a buffer twice as large
123 * as we're going to read. Then we read into the upper half
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 {
136 int len;
137 _cleanup_free_ char *buf = NULL;
138 char *ptr, *word = NULL;
139 struct _mate *him;
140
141 restart:
142 len = bufsize >> 1;
143 buf = malloc(bufsize + 1);
144 if (!buf)
145 return log_oom();
146 memset(buf, ' ', bufsize);
147 buf[bufsize] = '\0';
148
149 ptr = buf + len;
150 while ((read(fd, buf + len, len)) > 0) {
151 while (ptr && *ptr) {
152 word = ptr;
153 ptr = strpbrk(word," \n\t\r");
154 if (!ptr && word < (buf + len)) {
155 bufsize = bufsize << 1;
156 if (debug)
157 fprintf(stderr, "ID overflow, restarting with size %zu\n", bufsize);
158 lseek(fd, 0, SEEK_SET);
159 goto restart;
160 }
161 if (ptr) {
162 *ptr = '\0';
163 ptr++;
164 if (isempty(word))
165 continue;
166
167 if (debug)
168 fprintf(stderr, "Found word %s\n", word);
169 him = malloc(sizeof (struct _mate));
170 if (!him)
171 return log_oom();
172 him->name = strdup(word);
173 if (!him->name) {
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 ptr -= len;
188 }
189
190 return 0;
191 }
192
193 /*
194 * invite
195 *
196 * Adds a new ID 'us' to the internal list,
197 * marks it as confirmed.
198 */
199 static void invite(char *us)
200 {
201 struct udev_list_node *him_node;
202 struct _mate *who = NULL;
203
204 if (debug)
205 fprintf(stderr, "Adding ID '%s'\n", us);
206
207 udev_list_node_foreach(him_node, &bunch) {
208 struct _mate *him = node_to_mate(him_node);
209
210 if (streq(him->name, us)) {
211 him->state = STATE_CONFIRMED;
212 who = him;
213 }
214 }
215 if (debug && !who)
216 fprintf(stderr, "ID '%s' not in database\n", us);
217
218 }
219
220 /*
221 * reject
222 *
223 * Marks the ID 'us' as invalid,
224 * causing it to be removed when the
225 * list is written out.
226 */
227 static void reject(char *us)
228 {
229 struct udev_list_node *him_node;
230 struct _mate *who = NULL;
231
232 if (debug)
233 fprintf(stderr, "Removing ID '%s'\n", us);
234
235 udev_list_node_foreach(him_node, &bunch) {
236 struct _mate *him = node_to_mate(him_node);
237
238 if (streq(him->name, us)) {
239 him->state = STATE_NONE;
240 who = him;
241 }
242 }
243 if (debug && !who)
244 fprintf(stderr, "ID '%s' not in database\n", us);
245 }
246
247 /*
248 * kickout
249 *
250 * Remove all IDs in the internal list which are not part
251 * of the list passed via the command line.
252 */
253 static void kickout(void)
254 {
255 struct udev_list_node *him_node;
256 struct udev_list_node *tmp;
257
258 udev_list_node_foreach_safe(him_node, tmp, &bunch) {
259 struct _mate *him = node_to_mate(him_node);
260
261 if (him->state == STATE_OLD) {
262 udev_list_node_remove(&him->node);
263 free(him->name);
264 free(him);
265 }
266 }
267 }
268
269 /*
270 * missing
271 *
272 * Counts all missing IDs in the internal list.
273 */
274 static int missing(int fd)
275 {
276 char *buf;
277 int ret = 0;
278 struct udev_list_node *him_node;
279
280 buf = malloc(bufsize);
281 if (!buf)
282 return log_oom();
283
284 udev_list_node_foreach(him_node, &bunch) {
285 struct _mate *him = node_to_mate(him_node);
286
287 if (him->state == STATE_NONE) {
288 ret++;
289 } else {
290 while (strlen(him->name)+1 >= bufsize) {
291 char *tmpbuf;
292
293 bufsize = bufsize << 1;
294 tmpbuf = realloc(buf, bufsize);
295 if (!tmpbuf) {
296 free(buf);
297 return log_oom();
298 }
299 buf = tmpbuf;
300 }
301 snprintf(buf, strlen(him->name)+2, "%s ", him->name);
302 if (write(fd, buf, strlen(buf)) < 0) {
303 free(buf);
304 return -1;
305 }
306 }
307 }
308
309 free(buf);
310 return ret;
311 }
312
313 /*
314 * everybody
315 *
316 * Prints out the status of the internal list.
317 */
318 static void everybody(void)
319 {
320 struct udev_list_node *him_node;
321 const char *state = "";
322
323 udev_list_node_foreach(him_node, &bunch) {
324 struct _mate *him = node_to_mate(him_node);
325
326 switch (him->state) {
327 case STATE_NONE:
328 state = "none";
329 break;
330 case STATE_OLD:
331 state = "old";
332 break;
333 case STATE_CONFIRMED:
334 state = "confirmed";
335 break;
336 }
337 fprintf(stderr, "ID: %s=%s\n", him->name, state);
338 }
339 }
340
341 int main(int argc, char **argv) {
342 static const struct option options[] = {
343 { "add", no_argument, NULL, 'a' },
344 { "remove", no_argument, NULL, 'r' },
345 { "debug", no_argument, NULL, 'd' },
346 { "help", no_argument, NULL, 'h' },
347 {}
348 };
349 int argi;
350 char *checkpoint, *us;
351 int fd;
352 int i;
353 int ret = EXIT_SUCCESS;
354 int prune = 0;
355 char tmpdir[UTIL_PATH_SIZE];
356
357 log_set_target(LOG_TARGET_AUTO);
358 udev_parse_config();
359 log_parse_environment();
360 log_open();
361
362 for (;;) {
363 int option;
364
365 option = getopt_long(argc, argv, "ardh", options, NULL);
366 if (option == -1)
367 break;
368
369 switch (option) {
370 case 'a':
371 prune = 0;
372 break;
373 case 'r':
374 prune = 1;
375 break;
376 case 'd':
377 debug = 1;
378 break;
379 case 'h':
380 usage();
381 return 0;
382 default:
383 return 1;
384 }
385 }
386
387 argi = optind;
388 if (argi + 2 > argc) {
389 printf("Missing parameter(s)\n");
390 return 1;
391 }
392 checkpoint = argv[argi++];
393 us = argv[argi++];
394
395 if (signal(SIGALRM, sig_alrm) == SIG_ERR) {
396 fprintf(stderr, "Cannot set SIGALRM: %m\n");
397 return 2;
398 }
399
400 udev_list_node_init(&bunch);
401
402 if (debug)
403 fprintf(stderr, "Using checkpoint '%s'\n", checkpoint);
404
405 strscpyl(tmpdir, sizeof(tmpdir), "/run/udev/collect", NULL);
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 (streq(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 = new(struct _mate, 1);
434 if (!him) {
435 ret = ENOMEM;
436 goto out;
437 }
438
439 him->name = strdup(argv[i]);
440 if (!him->name) {
441 free(him);
442 ret = ENOMEM;
443 goto out;
444 }
445
446 him->state = STATE_NONE;
447 udev_list_node_append(&him->node, &bunch);
448 } else {
449 if (debug)
450 fprintf(stderr, "ID %s: found in database\n", argv[i]);
451 who->state = STATE_CONFIRMED;
452 }
453 }
454
455 if (prune)
456 reject(us);
457 else
458 invite(us);
459
460 if (debug) {
461 everybody();
462 fprintf(stderr, "Prune lists\n");
463 }
464 kickout();
465
466 lseek(fd, 0, SEEK_SET);
467 ftruncate(fd, 0);
468 ret = missing(fd);
469
470 lockf(fd, F_ULOCK, 0);
471 close(fd);
472 out:
473 if (debug)
474 everybody();
475 if (ret >= 0)
476 printf("COLLECT_%s=%d\n", checkpoint, ret);
477 return ret;
478 }