chiark / gitweb /
collect: extra to synchronize actions across events
authorHannes Reinecke <hare@suse.de>
Tue, 24 Jul 2007 11:28:18 +0000 (13:28 +0200)
committerKay Sievers <kay.sievers@vrfy.org>
Tue, 24 Jul 2007 11:28:18 +0000 (13:28 +0200)
extras/collect/Makefile [new file with mode: 0644]
extras/collect/collect.c [new file with mode: 0644]
test/simple-build-check.sh

diff --git a/extras/collect/Makefile b/extras/collect/Makefile
new file mode 100644 (file)
index 0000000..6168a8b
--- /dev/null
@@ -0,0 +1,64 @@
+# Makefile for udev extra invoked by the udev main Makefile
+
+PROG = collect
+OBJ =
+HEADERS =
+GEN_HEADERS =
+MAN_PAGES =
+
+prefix =
+etcdir =       ${prefix}/etc
+sbindir =      ${prefix}/sbin
+usrbindir =    ${prefix}/usr/bin
+usrsbindir =   ${prefix}/usr/sbin
+libudevdir =   ${prefix}/lib/udev
+mandir =       ${prefix}/usr/share/man
+configdir =    ${etcdir}/udev/
+
+INSTALL = install -c
+INSTALL_PROGRAM = ${INSTALL}
+INSTALL_DATA  = ${INSTALL} -m 644
+INSTALL_SCRIPT = ${INSTALL}
+
+all: $(PROG) $(MAN_PAGES)
+.PHONY: all
+.DEFAULT: all
+
+%.o: %.c $(GEN_HEADERS)
+       $(E) "  CC      " $@
+       $(Q) $(CC) -c $(CFLAGS) $< -o $@
+
+$(PROG): %: $(HEADERS) %.o $(OBJS)
+       $(E) "  LD      " $@
+       $(Q) $(LD) $(LDFLAGS) $@.o $(OBJS) -o $@ $(LIBUDEV) $(LIB_OBJS)
+
+# man pages
+%.8: %.xml
+       $(E) "  XMLTO   " $@
+       $(Q) xmlto man $?
+.PRECIOUS: %.8
+
+clean:
+       $(E) "  CLEAN   "
+       $(Q) rm -f $(PROG) $(OBJS) $(GEN_HEADERS)
+.PHONY: clean
+
+install-bin: all
+       $(INSTALL_PROGRAM) -D $(PROG) $(DESTDIR)$(libudevdir)/$(PROG)
+.PHONY: install-bin
+
+uninstall-bin:
+       - rm $(DESTDIR)$(libudevdir)/$(PROG)
+.PHONY: uninstall-bin
+
+install-man:
+       @echo "Please create a man page for this tool."
+.PHONY: install-man
+
+uninstall-man:
+       @echo "Please create a man page for this tool."
+.PHONY: uninstall-man
+
+install-config:
+       @echo "no config file to install"
+.PHONY: install-config
diff --git a/extras/collect/collect.c b/extras/collect/collect.c
new file mode 100644 (file)
index 0000000..0a852f2
--- /dev/null
@@ -0,0 +1,429 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "../../list.h"
+
+#define TMPFILE                        "/dev/.udev/collect"
+#define BUFSIZE                        16
+#define UDEV_ALARM_TIMEOUT     180
+
+enum collect_state {
+       STATE_NONE,
+       STATE_OLD,
+       STATE_CONFIRMED,
+};
+
+struct _mate {
+       struct list_head node;
+       char *name;
+       enum collect_state state;
+};
+
+static LIST_HEAD(bunch);
+static int debug;
+
+/* This can increase dynamically */
+static int bufsize = BUFSIZE;
+
+static void sig_alrm(int signo)
+{
+       exit(4);
+}
+
+static void usage(void)
+{
+       printf("usage: collect [--add|--remove] [--debug] <checkpoint> <id> <idlist>\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");
+}
+
+/*
+ * prepare
+ *
+ * Prepares the database file
+ */
+static int prepare(char *dir, char *filename)
+{
+       struct stat statbuf;
+       char buf[512];
+       int fd;
+
+       if (stat(dir, &statbuf) < 0)
+               mkdir(dir, 0700);
+
+       sprintf(buf, "%s/%s", dir, filename);
+
+       fd = open(buf,O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
+       if (fd < 0)
+               fprintf(stderr, "Cannot open %s: %s\n", buf, strerror(errno));
+
+       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: %s\n", buf, strerror(errno));
+               }
+       }
+
+       return fd;
+}
+
+/*
+ * Read checkpoint file
+ *
+ * Tricky reading this. We allocate a buffer twice as large
+ * as we're goint 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 = calloc(1,bufsize + 1);
+       if (!buf) {
+               fprintf(stderr, "Out of memory\n");
+               return -1;
+       }
+       memset(buf, ' ', bufsize);
+       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 %d\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));
+                               him->name = malloc(strlen(word) + 1);
+                               strcpy(him->name, word);
+                               him->state = STATE_OLD;
+                               list_add_tail(&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 _mate *him, *who;
+
+       if (debug)
+               fprintf(stderr, "Adding ID '%s'\n", us);
+
+       who = NULL;
+       list_for_each_entry(him, &bunch, node) {
+               if (!strcmp(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 _mate *him, *who;
+
+       if (debug)
+               fprintf(stderr, "Removing ID '%s'\n", us);
+
+       who = NULL;
+       list_for_each_entry(him, &bunch, node) {
+               if (!strcmp(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 commandline.
+ */
+static void kickout(void)
+{
+       struct _mate *him, *them;
+
+       list_for_each_entry_safe(him, them, &bunch, node) {
+               if (him->state == STATE_OLD) {
+                       list_del(&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 _mate *him;
+
+       buf = malloc(bufsize);
+       if (!buf)
+               return -1;
+
+       list_for_each_entry(him, &bunch, node) {
+               if (him->state == STATE_NONE) {
+                       ret++;
+               } else {
+                       sprintf(buf, "%s ", him->name);
+                       write(fd, buf, strlen(buf));
+               }
+       }
+
+       free(buf);
+
+       return ret;
+}
+
+/*
+ * everybody
+ *
+ * Prints out the status of the internal list.
+ */
+static void everybody(void)
+{
+       struct _mate *him;
+       const char *state = "";
+
+       list_for_each_entry(him, &bunch, 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)
+{
+       static const struct option options[] = {
+               { "add", 0, NULL, 'a' },
+               { "remove", 0, NULL, 'r' },
+               { "debug", 0, NULL, 'd' },
+               { "help", 0, NULL, 'h' },
+               {}
+       };
+       int argi;
+       char *checkpoint, *us;
+       struct _mate *him, *who;
+       int fd;
+       int i;
+       int ret = 0;
+       int prune = 0;
+
+       while (1) {
+               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: %s\n", strerror(errno));
+               ret = 2;
+               goto exit;
+       }
+
+       INIT_LIST_HEAD(&bunch);
+
+       if (debug)
+               fprintf(stderr, "Using checkpoint '%s'\n", checkpoint);
+
+       fd = prepare(TMPFILE, checkpoint);
+       if (fd < 0) {
+               ret = 3;
+               goto out;
+       }
+
+       if (checkout(fd) < 0) {
+               ret = 2;
+               goto out;
+       }
+
+       for (i = argi; i < argc; i++) {
+               who = NULL;
+               list_for_each_entry(him, &bunch, node) {
+                       if (!strcmp(him->name, argv[i]))
+                               who = him;
+               }
+               if (!who) {
+                       if (debug)
+                               fprintf(stderr, "ID %s: not in database\n", argv[i]);
+                       him = malloc(sizeof (struct _mate));
+                       him->name = malloc(strlen(argv[i]) + 1);
+                       strcpy(him->name, argv[i]);
+                       him->state = STATE_NONE;
+                       list_add_tail(&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:
+       return ret;
+}
index a2cbbfd..9939b88 100755 (executable)
@@ -11,6 +11,7 @@ EXTRAS="\
        extras/floppy \
        extras/firmware \
        extras/path_id \
+       extras/collect \
        extras/rule_generator"
 
 # with debug