summaryrefslogtreecommitdiff
path: root/grep-dctrl
diff options
context:
space:
mode:
Diffstat (limited to 'grep-dctrl')
-rw-r--r--grep-dctrl/grep-dctrl.c875
-rw-r--r--grep-dctrl/grep-dctrl.rc26
-rw-r--r--grep-dctrl/rc.c206
-rw-r--r--grep-dctrl/rc.h36
4 files changed, 1143 insertions, 0 deletions
diff --git a/grep-dctrl/grep-dctrl.c b/grep-dctrl/grep-dctrl.c
new file mode 100644
index 0000000..687f8cf
--- /dev/null
+++ b/grep-dctrl/grep-dctrl.c
@@ -0,0 +1,875 @@
+/* dctrl-tools - Debian control file inspection tools
+ Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+ Antti-Juhani Kaijanaho
+
+ 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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <argp.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include "fnutil.h"
+#include "fsaf.h"
+#include "i18n.h"
+#include "misc.h"
+#include "msg.h"
+#include "paragraph.h"
+#include "predicate.h"
+#include "rc.h"
+#include "util.h"
+
+const char * argp_program_version = "grep-dctrl (dctrl-tools) " VERSION;
+const char * argp_program_bug_address = MAINTAINER;
+
+const char description [] = "Description";
+size_t description_inx;
+
+static char progdoc [] = N_("grep-dctrl -- grep Debian control files");
+
+static char argsdoc [] = "PREDICATE [FILENAME...]";
+
+enum {
+ OPT_CONFIG=256,
+ OPT_OPTPARSE,
+ OPT_SILENT,
+ OPT_EQ,
+ OPT_LT,
+ OPT_LE,
+ OPT_GT,
+ OPT_GE,
+ OPT_MMAP,
+ OPT_IGN_ERRS,
+ OPT_PATTERN
+};
+
+#undef BANNER
+
+#ifdef BANNER
+void banner(bool automatic)
+{
+ char * fname = fnqualify_xalloc("~/.grep-dctrl-banner-shown");
+ struct stat st;
+ if (automatic) {
+ int r = stat(fname, &st);
+ if (r == 0) goto end;
+ }
+ FILE * fp = fopen("/dev/tty", "w");
+ if (fp == 0) {
+ perror("/dev/tty");
+ goto end;
+ }
+ fprintf(fp,
+ "==========================================================================\n"
+ " NOTE \n"
+ " grep-dctrl has been rewritten from scratch. Although this does add new \n"
+ " features, regressions are certainly possible. Please watch for them and \n"
+ " report them to the BTS. \n"
+ "==========================================================================\n"
+ "(The above annoying banner will not be shown to you again, unless you\n"
+ "request it with the -B switch. It will also be removed entirely soon.)\n");
+
+ int r = creat(fname, 0644);
+ if (r == -1) perror(fname);
+
+ if (!automatic) exit(0);
+
+ for (int i = 15; i > 0; i--) {
+ fprintf(fp, "%2d seconds until program is resumed...\r", i);
+ fflush(fp);
+ sleep(1);
+ }
+ fprintf(fp, " \r");
+ fflush(fp);
+end:
+ free(fname);
+}
+#endif
+
+static struct argp_option options[] = {
+#ifdef BANNER
+ { "banner", 'B', 0, 0, N_("Show the testing banner.") },
+#endif
+ { "errorlevel", 'l', N_("LEVEL"), 0, N_("Set debugging level to LEVEL.") },
+ { "field", 'F', N_("FIELD,FIELD,..."), 0, N_("Restrict pattern matching to the FIELDs given.") },
+ { 0, 'P', 0, 0, N_("This is a shorthand for -FPackage.") },
+ { 0, 'S', 0, 0, N_("This is a shorthand for -FSource:Package.") },
+ { "show-field", 's', N_("FIELD,FIELD,..."), 0, N_("Show only the body of these fields from the matching paragraphs.") },
+ { 0, 'd', 0, 0, N_("Show only the first line of the \"Description\" field from the matching paragraphs.") },
+ { "no-field-names", 'n', 0, 0, N_("Suppress field names when showing specified fields.") },
+ { "eregex", 'e', 0, 0, N_("Regard the pattern as an extended POSIX regular expression.") },
+ { "regex", 'r', 0, 0, N_("The pattern is a standard POSIX regular expression.") },
+ { "ignore-case", 'i', 0, 0, N_("Ignore case when looking for a match.") },
+ { "invert-match", 'v', 0, 0, N_("Show only paragraphs that do not match.") },
+ { "count", 'c', 0, 0, N_("Show only the count of matching paragraphs.") },
+ { "config-file", OPT_CONFIG, N_("FNAME"),0, N_("Use FNAME as the config file.") },
+ { "exact-match", 'X', 0, 0, N_("Do an exact match.") },
+ { "copying", 'C', 0, 0, N_("Print out the copyright license.") },
+ { "and", 'a', 0, 0, N_("Conjunct predicates.") },
+ { "or", 'o', 0, 0, N_("Disjunct predicates.") },
+ { "not", '!', 0, 0, N_("Negate the following predicate.") },
+ { "eq", OPT_EQ, 0, 0, N_("Test for version number equality.") },
+ { "lt", OPT_LT, 0, 0, N_("Version number comparison: <.") },
+ { "le", OPT_LE, 0, 0, N_("Version number comparison: <=.") },
+ { "gt", OPT_GT, 0, 0, N_("Version number comparison: >.") },
+ { "ge", OPT_GE, 0, 0, N_("Version number comparison: >=.") },
+ { "debug-optparse", OPT_OPTPARSE, 0, 0, N_("Debug option parsing.") },
+ { "quiet", 'q', 0, 0, N_("Do no output to stdout.") },
+ { "silent", OPT_SILENT, 0, 0, N_("Do no output to stdout.") },
+ { "mmap", OPT_MMAP, 0, 0, N_("Attempt mmapping input files") },
+ { "ignore-parse-errors", OPT_IGN_ERRS, 0, 0, N_("Ignore parse errors") },
+ { "pattern", OPT_PATTERN, N_("PATTERN"), 0, N_("Specify the pattern to search for") },
+ { 0 }
+};
+
+
+// Tokens
+#define TOK_EOD 0
+#define TOK_NOT 1
+#define TOK_AND 2
+#define TOK_OR 3
+#define TOK_LP 4 /* left paren */
+#define TOK_RP 5 /* right paren */
+#define TOK_ATOM_BASE 6 /* All tokens >= TOK_ATOM_BASE are atoms; the
+ difference is the atom index. */
+
+#define MAX_FNAMES 4096
+#define MAX_TOKS 16384
+
+static int debug_optparse = 0;
+
+struct arguments {
+ /* Parser state flag: last token seen was ')' */
+ bool just_seen_cparen;
+ /* Top of the parser stack. */
+ size_t top;
+ /* Number of file names seen. */
+ size_t num_fnames;
+ /**/
+ size_t num_show_fields;
+ /**/
+ size_t num_search_fields;
+ /* A machine-readable representation of the predicate. */
+ struct predicate p;
+ /* Configuration file name */
+ char const * rcname;
+ /* Ignore parse errors? */
+ bool ignore_errors;
+ /* Quiet operation? */
+ bool quiet;
+ /* Do show field names? */
+ bool show_field_name;
+ /* Do show (only) first line of Description? */
+ bool short_descr;
+ /* Does show_fields contain Description? */
+ bool description_selected;
+ /* Count matching paragraphs? */
+ bool count;
+ /* Invert match? */
+ bool invert_match;
+ /* First unused position in toks. */
+ size_t toks_np;
+ /* Token read position. */
+ size_t toks_pos;
+ /* Finished with the predicate scanning? */
+ bool finished;
+ /* Are we inside an atom? */
+ bool in_atom;
+ /* Pattern error? */
+ bool pattern_error;
+ /* Token stream for the predicate parser. */
+ int toks[MAX_TOKS];
+ /* For each atom, give code with which it can be accessed. */
+ struct atom_code {
+ size_t n;
+ int * routine;
+ } *atom_code[MAX_ATOMS];
+ /* File names seen on the command line. */
+ struct ifile fname[MAX_FNAMES];
+ /**/
+ struct show_fields {
+ char const * name;
+ size_t inx;
+ size_t repl; // the field to use if this is empty
+ } show_fields[MAX_FIELDS];
+ /* Search field names seen during current atom. */
+ char * search_fields[MAX_FIELDS];
+};
+
+struct atom * clone_atom(struct arguments * args)
+{
+ if (args->p.num_atoms >= MAX_ATOMS) {
+ message(L_FATAL, _("predicate is too complex"), 0);
+ fail();
+ }
+ int oa = args->p.num_atoms-1;
+ struct atom * atom = get_current_atom(&args->p);
+ int na = args->p.num_atoms;
+ struct atom * rv = &args->p.atoms[args->p.num_atoms++];
+ rv->field_name = atom->field_name;
+ rv->field_inx = atom->field_inx;
+ rv->mode = atom->mode;
+ rv->ignore_case = atom->ignore_case;
+ rv->pat = atom->pat;
+ rv->patlen = atom->patlen;
+ struct atom_code * ac = args->atom_code[oa];
+ args->atom_code[na] = ac;
+ assert(ac->n > 0);
+ ac->n += 2;
+ ac->routine = realloc(ac->routine, ac->n * sizeof *ac->routine);
+ if (ac->routine == 0) fatal_enomem(0);
+ ac->routine[ac->n-2] = I_PUSH(na);
+ ac->routine[ac->n-1] = I_OR;
+ return rv;
+}
+
+static void finish_atom(struct arguments * args)
+{
+ assert(args->in_atom);
+ args->in_atom = false;
+ struct atom * atom = get_current_atom(&args->p);
+ if (atom->pat == 0) {
+ args->pattern_error = true;
+ return;
+ }
+ for (size_t i = 0; i < args->num_search_fields; i++) {
+ if (i > 0) atom = clone_atom(args);
+ atom->field_name = args->search_fields[i];
+ predicate_finish_atom(&args->p);
+ }
+ // If there are no fields, we have not yet run this...
+ // ... but it must be done (especially with -r/-e atoms)
+ if (args->num_search_fields == 0) predicate_finish_atom(&args->p);
+ args->num_search_fields = 0;
+}
+
+#if 0
+/* Pop off one stack state, inserting the associated instructions to
+ * the predicate program. If paren is true, current state must be
+ * STATE_PAREN, and if paren is false, it must not be STATE_PAREN. */
+static void leave(struct arguments * args, int paren)
+{
+ debug_message("leaving...", 0);
+ assert(paren == (args->state == STATE_PAREN));
+ if (args->state == STATE_ATOM) finish_atom(args);
+ assert(args->top > 0);
+ --args->top;
+ for (struct insn_node * it = args->stack[args->top].insns_first;
+ it != 0;) {
+ addinsn(&args->p, it->insn);
+ struct insn_node * next = it->next;
+ free(it);
+ it = next;
+ }
+ args->stack[args->top].insns_first = 0;
+ args->stack[args->top].insns_last = 0;
+ args->state = args->stack[args->top].state;
+}
+#endif
+
+#define APPTOK(tok) do { apptok(args, (tok)); } while (0)
+
+static void apptok(struct arguments * args, const int tok)
+{
+ debug_message("apptok", 0);
+ if (args->in_atom && tok < TOK_ATOM_BASE) {
+ finish_atom(args);
+ }
+ if (args->toks_np >= MAX_TOKS) {
+ message(L_FATAL, _("predicate is too long"), 0);
+ fail();
+ }
+ args->toks[args->toks_np++] = tok;
+}
+
+#define FINISH do { finish(args); } while (0)
+
+/* Flush the state stack. */
+static void finish(struct arguments * args)
+{
+ assert(!args->finished);
+ if (args->in_atom) finish_atom(args);
+ args->finished = true;
+}
+
+#define ENTER_ATOM (enter_atom((args)))
+
+/* FIXME: UPDATE COMMENT
+If necessary, enter STATE_ATOM and allocate a new atom, pushing
+ * along with the old state a PUSH instruction for the new atom to the
+ * parser stack. If we are already in STATE_ATOM, reuse the current
+ * atom. */
+static struct atom * enter_atom(struct arguments * args)
+{
+ struct atom * rv;
+ if (args->in_atom) {
+ assert(args->p.num_atoms > 0);
+ return &args->p.atoms[args->p.num_atoms-1];
+ }
+ args->in_atom = true;
+ if (args->p.num_atoms >= MAX_ATOMS) {
+ message(L_FATAL, _("predicate is too complex"), 0);
+ fail();
+ }
+ APPTOK(args->p.num_atoms + TOK_ATOM_BASE);
+ args->atom_code[args->p.num_atoms] =
+ malloc(sizeof *args->atom_code[args->p.num_atoms]);
+ if (args->atom_code[args->p.num_atoms] == 0) fatal_enomem(0);
+ args->atom_code[args->p.num_atoms]->n = 1;
+ args->atom_code[args->p.num_atoms]->routine = malloc(1 * sizeof(int));
+ if (args->atom_code[args->p.num_atoms]->routine == 0) {
+ fatal_enomem(0);
+ }
+ args->atom_code[args->p.num_atoms]->routine[0]
+ = I_PUSH(args->p.num_atoms);
+ rv = &args->p.atoms[args->p.num_atoms++];
+ rv->field_name = 0;
+ rv->field_inx = -1;
+ rv->mode = M_SUBSTR;
+ rv->ignore_case = 0;
+ rv->pat = 0;
+ rv->patlen = 0;
+ return rv;
+}
+
+#define set_mode(nmode) do { \
+ atom = ENTER_ATOM; \
+ if (atom->mode != M_SUBSTR) { \
+ message(L_FATAL, _("inconsistent atom modifiers"), 0); \
+ fail(); \
+ } \
+ atom->mode = (nmode); \
+} while (0)
+
+static error_t parse_opt (int key, char * arg, struct argp_state * state)
+{
+ struct arguments * args = state->input;
+ bool just_seen_cparen = args->just_seen_cparen;
+ args->just_seen_cparen = false;
+ struct atom * atom;
+ debug_message("parse_opt", 0);
+#ifdef INCLUDE_DEBUG_MSGS
+ if (do_msg(L_DEBUG)) {
+ fprintf(stderr, "%s: in_atom = %s\n",
+ get_progname(),
+ args->in_atom ? "true" : "false");
+ }
+#endif
+ switch (key) {
+ case 'C':
+ if (!to_stdout (COPYING)) fail();
+ exit(0);
+#ifdef BANNER
+ case 'B':
+ banner(false);
+#endif
+ case 'v':
+ args->invert_match = true;
+ break;
+ case 'c':
+ args->count = true;
+ break;
+ case 'q': case OPT_SILENT:
+ debug_message("parse_opt: q", 0);
+ args->quiet = true;
+ break;
+ case 'n':
+ debug_message("parse_opt: n", 0);
+ args->show_field_name = false;
+ break;
+ case 'd':
+ args->short_descr = true;
+ break;
+ case 's': {
+ char * carg = strdup(arg);
+ if (carg == 0) fatal_enomem(0);
+ for (char * s = strtok(carg, ","); s != 0; s = strtok(0, ",")){
+ struct show_fields * sf =
+ &args->show_fields[args->num_show_fields];
+ sf->name = strdup(s);
+ if (sf->name == 0) fatal_enomem(0);
+ char * repl = strchr(sf->name, ':');
+ if (repl != NULL) {
+ *repl = '\0';
+ ++repl;
+ }
+ sf->inx = fieldtrie_insert(sf->name);
+ if (sf->inx == description_inx) {
+ args->description_selected = true;
+ }
+ sf->repl = repl == NULL
+ ? (size_t)(-1)
+ : fieldtrie_insert(repl);
+ ++args->num_show_fields;
+ }
+ free(carg);
+ }
+ break;
+ case 'l': {
+ int ll = str2loglevel(arg);
+ if (ll < 0)
+ {
+ message(L_FATAL, _("no such log level"), arg);
+ fail();
+ }
+ set_loglevel(ll);
+ debug_message("parse_opt: l", 0);
+ }
+ break;
+ case '!':
+ debug_message("parse_opt: !", 0);
+ APPTOK(TOK_NOT);
+ break;
+ case 'a':
+ debug_message("parse_opt: a", 0);
+ APPTOK(TOK_AND);
+ break;
+ case 'o':
+ debug_message("parse_opt: o", 0);
+ APPTOK(I_OR);
+ break;
+ case 'S':
+ debug_message("parse_opt: S", 0);
+ arg = "Source:Package";
+ goto case_F;
+ case 'P':
+ debug_message("parse_opt: P", 0);
+ arg = "Package";
+ goto case_F;
+ case_F:
+ case 'F': {
+ debug_message("parse_opt: Fv", 0);
+ atom = ENTER_ATOM;
+ char * carg = strdup(arg);
+ if (carg == 0) fatal_enomem(0);
+ for (char * s = strtok(carg, ","); s != 0; s = strtok(0, ",")){
+ char * tmp = strdup(s);
+ if (tmp == 0) fatal_enomem(0);
+ args->search_fields[args->num_search_fields++] = tmp;
+ }
+ free(carg);
+ }
+ break;
+ case 'X':
+ debug_message("parse_opt: X", 0);
+ set_mode(M_EXACT);
+ break;
+ case 'r':
+ debug_message("parse_opt: r", 0);
+ set_mode(M_REGEX);
+ break;
+ case 'e':
+ debug_message("parse_opt: e", 0);
+ set_mode(M_EREGEX);
+ break;
+ case OPT_EQ:
+ debug_message("parse_opt: eq", 0);
+ set_mode(M_VER_EQ);
+ break;
+ case OPT_LT:
+ debug_message("parse_opt: lt", 0);
+ set_mode(M_VER_LT);
+ break;
+ case OPT_LE:
+ debug_message("parse_opt: le", 0);
+ set_mode(M_VER_LE);
+ break;
+ case OPT_GT:
+ debug_message("parse_opt: gt", 0);
+ set_mode(M_VER_GT);
+ break;
+ case OPT_GE:
+ debug_message("parse_opt: ge", 0);
+ set_mode(M_VER_GE);
+ break;
+ case OPT_MMAP:
+ debug_message("parse_opt: mmap", 0);
+ fsaf_mmap = 1;
+ break;
+ case 'i':
+ debug_message("parse_opt: i", 0);
+ atom = ENTER_ATOM;
+ atom->ignore_case = 1;
+ break;
+ case OPT_OPTPARSE:
+ debug_message("parse_opt: optparse", 0);
+ debug_optparse = 1;
+ break;
+ case OPT_IGN_ERRS:
+ debug_message("parse_opt: ignore-parse-errors", 0);
+ args->ignore_errors = 1;
+ break;
+ case OPT_PATTERN:
+ debug_message("parse_opt: pattern", 0);
+ atom = ENTER_ATOM;
+ if (atom->pat != 0) {
+ message(L_FATAL, _("Multiple patterns for the same "
+ "atom are not allowed"), 0);
+ fail();
+ }
+ atom->patlen = strlen(arg);
+ atom->pat = malloc(atom->patlen+1);
+ if (atom->pat == 0) fatal_enomem(0);
+ strcpy((char*)atom->pat, arg);
+ break;
+ case ARGP_KEY_ARG:
+ debug_message("parse_opt: argument", 0);
+ redo:
+ debug_message("!!!", 0);
+ if (strcmp(arg, "!") == 0) {
+ debug_message("parse_opt: !", 0);
+ APPTOK(TOK_NOT);
+ break;
+ }
+ if (strcmp(arg, "(") == 0) {
+ debug_message("parse_opt: (", 0);
+ APPTOK(TOK_LP);
+ break;
+ }
+ if (strcmp(arg, ")") == 0) {
+ debug_message("parse_opt: )", 0);
+ args->just_seen_cparen = true;
+ APPTOK(TOK_RP);
+ break;
+ }
+ if (args->finished) {
+ char const * s;
+ if (args->num_fnames >= MAX_FNAMES) {
+ message(L_FATAL, _("too many file names"), 0);
+ fail();
+ }
+ s = strdup(arg);
+ if (s == 0) fatal_enomem(0);
+ args->fname[args->num_fnames++] =
+ (struct ifile){ .mode = m_read, .s = s };
+ break;
+ }
+ if (just_seen_cparen) { FINISH; goto redo; }
+ if (strcmp(arg, "--") == 0) { FINISH; break; }
+ atom = ENTER_ATOM;
+ if (atom->pat != 0) { FINISH; goto redo; }
+ atom->patlen = strlen(arg);
+ atom->pat = malloc(atom->patlen+1);
+ if (atom->pat == 0) fatal_enomem(0);
+ strcpy((char*)atom->pat, arg);
+ break;
+ case ARGP_KEY_END:
+ debug_message("parse_opt: end", 0);
+ if (!args->finished) FINISH;
+ break;
+ case ARGP_KEY_ARGS: case ARGP_KEY_INIT: case ARGP_KEY_SUCCESS:
+ case ARGP_KEY_ERROR: case ARGP_KEY_FINI: case ARGP_KEY_NO_ARGS:
+ debug_message("parse_opt: ignored", 0);
+ break;
+ case OPT_CONFIG:
+ debug_message("parse_opt: --config-file", 0);
+ args->rcname = strdup(arg);
+ break;
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+ return 0;
+}
+
+static void dump_args(struct arguments * args)
+{
+ size_t i;
+ assert(args->finished);
+ assert(args->top == 0);
+ printf("num_atoms = %zi\n", args->p.num_atoms);
+ for (i = 0; i < args->p.num_atoms; i++) {
+ printf("atoms[%zi].field_name = %s\n", i, args->p.atoms[i].field_name);
+ printf("atoms[%zi].mode = %i\n", i, args->p.atoms[i].mode);
+ printf("atoms[%zi].ignore_case = %i\n", i, args->p.atoms[i].ignore_case);
+ printf("atoms[%zi].pat = %s\n", i, args->p.atoms[i].pat);
+ }
+ printf("proglen = %zi\n", args->p.proglen);
+ for (i = 0; i < args->p.proglen; i++) {
+ int op = args->p.program[i];
+ printf("program[%zi] = ", i);
+ switch (op) {
+ case I_NOP: puts("NOP"); break;
+ case I_NEG: puts("NEG"); break;
+ case I_AND: puts("AND"); break;
+ case I_OR: puts("OR"); break;
+ default:
+ printf("PUSH(%i)\n", op - I_PUSH(0));
+ }
+ }
+ printf("num_fnames = %zi\n", args->num_fnames);
+ for (i = 0; i < args->num_fnames; i++) {
+ printf("fname[%zi].mode = %s, fname[%zi].s = %s\n",
+ i, ifile_modes[args->fname[i].mode],
+ i, args->fname[i].s);
+ }
+}
+
+static
+int peek_token(struct arguments const * args)
+{
+ assert(args->toks_pos <= args->toks_np);
+ if (args->toks_pos == args->toks_np) return TOK_EOD;
+ return args->toks[args->toks_pos];
+}
+
+static
+int get_token(struct arguments * args)
+{
+ assert(args->toks_pos <= args->toks_np);
+ if (args->toks_pos == args->toks_np) return TOK_EOD;
+ return args->toks[args->toks_pos++];
+}
+
+static void unexpected(int tok)
+{
+ switch (tok) {
+ case TOK_EOD:
+ message(L_FATAL, _("unexpected end of predicate"), 0);
+ fail();
+ case TOK_NOT:
+ message(L_FATAL, _("unexpected '!' in command line"), 0);
+ fail();
+ case TOK_AND:
+ message(L_FATAL, _("unexpected '-a' in command line"), 0);
+ fail();
+ case TOK_OR :
+ message(L_FATAL, _("unexpected '-o' in command line"), 0);
+ fail();
+ case TOK_LP :
+ message(L_FATAL, _("unexpected '(' in command line"), 0);
+ fail();
+ case TOK_RP :
+ message(L_FATAL, _("unexpected ')' in command line"), 0);
+ fail();
+ default:
+ assert(tok >=TOK_ATOM_BASE);
+ message(L_FATAL, _("unexpected atom in command line"), 0);
+ fail();
+ }
+}
+
+static void parse_conj(struct arguments * args);
+
+static void parse_prim(struct arguments * args)
+{
+ if (peek_token(args) == TOK_LP) {
+ get_token(args);
+ parse_conj(args);
+ if (get_token(args) != TOK_RP) {
+ message(L_FATAL, _("missing ')' in command line"), 0);
+ fail();
+ }
+ return;
+ }
+ if (peek_token(args) < TOK_ATOM_BASE) unexpected(peek_token(args));
+ int atom = get_token(args) - TOK_ATOM_BASE;
+ assert(atom >= 0);
+ assert(atom < MAX_ATOMS);
+ struct atom_code *ac = args->atom_code[atom];
+ for (size_t i = 0; i < ac->n; i++) {
+ addinsn(&args->p, ac->routine[i]);
+ }
+/*
+ addinsn(&args->p, I_PUSH(atom));
+*/
+}
+
+static void parse_neg(struct arguments * args)
+{
+ bool neg = false;
+ if (peek_token(args) == TOK_NOT) {
+ neg = true;
+ get_token(args);
+ }
+ parse_prim(args);
+ if (neg) addinsn(&args->p, I_NEG);
+}
+
+static void parse_disj(struct arguments * args)
+{
+ parse_neg(args);
+ while (peek_token(args) == TOK_OR) {
+ get_token(args);
+ parse_neg(args);
+ addinsn(&args->p, I_OR);
+ }
+}
+
+static void parse_conj(struct arguments * args)
+{
+ parse_disj(args);
+ while (peek_token(args) == TOK_AND) {
+ get_token(args);
+ parse_disj(args);
+ addinsn(&args->p, I_AND);
+ }
+}
+
+static void parse_predicate(struct arguments * args)
+{
+ args->toks_pos = 0;
+ parse_conj(args);
+ int tok = peek_token(args);
+ if (tok != TOK_EOD) unexpected(tok);
+}
+
+static struct argp argp = { .options = options,
+ .parser = parse_opt,
+ .args_doc = argsdoc,
+ .doc = progdoc };
+
+int main (int argc, char * argv[])
+{
+ setlocale(LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+
+ fieldtrie_init();
+
+ static struct arguments args;
+ args.show_field_name = true;
+ msg_set_progname(argv[0]);
+ init_predicate(&args.p);
+ description_inx = fieldtrie_insert(description);
+ argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, &args);
+#ifdef BANNER
+ banner(true);
+#endif
+ parse_predicate(&args);
+ if (args.pattern_error) {
+ message(L_FATAL, _("A pattern is mandatory"), 0);
+ fail();
+ }
+
+ if (debug_optparse) { dump_args(&args); return 0; }
+
+ if (args.p.num_atoms == 0) {
+ message(L_FATAL, _("a predicate is required"), 0);
+ fail();
+ }
+
+ if (!check_predicate(&args.p)) {
+ message(L_FATAL, _("malformed predicate"), 0);
+ fail();
+ }
+
+ if (args.short_descr && !args.description_selected) {
+ if (args.num_show_fields >= MAX_FIELDS) {
+ message(L_FATAL, _("too many output fields"), 0);
+ fail();
+ }
+ message(L_INFORMATIONAL,
+ _("Adding \"Description\" to selected output fields because of -d"),
+ 0);
+ args.show_fields[args.num_show_fields].name = description;
+ args.show_fields[args.num_show_fields].inx = description_inx;
+ ++args.num_show_fields;
+ }
+
+ if (!args.show_field_name && args.num_show_fields == 0) {
+ message(L_FATAL,
+ _("cannot suppress field names when showing whole paragraphs"),
+ 0);
+ fail();
+ }
+
+ size_t count = 0;
+ bool found = false;
+ for (size_t i = 0; i < args.num_fnames || (i == 0 && args.num_fnames == 0); ++i) {
+ int fd;
+ struct ifile fname;
+ if (args.num_fnames == 0) {
+ // Hardcode grep-dctrl <-> "-" mapping so that
+ // Debian packages can genuinely depend on it.
+ char * argv0 = fnbase(argv[0]);
+ if (strcmp(argv0, "grep-dctrl") == 0) {
+ fname = (struct ifile){ .mode = m_read,
+ .s = "-" };
+ } else {
+ fname = find_ifile_by_exename(argv0, args.rcname);
+ }
+ } else {
+ fname = args.fname[i];
+ }
+
+ if (fname.mode == m_error) break;
+
+ fd = open_ifile(fname);
+ if (fd == -1) break;
+
+ if (!chk_ifile(fname, fd)) break;
+
+ FSAF * fp = fsaf_fdopen(fd, fname.s);
+ para_parser_t pp;
+ para_parser_init(&pp, fp, true, args.ignore_errors);
+ para_t para;
+ para_init(&pp, &para);
+ while (1) {
+ para_parse_next(&para);
+ if (para_eof(&pp)) break;
+ if ((args.invert_match
+ || !does_para_satisfy(&args.p, &para))
+ && (!args.invert_match
+ || does_para_satisfy(&args.p, &para))) {
+ continue;
+ }
+ if (args.quiet) {
+ exit(0);
+ }
+ found = true;
+ if (args.count) {
+ ++count;
+ continue;
+ }
+ if (args.num_show_fields == 0) {
+ struct fsaf_read_rv r = get_whole_para(&para);
+ fwrite(r.b, 1, r.len, stdout);
+ putchar('\n');
+ continue;
+ }
+ for (size_t j = 0; j < args.num_show_fields; j++) {
+ if (args.show_field_name) {
+ printf("%s: ", args.show_fields[j].name);
+ }
+ struct fsaf_read_rv r
+ = get_field(&para,
+ args.show_fields[j].inx,
+ args.show_fields[j].repl);
+ if (args.short_descr &&
+ args.show_fields[j].inx == description_inx) {
+ char * nl = memchr(r.b, '\n', r.len);
+ if (nl != 0) r.len = nl - r.b;
+ }
+ fwrite(r.b, 1, r.len, stdout);
+ puts("");
+ continue;
+ }
+ if (args.num_show_fields > 1) puts("");
+ }
+
+ fsaf_close(fp);
+ close_ifile(fname, fd);
+ }
+ if (count) printf("%zi\n", count);
+ return errors_reported() ? 2 : found ? 0 : 1;
+}
+
diff --git a/grep-dctrl/grep-dctrl.rc b/grep-dctrl/grep-dctrl.rc
new file mode 100644
index 0000000..31e03b5
--- /dev/null
+++ b/grep-dctrl/grep-dctrl.rc
@@ -0,0 +1,26 @@
+# Configuration for grep-dctrl. The config line format is
+# line-oriented. Each line defines one executable name - default
+# input file pair; the elements of the pair are in order and separated
+# by whitespace. The hash '#' begins a comment that lasts to the end
+# of line. Empty lines are ignored.
+
+# There is some magic in the default input file name field.
+# If it starts with
+# @exec, the rest of the name is given to /bin/sh -c and its stdout is
+# used as input, if no explicit file name is given on the command
+# line
+# A double @ at the start of the field denotes a single @ in the actual
+# name.
+
+# Template:
+# <executable name> <default input file name>
+
+# The following line used to be required, but it is now hardcoded.
+# grep-dctrl -
+
+# The following two pairs define common aliases for grep-dctrl.
+grep-status /var/lib/dpkg/status
+grep-available /var/lib/dpkg/available
+
+# A new feature: grepping apt's available information
+grep-aptavail @exec apt-cache dumpavail
diff --git a/grep-dctrl/rc.c b/grep-dctrl/rc.c
new file mode 100644
index 0000000..b94b669
--- /dev/null
+++ b/grep-dctrl/rc.c
@@ -0,0 +1,206 @@
+/* grep-dctrl - grep Debian control files
+ Copyright (C) 1999, 2003, 2004 Antti-Juhani Kaijanaho
+
+ 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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ The author can be reached via mail at (ISO 8859-1 charset for the city)
+ Antti-Juhani Kaijanaho
+ Helokantie 1 A 16
+ FIN-40640 JYVÄSKYLÄ
+ FINLAND
+ EUROPE
+ and via electronic mail from
+ gaia@iki.fi
+ If you have a choice, use the email address; it is more likely to
+ stay current.
+
+*/
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "msg.h"
+#include "fnutil.h"
+#include "getaline.h"
+#include "rc.h"
+#include "strutil.h"
+#include "util.h"
+
+char const * const ifile_modes[] = { [m_error] = "m_error",
+ [m_read] = "m_read",
+ [m_exec] = "m_exec" };
+
+static struct ifile parse(char * s)
+{
+ assert(s != 0);
+ s = (char*)left_trimmed(s);
+ trim_right(s);
+ if (*s == 0) return (struct ifile) { .mode = m_read, .s = "-" };
+ static const char at_exec[] = "@exec ";
+ if (s[0] != '@') return (struct ifile){ .mode = m_read, .s = s };
+ assert(s[0] == '@');
+ if (s[1] == '@') return (struct ifile){ .mode = m_read, .s = s+1 };
+ if (strncmp(s, at_exec, sizeof at_exec - 1) == 0) {
+ return (struct ifile){ .mode = m_exec,
+ .s = s + sizeof at_exec - 1 };
+ }
+ debug_message(s, 0);
+ message(L_IMPORTANT, _("Malformed default input file name"), 0);
+ return (struct ifile){ .mode = m_error, .s = 0 };
+}
+
+static bool perms_ok(char const * fname, int fd)
+{
+ struct stat stat;
+ int r = fstat(fd, &stat);
+ if (r == -1) {
+ errno_msg(L_IMPORTANT, fname);
+ return false;
+ }
+ if (stat.st_uid != 0 && stat.st_uid != getuid()) {
+ message(L_IMPORTANT, _("not owned by you or root, ignoring"),
+ fname);
+ return false;
+ }
+ if ((stat.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
+ message(L_IMPORTANT, _("write permissions for "
+ "group or others, ignoring"),
+ fname);
+ return false;
+ }
+ return true;
+}
+
+struct ifile find_ifile_by_exename(const char * exename, const char * rcfname)
+{
+ static const char * default_rcfiles [] = {
+ /* Order is significant: earlier files override later ones. */
+ "~/.grep-dctrlrc",
+ SYSCONF "/grep-dctrl.rc",
+ 0 };
+ int i;
+ char * fname;
+ int lineno;
+ FILE * f;
+
+ assert(exename != 0);
+
+ if (rcfname == 0) {
+ struct ifile rv = { .mode = m_error, .s = 0 };
+ for (i = 0;
+ rv.mode == m_error && default_rcfiles [i] != 0;
+ i++) {
+ rv = find_ifile_by_exename(exename, default_rcfiles [i]);
+ }
+ return rv;
+ }
+
+ assert(rcfname != 0);
+
+ fname = fnqualify(rcfname);
+ if (fname == 0) {
+ errno_msg(L_IMPORTANT, rcfname);
+ return (struct ifile){ .mode = m_error, .s = 0 };
+ }
+
+ message(L_INFORMATIONAL, _("reading config file"), fname);
+
+ f = fopen(fname, "r");
+ if (f == 0) {
+ message(L_INFORMATIONAL, strerror(errno), fname);
+ return (struct ifile){ .mode = m_error, .s = 0 };
+ }
+
+ // check permissions
+ if (!perms_ok(fname, fileno(f))) {
+ return (struct ifile){ .mode = m_error, .s = 0 };
+ }
+
+ lineno = 0;
+ char * rv = 0;
+ while (1) {
+ static char * line = 0;
+ char * line_exe;
+ char * line_ifile;
+
+ /* If this is not the first call, line may be non-null
+ and must be freed. It must be freed on all
+ non-first iterations, too. */
+ free(line);
+
+ line = getaline (f);
+ if (line == 0) {
+ message(L_FATAL, _("read failure or out of memory"),
+ fname);
+ fail();
+ }
+
+ ++lineno;
+
+ if (*line == 0) {
+ rv = 0;
+ break;
+ }
+
+ chop_comment(line, '#');
+ chomp(line);
+
+ if (left_trimmed(line) [0] == 0) {
+ continue;
+ }
+
+ line_exe = strtok(line, " \t");
+ if (line_exe == 0) {
+ line_message(L_IMPORTANT,
+ _("syntax error: need a executable name"),
+ fname, lineno);
+ continue;
+ }
+
+ line_ifile = strtok(0, "\n\t");
+ if (line_ifile == 0) {
+ line_message(L_IMPORTANT,
+ _("syntax error: need an input file name"),
+ fname, lineno);
+ continue;
+ }
+
+ message(L_INFORMATIONAL, _("considering executable name"), line_exe);
+ if (strcmp (exename, line_exe) == 0) {
+ message(L_INFORMATIONAL, _("yes, will use executable name"), line_exe);
+ rv = line_ifile;
+ message(L_INFORMATIONAL, _("default input file"), rv);
+ break;
+ }
+ }
+
+ fclose(f);
+ free(fname);
+
+ if (rv != 0) {
+ return parse(rv);
+ } else {
+ message(L_IMPORTANT, _("executable name not found; "
+ "reading from standard input"), 0);
+ return (struct ifile) { .mode = m_read, .s = "-" };
+ }
+}
+
diff --git a/grep-dctrl/rc.h b/grep-dctrl/rc.h
new file mode 100644
index 0000000..e112424
--- /dev/null
+++ b/grep-dctrl/rc.h
@@ -0,0 +1,36 @@
+/* dctrl-tools - Debian control file inspection tools
+ Copyright (C) 1999, 2003 Antti-Juhani Kaijanaho
+
+ 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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef RC_H__
+#define RC_H__
+
+#include "ifile.h"
+
+extern char const * const ifile_modes[];
+
+/* Search for exename (only basename, though) in rcfname (or if it is
+ null, in default rc files) and return the found corresponding
+ default input file name. The mode will be m_error, if not found or
+ other error occurred, m_read, if it is intended to be interpreted
+ as a file name, and m_exec, if the string is to be interpreted as a
+ shell command whose stdout should be used. The returned pointer
+ will be invalidated by the next call to this function. */
+struct ifile
+find_ifile_by_exename (const char * exename, const char * rcfname);
+
+#endif