summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/.gitignore6
-rw-r--r--tools/Makefile.am80
-rw-r--r--tools/datconv.c566
-rw-r--r--tools/err.c69
-rw-r--r--tools/gdbm_dump.c132
-rw-r--r--tools/gdbm_load.c317
-rw-r--r--tools/gdbmapp.h85
-rw-r--r--tools/gdbmshell.c3181
-rw-r--r--tools/gdbmtool.c293
-rw-r--r--tools/gdbmtool.h383
-rw-r--r--tools/gdbmtool.supp19
-rw-r--r--tools/gram.y443
-rw-r--r--tools/input-argv.c132
-rw-r--r--tools/input-file.c90
-rw-r--r--tools/input-null.c53
-rw-r--r--tools/input-rl.c223
-rw-r--r--tools/input-std.c57
-rw-r--r--tools/lex.l754
-rw-r--r--tools/mem.c107
-rw-r--r--tools/parseopt.c702
-rw-r--r--tools/progname.c35
-rw-r--r--tools/util.c131
-rw-r--r--tools/var.c835
-rw-r--r--tools/wordwrap.c636
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, &param))
+ 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 (&param, &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 (&param, &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 (&param);
+
+ 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;
+}
+
+
+