chiark / gitweb /
collect: use udev_list
[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 "../../udev/lib/libudev.h"
35 #include "../../udev/lib/libudev-private.h"
36
37 #define TMPFILE                 UDEV_PREFIX "/dev/.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 goint 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 = malloc(strlen(word) + 1);
175                                 strcpy(him->name, word);
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 (!strcmp(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 (!strcmp(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 commandline.
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 -1;
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 -1;
300                                 }
301                                 buf = tmpbuf;
302                         }
303                         snprintf(buf, strlen(him->name)+2, "%s ", him->name);
304                         write(fd, buf, strlen(buf));
305                 }
306         }
307
308         free(buf);
309         return ret;
310 }
311
312 /*
313  * everybody
314  *
315  * Prints out the status of the internal list.
316  */
317 static void everybody(void)
318 {
319         struct udev_list_node *him_node;
320         const char *state = "";
321
322         udev_list_node_foreach(him_node, &bunch) {
323                 struct _mate *him = node_to_mate(him_node);
324
325                 switch (him->state) {
326                 case STATE_NONE:
327                         state = "none";
328                         break;
329                 case STATE_OLD:
330                         state = "old";
331                         break;
332                 case STATE_CONFIRMED:
333                         state = "confirmed";
334                         break;
335                 fprintf(stderr, "ID: %s=%s\n", him->name, state);
336                 }
337         }
338 }
339
340 int main(int argc, char **argv)
341 {
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 = 0;
354         int prune = 0;
355
356         while (1) {
357                 int option;
358
359                 option = getopt_long(argc, argv, "ardh", options, NULL);
360                 if (option == -1)
361                         break;
362
363                 switch (option) {
364                 case 'a':
365                         prune = 0;
366                         break;
367                 case 'r':
368                         prune = 1;
369                         break;
370                 case 'd':
371                         debug = 1;
372                         break;
373                 case 'h':
374                         usage();
375                         goto exit;
376                 default:
377                         ret = 1;
378                         goto exit;
379                 }
380         }
381
382         argi = optind;
383         if (argi + 2 > argc) {
384                 printf("Missing parameter(s)\n");
385                 ret = 1;
386                 goto exit;
387         }
388         checkpoint = argv[argi++];
389         us = argv[argi++];
390
391         if (signal(SIGALRM, sig_alrm) == SIG_ERR) {
392                 fprintf(stderr, "Cannot set SIGALRM: %s\n", strerror(errno));
393                 ret = 2;
394                 goto exit;
395         }
396
397         udev_list_init(&bunch);
398
399         if (debug)
400                 fprintf(stderr, "Using checkpoint '%s'\n", checkpoint);
401
402         fd = prepare(TMPFILE, checkpoint);
403         if (fd < 0) {
404                 ret = 3;
405                 goto out;
406         }
407
408         if (checkout(fd) < 0) {
409                 ret = 2;
410                 goto out;
411         }
412
413         for (i = argi; i < argc; i++) {
414                 struct udev_list_node *him_node;
415                 struct _mate *who;
416
417                 who = NULL;
418                 udev_list_node_foreach(him_node, &bunch) {
419                         struct _mate *him = node_to_mate(him_node);
420
421                         if (!strcmp(him->name, argv[i]))
422                                 who = him;
423                 }
424                 if (!who) {
425                         struct _mate *him;
426
427                         if (debug)
428                                 fprintf(stderr, "ID %s: not in database\n", argv[i]);
429                         him = malloc(sizeof (struct _mate));
430                         him->name = malloc(strlen(argv[i]) + 1);
431                         strcpy(him->name, argv[i]);
432                         him->state = STATE_NONE;
433                         udev_list_node_append(&him->node, &bunch);
434                 } else {
435                         if (debug)
436                                 fprintf(stderr, "ID %s: found in database\n", argv[i]);
437                         who->state = STATE_CONFIRMED;
438                 }
439         }
440
441         if (prune)
442                 reject(us);
443         else
444                 invite(us);
445
446         if (debug) {
447                 everybody();
448                 fprintf(stderr, "Prune lists\n");
449         }
450         kickout();
451
452         lseek(fd, 0, SEEK_SET);
453         ftruncate(fd, 0);
454         ret = missing(fd);
455
456         lockf(fd, F_ULOCK, 0);
457         close(fd);
458  out:
459         if (debug)
460                 everybody();
461         if (ret >= 0)
462                 printf("COLLECT_%s=%d\n", checkpoint, ret);
463  exit:
464         return ret;
465 }