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