mirror of
git://projects.qi-hardware.com/wernermisc.git
synced 2024-11-22 07:00:39 +02:00
6a3e471304
- gobble.c (gobble_buf): recognize (and ignore) the following fields: Bugs, Installed-Size, MD5sum, Origin, Original-Maintainer, SHA1, SHA256, Task. - pkg.h (enum relop): added comments showing the respective symbol - pkg.h (enum relop): added rel_gg (>>), rel_le (<=), and rel_ll (<<) - gobble.c (gobble_buf): recognize >>, <=, and << - TODO: record progress in support beyond Jlime/OE
572 lines
9.3 KiB
C
572 lines
9.3 KiB
C
/*
|
|
* gobble.c - Read a package database file in a hurry
|
|
*
|
|
* Written 2010 by Werner Almesberger
|
|
* Copyright 2010 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.
|
|
*/
|
|
|
|
|
|
#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 "pkg.h"
|
|
#include "qpkg.h"
|
|
#include "gobble.h"
|
|
|
|
|
|
#define CHARS_AFTER_ERROR 20
|
|
|
|
|
|
#ifdef BREAKNECK_SPEED
|
|
|
|
#define EXPECT(s) do { buf += sizeof(s)-1; } while (0)
|
|
|
|
#else /* !BREAKNECK_SPEED */
|
|
|
|
#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)
|
|
|
|
#endif
|
|
|
|
|
|
#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)
|
|
|
|
|
|
#define DONE goto done
|
|
|
|
|
|
static void finish_pkg(struct pkg *new, struct jrb *jrb)
|
|
{
|
|
struct pkg *old;
|
|
|
|
if (!new->version) {
|
|
fprintf(stderr, "package %.*s has no version\n",
|
|
ID2PF(new->id));
|
|
exit(1);
|
|
}
|
|
if (!new->arch) {
|
|
fprintf(stderr,
|
|
"package %.*s version %.*s has no architecture\n",
|
|
ID2PF(new->id), ID2PF(new->version));
|
|
exit(1);
|
|
}
|
|
if (!new->filename && !(new->flags & QPKG_INSTALLED)) {
|
|
fprintf(stderr,
|
|
"package %.*s version %.*s has no file name "
|
|
"(nor is it installed)\n",
|
|
ID2PF(new->id), ID2PF(new->version));
|
|
exit(1);
|
|
}
|
|
|
|
for (old = new->more; old; old = old->more)
|
|
if (old->version == new->version)
|
|
goto compact;
|
|
return;
|
|
|
|
compact:
|
|
jrb->val = new->more;
|
|
old->flags |= new->flags;
|
|
free_pkg(new);
|
|
}
|
|
|
|
|
|
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 jrb *jrb = NULL; /* RB tree node of current package */
|
|
struct ref **anchor = NULL;
|
|
int i, failed_at = 0;
|
|
|
|
initial:
|
|
if (buf == end)
|
|
DONE;
|
|
if (*buf == '\n') {
|
|
lineno++;
|
|
buf++;
|
|
goto initial;
|
|
}
|
|
|
|
/* decode the tag */
|
|
|
|
switch (*buf++) {
|
|
case 'A': /* Architecture // Auto-Installed */
|
|
switch (NEXT) {
|
|
case 'r':
|
|
EXPECT("chitecture:");
|
|
goto architecture;
|
|
case 'u':
|
|
EXPECT("to-Installed:");
|
|
goto skip_data;
|
|
default:
|
|
FAIL;
|
|
}
|
|
|
|
case 'B': /* Bugs */
|
|
EXPECT("ugs:");
|
|
goto skip_data;
|
|
|
|
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-Size, Installed-Time */
|
|
EXPECT("nstalled-");
|
|
switch (NEXT) {
|
|
case 'S':
|
|
EXPECT("ize:");
|
|
goto skip_data;
|
|
case 'T':
|
|
EXPECT("ime:");
|
|
goto skip_data;
|
|
default:
|
|
FAIL;
|
|
}
|
|
|
|
case 'L': /* License */
|
|
EXPECT("icense:");
|
|
goto skip_data;
|
|
|
|
case 'M': /* Maintainer, MD5Sum, MD5sum */
|
|
switch (NEXT) {
|
|
case 'a':
|
|
EXPECT("intainer:");
|
|
goto skip_data;
|
|
case 'D':
|
|
EXPECT("5");
|
|
switch (NEXT) {
|
|
case 'S':
|
|
case 's':
|
|
break;
|
|
default:
|
|
FAIL;
|
|
}
|
|
EXPECT("um:");
|
|
goto skip_data;
|
|
default:
|
|
FAIL;
|
|
}
|
|
|
|
case 'O': /* OE, Origin, Original-Maintainer */
|
|
switch (NEXT) {
|
|
case 'E':
|
|
EXPECT(":");
|
|
goto skip_data;
|
|
case 'r':
|
|
EXPECT("igin");
|
|
switch (NEXT) {
|
|
case ':':
|
|
break;
|
|
case 'a':
|
|
EXPECT("l-Maintainer:");
|
|
break;
|
|
default:
|
|
FAIL;
|
|
}
|
|
goto skip_data;
|
|
default:
|
|
FAIL;
|
|
}
|
|
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, SHA1, SHA256, Size, Source, Suggests
|
|
// Status */
|
|
switch (NEXT) {
|
|
case 'e':
|
|
EXPECT("ction:");
|
|
goto skip_data;
|
|
case 'H':
|
|
EXPECT("A");
|
|
switch (NEXT) {
|
|
case '1':
|
|
EXPECT(":");
|
|
break;
|
|
case '2':
|
|
EXPECT("56:");
|
|
break;
|
|
default:
|
|
FAIL;
|
|
}
|
|
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 'T': /* Task */
|
|
EXPECT("ask:");
|
|
goto skip_data;
|
|
|
|
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:
|
|
if (pkg)
|
|
finish_pkg(pkg, jrb);
|
|
|
|
WHITESPACE;
|
|
jrb = ID(packages);
|
|
pkg = new_pkg(jrb);
|
|
goto eol;
|
|
|
|
version:
|
|
WHITESPACE;
|
|
if (pkg->version)
|
|
FAIL;
|
|
pkg->version = ID(versions)->key;
|
|
goto eol;
|
|
|
|
architecture:
|
|
WHITESPACE;
|
|
if (pkg->arch)
|
|
FAIL;
|
|
pkg->arch = buf;
|
|
goto skip_data;
|
|
|
|
provides:
|
|
anchor = &pkg->provides;
|
|
/*
|
|
* There should never be a version in the provisions, so it's a bit
|
|
* wasteful to use a structure that has a version field. But then, code
|
|
* reuse is nice, too.
|
|
*/
|
|
goto list_with_version;
|
|
|
|
status:
|
|
pkg->flags |= QPKG_INSTALLED;
|
|
/* @@@ later */
|
|
goto skip_data;
|
|
|
|
filename:
|
|
WHITESPACE;
|
|
if (pkg->filename)
|
|
FAIL;
|
|
pkg->filename = buf;
|
|
goto skip_data;
|
|
|
|
eol:
|
|
while (buf != end) {
|
|
if (*buf == ' ' || *buf == '\t') {
|
|
buf++;
|
|
continue;
|
|
}
|
|
if (*buf++ != '\n')
|
|
FAIL;
|
|
lineno++;
|
|
if (buf == end)
|
|
DONE;
|
|
if (*buf == ' ' || *buf == '\t')
|
|
FAIL;
|
|
goto initial;
|
|
}
|
|
DONE;
|
|
|
|
skip_data:
|
|
while (buf != end) {
|
|
if (*buf++ != '\n')
|
|
continue;
|
|
lineno++;
|
|
if (buf == end)
|
|
DONE;
|
|
if (*buf != ' ' && *buf != '\t')
|
|
goto initial;
|
|
}
|
|
DONE;
|
|
|
|
list_with_version:
|
|
while (1) {
|
|
struct ref *ref;
|
|
|
|
WHITESPACE;
|
|
ref = alloc_type(struct ref);
|
|
ref->pkg = ID(packages)->key;
|
|
|
|
/*
|
|
* Work around the Wireshark Anomaly
|
|
*/
|
|
if (buf != end && *buf == ')')
|
|
buf++;
|
|
|
|
WHITESPACE;
|
|
if (buf == end || *buf != '(')
|
|
ref->version = NULL;
|
|
else {
|
|
buf++;
|
|
switch (NEXT) {
|
|
case '=':
|
|
ref->relop = rel_eq;
|
|
break;
|
|
case '<':
|
|
switch (NEXT) {
|
|
case ' ':
|
|
ref->relop = rel_lt;
|
|
break;
|
|
case '=':
|
|
ref->relop = rel_le;
|
|
break;
|
|
case '<':
|
|
ref->relop = rel_ll;
|
|
break;
|
|
default:
|
|
buf--;
|
|
}
|
|
break;
|
|
case '>':
|
|
switch (NEXT) {
|
|
case '=':
|
|
ref->relop = rel_ge;
|
|
break;
|
|
case '>':
|
|
ref->relop = rel_gg;
|
|
break;
|
|
default:
|
|
FAIL;
|
|
}
|
|
break;
|
|
default:
|
|
FAIL;
|
|
}
|
|
WHITESPACE;
|
|
ref->version = ID(versions)->key;
|
|
EXPECT(")");
|
|
}
|
|
*anchor = ref;
|
|
ref->next = NULL;
|
|
anchor = &ref->next;
|
|
if (buf == end)
|
|
DONE;
|
|
if (*buf != ',')
|
|
break;
|
|
buf++;
|
|
}
|
|
anchor = NULL;
|
|
goto eol;
|
|
|
|
done:
|
|
if (pkg)
|
|
finish_pkg(pkg, jrb);
|
|
return;
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
/*
|
|
* We should be able to test for __UCLIBC_HAS_ADVANCED_REALTIME__ specifically,
|
|
* but that doesn't work for some reason. So let's just omit posix_madvise on
|
|
* __UCLIBC__ in general.
|
|
*/
|
|
|
|
|
|
#if !defined(__UCLIBC__)
|
|
|
|
static int do_madvise(void *addr, size_t len, int advice)
|
|
{
|
|
return posix_madvise(addr, len, advice);
|
|
}
|
|
|
|
#else /* __UCLIBC__ */
|
|
|
|
#define do_madvise(addr, len, advice) 0
|
|
|
|
#endif /* __UCLIBC__ */
|
|
|
|
|
|
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 (do_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 (do_madvise(map, st.st_size, POSIX_MADV_RANDOM) < 0) {
|
|
perror("posix_madvise(POSIX_MADV_RANDOM)");
|
|
exit(1);
|
|
}
|
|
}
|