/* * 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); } }