chiark / gitweb /
remove unused includes
[elogind.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 }