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