chiark / gitweb /
14184a5e058977361d9927afce60005e8517d806
[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/list.h"
35
36 #define TMPFILE                 UDEV_PREFIX "/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 size_t 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 %zi\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                         while (strlen(him->name)+1 >= bufsize) {
275                                 char *tmpbuf;
276
277                                 bufsize = bufsize << 1;
278                                 tmpbuf = realloc(buf, bufsize);
279                                 if (!tmpbuf) {
280                                         free(buf);
281                                         return -1;
282                                 }
283                                 buf = tmpbuf;
284                         }
285                         snprintf(buf, strlen(him->name)+2, "%s ", him->name);
286                         write(fd, buf, strlen(buf));
287                 }
288         }
289
290         free(buf);
291
292         return ret;
293 }
294
295 /*
296  * everybody
297  *
298  * Prints out the status of the internal list.
299  */
300 static void everybody(void)
301 {
302         struct _mate *him;
303         const char *state = "";
304
305         list_for_each_entry(him, &bunch, node) {
306                 switch (him->state) {
307                 case STATE_NONE:
308                         state = "none";
309                         break;
310                 case STATE_OLD:
311                         state = "old";
312                         break;
313                 case STATE_CONFIRMED:
314                         state = "confirmed";
315                         break;
316                 fprintf(stderr, "ID: %s=%s\n", him->name, state);
317                 }
318         }
319 }
320
321 int main(int argc, char **argv)
322 {
323         static const struct option options[] = {
324                 { "add", 0, NULL, 'a' },
325                 { "remove", 0, NULL, 'r' },
326                 { "debug", 0, NULL, 'd' },
327                 { "help", 0, NULL, 'h' },
328                 {}
329         };
330         int argi;
331         char *checkpoint, *us;
332         struct _mate *him, *who;
333         int fd;
334         int i;
335         int ret = 0;
336         int prune = 0;
337
338         while (1) {
339                 int option;
340
341                 option = getopt_long(argc, argv, "ardh", options, NULL);
342                 if (option == -1)
343                         break;
344
345                 switch (option) {
346                 case 'a':
347                         prune = 0;
348                         break;
349                 case 'r':
350                         prune = 1;
351                         break;
352                 case 'd':
353                         debug = 1;
354                         break;
355                 case 'h':
356                         usage();
357                         goto exit;
358                 default:
359                         ret = 1;
360                         goto exit;
361                 }
362         }
363
364         argi = optind;
365         if (argi + 2 > argc) {
366                 printf("Missing parameter(s)\n");
367                 ret = 1;
368                 goto exit;
369         }
370         checkpoint = argv[argi++];
371         us = argv[argi++];
372
373         if (signal(SIGALRM, sig_alrm) == SIG_ERR) {
374                 fprintf(stderr, "Cannot set SIGALRM: %s\n", strerror(errno));
375                 ret = 2;
376                 goto exit;
377         }
378
379         INIT_LIST_HEAD(&bunch);
380
381         if (debug)
382                 fprintf(stderr, "Using checkpoint '%s'\n", checkpoint);
383
384         fd = prepare(TMPFILE, checkpoint);
385         if (fd < 0) {
386                 ret = 3;
387                 goto out;
388         }
389
390         if (checkout(fd) < 0) {
391                 ret = 2;
392                 goto out;
393         }
394
395         for (i = argi; i < argc; i++) {
396                 who = NULL;
397                 list_for_each_entry(him, &bunch, node) {
398                         if (!strcmp(him->name, argv[i]))
399                                 who = him;
400                 }
401                 if (!who) {
402                         if (debug)
403                                 fprintf(stderr, "ID %s: not in database\n", argv[i]);
404                         him = malloc(sizeof (struct _mate));
405                         him->name = malloc(strlen(argv[i]) + 1);
406                         strcpy(him->name, argv[i]);
407                         him->state = STATE_NONE;
408                         list_add_tail(&him->node, &bunch);
409                 } else {
410                         if (debug)
411                                 fprintf(stderr, "ID %s: found in database\n", argv[i]);
412                         who->state = STATE_CONFIRMED;
413                 }
414         }
415
416         if (prune)
417                 reject(us);
418         else
419                 invite(us);
420
421         if (debug) {
422                 everybody();
423                 fprintf(stderr, "Prune lists\n");
424         }
425         kickout();
426
427         lseek(fd, 0, SEEK_SET);
428         ftruncate(fd, 0);
429         ret = missing(fd);
430
431         lockf(fd, F_ULOCK, 0);
432         close(fd);
433  out:
434         if (debug)
435                 everybody();
436         if (ret >= 0)
437                 printf("COLLECT_%s=%d\n", checkpoint, ret);
438  exit:
439         return ret;
440 }