diff options
-rw-r--r-- | extras/collect/Makefile | 64 | ||||
-rw-r--r-- | extras/collect/collect.c | 429 | ||||
-rwxr-xr-x | test/simple-build-check.sh | 1 |
3 files changed, 494 insertions, 0 deletions
diff --git a/extras/collect/Makefile b/extras/collect/Makefile new file mode 100644 index 0000000000..6168a8b805 --- /dev/null +++ b/extras/collect/Makefile @@ -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 index 0000000000..0a852f2db2 --- /dev/null +++ b/extras/collect/collect.c @@ -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; +} diff --git a/test/simple-build-check.sh b/test/simple-build-check.sh index a2cbbfd631..9939b88fb3 100755 --- a/test/simple-build-check.sh +++ b/test/simple-build-check.sh @@ -11,6 +11,7 @@ EXTRAS="\ extras/floppy \ extras/firmware \ extras/path_id \ + extras/collect \ extras/rule_generator" # with debug |