chiark / gitweb /
udev: check malloc return in collect/collect.c
[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 log_oom();
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                                 if (!him) {
171                                         free(buf);
172                                         return log_oom();
173                                 }
174                                 him->name = strdup(word);
175                                 if (!him->name) {
176                                         free(buf);
177                                         free(him);
178                                         return log_oom();
179                                 }
180                                 him->state = STATE_OLD;
181                                 udev_list_node_append(&him->node, &bunch);
182                                 word = NULL;
183                         }
184                 }
185                 memcpy(buf, buf + len, len);
186                 memset(buf + len, ' ', len);
187
188                 if (!ptr)
189                         ptr = word;
190                 if (!ptr)
191                         break;
192                 ptr -= len;
193         }
194
195         free(buf);
196         return 0;
197 }
198
199 /*
200  * invite
201  *
202  * Adds a new ID 'us' to the internal list,
203  * marks it as confirmed.
204  */
205 static void invite(char *us)
206 {
207         struct udev_list_node *him_node;
208         struct _mate *who = NULL;
209
210         if (debug)
211                 fprintf(stderr, "Adding ID '%s'\n", us);
212
213         udev_list_node_foreach(him_node, &bunch) {
214                 struct _mate *him = node_to_mate(him_node);
215
216                 if (!strcmp(him->name, us)) {
217                         him->state = STATE_CONFIRMED;
218                         who = him;
219                 }
220         }
221         if (debug && !who)
222                 fprintf(stderr, "ID '%s' not in database\n", us);
223
224 }
225
226 /*
227  * reject
228  *
229  * Marks the ID 'us' as invalid,
230  * causing it to be removed when the
231  * list is written out.
232  */
233 static void reject(char *us)
234 {
235         struct udev_list_node *him_node;
236         struct _mate *who = NULL;
237
238         if (debug)
239                 fprintf(stderr, "Removing ID '%s'\n", us);
240
241         udev_list_node_foreach(him_node, &bunch) {
242                 struct _mate *him = node_to_mate(him_node);
243
244                 if (!strcmp(him->name, us)) {
245                         him->state = STATE_NONE;
246                         who = him;
247                 }
248         }
249         if (debug && !who)
250                 fprintf(stderr, "ID '%s' not in database\n", us);
251 }
252
253 /*
254  * kickout
255  *
256  * Remove all IDs in the internal list which are not part
257  * of the list passed via the commandline.
258  */
259 static void kickout(void)
260 {
261         struct udev_list_node *him_node;
262         struct udev_list_node *tmp;
263
264         udev_list_node_foreach_safe(him_node, tmp, &bunch) {
265                 struct _mate *him = node_to_mate(him_node);
266
267                 if (him->state == STATE_OLD) {
268                         udev_list_node_remove(&him->node);
269                         free(him->name);
270                         free(him);
271                 }
272         }
273 }
274
275 /*
276  * missing
277  *
278  * Counts all missing IDs in the internal list.
279  */
280 static int missing(int fd)
281 {
282         char *buf;
283         int ret = 0;
284         struct udev_list_node *him_node;
285
286         buf = malloc(bufsize);
287         if (!buf)
288                 return log_oom();
289
290         udev_list_node_foreach(him_node, &bunch) {
291                 struct _mate *him = node_to_mate(him_node);
292
293                 if (him->state == STATE_NONE) {
294                         ret++;
295                 } else {
296                         while (strlen(him->name)+1 >= bufsize) {
297                                 char *tmpbuf;
298
299                                 bufsize = bufsize << 1;
300                                 tmpbuf = realloc(buf, bufsize);
301                                 if (!tmpbuf) {
302                                         free(buf);
303                                         return log_oom();
304                                 }
305                                 buf = tmpbuf;
306                         }
307                         snprintf(buf, strlen(him->name)+2, "%s ", him->name);
308                         write(fd, buf, strlen(buf));
309                 }
310         }
311
312         free(buf);
313         return ret;
314 }
315
316 /*
317  * everybody
318  *
319  * Prints out the status of the internal list.
320  */
321 static void everybody(void)
322 {
323         struct udev_list_node *him_node;
324         const char *state = "";
325
326         udev_list_node_foreach(him_node, &bunch) {
327                 struct _mate *him = node_to_mate(him_node);
328
329                 switch (him->state) {
330                 case STATE_NONE:
331                         state = "none";
332                         break;
333                 case STATE_OLD:
334                         state = "old";
335                         break;
336                 case STATE_CONFIRMED:
337                         state = "confirmed";
338                         break;
339                 }
340                 fprintf(stderr, "ID: %s=%s\n", him->name, state);
341         }
342 }
343
344 int main(int argc, char **argv)
345 {
346         struct udev *udev;
347         static const struct option options[] = {
348                 { "add", no_argument, NULL, 'a' },
349                 { "remove", no_argument, NULL, 'r' },
350                 { "debug", no_argument, NULL, 'd' },
351                 { "help", no_argument, NULL, 'h' },
352                 {}
353         };
354         int argi;
355         char *checkpoint, *us;
356         int fd;
357         int i;
358         int ret = EXIT_SUCCESS;
359         int prune = 0;
360         char tmpdir[UTIL_PATH_SIZE];
361
362         udev = udev_new();
363         if (udev == NULL) {
364                 ret = EXIT_FAILURE;
365                 goto exit;
366         }
367
368         while (1) {
369                 int option;
370
371                 option = getopt_long(argc, argv, "ardh", options, NULL);
372                 if (option == -1)
373                         break;
374
375                 switch (option) {
376                 case 'a':
377                         prune = 0;
378                         break;
379                 case 'r':
380                         prune = 1;
381                         break;
382                 case 'd':
383                         debug = 1;
384                         break;
385                 case 'h':
386                         usage();
387                         goto exit;
388                 default:
389                         ret = 1;
390                         goto exit;
391                 }
392         }
393
394         argi = optind;
395         if (argi + 2 > argc) {
396                 printf("Missing parameter(s)\n");
397                 ret = 1;
398                 goto exit;
399         }
400         checkpoint = argv[argi++];
401         us = argv[argi++];
402
403         if (signal(SIGALRM, sig_alrm) == SIG_ERR) {
404                 fprintf(stderr, "Cannot set SIGALRM: %s\n", strerror(errno));
405                 ret = 2;
406                 goto exit;
407         }
408
409         udev_list_node_init(&bunch);
410
411         if (debug)
412                 fprintf(stderr, "Using checkpoint '%s'\n", checkpoint);
413
414         util_strscpyl(tmpdir, sizeof(tmpdir), "/run/udev/collect", NULL);
415         fd = prepare(tmpdir, checkpoint);
416         if (fd < 0) {
417                 ret = 3;
418                 goto out;
419         }
420
421         if (checkout(fd) < 0) {
422                 ret = 2;
423                 goto out;
424         }
425
426         for (i = argi; i < argc; i++) {
427                 struct udev_list_node *him_node;
428                 struct _mate *who;
429
430                 who = NULL;
431                 udev_list_node_foreach(him_node, &bunch) {
432                         struct _mate *him = node_to_mate(him_node);
433
434                         if (!strcmp(him->name, argv[i]))
435                                 who = him;
436                 }
437                 if (!who) {
438                         struct _mate *him;
439
440                         if (debug)
441                                 fprintf(stderr, "ID %s: not in database\n", argv[i]);
442                         him = malloc(sizeof (struct _mate));
443                         if (!him) {
444                                 ret = ENOMEM;
445                                 goto out;
446                         }
447
448                         him->name = malloc(strlen(argv[i]) + 1);
449                         if (!him->name) {
450                                 ret = ENOMEM;
451                                 goto out;
452                         }
453
454                         strcpy(him->name, argv[i]);
455                         him->state = STATE_NONE;
456                         udev_list_node_append(&him->node, &bunch);
457                 } else {
458                         if (debug)
459                                 fprintf(stderr, "ID %s: found in database\n", argv[i]);
460                         who->state = STATE_CONFIRMED;
461                 }
462         }
463
464         if (prune)
465                 reject(us);
466         else
467                 invite(us);
468
469         if (debug) {
470                 everybody();
471                 fprintf(stderr, "Prune lists\n");
472         }
473         kickout();
474
475         lseek(fd, 0, SEEK_SET);
476         ftruncate(fd, 0);
477         ret = missing(fd);
478
479         lockf(fd, F_ULOCK, 0);
480         close(fd);
481 out:
482         if (debug)
483                 everybody();
484         if (ret >= 0)
485                 printf("COLLECT_%s=%d\n", checkpoint, ret);
486 exit:
487         udev_unref(udev);
488         return ret;
489 }