diff options
Diffstat (limited to 'grep-dctrl')
-rw-r--r-- | grep-dctrl/grep-dctrl.c | 875 | ||||
-rw-r--r-- | grep-dctrl/grep-dctrl.rc | 26 | ||||
-rw-r--r-- | grep-dctrl/rc.c | 206 | ||||
-rw-r--r-- | grep-dctrl/rc.h | 36 |
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, ¶); + while (1) { + para_parse_next(¶); + if (para_eof(&pp)) break; + if ((args.invert_match + || !does_para_satisfy(&args.p, ¶)) + && (!args.invert_match + || does_para_satisfy(&args.p, ¶))) { + 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(¶); + 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(¶, + 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 |