aboutsummaryrefslogtreecommitdiff
path: root/src/recover.c
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2016-07-19 12:01:48 +0300
committerSergey Poznyakoff <gray@gnu.org>2016-07-19 12:58:16 +0300
commit85f1e72da83e3078e2ae1f78093ef0966e43cec1 (patch)
treeb00707979e9088d5f7b7880d438260eb063c9160 /src/recover.c
parent8e83f209342e2b035bbb19efa87ec5295158ef65 (diff)
downloadgdbm-85f1e72da83e3078e2ae1f78093ef0966e43cec1.tar.gz
gdbm-85f1e72da83e3078e2ae1f78093ef0966e43cec1.tar.bz2
Implement gdbm_recover function
* configure.ac: Don't check for rename. * src/Makefile.am (libgdbm_la_SOURCES): Add recover.c * src/recover.c: New file. * src/bucket.c (_gdbm_get_bucket): Remove extra space before [ * src/err.c (prerror): Take additional argument (gdbm_perror): Print system errno if necessary. * src/gdbm.h.in (GDBM_CLOERROR): New flag. (gdbm_fd_open, gdbm_copy_meta): New proto. (gdbm_last_syserr,gdbm_db_strerror,gdbm_recover): New proto. (gdbm_syserr): New extern. (gdbm_recovery): New struct. (GDBM_RCVR_DEFAULT,GDBM_RCVR_ERRFUN) (GDBM_RCVR_MAX_FAILED_KEYS) (GDBM_RCVR_MAX_FAILED_BUCKETS) (GDBM_RCVR_MAX_FAILURES) (GDBM_RCVR_BACKUP): New flags. (GDBM_BACKUP_FAILED): New error code. * src/gdbmclose.c (gdbm_close): Work correctly if dbf->desc == -1. * src/gdbmcount.c (gdbm_count): Remove spurious sorting. Use _gdbm_next_bucket_dir for iterating over the buckets. * src/gdbmdefs.h (struct gdbm_file_info)<last_syserror> <last_errstr>: New members. * src/gdbmerrno.c (gdbm_set_errno): Set last_syserror as well. (gdbm_clear_error): Reset last_syserror. (gdbm_last_syserr): New function. (gdbm_errlist): New entry for GDBM_BACKUP_FAILED. (gdbm_db_strerror): New function. (gdbm_syserr): New global. * src/gdbmload.c (get_parms): Buffer can be NULL. * src/gdbmopen.c (gdbm_fd_open): New function. (gdbm_open): Rewrite as a wrapper over gdbm_fd_open. * src/gdbmreorg.c (gdbm_reorganize): Rewrite as a wrapper over gdbm_recover. * src/proto.h (_gdbm_next_bucket_dir): New proto. * src/gdbmtool.c: New command: recover. * tests/.gitignore: Add gtrecover * tests/gtrecover.c: New test program. * tests/Makefile.am: Build gtrecover
Diffstat (limited to 'src/recover.c')
-rw-r--r--src/recover.c359
1 files changed, 359 insertions, 0 deletions
diff --git a/src/recover.c b/src/recover.c
new file mode 100644
index 0000000..ad786b3
--- /dev/null
+++ b/src/recover.c
@@ -0,0 +1,359 @@
+/* This file is part of GDBM, the GNU data base manager.
+ Copyright (C) 2016 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"
+
+#define TMPSUF ".XXXXXX"
+
+int
+gdbm_copy_meta (GDBM_FILE dst, GDBM_FILE src)
+{
+ struct stat st;
+
+ if (fstat (src->desc, &st))
+ {
+ gdbm_set_errno (src, GDBM_FILE_STAT_ERROR, src->need_recovery);
+ return -1;
+ }
+ if (fchown (dst->desc, st.st_uid, st.st_gid))
+ {
+ gdbm_set_errno (dst, GDBM_ERR_FILE_OWNER, dst->need_recovery);
+ return -1;
+ }
+ if (fchmod (dst->desc, st.st_mode & 0777))
+ {
+ gdbm_set_errno (dst, GDBM_ERR_FILE_MODE, dst->need_recovery);
+ return -1;
+ }
+ return 0;
+}
+
+static char *
+backup_name (char const *name)
+{
+ char *buf;
+ size_t len;
+ size_t suf_pos;
+ size_t suf_len;
+
+#define INITIAL_SUFFIX ".~1~"
+
+ len = strlen (name + sizeof (INITIAL_SUFFIX));
+ buf = malloc (len);
+ if (!buf)
+ return NULL;
+ strcpy (buf, name);
+ suf_pos = strlen (buf) + 2;
+ suf_len = 1;
+ strcat (buf, INITIAL_SUFFIX);
+
+ while (access (buf, F_OK) == 0)
+ {
+ size_t i = suf_len;
+ while (buf[suf_pos + i - 1] == '9')
+ {
+ buf[suf_pos + i - 1] = '0';
+ i--;
+ if (i == 0)
+ {
+ char *p = realloc (buf, ++len);
+ if (!p)
+ {
+ SAVE_ERRNO (free (buf));
+ return NULL;
+ }
+ memmove (p + suf_pos + 1, p + suf_pos, suf_len + 2);
+ buf = p;
+ suf_len++;
+ i++;
+ }
+ }
+ ++buf[suf_pos + i - 1];
+ }
+ return buf;
+}
+
+static int
+_gdbm_finish_transfer (GDBM_FILE dbf, GDBM_FILE new_dbf,
+ gdbm_recovery *rcvr, int flags)
+{
+ int i;
+
+ /* Write everything. */
+ if (_gdbm_end_update (new_dbf))
+ {
+ gdbm_close (new_dbf);
+ return -1;
+ }
+ gdbm_sync (new_dbf);
+
+ if (gdbm_copy_meta (new_dbf, dbf))
+ {
+ gdbm_close (new_dbf);
+ return -1;
+ }
+
+#if HAVE_MMAP
+ _gdbm_mapped_unmap (dbf);
+#endif
+
+ if (flags & GDBM_RCVR_BACKUP)
+ {
+ char *bkname = backup_name (dbf->name);
+ if (!bkname)
+ {
+ SAVE_ERRNO (gdbm_close (new_dbf));
+ gdbm_set_errno (NULL, GDBM_BACKUP_FAILED, FALSE);
+ return -1;
+ }
+ if (rename (dbf->name, bkname) != 0)
+ {
+ SAVE_ERRNO (gdbm_close (new_dbf); free (bkname));
+ gdbm_set_errno (NULL, GDBM_BACKUP_FAILED, FALSE);
+ return -1;
+ }
+ rcvr->backup_name = bkname;
+ }
+
+ /* Move the new file to old name. */
+
+ if (rename (new_dbf->name, dbf->name) != 0)
+ {
+ gdbm_set_errno (NULL, GDBM_REORGANIZE_FAILED, FALSE);
+ gdbm_close (new_dbf);
+ return -1;
+ }
+
+ /* Fix up DBF to have the correct information for the new file. */
+ if (dbf->file_locking)
+ _gdbm_unlock_file (dbf);
+ close (dbf->desc);
+ free (dbf->header);
+ free (dbf->dir);
+
+ if (dbf->bucket_cache != NULL)
+ {
+ for (i = 0; i < dbf->cache_size; i++)
+ {
+ free (dbf->bucket_cache[i].ca_bucket);
+ free (dbf->bucket_cache[i].ca_data.dptr);
+ }
+ free (dbf->bucket_cache);
+ }
+
+ dbf->desc = new_dbf->desc;
+ dbf->header = new_dbf->header;
+ dbf->dir = new_dbf->dir;
+ dbf->bucket = new_dbf->bucket;
+ dbf->bucket_dir = new_dbf->bucket_dir;
+ dbf->last_read = new_dbf->last_read;
+ dbf->bucket_cache = new_dbf->bucket_cache;
+ dbf->cache_size = new_dbf->cache_size;
+ dbf->header_changed = new_dbf->header_changed;
+ dbf->directory_changed = new_dbf->directory_changed;
+ dbf->bucket_changed = new_dbf->bucket_changed;
+ dbf->second_changed = new_dbf->second_changed;
+
+ free (new_dbf);
+
+ #if HAVE_MMAP
+ /* Re-initialize mapping if required */
+ if (dbf->memory_mapping)
+ _gdbm_mapped_init (dbf);
+ #endif
+
+ /* Make sure the new database is all on disk. */
+ __fsync (dbf);
+
+ /* Force the right stuff for a correct bucket cache. */
+ dbf->cache_entry = &dbf->bucket_cache[0];
+ return _gdbm_get_bucket (dbf, 0);
+ }
+
+int
+_gdbm_next_bucket_dir (GDBM_FILE dbf, int bucket_dir)
+{
+ int dir_count = GDBM_DIR_COUNT (dbf);
+ if (bucket_dir < 0 || bucket_dir >= dir_count)
+ bucket_dir = dir_count;
+ else
+ {
+ off_t cur = dbf->dir[bucket_dir];
+ while (++bucket_dir < dir_count && cur == dbf->dir[bucket_dir])
+ ;
+ }
+ return bucket_dir;
+}
+
+
+static int
+run_recovery (GDBM_FILE dbf, GDBM_FILE new_dbf, gdbm_recovery *rcvr, int flags)
+{
+ int bucket_dir, i;
+ int nbuckets = GDBM_DIR_COUNT (dbf);
+
+ for (bucket_dir = 0; bucket_dir < nbuckets;
+ bucket_dir = _gdbm_next_bucket_dir (dbf, bucket_dir))
+ {
+
+ if (_gdbm_get_bucket (dbf, bucket_dir))
+ {
+ if (flags & GDBM_RCVR_ERRFUN)
+ rcvr->errfun (rcvr->data, _("can't read bucket #%d: %s"),
+ bucket_dir,
+ gdbm_db_strerror (dbf));
+ rcvr->failed_buckets++;
+ if ((flags & GDBM_RCVR_MAX_FAILED_BUCKETS)
+ && rcvr->failed_buckets == rcvr->max_failed_buckets)
+ return -1;
+ if ((flags & GDBM_RCVR_MAX_FAILURES)
+ && (rcvr->failed_buckets + rcvr->failed_keys) == rcvr->max_failures)
+ return -1;
+ }
+ else
+ {
+ rcvr->recovered_buckets++;
+ for (i = 0; i < dbf->header->bucket_elems; i++)
+ {
+ char *dptr;
+ datum key, data;
+
+ if (dbf->bucket->h_table[i].hash_value == -1)
+ continue;
+ dptr = _gdbm_read_entry (dbf, i);
+ if (dptr)
+ rcvr->recovered_keys++;
+ else
+ {
+ if (flags & GDBM_RCVR_ERRFUN)
+ rcvr->errfun (rcvr->data,
+ _("can't read key pair %d:%d (%lu:%d): %s"),
+ bucket_dir, i,
+ (unsigned long) dbf->bucket->h_table[i].data_pointer,
+ dbf->bucket->h_table[i].key_size
+ + dbf->bucket->h_table[i].data_size,
+ gdbm_db_strerror (dbf));
+ rcvr->failed_keys++;
+ if ((flags & GDBM_RCVR_MAX_FAILED_KEYS)
+ && rcvr->failed_keys == rcvr->max_failed_keys)
+ return -1;
+ if ((flags & GDBM_RCVR_MAX_FAILURES)
+ && (rcvr->failed_buckets + rcvr->failed_keys) == rcvr->max_failures)
+ return -1;
+ continue;
+ }
+
+ key.dptr = dptr;
+ key.dsize = dbf->bucket->h_table[i].key_size;
+
+ data.dptr = dptr + key.dsize;
+ data.dsize = dbf->bucket->h_table[i].data_size;
+
+ if (gdbm_store (new_dbf, key, data, GDBM_INSERT) != 0)
+ {
+ if (flags & GDBM_RCVR_ERRFUN)
+ rcvr->errfun (rcvr->data,
+ _("fatal: can't store element %d:%d (%lu:%d): %s"),
+ bucket_dir, i,
+ (unsigned long) dbf->bucket->h_table[i].data_pointer,
+ dbf->bucket->h_table[i].key_size
+ + dbf->bucket->h_table[i].data_size,
+ gdbm_db_strerror (new_dbf));
+ return -1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+int
+gdbm_recover (GDBM_FILE dbf, gdbm_recovery *rcvr, int flags)
+{
+ GDBM_FILE new_dbf; /* The new file. */
+ char *new_name; /* A temporary name. */
+ size_t len;
+ int fd;
+ int rc;
+ gdbm_recovery rs;
+
+ /* Readers can not reorganize! */
+ if (dbf->read_write == GDBM_READER)
+ {
+ gdbm_set_errno (dbf, GDBM_READER_CANT_REORGANIZE, dbf->need_recovery);
+ return -1;
+ }
+
+ /* Initialize gdbm_recovery structure */
+ if (!rcvr)
+ {
+ rcvr = &rs;
+ flags = 0;
+ }
+ rcvr->recovered_keys = 0;
+ rcvr->recovered_buckets = 0;
+ rcvr->failed_keys = 0;
+ rcvr->failed_buckets = 0;
+ rcvr->backup_name = NULL;
+
+ len = strlen (dbf->name);
+ new_name = malloc (len + sizeof (TMPSUF));
+ if (!new_name)
+ {
+ gdbm_set_errno (NULL, GDBM_MALLOC_ERROR, FALSE);
+ return -1;
+ }
+ strcat (strcpy (new_name, dbf->name), TMPSUF);
+
+ fd = mkstemp (new_name);
+ if (fd == -1)
+ {
+ gdbm_set_errno (NULL, GDBM_FILE_OPEN_ERROR, FALSE);
+ free (new_name);
+ return -1;
+ }
+
+ new_dbf = gdbm_fd_open (fd, new_name, dbf->header->block_size,
+ GDBM_WRCREAT
+ | (dbf->cloexec ? GDBM_CLOEXEC : 0)
+ | GDBM_CLOERROR, dbf->fatal_err);
+
+ SAVE_ERRNO (free (new_name));
+
+ if (new_dbf == NULL)
+ {
+ gdbm_set_errno (NULL, GDBM_REORGANIZE_FAILED, FALSE);
+ return -1;
+ }
+
+ rc = run_recovery (dbf, new_dbf, rcvr, flags);
+
+ if (rc == 0)
+ {
+ rc = _gdbm_finish_transfer (dbf, new_dbf, rcvr, flags);
+ if (rc == 0)
+ {
+ gdbm_clear_error (dbf);
+ dbf->need_recovery = FALSE;
+ }
+ }
+ else
+ gdbm_close (new_dbf);
+
+ return rc;
+}

Return to:

Send suggestions and report system problems to the System administrator.