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