aboutsummaryrefslogtreecommitdiff
path: root/tools/gdbmshell.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/gdbmshell.c')
-rw-r--r--tools/gdbmshell.c3269
1 files changed, 3269 insertions, 0 deletions
diff --git a/tools/gdbmshell.c b/tools/gdbmshell.c
new file mode 100644
index 0000000..5ab865d
--- /dev/null
+++ b/tools/gdbmshell.c
@@ -0,0 +1,3269 @@
+/* This file is part of GDBM, the GNU data base manager.
+ Copyright (C) 1990-2023 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_mru, 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;
+ char *p;
+ int summary = 0;
+
+ 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)
+ {
+ if (strcmp (arg->v.string, "verbose") == 0)
+ {
+ rcvr.errfun = err_printer;
+ flags |= GDBM_RCVR_ERRFUN;
+ }
+ else if (strcmp (arg->v.string, "force") == 0)
+ {
+ flags |= GDBM_RCVR_FORCE;
+ }
+ else if (strcmp (arg->v.string, "summary") == 0)
+ {
+ summary = 1;
+ }
+ else if (strcmp (arg->v.string, "backup") == 0)
+ {
+ flags |= GDBM_RCVR_BACKUP;
+ }
+ else
+ {
+ lerror (&arg->loc, _("unrecognized argument: %s"), arg->v.string);
+ return GDBMSHELL_SYNTAX;
+ }
+ }
+ else if (arg->type == GDBM_ARG_KVPAIR)
+ {
+ if (arg->v.kvpair->type != KV_STRING)
+ {
+ lerror (&arg->loc, _("%s: bad argument type"), arg->v.kvpair->key);
+ return GDBMSHELL_SYNTAX;
+ }
+ else if (arg->v.kvpair->next)
+ {
+ lerror (&arg->loc, _("unexpected compound statement"));
+ return GDBMSHELL_SYNTAX;
+ }
+
+ if (strcmp (arg->v.kvpair->key, "max-failures") == 0)
+ {
+ rcvr.max_failures = strtoul (arg->v.kvpair->val.s, &p, 10);
+ if (*p)
+ {
+ lerror (&arg->loc, _("not a number (stopped near %s)"), p);
+ return GDBMSHELL_SYNTAX;
+ }
+ flags |= GDBM_RCVR_MAX_FAILURES;
+ }
+ else if (strcmp (arg->v.kvpair->key, "max-failed-keys") == 0)
+ {
+ rcvr.max_failed_keys = strtoul (arg->v.kvpair->val.s, &p, 10);
+ if (*p)
+ {
+ lerror (&arg->loc, _("not a number (stopped near %s)"), p);
+ return GDBMSHELL_SYNTAX;
+ }
+ flags |= GDBM_RCVR_MAX_FAILED_KEYS;
+ }
+ else if (strcmp (arg->v.kvpair->key, "max-failed-buckets") == 0)
+ {
+ rcvr.max_failures = strtoul (arg->v.kvpair->val.s, &p, 10);
+ if (*p)
+ {
+ lerror (&arg->loc, _("not a number (stopped near %s)"), p);
+ return GDBMSHELL_SYNTAX;
+ }
+ flags |= GDBM_RCVR_MAX_FAILED_BUCKETS;
+ }
+ else
+ {
+ lerror (&arg->loc, _("unrecognized argument: %s"), arg->v.kvpair->key);
+ return GDBMSHELL_SYNTAX;
+ }
+ }
+ else
+ {
+ lerror (&arg->loc, _("unexpected datum"));
+ 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))
+ {
+ lerror (PARAM_LOCPTR (param, 0),
+ _("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)<