diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/.gitignore | 6 | ||||
-rw-r--r-- | tools/Makefile.am | 80 | ||||
-rw-r--r-- | tools/datconv.c | 566 | ||||
-rw-r--r-- | tools/err.c | 69 | ||||
-rw-r--r-- | tools/gdbm_dump.c | 132 | ||||
-rw-r--r-- | tools/gdbm_load.c | 317 | ||||
-rw-r--r-- | tools/gdbmapp.h | 85 | ||||
-rw-r--r-- | tools/gdbmshell.c | 3181 | ||||
-rw-r--r-- | tools/gdbmtool.c | 293 | ||||
-rw-r--r-- | tools/gdbmtool.h | 383 | ||||
-rw-r--r-- | tools/gdbmtool.supp | 19 | ||||
-rw-r--r-- | tools/gram.y | 443 | ||||
-rw-r--r-- | tools/input-argv.c | 132 | ||||
-rw-r--r-- | tools/input-file.c | 90 | ||||
-rw-r--r-- | tools/input-null.c | 53 | ||||
-rw-r--r-- | tools/input-rl.c | 223 | ||||
-rw-r--r-- | tools/input-std.c | 57 | ||||
-rw-r--r-- | tools/lex.l | 754 | ||||
-rw-r--r-- | tools/mem.c | 107 | ||||
-rw-r--r-- | tools/parseopt.c | 702 | ||||
-rw-r--r-- | tools/progname.c | 35 | ||||
-rw-r--r-- | tools/util.c | 131 | ||||
-rw-r--r-- | tools/var.c | 835 | ||||
-rw-r--r-- | tools/wordwrap.c | 636 |
24 files changed, 9329 insertions, 0 deletions
diff --git a/tools/.gitignore b/tools/.gitignore new file mode 100644 index 0000000..57e8914 --- /dev/null +++ b/tools/.gitignore @@ -0,0 +1,6 @@ +gdbmtool +gdbm_dump +gdbm_load +gram.[ch] +gram.output +lex.c diff --git a/tools/Makefile.am b/tools/Makefile.am new file mode 100644 index 0000000..1002d92 --- /dev/null +++ b/tools/Makefile.am @@ -0,0 +1,80 @@ +# This file is part of GDBM. -*- Makefile -*- +# Copyright (C) 2007-2021 Free Software Foundation, Inc. +# +# GDBM 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 3, or (at your option) +# any later version. +# +# GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. + +# Flags +AM_CPPFLAGS=\ + -DLOCALEDIR=\"$(localedir)\"\ + -I$(top_srcdir)/src\ + -I$(top_builddir)/src\ + -I$(top_srcdir)/tools + +noinst_LIBRARIES = libgdbmapp.a + +libgdbmapp_a_SOURCES =\ + err.c\ + mem.c\ + gdbmapp.h\ + parseopt.c\ + progname.c\ + datconv.c\ + gram.c\ + input-argv.c\ + input-file.c\ + input-null.c\ + input-std.c\ + lex.c\ + gdbmshell.c\ + var.c\ + util.c\ + wordwrap.c + +if GDBM_COND_READLINE + libgdbmapp_a_SOURCES += input-rl.c +endif + +# Programs +bin_PROGRAMS = gdbmtool gdbm_load gdbm_dump + +EXTRA_DIST = gram.y lex.l +BUILT_SOURCES = gram.h gram.c lex.c +noinst_HEADERS = gram.h + +gram.c gram.h: gram.y +lex.c: lex.l + +gdbmtool_LDADD = \ + ./libgdbmapp.a\ + ../src/libgdbm.la\ + @READLINE_LIBS@ + +gdbmtool_SOURCES = \ + gdbmtool.h\ + gdbmtool.c + +AM_YFLAGS = -dv $(YFLAGS_DEBUG) +AM_LFLAGS = $(LFLAGS_DEBUG) + +.l.c: + $(AM_V_GEN)$(FLEX) -o $@ $(AM_LFLAGS) $< +.y.c: + $(AM_V_GEN)$(BISON) -o $@ $(AM_YFLAGS) $< + +if COND_GDBMTOOL_DEBUG + AM_CPPFLAGS += -DGDBMTOOL_DEBUG=1 +endif + +gdbm_load_LDADD = ./libgdbmapp.a ../src/libgdbm.la +gdbm_dump_LDADD = ./libgdbmapp.a ../src/libgdbm.la diff --git a/tools/datconv.c b/tools/datconv.c new file mode 100644 index 0000000..4d05f11 --- /dev/null +++ b/tools/datconv.c @@ -0,0 +1,566 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 1990-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +#include "gdbmtool.h" +#include <wctype.h> + +#define DEFFMT(name, type, fmt) \ +static int \ +name (FILE *fp, void *ptr, int size) \ +{ \ + fprintf (fp, fmt, *(type*) ptr); \ + return size; \ +} + +DEFFMT (f_char, char, "%c") +DEFFMT (f_short, short, "%hd") +DEFFMT (f_ushort, unsigned short, "%hu") +DEFFMT (f_int, int, "%d") +DEFFMT (f_uint, unsigned, "%u") +DEFFMT (f_long, long, "%ld") +DEFFMT (f_ulong, unsigned long, "%lu") +DEFFMT (f_llong, long long, "%lld") +DEFFMT (f_ullong, unsigned long long, "%llu") +DEFFMT (f_float, float, "%f") +DEFFMT (f_double, double, "%e") + +static int +f_stringz (FILE *fp, void *ptr, int size) +{ + wchar_t wc; + char *str = ptr; + int i; + + mbtowc (NULL, NULL, 0); + for (i = 0; i < size; ) + { + int n = mbtowc (&wc, &str[i], MB_CUR_MAX); + if (n == 0) + break; + if (n == -1 || !iswprint (wc)) + { + int c; + if ((c = escape (str[i]))) + fprintf (fp, "\\%c", c); + else + fprintf (fp, "\\%03o", *(unsigned char*)(str+i)); + i++; + } + else + { + fwrite (str + i, n, 1, fp); + i += n; + } + } + return i + 1; +} + +static int +f_string (FILE *fp, void *ptr, int size) +{ + wchar_t wc; + char *str = ptr; + int i; + + mbtowc (NULL, NULL, 0); + for (i = 0; i < size; ) + { + int n = mbtowc (&wc, &str[i], MB_CUR_MAX); + if (n == 0) + { + fprintf (fp, "\\%03o", *(unsigned char*)str); + i++; + } + else if (n == -1 || !iswprint (wc)) + { + int c; + if ((c = escape (str[i]))) + fprintf (fp, "\\%c", c); + else + fprintf (fp, "\\%03o", *(unsigned char*)(str+i)); + i++; + } + else + { + fwrite (str + i, n, 1, fp); + i += n; + } + } + return i; +} + +int +s_char (struct xdatum *xd, char *str) +{ + xd_store (xd, str, 1); + return 0; +} + +#define DEFNSCAN(name, type, temptype, strto) \ +int \ +name (struct xdatum *xd, char *str) \ +{ \ + temptype n; \ + type t; \ + char *p; \ + \ + errno = 0; \ + n = strto (str, &p, 0); \ + if (*p) \ + return 1; \ + if (errno == ERANGE || (t = n) != n) \ + return 1; \ + xd_store (xd, &t, sizeof (t)); \ + return 0; \ +} + +DEFNSCAN(s_short, short, long, strtol); +DEFNSCAN(s_ushort, unsigned short, unsigned long, strtoul); +DEFNSCAN(s_int, int, long, strtol) +DEFNSCAN(s_uint, unsigned, unsigned long, strtol) +DEFNSCAN(s_long, long, long, strtoul) +DEFNSCAN(s_ulong, unsigned long, unsigned long, strtoul) +DEFNSCAN(s_llong, long long, long long, strtoll) +DEFNSCAN(s_ullong, unsigned long long, unsigned long long, strtoull) + +int +s_double (struct xdatum *xd, char *str) +{ + double d; + char *p; + + errno = 0; + d = strtod (str, &p); + if (errno || *p) + return 1; + xd_store (xd, &d, sizeof (d)); + return 0; +} + +int +s_float (struct xdatum *xd, char *str) +{ + float d; + char *p; + + errno = 0; + d = strtod (str, &p); + if (errno || *p) + return 1; + xd_store (xd, &d, sizeof (d)); + return 0; +} + +int +s_stringz (struct xdatum *xd, char *str) +{ + xd_store (xd, str, strlen (str) + 1); + return 0; +} + +int +s_string (struct xdatum *xd, char *str) +{ + xd_store (xd, str, strlen (str)); + return 0; +} + +static struct datadef datatab[] = { + { "char", sizeof(char), f_char, s_char }, + { "short", sizeof(short), f_short, s_short }, + { "ushort", sizeof(unsigned short), f_ushort, s_ushort }, + { "int", sizeof(int), f_int, s_int }, + { "unsigned", sizeof(unsigned), f_uint, s_uint }, + { "uint", sizeof(unsigned), f_uint, s_uint }, + { "long", sizeof(long), f_long, s_long }, + { "ulong", sizeof(unsigned long), f_ulong, s_ulong }, + { "llong", sizeof(long long), f_llong, s_llong }, + { "ullong", sizeof(unsigned long long), f_ullong, s_ullong }, + { "float", sizeof(float), f_float, s_float }, + { "double", sizeof(double), f_double, s_double }, + { "stringz", 0, f_stringz, s_stringz }, + { "string", 0, f_string, s_string }, + { NULL } +}; + +struct datadef * +datadef_lookup (const char *name) +{ + struct datadef *p; + + for (p = datatab; p->name; p++) + if (strcmp (p->name, name) == 0) + return p; + return NULL; +} + +struct dsegm * +dsegm_new (int type) +{ + struct dsegm *p = emalloc (sizeof (*p)); + p->next = NULL; + p->type = type; + return p; +} + +struct dsegm * +dsegm_new_field (struct datadef *type, char *id, int dim) +{ + struct dsegm *p = dsegm_new (FDEF_FLD); + p->v.field.type = type; + p->v.field.name = id; + p->v.field.dim = dim; + return p; +} + +void +dsegm_list_free (struct dsegm *dp) +{ + while (dp) + { + struct dsegm *next = dp->next; + if (dp->type == FDEF_FLD) + free (dp->v.field.name); + free (dp); + dp = next; + } +} + +struct dsegm * +dsegm_list_find (struct dsegm *dp, char const *name) +{ + for (; dp; dp = dp->next) + if (dp->type == FDEF_FLD && dp->v.field.name && + strcmp (dp->v.field.name, name) == 0) + break; + return dp; +} + +void +datum_format (FILE *fp, datum const *dat, struct dsegm *ds) +{ + int off = 0; + char *delim[2]; + int first_field = 1; + + if (!ds) + { + fprintf (fp, "%.*s\n", dat->dsize, dat->dptr); + return; + } + + if (variable_get ("delim1", VART_STRING, (void*) &delim[0])) + abort (); + if (variable_get ("delim2", VART_STRING, (void*) &delim[1])) + abort (); + + for (; ds && off <= dat->dsize; ds = ds->next) + { + switch (ds->type) + { + case FDEF_FLD: + if (!first_field) + fwrite (delim[1], strlen (delim[1]), 1, fp); + if (ds->v.field.name) + fprintf (fp, "%s=", ds->v.field.name); + if (ds->v.field.dim > 1) + fprintf (fp, "{ "); + if (ds->v.field.type->format) + { + int i, n; + + for (i = 0; i < ds->v.field.dim; i++) + { + if (i) + fwrite (delim[0], strlen (delim[0]), 1, fp); + if (off + ds->v.field.type->size > dat->dsize) + { + fprintf (fp, _("(not enough data)")); + off += dat->dsize; + break; + } + else + { + n = ds->v.field.type->format (fp, + (char*) dat->dptr + off, + ds->v.field.type->size ? + ds->v.field.type->size : + dat->dsize - off); + off += n; + } + } + } + if (ds->v.field.dim > 1) + fprintf (fp, " }"); + first_field = 0; + break; + + case FDEF_OFF: + off = ds->v.n; + break; + + case FDEF_PAD: + off += ds->v.n; + break; + } + } +} + +struct xdatum +{ + char *dptr; + size_t dsize; + size_t dmax; + int off; +}; + +void +xd_expand (struct xdatum *xd, size_t size) +{ + if (xd->dmax < size || 1) + { + xd->dptr = erealloc (xd->dptr, size); + memset (xd->dptr + xd->dmax, 0, size - xd->dmax); + xd->dmax = size; + } +} + +void +xd_store (struct xdatum *xd, void *val, size_t size) +{ + xd_expand (xd, xd->off + size); + memcpy (xd->dptr + xd->off, val, size); + xd->off += size; + if (xd->off > xd->dsize) + xd->dsize = xd->off; +} + +static int +dsconv (struct xdatum *xd, struct dsegm *ds, struct kvpair *kv) +{ + int i; + int err = 0; + struct slist *s; + + if (!ds->v.field.type->scan) + abort (); + + if (kv->type == KV_STRING && ds->v.field.dim > 1) + { + /* If a char[] value was supplied as a quoted string. + convert it to list for further processing */ + if (ds->v.field.type->size == 1) + { + struct slist *head = slist_new_l (kv->val.s, 1); + struct slist *tail = head; + char *p; + for (p = kv->val.s + 1; *p; p++) + slist_insert (&tail, slist_new_l (p, 1)); + free (kv->val.s); + kv->val.l = head; + kv->type = KV_LIST; + } + } + + switch (kv->type) + { + case KV_STRING: + err = ds->v.field.type->scan (xd, kv->val.s); + if (err) + lerror (&kv->loc, _("cannot convert")); + break; + + case KV_LIST: + for (i = 0, s = kv->val.l; i < ds->v.field.dim && s; i++, s = s->next) + { + err = ds->v.field.type->scan (xd, s->str); + if (err) + { + lerror (&kv->loc, _("cannot convert value #%d: %s"), i, s->str); + break; + } + } + if (s) + { + lerror (&kv->loc, "surplus initializers ignored"); + err = 1; + } + } + return err; +} + +static int +datum_scan_notag (datum *dat, struct dsegm *ds, struct kvpair *kv) +{ + struct xdatum xd; + int err = 0; + + memset (&xd, 0, sizeof (xd)); + + for (; err == 0 && ds && kv; ds = ds->next) + { + if (kv->key) + { + lerror (&kv->loc, + _("mixing tagged and untagged values is not allowed")); + err = 1; + break; + } + + switch (ds->type) + { + case FDEF_FLD: + err = dsconv (&xd, ds, kv); + kv = kv->next; + break; + + case FDEF_OFF: + xd_expand (&xd, ds->v.n); + xd.off = ds->v.n; + break; + + case FDEF_PAD: + xd_expand (&xd, xd.off + ds->v.n); + xd.off += ds->v.n; + break; + } + } + + if (err) + { + free (xd.dptr); + return 1; + } + + dat->dptr = xd.dptr; + dat->dsize = xd.dsize; + + return 0; +} + +static int +datum_scan_tag (datum *dat, struct dsegm *ds, struct kvpair *kvlist) +{ + struct xdatum xd; + int err = 0; + struct kvpair *kv; + + /* Check keywords for consistency */ + for (kv = kvlist; kv; kv = kv->next) + { + if (!kv->key) + { + lerror (&kv->loc, + _("mixing tagged and untagged values is not allowed")); + return 1; + } + if (!dsegm_list_find (ds, kv->key)) + { + lerror (&kv->loc, _("%s: no such field in datum"), kv->key); + return 1; + } + } + + /* Initialize datum */ + memset (&xd, 0, sizeof (xd)); + + for (; err == 0 && ds; ds = ds->next) + { + switch (ds->type) + { + case FDEF_FLD: + kv = kvlist_find (kvlist, ds->v.field.name); + if (kv) + err = dsconv (&xd, ds, kv); + else + { + size_t sz = ds->v.field.type->size * ds->v.field.dim; + xd_expand (&xd, xd.off + sz); + xd.off += sz; + } + break; + + case FDEF_OFF: + xd_expand (&xd, ds->v.n); + xd.off = ds->v.n; + break; + + case FDEF_PAD: + xd_expand (&xd, xd.off + ds->v.n); + xd.off += ds->v.n; + break; + } + } + + if (err) + { + free (xd.dptr); + return 1; + } + + dat->dptr = xd.dptr; + dat->dsize = xd.dsize; + + return 0; +} + +int +datum_scan (datum *dat, struct dsegm *ds, struct kvpair *kv) +{ + return (kv->key ? datum_scan_tag : datum_scan_notag) (dat, ds, kv); +} + +void +dsprint (FILE *fp, int what, struct dsegm *ds) +{ + static char *dsstr[] = { "key", "content" }; + int delim; + + fprintf (fp, "define %s", dsstr[what]); + if (ds->next) + { + fprintf (fp, " {\n"); + delim = '\t'; + } + else + delim = ' '; + for (; ds; ds = ds->next) + { + switch (ds->type) + { + case FDEF_FLD: + fprintf (fp, "%c%s", delim, ds->v.field.type->name); + if (ds->v.field.name) + fprintf (fp, " %s", ds->v.field.name); + if (ds->v.field.dim > 1) + fprintf (fp, "[%d]", ds->v.field.dim); + break; + + case FDEF_OFF: + fprintf (fp, "%coffset %d", delim, ds->v.n); + break; + + case FDEF_PAD: + fprintf (fp, "%cpad %d", delim, ds->v.n); + break; + } + if (ds->next) + fputc (',', fp); + fputc ('\n', fp); + } + if (delim == '\t') + fputs ("}\n", fp); +} diff --git a/tools/err.c b/tools/err.c new file mode 100644 index 0000000..19ba6ef --- /dev/null +++ b/tools/err.c @@ -0,0 +1,69 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 2011-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +# include "autoconf.h" +# include "gdbm.h" +# include "gdbmapp.h" +# include <stdio.h> +# include <errno.h> +# include <string.h> + +static void +prerror (const char *fmt, va_list ap, const char *diag, const char *sysdiag) +{ + fprintf (stderr, "%s: ", progname); + vfprintf (stderr, fmt, ap); + if (diag) + fprintf (stderr, ": %s", diag); + if (sysdiag) + fprintf (stderr, ": %s", sysdiag); + fputc ('\n', stderr); +} + +void +verror (const char *fmt, va_list ap) +{ + prerror (fmt, ap, NULL, NULL); +} + +void +error (const char *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + verror (fmt, ap); + va_end (ap); +} + +void +sys_perror (int code, const char *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + prerror (fmt, ap, strerror (code), NULL); + va_end (ap); +} + +void +gdbm_perror (const char *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + prerror (fmt, ap, gdbm_strerror (gdbm_errno), + gdbm_syserr[gdbm_errno] ? strerror (errno) : NULL); + va_end (ap); +} + diff --git a/tools/gdbm_dump.c b/tools/gdbm_dump.c new file mode 100644 index 0000000..ada191d --- /dev/null +++ b/tools/gdbm_dump.c @@ -0,0 +1,132 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 2011-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +# include "autoconf.h" +# include "gdbm.h" +# include "gdbmapp.h" +# include "gdbmdefs.h" + +char *parseopt_program_doc = N_("dump a GDBM database to a file"); +char *parseopt_program_args = N_("DB_FILE [FILE]"); +struct gdbm_option optab[] = { + { 'H', "format", "binary|ascii|0|1", N_("select dump format") }, + { 0 } +}; + +int format = GDBM_DUMP_FMT_ASCII; + +int +main (int argc, char **argv) +{ + GDBM_FILE dbf; + int rc, opt; + char *dbname, *filename; + FILE *fp; + +#ifdef HAVE_SETLOCALE + setlocale (LC_ALL, ""); +#endif + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + set_progname (argv[0]); + + for (opt = parseopt_first (argc, argv, optab); + opt != EOF; + opt = parseopt_next ()) + { + switch (opt) + { + case 'H': + if (strcmp (optarg, "binary") == 0) + format = GDBM_DUMP_FMT_BINARY; + else if (strcmp (optarg, "ascii") == 0) + format = GDBM_DUMP_FMT_ASCII; + else + { + format = atoi (optarg); + switch (format) + { + case GDBM_DUMP_FMT_BINARY: + case GDBM_DUMP_FMT_ASCII: + break; + default: + error (_("unknown dump format")); + exit (EXIT_USAGE); + } + } + break; + + default: + error (_("unknown option")); + exit (EXIT_USAGE); + } + } + + argc -= optind; + argv += optind; + + if (argc == 0) + { + parseopt_print_help (); + exit (EXIT_OK); + } + + if (argc > 2) + { + error (_("too many arguments; try `%s -h' for more info"), progname); + exit (EXIT_USAGE); + } + + dbname = argv[0]; + if (argc == 2) + filename = argv[1]; + else + filename = NULL; + + if (!filename || strcmp (filename, "-") == 0) + { + filename = "<stdout>"; + fp = stdout; + } + else + { + fp = fopen (filename, "w"); + if (!fp) + { + sys_perror (errno, _("cannot open %s"), filename); + exit (EXIT_FATAL); + } + } + + dbf = gdbm_open (dbname, 0, GDBM_READER, 0600, NULL); + if (!dbf) + { + gdbm_perror (_("gdbm_open failed")); + exit (EXIT_FATAL); + } + + rc = gdbm_dump_to_file (dbf, fp, format); + if (rc) + { + gdbm_perror (_("dump error"), filename); + } + + gdbm_close (dbf); + + exit (rc == GDBM_NO_ERROR ? EXIT_OK : EXIT_FATAL); +} + diff --git a/tools/gdbm_load.c b/tools/gdbm_load.c new file mode 100644 index 0000000..41cb820 --- /dev/null +++ b/tools/gdbm_load.c @@ -0,0 +1,317 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 2011-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +# include "autoconf.h" +# include "gdbm.h" +# include "gdbmapp.h" +# include "gdbmdefs.h" +# include <pwd.h> +# include <grp.h> + +int replace = 0; +int meta_mask = 0; +int no_meta_option; + +int mode; +uid_t owner_uid; +gid_t owner_gid; + +char *parseopt_program_doc = N_("load a GDBM database from a file"); +char *parseopt_program_args = N_("FILE [DB_FILE]"); +struct gdbm_option optab[] = { + { 'r', "replace", NULL, N_("replace records in the existing database") }, + { 'm', "mode", N_("MODE"), N_("set file mode") }, + { 'u', "user", N_("NAME|UID[:NAME|GID]"), N_("set file owner") }, + { 'n', "no-meta", NULL, N_("do not attempt to set file meta-data") }, + { 'M', "mmap", NULL, N_("use memory mapping") }, + { 'c', "cache-size", N_("NUM"), N_("set the cache size") }, + { 'b', "block-size", N_("NUM"), N_("set the block size") }, + { 0 } +}; + +static int +set_meta_info (GDBM_FILE dbf) +{ + if (meta_mask) + { + int fd = gdbm_fdesc (dbf); + + if (meta_mask & GDBM_META_MASK_OWNER) + { + if (fchown (fd, owner_uid, owner_gid)) + { + GDBM_SET_ERRNO (dbf, GDBM_ERR_FILE_OWNER, FALSE); + return 1; + } + } + if ((meta_mask & GDBM_META_MASK_MODE) && fchmod (fd, mode)) + { + GDBM_SET_ERRNO (dbf, GDBM_ERR_FILE_OWNER, FALSE); + return 1; + } + } + return 0; +} + +static int +get_int (const char *arg) +{ + char *p; + long n; + + errno = 0; + n = strtol (arg, &p, 0); + if (*p) + { + error (_("invalid number: %s"), arg); + exit (EXIT_USAGE); + } + if (errno) + { + error (_("invalid number: %s: %s"), arg, strerror (errno)); + exit (EXIT_USAGE); + } + return n; +} + +int +main (int argc, char **argv) +{ + GDBM_FILE dbf = NULL; + int rc, opt; + char *dbname, *filename; + FILE *fp; + unsigned long err_line, n; + char *end; + int oflags = GDBM_NEWDB|GDBM_NOMMAP; + int cache_size = 0; + int block_size = 0; + +#ifdef HAVE_SETLOCALE + setlocale (LC_ALL, ""); +#endif + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + set_progname (argv[0]); + + for (opt = parseopt_first (argc, argv, optab); + opt != EOF; + opt = parseopt_next ()) + { + switch (opt) + { + case 'b': + block_size = get_int (optarg); + break; + + case 'c': + cache_size = get_int (optarg); + break; + + case 'm': + { + errno = 0; + n = strtoul (optarg, &end, 8); + if (*end == 0 && errno == 0) + { + mode = n & 0777; + meta_mask |= GDBM_META_MASK_MODE; + } + else + { + error ("%s", _("invalid octal number")); + exit (EXIT_USAGE); + } + } + break; + + case 'u': + { + size_t len; + struct passwd *pw; + + len = strcspn (optarg, ".:"); + if (optarg[len]) + optarg[len++] = 0; + pw = getpwnam (optarg); + if (pw) + owner_uid = pw->pw_uid; + else + { + errno = 0; + n = strtoul (optarg, &end, 10); + if (*end == 0 && errno == 0) + owner_uid = n; + else + { + error (_("invalid user name: %s"), optarg); + exit (EXIT_USAGE); + } + } + + if (optarg[len]) + { + char *grname = optarg + len; + struct group *gr = getgrnam (grname); + if (gr) + owner_gid = gr->gr_gid; + else + { + errno = 0; + n = strtoul (grname, &end, 10); + if (*end == 0 && errno == 0) + owner_gid = n; + else + { + error (_("invalid group name: %s"), grname); + exit (EXIT_USAGE); + } + } + } + else + { + if (!pw) + { + pw = getpwuid (owner_uid); + if (!pw) + { + error (_("no such UID: %lu"), (unsigned long)owner_uid); + exit (EXIT_USAGE); + } + } + owner_gid = pw->pw_gid; + } + meta_mask |= GDBM_META_MASK_OWNER; + } + break; + + case 'r': + replace = 1; + break; + + case 'n': + no_meta_option = 1; + break; + + case 'M': + oflags &= ~GDBM_NOMMAP; + break; + + default: + error (_("unknown option")); + exit (EXIT_USAGE); + } + } + + argc -= optind; + argv += optind; + + if (argc == 0) + { + parseopt_print_help (); + exit (EXIT_OK); + } + + if (argc > 2) + { + error (_("too many arguments; try `%s -h' for more info"), progname); + exit (EXIT_USAGE); + } + + filename = argv[0]; + if (argc == 2) + dbname = argv[1]; + else + dbname = NULL; + + if (strcmp (filename, "-") == 0) + { + filename = "<stdin>"; + fp = stdin; + } + else + { + fp = fopen (filename, "r"); + if (!fp) + { + sys_perror (errno, _("cannot open %s"), filename); + exit (EXIT_FATAL); + } + } + + if (dbname) + { + dbf = gdbm_open (dbname, block_size, oflags, 0600, NULL); + if (!dbf) + { + gdbm_perror (_("gdbm_open failed")); + exit (EXIT_FATAL); + } + + if (cache_size && + gdbm_setopt (dbf, GDBM_SETCACHESIZE, &cache_size, sizeof (int)) == -1) + error (_("gdbm_setopt failed: %s"), gdbm_strerror (gdbm_errno)); + } + + rc = gdbm_load_from_file (&dbf, fp, replace, + no_meta_option ? + (GDBM_META_MASK_MODE | GDBM_META_MASK_OWNER) : + meta_mask, + &err_line); + if (rc) + { + switch (gdbm_errno) + { + case GDBM_ERR_FILE_OWNER: + case GDBM_ERR_FILE_MODE: + error (_("error restoring metadata: %s (%s)"), + gdbm_strerror (gdbm_errno), strerror (errno)); + rc = EXIT_MILD; + break; + + default: + if (err_line) + gdbm_perror ("%s:%lu", filename, err_line); + else + gdbm_perror (_("cannot load from %s"), filename); + rc = EXIT_FATAL; + } + } + + if (dbf) + { + if (!no_meta_option && set_meta_info (dbf)) + { + error (_("error restoring metadata: %s (%s)"), + gdbm_strerror (gdbm_errno), strerror (errno)); + rc = EXIT_MILD; + } + + if (!dbname) + { + if (gdbm_setopt (dbf, GDBM_GETDBNAME, &dbname, sizeof (dbname))) + gdbm_perror (_("gdbm_setopt failed")); + else + { + printf ("%s: created %s\n", progname, dbname); + free (dbname); + } + } + gdbm_close (dbf); + } + exit (rc); +} diff --git a/tools/gdbmapp.h b/tools/gdbmapp.h new file mode 100644 index 0000000..d766358 --- /dev/null +++ b/tools/gdbmapp.h @@ -0,0 +1,85 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 2011-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +#include <stdlib.h> +#include <stdarg.h> +#include "gettext.h" +#ifdef HAVE_LOCALE_H +# include <locale.h> +#endif + +extern const char *progname; + +void set_progname (const char *arg); +void gdbm_perror (const char *fmt, ...); +void sys_perror (int code, const char *fmt, ...); +void error (const char *fmt, ...); +void verror (const char *fmt, va_list ap); + +void *emalloc (size_t size); +void *erealloc (void *ptr, size_t size); +void *ecalloc (size_t nmemb, size_t size); +void *ezalloc (size_t size); +char *estrdup (const char *str); +void *e2nrealloc (void *p, size_t *pn, size_t s); + +#define PARSEOPT_HIDDEN 0x01 +#define PARSEOPT_ALIAS 0x02 + +struct gdbm_option +{ + int opt_short; + char *opt_long; + char *opt_arg; + char *opt_descr; + int opt_flags; +}; + +int parseopt_first (int pc, char **pv, struct gdbm_option *options); +int parseopt_next (void); +void parseopt_print_help (void); + +extern char *parseopt_program_name; +extern char *parseopt_program_doc; +extern char *parseopt_program_args; + +/* Application exit codes */ +#define EXIT_OK 0 +#define EXIT_FATAL 1 +#define EXIT_MILD 2 +#define EXIT_USAGE 3 + +/* Word-wrapping stream. */ +typedef struct wordwrap_file *WORDWRAP_FILE; + +WORDWRAP_FILE wordwrap_fdopen (int fd); +int wordwrap_close (WORDWRAP_FILE wf); +int wordwrap_flush (WORDWRAP_FILE wf); +int wordwrap_error (WORDWRAP_FILE wf); +int wordwrap_set_left_margin (WORDWRAP_FILE wf, unsigned left); +int wordwrap_next_left_margin (WORDWRAP_FILE wf, unsigned left); +int wordwrap_set_right_margin (WORDWRAP_FILE wf, unsigned right); +int wordwrap_write (WORDWRAP_FILE wf, char const *str, size_t len); +int wordwrap_puts (WORDWRAP_FILE wf, char const *str); +int wordwrap_putc (WORDWRAP_FILE wf, int c); +int wordwrap_para (WORDWRAP_FILE wf); +int wordwrap_vprintf (WORDWRAP_FILE wf, char const *fmt, va_list ap); +int wordwrap_printf (WORDWRAP_FILE wf, char const *fmt, ...); +int wordwrap_at_bol (WORDWRAP_FILE wf); +int wordwrap_at_eol (WORDWRAP_FILE wf); +void wordwrap_word_start (WORDWRAP_FILE wf); +void wordwrap_word_end (WORDWRAP_FILE wf); + diff --git a/tools/gdbmshell.c b/tools/gdbmshell.c new file mode 100644 index 0000000..06a0d0c --- /dev/null +++ b/tools/gdbmshell.c @@ -0,0 +1,3181 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 1990-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +#include "gdbmtool.h" +#include "gdbm.h" +#include "gram.h" + +#include <errno.h> +#include <ctype.h> +#include <signal.h> +#include <pwd.h> +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <termios.h> +#include <stdarg.h> +#ifdef HAVE_LOCALE_H +# include <locale.h> +#endif + +static GDBM_FILE gdbm_file = NULL; /* Database to operate upon */ +static datum key_data; /* Current key */ +static datum return_data; /* Current data */ + +/* Return values for handlers: */ +enum + { + GDBMSHELL_OK, /* Success */ + GDBMSHELL_GDBM_ERR, /* GDBM error */ + GDBMSHELL_SYNTAX, /* Syntax error (invalid argument etc) */ + GDBMSHELL_ERR, /* Other error */ + GDBMSHELL_CANCEL /* Operation canceled */ + }; + +static void +datum_free (datum *dp) +{ + free (dp->dptr); + dp->dptr = NULL; +} + + +int +gdbmshell_setopt (char *name, int opt, int val) +{ + if (gdbm_file) + { + if (gdbm_setopt (gdbm_file, opt, &val, sizeof (val)) == -1) + { + dberror (_("%s failed"), name); + return 1; + } + } + return 0; +} + +static void +closedb (void) +{ + if (gdbm_file) + { + gdbm_close (gdbm_file); + gdbm_file = NULL; + variable_unset ("fd"); + } + + datum_free (&key_data); + datum_free (&return_data); +} + +static int +opendb (char *dbname, int fd) +{ + int cache_size = 0; + int block_size = 0; + int flags; + int filemode; + GDBM_FILE db; + int n; + + switch (variable_get ("cachesize", VART_INT, (void**) &cache_size)) + { + case VAR_OK: + case VAR_ERR_NOTSET: + break; + default: + abort (); + } + switch (variable_get ("blocksize", VART_INT, (void**) &block_size)) + { + case VAR_OK: + case VAR_ERR_NOTSET: + break; + default: + abort (); + } + + if (variable_get ("open", VART_INT, (void**) &flags) != VAR_OK) + abort (); + + if (flags == GDBM_NEWDB) + { + if (interactive () && variable_is_true ("confirm") && + access (dbname, F_OK) == 0) + { + if (!getyn (_("database %s already exists; overwrite"), dbname)) + return GDBMSHELL_CANCEL; + } + } + + if (variable_get ("format", VART_INT, (void**) &n) != VAR_OK) + abort (); + + flags |= n; + + if (!variable_is_true ("lock")) + flags |= GDBM_NOLOCK; + if (!variable_is_true ("mmap")) + flags |= GDBM_NOMMAP; + if (variable_is_true ("sync")) + flags |= GDBM_SYNC; + + if (variable_get ("filemode", VART_INT, (void**) &filemode)) + abort (); + + if (fd > 0) + db = gdbm_fd_open (fd, dbname, block_size, flags | GDBM_CLOERROR, NULL); + else + { + char *name = tildexpand (dbname); + db = gdbm_open (name, block_size, flags, filemode, NULL); + free (name); + } + + if (db == NULL) + { + dberror (_("cannot open database %s"), dbname); + return GDBMSHELL_GDBM_ERR; + } + + if (cache_size && + gdbm_setopt (db, GDBM_CACHESIZE, &cache_size, sizeof (int)) == -1) + dberror (_("%s failed"), "GDBM_CACHESIZE"); + + if (variable_is_true ("coalesce")) + { + gdbmshell_setopt ("GDBM_SETCOALESCEBLKS", GDBM_SETCOALESCEBLKS, 1); + } + if (variable_is_true ("centfree")) + { + gdbmshell_setopt ("GDBM_SETCENTFREE", GDBM_SETCENTFREE, 1); + } + + if (gdbm_file) + gdbm_close (gdbm_file); + + gdbm_file = db; + return GDBMSHELL_OK; +} + +static int +checkdb (void) +{ + if (!gdbm_file) + { + char *filename; + int fd = -1; + variable_get ("filename", VART_STRING, (void**) &filename); + variable_get ("fd", VART_INT, (void**) &fd); + return opendb (filename, fd); + } + return GDBMSHELL_OK; +} + +static int +checkdb_begin (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv GDBM_ARG_UNUSED, + size_t *exp_count GDBM_ARG_UNUSED) +{ + return checkdb (); +} + +static size_t +bucket_print_lines (hash_bucket *bucket) +{ + return 10 + gdbm_file->header->bucket_elems + 3 + bucket->av_count; +} + +static void +format_key_start (FILE *fp, bucket_element *elt) +{ + int size = SMALL < elt->key_size ? SMALL : elt->key_size; + int i; + + for (i = 0; i < size; i++) + { + if (isprint (elt->key_start[i])) + fprintf (fp, " %c", elt->key_start[i]); + else + fprintf (fp, " %03o", elt->key_start[i]); + } +} + +static inline int +bucket_refcount (void) +{ + return 1 << (gdbm_file->header->dir_bits - gdbm_file->bucket->bucket_bits); +} + +static inline int +bucket_dir_start (void) +{ + int d = gdbm_file->header->dir_bits - gdbm_file->bucket->bucket_bits; + return (gdbm_file->bucket_dir >> d) << d; +} + +static inline int +bucket_dir_sibling (void) +{ + int d = gdbm_file->header->dir_bits - gdbm_file->bucket->bucket_bits; + return ((gdbm_file->bucket_dir >> d) ^ 1) << d; +} + +/* Debug procedure to print the contents of the current hash bucket. */ +static void +print_bucket (FILE *fp) +{ + int index; + int hash_prefix; + off_t adr = gdbm_file->dir[gdbm_file->bucket_dir]; + hash_bucket *bucket = gdbm_file->bucket; + int start = bucket_dir_start (); + int dircount = bucket_refcount (); + + hash_prefix = start << (GDBM_HASH_BITS - gdbm_file->header->dir_bits); + + fprintf (fp, "******* "); + fprintf (fp, _("Bucket #%d"), gdbm_file->bucket_dir); + fprintf (fp, " **********\n\n"); + fprintf (fp, + _("address = %lu\n" + "depth = %d\n" + "hash prefix = %08x\n" + "references = %u"), + (unsigned long) adr, + bucket->bucket_bits, + hash_prefix, + dircount); + if (dircount > 1) + { + fprintf (fp, " (%d-%d)", start, start + dircount - 1); + } + fprintf (fp, "\n"); + + fprintf (fp, + _("count = %d\n" + "load factor = %3d\n"), + bucket->count, + bucket->count * 100 / gdbm_file->header->bucket_elems); + + fprintf (fp, "%s", _("Hash Table:\n")); + fprintf (fp, + _(" # hash value key size data size data adr home key start\n")); + for (index = 0; index < gdbm_file->header->bucket_elems; index++) + { + fprintf (fp, " %4d %12x %11d %11d %11lu %4d", index, + bucket->h_table[index].hash_value, + bucket->h_table[index].key_size, + bucket->h_table[index].data_size, + (unsigned long) bucket->h_table[index].data_pointer, + bucket->h_table[index].hash_value % + gdbm_file->header->bucket_elems); + if (bucket->h_table[index].key_size) + { + fprintf (fp, " "); + format_key_start (fp, &bucket->h_table[index]); + } + fprintf (fp, "\n"); + } + + fprintf (fp, _("\nAvail count = %d\n"), bucket->av_count); + fprintf (fp, _("Address size\n")); + for (index = 0; index < bucket->av_count; index++) + fprintf (fp, "%11lu%9d\n", + (unsigned long) bucket->bucket_avail[index].av_adr, + bucket->bucket_avail[index].av_size); +} + +struct avail_list_counter +{ + size_t min_size; + size_t lines; +}; + +static int +avail_list_count (avail_block *avblk, off_t off, void *data) +{ + struct avail_list_counter *ctr = data; + + ctr->lines += avblk->count; + return ctr->lines > ctr->min_size; +} + +static size_t +_gdbm_avail_list_size (GDBM_FILE dbf, size_t min_size) +{ + struct avail_list_counter ctr; + ctr.min_size = 0; + ctr.lines = 0; + gdbm_avail_traverse (dbf, avail_list_count, &ctr); + return ctr.lines; +} + +static void +av_table_display (avail_elem *av_table, int count, FILE *fp) +{ + int i; + + for (i = 0; i < count; i++) + { + fprintf (fp, " %15d %10lu \n", + av_table[i].av_size, (unsigned long) av_table[i].av_adr); + } +} + +static int +avail_list_print (avail_block *avblk, off_t n, void *data) +{ + FILE *fp = data; + + fputc ('\n', fp); + if (n == 0)//FIXME + fprintf (fp, "%s", _("header block")); + else + fprintf (fp, _("block = %lu"), (unsigned long) n); + fprintf (fp, _("\nsize = %d\ncount = %d\n"), + avblk->size, avblk->count); + av_table_display (avblk->av_table, avblk->count, fp); + return 0; +} + +static int +_gdbm_print_avail_list (FILE *fp, GDBM_FILE dbf) +{ + int rc = gdbm_avail_traverse (dbf, avail_list_print, fp); + if (rc) + dberror (_("%s failed"), "gdbm_avail_traverse"); + return GDBMSHELL_GDBM_ERR; +} + +static void +_gdbm_print_bucket_cache (FILE *fp, GDBM_FILE dbf) +{ + if (dbf->cache_num) + { + int i; + cache_elem *elem; + + fprintf (fp, + _("Bucket Cache (size %zu/%zu):\n Index: Address Changed Data_Hash \n"), + dbf->cache_num, dbf->cache_size); + for (elem = dbf->cache_entry, i = 0; elem; elem = elem->ca_next, i++) + { + fprintf (fp, " %5d: %15lu %7s %x\n", + i, + (unsigned long) elem->ca_adr, + (elem->ca_changed ? _("True") : _("False")), + elem->ca_data.hash_val); + } + } + else + fprintf (fp, _("Bucket cache is empty.\n")); +} + +static int +trimnl (char *str) +{ + int len = strlen (str); + + if (str[len - 1] == '\n') + { + str[--len] = 0; + return 1; + } + return 0; +} + +static int +get_screen_lines (void) +{ +#ifdef TIOCGWINSZ + if (isatty (1)) + { + struct winsize ws; + + ws.ws_col = ws.ws_row = 0; + if ((ioctl(1, TIOCGWINSZ, (char *) &ws) < 0) || ws.ws_row == 0) + { + const char *lines = getenv ("LINES"); + if (lines) + ws.ws_row = strtol (lines, NULL, 10); + } + return ws.ws_row; + } +#else + const char *lines = getenv ("LINES"); + if (lines) + return strtol (lines, NULL, 10); +#endif + return -1; +} + +/* Open database */ +static int +open_handler (struct command_param *param, + struct command_environ *cenv GDBM_ARG_UNUSED) +{ + char *filename; + int fd = -1; + int rc; + + closedb (); + + if (param->argc == 1) + filename = PARAM_STRING (param, 0); + else + { + variable_get ("filename", VART_STRING, (void**) &filename); + variable_get ("fd", VART_INT, (void**) &fd); + } + + if ((rc = opendb (filename, fd)) == GDBMSHELL_OK) + { + variable_set ("filename", VART_STRING, filename); + if (fd >= 0) + variable_set ("fd", VART_INT, &fd); + else + variable_unset ("fd"); + } + return rc; +} + +/* Close database */ +static int +close_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv GDBM_ARG_UNUSED) +{ + if (!gdbm_file) + terror ("%s", _("nothing to close")); + else + closedb (); + return GDBMSHELL_OK; +} + +static char * +count_to_str (gdbm_count_t count, char *buf, size_t bufsize) +{ + char *p = buf + bufsize; + + *--p = 0; + if (count == 0) + *--p = '0'; + else + while (count) + { + if (p == buf) + return NULL; + *--p = '0' + count % 10; + count /= 10; + } + return p; +} + +/* count - count items in the database */ +static int +count_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv) +{ + gdbm_count_t count; + + if (gdbm_count (gdbm_file, &count)) + { + dberror (_("%s failed"), "gdbm_count"); + return GDBMSHELL_GDBM_ERR; + } + else + { + char buf[128]; + char *p = count_to_str (count, buf, sizeof buf); + + if (!p) + terror ("%s", _("count buffer overflow")); + else + fprintf (cenv->fp, + ngettext ("There is %s item in the database.\n", + "There are %s items in the database.\n", + count), + p); + } + return GDBMSHELL_OK; +} + +/* delete KEY - delete a key*/ +static int +delete_handler (struct command_param *param, struct command_environ *cenv) +{ + if (gdbm_delete (gdbm_file, PARAM_DATUM (param, 0)) != 0) + { + if (gdbm_errno == GDBM_ITEM_NOT_FOUND) + { + if (!gdbm_error_is_masked (gdbm_errno)) + terror ("%s", _("No such item found")); + } + else + dberror ("%s", _("Can't delete")); + return GDBMSHELL_GDBM_ERR; + } + return GDBMSHELL_OK; +} + +/* fetch KEY - fetch a record by its key */ +static int +fetch_handler (struct command_param *param, struct command_environ *cenv) +{ + return_data = gdbm_fetch (gdbm_file, PARAM_DATUM (param, 0)); + if (return_data.dptr != NULL) + { + datum_format (cenv->fp, &return_data, dsdef[DS_CONTENT]); + fputc ('\n', cenv->fp); + datum_free (&return_data); + return GDBMSHELL_OK; + } + else if (gdbm_errno == GDBM_ITEM_NOT_FOUND) + { + if (!gdbm_error_is_masked (gdbm_errno)) + terror ("%s", _("No such item found")); + } + else + dberror ("%s", _("Can't fetch data")); + return GDBMSHELL_GDBM_ERR; +} + +/* store KEY DATA - store data */ +static int +store_handler (struct command_param *param, + struct command_environ *cenv GDBM_ARG_UNUSED) +{ + if (gdbm_store (gdbm_file, + PARAM_DATUM (param, 0), PARAM_DATUM (param, 1), + GDBM_REPLACE) != 0) + { + dberror ("%s", _("Item not inserted")); + return GDBMSHELL_GDBM_ERR; + } + return GDBMSHELL_OK; +} + +/* first - begin iteration */ + +static int +firstkey_handler (struct command_param *param, struct command_environ *cenv) +{ + datum_free (&key_data); + key_data = gdbm_firstkey (gdbm_file); + if (key_data.dptr != NULL) + { + datum_format (cenv->fp, &key_data, dsdef[DS_KEY]); + fputc ('\n', cenv->fp); + + return_data = gdbm_fetch (gdbm_file, key_data); + datum_format (cenv->fp, &return_data, dsdef[DS_CONTENT]); + fputc ('\n', cenv->fp); + + datum_free (&return_data); + return GDBMSHELL_OK; + } + else if (gdbm_errno == GDBM_ITEM_NOT_FOUND) + { + if (!gdbm_error_is_masked (gdbm_errno)) + fprintf (cenv->fp, _("No such item found.\n")); + } + else + dberror ("%s", _("Can't find first key")); + return GDBMSHELL_GDBM_ERR; +} + +/* next [KEY] - next key */ +static int +nextkey_handler (struct command_param *param, struct command_environ *cenv) +{ + if (param->argc == 1) + { + datum_free (&key_data); + key_data.dptr = emalloc (PARAM_DATUM (param, 0).dsize); + key_data.dsize = PARAM_DATUM (param, 0).dsize; + memcpy (key_data.dptr, PARAM_DATUM (param, 0).dptr, key_data.dsize); + } + return_data = gdbm_nextkey (gdbm_file, key_data); + if (return_data.dptr != NULL) + { + datum_free (&key_data); + key_data = return_data; + datum_format (cenv->fp, &key_data, dsdef[DS_KEY]); + fputc ('\n', cenv->fp); + + return_data = gdbm_fetch (gdbm_file, key_data); + datum_format (cenv->fp, &return_data, dsdef[DS_CONTENT]); + fputc ('\n', cenv->fp); + + datum_free (&return_data); + return GDBMSHELL_OK; + } + else if (gdbm_errno == GDBM_ITEM_NOT_FOUND) + { + if (!gdbm_error_is_masked (gdbm_errno)) + terror ("%s", _("No such item found")); + datum_free (&key_data); + } + else + dberror ("%s", _("Can't find next key")); + return GDBMSHELL_GDBM_ERR; +} + +/* reorganize */ +static int +reorganize_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv) +{ + if (gdbm_reorganize (gdbm_file)) + { + dberror ("%s", _("Reorganization failed")); + return GDBMSHELL_GDBM_ERR; + } + else + fprintf (cenv->fp, "%s\n", _("Reorganization succeeded.")); + return GDBMSHELL_OK; +} + +static void +err_printer (void *data GDBM_ARG_UNUSED, char const *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + fprintf (stderr, "\n"); +} + +/* recover summary verbose backup max-failed-keys=N max-failed-buckets=N max-failures=N */ +static int +recover_handler (struct command_param *param, struct command_environ *cenv) +{ + gdbm_recovery rcvr; + int flags = 0; + int rc; + int i; + char *p; + int summary = 0; + + for (i = 0; i < param->argc; i++) + { + char *arg = PARAM_STRING (param, i); + if (strcmp (arg, "verbose") == 0) + { + rcvr.errfun = err_printer; + flags |= GDBM_RCVR_ERRFUN; + } + else if (strcmp (arg, "force") == 0) + { + flags |= GDBM_RCVR_FORCE; + } + else if (strcmp (arg, "summary") == 0) + { + summary = 1; + } + else if (strcmp (arg, "backup") == 0) + { + flags |= GDBM_RCVR_BACKUP; + } + else if (strncmp (arg, "max-failures=", 13) == 0) + { + rcvr.max_failures = strtoul (arg + 13, &p, 10); + if (*p) + { + terror (_("not a number (stopped near %s)"), p); + return 1; + } + flags |= GDBM_RCVR_MAX_FAILURES; + } + else if (strncmp (arg, "max-failed-keys=", 16) == 0) + { + rcvr.max_failed_keys = strtoul (arg + 16, &p, 10); + if (*p) + { + terror (_("not a number (stopped near %s)"), p); + return 1; + } + flags |= GDBM_RCVR_MAX_FAILED_KEYS; + } + else if (strncmp (arg, "max-failed-buckets=", 19) == 0) + { + rcvr.max_failures = strtoul (arg + 19, &p, 10); + if (*p) + { + terror (_("not a number (stopped near %s)"), p); + return 1; + } + flags |= GDBM_RCVR_MAX_FAILED_BUCKETS; + } + else + { + terror (_("unrecognized argument: %s"), arg); + return GDBMSHELL_SYNTAX; + } + } + + rc = gdbm_recover (gdbm_file, &rcvr, flags); + + if (rc == 0) + { + fprintf (cenv->fp, _("Recovery succeeded.\n")); + if (summary) + { + fprintf (cenv->fp, + _("Keys recovered: %lu, failed: %lu, duplicate: %lu\n"), + (unsigned long) rcvr.recovered_keys, + (unsigned long) rcvr.failed_keys, + (unsigned long) rcvr.duplicate_keys); + fprintf (cenv->fp, + _("Buckets recovered: %lu, failed: %lu\n"), + (unsigned long) rcvr.recovered_buckets, + (unsigned long) rcvr.failed_buckets); + } + + if (rcvr.backup_name) + { + fprintf (cenv->fp, + _("Original database preserved in file %s"), + rcvr.backup_name); + free (rcvr.backup_name); + } + fputc ('\n', cenv->fp); + } + else + { + dberror ("%s", _("Recovery failed")); + rc = GDBMSHELL_GDBM_ERR; + } + return rc; +} + +/* avail - print available list */ +static int +avail_begin (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv GDBM_ARG_UNUSED, + size_t *exp_count) +{ + int rc = checkdb (); + if (rc == GDBMSHELL_OK) + { + if (exp_count) + *exp_count = _gdbm_avail_list_size (gdbm_file, SIZE_T_MAX); + } + return rc; +} + +static int +avail_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv) +{ + return _gdbm_print_avail_list (cenv->fp, gdbm_file); +} + +/* print current bucket */ +static int +print_current_bucket_begin (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv GDBM_ARG_UNUSED, + size_t *exp_count) +{ + int rc = checkdb (); + + if (rc == GDBMSHELL_OK) + { + if (exp_count) + *exp_count = gdbm_file->bucket + ? bucket_print_lines (gdbm_file->bucket) + 3 + : 1; + } + return rc; +} + +static int +print_current_bucket_handler (struct command_param *param, + struct command_environ *cenv) +{ + if (!gdbm_file->bucket) + fprintf (cenv->fp, _("no current bucket\n")); + else + print_bucket (cenv->fp); + return GDBMSHELL_OK; +} + +int +getnum (int *pnum, char *arg, char **endp) +{ + char *p; + unsigned long x = strtoul (arg, &p, 10); + if (*p && !isspace (*p)) + { + terror (_("not a number (stopped near %s)"), p); + return 1; + } + while (*p && isspace (*p)) + p++; + if (endp) + *endp = p; + else if (*p) + { + terror (_("not a number (stopped near %s)"), p); + return 1; + } + *pnum = x; + return 0; +} + +/* bucket NUM - print a bucket and set it as a current one. + Uses print_current_bucket_handler */ +static int +print_bucket_begin (struct command_param *param, + struct command_environ *cenv GDBM_ARG_UNUSED, + size_t *exp_count) +{ + int rc; + int n = -1; + + if ((rc = checkdb ()) != GDBMSHELL_OK) + return rc; + + if (param->argc == 1) + { + if (getnum (&n, PARAM_STRING (param, 0), NULL)) + return GDBMSHELL_SYNTAX; + + if (n >= GDBM_DIR_COUNT (gdbm_file)) + { + terror (_("bucket number out of range (0..%lu)"), + GDBM_DIR_COUNT (gdbm_file)); + return GDBMSHELL_SYNTAX; + } + } + else if (!gdbm_file->bucket) + n = 0; + + if (n != -1) + { + if (_gdbm_get_bucket (gdbm_file, n)) + { + dberror (_("%s failed"), "_gdbm_get_bucket"); + return GDBMSHELL_GDBM_ERR; + } + } + + if (exp_count) + *exp_count = bucket_print_lines (gdbm_file->bucket) + 3; + return GDBMSHELL_OK; +} + +static int +print_sibling_bucket_begin (struct command_param *param, + struct command_environ *cenv GDBM_ARG_UNUSED, + size_t *exp_count) +{ + int rc, n0, n, bucket_bits; + + if ((rc = checkdb ()) != GDBMSHELL_OK) + return rc; + if (!gdbm_file->bucket) + { + fprintf (stderr, _("no current bucket\n")); + return GDBMSHELL_ERR; + } + + n0 = gdbm_file->bucket_dir; + bucket_bits = gdbm_file->bucket->bucket_bits; + n = bucket_dir_sibling (); + + if (n > GDBM_DIR_COUNT (gdbm_file)) + { + fprintf (stderr, _("no sibling\n")); + return GDBMSHELL_ERR; + } + + if (_gdbm_get_bucket (gdbm_file, n)) + { + dberror (_("%s failed"), "_gdbm_get_bucket"); + return GDBMSHELL_GDBM_ERR; + } + + if (bucket_bits != gdbm_file->bucket->bucket_bits) + { + fprintf (stderr, _("no sibling\n")); + if (_gdbm_get_bucket (gdbm_file, n0)) + { + dberror (_("%s failed"), "_gdbm_get_bucket"); + return GDBMSHELL_GDBM_ERR; + } + return GDBMSHELL_ERR; + } + + if (exp_count) + *exp_count = bucket_print_lines (gdbm_file->bucket) + 3; + return GDBMSHELL_OK; +} + +/* dir - print hash directory */ +static int +print_dir_begin (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv GDBM_ARG_UNUSED, + size_t *exp_count) +{ + int rc; + + if ((rc = checkdb ()) == GDBMSHELL_OK) + { + if (exp_count) + *exp_count = GDBM_DIR_COUNT (gdbm_file) + 3; + } + return rc; +} + +static size_t +bucket_count (void) +{ + size_t count = 0; + + if (gdbm_bucket_count (gdbm_file, &count)) + { + dberror ("%s", "gdbm_bucket_count"); + } + return count; +} + +static int +print_dir_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv) +{ + int i; + + fprintf (cenv->fp, _("Hash table directory.\n")); + fprintf (cenv->fp, _(" Size = %d. Capacity = %lu. Bits = %d, Buckets = %zu.\n\n"), + gdbm_file->header->dir_size, + GDBM_DIR_COUNT (gdbm_file), + gdbm_file->header->dir_bits, + bucket_count ()); + + fprintf (cenv->fp, "#%11s %8s %s\n", + _("Index"), _("Hash Pfx"), _("Bucket address")); + for (i = 0; i < GDBM_DIR_COUNT (gdbm_file); i++) + fprintf (cenv->fp, " %10d: %08x %12lu\n", + i, + i << (GDBM_HASH_BITS - gdbm_file->header->dir_bits), + (unsigned long) gdbm_file->dir[i]); + + return GDBMSHELL_OK; +} + +/* header - print file handler */ +static int +print_header_begin (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv GDBM_ARG_UNUSED, + size_t *exp_count) +{ + int rc; + int n; + + if ((rc = checkdb ()) != GDBMSHELL_OK) + return rc; + + switch (gdbm_file->header->header_magic) + { + case GDBM_OMAGIC: + case GDBM_MAGIC: + n = 14; + break; + + case GDBM_NUMSYNC_MAGIC: + n = 19; + break; + + default: + abort (); + } + + if (exp_count) + *exp_count = n; + + return GDBMSHELL_OK; +} + +static int +print_header_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv) +{ + FILE *fp = cenv->fp; + char const *type; + + switch (gdbm_file->header->header_magic) + { + case GDBM_OMAGIC: + type = "GDBM (old)"; + break; + + case GDBM_MAGIC: + type = "GDBM (standard)"; + break; + + case GDBM_NUMSYNC_MAGIC: + type = "GDBM (numsync)"; + break; + + default: + abort (); + } + + fprintf (fp, _("\nFile Header: \n\n")); + fprintf (fp, _(" type = %s\n"), type); + fprintf (fp, _(" directory start = %lu\n"), + (unsigned long) gdbm_file->header->dir); + fprintf (fp, _(" directory size = %d\n"), gdbm_file->header->dir_size); + fprintf (fp, _(" directory depth = %d\n"), gdbm_file->header->dir_bits); + fprintf (fp, _(" block size = %d\n"), gdbm_file->header->block_size); + fprintf (fp, _(" bucket elems = %d\n"), gdbm_file->header->bucket_elems); + fprintf (fp, _(" bucket size = %d\n"), gdbm_file->header->bucket_size); + fprintf (fp, _(" header magic = %x\n"), gdbm_file->header->header_magic); + fprintf (fp, _(" next block = %lu\n"), + (unsigned long) gdbm_file->header->next_block); + + fprintf (fp, _(" avail size = %d\n"), gdbm_file->avail->size); + fprintf (fp, _(" avail count = %d\n"), gdbm_file->avail->count); + fprintf (fp, _(" avail next block= %lu\n"), + (unsigned long) gdbm_file->avail->next_block); + + if (gdbm_file->xheader) + { + fprintf (fp, _("\nExtended Header: \n\n")); + fprintf (fp, _(" version = %d\n"), gdbm_file->xheader->version); + fprintf (fp, _(" numsync = %u\n"), gdbm_file->xheader->numsync); + } + + return GDBMSHELL_OK; +} + +static int +sync_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv GDBM_ARG_UNUSED) +{ + if (gdbm_sync (gdbm_file)) + { + dberror ("%s", "gdbm_sync"); + return GDBMSHELL_GDBM_ERR; + } + return GDBMSHELL_OK; +} + +static int +upgrade_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv GDBM_ARG_UNUSED) +{ + if (gdbm_convert (gdbm_file, GDBM_NUMSYNC)) + { + dberror ("%s", "gdbm_convert"); + return GDBMSHELL_GDBM_ERR; + } + return GDBMSHELL_OK; +} + +static int +downgrade_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv GDBM_ARG_UNUSED) +{ + if (gdbm_convert (gdbm_file, 0)) + { + dberror ("%s", "gdbm_convert"); + return GDBMSHELL_GDBM_ERR; + } + return GDBMSHELL_OK; +} + +struct snapshot_status_info +{ + char const *code; + char const *descr; + void (*fn) (FILE *, char const *, char const *); +}; + +#define MODBUFSIZE 10 + +static char * +decode_mode (mode_t mode, char *buf) +{ + char *s = buf; + *s++ = mode & S_IRUSR ? 'r' : '-'; + *s++ = mode & S_IWUSR ? 'w' : '-'; + *s++ = (mode & S_ISUID + ? (mode & S_IXUSR ? 's' : 'S') + : (mode & S_IXUSR ? 'x' : '-')); + *s++ = mode & S_IRGRP ? 'r' : '-'; + *s++ = mode & S_IWGRP ? 'w' : '-'; + *s++ = (mode & S_ISGID + ? (mode & S_IXGRP ? 's' : 'S') + : (mode & S_IXGRP ? 'x' : '-')); + *s++ = mode & S_IROTH ? 'r' : '-'; + *s++ = mode & S_IWOTH ? 'w' : '-'; + *s++ = (mode & S_ISVTX + ? (mode & S_IXOTH ? 't' : 'T') + : (mode & S_IXOTH ? 'x' : '-')); + *s = '\0'; + return buf; +} + +struct error_entry +{ + const char *msg; + int gdbm_err; + int sys_err; +}; + +static void +error_push (struct error_entry *stk, int *tos, int maxstk, char const *text, + int gdbm_err, int sys_err) +{ + if (*tos == maxstk) + abort (); + stk += *tos; + ++ *tos; + stk->msg = text; + stk->gdbm_err = gdbm_err; + stk->sys_err = sys_err; +} + +static void +print_snapshot (char const *snapname, FILE *fp) +{ + struct stat st; + char buf[MODBUFSIZE]; + + if (stat (snapname, &st) == 0) + { +# define MAXERRS 4 + struct error_entry errs[MAXERRS]; + int errn = 0; + int i; + + switch (st.st_mode & ~S_IFREG) + { + case S_IRUSR: + case S_IWUSR: + break; + + default: + error_push (errs, &errn, ARRAY_SIZE (errs), N_("bad file mode"), + 0, 0); + } + + fprintf (fp, "%s: ", snapname); + fprintf (fp, "%03o %s ", st.st_mode & 0777, + decode_mode (st.st_mode, buf)); +#if HAVE_STRUCT_STAT_ST_MTIM + fprintf (fp, "%ld.%09ld", st.st_mtim.tv_sec, st.st_mtim.tv_nsec); +#else + fprintf (fp, "%ld [%s]", st.st_mtime, _("insufficient precision")); +#endif + if (S_ISREG (st.st_mode)) + { + GDBM_FILE dbf; + + dbf = gdbm_open (snapname, 0, GDBM_READER, 0, NULL); + if (dbf) + { + if (dbf->xheader) + fprintf (fp, " %u", dbf->xheader->numsync); + else + /* TRANSLATORS: Stands for "Not Available". */ + fprintf (fp, " %s", _("N/A")); + } + else if (gdbm_check_syserr (gdbm_errno)) + { + if (errno == EACCES) + fprintf (fp, " ?"); + else + error_push (errs, &errn, ARRAY_SIZE (errs), + N_("can't open database"), + gdbm_errno, errno); + } + else + error_push (errs, &errn, ARRAY_SIZE (errs), + N_("can't open database"), + gdbm_errno, 0); + } + else + error_push (errs, &errn, ARRAY_SIZE (errs), + N_("not a regular file"), + 0, 0); + fputc ('\n', fp); + for (i = 0; i < errn; i++) + { + fprintf (fp, "%s: %s: %s", snapname, _("ERROR"), gettext (errs[i].msg)); + if (errs[i].gdbm_err) + fprintf (fp, ": %s", gdbm_strerror (errs[i].gdbm_err)); + if (errs[i].sys_err) + fprintf (fp, ": %s", strerror (errs[i].sys_err)); + fputc ('\n', fp); + } + } + else + { + fprintf (fp, _("%s: ERROR: can't stat: %s"), snapname, strerror (errno)); + return; + } +} + +static void +snapshot_print_fn (FILE *fp, char const *sa, char const *sb) +{ + print_snapshot (sa, fp); + print_snapshot (sb, fp); +} + +static void +snapshot_err_fn (FILE *fp, char const *sa, char const *sb) +{ + switch (errno) + { + default: + print_snapshot (sa, fp); + print_snapshot (sb, fp); + break; + + case EINVAL: + fprintf (fp, "%s.\n", + _("Invalid arguments in call to gdbm_latest_snapshot")); + break; + + case ENOSYS: + fprintf (fp, "%s.\n", + _("Function is not implemented: GDBM is built without crash-tolerance support")); + break; + } +} + +static struct snapshot_status_info snapshot_status_info[] = { + [GDBM_SNAPSHOT_OK] = { + "GDBM_SNAPSHOT_OK", + N_("Selected the most recent snapshot") + }, + [GDBM_SNAPSHOT_BAD] = { + "GDBM_SNAPSHOT_BAD", + N_("Neither snapshot is readable"), + snapshot_print_fn + }, + [GDBM_SNAPSHOT_ERR] = { + "GDBM_SNAPSHOT_ERR", + N_("Error selecting snapshot"), + snapshot_err_fn + }, + [GDBM_SNAPSHOT_SAME] = { + "GDBM_SNAPSHOT_SAME", + N_("Snapshot modes and dates are the same"), + snapshot_print_fn + }, + [GDBM_SNAPSHOT_SUSPICIOUS] = { + "GDBM_SNAPSHOT_SUSPICIOUS", + N_("Snapshot sync counters differ by more than 1"), + snapshot_print_fn + } +}; + +static int +snapshot_handler (struct command_param *param, struct command_environ *cenv) +{ + char *sa = tildexpand (PARAM_STRING (param, 0)); + char *sb = tildexpand (PARAM_STRING (param, 1)); + char const *sel; + int rc = gdbm_latest_snapshot (sa, sb, &sel); + + if (rc >= 0 && rc < ARRAY_SIZE (snapshot_status_info)) + { + fprintf (cenv->fp, + "%s: %s.\n", + snapshot_status_info[rc].code, + gettext (snapshot_status_info[rc].descr)); + if (snapshot_status_info[rc].fn) + snapshot_status_info[rc].fn (cenv->fp, sa, sb); + if (rc == GDBM_SNAPSHOT_OK) + print_snapshot (sel, cenv->fp); + } + else + { + terror (_("unexpected error code: %d"), rc); + return GDBMSHELL_ERR; + } + return GDBMSHELL_OK; +} + + +/* hash KEY - hash the key */ +static int +hash_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv) +{ + if (gdbm_file) + { + int hashval, bucket, off; + _gdbm_hash_key (gdbm_file, PARAM_DATUM (param, 0), + &hashval, &bucket, &off); + fprintf (cenv->fp, _("hash value = %x, bucket #%u, slot %u"), + hashval, + hashval >> (GDBM_HASH_BITS - gdbm_file->header->dir_bits), + hashval % gdbm_file->header->bucket_elems); + } + else + fprintf (cenv->fp, _("hash value = %x"), + _gdbm_hash (PARAM_DATUM (param, 0))); + fprintf (cenv->fp, ".\n"); + return GDBMSHELL_OK; +} + +/* cache - print the bucket cache */ +static int +print_cache_begin (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv GDBM_ARG_UNUSED, + size_t *exp_count) +{ + int rc; + + if ((rc = checkdb ()) == GDBMSHELL_OK) + { + if (exp_count) + *exp_count = gdbm_file->cache_num + 1; + } + return rc; +} + +static int +print_cache_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv) +{ + _gdbm_print_bucket_cache (cenv->fp, gdbm_file); + return GDBMSHELL_OK; +} + +/* version - print GDBM version */ +static int +print_version_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv) +{ + fprintf (cenv->fp, "%s\n", gdbm_version); + return GDBMSHELL_OK; +} + +/* list - List all entries */ +static int +list_begin (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv GDBM_ARG_UNUSED, + size_t *exp_count) +{ + int rc; + + if ((rc = checkdb ()) == GDBMSHELL_OK) + { + if (param->argc) + { + if (strcmp (PARAM_STRING (param, 0), "bucket")) + { + fprintf (stderr, _("unrecognized parameter: %s\n"), + PARAM_STRING (param, 0)); + return GDBMSHELL_ERR; + } + + if (!gdbm_file->bucket) + { + fprintf (stderr, "%s", _("select bucket first\n")); + return GDBMSHELL_ERR; + } + + if (exp_count) + { + int n = 0, i; + + for (i = 0; i < gdbm_file->bucket->count; i++) + { + if (gdbm_file->bucket->h_table[i].hash_value != -1) + n++; + } + *exp_count = n; + } + } + else + { + if (exp_count) + { + gdbm_count_t count; + + if (gdbm_count (gdbm_file, &count)) + *exp_count = 0; + else if (count > SIZE_T_MAX) + *exp_count = SIZE_T_MAX; + else + *exp_count = count; + } + } + } + + return rc; +} + +static int +list_bucket_keys (struct command_environ *cenv) +{ + int rc = GDBMSHELL_OK; + int i; + hash_bucket *bucket = gdbm_file->bucket; + + for (i = 0; i < bucket->count; i++) + { + if (bucket->h_table[i].hash_value != -1) + { + datum key, content; + + key.dptr = _gdbm_read_entry (gdbm_file, i); + if (!key.dptr) + { + dberror (_("error reading entry %d"),i); + rc = GDBMSHELL_GDBM_ERR; + } + key.dsize = bucket->h_table[i].key_size; + + content = gdbm_fetch (gdbm_file, key); + if (!content.dptr) + { + dberror ("%s", "gdbm_fetch"); + terror ("%s", _("the key was:")); + datum_format (stderr, &key, dsdef[DS_KEY]); + rc = GDBMSHELL_GDBM_ERR; + } + else + { + datum_format (cenv->fp, &key, dsdef[DS_KEY]); + fputc (' ', cenv->fp); + datum_format (cenv->fp, &content, dsdef[DS_CONTENT]); + fputc ('\n', cenv->fp); + } + free (content.dptr); + } + } + return rc; +} + +static int +list_all_keys (struct command_environ *cenv) +{ + datum key; + datum data; + int rc = GDBMSHELL_OK; + + key = gdbm_firstkey (gdbm_file); + if (!key.dptr && gdbm_errno != GDBM_ITEM_NOT_FOUND) + { + dberror ("%s", "gdbm_firstkey"); + return GDBMSHELL_GDBM_ERR; + } + while (key.dptr) + { + datum nextkey; + + data = gdbm_fetch (gdbm_file, key); + if (!data.dptr) + { + dberror ("%s", "gdbm_fetch"); + terror ("%s", _("the key was:")); + datum_format (stderr, &key, dsdef[DS_KEY]); + rc = GDBMSHELL_GDBM_ERR; + } + else + { + datum_format (cenv->fp, &key, dsdef[DS_KEY]); + fputc (' ', cenv->fp); + datum_format (cenv->fp, &data, dsdef[DS_CONTENT]); + fputc ('\n', cenv->fp); + free (data.dptr); + } + nextkey = gdbm_nextkey (gdbm_file, key); + free (key.dptr); + key = nextkey; + } + if (gdbm_errno != GDBM_ITEM_NOT_FOUND) + { + dberror ("%s", "gdbm_nextkey"); + rc = GDBMSHELL_GDBM_ERR; + } + return rc; +} + +static int +list_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv) +{ + if (param->argc) + return list_bucket_keys (cenv); + else + return list_all_keys (cenv); +} + +/* quit - quit the program */ +static int +quit_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv GDBM_ARG_UNUSED) +{ + input_context_drain (); + if (input_context_push (instream_null_create ())) + exit (EXIT_FATAL); + return GDBMSHELL_OK; +} + +/* export FILE [truncate] - export to a flat file format */ +static int +export_handler (struct command_param *param, + struct command_environ *cenv GDBM_ARG_UNUSED) +{ + int format = GDBM_DUMP_FMT_ASCII; + int flags = GDBM_WRCREAT; + int i; + int filemode; + int rc = GDBMSHELL_OK; + + for (i = 1; i < param->argc; i++) + { + if (strcmp (PARAM_STRING (param, i), "truncate") == 0) + flags = GDBM_NEWDB; + else if (strcmp (PARAM_STRING (param, i), "binary") == 0) + format = GDBM_DUMP_FMT_BINARY; + else if (strcmp (PARAM_STRING (param, i), "ascii") == 0) + format = GDBM_DUMP_FMT_ASCII; + else + { + terror (_("unrecognized argument: %s"), PARAM_STRING (param, i)); + return GDBMSHELL_SYNTAX; + } + } + + if (variable_get ("filemode", VART_INT, (void**) &filemode)) + abort (); + if (gdbm_dump (gdbm_file, PARAM_STRING (param, 0), format, flags, filemode)) + { + dberror ("%s", _("error dumping database")); + rc = GDBMSHELL_GDBM_ERR; + } + return rc; +} + +/* import FILE [replace] [nometa] - import from a flat file */ +static int +import_handler (struct command_param *param, + struct command_environ *cenv GDBM_ARG_UNUSED) +{ + int flag = GDBM_INSERT; + unsigned long err_line; + int meta_mask = 0; + int i; + int rc = GDBMSHELL_OK; + char *file_name; + + for (i = 1; i < param->argc; i++) + { + if (strcmp (PARAM_STRING (param, i), "replace") == 0) + flag = GDBM_REPLACE; + else if (strcmp (PARAM_STRING (param, i), "nometa") == 0) + meta_mask = GDBM_META_MASK_MODE | GDBM_META_MASK_OWNER; + else + { + terror (_("unrecognized argument: %s"), + PARAM_STRING (param, i)); + return GDBMSHELL_SYNTAX; + } + } + + rc = gdbm_load (&gdbm_file, PARAM_STRING (param, 0), flag, + meta_mask, &err_line); + if (rc && gdbm_errno == GDBM_NO_DBNAME) + { + char *save_mode; + + variable_get ("open", VART_STRING, (void**) &save_mode); + save_mode = estrdup (save_mode); + variable_set ("open", VART_STRING, "newdb"); + + rc = checkdb (); + variable_set ("open", VART_STRING, save_mode); + free (save_mode); + + if (rc) + return rc; + + rc = gdbm_load (&gdbm_file, PARAM_STRING (param, 0), flag, + meta_mask, &err_line); + } + if (rc) + { + switch (gdbm_errno) + { + case GDBM_ERR_FILE_OWNER: + case GDBM_ERR_FILE_MODE: + dberror ("%s", _("error restoring metadata")); + break; + + default: + if (err_line) + dberror ("%s:%lu", PARAM_STRING (param, 0), err_line); + else + dberror (_("cannot load from %s"), PARAM_STRING (param, 0)); + } + return GDBMSHELL_GDBM_ERR; + } + + if (gdbm_setopt (gdbm_file, GDBM_GETDBNAME, &file_name, sizeof (file_name))) + { + dberror ("%s", "GDBM_GETDBNAME"); + rc = GDBMSHELL_GDBM_ERR; + } + else + { + variable_set ("filename", VART_STRING, file_name); + variable_unset ("fd"); + } + return rc; +} + +/* status - print current program status */ +static int +status_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv) +{ + char *file_name; + + variable_get ("filename", VART_STRING, (void**) &file_name); + fprintf (cenv->fp, _("Database file: %s\n"), file_name); + if (gdbm_file) + fprintf (cenv->fp, "%s\n", _("Database is open")); + else + fprintf (cenv->fp, "%s\n", _("Database is not open")); + dsprint (cenv->fp, DS_KEY, dsdef[DS_KEY]); + dsprint (cenv->fp, DS_CONTENT, dsdef[DS_CONTENT]); + return GDBMSHELL_OK; +} + +#if GDBM_DEBUG_ENABLE +static int +debug_flag_printer (void *data, int flag, char const *tok) +{ + FILE *fp = data; + fprintf (fp, " %s", tok); + return 0; +} +#endif + +static int +debug_handler (struct command_param *param, struct command_environ *cenv) +{ +#if GDBM_DEBUG_ENABLE + if (param->vararg) + { + struct gdbmarg *arg; + int i; + + for (arg = param->vararg, i = 0; arg; arg = arg->next, i++) + { + if (arg->type == GDBM_ARG_STRING) + { + int flag; + int negate; + char const *tok = arg->v.string; + + if (tok[0] == '-') + { + ++tok; + negate = 1; + } + else if (tok[0] == '+') + { + ++tok; + negate = 0; + } + else + negate = 0; + + flag = gdbm_debug_token (tok); + if (flag) + { + if (negate) + gdbm_debug_flags &= ~flag; + else + gdbm_debug_flags |= flag; + } + else + terror (_("unknown debug flag: %s"), tok); + } + else + terror (_("invalid type of argument %d"), i); + } + } + else + { + fprintf (cenv->fp, _("Debug flags:")); + if (gdbm_debug_flags) + { + gdbm_debug_parse_state (debug_flag_printer, cenv->fp); + } + else + fprintf (cenv->fp, " %s", _("none")); + fputc ('\n', cenv->fp); + } +#else + terror ("%s", _("compiled without debug support")); +#endif + return GDBMSHELL_OK; +} + +static int +shell_handler (struct command_param *param, + struct command_environ *cenv GDBM_ARG_UNUSED) +{ + char *argv[4]; + pid_t pid, rc; + int status; + + argv[0] = getenv ("$SHELL"); + if (!argv[0]) + argv[0] = "/bin/sh"; + if (param->vararg) + { + argv[1] = "-c"; + argv[2] = param->vararg->v.string; + argv[3] = NULL; + } + else + { + argv[1] = NULL; + } + + pid = fork (); + if (pid == -1) + { + terror ("fork: %s", strerror (errno)); + return GDBMSHELL_ERR; + } + if (pid == 0) + { + execv (argv[0], argv); + perror (argv[0]); + _exit (127); + } + + rc = waitpid (pid, &status, 0); + if (rc == -1) + { + terror ("waitpid: %s", strerror (errno)); + rc = GDBMSHELL_ERR; + } + else if (!interactive ()) + { + if (WIFEXITED (status)) + { + if (WEXITSTATUS (status) != 0) + terror (_("command failed with status %d"), WEXITSTATUS (status)); + } + else if (WIFSIGNALED (status)) + terror (_("command terminated on signal %d"), WTERMSIG (status)); + } + return rc; +} + +static int +source_handler (struct command_param *param, + struct command_environ *cenv GDBM_ARG_UNUSED) +{ + char *fname = tildexpand (PARAM_STRING (param, 0)); + instream_t istr = instream_file_create (fname); + free (fname); + if (istr && input_context_push (istr) == 0) + { + yyparse (); + input_context_drain (); + yylex_destroy (); + } + return GDBMSHELL_OK; +} + +static int +perror_handler (struct command_param *param, struct command_environ *cenv) +{ + int n; + + if (param->argc) + { + if (getnum (&n, PARAM_STRING (param, 0), NULL)) + return GDBMSHELL_SYNTAX; + } + else if ((n = checkdb ()) != GDBMSHELL_OK) + { + return n; + } + else + { + n = gdbm_last_errno (gdbm_file); + } + fprintf (cenv->fp, "GDBM error code %d: \"%s\"\n", n, gdbm_strerror (n)); + if (gdbm_check_syserr (n)) + { + if (param->argc) + fprintf (cenv->fp, "Examine errno.\n"); + else + fprintf (cenv->fp, "System error code %d: \"%s\"\n", + gdbm_last_syserr (gdbm_file), + strerror (gdbm_last_syserr (gdbm_file))); + } + return GDBMSHELL_OK; +} + +struct history_param +{ + int from; + int count; +}; + +static int +input_history_begin (struct command_param *param, + struct command_environ *cenv GDBM_ARG_UNUSED, + size_t *exp_count) +{ + struct history_param *p; + int hlen = input_history_size (); + int from = 0, count = hlen; + + if (hlen == -1) + { + /* TRANSLATORS: %s is the stream name */ + terror (_("input history is not available for %s input stream"), + input_stream_name ()); + return GDBMSHELL_OK; + } + + switch (param->argc) + { + case 1: + if (getnum (&count, param->argv[0]->v.string, NULL)) + return 1; + if (count > hlen) + count = hlen; + else + from = hlen - count; + break; + + case 2: + if (getnum (&from, param->argv[0]->v.string, NULL)) + return 1; + if (from) + --from; + if (getnum (&count, param->argv[1]->v.string, NULL)) + return GDBMSHELL_OK; + + if (count > hlen) + count = hlen; + } + p = emalloc (sizeof *p); + p->from = from; + p->count = count; + cenv->data = p; + if (exp_count) + *exp_count = count; + return GDBMSHELL_OK; +} + +static int +input_history_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv) +{ + struct history_param *p = cenv->data; + int i; + FILE *fp = cenv->fp; + + for (i = 0; i < p->count; i++) + { + const char *s = input_history_get (p->from + i); + if (!s) + break; + fprintf (fp, "%4d) %s\n", p->from + i + 1, s); + } + return GDBMSHELL_OK; +} + + +static int help_handler (struct command_param *, struct command_environ *); +static int help_begin (struct command_param *, struct command_environ *, + size_t *); + +struct argdef +{ + char *name; + int type; + int ds; +}; + +#define NARGS 10 + +enum command_repeat_type + { + REPEAT_NEVER, + REPEAT_ALWAYS, + REPEAT_NOARG + }; + +struct command +{ + char *name; /* Command name */ + size_t len; /* Name length */ + int tok; + int (*begin) (struct command_param *param, struct command_environ *cenv, size_t *); + int (*handler) (struct command_param *param, struct command_environ *cenv); + void (*end) (void *data); + struct argdef args[NARGS]; + int variadic; + enum command_repeat_type repeat; + char *doc; +}; + +static struct command command_tab[] = { + { + .name = "count", + .doc = N_("count (number of entries)"), + .tok = T_CMD, + .begin = checkdb_begin, + .handler = count_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "delete", + .args = { + { N_("KEY"), GDBM_ARG_DATUM, DS_KEY }, + { NULL } + }, + .doc = N_("delete a record"), + .tok = T_CMD, + .begin = checkdb_begin, + .handler = delete_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "export", + .args = { + { N_("FILE"), GDBM_ARG_STRING }, + { "[truncate]", GDBM_ARG_STRING }, + { "[binary|ascii]", GDBM_ARG_STRING }, + { NULL } + }, + .doc = N_("export"), + .tok = T_CMD, + .begin = checkdb_begin, + .handler = export_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "fetch", + .args = { + { N_("KEY"), GDBM_ARG_DATUM, DS_KEY }, + { NULL } + }, + .doc = N_("fetch record"), + .tok = T_CMD, + .begin = checkdb_begin, + .handler = fetch_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "import", + .args = { + { N_("FILE"), GDBM_ARG_STRING }, + { "[replace]", GDBM_ARG_STRING }, + { "[nometa]" , GDBM_ARG_STRING }, + { NULL } + }, + .doc = N_("import"), + .tok = T_CMD, + .handler = import_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "list", + .args = { + { "[bucket]", GDBM_ARG_STRING }, + { NULL }, + }, + .doc = N_("list"), + .tok = T_CMD, + .begin = list_begin, + .handler = list_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "next", + .args = { + { N_("[KEY]"), GDBM_ARG_DATUM, DS_KEY }, + { NULL } + }, + .doc = N_("continue iteration: get next key and datum"), + .tok = T_CMD, + .begin = checkdb_begin, + .handler = nextkey_handler, + .variadic = FALSE, + .repeat = REPEAT_NOARG, + }, + { + .name = "store", + .args = { + { N_("KEY"), GDBM_ARG_DATUM, DS_KEY }, + { N_("DATA"), GDBM_ARG_DATUM, DS_CONTENT }, + { NULL } + }, + .doc = N_("store"), + .tok = T_CMD, + .begin = checkdb_begin, + .handler = store_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "first", + .doc = N_("begin iteration: get first key and datum"), + .tok = T_CMD, + .begin = checkdb_begin, + .handler = firstkey_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "reorganize", + .doc = N_("reorganize"), + .tok = T_CMD, + .begin = checkdb_begin, + .handler = reorganize_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "recover", + .args = { + { "[verbose]", GDBM_ARG_STRING }, + { "[summary]", GDBM_ARG_STRING }, + { "[backup]", GDBM_ARG_STRING }, + { "[force]", GDBM_ARG_STRING }, + { "[max-failed-keys=N]", GDBM_ARG_STRING }, + { "[max-failed-buckets=N]", GDBM_ARG_STRING }, + { "[max-failures=N]", GDBM_ARG_STRING }, + { NULL } + }, + .doc = N_("recover the database"), + .tok = T_CMD, + .begin = checkdb_begin, + .handler = recover_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "avail", + .doc = N_("print avail list"), + .tok = T_CMD, + .begin = avail_begin, + .handler = avail_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "bucket", + .args = { + { N_("[NUMBER]"), GDBM_ARG_STRING }, + { NULL } + }, + .doc = N_("print a bucket"), + .tok = T_CMD, + .begin = print_bucket_begin, + .handler = print_current_bucket_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "current", + .doc = N_("print current bucket"), + .tok = T_CMD, + .begin = print_current_bucket_begin, + .handler = print_current_bucket_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "sibling", + .doc = N_("print sibling bucket"), + .tok = T_CMD, + .begin = print_sibling_bucket_begin, + .handler = print_current_bucket_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "dir", + .doc = N_("print hash directory"), + .tok = T_CMD, + .begin = print_dir_begin, + .handler = print_dir_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "header", + .doc = N_("print database file header"), + .tok = T_CMD, + .begin = print_header_begin, + .handler = print_header_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "hash", + .args = { + { N_("KEY"), GDBM_ARG_DATUM, DS_KEY }, + { NULL } + }, + .doc = N_("hash value of key"), + .tok = T_CMD, + .handler = hash_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "cache", + .doc = N_("print the bucket cache"), + .tok = T_CMD, + .begin = print_cache_begin, + .handler = print_cache_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "status", + .doc = N_("print current program status"), + .tok = T_CMD, + .handler = status_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "sync", + .doc = N_("Synchronize the database with disk copy"), + .tok = T_CMD, + .begin = checkdb_begin, + .handler = sync_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "upgrade", + .doc = N_("Upgrade the database to extended format"), + .tok = T_CMD, + .begin = checkdb_begin, + .handler = upgrade_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "downgrade", + .doc = N_("Downgrade the database to standard format"), + .tok = T_CMD, + .begin = checkdb_begin, + .handler = downgrade_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "snapshot", + .args = { + { "FILE", GDBM_ARG_STRING }, + { "FILE", GDBM_ARG_STRING }, + { NULL } + }, + .doc = N_("analyze two database snapshots"), + .tok = T_CMD, + .handler = snapshot_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "version", + .doc = N_("print version of gdbm"), + .tok = T_CMD, + .handler = print_version_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "help", + .doc = N_("print this help list"), + .tok = T_CMD, + .begin = help_begin, + .handler = help_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "quit", + .doc = N_("quit the program"), + .tok = T_CMD, + .handler = quit_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "set", + .args = { + { "[VAR=VALUE...]" }, + { NULL } + }, + .doc = N_("set or list variables"), + .tok = T_SET, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "unset", + .args = { + { "VAR..." }, + { NULL } + }, + .doc = N_("unset variables"), + .tok = T_UNSET, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "define", + .args = { + { "key|content", GDBM_ARG_STRING }, + { "{ FIELD-LIST }", GDBM_ARG_STRING }, + { NULL } + }, + .doc = N_("define datum structure"), + .tok = T_DEF, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "source", + .args = { + { "FILE", GDBM_ARG_STRING }, + { NULL } + }, + .doc = N_("source command script"), + .tok = T_CMD, + .handler = source_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "close", + .doc = N_("close the database"), + .tok = T_CMD, + .handler = close_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "open", + .args = { + { "[FILE]", GDBM_ARG_STRING }, + { NULL } + }, + .doc = N_("open new database"), + .tok = T_CMD, + .handler = open_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "history", + .args = { + { N_("[FROM]"), GDBM_ARG_STRING }, + { N_("[COUNT]"), GDBM_ARG_STRING }, + { NULL } + }, + .doc = N_("show input history"), + .tok = T_CMD, + .begin = input_history_begin, + .handler = input_history_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { + .name = "debug", + .doc = N_("query/set debug level"), + .tok = T_CMD, + .handler = debug_handler, + .variadic = TRUE, + .repeat = REPEAT_NEVER, + }, + { + .name = "shell", + .doc = N_("invoke the shell"), + .tok = T_SHELL, + .handler = shell_handler, + .variadic = TRUE, + .repeat = REPEAT_NEVER, + }, + { + .name = "perror", + .args = { + { "[CODE]", GDBM_ARG_STRING }, + { NULL } + }, + .doc = N_("describe GDBM error code"), + .tok = T_CMD, + .handler = perror_handler, + .variadic = FALSE, + .repeat = REPEAT_NEVER, + }, + { NULL } +}; + +static int commands_sorted; + +static int +cmdcmp (const void *a, const void *b) +{ + struct command const *ac = a; + struct command const *bc = b; + return strcmp (ac->name, bc->name); +} + +/* Generator function for command completion. STATE lets us know whether + to start from scratch; without any state (i.e. STATE == 0), then we + start at the top of the list. */ +char * +command_generator (const char *text, int state) +{ + const char *name; + static int len; + static struct command *cmd; + + /* If this is a new word to complete, initialize now. This includes + saving the length of TEXT for efficiency, and initializing the index + variable to 0. */ + if (!state) + { + cmd = command_tab; + len = strlen (text); + } + + if (!cmd || !cmd->name) + return NULL; + + /* Return the next name which partially matches from the command list. */ + while ((name = cmd->name)) + { + cmd++; + if (strncmp (name, text, len) == 0) + return strdup (name); + } + + /* If no names matched, then return NULL. */ + return NULL; +} + +/* ? - help handler */ +#define CMDCOLS 30 + +static int +help_begin (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv GDBM_ARG_UNUSED, + size_t *exp_count) +{ + if (exp_count) + *exp_count = ARRAY_SIZE (command_tab) + 1; + return 0; +} + +static int +help_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv) +{ + struct command *cmd; + WORDWRAP_FILE wf; + + fflush (cenv->fp); + wf = wordwrap_fdopen (fileno (cenv->fp)); + + for (cmd = command_tab; cmd->name; cmd++) + { + int i; + int n; + + wordwrap_set_left_margin (wf, 1); + wordwrap_set_right_margin (wf, 0); + n = strlen (cmd->name); + wordwrap_write (wf, cmd->name, n); + + wordwrap_set_left_margin (wf, n + 2); + for (i = 0; i < NARGS && cmd->args[i].name; i++) + { + wordwrap_printf (wf, " %s", gettext (cmd->args[i].name)); + } + + wordwrap_set_right_margin (wf, 0); + wordwrap_set_left_margin (wf, CMDCOLS); + + wordwrap_printf (wf, " %s", gettext (cmd->doc)); + wordwrap_flush (wf); + } + wordwrap_close (wf); + return 0; +} + +int +command_lookup (const char *str, struct locus *loc, struct command **pcmd) +{ + enum { fcom_init, fcom_found, fcom_ambig, fcom_abort } state = fcom_init; + struct command *cmd, *found = NULL; + size_t len = strlen (str); + + for (cmd = command_tab; state != fcom_abort && cmd->name; cmd++) + { + size_t n = len < cmd->len ? len : cmd->len; + if (memcmp (cmd->name, str, n) == 0 && str[n] == 0) + { + switch (state) + { + case fcom_init: + found = cmd; + state = fcom_found; + break; + + case fcom_found: + if (!interactive ()) + { + state = fcom_abort; + found = NULL; + continue; + } + fprintf (stderr, "ambiguous command: %s\n", str); + fprintf (stderr, " %s\n", found->name); + found = NULL; + state = fcom_ambig; + /* fall through */ + case fcom_ambig: + fprintf (stderr, " %s\n", cmd->name); + break; + + case fcom_abort: + /* should not happen */ + abort (); + } + } + } + + if (state == fcom_init) + lerror (loc, interactive () ? _("Invalid command. Try ? for help.") : + _("Unknown command")); + if (!found) + return T_BOGUS; + + *pcmd = found; + return found->tok; +} + +struct gdbmarg * +gdbmarg_string (char *string, struct locus *loc) +{ + struct gdbmarg *arg = ecalloc (1, sizeof (*arg)); + arg->next = NULL; + arg->type = GDBM_ARG_STRING; + arg->ref = 1; + if (loc) + arg->loc = *loc; + arg->v.string = string; + return arg; +} + +struct gdbmarg * +gdbmarg_datum (datum *dat, struct locus *loc) +{ + struct gdbmarg *arg = ecalloc (1, sizeof (*arg)); + arg->next = NULL; + arg->type = GDBM_ARG_DATUM; + arg->ref = 1; + if (loc) + arg->loc = *loc; + arg->v.dat = *dat; + return arg; +} + +struct gdbmarg * +gdbmarg_kvpair (struct kvpair *kvp, struct locus *loc) +{ + struct gdbmarg *arg = ecalloc (1, sizeof (*arg)); + arg->next = NULL; + arg->type = GDBM_ARG_KVPAIR; + arg->ref = 1; + if (loc) + arg->loc = *loc; + arg->v.kvpair = kvp; + return arg; +} + +struct slist * +slist_new_s (char *s) +{ + struct slist *lp = emalloc (sizeof (*lp)); + lp->next = NULL; + lp->str = s; + return lp; +} + +struct slist * +slist_new (char const *s) +{ + return slist_new_s (estrdup (s)); +} + +struct slist * +slist_new_l (char const *s, size_t l) +{ + char *copy = emalloc (l + 1); + memcpy (copy, s, l); + copy[l] = 0; + return slist_new_s (copy); +} + +void +slist_free (struct slist *lp) +{ + while (lp) + { + struct slist *next = lp->next; + free (lp->str); + free (lp); + lp = next; + } +} + +void +slist_insert (struct slist **where, struct slist *what) +{ + if (*where) + { + while (what->next) + what = what->next; + what->next = (*where)->next; + (*where)->next = what; + } + else + what->next = NULL; + *where = what; +} + +struct kvpair * +kvpair_string (struct locus *loc, char *val) +{ + struct kvpair *p = ecalloc (1, sizeof (*p)); + p->type = KV_STRING; + if (loc) + p->loc = *loc; + p->val.s = val; + return p; +} + +struct kvpair * +kvpair_list (struct locus *loc, struct slist *s) +{ + struct kvpair *p = ecalloc (1, sizeof (*p)); + p->type = KV_LIST; + if (loc) + p->loc = *loc; + p->val.l = s; + return p; +} + +void +kvlist_free (struct kvpair *kvp) +{ + while (kvp) + { + struct kvpair *next = kvp->next; + free (kvp->key); + switch (kvp->type) + { + case KV_STRING: + free (kvp->val.s); + break; + + case KV_LIST: + slist_free (kvp->val.l); + break; + } + free (kvp); + kvp = next; + } +} + +struct kvpair * +kvlist_find (struct kvpair *kv, char const *tag) +{ + for (; kv; kv = kv->next) + if (kv->key && strcmp (kv->key, tag) == 0) + break; + return kv; +} + +int +gdbmarg_free (struct gdbmarg *arg) +{ + if (arg && --arg->ref == 0) + { + switch (arg->type) + { + case GDBM_ARG_STRING: + free (arg->v.string); + break; + + case GDBM_ARG_KVPAIR: + kvlist_free (arg->v.kvpair); + break; + + case GDBM_ARG_DATUM: + free (arg->v.dat.dptr); + break; + } + free (arg); + return 0; + } + return 1; +} + +void +gdbmarg_destroy (struct gdbmarg **parg) +{ + if (parg && gdbmarg_free (*parg)) + *parg = NULL; +} + +void +gdbmarglist_init (struct gdbmarglist *lst, struct gdbmarg *arg) +{ + if (arg) + arg->next = NULL; + lst->head = lst->tail = arg; +} + +void +gdbmarglist_add (struct gdbmarglist *lst, struct gdbmarg *arg) +{ + arg->next = NULL; + if (lst->tail) + lst->tail->next = arg; + else + lst->head = arg; + lst->tail = arg; +} + +void +gdbmarglist_free (struct gdbmarglist *lst) +{ + struct gdbmarg *arg; + + for (arg = lst->head; arg; ) + { + struct gdbmarg *next = arg->next; + gdbmarg_free (arg); + arg = next; + } + lst->head = lst->tail = NULL; +} + +static void +param_expand (struct command_param *p) +{ + if (p->argc == p->argmax) + p->argv = e2nrealloc (p->argv, &p->argmax, sizeof (p->argv[0])); +} + +static void +param_free_argv (struct command_param *p) +{ + size_t i; + + for (i = 0; i < p->argc; i++) + gdbmarg_destroy (&p->argv[i]); + p->argc = 0; +} + +static void +param_free (struct command_param *p) +{ + param_free_argv (p); + free (p->argv); + p->argv = NULL; + p->argmax = 0; +} + +static struct gdbmarg *coerce (struct gdbmarg *arg, struct argdef *def); + +static int +param_push_arg (struct command_param *p, struct gdbmarg *arg, + struct argdef *def) +{ + param_expand (p); + if ((p->argv[p->argc] = coerce (arg, def)) == NULL) + { + return 1; + } + p->argc++; + return 0; +} + +static void +param_term (struct command_param *p) +{ + param_expand (p); + p->argv[p->argc] = NULL; +} + +typedef struct gdbmarg *(*coerce_type_t) (struct gdbmarg *arg, + struct argdef *def); + +struct gdbmarg * +coerce_ref (struct gdbmarg *arg, struct argdef *def) +{ + ++arg->ref; + return arg; +} + +struct gdbmarg * +coerce_k2d (struct gdbmarg *arg, struct argdef *def) +{ + datum d; + + if (datum_scan (&d, dsdef[def->ds], arg->v.kvpair)) + return NULL; + return gdbmarg_datum (&d, &arg->loc); +} + +struct gdbmarg * +coerce_s2d (struct gdbmarg *arg, struct argdef *def) +{ + datum d; + struct kvpair kvp; + + memset (&kvp, 0, sizeof (kvp)); + kvp.type = KV_STRING; + kvp.val.s = arg->v.string; + + if (datum_scan (&d, dsdef[def->ds], &kvp)) + return NULL; + return gdbmarg_datum (&d, &arg->loc); +} + +#define coerce_fail NULL + +coerce_type_t coerce_tab[GDBM_ARG_MAX][GDBM_ARG_MAX] = { + /* s d k */ + /* s */ { coerce_ref, coerce_fail, coerce_fail }, + /* d */ { coerce_s2d, coerce_ref, coerce_k2d }, + /* k */ { coerce_fail, coerce_fail, coerce_ref } +}; + +char *argtypestr[] = { "string", "datum", "k/v pair" }; + +static struct gdbmarg * +coerce (struct gdbmarg *arg, struct argdef *def) +{ + if (!coerce_tab[def->type][arg->type]) + { + lerror (&arg->loc, _("cannot coerce %s to %s"), + argtypestr[arg->type], argtypestr[def->type]); + return NULL; + } + return coerce_tab[def->type][arg->type] (arg, def); +} + +static struct command *last_cmd; +static struct gdbmarglist last_args; + +int +run_last_command (void) +{ + if (interactive ()) + { + if (last_cmd) + { + switch (last_cmd->repeat) + { + case REPEAT_NEVER: + break; + case REPEAT_NOARG: + gdbmarglist_free (&last_args); + /* FALLTHROUGH */ + case REPEAT_ALWAYS: + return run_command (last_cmd, &last_args); + + default: + abort (); + } + } + } + return 0; +} + +static void +format_arg (struct gdbmarg *arg, struct argdef *def, FILE *fp) +{ + switch (arg->type) + { + case GDBM_ARG_STRING: + fprintf (fp, " %s", arg->v.string); + break; + + case GDBM_ARG_DATUM: + if (def && def->type == GDBM_ARG_DATUM) + { + fputc (' ', fp); + datum_format (fp, &arg->v.dat, dsdef[def->ds]); + } + else + /* Shouldn't happen */ + terror ("%s:%d: INTERNAL ERROR: unexpected data type in arglist", + __FILE__, __LINE__); + break; + + case GDBM_ARG_KVPAIR: + { + struct kvpair *kvp = arg->v.kvpair; + fprintf (fp, " %s ", kvp->key); + switch (kvp->type) + { + case KV_STRING: + fprintf (fp, "%s", kvp->val.s); + break; + + case KV_LIST: + { + struct slist *p = kvp->val.l; + fprintf (fp, "%s", p->str); + while ((p = p->next) != NULL) + fprintf (fp, ", %s", p->str); + } + } + } + } +} + +struct timing +{ + struct timeval real; + struct timeval user; + struct timeval sys; +}; + +void +timing_start (struct timing *t) +{ + struct rusage r; + gettimeofday (&t->real, NULL); + getrusage (RUSAGE_SELF, &r); + t->user = r.ru_utime; + t->sys = r.ru_stime; +} + +static inline struct timeval +timeval_sub (struct timeval a, struct timeval b) +{ + struct timeval diff; + + diff.tv_sec = a.tv_sec - b.tv_sec; + diff.tv_usec = a.tv_usec - b.tv_usec; + if (diff.tv_usec < 0) + { + --diff.tv_sec; + diff.tv_usec += 1000000; + } + + return diff; +} + +void +timing_stop (struct timing *t) +{ + struct rusage r; + struct timeval now; + + gettimeofday (&now, NULL); + getrusage (RUSAGE_SELF, &r); + t->real = timeval_sub (now, t->real); + t->user = timeval_sub (r.ru_utime, t->user); + t->sys = timeval_sub (r.ru_stime, t->sys); +} + +static int +argsprep (struct command *cmd, struct gdbmarglist *arglist, + struct command_param *param) +{ + int i; + struct gdbmarg *arg = arglist ? arglist->head : NULL; + char argbuf[128]; + + for (i = 0; cmd->args[i].name && arg; i++, arg = arg->next) + { + if (param_push_arg (param, arg, &cmd->args[i])) + return 1; + } + + for (; cmd->args[i].name; i++) + { + char *argname = cmd->args[i].name; + struct gdbmarg *t; + + if (*argname == '[') + /* Optional argument */ + break; + + if (!interactive ()) + { + terror (_("%s: not enough arguments"), cmd->name); + return 1; + } + printf ("%s? ", argname); + fflush (stdout); + if (fgets (argbuf, sizeof argbuf, stdin) == NULL) + { + terror ("%s", _("unexpected eof")); + return 1; + } + + trimnl (argbuf); + + t = gdbmarg_string (estrdup (argbuf), &yylloc); + if (param_push_arg (param, t, &cmd->args[i])) + { + gdbmarg_free (t); + return 1; + } + } + + if (arg && !cmd->variadic) + { + terror (_("%s: too many arguments"), cmd->name); + return 1; + } + + param_term (param); + param->vararg = arg; + + return 0; +} + +int +run_command (struct command *cmd, struct gdbmarglist *arglist) +{ + int i; + char *pager = NULL; + size_t expected_lines, *expected_lines_ptr; + FILE *pagfp = NULL; + struct command_param param = HANDLER_PARAM_INITIALIZER; + struct command_environ cenv = COMMAND_ENVIRON_INITIALIZER; + int rc = 0; + struct timing tm; + + if (argsprep (cmd, arglist, ¶m)) + rc = GDBMSHELL_ERR; + else + { + variable_get ("pager", VART_STRING, (void**) &pager); + + /* Prepare for calling the handler */ + pagfp = NULL; + + if (variable_is_true ("trace")) + { + fprintf (stderr, "+ %s", cmd->name); + for (i = 0; i < param.argc; i++) + { + format_arg (param.argv[i], &cmd->args[i], stderr); + } + + if (param.vararg) + { + struct gdbmarg *arg; + for (arg = param.vararg; arg; arg = arg->next) + format_arg (arg, NULL, stderr); + } + fputc ('\n', stderr); + } + + expected_lines = 0; + expected_lines_ptr = (interactive () && pager) ? &expected_lines : NULL; + rc = 0; + if (!(cmd->begin && + (rc = cmd->begin (¶m, &cenv, expected_lines_ptr)) != 0)) + { + if (pager && expected_lines > get_screen_lines ()) + { + pagfp = popen (pager, "w"); + if (pagfp) + cenv.fp = pagfp; + else + { + terror (_("cannot run pager `%s': %s"), pager, + strerror (errno)); + pager = NULL; + cenv.fp = stdout; + } + } + else + cenv.fp = stdout; + + timing_start (&tm); + rc = cmd->handler (¶m, &cenv); + timing_stop (&tm); + if (cmd->end) + cmd->end (cenv.data); + else if (cenv.data) + free (cenv.data); + + if (variable_is_true ("timing")) + { + fprintf (cenv.fp, "[%s r=%lu.%06lu u=%lu.%06lu s=%lu.%06lu]\n", + cmd->name, + tm.real.tv_sec, tm.real.tv_usec, + tm.user.tv_sec, tm.user.tv_usec, + tm.sys.tv_sec, tm.sys.tv_usec); + } + + if (pagfp) + pclose (pagfp); + } + } + + param_free (¶m); + + switch (rc) + { + case GDBMSHELL_OK: + last_cmd = cmd; + if (arglist->head != last_args.head) + { + gdbmarglist_free (&last_args); + last_args = *arglist; + } + rc = 0; + break; + + case GDBMSHELL_GDBM_ERR: + gdbmarglist_free (arglist); + if (variable_has_errno ("errorexit", gdbm_errno)) + rc = 1; + else + rc = 0; + break; + + default: + gdbmarglist_free (arglist); + rc = 0; + } + + return rc; +} + +int +gdbmshell_run (int (*init) (void *, instream_t *), void *data) +{ + int rc; + int i; + instream_t instream; + + if (!commands_sorted) + { + /* Initialize .len fields */ + for (i = 0; command_tab[i].name; i++) + command_tab[i].len = strlen (command_tab[i].name); + /* Sort the entries by name. */ + qsort (command_tab, i, sizeof (command_tab[0]), cmdcmp); + commands_sorted = 1; + } + + /* Initialize variables. */ + dsdef[DS_KEY] = dsegm_new_field (datadef_lookup ("string"), NULL, 1); + dsdef[DS_CONTENT] = dsegm_new_field (datadef_lookup ("string"), NULL, 1); + + variables_init (); + variable_set ("open", VART_STRING, "wrcreat"); + variable_set ("pager", VART_STRING, getenv ("PAGER")); + + last_cmd = NULL; + gdbmarglist_init (&last_args, NULL); + + lex_trace (0); + + rc = init (data, &instream); + if (rc == 0) + { + rc = input_context_push (instream); + if (rc == 0) + { + struct sigaction act, old_act; + + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + act.sa_handler = SIG_IGN; + sigaction (SIGPIPE, &act, &old_act); + /* Welcome message. */ + if (instream_interactive (instream) && !variable_is_true ("quiet")) + printf (_("\nWelcome to the gdbm tool. Type ? for help.\n\n")); + rc = yyparse (); + input_context_drain (); + yylex_destroy (); + closedb (); + sigaction (SIGPIPE, &old_act, NULL); + } + else + instream_close (instream); + } + + gdbmarglist_free (&last_args); + + for (i = 0; i < DS_MAX; i++) + { + dsegm_list_free (dsdef[i]); + dsdef[i] = NULL; + } + + variables_free (); + + return rc; +} + +static int +init (void *data, instream_t *pinstr) +{ + *pinstr = data; + return 0; +} + +int +gdbmshell (instream_t input) +{ + return gdbmshell_run (init, input); +} diff --git a/tools/gdbmtool.c b/tools/gdbmtool.c new file mode 100644 index 0000000..dfe7094 --- /dev/null +++ b/tools/gdbmtool.c @@ -0,0 +1,293 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 1990-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +#include "gdbmtool.h" +#include <errno.h> +#include <ctype.h> +#include <signal.h> +#include <pwd.h> +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <termios.h> +#include <stdarg.h> +#ifdef HAVE_LOCALE_H +# include <locale.h> +#endif + +static void +source_rcfile (void) +{ + instream_t istr = NULL; + + if (access (GDBMTOOLRC, R_OK) == 0) + { + istr = instream_file_create (GDBMTOOLRC); + } + else + { + char *fname; + char *p = getenv ("HOME"); + if (!p) + { + struct passwd *pw = getpwuid (getuid ()); + if (!pw) + { + terror (_("cannot find home directory")); + return; + } + p = pw->pw_dir; + } + fname = mkfilename (p, GDBMTOOLRC, NULL); + if (access (fname, R_OK) == 0) + { + istr = instream_file_create (GDBMTOOLRC); + } + free (fname); + } + + if (istr) + { + if (input_context_push (istr)) + exit (EXIT_FATAL); + yyparse (); + } +} + +#if GDBM_DEBUG_ENABLE +void +debug_printer (char const *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); +} +#endif + + +char *parseopt_program_doc = N_("examine and/or modify a GDBM database"); +char *parseopt_program_args = N_("DBFILE [COMMAND [ARG ...]]"); + +enum { + OPT_LEX_TRACE = 256, + OPT_GRAM_TRACE +}; + +struct gdbm_option optab[] = { + { 'b', "block-size", N_("SIZE"), N_("set block size") }, + { 'c', "cache-size", N_("SIZE"), N_("set cache size") }, + { 'f', "file", N_("FILE"), N_("read commands from FILE") }, + { 'g', NULL, "FILE", NULL, PARSEOPT_HIDDEN }, + { 'l', "no-lock", NULL, N_("disable file locking") }, + { 'm', "no-mmap", NULL, N_("do not use mmap") }, + { 'n', "newdb", NULL, N_("create database") }, + { 'N', "norc", NULL, N_("do not read .gdbmtoolrc file") }, + { 'r', "read-only", NULL, N_("open database in read-only mode") }, + { 's', "synchronize", NULL, N_("synchronize to disk after each write") }, + { 'q', "quiet", NULL, N_("don't print initial banner") }, + { 'd', "db-descriptor", + /* TRANSLATORS: File Descriptor. */ + N_("FD"), + N_("open database at the given file descriptor") }, + { 'x', "extended", NULL, N_("extended format (numsync)") }, + { 0, "numsync", NULL, NULL, PARSEOPT_ALIAS }, + { 't', "trace", NULL, N_("enable trace mode") }, + { 'T', "timing", NULL, N_("print timing after each command") }, +#if GDBMTOOL_DEBUG + { OPT_LEX_TRACE, "lex-trace", NULL, N_("enable lexical analyzer traces") }, + { OPT_GRAM_TRACE, "gram-trace", NULL, N_("enable grammar traces") }, +#endif + { 0 } +}; + +#ifdef WITH_READLINE +# define instream_default_create instream_readline_create +#else +# define instream_default_create instream_stdin_create +#endif + +struct gdbmtool_closure +{ + int argc; + char **argv; +}; + +static int +gdbmtool_init (void *data, instream_t *pinstr) +{ + struct gdbmtool_closure *clos = data; + int argc = clos->argc; + char **argv = clos->argv; + int opt; + int bv; + int norc = 0; + char *source = NULL; + instream_t input = NULL; + + for (opt = parseopt_first (argc, argv, optab); + opt != EOF; + opt = parseopt_next ()) + switch (opt) + { + case 'd': + if (variable_set ("fd", VART_STRING, optarg) != VAR_OK) + { + terror (_("invalid file descriptor: %s"), optarg); + exit (EXIT_USAGE); + } + break; + + case 'f': + source = optarg; + break; + + case 'l': + bv = 0; + variable_set ("lock", VART_BOOL, &bv); + break; + + case 'm': + bv = 0; + variable_set ("mmap", VART_BOOL, &bv); + break; + + case 's': + bv = 1; + variable_set ("sync", VART_BOOL, &bv); + break; + + case 'r': + variable_set ("open", VART_STRING, "readonly"); + break; + + case 'n': + variable_set ("open", VART_STRING, "newdb"); + break; + + case 'N': + norc = 1; + break; + + case 'c': + variable_set ("cachesize", VART_STRING, optarg); + break; + + case 'b': + variable_set ("blocksize", VART_STRING, optarg); + break; + + case 'g': + variable_set ("filename", VART_STRING, optarg); + break; + + case 't': + bv = 1; + variable_set ("trace", VART_BOOL, &bv); + break; + + case 'T': + bv = 1; + variable_set ("timing", VART_BOOL, &bv); + break; + + case 'q': + bv = 1; + variable_set ("quiet", VART_BOOL, &bv); + break; + + case 'x': + variable_set ("format", VART_STRING, "numsync"); + break; + + case OPT_LEX_TRACE: + lex_trace (1); + break; + + case OPT_GRAM_TRACE: + gram_trace (1); + break; + + default: + if (optopt == 0) + terror (_("unknown option %s; try `%s -h' for more info"), + argv[optind-1], progname); + else + terror (_("unknown option %c; try `%s -h' for more info"), + optopt, progname); + exit (EXIT_USAGE); + } + + argc -= optind; + argv += optind; + + if (source && strcmp (source, "-")) + { + input = instream_file_create (source); + if (!input) + exit (1); + } + + if (argc >= 1) + { + variable_set ("filename", VART_STRING, argv[0]); + argc--; + argv++; + if (argc) + { + if (input) + { + terror (_("--file and command cannot be used together")); + exit (EXIT_USAGE); + } + input = instream_argv_create (argc, argv); + if (!input) + exit (1); + } + } + + if (!norc) + source_rcfile (); + + if (!input) + input = instream_default_create (); + + *pinstr = input; + + return 0; +} + +int +main (int argc, char *argv[]) +{ + struct gdbmtool_closure closure; + + set_progname (argv[0]); +#if GDBM_DEBUG_ENABLE + gdbm_debug_printer = debug_printer; +#endif + +#ifdef HAVE_SETLOCALE + setlocale (LC_ALL, ""); +#endif + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + closure.argc = argc; + closure.argv = argv; + + return gdbmshell_run (gdbmtool_init, &closure); +} diff --git a/tools/gdbmtool.h b/tools/gdbmtool.h new file mode 100644 index 0000000..432e9a6 --- /dev/null +++ b/tools/gdbmtool.h @@ -0,0 +1,383 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 1990-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +#include "autoconf.h" +#include "gdbmdefs.h" +#include "gdbm.h" +#include "gdbmapp.h" +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <ctype.h> +#include <limits.h> + +#ifndef GDBM_ARG_UNUSED +# define GDBM_ARG_UNUSED __attribute__ ((__unused__)) +#endif + +#ifndef GDBM_PRINTFLIKE +# define GDBM_PRINTFLIKE(fmt,narg) \ + __attribute__ ((__format__ (__printf__, fmt, narg))) +#endif + +/* Position in input file */ +struct point +{ + char *file; /* file name */ + unsigned line; /* line number */ + unsigned col; /* column number */ +}; + +/* Location in input file */ +struct locus +{ + struct point beg, end; +}; + +typedef struct locus gdbm_yyltype_t; + +#define YYLTYPE gdbm_yyltype_t + +#define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + { \ + if (N) \ + { \ + (Current).beg = YYRHSLOC(Rhs, 1).beg; \ + (Current).end = YYRHSLOC(Rhs, N).end; \ + } \ + else \ + { \ + (Current).beg = YYRHSLOC(Rhs, 0).end; \ + (Current).end = (Current).beg; \ + } \ + } \ + while (0) + +#define YY_LOCATION_PRINT(File, Loc) locus_print (File, &(Loc)) + +void locus_print (FILE *fp, struct locus const *loc); +void vlerror (struct locus *loc, const char *fmt, va_list ap); +void lerror (struct locus *loc, const char *fmt, ...) + GDBM_PRINTFLIKE (2, 3); + +void terror (const char *fmt, ...) + GDBM_PRINTFLIKE (1, 2); +void dberror (char const *fmt, ...) + GDBM_PRINTFLIKE (1, 2); + +char *make_prompt (void); + +#define GDBMTOOLRC ".gdbmtoolrc" +#define GDBMTOOL_DEFFILE "junk.gdbm" + +typedef struct instream *instream_t; + +struct instream +{ + char *in_name; /* Input stream name */ + int in_inter; /* True if this is an interactive stream */ + ssize_t (*in_read) (instream_t, char*, size_t); + /* Read from stream */ + void (*in_close) (instream_t); + /* Close the stream */ + int (*in_eq) (instream_t, instream_t); + /* Return true if both streams refer to the + same input */ + int (*in_history_size) (instream_t); + /* Return size of the history buffer (entries) */ + const char *(*in_history_get) (instream_t, int); + /* Get Nth line from the history buffer */ +}; + +static inline char const * +instream_name (instream_t in) +{ + return in->in_name; +} + +static inline ssize_t +instream_read (instream_t in, char *buf, size_t size) +{ + return in->in_read (in, buf, size); +} + +static inline void +instream_close (instream_t in) +{ + in->in_close (in); +} + +static inline int +instream_interactive (instream_t in) +{ + return in->in_inter; +} + +static inline int +instream_eq (instream_t a, instream_t b) +{ + return a->in_eq (a, b); +} + +static inline int +instream_history_size (instream_t in) +{ + return in->in_history_size ? in->in_history_size (in) : -1; +} + +static inline const char * +instream_history_get (instream_t in, int n) +{ + return in->in_history_get ? in->in_history_get (in, n) : NULL; +} + +instream_t instream_stdin_create (void); +instream_t instream_argv_create (int argc, char **argv); +instream_t instream_file_create (char const *name); +instream_t instream_null_create (void); +#ifdef WITH_READLINE +instream_t instream_readline_create (void); +#endif + +int interactive (void); +int input_context_push (instream_t); +int input_context_pop (void); +void input_context_drain (void); +int input_history_size (void); +const char *input_history_get (int n); +const char *input_stream_name (void); + + +void print_prompt_at_bol (void); +char *command_generator (const char *text, int state); + + +struct slist +{ + struct slist *next; + char *str; +}; + +struct slist *slist_new (char const *s); +struct slist *slist_new_s (char *s); +struct slist *slist_new_l (char const *s, size_t l); +void slist_free (struct slist *); +void slist_insert (struct slist **where, struct slist *what); + +#define KV_STRING 0 +#define KV_LIST 1 + +struct kvpair +{ + struct kvpair *next; + int type; + struct locus loc; + char *key; + union + { + char *s; + struct slist *l; + } val; +}; + +struct kvpair *kvpair_string (struct locus *loc, char *val); +struct kvpair *kvpair_list (struct locus *loc, struct slist *s); +struct kvpair *kvlist_find (struct kvpair *kv, char const *tag); +void kvlist_free (struct kvpair *kvp); + + +#define GDBM_ARG_STRING 0 +#define GDBM_ARG_DATUM 1 +#define GDBM_ARG_KVPAIR 2 +#define GDBM_ARG_MAX 3 + +/* Argument to a command handler */ +struct gdbmarg +{ + struct gdbmarg *next; + int type; + int ref; + struct locus loc; + union + { + char *string; + datum dat; + struct kvpair *kvpair; + } v; +}; + +/* List of arguments */ +struct gdbmarglist +{ + struct gdbmarg *head, *tail; +}; + +struct command_param +{ + size_t argc; + size_t argmax; + struct gdbmarg **argv; + struct gdbmarg *vararg; +}; + +#define HANDLER_PARAM_INITIALIZER { 0, 0, NULL, NULL } + +#define PARAM_STRING(p,n) ((p)->argv[n]->v.string) +#define PARAM_DATUM(p,n) ((p)->argv[n]->v.dat) +#define PARAM_KVPAIR(p,n) ((p)->argv[n]->v.kvpair) + +void gdbmarglist_init (struct gdbmarglist *, struct gdbmarg *); +void gdbmarglist_add (struct gdbmarglist *, struct gdbmarg *); +void gdbmarglist_free (struct gdbmarglist *lst); + +struct gdbmarg *gdbmarg_string (char *, struct locus *); +struct gdbmarg *gdbmarg_datum (datum *, struct locus *); +struct gdbmarg *gdbmarg_kvpair (struct kvpair *kvl, struct locus *); + +int gdbmarg_free (struct gdbmarg *arg); +void gdbmarg_destroy (struct gdbmarg **parg); + +struct command_environ +{ + FILE *fp; + void *data; +}; + +#define COMMAND_ENVIRON_INITIALIZER { NULL, NULL } + +struct command; +int command_lookup (const char *str, struct locus *loc, struct command **pcmd); + +int run_command (struct command *cmd, struct gdbmarglist *arglist); +int run_last_command (void); + +struct xdatum; +void xd_expand (struct xdatum *xd, size_t size); +void xd_store (struct xdatum *xd, void *val, size_t size); + + +struct datadef +{ + char *name; + int size; + int (*format) (FILE *, void *ptr, int size); + int (*scan) (struct xdatum *xd, char *str); +}; + +struct datadef *datadef_lookup (const char *name); + +struct field +{ + struct datadef *type; + int dim; + char *name; +}; + +#define FDEF_FLD 0 +#define FDEF_OFF 1 +#define FDEF_PAD 2 + +struct dsegm +{ + struct dsegm *next; + int type; + union + { + int n; + struct field field; + } v; +}; + +struct dsegm *dsegm_new (int type); +struct dsegm *dsegm_new_field (struct datadef *type, char *id, int dim); +void dsegm_list_free (struct dsegm *dp); +struct dsegm *dsegm_list_find (struct dsegm *dp, char const *name); + +#define DS_KEY 0 +#define DS_CONTENT 1 +#define DS_MAX 2 + +extern struct dsegm *dsdef[]; + +#define VART_STRING 0 +#define VART_BOOL 1 +#define VART_INT 2 + +enum + { + VAR_OK, /* operation succeeded */ + VAR_ERR_NOTSET, /* Only for variable_get: variable is not set */ + VAR_ERR_NOTDEF, /* no such variable */ + VAR_ERR_BADTYPE, /* variable cannot be coerced to the requested type + (software error) */ + VAR_ERR_BADVALUE, /* Only for variable_set: the value is not valid for + this variable. */ + VAR_ERR_GDBM, /* GDBM error */ + }; + + +int variable_set (const char *name, int type, void *val); +int variable_get (const char *name, int type, void **val); +int variable_unset(const char *name); +int variable_is_set (const char *name); +int variable_is_true (const char *name); +void variable_print_all (FILE *fp); +static inline int +variable_has_errno (char *varname, int e) +{ + return variable_get (varname, VART_INT, (void**)&e) == VAR_OK && e == 1; +} +static inline int +gdbm_error_is_masked (int e) +{ + return variable_has_errno ("errormask", e); +} + +int unescape (int c); +int escape (int c); +void begin_def (void); +void end_def (void); + +int yylex (void); +int yylex_destroy (void); +void yyerror (char const *s); +#define YYERROR_IS_DECLARED 1 +int yyparse (void); + +void lex_trace (int n); +void gram_trace (int n); + +void datum_format (FILE *fp, datum const *dat, struct dsegm *ds); +int datum_scan (datum *dat, struct dsegm *ds, struct kvpair *kv); +void dsprint (FILE *fp, int what, struct dsegm *ds); + +char *mkfilename (const char *dir, const char *file, const char *suf); +char *tildexpand (char *s); +int vgetyn (const char *prompt, va_list ap); +int getyn (const char *prompt, ...) GDBM_PRINTFLIKE (1, 2); + +int getnum (int *pnum, char *arg, char **endp); + +int gdbmshell (instream_t input); +int gdbmshell_run (int (*init) (void *, instream_t *), void *data); + +int gdbmshell_setopt (char *name, int opt, int val); + +void variables_init (void); +void variables_free (void); + diff --git a/tools/gdbmtool.supp b/tools/gdbmtool.supp new file mode 100644 index 0000000..247be73 --- /dev/null +++ b/tools/gdbmtool.supp @@ -0,0 +1,19 @@ +{ + <parseopt_first> + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:realloc + fun:erealloc + fun:add_options + fun:parseopt_first +} +{ + <parseopt_first> + Memcheck:Leak + match-leak-kinds: reachable + fun:realloc + fun:erealloc + fun:add_options + fun:parseopt_first +} diff --git a/tools/gram.y b/tools/gram.y new file mode 100644 index 0000000..baf72af --- /dev/null +++ b/tools/gram.y @@ -0,0 +1,443 @@ +%{ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 1990-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +#include <autoconf.h> +#include "gdbmtool.h" + +struct dsegm *dsdef[DS_MAX]; + +%} + +%define parse.error verbose +%locations + +%token <type> T_TYPE +%token T_OFF "off" + T_PAD "pad" + T_DEF "define" + T_SET "set" + T_UNSET "unset" + T_BOGUS + +%token <cmd> T_CMD "command verb" +%token <cmd> T_SHELL "shell" +%token <num> T_NUM "number" +%token <string> T_IDENT "identifier" T_WORD "word" +%type <cmd> command +%type <string> string +%type <arg> arg +%type <arglist> arglist arg1list +%type <dsegm> def defbody +%type <dsegmlist> deflist +%type <num> defid +%type <kvpair> kvpair compound value +%type <kvlist> kvlist +%type <slist> slist + +%union { + char *string; + struct kvpair *kvpair; + struct { struct kvpair *head, *tail; } kvlist; + struct { struct slist *head, *tail; } slist; + struct gdbmarg *arg; + struct gdbmarglist arglist; + int num; + struct datadef *type; + struct dsegm *dsegm; + struct { struct dsegm *head, *tail; } dsegmlist; + struct command *cmd; +} + +%destructor { gdbmarglist_free (&$$); } <arglist> +%destructor { gdbmarg_free ($$); } <arg> +%destructor { kvlist_free ($$.head); } <kvlist> +%destructor { kvlist_free ($$); } <kvpair> +%destructor { slist_free ($$.head); } <slist> +%destructor { free ($$); } <string> +%destructor { dsegm_list_free ($$); } <dsegm> +%destructor { dsegm_list_free ($$.head); } <dsegmlist> + +%% + +input : /* empty */ + | stmtlist + ; + +stmtlist : stmt + | stmtlist stmt + ; + +stmt : /* empty */ eol + { + if (run_last_command ()) + { + YYABORT; + } + } + | command arglist eol + { + if (run_command ($1, &$2)) + { + YYABORT; + } + } + | set eol + | defn eol + | T_BOGUS eol + { + if (interactive ()) + { + yyclearin; + yyerrok; + } + else + YYERROR; + } + | error { end_def(); } eol + { + if (interactive ()) + { + yyclearin; + yyerrok; + } + else + YYERROR; + } + ; + +command : T_CMD + | T_SHELL + ; + +eol : '\n' + | ';' + ; + +arglist : /* empty */ + { + gdbmarglist_init (&$$, NULL); + } + | arg1list + ; + +arg1list : arg + { + gdbmarglist_init (&$$, $1); + } + | arg1list arg + { + gdbmarglist_add (&$1, $2); + $$ = $1; + } + ; + +arg : string + { + $$ = gdbmarg_string ($1, &@1); + } + | compound + { + $$ = gdbmarg_kvpair ($1, &@1); + } + ; + +compound : '{' kvlist '}' + { + $$ = $2.head; + } + ; + +kvlist : kvpair + { + $$.head = $$.tail = $1; + } + | kvlist ',' kvpair + { + if (kvlist_find ($1.head, $3->key)) + { + lerror (&@3, _("duplicate tag: %s"), $3->key); + kvlist_free ($1.head); + $1.head = $1.tail = NULL; + YYERROR; + } + $1.tail->next = $3; + $1.tail = $3; + $$ = $1; + } + ; + +kvpair : value + | T_IDENT '=' value + { + $3->key = $1; + $$ = $3; + } + ; + +value : string + { + $$ = kvpair_string (&@1, $1); + } + | '{' slist '}' + { + $$ = kvpair_list (&@1, $2.head); + } + ; + +slist : string + { + $$.head = $$.tail = slist_new_s ($1); + } + | slist ',' string + { + struct slist *s = slist_new_s ($3); + slist_insert (&$1.tail, s); + $$ = $1; + } + ; + +string : T_IDENT + | T_WORD + ; + +defn : T_DEF defid { begin_def (); } defbody + { + end_def (); + dsegm_list_free (dsdef[$2]); + dsdef[$2] = $4; + } + ; + +defbody : '{' deflist optcomma '}' + { + $$ = $2.head; + } + | T_TYPE + { + $$ = dsegm_new_field ($1, NULL, 1); + } + ; + +optcomma : /* empty */ + | ',' + ; + +defid : T_IDENT + { + if (strcmp ($1, "key") == 0) + { + $$ = DS_KEY; + free ($1); + } + else if (strcmp ($1, "content") == 0) + { + $$ = DS_CONTENT; + free ($1); + } + else + { + terror (_("expected \"key\" or \"content\", " + "but found \"%s\""), $1); + free ($1); + YYERROR; + } + } + ; + +deflist : def + { + $$.head = $$.tail = $1; + } + | deflist ',' def + { + $1.tail->next = $3; + $1.tail = $3; + $$ = $1; + } + ; + +def : T_TYPE T_IDENT + { + $$ = dsegm_new_field ($1, $2, 1); + } + | T_TYPE T_IDENT '[' T_NUM ']' + { + $$ = dsegm_new_field ($1, $2, $4); + } + | T_OFF T_NUM + { + $$ = dsegm_new (FDEF_OFF); + $$->v.n = $2; + } + | T_PAD T_NUM + { + $$ = dsegm_new (FDEF_PAD); + $$->v.n = $2; + } + ; + +set : T_SET + { + variable_print_all (stdout); + } + | T_SET asgnlist + | T_UNSET varlist + ; + +asgnlist : asgn + | asgnlist asgn + ; + +asgn : T_IDENT + { + int t = 1; + int rc; + char *varname = $1; + + rc = variable_set (varname, VART_BOOL, &t); + if (rc == VAR_ERR_NOTDEF && strncmp (varname, "no", 2) == 0) + { + t = 0; + varname += 2; + rc = variable_set (varname, VART_BOOL, &t); + } + + switch (rc) + { + case VAR_OK: + break; + + case VAR_ERR_NOTDEF: + lerror (&@1, _("no such variable: %s"), varname); + break; + + case VAR_ERR_BADTYPE: + lerror (&@1, _("%s is not a boolean variable"), varname); + break; + + case VAR_ERR_BADVALUE: + lerror (&@1, _("%s: setting is not allowed"), $1); + break; + + case VAR_ERR_GDBM: + dberror ("%s", _("can't set variable")); + break; + + default: + lerror (&@1, _("unexpected error setting %s: %d"), $1, rc); + } + free ($1); + } + | T_IDENT '=' string + { + int rc = variable_set ($1, VART_STRING, $3); + switch (rc) + { + case VAR_OK: + break; + + case VAR_ERR_NOTDEF: + lerror (&@1, _("no such variable: %s"), $1); + break; + + case VAR_ERR_BADTYPE: + lerror (&@1, _("%s: bad variable type"), $1); + break; + + case VAR_ERR_BADVALUE: + lerror (&@1, _("%s: value %s is not allowed"), $1, $3); + break; + + default: + lerror (&@1, _("unexpected error setting %s: %d"), $1, rc); + } + free ($1); + free ($3); + } + ; + +varlist : var + | varlist var + ; + +var : T_IDENT + { + int rc = variable_unset ($1); + switch (rc) + { + case VAR_OK: + break; + + case VAR_ERR_NOTDEF: + lerror (&@1, _("no such variable: %s"), $1); + break; + + case VAR_ERR_BADVALUE: + lerror (&@1, _("%s: variable cannot be unset"), $1); + break; + } + free ($1); + } + ; + +%% + +void +terror (const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + vlerror (&yylloc, fmt, ap); + va_end (ap); +} + +/* gdbmshell-specific version of gdbm_perror */ +void +dberror (char const *fmt, ...) +{ + int ec = errno; + va_list ap; + + if (gdbm_error_is_masked (gdbm_errno)) + return; + if (!interactive ()) + fprintf (stderr, "%s: ", progname); + YY_LOCATION_PRINT (stderr, yylloc); + fprintf (stderr, ": "); + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + fprintf (stderr, ": %s", gdbm_strerror (gdbm_errno)); + if (gdbm_check_syserr (gdbm_errno)) + fprintf (stderr, ": %s", strerror (ec)); + fputc ('\n', stderr); +} + +void +yyerror (char const *s) +{ + terror ("%s", s); +} + +void +gram_trace (int n) +{ +#if GDBMTOOL_DEBUG + yydebug = 1; +#endif +} diff --git a/tools/input-argv.c b/tools/input-argv.c new file mode 100644 index 0000000..acb6544 --- /dev/null +++ b/tools/input-argv.c @@ -0,0 +1,132 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 2018-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +#include "gdbmtool.h" +#include <stdlib.h> + +struct instream_argv +{ + struct instream base; /* Base structure */ + int argc; /* Number of arguments */ + char **argv; /* Vector of arguments */ + int idx; /* Index of the current argument */ + char *cur; /* Current position in argv[idx] */ + int delim; /* True if cur points to a delimiter */ + int quote; /* True if the argument must be quoted */ +}; + +static ssize_t +instream_argv_read (instream_t istr, char *buf, size_t size) +{ + size_t total = 0; + struct instream_argv *i = (struct instream_argv*)istr; + char const specials[] = " \"\t\n[]{},"; + char const escapable[] = "\\\""; + + while (total < size) + { + if (*i->cur == 0) + { + if (i->quote) + { + buf[total++] = '"'; + i->quote = 0; + continue; + } + + if (i->idx == i->argc) + { + if (!i->delim) + { + i->cur = "\n"; + i->delim = 1; + } + else + break; + } + else if (!i->delim) + { + i->cur = " "; + i->delim = 1; + } + else + { + size_t len; + i->cur = i->argv[i->idx++]; + i->delim = 0; + len = strlen (i->cur); + if (len > 1 && i->cur[0] == '"' && i->cur[len-1] == '"') + i->quote = 0; + else if (i->cur[strcspn (i->cur, specials)]) + { + buf[total++] = '"'; + i->quote = 1; + continue; + } + else + i->quote = 0; + } + } + + if (strchr (escapable, *i->cur)) + { + if (total + 2 > size) + break; + buf[total++] = '\\'; + } + buf[total++] = *i->cur++; + } + return total; +} + +static void +instream_argv_close (instream_t istr) +{ + struct instream_argv *i = (struct instream_argv *)istr; + free (i); +} + +static int +instream_argv_eq (instream_t a, instream_t b) +{ + return 0; +} + +instream_t +instream_argv_create (int argc, char **argv) +{ + struct instream_argv *istr; + + istr = emalloc (sizeof *istr); + istr->base.in_name = "argv"; + istr->base.in_inter = 0; + istr->base.in_read = instream_argv_read; + istr->base.in_close = instream_argv_close; + istr->base.in_eq = instream_argv_eq; + istr->base.in_history_size = NULL; + istr->base.in_history_get = NULL; + + istr->argc = argc; + istr->argv = argv; + istr->idx = 0; + istr->cur = ""; + istr->delim = 1; + istr->quote = 0; + + return (instream_t) istr; +} + + diff --git a/tools/input-file.c b/tools/input-file.c new file mode 100644 index 0000000..0c83555 --- /dev/null +++ b/tools/input-file.c @@ -0,0 +1,90 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 2018-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +#include "gdbmtool.h" + +struct instream_file +{ + struct instream base; /* Base structure */ + FILE *fp; /* Opened file */ + dev_t dev; /* Device number */ + ino_t ino; /* Inode number */ +}; + +static ssize_t +instream_file_read (instream_t istr, char *buf, size_t size) +{ + struct instream_file *file = (struct instream_file *)istr; + return fread (buf, 1, size, file->fp); +} + +static void +instream_file_close (instream_t istr) +{ + struct instream_file *file = (struct instream_file *)istr; + fclose (file->fp); + free (file->base.in_name); + free (file); +} + +static int +instream_file_eq (instream_t a, instream_t b) +{ + struct instream_file *file_a = (struct instream_file *)a; + struct instream_file *file_b = (struct instream_file *)b; + return file_a->dev == file_b->dev && file_a->ino == file_b->ino; +} + +instream_t +instream_file_create (char const *name) +{ + struct instream_file *istr; + struct stat st; + FILE *fp; + + if (stat (name, &st)) + { + terror (_("cannot open `%s': %s"), name, strerror (errno)); + return NULL; + } + else if (!S_ISREG (st.st_mode)) + { + terror (_("%s is not a regular file"), name); + return NULL; + } + + fp = fopen (name, "r"); + if (!fp) + { + terror (_("cannot open %s for reading: %s"), name, + strerror (errno)); + return NULL; + } + + istr = emalloc (sizeof *istr); + istr->base.in_name = estrdup (name); + istr->base.in_inter = 0; + istr->base.in_read = instream_file_read; + istr->base.in_close = instream_file_close; + istr->base.in_eq = instream_file_eq; + istr->base.in_history_size = NULL; + istr->base.in_history_get = NULL; + istr->fp = fp; + istr->dev = st.st_dev; + istr->ino = st.st_ino; + + return (instream_t) istr; +} diff --git a/tools/input-null.c b/tools/input-null.c new file mode 100644 index 0000000..7bc95ce --- /dev/null +++ b/tools/input-null.c @@ -0,0 +1,53 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 2018-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +#include "gdbmtool.h" + +static ssize_t +instream_null_read (instream_t istr, char *buf, size_t size) +{ + return 0; +} + +static void +instream_null_close (instream_t istr) +{ + free (istr); +} + +static int +instream_null_eq (instream_t a, instream_t b) +{ + return a == b; +} + +instream_t +instream_null_create (void) +{ + struct instream *istr; + + istr = emalloc (sizeof *istr); + istr->in_name = "null"; + istr->in_inter = 0; + istr->in_read = instream_null_read; + istr->in_close = instream_null_close; + istr->in_eq = instream_null_eq; + istr->in_history_size = NULL; + istr->in_history_get = NULL; + + return istr; +} + diff --git a/tools/input-rl.c b/tools/input-rl.c new file mode 100644 index 0000000..6d91bd5 --- /dev/null +++ b/tools/input-rl.c @@ -0,0 +1,223 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 2016-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +#include "gdbmtool.h" +#include <readline/readline.h> +#include <readline/history.h> + +static char *pre_input_line; + +static int +pre_input (void) +{ + if (pre_input_line) + { + rl_insert_text (pre_input_line); + free (pre_input_line); + pre_input_line = NULL; + rl_redisplay (); + } + return 0; +} + +static int +retrieve_history (char *str) +{ + char *out; + int rc; + + rc = history_expand (str, &out); + switch (rc) + { + case -1: + yyerror (out); + free (out); + return 1; + + case 0: + free (out); + break; + + case 1: + pre_input_line = out; + return 1; + + case 2: + printf ("%s\n", out); + free (out); + return 1; + } + return 0; +} + +#define HISTFILE_PREFIX "~/." +#define HISTFILE_SUFFIX "_history" + +static char *history_file_name; + +static char * +get_history_file_name (void) +{ + if (!history_file_name) + { + char *hname; + + hname = emalloc (sizeof HISTFILE_PREFIX + strlen (rl_readline_name) + + sizeof HISTFILE_SUFFIX - 1); + strcpy (hname, HISTFILE_PREFIX); + strcat (hname, rl_readline_name); + strcat (hname, HISTFILE_SUFFIX); + history_file_name = tildexpand (hname); + free (hname); + } + return history_file_name; +} + +static char ** +shell_completion (const char *text, int start, int end) +{ + char **matches; + + matches = (char **) NULL; + + /* If this word is at the start of the line, then it is a command + to complete. Otherwise it is the name of a file in the current + directory. */ + if (start == 0) + matches = rl_completion_matches (text, command_generator); + + return (matches); +} + +static void +instream_readline_close (instream_t istr) +{ + if (history_file_name) + { + write_history (history_file_name); + free (history_file_name); + history_file_name = NULL; + } + free (istr); +} + +static ssize_t +stdin_read_readline (instream_t istr, char *buf, size_t size) +{ + static char *input_line; + static size_t input_length; + static size_t input_off; +#define input_ptr() (input_line + input_off) +#define input_size() (input_length - input_off) + size_t len = input_size (); + if (!len) + { + if (input_line) + { + newline: + free (input_line); + input_line = NULL; + buf[0] = '\n'; + return 1; + } + else + { + char *prompt; + again: + prompt = make_prompt (); + input_line = readline (prompt); + free (prompt); + if (!input_line) + return 0; + input_length = strlen (input_line); + input_off = 0; + if (input_length) + { + if (retrieve_history (input_line)) + { + free (input_line); + goto again; + } + } + else + goto newline; + len = input_size (); + add_history (input_line); + } + } + + if (len > size) + len = size; + memcpy (buf, input_ptr (), len); + input_off += len; + + return len; +} + +static ssize_t +instream_readline_read (instream_t istr, char *buf, size_t size) +{ + if (istr->in_inter) + return stdin_read_readline (istr, buf, size); + return fread (buf, 1, size, stdin); +} + +static int +instream_readline_eq (instream_t a, instream_t b) +{ + return 0; +} + +static int +instream_readline_history_size (instream_t istr) +{ + return history_length; +} + +static const char * +instream_readline_history_get (instream_t instr, int n) +{ + if (n < history_length) + return history_list ()[n]->line; + return NULL; +} + +instream_t +instream_readline_create (void) +{ + struct instream *istr; + + if (isatty (fileno (stdin))) + { + istr = emalloc (sizeof *istr); + istr->in_name = "stdin"; + istr->in_inter = 1; + istr->in_read = instream_readline_read; + istr->in_close = instream_readline_close; + istr->in_eq = instream_readline_eq; + istr->in_history_size = instream_readline_history_size; + istr->in_history_get = instream_readline_history_get; + + /* Allow conditional parsing of the ~/.inputrc file. */ + rl_readline_name = (char *) progname; + rl_attempted_completion_function = shell_completion; + rl_pre_input_hook = pre_input; + read_history (get_history_file_name ()); + } + else + istr = instream_stdin_create (); + return istr; +} diff --git a/tools/input-std.c b/tools/input-std.c new file mode 100644 index 0000000..ca3059c --- /dev/null +++ b/tools/input-std.c @@ -0,0 +1,57 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 2016-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +#include "gdbmtool.h" + +static ssize_t +instream_stdin_read (instream_t istr, char *buf, size_t size) +{ + if (istr->in_inter) + print_prompt_at_bol (); + if (fgets (buf, size, stdin) == NULL) + return 0; + return strlen (buf); +} + +static void +instream_stdin_close (instream_t istr) +{ + free (istr); +} + +static int +instream_stdin_eq (instream_t a, instream_t b) +{ + return 0; +} + +instream_t +instream_stdin_create (void) +{ + struct instream *istr; + + istr = emalloc (sizeof *istr); + istr->in_name = "stdin"; + istr->in_inter = isatty (fileno (stdin)); + istr->in_read = instream_stdin_read; + istr->in_close = instream_stdin_close; + istr->in_eq = instream_stdin_eq; + istr->in_history_size = NULL; + istr->in_history_get = NULL; + + return istr; +} + diff --git a/tools/lex.l b/tools/lex.l new file mode 100644 index 0000000..3d63687 --- /dev/null +++ b/tools/lex.l @@ -0,0 +1,754 @@ +%{ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 1990-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +#include "gdbmtool.h" +#include "gram.h" + +struct context /* Input context */ +{ + struct context *parent; /* Pointer to the parent context */ + struct locus locus; /* Locus */ + struct point point; + YY_BUFFER_STATE buf; /* Buffer */ + instream_t input; +}; + +static struct context *context_tos; + +/* Advance locus to the next line */ +void +advance_line (void) +{ + ++context_tos->point.line; + context_tos->point.col = 0; +} + +#define YY_USER_ACTION \ + do \ + { \ + if (YYSTATE == 0) \ + { \ + yylloc.beg = context_tos->point; \ + yylloc.beg.col++; \ + } \ + context_tos->point.col += yyleng; \ + yylloc.end = context_tos->point; \ + } \ + while (0); + +#undef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + do \ + { \ + if (context_tos) \ + result = instream_read (context_tos->input, buf, max_size); \ + else \ + result = 0; \ + } \ + while (0); + +void string_begin (void); +void string_add (const char *s, int l); +void string_addc (int c); +char *string_end (int empty_null); +int unescape (int c); + +struct file_name +{ + struct file_name *next; + char str[1]; +}; + +static struct file_name *file_head; + +static void +file_names_free (void) +{ + while (file_head) + { + struct file_name *next = file_head->next; + free (file_head); + file_head = next; + } +} + +char * +file_name_alloc (char const *s) +{ + struct file_name *f = emalloc (sizeof (*f) + strlen (s)); + strcpy (f->str, s); + f->next = file_head; + if (!file_head) + atexit (file_names_free); + file_head = f; + return f->str; +} + +int +interactive (void) +{ + return context_tos && instream_interactive (context_tos->input); +} + +int +input_history_size (void) +{ + return context_tos ? instream_history_size (context_tos->input) : 0; +} + +const char * +input_history_get (int n) +{ + return context_tos ? instream_history_get (context_tos->input, n) : NULL; +} + +const char * +input_stream_name (void) +{ + return context_tos ? context_tos->input->in_name : "NULL"; +} + +static struct context * +input_context_lookup (instream_t istr) +{ + struct context *cp; + + for (cp = context_tos; cp; cp = cp->parent) + if (instream_eq (cp->input, istr)) + break; + return cp; +} + +int +input_context_push (instream_t input) +{ + struct context *cp; + + cp = input_context_lookup (input); + if (cp) + { + terror (_("recursive sourcing")); + if (cp->parent) + lerror (&cp->locus, _("%s already sourced here"), + instream_name (input)); + return 1; + } + + yy_switch_to_buffer (yy_create_buffer (NULL, YY_BUF_SIZE)); + + /* Create new context */ + + cp = ecalloc (1, sizeof (*cp)); + cp->locus = yylloc; + cp->point.file = file_name_alloc (instream_name (input)); + cp->point.line = 1; + cp->point.col = 0; + + cp->input = input; + cp->buf = YY_CURRENT_BUFFER; + cp->parent = context_tos; + context_tos = cp; + + return 0; +} + +void +lex_trace (int n) +{ + yy_flex_debug = n; +} + +int +input_context_pop (void) +{ + struct context *cp; + + if (!context_tos) + return 1; + instream_close (context_tos->input); + cp = context_tos->parent; + free (context_tos); + context_tos = cp; + yy_delete_buffer (YY_CURRENT_BUFFER); + if (!cp) + return 1; + + yylloc = cp->locus; + yy_switch_to_buffer (cp->buf); + + return 0; +} + +void +input_context_drain (void) +{ + while (!input_context_pop ()) + ; +} + +static int +t_num (int base) +{ + long n; + errno = 0; + n = strtol (yytext, NULL, base); + if (errno) + { + lerror (&yylloc, "%s", strerror (errno)); + return T_BOGUS; + } + if (n < INT_MIN || n > INT_MAX) + { + lerror (&yylloc, "value out of range"); + return T_BOGUS; + } + yylval.num = n; + return T_NUM; +} + +%} + +%option noinput +%option nounput +%option nodefault + +%x CMD STR MLSTR DEF SHELLWS SHELL SHSTR SHQ + +WS [ \t][ \t]* +IDENT [a-zA-Z_][a-zA-Z_0-9-]* +N [0-9][0-9]* +P [1-9][0-9]* +X [0-9a-fA-F] +O [0-7] + +%% +^[ \t]*#[ \t]*line[ \t].*\n { + char *p; + char *file = NULL; + int line, len; + + for (p = strchr (yytext, '#') + 1; *p == ' ' || *p == '\t'; p++); + p += 4; + for (; *p == ' ' || *p == '\t'; p++); + + line = strtol (p, &p, 10); + for (; *p == ' ' || *p == '\t'; p++); + + if (*p == '"') + { + p++; + len = strcspn (p, "\""); + if (p[len] == 0) + { + yyerror (_("invalid #line statement")); + REJECT; + } + file = emalloc (len + 1); + memcpy (file, p, len); + file[len] = 0; + for (p += len + 1; *p == ' ' || *p == '\t'; p++); + } + if (*p != '\n' ) + { + yyerror (_("invalid #line statement")); + free (file); + REJECT; + } + if (file) + context_tos->point.file = file; + context_tos->point.line = line; + context_tos->point.col = 0; +} +#.*\n advance_line (); +#.* /* end-of-file comment */; + +<INITIAL>{ +\? { BEGIN (CMD); + return command_lookup ("help", &yylloc, &yylval.cmd); } +\! { + BEGIN (SHELLWS); + string_begin (); + return command_lookup ("shell", &yylloc, &yylval.cmd); + } +{IDENT} { + int t; + t = command_lookup (yytext, &yylloc, &yylval.cmd); + if (t == T_SHELL) + BEGIN (SHELLWS); + else + BEGIN (CMD); + return t; + } +{WS} ; +} + +<DEF>{ +off { return T_OFF; } +pad { return T_PAD; } +0[xX]{X}{X}* { return t_num (8); }; +0{O}{O}* { return t_num (16); }; +0|{P} { return t_num (10); }; +{IDENT} { if ((yylval.type = datadef_lookup (yytext))) + return T_TYPE; + else + { + yylval.string = estrdup (yytext); + return T_IDENT; + } + } +[^ \"\t\n;\[\]{},=]+ { yylval.string = estrdup (yytext); return T_WORD; } +\n { advance_line (); } +{WS} ; +. return yytext[0]; +} + +<CMD>{ +{IDENT} { yylval.string = estrdup (yytext); return T_IDENT; } +[^ \"\t\n;\[\]{},=]+ { yylval.string = estrdup (yytext); return T_WORD; } +\"[^\\\"\n]*\" { + yylval.string = emalloc (yyleng - 1); + memcpy (yylval.string, yytext+1, yyleng-2); + yylval.string[yyleng-2] = 0; + return T_WORD; } +\"[^\\\"\n]*\\\n { + advance_line (); + string_begin (); + string_add (yytext + 1, yyleng - 2); + BEGIN (MLSTR); } +\"[^\\\"\n]*\\. { + string_begin (); + string_add (yytext + 1, yyleng - 3); + string_addc (unescape (yytext[yyleng-1])); + BEGIN (STR); } +; { BEGIN (INITIAL); return ';'; } +{WS} ; +} + +<STR,MLSTR>{ +[^\\\"\n]*\" { if (yyleng > 1) + string_add (yytext, yyleng - 1); + yylval.string = string_end (FALSE); + BEGIN (CMD); + return T_WORD; } +[^\\\"\n]*\\\n { advance_line (); + string_add (yytext, yyleng - 1); } +[^\\\"\n]*\\. { string_add (yytext, yyleng - 2); + string_addc (unescape (yytext[yyleng-1])); } +} + +<SHELLWS>{ +{WS} { BEGIN(SHELL); } +"\n" { + BEGIN (INITIAL); + advance_line (); + return '\n'; + } +} + +<SHELL>{ +\\\n { advance_line (); string_add (yytext, yyleng); } +\\. { string_add (yytext, yyleng); } +\'[^\'\n]*\' { string_add (yytext, yyleng); } +\'[^\'\n]*\n { advance_line (); + string_add (yytext, yyleng); + BEGIN (SHQ); } +\"[^\\\"\n]*\" { string_add (yytext, yyleng); } +\"[^\\\"\n]*\n { advance_line (); + string_add (yytext, yyleng); + BEGIN (SHSTR); } +\"[^\\\"\n]*\\. { string_add (yytext, yyleng); + BEGIN (SHSTR); } +"\n" { + BEGIN (INITIAL); + advance_line (); + yyless (0); + if ((yylval.string = string_end (TRUE)) != NULL) + return T_WORD; + } +. string_addc (yytext[0]); +} + +<SHSTR>{ +[^\\\"]*\\. { string_add (yytext, yyleng); } +[^\\\"]*\" { string_add (yytext, yyleng); + BEGIN (SHELL); } +} + +<SHQ>{ +[^\'\n]*\n { advance_line (); + string_add (yytext, yyleng); } +[^\'\n]*\' { string_add (yytext, yyleng); + BEGIN (SHELL); } +} + +<*>\n { BEGIN (INITIAL); advance_line (); return '\n'; } + +<INITIAL,CMD,DEF>. return yytext[0]; +%% + +int +yywrap (void) +{ + return input_context_pop (); +} + +void +begin_def (void) +{ + BEGIN (DEF); +} + +void +end_def (void) +{ + BEGIN (CMD); +} + +void +print_prompt_at_bol (void) +{ + if (YY_AT_BOL ()) + { + char *s = make_prompt (); + fputs (s, stdout); + fflush (stdout); + free (s); + } +} + + +struct strseg +{ + struct strseg *next; + int len; + char ptr[1]; +}; + +static struct strseg *strseg_head, *strseg_tail; + +void +string_begin (void) +{ + strseg_head = strseg_tail = NULL; +} + +void +strseg_attach (struct strseg *seg) +{ + seg->next = NULL; + if (strseg_tail) + strseg_tail->next = seg; + else + strseg_head = seg; + strseg_tail = seg; +} + +void +string_add (const char *s, int l) +{ + struct strseg *seg = emalloc (sizeof (*seg) + l); + memcpy (seg->ptr, s, l); + seg->len = l; + strseg_attach (seg); +} + +void +string_addc (int c) +{ + struct strseg *seg = emalloc (sizeof (*seg)); + seg->ptr[0] = c; + seg->len = 1; + strseg_attach (seg); +} + +/* + * Compose the collected string segments into a nul-terminated string. + * Return the allocated string. + * If EMPTY_NULL is TRUE and the resulting string length is 0, return + * NULL instead. + */ +char * +string_end (int empty_null) +{ + int len = 0; + struct strseg *seg; + char *ret, *p; + + for (seg = strseg_head; seg; seg = seg->next) + len += seg->len; + + if (len == 0 && empty_null) + ret = NULL; + else + { + ret = emalloc (len + 1); + p = ret; + for (seg = strseg_head; seg; ) + { + struct strseg *next = seg->next; + memcpy (p, seg->ptr, seg->len); + p += seg->len; + free (seg); + seg = next; + } + *p = 0; + } + strseg_head = strseg_tail = NULL; + + return ret; +} + +static char transtab[] = "\\\\\"\"a\ab\bf\fn\nr\rt\tv\v"; + +int +unescape (int c) +{ + char *p; + + for (p = transtab; *p; p += 2) + { + if (*p == c) + return p[1]; + } + return c; +} + +int +escape (int c) +{ + char *p; + for (p = transtab + sizeof (transtab) - 2; p > transtab; p -= 2) + { + if (*p == c) + return p[-1]; + } + return 0; +} + +void +locus_print (FILE *fp, struct locus const *loc) +{ + if (loc->beg.file) + { + if (loc->beg.col == 0) + fprintf (fp, "%s:%u", + loc->beg.file, + loc->beg.line); + else if (strcmp (loc->beg.file, loc->end.file)) + fprintf (fp, "%s:%u.%u-%s:%u.%u", + loc->beg.file, + loc->beg.line, loc->beg.col, + loc->end.file, + loc->end.line, loc->end.col); + else if (loc->beg.line != loc->end.line) + fprintf (fp, "%s:%u.%u-%u.%u", + loc->beg.file, + loc->beg.line, loc->beg.col, + loc->end.line, loc->end.col); + else if (loc->beg.col != loc->end.col) + fprintf (fp, "%s:%u.%u-%u", + loc->beg.file, + loc->beg.line, loc->beg.col, + loc->end.col); + else + fprintf (fp, "%s:%u.%u", + loc->beg.file, + loc->beg.line, + loc->beg.col); + } +} + +void +vlerror (struct locus *loc, const char *fmt, va_list ap) +{ + if (!interactive ()) + fprintf (stderr, "%s: ", progname); + if (loc && loc->beg.file) + { + YY_LOCATION_PRINT (stderr, *loc); + fprintf (stderr, ": "); + } + vfprintf (stderr, fmt, ap); + fputc ('\n', stderr); +} + +void +lerror (struct locus *loc, const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + vlerror (loc, fmt, ap); + va_end (ap); +} + + +static struct slist * +pe_file_name (void) +{ + char *name = NULL; + variable_get ("filename", VART_STRING, (void**) &name); + return name ? slist_new (name) : NULL; +} + +static struct slist * +pe_program_name (void) +{ + return slist_new (progname); +} + +static struct slist * +pe_package_name (void) +{ + return slist_new (PACKAGE_NAME); +} + +static struct slist * +pe_program_version (void) +{ + return slist_new (PACKAGE_VERSION); +} + +static struct slist * +pe_space (void) +{ + return slist_new (" "); +} + +struct prompt_exp +{ + int ch; + struct slist *(*fun) (void); +}; + +struct prompt_exp prompt_exp[] = { + { 'f', pe_file_name }, + { 'p', pe_program_name }, + { 'P', pe_package_name }, + { 'v', pe_program_version }, + { '_', pe_space }, + { 0 } +}; + +static int +expand_char (int c, struct slist **tailp) +{ + struct prompt_exp *p; + + if (c && c != '%') + { + for (p = prompt_exp; p->ch; p++) + { + if (c == p->ch) + { + struct slist *s = p->fun (); + if (s) + slist_insert (tailp, s); + return 0; + } + } + } + return 1; +} + +char const * +psname (void) +{ + switch (YYSTATE) + { + case DEF: + case MLSTR: + case SHSTR: + case SHQ: + return "ps2"; + + default: + return "ps1"; + } +} + +char * +make_prompt (void) +{ + const char *s; + const char *prompt; + struct slist *head = NULL, *tail = NULL, *p; + char *ret, *end; + size_t len; + + switch (variable_get (psname (), VART_STRING, (void *) &prompt)) + { + case VAR_OK: + break; + + case VAR_ERR_NOTSET: + return NULL; + + default: + abort (); + } + + for (s = prompt; *s; ) + { + if (*s == '%' && s[1]) + { + if (s > prompt) + { + slist_insert (&tail, slist_new_l (prompt, s - prompt)); + if (!head) + head = tail; + } + if (expand_char (s[1], &tail) == 0) + { + if (!head) + head = tail; + prompt = s + 2; + } + else + prompt = s; + s += 2; + } + else + ++s; + } + + if (s > prompt) + { + slist_insert (&tail, slist_new_l (prompt, s - prompt)); + if (!head) + head = tail; + } + + len = 0; + for (p = head; p; p = p->next) + len += strlen (p->str); + + ret = emalloc (len + 1); + end = ret; + for (p = head; p; p = p->next) + { + s = p->str; + while (*s) + *end++ = *s++; + } + *end = 0; + + slist_free (head); + + return ret; +} + diff --git a/tools/mem.c b/tools/mem.c new file mode 100644 index 0000000..8b2da75 --- /dev/null +++ b/tools/mem.c @@ -0,0 +1,107 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 2011-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +# include "autoconf.h" +# include "gdbm.h" +# include "gdbmapp.h" +# include "gdbmdefs.h" + +void +ealloc_die (void) +{ + error ("%s", strerror (ENOMEM)); + exit (EXIT_FATAL); +} + +void * +emalloc (size_t size) +{ + void *p = malloc (size); + if (!p) + ealloc_die (); + return p; +} + +void * +erealloc (void *ptr, size_t size) +{ + void *newptr = realloc (ptr, size); + if (!newptr) + ealloc_die (); + return newptr; +} + +void * +ecalloc (size_t nmemb, size_t size) +{ + void *p = calloc (nmemb, size); + if (!p) + ealloc_die (); + return p; +} + +void * +ezalloc (size_t size) +{ + return ecalloc (1, size); +} + +char * +estrdup (const char *str) +{ + char *p; + + if (!str) + return NULL; + p = emalloc (strlen (str) + 1); + strcpy (p, str); + return p; +} + +void * +e2nrealloc (void *p, size_t *pn, size_t s) +{ + size_t n = *pn; + char *newp; + + if (!p) + { + if (!n) + { + /* The approximate size to use for initial small + allocation requests, when the invoking code + specifies an old size of zero. 64 bytes is + the largest "small" request for the + GNU C library malloc. */ + enum { DEFAULT_MXFAST = 64 }; + + n = DEFAULT_MXFAST / s; + n += !n; + } + } + else if ((size_t) -1 / 3 * 2 / s <= n) + { + ealloc_die (); + } + else + n += (n + 1) / 2; + + newp = erealloc (p, n * s); + *pn = n; + return newp; +} + + diff --git a/tools/parseopt.c b/tools/parseopt.c new file mode 100644 index 0000000..0169ae5 --- /dev/null +++ b/tools/parseopt.c @@ -0,0 +1,702 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 2011-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +# include "autoconf.h" +# include "gdbm.h" +# include "gdbmapp.h" +# include "gdbmdefs.h" +# include <stdio.h> +# include <stdarg.h> +# include <errno.h> +# include <string.h> +# include <ctype.h> +# ifdef HAVE_GETOPT_H +# include <getopt.h> +# endif + +static int argc; +static char **argv; + +static struct gdbm_option *option_tab; +static size_t option_count; +static size_t option_max; +static char *short_options; +static size_t short_option_count; +static size_t short_option_max; +#ifdef HAVE_GETOPT_LONG +static struct option *long_options; +static size_t long_option_count; +static size_t long_option_max; +#endif + +#define OPT_USAGE -2 + +struct gdbm_option parseopt_default_options[] = { + { 0, NULL, NULL, "" }, + { 'h', "help", NULL, N_("give this help list") }, + { 'V', "version", NULL, N_("print program version") }, + { OPT_USAGE, "usage", NULL, N_("give a short usage message") }, + { 0 } +}; + +#define OPT_END(opt) \ + ((opt)->opt_short == 0 && (opt)->opt_long == 0 && (opt)->opt_descr == NULL) +#define IS_OPTION(opt) \ + ((opt)->opt_short || (opt)->opt_long) +#define IS_GROUP_HEADER(opt) \ + (!IS_OPTION(opt) && (opt)->opt_descr) +#define IS_VALID_SHORT_OPTION(opt) \ + ((opt)->opt_short > 0 && (opt)->opt_short < 127 && \ + isalnum ((opt)->opt_short)) +#define IS_VALID_LONG_OPTION(opt) \ + ((opt)->opt_long != NULL) + + +static int +optcmp (const void *a, const void *b) +{ + struct gdbm_option const *ap = (struct gdbm_option const *)a; + struct gdbm_option const *bp = (struct gdbm_option const *)b; + + while (ap->opt_flags & PARSEOPT_ALIAS) + ap--; + while (bp->opt_flags & PARSEOPT_ALIAS) + bp--; + + if (IS_VALID_SHORT_OPTION(ap) && IS_VALID_SHORT_OPTION(bp)) + return ap->opt_short - bp->opt_short; + if (IS_VALID_LONG_OPTION(ap) && IS_VALID_LONG_OPTION(bp)) + return strcmp (ap->opt_long, bp->opt_long); + if (IS_VALID_LONG_OPTION(ap)) + return 1; + return -1; +} + +static void +sort_options (int start, int count) +{ + qsort (option_tab + start, count, sizeof (option_tab[0]), optcmp); +} + +static size_t +sort_group (size_t start) +{ + size_t i; + + for (i = start; i < option_count && !IS_GROUP_HEADER (&option_tab[i]); i++) + ; + sort_options (start, i - start); + return i + 1; +} + +static void +sort_all_options (void) +{ + size_t start; + + /* Ensure sane start of options. This is necessary because optcmp backs up + until it finds an element with cleared PARSEOPT_ALIAS flag bit. */ + option_tab[0].opt_flags &= PARSEOPT_ALIAS; + for (start = 0; start < option_count; ) + { + if (IS_GROUP_HEADER (&option_tab[start])) + start = sort_group (start + 1); + else + start = sort_group (start); + } +} + +static void +add_options (struct gdbm_option *options) +{ + size_t optcnt = 0; + size_t argcnt = 0; + size_t count = 0; + struct gdbm_option *opt; + + for (opt = options; !OPT_END(opt); opt++) + { + count++; + if (IS_OPTION(opt)) + { + optcnt++; + if (opt->opt_arg) + argcnt++; + } + } + + if (option_count + count + 1 > option_max) + { + option_max = option_count + count + 1; + option_tab = erealloc (option_tab, + sizeof (option_tab[0]) * option_max); + } + +#ifdef HAVE_GETOPT_LONG + if (long_option_count + optcnt + 1 > long_option_max) + { + long_option_max = long_option_count + optcnt + 1; + long_options = erealloc (long_options, + sizeof (long_options[0]) * long_option_max); + } +#endif + if (short_option_count + optcnt + argcnt + 1 > short_option_max) + { + short_option_max = short_option_count + optcnt + argcnt + 1; + short_options = erealloc (short_options, + sizeof (short_options[0]) * short_option_max); + } + + for (opt = options; !OPT_END(opt); opt++) + { + option_tab[option_count++] = *opt; + if (!IS_OPTION (opt)) + continue; + if (IS_VALID_SHORT_OPTION (opt)) + { + short_options[short_option_count++] = opt->opt_short; + if (opt->opt_arg) + short_options[short_option_count++] = ':'; + } +#ifdef HAVE_GETOPT_LONG + if (IS_VALID_LONG_OPTION (opt)) + { + long_options[long_option_count].name = opt->opt_long; + long_options[long_option_count].has_arg = opt->opt_arg != NULL; + long_options[long_option_count].flag = NULL; + long_options[long_option_count].val = opt->opt_short; + long_option_count++; + } +#endif + } + short_options[short_option_count] = 0; +#ifdef HAVE_GETOPT_LONG + memset (&long_options[long_option_count], 0, + sizeof long_options[long_option_count]); +#endif +} + +void +parseopt_free (void) +{ + free (option_tab); + option_tab = NULL; + free (short_options); + short_options = NULL; + short_option_count = short_option_max = 0; +#ifdef HAVE_GETOPT_LONG + free (long_options); + long_options = NULL; + long_option_count = long_option_max = 0; +#endif +} + +int +parseopt_first (int pc, char **pv, struct gdbm_option *opts) +{ + parseopt_free (); + add_options (opts); + add_options (parseopt_default_options); + opterr = 0; + argc = pc; + argv = pv; + return parseopt_next (); +} + +static unsigned short_opt_col = 2; +static unsigned long_opt_col = 6; +static unsigned doc_opt_col = 2; /* FIXME: Not used: there are no doc + options in this implementation */ +static unsigned header_col = 1; +static unsigned opt_doc_col = 29; +static unsigned usage_indent = 12; +static unsigned rmargin = 79; + +static unsigned dup_args = 0; +static unsigned dup_args_note = 1; + +enum usage_var_type + { + usage_var_column, + usage_var_bool + }; + +struct usage_var_def +{ + char *name; + unsigned *valptr; + enum usage_var_type type; +}; + +static struct usage_var_def usage_var[] = { + { "short-opt-col", &short_opt_col, usage_var_column }, + { "header-col", &header_col, usage_var_column }, + { "opt-doc-col", &opt_doc_col, usage_var_column }, + { "usage-indent", &usage_indent, usage_var_column }, + { "rmargin", &rmargin, usage_var_column }, + { "dup-args", &dup_args, usage_var_bool }, + { "dup-args-note", &dup_args_note, usage_var_bool }, + { "long-opt-col", &long_opt_col, usage_var_column }, + { "doc-opt-col", &doc_opt_col, usage_var_column }, + { NULL } +}; + +static void +set_usage_var (char const *text, char **end) +{ + struct usage_var_def *p; + int boolval = 1; + char const *prog_name = parseopt_program_name ? parseopt_program_name : progname; + size_t len = strcspn (text, ",="); + char *endp; + + if (len > 3 && memcmp (text, "no-", 3) == 0) + { + text += 3; + len -= 3; + boolval = 0; + } + + for (p = usage_var; p->name; p++) + { + if (strlen (p->name) == len && memcmp (p->name, text, len) == 0) + break; + } + + endp = (char*) text + len; + if (p) + { + if (p->type == usage_var_bool) + { + if (*endp == '=') + { + if (prog_name) + fprintf (stderr, "%s: ", prog_name); + fprintf (stderr, + _("error in ARGP_HELP_FMT: improper usage of [no-]%s\n"), + p->name); + endp = strchr (text + len, ','); + } + else + *p->valptr = boolval; + } + else if (*endp == '=') + { + unsigned long val; + + errno = 0; + val = strtoul (text + len + 1, &endp, 10); + if (errno || (*endp && *endp != ',')) + { + if (prog_name) + fprintf (stderr, "%s: ", prog_name); + fprintf (stderr, + _("error in ARGP_HELP_FMT: bad value for %s"), + p->name); + if (endp) + { + fprintf (stderr, _(" (near %s)"), endp); + } + fputc ('\n', stderr); + } + else if (val > UINT_MAX) + { + if (prog_name) + fprintf (stderr, "%s: ", prog_name); + fprintf (stderr, + _("error in ARGP_HELP_FMT: %s value is out of range\n"), + p->name); + } + else + *p->valptr = val; + } + else + { + if (prog_name) + fprintf (stderr, "%s: ", prog_name); + fprintf (stderr, + _("%s: ARGP_HELP_FMT parameter requires a value\n"), + p->name); + } + } + else + { + if (prog_name) + fprintf (stderr, "%s: ", prog_name); + fprintf (stderr, + _("%s: Unknown ARGP_HELP_FMT parameter\n"), + text); + } + *end = endp; +} + +static void +init_usage_vars (void) +{ + char *fmt, *p; + + fmt = getenv ("ARGP_HELP_FMT"); + if (!fmt || !*fmt) + return; + + while (1) + { + set_usage_var (fmt, &p); + if (*p == 0) + break; + else if (*p == ',') + p++; + else + { + char const *prog_name = parseopt_program_name ? parseopt_program_name : progname; + if (prog_name) + fprintf (stderr, "%s: ", prog_name); + fprintf (stderr, _("ARGP_HELP_FMT: missing delimiter near %s\n"), + p); + break; + } + fmt = p; + } +} + +char *parseopt_program_name; +const char *program_bug_address = "<" PACKAGE_BUGREPORT ">"; +void (*parseopt_help_hook) (FILE *stream); + +static int argsused; + +static int +print_arg (WORDWRAP_FILE wf, struct gdbm_option *opt, int delim) +{ + if (opt->opt_arg) + { + argsused = 1; + return wordwrap_printf (wf, "%c%s", delim, + opt->opt_arg[0] ? gettext (opt->opt_arg) : ""); + } + return 0; +} + +size_t +print_option (WORDWRAP_FILE wf, size_t num) +{ + struct gdbm_option *opt = option_tab + num; + size_t next, i; + int delim; + int w; + + if (IS_GROUP_HEADER (opt)) + { + wordwrap_set_left_margin (wf, header_col); + wordwrap_set_right_margin (wf, rmargin); + if (opt->opt_descr[0]) + wordwrap_puts (wf, gettext (opt->opt_descr)); + wordwrap_putc (wf, '\n'); + return num + 1; + } + + /* count aliases */ + for (next = num + 1; + next < option_count && option_tab[next].opt_flags & PARSEOPT_ALIAS; + next++); + + if (opt->opt_flags & PARSEOPT_HIDDEN) + return next; + + wordwrap_set_left_margin (wf, short_opt_col); + w = 0; + for (i = num; i < next; i++) + { + if (IS_VALID_SHORT_OPTION (&option_tab[i])) + { + if (w) + wordwrap_write (wf, ", ", 2); + wordwrap_printf (wf, "-%c", option_tab[i].opt_short); + delim = ' '; + if (dup_args) + print_arg (wf, opt, delim); + w = 1; + } + } + +#ifdef HAVE_GETOPT_LONG + w = 0; + wordwrap_set_left_margin (wf, long_opt_col); + for (i = num; i < next; i++) + { + if (IS_VALID_LONG_OPTION (&option_tab[i])) + { + if (w) + wordwrap_write (wf, ", ", 2); + wordwrap_printf (wf, "--%s", option_tab[i].opt_long); + delim = '='; + if (dup_args) + print_arg (wf, opt, delim); + w = 1; + } + } +#endif + if (!dup_args) + print_arg (wf, opt, delim); + + wordwrap_set_left_margin (wf, opt_doc_col); + if (opt->opt_descr[0]) + wordwrap_puts (wf, gettext (opt->opt_descr)); + + return next; +} + +void +parseopt_print_help (void) +{ + unsigned i; + WORDWRAP_FILE wf; + + argsused = 0; + + init_usage_vars (); + + wf = wordwrap_fdopen (1); + + wordwrap_printf (wf, "%s %s [%s]... %s\n", _("Usage:"), + parseopt_program_name ? parseopt_program_name : progname, + _("OPTION"), + gettext (parseopt_program_args)); + + wordwrap_set_right_margin (wf, rmargin); + if (parseopt_program_doc && parseopt_program_doc[0]) + wordwrap_puts (wf, gettext (parseopt_program_doc)); + wordwrap_para (wf); + + sort_all_options (); + for (i = 0; i < option_count; ) + { + i = print_option (wf, i); + } + wordwrap_para (wf); + +#ifdef HAVE_GETOPT_LONG + if (argsused && dup_args_note) + { + wordwrap_set_left_margin (wf, 0); + wordwrap_set_right_margin (wf, rmargin); + wordwrap_puts (wf, _("Mandatory or optional arguments to long options are also mandatory or optional for any corresponding short options.")); + wordwrap_para (wf); + } +#endif + if (parseopt_help_hook) + parseopt_help_hook (stdout);//FIXME + + wordwrap_set_left_margin (wf, 0); + wordwrap_set_right_margin (wf, rmargin); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + wordwrap_printf (wf, _("Report bugs to %s.\n"), program_bug_address); + +#ifdef PACKAGE_URL + wordwrap_printf (wf, _("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL); +#endif +} + +static int +cmpidx_short (const void *a, const void *b) +{ + unsigned const *ai = (unsigned const *)a; + unsigned const *bi = (unsigned const *)b; + + return option_tab[*ai].opt_short - option_tab[*bi].opt_short; +} + +#ifdef HAVE_GETOPT_LONG +static int +cmpidx_long (const void *a, const void *b) +{ + unsigned const *ai = (unsigned const *)a; + unsigned const *bi = (unsigned const *)b; + struct gdbm_option const *ap = option_tab + *ai; + struct gdbm_option const *bp = option_tab + *bi; + return strcmp (ap->opt_long, bp->opt_long); +} +#endif + +void +print_usage (void) +{ + WORDWRAP_FILE wf; + unsigned i; + unsigned *idxbuf; + unsigned nidx; + + init_usage_vars (); + + idxbuf = ecalloc (option_count, sizeof (idxbuf[0])); + + wf = wordwrap_fdopen (1); + wordwrap_set_right_margin (wf, rmargin); + wordwrap_printf (wf, "%s %s ", _("Usage:"), + parseopt_program_name ? parseopt_program_name : progname); + wordwrap_next_left_margin (wf, usage_indent); + + /* Print a list of short options without arguments. */ + for (i = nidx = 0; i < option_count; i++) + if (IS_VALID_SHORT_OPTION (&option_tab[i]) && !option_tab[i].opt_arg) + idxbuf[nidx++] = i; + + if (nidx) + { + qsort (idxbuf, nidx, sizeof (idxbuf[0]), cmpidx_short); + + wordwrap_puts (wf, "[-"); + for (i = 0; i < nidx; i++) + { + wordwrap_putc (wf, option_tab[idxbuf[i]].opt_short); + } + wordwrap_putc (wf, ']'); + } + + /* Print a list of short options with arguments. */ + for (i = nidx = 0; i < option_count; i++) + { + if (IS_VALID_SHORT_OPTION (&option_tab[i]) && option_tab[i].opt_arg) + idxbuf[nidx++] = i; + } + + if (nidx) + { + qsort (idxbuf, nidx, sizeof (idxbuf[0]), cmpidx_short); + + for (i = 0; i < nidx; i++) + { + struct gdbm_option *opt = option_tab + idxbuf[i]; + const char *arg = gettext (opt->opt_arg); + + wordwrap_word_start (wf); + wordwrap_puts (wf, " [-"); + wordwrap_putc (wf, opt->opt_short); + wordwrap_putc (wf, ' '); + wordwrap_puts (wf, arg); + wordwrap_putc (wf, ']'); + wordwrap_word_end (wf); + } + } + +#ifdef HAVE_GETOPT_LONG + /* Print a list of long options */ + for (i = nidx = 0; i < option_count; i++) + { + if (IS_VALID_LONG_OPTION (&option_tab[i])) + idxbuf[nidx++] = i; + } + + if (nidx) + { + qsort (idxbuf, nidx, sizeof (idxbuf[0]), cmpidx_long); + + for (i = 0; i < nidx; i++) + { + struct gdbm_option *opt = option_tab + idxbuf[i]; + const char *arg = opt->opt_arg ? gettext (opt->opt_arg) : NULL; + + wordwrap_word_start (wf); + wordwrap_write (wf, " [--", 4); + wordwrap_puts (wf, opt->opt_long); + if (opt->opt_arg) + { + wordwrap_putc (wf, '='); + wordwrap_write (wf, arg, strlen (arg)); + } + wordwrap_putc (wf, ']'); + wordwrap_word_end (wf); + } + } +#endif + wordwrap_close (wf); + free (idxbuf); +} + +const char version_etc_copyright[] = + /* Do *not* mark this string for translation. First %s is a copyright + symbol suitable for this locale, and second %s are the copyright + years. */ + "Copyright %s %s Free Software Foundation, Inc"; + +const char license_text[] = + "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law."; + +void +print_version_only (void) +{ + printf ("%s (%s) %s\n", + parseopt_program_name ? parseopt_program_name : progname, + PACKAGE_NAME, + PACKAGE_VERSION); + /* TRANSLATORS: Translate "(C)" to the copyright symbol + (C-in-a-circle), if this symbol is available in the user's + locale. Otherwise, do not translate "(C)"; leave it as-is. */ + printf (version_etc_copyright, _("(C)"), "2011-2019"); + putchar ('\n'); + puts (license_text); + putchar ('\n'); +} + + +static int +handle_option (int c) +{ + switch (c) + { + case 'h': + parseopt_print_help (); + exit (0); + + case 'V': + print_version_only (); + exit (0); + + case OPT_USAGE: + print_usage (); + exit (0); + + default: + break; + } + return 0; +} + +int +parseopt_next (void) +{ + int rc; + + do + { +#ifdef HAVE_GETOPT_LONG + rc = getopt_long (argc, argv, short_options, long_options, NULL); +#else + rc = getopt (argc, argv, short_options); +#endif + } + while (handle_option (rc)); + + if (rc == EOF || rc == '?') + parseopt_free (); + + return rc; +} diff --git a/tools/progname.c b/tools/progname.c new file mode 100644 index 0000000..63bbd15 --- /dev/null +++ b/tools/progname.c @@ -0,0 +1,35 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 2011-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +# include "autoconf.h" +# include "gdbm.h" +# include "gdbmapp.h" +# include <string.h> + +const char *progname; + +void +set_progname (const char *arg) +{ + const char *p = strrchr (arg, '/'); + if (p) + ++p; + else + p = arg; + if (strncmp (p, "lt-", 3) == 0) + p += 3; + progname = p; +} diff --git a/tools/util.c b/tools/util.c new file mode 100644 index 0000000..509524a --- /dev/null +++ b/tools/util.c @@ -0,0 +1,131 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 1990-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +#include "gdbmtool.h" +#include <pwd.h> + +char * +mkfilename (const char *dir, const char *file, const char *suf) +{ + char *tmp; + size_t dirlen = strlen (dir); + size_t suflen = suf ? strlen (suf) : 0; + size_t fillen = strlen (file); + size_t len; + + while (dirlen > 0 && dir[dirlen-1] == '/') + dirlen--; + + len = dirlen + (dir[0] ? 1 : 0) + fillen + suflen; + tmp = emalloc (len + 1); + memcpy (tmp, dir, dirlen); + if (dir[0]) + tmp[dirlen++] = '/'; + memcpy (tmp + dirlen, file, fillen); + if (suf) + memcpy (tmp + dirlen + fillen, suf, suflen); + tmp[len] = 0; + return tmp; +} + +char * +tildexpand (char *s) +{ + if (s[0] == '~') + { + char *p = s + 1; + size_t len = strcspn (p, "/"); + struct passwd *pw; + + if (len == 0) + pw = getpwuid (getuid ()); + else + { + char *user = emalloc (len + 1); + + memcpy (user, p, len); + user[len] = 0; + pw = getpwnam (user); + free (user); + } + if (pw) + return mkfilename (pw->pw_dir, p + len + 1, NULL); + } + return estrdup (s); +} + +int +vgetyn (const char *prompt, va_list ap) +{ + int state = 0; + int c, resp; + va_list aq; + + do + { + switch (state) + { + case 1: + if (c == ' ' || c == '\t') + continue; + resp = c; + state = 2; + /* fall through */ + case 2: + if (c == '\n') + { + switch (resp) + { + case 'y': + case 'Y': + return 1; + case 'n': + case 'N': + return 0; + default: + /* TRANSLATORS: Please, don't translate 'y' and 'n'. */ + fprintf (stdout, "%s\n", _("Please, reply 'y' or 'n'")); + } + /* fall through */ + } + else + break; + + case 0: + va_copy (aq, ap); + vfprintf (stdout, prompt, aq); + va_end (aq); + fprintf (stdout, " [y/n]?"); + fflush (stdout); + state = 1; + break; + } + } while ((c = getchar ()) != EOF); + exit (EXIT_USAGE); +} + +int +getyn (const char *prompt, ...) +{ + va_list ap; + int rc; + + va_start (ap, prompt); + rc = vgetyn (prompt, ap); + va_end (ap); + return rc; +} + diff --git a/tools/var.c b/tools/var.c new file mode 100644 index 0000000..5a1d750 --- /dev/null +++ b/tools/var.c @@ -0,0 +1,835 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 1990-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +#include "gdbmtool.h" + +#define VARF_DFL 0x00 /* Default flags -- everything disabled */ +#define VARF_SET 0x01 /* Variable is set */ +#define VARF_INIT 0x02 /* Variable is initialized */ +#define VARF_PROT 0x04 /* Variable is protected, i.e. cannot be unset */ +#define VARF_OCTAL 0x08 /* For integer variables -- use octal base */ + +#define VAR_IS_SET(v) ((v)->flags & VARF_SET) + +union value +{ + char *string; + int bool; + int num; +}; + +struct variable +{ + char *name; + int type; + int flags; + union value init; + union value v; + void *data; + int (*sethook) (struct variable *, union value *); + int (*typeconv) (struct variable *, int, void **); + void (*freehook) (void *); +}; + +static int open_sethook (struct variable *, union value *); +static int open_typeconv (struct variable *var, int type, void **retptr); +static int format_sethook (struct variable *, union value *); +static int format_typeconv (struct variable *var, int type, void **retptr); +static int fd_sethook (struct variable *, union value *); +static int centfree_sethook (struct variable *var, union value *v); +static int coalesce_sethook (struct variable *var, union value *v); +static int cachesize_sethook (struct variable *var, union value *v); +static int errormask_sethook (struct variable *var, union value *v); +static int errormask_typeconv (struct variable *var, int type, void **retptr); +static void errormask_freehook (void *); +static int errorexit_sethook (struct variable *var, union value *v); + +static struct variable vartab[] = { + /* Top-level prompt */ + { + .name = "ps1", + .type = VART_STRING, + .flags = VARF_INIT, + .init = { .string = "%p>%_" } + }, + /* Second-level prompt (used within "def" block) */ + { + .name = "ps2", + .type = VART_STRING, + .flags = VARF_INIT, + .init = { .string = "%_>%_" } + }, + /* This delimits array members */ + { + .name = "delim1", + .type = VART_STRING, + .flags = VARF_INIT|VARF_PROT, + .init = { .string = "," } + }, + /* This delimits structure members */ + { + .name = "delim2", + .type = VART_STRING, + .flags = VARF_INIT|VARF_PROT, + .init = { .string = "," } + }, + { + .name = "confirm", + .type = VART_BOOL, + .flags = VARF_INIT, + .init = { .bool = 1 } + }, + { + .name = "cachesize", + .type = VART_INT, + .flags = VARF_DFL, + .sethook = cachesize_sethook + }, + { + .name = "blocksize", + .type = VART_INT, + .flags = VARF_DFL + }, + { + .name = "open", + .type = VART_STRING, + .flags = VARF_DFL, + .sethook = open_sethook, + .typeconv = open_typeconv + }, + { + .name = "lock", + .type = VART_BOOL, + .flags = VARF_INIT, + .init = { .bool = 1 } + }, + { + .name = "mmap", + .type = VART_BOOL, + .flags = VARF_INIT, + .init = { .bool = 1 } + }, + { + .name = "sync", + .type = VART_BOOL, + .flags = VARF_INIT, + .init = { .bool = 0 } + }, + { + .name = "coalesce", + .type = VART_BOOL, + .flags = VARF_INIT, + .init = { .bool = 0 }, + .sethook = coalesce_sethook + }, + { + .name = "centfree", + .type = VART_BOOL, + .flags = VARF_INIT, + .init = { .bool = 0 }, + .sethook = centfree_sethook + }, + { + .name = "filemode", + .type = VART_INT, + .flags = VARF_INIT|VARF_OCTAL|VARF_PROT, + .init = { .num = 0644 } + }, + { + .name = "format", + .type = VART_STRING, + .flags = VARF_INIT, + .init = { .string = "standard" }, + .sethook = format_sethook, + .typeconv = format_typeconv + }, + { + .name = "pager", + .type = VART_STRING, + .flags = VARF_DFL + }, + { + .name = "quiet", + .type = VART_BOOL, + .flags = VARF_DFL + }, + { + .name = "filename", + .type = VART_STRING, + .flags = VARF_INIT|VARF_PROT, + { .string = GDBMTOOL_DEFFILE } + }, + { + .name = "fd", + .type = VART_INT, + .flags = VARF_DFL, + .sethook = fd_sethook + }, + { + .name = "errorexit", + .type = VART_STRING, + .sethook = errorexit_sethook, + .typeconv = errormask_typeconv, + .freehook = errormask_freehook + }, + { + .name = "errormask", + .type = VART_STRING, + .sethook = errormask_sethook, + .typeconv = errormask_typeconv, + .freehook = errormask_freehook + }, + { + .name = "timing", + .type = VART_BOOL + }, + { + .name = "trace", + .type = VART_BOOL + }, + { NULL } +}; + +static struct variable * +varfind (const char *name) +{ + struct variable *vp; + + for (vp = vartab; vp->name; vp++) + if (strcmp (vp->name, name) == 0) + return vp; + + return NULL; +} + +typedef int (*setvar_t) (union value *, void *, int); + +static int +s2s (union value *vp, void *val, int flags) +{ + vp->string = estrdup (val); + return VAR_OK; +} + +static int +b2s (union value *vp, void *val, int flags) +{ + vp->string = estrdup (*(int*)val ? "true" : "false"); + return VAR_OK; +} + +static int +i2s (union value *vp, void *val, int flags) +{ + char buf[128]; + snprintf (buf, sizeof buf, "%d", *(int*)val); + vp->string = estrdup (buf); + return VAR_OK; +} + +static int +s2b (union value *vp, void *val, int flags) +{ + static char *trueval[] = { "on", "true", "yes", NULL }; + static char *falseval[] = { "off", "false", "no", NULL }; + int i; + unsigned long n; + char *p; + + for (i = 0; trueval[i]; i++) + if (strcasecmp (trueval[i], val) == 0) + { + vp->bool = 1; + return VAR_OK; + } + + for (i = 0; falseval[i]; i++) + if (strcasecmp (falseval[i], val) == 0) + { + vp->bool = 0; + return VAR_OK; + } + + n = strtoul (val, &p, 0); + if (*p) + return VAR_ERR_BADTYPE; + vp->bool = !!n; + return VAR_OK; +} + +static int +s2i (union value *vp, void *val, int flags) +{ + char *p; + int n = strtoul (val, &p, (flags & VARF_OCTAL) ? 8 : 10); + + if (*p) + return VAR_ERR_BADTYPE; + + vp->num = n; + return VAR_OK; +} + +static int +b2b (union value *vp, void *val, int flags) +{ + vp->bool = !!*(int*)val; + return VAR_OK; +} + +static int +b2i (union value *vp, void *val, int flags) +{ + vp->num = *(int*)val; + return VAR_OK; +} + +static int +i2i (union value *vp, void *val, int flags) +{ + vp->num = *(int*)val; + return VAR_OK; +} + +static int +i2b (union value *vp, void *val, int flags) +{ + vp->bool = *(int*)val; + return VAR_OK; +} + +static setvar_t setvar[3][3] = { + /* s b i */ + /* s */ { s2s, b2s, i2s }, + /* b */ { s2b, b2b, i2b }, + /* i */ { s2i, b2i, i2i } +}; + +int +variable_set (const char *name, int type, void *val) +{ + struct variable *vp = varfind (name); + int rc; + union value v, *valp; + + if (!vp) + return VAR_ERR_NOTDEF; + + if (val) + { + memset (&v, 0, sizeof (v)); + rc = setvar[vp->type][type] (&v, val, vp->flags); + if (rc) + return rc; + valp = &v; + } + else + { + if (vp->flags & VARF_PROT) + return VAR_ERR_BADVALUE; + valp = NULL; + } + + if (vp->sethook && (rc = vp->sethook (vp, valp)) != VAR_OK) + return rc; + + if (vp->type == VART_STRING && (vp->flags & VARF_SET)) + free (vp->v.string); + + if (!val) + { + vp->flags &= VARF_SET; + } + else + { + vp->v = v; + vp->flags |= VARF_SET; + } + + return VAR_OK; +} + +int +variable_unset (const char *name) +{ + struct variable *vp = varfind (name); + int rc; + + if (!vp) + return VAR_ERR_NOTDEF; + if (vp->flags & VARF_PROT) + return VAR_ERR_BADVALUE; + + if (vp->sethook && (rc = vp->sethook (vp, NULL)) != VAR_OK) + return rc; + + if (vp->type == VART_STRING) + { + free (vp->v.string); + vp->v.string = NULL; + } + vp->flags &= ~VARF_SET; + + return VAR_OK; +} + +int +variable_get (const char *name, int type, void **val) +{ + struct variable *vp = varfind (name); + + if (!vp) + return VAR_ERR_NOTDEF; + + if (!VAR_IS_SET (vp)) + return VAR_ERR_NOTSET; + + if (type != vp->type) + { + if (vp->typeconv) + { + return vp->typeconv (vp, type, val); + } + else + return VAR_ERR_BADTYPE; + } + + switch (vp->type) + { + case VART_STRING: + *val = vp->v.string; + break; + + case VART_BOOL: + *(int*)val = vp->v.bool; + break; + + case VART_INT: + *(int*)val = vp->v.num; + break; + } + + return VAR_OK; +} + +static int +varcmp (const void *a, const void *b) +{ + return strcmp (((struct variable const *)a)->name, + ((struct variable const *)b)->name); +} + +void +variable_print_all (FILE *fp) +{ + struct variable *vp; + char *s; + static int sorted; + + if (!sorted) + { + qsort (vartab, ARRAY_SIZE (vartab) - 1, sizeof (vartab[0]), varcmp); + sorted = 1; + } + + for (vp = vartab; vp->name; vp++) + { + if (!VAR_IS_SET (vp)) + { + fprintf (fp, "# %s is unset", vp->name); + } + else + { + switch (vp->type) + { + case VART_INT: + fprintf (fp, (vp->flags & VARF_OCTAL) ? "%s=%03o" : "%s=%d", + vp->name, vp->v.num); + break; + + case VART_BOOL: + fprintf (fp, "%s%s", vp->v.bool ? "" : "no", vp->name); + break; + + case VART_STRING: + fprintf (fp, "%s=\"", vp->name); + for (s = vp->v.string; *s; s++) + { + int c; + + if (isprint (*s)) + fputc (*s, fp); + else if ((c = escape (*s))) + fprintf (fp, "\\%c", c); + else + fprintf (fp, "\\%03o", *s); + } + fprintf (fp, "\""); + } + } + fputc ('\n', fp); + } +} + +int +variable_is_set (const char *name) +{ + struct variable *vp = varfind (name); + + if (!vp) + return 0; + return VAR_IS_SET (vp); +} + +int +variable_is_true (const char *name) +{ + int n; + + if (variable_get (name, VART_BOOL, (void **) &n) == VAR_OK) + return n; + return 0; +} + +void +variables_free (void) +{ + struct variable *vp; + + for (vp = vartab; vp->name; vp++) + { + if (vp->type == VART_STRING && (vp->flags & VARF_SET)) + free (vp->v.string); + vp->v.string = NULL; + if (vp->freehook && vp->data) + { + vp->freehook (vp->data); + vp->data = NULL; + } + vp->flags &= ~VARF_SET; + } +} + +void +variables_init (void) +{ + struct variable *vp; + + for (vp = vartab; vp->name; vp++) + { + if (!(vp->flags & VARF_SET) && (vp->flags & VARF_INIT)) + { + if (vp->type == VART_STRING) + variable_set (vp->name, vp->type, vp->init.string); + else + variable_set (vp->name, vp->type, &vp->init); + } + } +} + +struct kwtrans +{ + char *s; + int t; +}; + +static int +string_to_int (char const *s, struct kwtrans *t) +{ + int i; + + for (i = 0; t[i].s; i++) + if (strcmp (t[i].s, s) == 0) + return t[i].t; + return -1; +} + +#if 0 +static char const * +int_to_string (int n, struct kwtrans *t) +{ + int i; + + for (i = 0; t[i].s; i++) + if (t[i].t == n) + return t[i].s; + return NULL; +} +#endif + +static struct kwtrans db_open_flags[] = { + { "newdb", GDBM_NEWDB }, + { "wrcreat", GDBM_WRCREAT }, + { "rw", GDBM_WRCREAT }, + { "reader", GDBM_READER }, + { "readonly", GDBM_READER }, + { NULL } +}; + +static int +open_sethook (struct variable *var, union value *v) +{ + int n; + if (!v) + return VAR_ERR_BADVALUE; + n = string_to_int (v->string, db_open_flags); + if (n == -1) + return VAR_ERR_BADVALUE; + return VAR_OK; +} + +static int +open_typeconv (struct variable *var, int type, void **retptr) +{ + if (type == VART_INT) + { + *(int*) retptr = string_to_int (var->v.string, db_open_flags); + return VAR_OK; + } + return VAR_ERR_BADTYPE; +} + +static int +format_sethook (struct variable *var, union value *v) +{ + if (!v) + return VAR_OK; + return _gdbm_str2fmt (v->string) == -1 ? VAR_ERR_BADVALUE : VAR_OK; +} + +static int +format_typeconv (struct variable *var, int type, void **retptr) +{ + if (type == VART_INT) + { + *(int*) retptr = _gdbm_str2fmt (var->v.string); + return VAR_OK; + } + return VAR_ERR_BADTYPE; +} + +static int +fd_sethook (struct variable *var, union value *v) +{ + if (!v) + return VAR_OK; + if (v->num < 0) + return VAR_ERR_BADVALUE; + return VAR_OK; +} + +static int +cachesize_sethook (struct variable *var, union value *v) +{ + if (!v) + return VAR_OK; + if (v->num < 0) + return VAR_ERR_BADVALUE; + return gdbmshell_setopt ("GDBM_SETCACHESIZE", GDBM_SETCACHESIZE, v->num) == 0 + ? VAR_OK : VAR_ERR_GDBM; +} + +static int +centfree_sethook (struct variable *var, union value *v) +{ + if (!v) + return VAR_OK; + return gdbmshell_setopt ("GDBM_SETCENTFREE", GDBM_SETCENTFREE, v->bool) == 0 + ? VAR_OK : VAR_ERR_GDBM; +} + +static int +coalesce_sethook (struct variable *var, union value *v) +{ + if (!v) + return VAR_OK; + return gdbmshell_setopt ("GDBM_SETCOALESCEBLKS", GDBM_SETCOALESCEBLKS, v->bool) == 0 + ? VAR_OK : VAR_ERR_GDBM; +} + +const char * const errname[_GDBM_MAX_ERRNO+1] = { + [GDBM_NO_ERROR] = "GDBM_NO_ERROR", + [GDBM_MALLOC_ERROR] = "GDBM_MALLOC_ERROR", + [GDBM_BLOCK_SIZE_ERROR] = "GDBM_BLOCK_SIZE_ERROR", + [GDBM_FILE_OPEN_ERROR] = "GDBM_FILE_OPEN_ERROR", + [GDBM_FILE_WRITE_ERROR] = "GDBM_FILE_WRITE_ERROR", + [GDBM_FILE_SEEK_ERROR] = "GDBM_FILE_SEEK_ERROR", + [GDBM_FILE_READ_ERROR] = "GDBM_FILE_READ_ERROR", + [GDBM_BAD_MAGIC_NUMBER] = "GDBM_BAD_MAGIC_NUMBER", + [GDBM_EMPTY_DATABASE] = "GDBM_EMPTY_DATABASE", + [GDBM_CANT_BE_READER] = "GDBM_CANT_BE_READER", + [GDBM_CANT_BE_WRITER] = "GDBM_CANT_BE_WRITER", + [GDBM_READER_CANT_DELETE] = "GDBM_READER_CANT_DELETE", + [GDBM_READER_CANT_STORE] = "GDBM_READER_CANT_STORE", + [GDBM_READER_CANT_REORGANIZE] = "GDBM_READER_CANT_REORGANIZE", + [GDBM_UNKNOWN_ERROR] = "GDBM_UNKNOWN_ERROR", + [GDBM_ITEM_NOT_FOUND] = "GDBM_ITEM_NOT_FOUND", + [GDBM_REORGANIZE_FAILED] = "GDBM_REORGANIZE_FAILED", + [GDBM_CANNOT_REPLACE] = "GDBM_CANNOT_REPLACE", + [GDBM_MALFORMED_DATA] = "GDBM_MALFORMED_DATA", + [GDBM_OPT_ALREADY_SET] = "GDBM_OPT_ALREADY_SET", + [GDBM_OPT_BADVAL] = "GDBM_OPT_BADVAL", + [GDBM_BYTE_SWAPPED] = "GDBM_BYTE_SWAPPED", + [GDBM_BAD_FILE_OFFSET] = "GDBM_BAD_FILE_OFFSET", + [GDBM_BAD_OPEN_FLAGS] = "GDBM_BAD_OPEN_FLAGS", + [GDBM_FILE_STAT_ERROR] = "GDBM_FILE_STAT_ERROR", + [GDBM_FILE_EOF] = "GDBM_FILE_EOF", + [GDBM_NO_DBNAME] = "GDBM_NO_DBNAME", + [GDBM_ERR_FILE_OWNER] = "GDBM_ERR_FILE_OWNER", + [GDBM_ERR_FILE_MODE] = "GDBM_ERR_FILE_MODE", + [GDBM_NEED_RECOVERY] = "GDBM_NEED_RECOVERY", + [GDBM_BACKUP_FAILED] = "GDBM_BACKUP_FAILED", + [GDBM_DIR_OVERFLOW] = "GDBM_DIR_OVERFLOW", + [GDBM_BAD_BUCKET] = "GDBM_BAD_BUCKET", + [GDBM_BAD_HEADER] = "GDBM_BAD_HEADER", + [GDBM_BAD_AVAIL] = "GDBM_BAD_AVAIL", + [GDBM_BAD_HASH_TABLE] = "GDBM_BAD_HASH_TABLE", + [GDBM_BAD_DIR_ENTRY] = "GDBM_BAD_DIR_ENTRY", + [GDBM_FILE_CLOSE_ERROR] = "GDBM_FILE_CLOSE_ERROR", + [GDBM_FILE_SYNC_ERROR] = "GDBM_FILE_SYNC_ERROR", + [GDBM_FILE_TRUNCATE_ERROR] = "GDBM_FILE_TRUNCATE_ERROR", + [GDBM_BUCKET_CACHE_CORRUPTED] = "GDBM_BUCKET_CACHE_CORRUPTED", + [GDBM_BAD_HASH_ENTRY] = "GDBM_BAD_HASH_ENTRY", + [GDBM_ERR_SNAPSHOT_CLONE] = "GDBM_ERR_SNAPSHOT_CLONE", + [GDBM_ERR_REALPATH] = "GDBM_ERR_REALPATH", + [GDBM_ERR_USAGE] = "GDBM_ERR_USAGE", +}; + +static int +str2errcode (char const *str) +{ + int i; +#define GDBM_PREFIX "GDBM_" +#define GDBM_PREFIX_LEN (sizeof (GDBM_PREFIX) - 1) + + if (strncasecmp (str, GDBM_PREFIX, GDBM_PREFIX_LEN) == 0) + str += GDBM_PREFIX_LEN; + + for (i = 0; i < ARRAY_SIZE (errname); i++) + if (strcasecmp (errname[i] + GDBM_PREFIX_LEN, str) == 0) + return i; + + return -1; +} + +#define ERROR_MASK_SIZE (_GDBM_MAX_ERRNO+1) + +static int +errormask_sethook (struct variable *var, union value *v) +{ + char *errmask = var->data; + + if (!v || strcmp (v->string, "false") == 0) + { + if (var->data) + memset (errmask, 0, ERROR_MASK_SIZE); + } + else + { + char *t; + + if (!errmask) + { + errmask = calloc (ERROR_MASK_SIZE, sizeof (char)); + var->data = errmask; + } + + if (strcmp (v->string, "true") == 0) + { + memset (errmask, 1, ERROR_MASK_SIZE); + free (v->string); + v->string = estrdup ("all"); + } + else + { + for (t = strtok (v->string, ","); t; t = strtok (NULL, ",")) + { + int len, val, e; + + while (t[0] == ' ' || t[0] == '\t') + t++; + len = strlen (t); + while (len > 0 && (t[len-1] == ' ' || t[len-1] == '\t')) + len--; + t[len] = 0; + + if (t[0] == '-') + { + val = 0; + t++; + } + else if (t[0] == '+') + { + val = 1; + t++; + } + else + { + val = 1; + } + if (strcmp (t, "all") == 0) + { + for (e = 1; e < ERROR_MASK_SIZE; e++) + errmask[e] = val; + } + else + { + e = str2errcode (t); + if (e == -1) + terror (_("unrecognized error code: %s"), t); + else + errmask[e] = val; + } + } + } + } + return VAR_OK; +} + +static int +errormask_typeconv (struct variable *var, int type, void **retptr) +{ + char *errmask = var->data; + + if (type == VART_INT) + { + int n = *(int*) retptr; + if (n >= 0 && n < ERROR_MASK_SIZE) + { + *(int*) retptr = errmask ? errmask[n] : 0; + return VAR_OK; + } + else + return VAR_ERR_BADVALUE; + } + return VAR_ERR_BADTYPE; +} + +static void +errormask_freehook (void *data) +{ + free (data); +} + +static int +errorexit_sethook (struct variable *var, union value *v) +{ + if (interactive ()) + { + return VAR_ERR_BADVALUE; + } + return errormask_sethook (var, v); +} diff --git a/tools/wordwrap.c b/tools/wordwrap.c new file mode 100644 index 0000000..890291e --- /dev/null +++ b/tools/wordwrap.c @@ -0,0 +1,636 @@ +/* This file is part of GDBM, the GNU data base manager. + Copyright (C) 2011-2021 Free Software Foundation, Inc. + + GDBM 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 3, or (at your option) + any later version. + + GDBM 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 GDBM. If not, see <http://www.gnu.org/licenses/>. */ + +#include "autoconf.h" +#include "gdbmapp.h" +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <wctype.h> +#include <wchar.h> +#include <errno.h> +#include <limits.h> +#include <termios.h> +#include <sys/ioctl.h> + +#define UNSET ((unsigned)-1) +#define ISSET(c) (c != UNSET) +#define DEFAULT_RIGHT_MARGIN 80 + +struct wordwrap_file +{ + int fd; /* Output file descriptor. */ + unsigned left_margin; /* Left margin. */ + unsigned right_margin; /* Right margin. */ + char *buffer; /* Output buffer. */ + size_t bufsize; /* Size of buffer in bytes. */ + unsigned offset; /* Offset of the writing point in the buffer */ + unsigned column; /* Number of screen column, i.e. the (multibyte) + character corresponding to the offset. */ + unsigned last_ws; /* Offset of the beginning of the last whitespace + sequence written to the buffer. */ + unsigned ws_run; /* Number of characters in the whitespace sequence. */ + unsigned word_start; /* Start of a sequence that should be treated as a + single word. */ + unsigned next_left_margin; /* Left margin to be set after next flush. */ + + int indent; /* If 1, reindent next line. */ + int unibyte; /* 0: Normal operation. + 1: multibyte functions disabled for this line. */ + int err; /* Last errno value associated with this file. */ +}; + +/* + * Reset the file for the next input line. + */ +static void +wordwrap_line_init (WORDWRAP_FILE wf) +{ + wf->offset = wf->column = wf->left_margin; + wf->last_ws = UNSET; + wf->ws_run = 0; + wf->unibyte = 0; +} + +/* + * Detect the value of the right margin. Use TIOCGWINSZ ioctl, the COLUMNS + * environment variable, or the default value, in that order. + */ +static unsigned +detect_right_margin (WORDWRAP_FILE wf) +{ + struct winsize ws; + unsigned r = 0; + + ws.ws_col = ws.ws_row = 0; + if ((ioctl (wf->fd, TIOCGWINSZ, (char *) &ws) < 0) || ws.ws_col == 0) + { + char *p = getenv ("COLUMNS"); + if (p) + { + unsigned long n; + char *ep; + errno = 0; + n = strtoul (p, &ep, 10); + if (!(errno || *ep || n > UINT_MAX)) + r = n; + } + else + r = DEFAULT_RIGHT_MARGIN; + } + else + r = ws.ws_col; + return r; +} + +/* + * Create a wordwrap file operating on file descriptor FD. + * In the contrast to the libc fdopen, the descriptor is dup'ed. + * Left margin is set to 0, right margin is auto detected. + */ +WORDWRAP_FILE +wordwrap_fdopen (int fd) +{ + struct wordwrap_file *wf; + int ec; + + if ((wf = calloc (1, sizeof (*wf))) == NULL) + return NULL; + if ((wf->fd = dup (fd)) == -1) + { + ec = errno; + free (wf); + errno = ec; + return NULL; + } + + wf->last_ws = UNSET; + wf->word_start = UNSET; + wf->next_left_margin = UNSET; + + wordwrap_set_right_margin (wf, 0); + + return wf; +} + +/* + * Close the descriptor associated with the wordwrap file, and deallocate + * the memory. + */ +int +wordwrap_close (WORDWRAP_FILE wf) +{ + int rc; + + rc = wordwrap_flush (wf); + close (wf->fd); + free (wf->buffer); + free (wf); + + return rc; +} + +/* + * Return true if wordwrap file is at the beginning of line. + */ +int +wordwrap_at_bol (WORDWRAP_FILE wf) +{ + return wf->column == wf->left_margin; +} + +/* + * Return true if wordwrap file is at the end of line. + */ +int +wordwrap_at_eol (WORDWRAP_FILE wf) +{ + return wf->column == wf->right_margin; +} + +/* + * Write SIZE bytes from the buffer to the file. + * Return the number of bytes written. + * Set the file error indicator on error. + */ +static ssize_t +full_write (WORDWRAP_FILE wf, size_t size) +{ + ssize_t total = 0; + + while (total < size) + { + ssize_t n = write (wf->fd, wf->buffer + total, size - total); + if (n == -1) + { + wf->err = errno; + break; + } + if (n == 0) + { + wf->err = ENOSPC; + break; + } + total += n; + } + return total; +} + +/* + * A fail-safe version of mbrtowc. If the call to mbrtowc, fails, + * switches the stream to the unibyte mode. + */ +static inline size_t +safe_mbrtowc (WORDWRAP_FILE wf, wchar_t *wc, const char *s, mbstate_t *ps) +{ + if (!wf->unibyte) + { + size_t n = mbrtowc (wc, s, MB_CUR_MAX, ps); + if (n == (size_t) -1 || n == (size_t) -2) + wf->unibyte = 1; + else + return n; + } + *wc = *(unsigned char *)s; + return 1; +} + +/* + * Return length of the whitespace prefix in STR. + */ +static size_t +wsprefix (WORDWRAP_FILE wf, char const *str, size_t size) +{ + size_t i; + mbstate_t mbs; + wchar_t wc; + + memset (&mbs, 0, sizeof (mbs)); + for (i = 0; i < size; ) + { + size_t n = safe_mbrtowc (wf, &wc, &str[i], &mbs); + + if (!iswblank (wc)) + break; + + i += n; + } + + return i; +} + +/* + * Rescan N bytes from the current buffer from the current offset. + * Update offset, column, and whitespace segment counters. + */ +static void +wordwrap_rescan (WORDWRAP_FILE wf, size_t n) +{ + mbstate_t mbs; + wchar_t wc; + + wordwrap_line_init (wf); + + memset (&mbs, 0, sizeof (mbs)); + while (wf->offset < n) + { + size_t n = safe_mbrtowc (wf, &wc, &wf->buffer[wf->offset], &mbs); + + if (iswblank (wc)) + { + if (ISSET (wf->last_ws) && wf->last_ws + wf->ws_run == wf->offset) + wf->ws_run++; + else + { + wf->last_ws = wf->offset; + wf->ws_run = 1; + } + } + + wf->offset += n; + wf->column++; + } +} + +/* + * Flush SIZE bytes from the current buffer to the FD. + * Reinitialize WF for the next line. + */ +static int +flush_line (WORDWRAP_FILE wf, size_t size) +{ + ssize_t n; + size_t len; + char c; + + if (ISSET (wf->last_ws) && size == wf->last_ws + wf->ws_run) + len = wf->last_ws; + else + len = size; + + if (len > wf->left_margin) + { + n = full_write (wf, len); + if (n == -1) + return -1; + + if (n < len) + { + //FIXME: this breaks column and ws tracking + abort (); + } + } + + c = '\n'; + write (wf->fd, &c, 1); + + if (ISSET (wf->next_left_margin)) + { + wf->left_margin = wf->next_left_margin; + wf->next_left_margin = UNSET; + } + + n = wf->offset - size; + if (n > 0) + { + size_t wsn; + + wsn = wsprefix (wf, wf->buffer + size, n); + + size += wsn; + n -= wsn; + + if (n) + memmove (wf->buffer + wf->left_margin, wf->buffer + size, n); + } + + if (wf->indent) + { + memset (wf->buffer, ' ', wf->left_margin); + wf->indent = 0; + } + wordwrap_rescan (wf, wf->left_margin + n); + + return 0; +} + +/* + * Flush the wordwrap file buffer. + */ +int +wordwrap_flush (WORDWRAP_FILE wf) +{ + if (wf->offset > wf->left_margin) + return flush_line (wf, wf->offset); + return 0; +} + +/* + * Return error indicator (last errno value). + */ +int +wordwrap_error (WORDWRAP_FILE wf) +{ + return wf->err; +} + +/* + * Set left margin value. + */ +int +wordwrap_set_left_margin (WORDWRAP_FILE wf, unsigned left) +{ + int bol; + + if (left == wf->left_margin) + return 0; + else if (left >= wf->right_margin) + { + wf->err = errno = EINVAL; + return -1; + } + + bol = wordwrap_at_bol (wf); + wf->left_margin = left; + if (left < wf->offset) + { + wf->indent = 1; + if (!bol) + flush_line (wf, wf->offset);//FIXME: remove trailing ws + } + else + { + wf->indent = left > wf->offset; + if (wf->indent) + memset (wf->buffer + wf->offset, ' ', wf->left_margin - wf->offset); + } + wordwrap_line_init (wf); + + return 0; +} + +/* + * Set delayed left margin value. The new value will take effect after the + * current line is flushed. + */ +int +wordwrap_next_left_margin (WORDWRAP_FILE wf, unsigned left) +{ + if (left == wf->left_margin) + return 0; + else if (left >= wf->right_margin) + { + wf->err = errno = EINVAL; + return -1; + } + wf->next_left_margin = left; + wf->indent = 1; + return 0; +} + +/* + * Set right margin for the file. + */ +int +wordwrap_set_right_margin (WORDWRAP_FILE wf, unsigned right) +{ + if (right == 0) + right = detect_right_margin (wf); + + if (right == wf->right_margin) + return 0; + else if (right <= wf->left_margin) + { + wf->err = errno = EINVAL; + return -1; + } + else + { + char *p; + size_t size; + + if (right < wf->offset) + { + if (wordwrap_flush (wf)) + return -1; + } + + size = MB_CUR_MAX * (right + 1); + p = realloc (wf->buffer, size); + if (!p) + { + wf->err = errno; + return -1; + } + + wf->buffer = p; + wf->bufsize = size; + wf->right_margin = right; + } + + return 0; +} + +/* + * Mark current output position as the word start. The normal whitespace + * splitting is disabled, until wordwrap_word_end is called or the current + * buffer is flushed, whichever happens first. + * The functions wordwrap_word_start () / wordwrap_word_end () mark the + * sequence of characters that should not be split on whitespace, such as, + * e.g. option name with argument in help output ("-f FILE"). + */ +void +wordwrap_word_start (WORDWRAP_FILE wf) +{ + wf->word_start = wf->offset; +} + +/* + * Disable word marker. + */ +void +wordwrap_word_end (WORDWRAP_FILE wf) +{ + wf->word_start = UNSET; +} + +/* + * Write LEN bytes from the string STR to the wordwrap file. + */ +int +wordwrap_write (WORDWRAP_FILE wf, char const *str, size_t len) +{ + size_t i; + wchar_t wc; + mbstate_t mbs; + + memset (&mbs, 0, sizeof (mbs)); + for (i = 0; i < len; ) + { + size_t n = safe_mbrtowc (wf, &wc, &str[i], &mbs); + + if (wf->column + 1 == wf->right_margin || wc == '\n') + { + size_t len; + + if (ISSET (wf->word_start)) + { + len = wf->word_start; + wf->word_start = UNSET; + } + else if (!iswspace (wc) && ISSET (wf->last_ws)) + len = wf->last_ws; + else + len = wf->offset; + + flush_line (wf, len); + if (wc == '\n') + { + i += n; + continue; + } + } + + if (iswblank (wc)) + { + if (wf->offset == wf->left_margin) + { + /* Skip leading whitespace */ + i += n; + continue; + } + else if (ISSET (wf->last_ws) && wf->last_ws + wf->ws_run == wf->offset) + wf->ws_run++; + else + { + wf->last_ws = wf->offset; + wf->ws_run = 1; + } + } + + memcpy (wf->buffer + wf->offset, str + i, n); + + wf->offset += n; + wf->column++; + + i += n; + } + return 0; +} + +/* + * Write a nul-terminated string STR to the file (terminating \0 not + * included). + */ +int +wordwrap_puts (WORDWRAP_FILE wf, char const *str) +{ + return wordwrap_write (wf, str, strlen (str)); +} + +/* + * Write a single character to the file. + */ +int +wordwrap_putc (WORDWRAP_FILE wf, int c) +{ + char ch = c; + return wordwrap_write (wf, &ch, 1); +} + +/* + * Insert a paragraph (empty line). + */ +int +wordwrap_para (WORDWRAP_FILE wf) +{ + return wordwrap_write (wf, "\n\n", 2); +} + +/* + * Format AP according to FMT and write the formatted output to file. + */ +int +wordwrap_vprintf (WORDWRAP_FILE wf, char const *fmt, va_list ap) +{ + size_t buflen = 64; + char *buf; + ssize_t n; + int rc; + + buf = malloc (buflen); + if (!buf) + { + wf->err = errno; + return -1; + } + + for (;;) + { + va_list aq; + + va_copy (aq, ap); + n = vsnprintf (buf, buflen, fmt, aq); + va_end (aq); + + if (n < 0 || n >= buflen || !memchr(buf, '\0', n + 1)) + { + char *p; + + if ((size_t) -1 / 3 * 2 <= buflen) + { + wf->err = ENOMEM; + free (buf); + return -1; + } + + buflen += (buflen + 1) / 2; + p = realloc (buf, buflen); + if (!p) + { + wf->err = errno; + free (buf); + return -1; + } + buf = p; + } + else + break; + } + + rc = wordwrap_write (wf, buf, n); + free (buf); + return rc; +} + +/* + * Format argument list according to FMT and write the formatted output + * to file. + */ +int +wordwrap_printf (WORDWRAP_FILE wf, char const *fmt, ...) +{ + va_list ap; + int rc; + + va_start (ap, fmt); + rc = wordwrap_vprintf (wf, fmt, ap); + va_end (ap); + return rc; +} + + + |