summaryrefslogtreecommitdiff
path: root/src/grp-udev/collect/collect.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/grp-udev/collect/collect.c')
-rw-r--r--src/grp-udev/collect/collect.c491
1 files changed, 491 insertions, 0 deletions
diff --git a/src/grp-udev/collect/collect.c b/src/grp-udev/collect/collect.c
new file mode 100644
index 0000000000..f1e326820f
--- /dev/null
+++ b/src/grp-udev/collect/collect.c
@@ -0,0 +1,491 @@
+/*
+ * Collect variables across events.
+ *
+ * usage: collect [--add|--remove] <checkpoint> <id> <idlist>
+ *
+ * Adds ID <id> to the list governed by <checkpoint>.
+ * <id> must be part of the ID list <idlist>.
+ * If all IDs given by <idlist> are listed (ie collect has been
+ * invoked for each ID in <idlist>) collect returns 0, the
+ * number of missing IDs otherwise.
+ * A negative number is returned on error.
+ *
+ * Copyright(C) 2007, Hannes Reinecke <hare@suse.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#include "libudev-private.h"
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/macro.h"
+#include "systemd-basic/stdio-util.h"
+#include "systemd-basic/string-util.h"
+
+#define BUFSIZE 16
+#define UDEV_ALARM_TIMEOUT 180
+
+enum collect_state {
+ STATE_NONE,
+ STATE_OLD,
+ STATE_CONFIRMED,
+};
+
+struct _mate {
+ struct udev_list_node node;
+ char *name;
+ enum collect_state state;
+};
+
+static struct udev_list_node bunch;
+static int debug;
+
+/* This can increase dynamically */
+static size_t bufsize = BUFSIZE;
+
+static inline struct _mate *node_to_mate(struct udev_list_node *node)
+{
+ return container_of(node, struct _mate, node);
+}
+
+noreturn static void sig_alrm(int signo)
+{
+ exit(4);
+}
+
+static void usage(void)
+{
+ printf("%s [options] <checkpoint> <id> <idlist>\n\n"
+ "Collect variables across events.\n\n"
+ " -h --help Print this message\n"
+ " -a --add Add ID <id> to the list <idlist>\n"
+ " -r --remove Remove ID <id> from the list <idlist>\n"
+ " -d --debug Debug to stderr\n\n"
+ " Adds ID <id> to the list governed by <checkpoint>.\n"
+ " <id> must be part of the list <idlist>.\n"
+ " If all IDs given by <idlist> are listed (ie collect has been\n"
+ " invoked for each ID in <idlist>) collect returns 0, the\n"
+ " number of missing IDs otherwise.\n"
+ " On error a negative number is returned.\n\n"
+ , program_invocation_short_name);
+}
+
+/*
+ * prepare
+ *
+ * Prepares the database file
+ */
+static int prepare(char *dir, char *filename)
+{
+ char buf[PATH_MAX];
+ int r, fd;
+
+ r = mkdir(dir, 0700);
+ if (r < 0 && errno != EEXIST)
+ return -errno;
+
+ snprintf(buf, sizeof buf, "%s/%s", dir, filename);
+
+ fd = open(buf, O_RDWR|O_CREAT|O_CLOEXEC, S_IRUSR|S_IWUSR);
+ if (fd < 0)
+ fprintf(stderr, "Cannot open %s: %m\n", buf);
+
+ if (lockf(fd,F_TLOCK,0) < 0) {
+ if (debug)
+ fprintf(stderr, "Lock taken, wait for %d seconds\n", UDEV_ALARM_TIMEOUT);
+ if (errno == EAGAIN || errno == EACCES) {
+ alarm(UDEV_ALARM_TIMEOUT);
+ lockf(fd, F_LOCK, 0);
+ if (debug)
+ fprintf(stderr, "Acquired lock on %s\n", buf);
+ } else {
+ if (debug)
+ fprintf(stderr, "Could not get lock on %s: %m\n", buf);
+ }
+ }
+
+ return fd;
+}
+
+/*
+ * Read checkpoint file
+ *
+ * Tricky reading this. We allocate a buffer twice as large
+ * as we're going to read. Then we read into the upper half
+ * of that buffer and start parsing.
+ * Once we do _not_ find end-of-work terminator (whitespace
+ * character) we move the upper half to the lower half,
+ * adjust the read pointer and read the next bit.
+ * Quite clever methinks :-)
+ * I should become a programmer ...
+ *
+ * Yes, one could have used fgets() for this. But then we'd
+ * have to use freopen etc which I found quite tedious.
+ */
+static int checkout(int fd)
+{
+ int len;
+ char *buf, *ptr, *word = NULL;
+ struct _mate *him;
+
+ restart:
+ len = bufsize >> 1;
+ buf = malloc(bufsize + 1);
+ if (!buf)
+ return log_oom();
+ memset(buf, ' ', bufsize);
+ buf[bufsize] = '\0';
+
+ ptr = buf + len;
+ while ((read(fd, buf + len, len)) > 0) {
+ while (ptr && *ptr) {
+ word = ptr;
+ ptr = strpbrk(word," \n\t\r");
+ if (!ptr && word < (buf + len)) {
+ bufsize = bufsize << 1;
+ if (debug)
+ fprintf(stderr, "ID overflow, restarting with size %zu\n", bufsize);
+ free(buf);
+ lseek(fd, 0, SEEK_SET);
+ goto restart;
+ }
+ if (ptr) {
+ *ptr = '\0';
+ ptr++;
+ if (!strlen(word))
+ continue;
+
+ if (debug)
+ fprintf(stderr, "Found word %s\n", word);
+ him = malloc(sizeof (struct _mate));
+ if (!him) {
+ free(buf);
+ return log_oom();
+ }
+ him->name = strdup(word);
+ if (!him->name) {
+ free(buf);
+ free(him);
+ return log_oom();
+ }
+ him->state = STATE_OLD;
+ udev_list_node_append(&him->node, &bunch);
+ word = NULL;
+ }
+ }
+ memcpy(buf, buf + len, len);
+ memset(buf + len, ' ', len);
+
+ if (!ptr)
+ ptr = word;
+ if (!ptr)
+ break;
+ ptr -= len;
+ }
+
+ free(buf);
+ return 0;
+}
+
+/*
+ * invite
+ *
+ * Adds a new ID 'us' to the internal list,
+ * marks it as confirmed.
+ */
+static void invite(char *us)
+{
+ struct udev_list_node *him_node;
+ struct _mate *who = NULL;
+
+ if (debug)
+ fprintf(stderr, "Adding ID '%s'\n", us);
+
+ udev_list_node_foreach(him_node, &bunch) {
+ struct _mate *him = node_to_mate(him_node);
+
+ if (streq(him->name, us)) {
+ him->state = STATE_CONFIRMED;
+ who = him;
+ }
+ }
+ if (debug && !who)
+ fprintf(stderr, "ID '%s' not in database\n", us);
+
+}
+
+/*
+ * reject
+ *
+ * Marks the ID 'us' as invalid,
+ * causing it to be removed when the
+ * list is written out.
+ */
+static void reject(char *us)
+{
+ struct udev_list_node *him_node;
+ struct _mate *who = NULL;
+
+ if (debug)
+ fprintf(stderr, "Removing ID '%s'\n", us);
+
+ udev_list_node_foreach(him_node, &bunch) {
+ struct _mate *him = node_to_mate(him_node);
+
+ if (streq(him->name, us)) {
+ him->state = STATE_NONE;
+ who = him;
+ }
+ }
+ if (debug && !who)
+ fprintf(stderr, "ID '%s' not in database\n", us);
+}
+
+/*
+ * kickout
+ *
+ * Remove all IDs in the internal list which are not part
+ * of the list passed via the command line.
+ */
+static void kickout(void)
+{
+ struct udev_list_node *him_node;
+ struct udev_list_node *tmp;
+
+ udev_list_node_foreach_safe(him_node, tmp, &bunch) {
+ struct _mate *him = node_to_mate(him_node);
+
+ if (him->state == STATE_OLD) {
+ udev_list_node_remove(&him->node);
+ free(him->name);
+ free(him);
+ }
+ }
+}
+
+/*
+ * missing
+ *
+ * Counts all missing IDs in the internal list.
+ */
+static int missing(int fd)
+{
+ char *buf;
+ int ret = 0;
+ struct udev_list_node *him_node;
+
+ buf = malloc(bufsize);
+ if (!buf)
+ return log_oom();
+
+ udev_list_node_foreach(him_node, &bunch) {
+ struct _mate *him = node_to_mate(him_node);
+
+ if (him->state == STATE_NONE) {
+ ret++;
+ } else {
+ while (strlen(him->name)+1 >= bufsize) {
+ char *tmpbuf;
+
+ bufsize = bufsize << 1;
+ tmpbuf = realloc(buf, bufsize);
+ if (!tmpbuf) {
+ free(buf);
+ return log_oom();
+ }
+ buf = tmpbuf;
+ }
+ snprintf(buf, strlen(him->name)+2, "%s ", him->name);
+ if (write(fd, buf, strlen(buf)) < 0) {
+ free(buf);
+ return -1;
+ }
+ }
+ }
+
+ free(buf);
+ return ret;
+}
+
+/*
+ * everybody
+ *
+ * Prints out the status of the internal list.
+ */
+static void everybody(void)
+{
+ struct udev_list_node *him_node;
+ const char *state = "";
+
+ udev_list_node_foreach(him_node, &bunch) {
+ struct _mate *him = node_to_mate(him_node);
+
+ switch (him->state) {
+ case STATE_NONE:
+ state = "none";
+ break;
+ case STATE_OLD:
+ state = "old";
+ break;
+ case STATE_CONFIRMED:
+ state = "confirmed";
+ break;
+ }
+ fprintf(stderr, "ID: %s=%s\n", him->name, state);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ struct udev *udev;
+ static const struct option options[] = {
+ { "add", no_argument, NULL, 'a' },
+ { "remove", no_argument, NULL, 'r' },
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ {}
+ };
+ int argi;
+ char *checkpoint, *us;
+ int fd;
+ int i;
+ int ret = EXIT_SUCCESS;
+ int prune = 0;
+ char tmpdir[UTIL_PATH_SIZE];
+
+ udev = udev_new();
+ if (udev == NULL) {
+ ret = EXIT_FAILURE;
+ goto exit;
+ }
+
+ for (;;) {
+ int option;
+
+ option = getopt_long(argc, argv, "ardh", options, NULL);
+ if (option == -1)
+ break;
+
+ switch (option) {
+ case 'a':
+ prune = 0;
+ break;
+ case 'r':
+ prune = 1;
+ break;
+ case 'd':
+ debug = 1;
+ break;
+ case 'h':
+ usage();
+ goto exit;
+ default:
+ ret = 1;
+ goto exit;
+ }
+ }
+
+ argi = optind;
+ if (argi + 2 > argc) {
+ printf("Missing parameter(s)\n");
+ ret = 1;
+ goto exit;
+ }
+ checkpoint = argv[argi++];
+ us = argv[argi++];
+
+ if (signal(SIGALRM, sig_alrm) == SIG_ERR) {
+ fprintf(stderr, "Cannot set SIGALRM: %m\n");
+ ret = 2;
+ goto exit;
+ }
+
+ udev_list_node_init(&bunch);
+
+ if (debug)
+ fprintf(stderr, "Using checkpoint '%s'\n", checkpoint);
+
+ strscpyl(tmpdir, sizeof(tmpdir), "/run/udev/collect", NULL);
+ fd = prepare(tmpdir, checkpoint);
+ if (fd < 0) {
+ ret = 3;
+ goto out;
+ }
+
+ if (checkout(fd) < 0) {
+ ret = 2;
+ goto out;
+ }
+
+ for (i = argi; i < argc; i++) {
+ struct udev_list_node *him_node;
+ struct _mate *who;
+
+ who = NULL;
+ udev_list_node_foreach(him_node, &bunch) {
+ struct _mate *him = node_to_mate(him_node);
+
+ if (streq(him->name, argv[i]))
+ who = him;
+ }
+ if (!who) {
+ struct _mate *him;
+
+ if (debug)
+ fprintf(stderr, "ID %s: not in database\n", argv[i]);
+ him = new(struct _mate, 1);
+ if (!him) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ him->name = strdup(argv[i]);
+ if (!him->name) {
+ free(him);
+ ret = ENOMEM;
+ goto out;
+ }
+
+ him->state = STATE_NONE;
+ udev_list_node_append(&him->node, &bunch);
+ } else {
+ if (debug)
+ fprintf(stderr, "ID %s: found in database\n", argv[i]);
+ who->state = STATE_CONFIRMED;
+ }
+ }
+
+ if (prune)
+ reject(us);
+ else
+ invite(us);
+
+ if (debug) {
+ everybody();
+ fprintf(stderr, "Prune lists\n");
+ }
+ kickout();
+
+ lseek(fd, 0, SEEK_SET);
+ ftruncate(fd, 0);
+ ret = missing(fd);
+
+ lockf(fd, F_ULOCK, 0);
+ close(fd);
+out:
+ if (debug)
+ everybody();
+ if (ret >= 0)
+ printf("COLLECT_%s=%d\n", checkpoint, ret);
+exit:
+ udev_unref(udev);
+ return ret;
+}