chiark / gitweb /
3c46e40de11cb84de379957b5163d981aaefa5f8
[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 <stdlib.h>
24 #include <stddef.h>
25 #include <unistd.h>
26 #include <signal.h>
27 #include <fcntl.h>
28 #include <errno.h>
29 #include <string.h>
30 #include <getopt.h>
31 #include <sys/types.h>
32 #include <sys/stat.h>
33
34 #include "libudev.h"
35 #include "libudev-private.h"
36 #include "macro.h"
37
38 #define BUFSIZE                        16
39 #define UDEV_ALARM_TIMEOUT        180
40
41 enum collect_state {
42         STATE_NONE,
43         STATE_OLD,
44         STATE_CONFIRMED,
45 };
46
47 struct _mate {
48         struct udev_list_node node;
49         char *name;
50         enum collect_state state;
51 };
52
53 static struct udev_list_node bunch;
54 static int debug;
55
56 /* This can increase dynamically */
57 static size_t bufsize = BUFSIZE;
58
59 static inline struct _mate *node_to_mate(struct udev_list_node *node)
60 {
61         return container_of(node, struct _mate, node);
62 }
63
64 _noreturn_ static void sig_alrm(int signo)
65 {
66         exit(4);
67 }
68
69 static void usage(void)
70 {
71         printf("usage: collect [--add|--remove] [--debug] <checkpoint> <id> <idlist>\n"
72                "\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"
79                "\n");
80 }
81
82 /*
83  * prepare
84  *
85  * Prepares the database file
86  */
87 static int prepare(char *dir, char *filename)
88 {
89         struct stat statbuf;
90         char buf[512];
91         int fd;
92
93         if (stat(dir, &statbuf) < 0)
94                 mkdir(dir, 0700);
95
96         snprintf(buf, sizeof(buf), "%s/%s", dir, filename);
97
98         fd = open(buf,O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
99         if (fd < 0)
100                 fprintf(stderr, "Cannot open %s: %s\n", buf, strerror(errno));
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 (errno == EAGAIN || errno == 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: %s\n", buf, strerror(errno));
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         char *buf, *ptr, *word = NULL;
138         struct _mate *him;
139
140  restart:
141         len = bufsize >> 1;
142         buf = calloc(1,bufsize + 1);
143         if (!buf) {
144                 fprintf(stderr, "Out of memory.\n");
145                 return log_oom();
146         }
147         memset(buf, ' ', bufsize);
148         ptr = buf + len;
149         while ((read(fd, buf + len, len)) > 0) {
150                 while (ptr && *ptr) {
151                         word = ptr;
152                         ptr = strpbrk(word," \n\t\r");
153                         if (!ptr && word < (buf + len)) {
154                                 bufsize = bufsize << 1;
155                                 if (debug)
156                                         fprintf(stderr, "ID overflow, restarting with size %zi\n", bufsize);
157                                 free(buf);
158                                 lseek(fd, 0, SEEK_SET);
159                                 goto restart;
160                         }
161                         if (ptr) {
162                                 *ptr = '\0';
163                                 ptr++;
164                                 if (!strlen(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                                         free(buf);
172                                         return log_oom();
173                                 }
174                                 him->name = strdup(word);
175                                 if (!him->name) {
176                                         free(buf);
177                                         free(him);
178                                         return log_oom();
179                                 }
180                                 him->state = STATE_OLD;
181                                 udev_list_node_append(&him->node, &bunch);
182                                 word = NULL;
183                         }
184                 }
185                 memcpy(buf, buf + len, len);
186                 memset(buf + len, ' ', len);
187
188                 if (!ptr)
189                         ptr = word;
190                 if (!ptr)
191                         break;
192                 ptr -= len;
193         }
194
195         free(buf);
196         return 0;
197 }
198
199 /*
200  * invite
201  *
202  * Adds a new ID 'us' to the internal list,
203  * marks it as confirmed.
204  */
205 static void invite(char *us)
206 {
207         struct udev_list_node *him_node;
208         struct _mate *who = NULL;
209
210         if (debug)
211                 fprintf(stderr, "Adding ID '%s'\n", us);
212
213         udev_list_node_foreach(him_node, &bunch) {
214                 struct _mate *him = node_to_mate(him_node);
215
216                 if (!strcmp(him->name, us)) {
217                         him->state = STATE_CONFIRMED;
218                         who = him;
219                 }
220         }
221         if (debug && !who)
222                 fprintf(stderr, "ID '%s' not in database\n", us);
223
224 }
225
226 /*
227  * reject
228  *
229  * Marks the ID 'us' as invalid,
230  * causing it to be removed when the
231  * list is written out.
232  */
233 static void reject(char *us)
234 {
235         struct udev_list_node *him_node;
236         struct _mate *who = NULL;
237
238         if (debug)
239                 fprintf(stderr, "Removing ID '%s'\n", us);
240
241         udev_list_node_foreach(him_node, &bunch) {
242                 struct _mate *him = node_to_mate(him_node);
243
244                 if (!strcmp(him->name, us)) {
245                         him->state = STATE_NONE;
246                         who = him;
247                 }
248         }
249         if (debug && !who)
250                 fprintf(stderr, "ID '%s' not in database\n", us);
251 }
252
253 /*
254  * kickout
255  *
256  * Remove all IDs in the internal list which are not part
257  * of the list passed via the commandline.
258  */
259 static void kickout(void)
260 {
261         struct udev_list_node *him_node;
262         struct udev_list_node *tmp;
263
264         udev_list_node_foreach_safe(him_node, tmp, &bunch) {
265                 struct _mate *him = node_to_mate(him_node);
266
267                 if (him->state == STATE_OLD) {
268                         udev_list_node_remove(&him->node);
269                         free(him->name);
270                         free(him);
271                 }
272         }
273 }
274
275 /*
276  * missing
277  *
278  * Counts all missing IDs in the internal list.
279  */
280 static int missing(int fd)
281 {
282         char *buf;
283         int ret = 0;
284         struct udev_list_node *him_node;
285
286         buf = malloc(bufsize);
287         if (!buf)
288                 return log_oom();
289
290         udev_list_node_foreach(him_node, &bunch) {
291                 struct _mate *him = node_to_mate(him_node);
292
293                 if (him->state == STATE_NONE) {
294                         ret++;
295                 } else {
296                         while (strlen(him->name)+1 >= bufsize) {
297                                 char *tmpbuf;
298
299                                 bufsize = bufsize << 1;
300                                 tmpbuf = realloc(buf, bufsize);
301                                 if (!tmpbuf) {
302                                         free(buf);
303                                         return log_oom();
304                                 }
305                                 buf = tmpbuf;
306                         }
307                         snprintf(buf, strlen(him->name)+2, "%s ", him->name);
308                         if (write(fd, buf, strlen(buf)) < 0) {
309                                 free(buf);
310                                 return -1;
311                         }
312                 }
313         }
314
315         free(buf);
316         return ret;
317 }
318
319 /*
320  * everybody
321  *
322  * Prints out the status of the internal list.
323  */
324 static void everybody(void)
325 {
326         struct udev_list_node *him_node;
327         const char *state = "";
328
329         udev_list_node_foreach(him_node, &bunch) {
330                 struct _mate *him = node_to_mate(him_node);
331
332                 switch (him->state) {
333                 case STATE_NONE:
334                         state = "none";
335                         break;
336                 case STATE_OLD:
337                         state = "old";
338                         break;
339                 case STATE_CONFIRMED:
340                         state = "confirmed";
341                         break;
342                 }
343                 fprintf(stderr, "ID: %s=%s\n", him->name, state);
344         }
345 }
346
347 int main(int argc, char **argv)
348 {
349         struct udev *udev;
350         static const struct option options[] = {
351                 { "add", no_argument, NULL, 'a' },
352                 { "remove", no_argument, NULL, 'r' },
353                 { "debug", no_argument, NULL, 'd' },
354                 { "help", no_argument, NULL, 'h' },
355                 {}
356         };
357         int argi;
358         char *checkpoint, *us;
359         int fd;
360         int i;
361         int ret = EXIT_SUCCESS;
362         int prune = 0;
363         char tmpdir[UTIL_PATH_SIZE];
364
365         udev = udev_new();
366         if (udev == NULL) {
367                 ret = EXIT_FAILURE;
368                 goto exit;
369         }
370
371         while (1) {
372                 int option;
373
374                 option = getopt_long(argc, argv, "ardh", options, NULL);
375                 if (option == -1)
376                         break;
377
378                 switch (option) {
379                 case 'a':
380                         prune = 0;
381                         break;
382                 case 'r':
383                         prune = 1;
384                         break;
385                 case 'd':
386                         debug = 1;
387                         break;
388                 case 'h':
389                         usage();
390                         goto exit;
391                 default:
392                         ret = 1;
393                         goto exit;
394                 }
395         }
396
397         argi = optind;
398         if (argi + 2 > argc) {
399                 printf("Missing parameter(s)\n");
400                 ret = 1;
401                 goto exit;
402         }
403         checkpoint = argv[argi++];
404         us = argv[argi++];
405
406         if (signal(SIGALRM, sig_alrm) == SIG_ERR) {
407                 fprintf(stderr, "Cannot set SIGALRM: %s\n", strerror(errno));
408                 ret = 2;
409                 goto exit;
410         }
411
412         udev_list_node_init(&bunch);
413
414         if (debug)
415                 fprintf(stderr, "Using checkpoint '%s'\n", checkpoint);
416
417         util_strscpyl(tmpdir, sizeof(tmpdir), "/run/udev/collect", NULL);
418         fd = prepare(tmpdir, checkpoint);
419         if (fd < 0) {
420                 ret = 3;
421                 goto out;
422         }
423
424         if (checkout(fd) < 0) {
425                 ret = 2;
426                 goto out;
427         }
428
429         for (i = argi; i < argc; i++) {
430                 struct udev_list_node *him_node;
431                 struct _mate *who;
432
433                 who = NULL;
434                 udev_list_node_foreach(him_node, &bunch) {
435                         struct _mate *him = node_to_mate(him_node);
436
437                         if (!strcmp(him->name, argv[i]))
438                                 who = him;
439                 }
440                 if (!who) {
441                         struct _mate *him;
442
443                         if (debug)
444                                 fprintf(stderr, "ID %s: not in database\n", argv[i]);
445                         him = malloc(sizeof (struct _mate));
446                         if (!him) {
447                                 ret = ENOMEM;
448                                 goto out;
449                         }
450
451                         him->name = malloc(strlen(argv[i]) + 1);
452                         if (!him->name) {
453                                 ret = ENOMEM;
454                                 goto out;
455                         }
456
457                         strcpy(him->name, argv[i]);
458                         him->state = STATE_NONE;
459                         udev_list_node_append(&him->node, &bunch);
460                 } else {
461                         if (debug)
462                                 fprintf(stderr, "ID %s: found in database\n", argv[i]);
463                         who->state = STATE_CONFIRMED;
464                 }
465         }
466
467         if (prune)
468                 reject(us);
469         else
470                 invite(us);
471
472         if (debug) {
473                 everybody();
474                 fprintf(stderr, "Prune lists\n");
475         }
476         kickout();
477
478         lseek(fd, 0, SEEK_SET);
479         ftruncate(fd, 0);
480         ret = missing(fd);
481
482         lockf(fd, F_ULOCK, 0);
483         close(fd);
484 out:
485         if (debug)
486                 everybody();
487         if (ret >= 0)
488                 printf("COLLECT_%s=%d\n", checkpoint, ret);
489 exit:
490         udev_unref(udev);
491         return ret;
492 }