1 /* SPDX-License-Identifier: GPL-2.0+ */
3 * Collect variables across events.
5 * usage: collect [--add|--remove] <checkpoint> <id> <idlist>
7 * Adds ID <id> to the list governed by <checkpoint>.
8 * <id> must be part of the ID list <idlist>.
9 * If all IDs given by <idlist> are listed (ie collect has been
10 * invoked for each ID in <idlist>) collect returns 0, the
11 * number of missing IDs otherwise.
12 * A negative number is returned on error.
14 * Copyright © 2007, Hannes Reinecke <hare@suse.de>
16 * This program is free software: you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation, either version 2 of the License, or
19 * (at your option) any later version.
27 #include "alloc-util.h"
28 #include "libudev-private.h"
30 #include "stdio-util.h"
31 #include "string-util.h"
32 #include "udev-util.h"
35 #define UDEV_ALARM_TIMEOUT 180
44 struct udev_list_node node
;
46 enum collect_state state
;
49 static struct udev_list_node bunch
;
52 /* This can increase dynamically */
53 static size_t bufsize
= BUFSIZE
;
55 static inline struct _mate
*node_to_mate(struct udev_list_node
*node
)
57 return container_of(node
, struct _mate
, node
);
60 _noreturn_
static void sig_alrm(int signo
)
65 static void usage(void)
67 printf("%s [options] <checkpoint> <id> <idlist>\n\n"
68 "Collect variables across events.\n\n"
69 " -h --help Print this message\n"
70 " -a --add Add ID <id> to the list <idlist>\n"
71 " -r --remove Remove ID <id> from the list <idlist>\n"
72 " -d --debug Debug to stderr\n\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\n"
79 , program_invocation_short_name
);
85 * Prepares the database file
87 static int prepare(char *dir
, char *filename
)
93 if (r
< 0 && errno
!= EEXIST
)
96 snprintf(buf
, sizeof buf
, "%s/%s", dir
, filename
);
98 fd
= open(buf
, O_RDWR
|O_CREAT
|O_CLOEXEC
, S_IRUSR
|S_IWUSR
);
100 fprintf(stderr
, "Cannot open %s: %m\n", buf
);
102 if (lockf(fd
,F_TLOCK
,0) < 0) {
104 fprintf(stderr
, "Lock taken, wait for %d seconds\n", UDEV_ALARM_TIMEOUT
);
105 if (IN_SET(errno
, EAGAIN
, EACCES
)) {
106 alarm(UDEV_ALARM_TIMEOUT
);
107 lockf(fd
, F_LOCK
, 0);
109 fprintf(stderr
, "Acquired lock on %s\n", buf
);
112 fprintf(stderr
, "Could not get lock on %s: %m\n", buf
);
120 * Read checkpoint file
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 ...
131 * Yes, one could have used fgets() for this. But then we'd
132 * have to use freopen etc which I found quite tedious.
134 static int checkout(int fd
)
137 _cleanup_free_
char *buf
= NULL
;
138 char *ptr
, *word
= NULL
;
143 buf
= malloc(bufsize
+ 1);
146 memset(buf
, ' ', bufsize
);
150 while ((read(fd
, buf
+ len
, len
)) > 0) {
151 while (ptr
&& *ptr
) {
153 ptr
= strpbrk(word
," \n\t\r");
154 if (!ptr
&& word
< (buf
+ len
)) {
155 bufsize
= bufsize
<< 1;
157 fprintf(stderr
, "ID overflow, restarting with size %zu\n", bufsize
);
158 lseek(fd
, 0, SEEK_SET
);
168 fprintf(stderr
, "Found word %s\n", word
);
169 him
= malloc(sizeof (struct _mate
));
172 him
->name
= strdup(word
);
177 him
->state
= STATE_OLD
;
178 udev_list_node_append(&him
->node
, &bunch
);
182 memcpy(buf
, buf
+ len
, len
);
183 memset(buf
+ len
, ' ', len
);
196 * Adds a new ID 'us' to the internal list,
197 * marks it as confirmed.
199 static void invite(char *us
)
201 struct udev_list_node
*him_node
;
202 struct _mate
*who
= NULL
;
205 fprintf(stderr
, "Adding ID '%s'\n", us
);
207 udev_list_node_foreach(him_node
, &bunch
) {
208 struct _mate
*him
= node_to_mate(him_node
);
210 if (streq(him
->name
, us
)) {
211 him
->state
= STATE_CONFIRMED
;
216 fprintf(stderr
, "ID '%s' not in database\n", us
);
223 * Marks the ID 'us' as invalid,
224 * causing it to be removed when the
225 * list is written out.
227 static void reject(char *us
)
229 struct udev_list_node
*him_node
;
230 struct _mate
*who
= NULL
;
233 fprintf(stderr
, "Removing ID '%s'\n", us
);
235 udev_list_node_foreach(him_node
, &bunch
) {
236 struct _mate
*him
= node_to_mate(him_node
);
238 if (streq(him
->name
, us
)) {
239 him
->state
= STATE_NONE
;
244 fprintf(stderr
, "ID '%s' not in database\n", us
);
250 * Remove all IDs in the internal list which are not part
251 * of the list passed via the command line.
253 static void kickout(void)
255 struct udev_list_node
*him_node
;
256 struct udev_list_node
*tmp
;
258 udev_list_node_foreach_safe(him_node
, tmp
, &bunch
) {
259 struct _mate
*him
= node_to_mate(him_node
);
261 if (him
->state
== STATE_OLD
) {
262 udev_list_node_remove(&him
->node
);
272 * Counts all missing IDs in the internal list.
274 static int missing(int fd
)
278 struct udev_list_node
*him_node
;
280 buf
= malloc(bufsize
);
284 udev_list_node_foreach(him_node
, &bunch
) {
285 struct _mate
*him
= node_to_mate(him_node
);
287 if (him
->state
== STATE_NONE
) {
290 while (strlen(him
->name
)+1 >= bufsize
) {
293 bufsize
= bufsize
<< 1;
294 tmpbuf
= realloc(buf
, bufsize
);
301 snprintf(buf
, strlen(him
->name
)+2, "%s ", him
->name
);
302 if (write(fd
, buf
, strlen(buf
)) < 0) {
316 * Prints out the status of the internal list.
318 static void everybody(void)
320 struct udev_list_node
*him_node
;
321 const char *state
= "";
323 udev_list_node_foreach(him_node
, &bunch
) {
324 struct _mate
*him
= node_to_mate(him_node
);
326 switch (him
->state
) {
333 case STATE_CONFIRMED
:
337 fprintf(stderr
, "ID: %s=%s\n", him
->name
, state
);
341 int main(int argc
, char **argv
) {
342 static const struct option options
[] = {
343 { "add", no_argument
, NULL
, 'a' },
344 { "remove", no_argument
, NULL
, 'r' },
345 { "debug", no_argument
, NULL
, 'd' },
346 { "help", no_argument
, NULL
, 'h' },
350 char *checkpoint
, *us
;
353 int ret
= EXIT_SUCCESS
;
355 char tmpdir
[UTIL_PATH_SIZE
];
357 log_set_target(LOG_TARGET_AUTO
);
359 log_parse_environment();
365 option
= getopt_long(argc
, argv
, "ardh", options
, NULL
);
388 if (argi
+ 2 > argc
) {
389 printf("Missing parameter(s)\n");
392 checkpoint
= argv
[argi
++];
395 if (signal(SIGALRM
, sig_alrm
) == SIG_ERR
) {
396 fprintf(stderr
, "Cannot set SIGALRM: %m\n");
400 udev_list_node_init(&bunch
);
403 fprintf(stderr
, "Using checkpoint '%s'\n", checkpoint
);
405 strscpyl(tmpdir
, sizeof(tmpdir
), "/run/udev/collect", NULL
);
406 fd
= prepare(tmpdir
, checkpoint
);
412 if (checkout(fd
) < 0) {
417 for (i
= argi
; i
< argc
; i
++) {
418 struct udev_list_node
*him_node
;
422 udev_list_node_foreach(him_node
, &bunch
) {
423 struct _mate
*him
= node_to_mate(him_node
);
425 if (streq(him
->name
, argv
[i
]))
432 fprintf(stderr
, "ID %s: not in database\n", argv
[i
]);
433 him
= new(struct _mate
, 1);
439 him
->name
= strdup(argv
[i
]);
446 him
->state
= STATE_NONE
;
447 udev_list_node_append(&him
->node
, &bunch
);
450 fprintf(stderr
, "ID %s: found in database\n", argv
[i
]);
451 who
->state
= STATE_CONFIRMED
;
462 fprintf(stderr
, "Prune lists\n");
466 lseek(fd
, 0, SEEK_SET
);
470 lockf(fd
, F_ULOCK
, 0);
476 printf("COLLECT_%s=%d\n", checkpoint
, ret
);