From 43b9dc1972fa75fe336db23ee53a470ffbbb91de Mon Sep 17 00:00:00 2001 From: Werner Almesberger Date: Fri, 19 Nov 2010 14:00:15 -0300 Subject: [PATCH] qpkg/: initial commit (sneak preview only, doesn't work properly yet) --- qpkg/Makefile | 11 ++ qpkg/gobble.c | 398 ++++++++++++++++++++++++++++++++++++++++++++++++++ qpkg/gobble.h | 6 + qpkg/id.c | 129 ++++++++++++++++ qpkg/id.h | 39 +++++ qpkg/prereq.c | 221 ++++++++++++++++++++++++++++ qpkg/prereq.h | 6 + qpkg/qpkg.c | 144 ++++++++++++++++++ qpkg/qpkg.h | 34 +++++ qpkg/util.h | 27 ++++ 10 files changed, 1015 insertions(+) create mode 100644 qpkg/Makefile create mode 100644 qpkg/gobble.c create mode 100644 qpkg/gobble.h create mode 100644 qpkg/id.c create mode 100644 qpkg/id.h create mode 100644 qpkg/prereq.c create mode 100644 qpkg/prereq.h create mode 100644 qpkg/qpkg.c create mode 100644 qpkg/qpkg.h create mode 100644 qpkg/util.h diff --git a/qpkg/Makefile b/qpkg/Makefile new file mode 100644 index 0000000..792091f --- /dev/null +++ b/qpkg/Makefile @@ -0,0 +1,11 @@ +CFLAGS = -Wall -Wshadow -g +# -O, so that we get data flow analysis, which helps to find more bugs + +OBJS = gobble.o id.o prereq.o qpkg.o + +all: qpkg + +qpkg: $(OBJS) + +clean: + rm -f $(OBJS) diff --git a/qpkg/gobble.c b/qpkg/gobble.c new file mode 100644 index 0000000..7f75d88 --- /dev/null +++ b/qpkg/gobble.c @@ -0,0 +1,398 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "id.h" +#include "qpkg.h" +#include "gobble.h" + + +#define CHARS_AFTER_ERROR 20 + + +#define EXPECT(s) \ + do { \ + if (end-buf < sizeof(s)-1) \ + FAIL; \ + if (memcmp(buf, s, sizeof(s)-1)) \ + FAIL; \ + buf += sizeof(s)-1; \ + } \ + while (0) + + +#define NEXT (buf == end ? '?' : *buf++) + + +#define WHITESPACE \ + do { \ + if (buf == end) \ + FAIL; \ + if (*buf == '\n') \ + break; \ + if (!isspace(*buf)) \ + break; \ + buf++; \ + } \ + while (0) + + +#define ISTERM(c) \ + ((c) == ' ' || (c) == '\t' || (c) == '\n' || \ + (c) == ',' || (c) == ')') + + +#define ID(tree) \ + ({ \ + const char *start; \ + \ + if (buf == end) \ + FAIL; \ + start = buf; \ + while (buf != end && !ISTERM(*buf)) \ + buf++; \ + make_id(tree, start, buf-start); \ + }) + + +#define FAIL \ + do { \ + failed_at = __LINE__; \ + goto fail; \ + } \ + while (0) + + +static void gobble_buf(const char *name, const char *buf, size_t len) +{ + const char *end = buf+len; + int lineno = 1; + struct pkg *pkg = NULL; /* current package */ + struct ref **anchor = NULL; + int i, failed_at = 0; + +initial: + if (buf == end) + return; + if (*buf == '\n') { + lineno++; + buf++; + goto initial; + } + + /* decode the tag */ + + switch (*buf++) { + case 'A': /* Architecture // Auto-Installed */ + switch (NEXT) { + case 'r': + EXPECT("chitecture:"); + /* @@@ use later */ + goto skip_data; + case 'u': + EXPECT("to-Installed:"); + goto skip_data; + default: + FAIL; + } + + case 'C': /* Conflicts // Conffiles */ + EXPECT("onf"); + switch (NEXT) { + case 'l': + EXPECT("icts:"); + goto conflicts; + case 'f': + EXPECT("iles:"); + goto skip_data; + default: + FAIL; + } + + case 'D': /* Depends, Description */ + EXPECT("e"); + switch (NEXT) { + case 'p': + EXPECT("ends:"); + goto depends; + case 's': + EXPECT("cription:"); + goto skip_data; + default: + FAIL; + } + + case 'F': /* Filename */ + EXPECT("ilename:"); + goto filename; + + case 'H': /* HomePage, Homepage */ + EXPECT("ome"); + switch (NEXT) { + case 'P': + case 'p': + EXPECT("age:"); + goto skip_data; + default: + FAIL; + } + + case 'I': /* Installed-Time */ + EXPECT("nstalled-Time:"); + goto skip_data; + + case 'L': /* License */ + EXPECT("icense:"); + goto skip_data; + + case 'M': /* Maintainer, MD5Sum */ + switch (NEXT) { + case 'a': + EXPECT("intainer:"); + goto skip_data; + case 'D': + EXPECT("5Sum:"); + goto skip_data; + default: + FAIL; + } + + case 'O': /* OE */ + EXPECT("E:"); + goto skip_data; + + case 'P': /* Package, Priority, Provides */ + switch (NEXT) { + case 'a': + EXPECT("ckage:"); + goto package; + case 'r': + break; + default: + FAIL; + } + switch (NEXT) { + case 'i': + EXPECT("ority:"); + goto skip_data; + case 'o': + EXPECT("vides:"); + goto provides; + default: + FAIL; + } + + case 'R': /* Recommends, Replaces */ + EXPECT("e"); + switch (NEXT) { + case 'c': + EXPECT("ommends:"); + goto skip_data; + case 'p': + EXPECT("laces:"); + goto skip_data; + default: + FAIL; + } + + case 'S': /* Section, Size, Source, Suggests // Status */ + switch (NEXT) { + case 'e': + EXPECT("ction:"); + goto skip_data; + case 'i': + EXPECT("ze:"); + goto skip_data; + case 'o': + EXPECT("urce:"); + goto skip_data; + case 'u': + EXPECT("ggests:"); + goto skip_data; + case 't': + EXPECT("atus:"); + goto status; + default: + FAIL; + } + + case 'V': /* Version */ + EXPECT("ersion:"); + goto version; + + default: + FAIL; + } + +conflicts: + anchor = &pkg->conflicts; + goto list_with_version; + +depends: + anchor = &pkg->depends; + goto list_with_version; + +package: + WHITESPACE; + pkg = alloc_type(struct pkg); + pkg->id = ID(packages); + pkg->more = pkg->id->value ? pkg->id->value : NULL; + pkg->id->value = pkg; + pkg->version = NULL; + pkg->filename = NULL; + pkg->installed = 0; + pkg->mark = 0; + goto eol; + +version: + WHITESPACE; + if (pkg->version) + FAIL; + pkg->version = ID(versions); + goto eol; + +provides: + /* @@@ later */ + goto skip_data; + +status: + pkg->installed = 1; + /* @@@ later */ + goto skip_data; + +filename: + WHITESPACE; + pkg->filename = buf; + goto skip_data; + +eol: + while (buf != end) { + if (*buf == ' ' || *buf == '\t') { + buf++; + continue; + } + if (*buf++ != '\n') + FAIL; + lineno++; + if (buf == end) + return; + if (*buf == ' ' || *buf == '\t') + FAIL; + goto initial; + } + return; + +skip_data: + while (buf != end) { + if (*buf++ != '\n') + continue; + lineno++; + if (buf == end) + return; + if (*buf != ' ' && *buf != '\t') + goto initial; + } + return; + +list_with_version: + while (1) { + struct ref *ref; + + WHITESPACE; + ref = alloc_type(struct ref); + ref->pkg = ID(packages); + + /* + * Work around the Wireshark Anomaly + */ + if (buf != end && *buf == ')') + buf++; + + WHITESPACE; + if (buf == end || *buf != '(') + ref->version = NULL; + else { + buf++; + if (buf == end) + FAIL; + switch (*buf++) { + case '=': + ref->relop = rel_eq; + break; + case '<': + ref->relop = rel_lt; + break; + case '>': + EXPECT("="); + ref->relop = rel_ge; + break; + default: + FAIL; + } + WHITESPACE; + ref->version = ID(versions); + EXPECT(")"); + } + ref->next = *anchor; + *anchor = ref; + if (buf == end) + return; + if (*buf != ',') + break; + buf++; + } + anchor = NULL; + goto eol; + +fail: + fprintf(stderr, "syntax derailment #%d at %s line %d: ", + failed_at, name, lineno); + for (i = 0; i != CHARS_AFTER_ERROR && buf != end; i++) { + if (*buf == '\n') + fprintf(stderr, "\\n"); + else if (isspace(*buf)) + fputc(' ', stderr); + else if (isprint(*buf)) + fputc(*buf, stderr); + buf++; + } + fprintf(stderr, "%s\n", buf == end ? "": "..."); + exit(1); +} + + +void gobble(const char *name) +{ + int fd; + struct stat st; + void *map; + + fd = open(name, O_RDONLY); + if (fd < 0) { + perror(name); + exit(1); + } + if (fstat(fd, &st) < 0) { + perror("fstat"); + exit(1); + } + map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (map == MAP_FAILED) { + perror("mmap"); + exit(1); + } + if (posix_madvise(map, st.st_size, POSIX_MADV_WILLNEED) < 0) { + perror("posix_madvise(POSIX_MADV_WILLNEED)"); + exit(1); + } + gobble_buf(name, map, st.st_size); + if (posix_madvise(map, st.st_size, POSIX_MADV_RANDOM) < 0) { + perror("posix_madvise(POSIX_MADV_RANDOM)"); + exit(1); + } +} diff --git a/qpkg/gobble.h b/qpkg/gobble.h new file mode 100644 index 0000000..5f6c693 --- /dev/null +++ b/qpkg/gobble.h @@ -0,0 +1,6 @@ +#ifndef GOBBLE_H +#define GOBBLE_H + +void gobble(const char *name); + +#endif /* !GOBBLE_H */ diff --git a/qpkg/id.c b/qpkg/id.c new file mode 100644 index 0000000..54a6cc3 --- /dev/null +++ b/qpkg/id.c @@ -0,0 +1,129 @@ +#include + +#include "util.h" +#include "id.h" + + +#define NODE2ID(n) ((struct id *) n) + + +static struct id *free_id = NULL; + + +int comp_id(const struct id *a, const struct id *b) +{ + int len = a->len < b->len ? a->len : b->len; + int cmp; + + cmp = memcmp(a->s, b->s, len); + if (cmp) + return cmp; + return a->len < b->len ? -1 : a->len > b->len; +} + + +struct tree *make_tree(int (*comp)(const struct id *a, const struct id *b)) +{ + struct tree *tree; + + tree = alloc_type(struct tree); + tree->comp = comp; + tree->root = NULL; + return tree; +} + + +static struct node *add_node(struct tree *tree, struct node *node) +{ + struct node **p, *last; + int cmp; + + p = &tree->root; + last = NULL; + while (*p) { + last = *p; + cmp = tree->comp(NODE2ID(node), NODE2ID(*p)); + if (cmp < 0) + p = &(*p)->left; + else if (cmp > 0) + p = &(*p)->right; + else + return *p; + } + *p = node; + node->up = last; + node->left = node->right = NULL; + return NULL; +} + + +struct id *make_id(struct tree *tree, const char *s, size_t len) +{ + struct id *id; + struct node *node; + + if (!free_id) + free_id = alloc_type(struct id); + id = free_id; + id->s = s; + id->len = len; + id->value = NULL; + node = add_node(tree, &id->node); + if (node) + return NODE2ID(node); + free_id = NULL; + return id; +} + + +const struct id *find_id(const struct tree *tree, const char *s, size_t len) +{ + struct id id = { + .s = s, + .len = len + }; + const struct node *n = tree->root; + int cmp; + + while (n) { + cmp = tree->comp(&id, NODE2ID(n)); + if (cmp < 0) + n = n->left; + else if (cmp > 0) + n = n->right; + else + return NODE2ID(n); + } + return NULL; +} + + +const struct id *first_id(const struct tree *tree) +{ + const struct node *n = tree->root; + + if (!n) + return NULL; + while (n->left) + n = n->left; + return NODE2ID(n); +} + + +const struct id *next_id(const struct id *id) +{ + const struct node *n = &id->node; + + if (n->right) { + n = n->right; + while (n->left) + n = n->left; + return NODE2ID(n); + } + while (n->up) { + if (n == n->up->left) + return NODE2ID(n->up); + n = n->up; + } + return NULL; +} diff --git a/qpkg/id.h b/qpkg/id.h new file mode 100644 index 0000000..b13738f --- /dev/null +++ b/qpkg/id.h @@ -0,0 +1,39 @@ +#ifndef ID_H +#define ID_H + +#include + + +struct id; + +/* + * @@@ basic binary trees are not a good choice. should use rb. + * To ease future migration, we separate the node structure from the rest. + */ + +struct node { + struct node *up, *left, *right; +}; + +struct tree { + int (*comp)(const struct id *a, const struct id *b); + struct node *root; +}; + +struct id { + struct node node; + const char *s; + size_t len; + void *value; +}; + + +int comp_id(const struct id *a, const struct id *b); + +struct tree *make_tree(int (*comp)(const struct id *a, const struct id *b)); +struct id *make_id(struct tree *tree, const char *s, size_t len); +const struct id *find_id(const struct tree *tree, const char *s, size_t len); +const struct id *first_id(const struct tree *tree); +const struct id *next_id(const struct id *id); + +#endif /* !ID_H */ diff --git a/qpkg/prereq.c b/qpkg/prereq.c new file mode 100644 index 0000000..841aedf --- /dev/null +++ b/qpkg/prereq.c @@ -0,0 +1,221 @@ +#include +#include +#include +#include +#include + +#include "id.h" +#include "qpkg.h" +#include "util.h" + + +struct list { + const struct ref *refs; + struct list *next; +}; + + +static struct pkg **best = NULL; +static struct pkg **stack = NULL; +static int n_best; /* undefined if best == NULL */ +static int n_stack = 0; +static int stack_max = 0; + + +#define ID2S(id) (int) (id)->len, (id)->s + + +static int epoch(const char **s, const struct id *id) +{ + const char *end = id->s+id->len; + int n = 0; + + while (*s != end && isdigit(**s)) { + n = 10*n+**s-'0'; + (*s)++; + } + if (*s == id->s || (*s != end && **s == ':')) + return n; + *s = id->s; + return 0; +} + + +static int comp_versions(const struct id *va, const struct id *vb) +{ + int epoch_a = 0, epoch_b = 0; + const char *a = va->s; + const char *b = vb->s; + const char *ea = a+va->len; + const char *eb = b+vb->len; + + epoch_a = epoch(&a, va); + epoch_b = epoch(&b, vb); + if (epoch_a != epoch_b) + return epoch_a-epoch_b; + + while (1) { + if (a == ea || b == eb) + return (a != ea)-(b != eb); + if (isdigit(*a) && isdigit(*b)) { + int na = 0, nb = 0; + + while (a != ea && isdigit(*a)) { + na = na*10+*a-'0'; + a++; + } + while (b != eb && isdigit(*b)) { + nb = nb*10+*b-'0'; + b++; + } + if (na == nb) + continue; + + return na-nb; + } + if (*a > *b) + return 1; + if (*a < *b) + return 1; + a++; + b++; + } +} + + +static void done(void) +{ + int size; + + if (best && n_best <= n_stack) + return; + free(best); + size = sizeof(*stack)*(n_stack+1); + best = alloc_size(size); + memcpy(best, stack, sizeof(*stack)*n_stack); + n_best = n_stack; + best[n_best] = NULL; +} + + +static void push(struct pkg *pkg) +{ +//fprintf(stderr, "push %.*s\n", ID2S(pkg->id)); + if (n_stack == stack_max) { + stack_max = (stack_max+1)*2; + stack = realloc(stack, sizeof(*stack)*stack_max); + if (!stack) { + perror("realloc"); + exit(1); + } + + } + stack[n_stack++] = pkg; + pkg->mark = 1; +} + + +static void pop(void) +{ + assert(n_stack); + n_stack--; + stack[n_stack]->mark = 0; +} + + +static int satisfies(const struct pkg *pkg, const struct ref *ref) +{ + int cmp; + + if (pkg->id != ref->pkg) + return 0; + if (!ref->version) + return 1; + assert(pkg->version); + cmp = comp_versions(pkg->version, ref->version); +//fprintf(stderr, "%.*s <%d> %.*s\n", +// ID2S(pkg->version), cmp, ID2S(ref->version)); + switch (ref->relop) { + case rel_eq: + return !cmp; + case rel_ge: + return cmp >= 0; + case rel_lt: + return cmp < 0; + default: + abort(); + } +} + + +static int conflicts(const struct pkg *pkg, const struct list *conf) +{ + /* @@@ */ + return 0; +} + + +static void resolve(struct list *next_dep, const struct ref *dep, + struct list *conf) +{ + struct list more_deps; + struct list more_conf = { + .next = conf + }; + struct pkg *pkg; + + while (!dep) { + if (!next_dep) { + done(); + return; + } + dep = next_dep->refs; + next_dep = next_dep->next; + } + for (pkg = dep->pkg->value; pkg; pkg = pkg->more) { + if (pkg->installed || pkg->mark) { + resolve(next_dep, dep->next, conf); + continue; + } + if (!satisfies(pkg, dep)) + continue; + if (conflicts(pkg, conf)) + continue; + push(pkg); + more_deps.refs = pkg->depends; + if (next_dep) { + more_deps.next = next_dep->next; + next_dep->next = &more_deps; + } else { + more_deps.next = NULL; + } + more_conf.refs = pkg->conflicts; + more_conf.next = conf; + resolve(next_dep ? next_dep : &more_deps, dep->next, + &more_conf); + if (next_dep) + next_dep->next = more_deps.next; + pop(); + } +} + + +struct pkg **prereq(struct pkg *pkg) +{ + struct list deps = { + .refs = pkg->depends, + .next = NULL + }; + +#if 0 + /* make sure we don't return NULL if all dependencies are met */ + if (!stack) { + stack = alloc_type(struct pkg *); + stack_max = 1; + } +#endif + /* @@@ make list of pre-existing conflicts */ + resolve(&deps, NULL, NULL); + free(stack); + return best; +} diff --git a/qpkg/prereq.h b/qpkg/prereq.h new file mode 100644 index 0000000..81cf066 --- /dev/null +++ b/qpkg/prereq.h @@ -0,0 +1,6 @@ +#ifndef PREREQ_H +#define PREREQ_H + +struct pkg **prereq(struct pkg *pkg); + +#endif /* !PREREQ_H */ diff --git a/qpkg/qpkg.c b/qpkg/qpkg.c new file mode 100644 index 0000000..76ec13e --- /dev/null +++ b/qpkg/qpkg.c @@ -0,0 +1,144 @@ +#include +#include +#include + +#include "id.h" +#include "prereq.h" +#include "gobble.h" +#include "qpkg.h" + + +struct tree *packages = NULL; +struct tree *versions = NULL; + + +static void list_all_packages(void) +{ + const struct id *id; + + for (id = first_id(packages); id; id = next_id(id)) { + struct pkg *pkg = id->value; + + printf("%.*s", (int) id->len, id->s); + if (!pkg) + printf(" (virtual)"); + else { + if (pkg->version) + printf(" (%.*s)", + (int) pkg->version->len, pkg->version->s); + if (pkg->more) + printf(" +++"); + } + printf("\n"); + } +} + + +static void list_one_package(const char *name) +{ + const struct id *id = find_id(packages, name, strlen(name)); + const struct pkg *pkg; + + if (!id) { + fprintf(stderr, "no such package \"%s\"\n", name); + exit(1); + } + for (pkg = id->value; pkg; pkg = pkg->more) + printf("%.*s\n", (int) pkg->version->len, pkg->version->s); +} + + +static void find_prereq(const char *name, const char *version) +{ + const struct id *id = find_id(packages, name, strlen(name)); + struct pkg *pkg; + struct pkg **pkgs; + + if (!id) { + fprintf(stderr, "no such package \"%s\"\n", name); + exit(1); + } + pkg = id->value; + if (!pkg) { + fprintf(stderr, "package %s is a ghost\n", name); + exit(1); + } + if (version) { + id = find_id(versions, version, strlen(version)); + if (!id) { + fprintf(stderr, "no such version\"%s\"\n", version); + exit(1); + } + while (pkg && pkg->version != id) + pkg = pkg->more; + if (pkg) { + fprintf(stderr, "no %s version\"%s\" found\n", + name, version); + exit(1); + } + } + + pkgs = prereq(pkg); + if (!pkgs) { + fprintf(stderr, "can't resolve %s%s%s\n", + name, version ? " version " : "", version ? version : ""); + exit(1); + } + while (*pkgs) { + const char *file = (*pkgs)->filename; + const char *end; + + if (file) { + end = strchr(file, '\n'); + printf("%.*s\n", (int) (end-file), file); + } + pkgs++; + } +} + + +static void usage(const char *name) +{ + fprintf(stderr, +"usage: %s [pkg-list ...] list [pkg]\n" +" %s [pkg-list ...] prereq pkg [version]\n" + , name, name); + exit(1); +} + + +int main(int argc, char **argv) +{ + int arg; + + packages = make_tree(comp_id); + versions = make_tree(comp_id); + for (arg = 1; arg != argc; arg++) { + if (!strcmp(argv[arg], "list")) { + switch (argc-arg) { + case 1: + list_all_packages(); + return 0; + case 2: + list_one_package(argv[arg+1]); + return 0; + default: + usage(*argv); + } + } else if (!strcmp(argv[arg], "prereq")) { + switch (argc-arg) { + case 2: + find_prereq(argv[arg+1], NULL); + return 0; + case 3: + find_prereq(argv[arg+1], argv[arg+2]); + return 0; + default: + usage(*argv); + } + } else { + gobble(argv[arg]); + } + } + return 0; +} diff --git a/qpkg/qpkg.h b/qpkg/qpkg.h new file mode 100644 index 0000000..d33e095 --- /dev/null +++ b/qpkg/qpkg.h @@ -0,0 +1,34 @@ +#ifndef QPKG_H +#define QPKG_H + +enum relop { + rel_eq, + rel_ge, + rel_lt, +}; + +struct pkg; + +struct ref { + struct id *pkg; + struct id *version; + enum relop relop; /* undefined if version == NULL */ + struct ref *next; +}; + +struct pkg { + struct id *id; + struct id *version; + struct ref *conflicts; + struct ref *depends; + const char *filename; + int installed; + struct pkg *more; + int mark; +}; + + +struct tree *packages; +struct tree *versions; + +#endif /* !QPKG_H */ diff --git a/qpkg/util.h b/qpkg/util.h new file mode 100644 index 0000000..66e6a0a --- /dev/null +++ b/qpkg/util.h @@ -0,0 +1,27 @@ +/* + * util.h - Common utility functions + * + * Written 2009 by Werner Almesberger + * Copyright 2009 by Werner Almesberger + * + * 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. + */ + +#ifndef UTIL_H +#define UTIL_H + +#include + + +#define alloc_size(s) \ + ({ void *alloc_size_tmp = malloc(s); \ + if (!alloc_size_tmp) \ + abort(); \ + alloc_size_tmp; }) + +#define alloc_type(t) ((t *) alloc_size(sizeof(t))) + +#endif /* !UTIL_H */