diff options
author | Sergey Poznyakoff <gray@gnu.org> | 2016-07-19 12:01:48 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org> | 2016-07-19 12:58:16 +0300 |
commit | 85f1e72da83e3078e2ae1f78093ef0966e43cec1 (patch) | |
tree | b00707979e9088d5f7b7880d438260eb063c9160 /src/recover.c | |
parent | 8e83f209342e2b035bbb19efa87ec5295158ef65 (diff) | |
download | gdbm-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.c | 359 |
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; +} |