diff options
author | Sergey Poznyakoff <gray@gnu.org> | 2021-08-10 18:53:16 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org> | 2021-08-10 19:05:43 +0300 |
commit | 7ef79d63fdabba3a9bf49468ef9ecfbcb0bf117f (patch) | |
tree | d279230fe5bd88e6c0d17945d2c2d6fbb644fb34 /src | |
parent | 6622d0c799456fc34b3d33338746cbdd91e30abc (diff) | |
download | gdbm-7ef79d63fdabba3a9bf49468ef9ecfbcb0bf117f.tar.gz gdbm-7ef79d63fdabba3a9bf49468ef9ecfbcb0bf117f.tar.bz2 |
Move gdbmtool shell functions to the library.
* src/Makefile.am (libgdbmapp_a_SOURCES): Move gdbm shell
support to the library.
* src/gdbmtool.c: Move shell support to another file.
* src/gdbmtool.h (file_descr): New extern.
(gdbmshell, gdbmshell_run)
(variables_init, variables_free): New functions.
* src/gdbmtool.supp: New file.
* src/var.c (VAR_IS_SET): Change definition.
(variable): New member: init.
(variable_set): Change meaning of VARF_INIT.
(variables_free,variables_init): New protos.
* src/gdbmtool.c: New file.
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 31 | ||||
-rw-r--r-- | src/gdbmshell.c | 2429 | ||||
-rw-r--r-- | src/gdbmtool.c | 2440 | ||||
-rw-r--r-- | src/gdbmtool.h | 9 | ||||
-rw-r--r-- | src/gdbmtool.supp | 19 | ||||
-rw-r--r-- | src/var.c | 45 |
6 files changed, 2567 insertions, 2406 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index fa08bf3..d4d53c6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -85,34 +85,35 @@ libgdbmapp_a_SOURCES =\ mem.c\ gdbmapp.h\ parseopt.c\ - progname.c - -# Programs -bin_PROGRAMS = gdbmtool gdbm_load gdbm_dump - -gdbmtool_LDADD = \ - ./libgdbmapp.a\ - ./libgdbm.la\ - @READLINE_LIBS@ - -gdbmtool_SOURCES = \ + progname.c\ datconv.c\ gram.y\ input-argv.c\ input-file.c\ input-null.c\ lex.l\ - gdbmtool.h\ - gdbmtool.c\ + gdbmshell.c\ var.c\ util.c if GDBM_COND_READLINE - gdbmtool_SOURCES += input-rl.c + libgdbmapp_a_SOURCES += input-rl.c else - gdbmtool_SOURCES += input-std.c + libgdbmapp_a_SOURCES += input-std.c endif +# Programs +bin_PROGRAMS = gdbmtool gdbm_load gdbm_dump + +gdbmtool_LDADD = \ + ./libgdbmapp.a\ + ./libgdbm.la\ + @READLINE_LIBS@ + +gdbmtool_SOURCES = \ + gdbmtool.h\ + gdbmtool.c + AM_YFLAGS = -dv $(YFLAGS_DEBUG) AM_LFLAGS = $(LFLAGS_DEBUG) diff --git a/src/gdbmshell.c b/src/gdbmshell.c new file mode 100644 index 0000000..d782d0d --- /dev/null +++ b/src/gdbmshell.c @@ -0,0 +1,2429 @@ +/* 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 <termios.h> +#include <stdarg.h> +#ifdef HAVE_LOCALE_H +# include <locale.h> +#endif + +char *file_name = NULL; /* Database file name */ +int file_descr = -1; /* Database file descriptor */ +int open_mode; /* Default open mode */ +int open_format; /* Default format for the open command */ + +static GDBM_FILE gdbm_file = NULL; /* Database to operate upon */ +static datum key_data; /* Current key */ +static datum return_data; /* Current data */ + + +static void +closedb (void) +{ + if (gdbm_file) + { + gdbm_close (gdbm_file); + gdbm_file = NULL; + free (file_name); + file_name = NULL; + file_descr = -1; + } + + free (key_data.dptr); + key_data.dptr = NULL; + key_data.dsize = 0; + + free (return_data.dptr); + return_data.dptr = NULL; + return_data.dsize = 0; +} + +static int +opendb (char *dbname, int fd) +{ + int cache_size = 0; + int block_size = 0; + int flags = open_format; + int filemode; + GDBM_FILE db; + + 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_is_true ("lock")) + flags |= GDBM_NOLOCK; + if (!variable_is_true ("mmap")) + flags |= GDBM_NOMMAP; + if (variable_is_true ("sync")) + flags |= GDBM_SYNC; + + if (open_mode == GDBM_NEWDB) + { + if (interactive () && variable_is_true ("confirm") && + access (dbname, F_OK) == 0) + { + if (!getyn (_("database %s already exists; overwrite"), dbname)) + return 1; + } + } + + if (variable_get ("filemode", VART_INT, (void**) &filemode)) + abort (); + + if (fd > 0) + db = gdbm_fd_open (fd, dbname, block_size, open_mode | flags, NULL); + else + db = gdbm_open (dbname, block_size, open_mode | flags, filemode, NULL); + + if (db == NULL) + { + terror (_("cannot open database %s: %s"), dbname, + gdbm_strerror (gdbm_errno)); + return 1; + } + + if (cache_size && + gdbm_setopt (db, GDBM_CACHESIZE, &cache_size, sizeof (int)) == -1) + terror (_("gdbm_setopt failed: %s"), gdbm_strerror (gdbm_errno)); + + if (variable_is_true ("coalesce")) + { + int t = 1; + if (gdbm_setopt (db, GDBM_SETCOALESCEBLKS, &t, sizeof (t)) == -1) + terror (_("gdbm_setopt failed: %s"), gdbm_strerror (gdbm_errno)); + } + if (variable_is_true ("centfree")) + { + int t = 1; + if (gdbm_setopt (db, GDBM_SETCENTFREE, &t, sizeof (t)) == -1) + terror (_("gdbm_setopt failed: %s"), gdbm_strerror (gdbm_errno)); + } + + if (gdbm_file) + gdbm_close (gdbm_file); + + gdbm_file = db; + return 0; +} + +static int +checkdb (void) +{ + if (!gdbm_file) + { + if (!file_name) + { + file_name = estrdup (GDBMTOOL_DEFFILE); + terror (_("warning: using default database file %s"), file_name); + } + return opendb (file_name, file_descr); + } + return 0; +} + +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 6 + 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]); + } +} + +/* Debug procedure to print the contents of the current hash bucket. */ +static void +print_bucket (FILE *fp, hash_bucket *bucket, const char *mesg, ...) +{ + int index; + va_list ap; + + fprintf (fp, "******* "); + va_start(ap, mesg); + vfprintf (fp, mesg, ap); + va_end (ap); + fprintf (fp, " **********\n\n"); + fprintf (fp, + _("bits = %d\ncount= %d\nHash Table:\n"), + bucket->bucket_bits, bucket->count); + 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 = %1d\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 void +_gdbm_print_avail_list (FILE *fp, GDBM_FILE dbf) +{ + if (gdbm_avail_traverse (dbf, avail_list_print, fp)) + terror ("%s", gdbm_strerror (gdbm_errno)); +} + +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 void +open_handler (struct command_param *param, + struct command_environ *cenv GDBM_ARG_UNUSED) +{ + char *name = tildexpand (PARAM_STRING (param, 0)); + closedb (); + if (opendb (name, -1) == 0) + file_name = name; + else + free (name); +} + +/* Close database */ +static void +close_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv GDBM_ARG_UNUSED) +{ + if (!gdbm_file) + terror (_("nothing to close")); + else + closedb (); +} + +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 void +count_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv) +{ + gdbm_count_t count; + + if (gdbm_count (gdbm_file, &count)) + terror ("gdbm_count: %s", gdbm_strerror (gdbm_errno)); + else + { + char buf[128]; + char *p = count_to_str (count, buf, sizeof buf); + + if (!p) + terror (_("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); + } +} + +/* delete KEY - delete a key*/ +static void +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) + terror (_("Item not found")); + else + terror (_("Can't delete: %s"), gdbm_strerror (gdbm_errno)); + } +} + +/* fetch KEY - fetch a record by its key */ +static void +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); + free (return_data.dptr); + } + else if (gdbm_errno == GDBM_ITEM_NOT_FOUND) + terror ("%s", _("No such item found.")); + else + terror (_("Can't fetch data: %s"), gdbm_strerror (gdbm_errno)); +} + +/* store KEY DATA - store data */ +static void +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) + terror (_("Item not inserted: %s."), gdbm_db_strerror (gdbm_file)); +} + +/* first - begin iteration */ + +static void +firstkey_handler (struct command_param *param, struct command_environ *cenv) +{ + if (key_data.dptr != NULL) + free (key_data.dptr); + 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); + + free (return_data.dptr); + } + else if (gdbm_errno == GDBM_ITEM_NOT_FOUND) + fprintf (cenv->fp, _("No such item found.\n")); + else + terror (_("Can't find key: %s"), gdbm_strerror (gdbm_errno)); +} + +/* next [KEY] - next key */ +static void +nextkey_handler (struct command_param *param, struct command_environ *cenv) +{ + if (param->argc == 1) + { + if (key_data.dptr != NULL) + free (key_data.dptr); + 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) + { + free (key_data.dptr); + 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); + + free (return_data.dptr); + } + else if (gdbm_errno == GDBM_ITEM_NOT_FOUND) + { + terror ("%s", _("No such item found.")); + free (key_data.dptr); + key_data.dptr = NULL; + } + else + terror (_("Can't find key: %s"), gdbm_strerror (gdbm_errno)); +} + +/* reorganize */ +static void +reorganize_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv) +{ + if (gdbm_reorganize (gdbm_file)) + terror ("%s", _("Reorganization failed.")); + else + fprintf (cenv->fp, "%s\n", _("Reorganization succeeded.")); +} + +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 sumamry verbose backup max-failed-keys=N max-failed-buckets=N max-failures=N */ +static void +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) + { + printf (_("not a number (stopped near %s)\n"), p); + return; + } + 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) + { + printf (_("not a number (stopped near %s)\n"), p); + return; + } + 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) + { + printf (_("not a number (stopped near %s)\n"), p); + return; + } + flags |= GDBM_RCVR_MAX_FAILED_BUCKETS; + } + else + { + terror (_("unrecognized argument: %s"), arg); + return; + } + } + + 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 + { + fprintf (stderr, _("Recovery failed: %s"), gdbm_strerror (gdbm_errno)); + if (gdbm_syserr[gdbm_errno]) + fprintf (stderr, ": %s", strerror (errno)); + fputc ('\n', stderr); + } +} + +/* 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) +{ + if (checkdb ()) + return 1; + if (exp_count) + *exp_count = _gdbm_avail_list_size (gdbm_file, SIZE_T_MAX); + return 0; +} + +static void +avail_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv) +{ + _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) +{ + if (checkdb ()) + return 1; + if (!gdbm_file->bucket) + return 0; + if (exp_count) + *exp_count = gdbm_file->bucket + ? bucket_print_lines (gdbm_file->bucket) + 3 + : 1; + return 0; +} + +static void +print_current_bucket_handler (struct command_param *param, + struct command_environ *cenv) +{ + if (!gdbm_file->bucket) + fprintf (cenv->fp, _("no current bucket\n")); + else + { + if (param->argc) + print_bucket (cenv->fp, gdbm_file->bucket, _("Bucket #%s"), + PARAM_STRING (param, 0)); + else + print_bucket (cenv->fp, gdbm_file->bucket, "%s", _("Current bucket")); + fprintf (cenv->fp, _("\n current directory entry = %d.\n"), + gdbm_file->bucket_dir); + fprintf (cenv->fp, _(" current bucket address = %lu.\n"), + (unsigned long) gdbm_file->cache_entry->ca_adr); + } +} + +int +getnum (int *pnum, char *arg, char **endp) +{ + char *p; + unsigned long x = strtoul (arg, &p, 10); + if (*p && !isspace (*p)) + { + printf (_("not a number (stopped near %s)\n"), p); + return 1; + } + while (*p && isspace (*p)) + p++; + if (endp) + *endp = p; + else if (*p) + { + printf (_("not a number (stopped near %s)\n"), 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 temp; + + if (checkdb ()) + return 1; + + if (getnum (&temp, PARAM_STRING (param, 0), NULL)) + return 1; + + if (temp >= GDBM_DIR_COUNT (gdbm_file)) + { + terror (_("Not a bucket.")); + return 1; + } + if (_gdbm_get_bucket (gdbm_file, temp)) + { + terror ("%s", gdbm_db_strerror (gdbm_file)); + return 1; + } + if (exp_count) + *exp_count = bucket_print_lines (gdbm_file->bucket) + 3; + return 0; +} + +/* 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) +{ + if (checkdb ()) + return 1; + if (exp_count) + *exp_count = GDBM_DIR_COUNT (gdbm_file) + 3; + return 0; +} + +static size_t +bucket_count (void) +{ + size_t count = 0; + + if (gdbm_bucket_count (gdbm_file, &count)) + { + terror ("gdbm_bucket_count: %s", gdbm_strerror (gdbm_errno)); + } + return count; +} + +static void +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. Bits = %d, Buckets = %zu.\n\n"), + gdbm_file->header->dir_size, gdbm_file->header->dir_bits, + bucket_count ()); + + for (i = 0; i < GDBM_DIR_COUNT (gdbm_file); i++) + fprintf (cenv->fp, " %10d: %12lu\n", + i, (unsigned long) gdbm_file->dir[i]); +} + +/* 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 n; + + if (checkdb ()) + return 1; + + 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 0; +} + +static void +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, _(" table = %lu\n"), + (unsigned long) gdbm_file->header->dir); + fprintf (fp, _(" table size = %d\n"), gdbm_file->header->dir_size); + fprintf (fp, _(" table bits = %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 nx blk = %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); + } +} + +static void +sync_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv GDBM_ARG_UNUSED) +{ + if (gdbm_sync (gdbm_file)) + terror ("%s", gdbm_db_strerror (gdbm_file)); +} + +static void +upgrade_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv GDBM_ARG_UNUSED) +{ + if (gdbm_convert (gdbm_file, GDBM_NUMSYNC)) + terror ("%s", gdbm_db_strerror (gdbm_file)); +} + +static void +downgrade_handler (struct command_param *param GDBM_ARG_UNUSED, + struct command_environ *cenv GDBM_ARG_UNUSED) +{ + if (gdbm_convert (gdbm_file, 0)) + terror ("%s", gdbm_db_strerror (gdbm_file)); +} + +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)); + fprintf (fp, "%ld.%09ld", st.st_mtim.tv_sec, st.st_mtim.tv_nsec); + 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 + } +}; + +void +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); +} + + +/* hash KEY - hash the key */ +static void +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"), |