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