qpkg/: initial commit (sneak preview only, doesn't work properly yet)

This commit is contained in:
Werner Almesberger 2010-11-19 14:00:15 -03:00
commit 43b9dc1972
10 changed files with 1015 additions and 0 deletions

11
qpkg/Makefile Normal file
View File

@ -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)

398
qpkg/gobble.c Normal file
View File

@ -0,0 +1,398 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#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);
}
}

6
qpkg/gobble.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef GOBBLE_H
#define GOBBLE_H
void gobble(const char *name);
#endif /* !GOBBLE_H */

129
qpkg/id.c Normal file
View File

@ -0,0 +1,129 @@
#include <string.h>
#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;
}

39
qpkg/id.h Normal file
View File

@ -0,0 +1,39 @@
#ifndef ID_H
#define ID_H
#include <sys/types.h>
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 */

221
qpkg/prereq.c Normal file
View File

@ -0,0 +1,221 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#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;
}

6
qpkg/prereq.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef PREREQ_H
#define PREREQ_H
struct pkg **prereq(struct pkg *pkg);
#endif /* !PREREQ_H */

144
qpkg/qpkg.c Normal file
View File

@ -0,0 +1,144 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#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;
}

34
qpkg/qpkg.h Normal file
View File

@ -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 */

27
qpkg/util.h Normal file
View File

@ -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 <stdlib.h>
#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 */