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