chiark / gitweb /
tabs are as useful as a hole in the head
[elogind.git] / src / 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 BUFSIZE                        16
38 #define UDEV_ALARM_TIMEOUT        180
39
40 enum collect_state {
41         STATE_NONE,
42         STATE_OLD,
43         STATE_CONFIRMED,
44 };
45
46 struct _mate {
47         struct udev_list_node node;
48         char *name;
49         enum collect_state state;
50 };
51
52 static struct udev_list_node bunch;
53 static int debug;
54
55 /* This can increase dynamically */
56 static size_t bufsize = BUFSIZE;
57
58 static struct _mate *node_to_mate(struct udev_list_node *node)
59 {
60         char *mate;
61
62         mate = (char *)node;
63         mate -= offsetof(struct _mate, node);
64         return (struct _mate *)mate;
65 }
66
67 static void sig_alrm(int signo)
68 {
69         exit(4);
70 }
71
72 static void usage(void)
73 {
74         printf("usage: collect [--add|--remove] [--debug] <checkpoint> <id> <idlist>\n"
75                "\n"
76                "  Adds ID <id> to the list governed by <checkpoint>.\n"
77                "  <id> must be part of the list <idlist>.\n"
78                "  If all IDs given by <idlist> are listed (ie collect has been\n"
79                "  invoked for each ID in <idlist>) collect returns 0, the\n"
80                "  number of missing IDs otherwise.\n"
81                "  On error a negative number is returned.\n"
82                "\n");
83 }
84
85 /*
86  * prepare
87  *
88  * Prepares the database file
89  */
90 static int prepare(char *dir, char *filename)
91 {
92         struct stat statbuf;
93         char buf[512];
94         int fd;
95
96         if (stat(dir, &statbuf) < 0)
97                 mkdir(dir, 0700);
98
99         sprintf(buf, "%s/%s", dir, filename);
100
101         fd = open(buf,O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
102         if (fd < 0)
103                 fprintf(stderr, "Cannot open %s: %s\n", buf, strerror(errno));
104
105         if (lockf(fd,F_TLOCK,0) < 0) {
106                 if (debug)
107                         fprintf(stderr, "Lock taken, wait for %d seconds\n", UDEV_ALARM_TIMEOUT);
108                 if (errno == EAGAIN || errno == EACCES) {
109                         alarm(UDEV_ALARM_TIMEOUT);
110                         lockf(fd, F_LOCK, 0);
111                         if (debug)
112                                 fprintf(stderr, "Acquired lock on %s\n", buf);
113                 } else {
114                         if (debug)
115                                 fprintf(stderr, "Could not get lock on %s: %s\n", buf, strerror(errno));
116                 }
117         }
118
119         return fd;
120 }
121
122 /*
123  * Read checkpoint file
124  *
125  * Tricky reading this. We allocate a buffer twice as large
126  * as we're going to read. Then we read into the upper half
127  * of that buffer and start parsing.
128  * Once we do _not_ find end-of-work terminator (whitespace
129  * character) we move the upper half to the lower half,
130  * adjust the read pointer and read the next bit.
131  * Quite clever methinks :-)
132  * I should become a programmer ...
133  *
134  * Yes, one could have used fgets() for this. But then we'd
135  * have to use freopen etc which I found quite tedious.
136  */
137 static int checkout(int fd)
138 {
139         int len;
140         char *buf, *ptr, *word = NULL;
141         struct _mate *him;
142
143  restart:
144         len = bufsize >> 1;
145         buf = calloc(1,bufsize + 1);
146         if (!buf) {
147                 fprintf(stderr, "Out of memory\n");
148                 return -1;
149         }
150         memset(buf, ' ', bufsize);
151         ptr = buf + len;
152         while ((read(fd, buf + len, len)) > 0) {
153                 while (ptr && *ptr) {
154                         word = ptr;
155                         ptr = strpbrk(word," \n\t\r");
156                         if (!ptr && word < (buf + len)) {
157                                 bufsize = bufsize << 1;
158                                 if (debug)
159                                         fprintf(stderr, "ID overflow, restarting with size %zi\n", bufsize);
160                                 free(buf);
161                                 lseek(fd, 0, SEEK_SET);
162                                 goto restart;
163                         }
164                         if (ptr) {
165                                 *ptr = '\0';
166                                 ptr++;
167                                 if (!strlen(word))
168                                         continue;
169
170                                 if (debug)
171                                         fprintf(stderr, "Found word %s\n", word);
172                                 him = malloc(sizeof (struct _mate));
173                                 him->name = strdup(word);
174                                 him->state = STATE_OLD;
175                                 udev_list_node_append(&him->node, &bunch);
176                                 word = NULL;
177                         }
178                 }
179                 memcpy(buf, buf + len, len);
180                 memset(buf + len, ' ', len);
181
182                 if (!ptr)
183                         ptr = word;
184                 if (!ptr)
185                         break;
186                 ptr -= len;
187         }
188
189         free(buf);
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 (!strcmp(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 (!strcmp(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 commandline.
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 -1;
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 -1;
298                                 }
299                                 buf = tmpbuf;
300                         }
301                         snprintf(buf, strlen(him->name)+2, "%s ", him->name);
302                         write(fd, buf, strlen(buf));
303                 }
304         }
305
306         free(buf);
307         return ret;
308 }
309
310 /*
311  * everybody
312  *
313  * Prints out the status of the internal list.
314  */
315 static void everybody(void)
316 {
317         struct udev_list_node *him_node;
318         const char *state = "";
319
320         udev_list_node_foreach(him_node, &bunch) {
321                 struct _mate *him = node_to_mate(him_node);
322
323                 switch (him->state) {
324                 case STATE_NONE:
325                         state = "none";
326                         break;
327                 case STATE_OLD:
328                         state = "old";
329                         break;
330                 case STATE_CONFIRMED:
331                         state = "confirmed";
332                         break;
333                 }
334                 fprintf(stderr, "ID: %s=%s\n", him->name, state);
335         }
336 }
337
338 int main(int argc, char **argv)
339 {
340         struct udev *udev;
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 = EXIT_SUCCESS;
353         int prune = 0;
354         char tmpdir[UTIL_PATH_SIZE];
355
356         udev = udev_new();
357         if (udev == NULL) {
358                 ret = EXIT_FAILURE;
359                 goto exit;
360         }
361
362         while (1) {
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                         goto exit;
382                 default:
383                         ret = 1;
384                         goto exit;
385                 }
386         }
387
388         argi = optind;
389         if (argi + 2 > argc) {
390                 printf("Missing parameter(s)\n");
391                 ret = 1;
392                 goto exit;
393         }
394         checkpoint = argv[argi++];
395         us = argv[argi++];
396
397         if (signal(SIGALRM, sig_alrm) == SIG_ERR) {
398                 fprintf(stderr, "Cannot set SIGALRM: %s\n", strerror(errno));
399                 ret = 2;
400                 goto exit;
401         }
402
403         udev_list_node_init(&bunch);
404
405         if (debug)
406                 fprintf(stderr, "Using checkpoint '%s'\n", checkpoint);
407
408         util_strscpyl(tmpdir, sizeof(tmpdir), udev_get_run_path(udev), "/collect", NULL);
409         fd = prepare(tmpdir, checkpoint);
410         if (fd < 0) {
411                 ret = 3;
412                 goto out;
413         }
414
415         if (checkout(fd) < 0) {
416                 ret = 2;
417                 goto out;
418         }
419
420         for (i = argi; i < argc; i++) {
421                 struct udev_list_node *him_node;
422                 struct _mate *who;
423
424                 who = NULL;
425                 udev_list_node_foreach(him_node, &bunch) {
426                         struct _mate *him = node_to_mate(him_node);
427
428                         if (!strcmp(him->name, argv[i]))
429                                 who = him;
430                 }
431                 if (!who) {
432                         struct _mate *him;
433
434                         if (debug)
435                                 fprintf(stderr, "ID %s: not in database\n", argv[i]);
436                         him = malloc(sizeof (struct _mate));
437                         him->name = malloc(strlen(argv[i]) + 1);
438                         strcpy(him->name, argv[i]);
439                         him->state = STATE_NONE;
440                         udev_list_node_append(&him->node, &bunch);
441                 } else {
442                         if (debug)
443                                 fprintf(stderr, "ID %s: found in database\n", argv[i]);
444                         who->state = STATE_CONFIRMED;
445                 }
446         }
447
448         if (prune)
449                 reject(us);
450         else
451                 invite(us);
452
453         if (debug) {
454                 everybody();
455                 fprintf(stderr, "Prune lists\n");
456         }
457         kickout();
458
459         lseek(fd, 0, SEEK_SET);
460         ftruncate(fd, 0);
461         ret = missing(fd);
462
463         lockf(fd, F_ULOCK, 0);
464         close(fd);
465 out:
466         if (debug)
467                 everybody();
468         if (ret >= 0)
469                 printf("COLLECT_%s=%d\n", checkpoint, ret);
470 exit:
471         udev_unref(udev);
472         return ret;
473 }