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