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