summaryrefslogtreecommitdiff
path: root/subversion/libsvn_fs_x/recovery.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_fs_x/recovery.c')
-rw-r--r--subversion/libsvn_fs_x/recovery.c263
1 files changed, 263 insertions, 0 deletions
diff --git a/subversion/libsvn_fs_x/recovery.c b/subversion/libsvn_fs_x/recovery.c
new file mode 100644
index 0000000..984b740
--- /dev/null
+++ b/subversion/libsvn_fs_x/recovery.c
@@ -0,0 +1,263 @@
+/* recovery.c --- FSX recovery functionality
+*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "recovery.h"
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "private/svn_string_private.h"
+
+#include "low_level.h"
+#include "rep-cache.h"
+#include "revprops.h"
+#include "transaction.h"
+#include "util.h"
+#include "cached_data.h"
+#include "index.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+
+/* Part of the recovery procedure. Return the largest revision *REV in
+ filesystem FS. Use SCRATCH_POOL for temporary allocation. */
+static svn_error_t *
+recover_get_largest_revision(svn_fs_t *fs,
+ svn_revnum_t *rev,
+ apr_pool_t *scratch_pool)
+{
+ /* Discovering the largest revision in the filesystem would be an
+ expensive operation if we did a readdir() or searched linearly,
+ so we'll do a form of binary search. left is a revision that we
+ know exists, right a revision that we know does not exist. */
+ apr_pool_t *iterpool;
+ svn_revnum_t left, right = 1;
+
+ iterpool = svn_pool_create(scratch_pool);
+ /* Keep doubling right, until we find a revision that doesn't exist. */
+ while (1)
+ {
+ svn_error_t *err;
+ svn_fs_x__revision_file_t *file;
+ svn_pool_clear(iterpool);
+
+ err = svn_fs_x__open_pack_or_rev_file(&file, fs, right, iterpool,
+ iterpool);
+ if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
+ {
+ svn_error_clear(err);
+ break;
+ }
+ else
+ SVN_ERR(err);
+
+ right <<= 1;
+ }
+
+ left = right >> 1;
+
+ /* We know that left exists and right doesn't. Do a normal bsearch to find
+ the last revision. */
+ while (left + 1 < right)
+ {
+ svn_revnum_t probe = left + ((right - left) / 2);
+ svn_error_t *err;
+ svn_fs_x__revision_file_t *file;
+ svn_pool_clear(iterpool);
+
+ err = svn_fs_x__open_pack_or_rev_file(&file, fs, probe, iterpool,
+ iterpool);
+ if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
+ {
+ svn_error_clear(err);
+ right = probe;
+ }
+ else
+ {
+ SVN_ERR(err);
+ left = probe;
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ /* left is now the largest revision that exists. */
+ *rev = left;
+ return SVN_NO_ERROR;
+}
+
+/* Baton used for recover_body below. */
+typedef struct recover_baton_t {
+ svn_fs_t *fs;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+} recover_baton_t;
+
+/* The work-horse for svn_fs_x__recover, called with the FS
+ write lock. This implements the svn_fs_x__with_write_lock()
+ 'body' callback type. BATON is a 'recover_baton_t *'. */
+static svn_error_t *
+recover_body(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ recover_baton_t *b = baton;
+ svn_fs_t *fs = b->fs;
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ svn_revnum_t max_rev;
+ svn_revnum_t youngest_rev;
+ svn_boolean_t revprop_missing = TRUE;
+ svn_boolean_t revprop_accessible = FALSE;
+
+ /* Lose potentially corrupted data in temp files */
+ SVN_ERR(svn_fs_x__reset_revprop_generation_file(fs, scratch_pool));
+
+ /* The admin may have created a plain copy of this repo before attempting
+ to recover it (hotcopy may or may not work with corrupted repos).
+ Bump the instance ID. */
+ SVN_ERR(svn_fs_x__set_uuid(fs, fs->uuid, NULL, scratch_pool));
+
+ /* We need to know the largest revision in the filesystem. */
+ SVN_ERR(recover_get_largest_revision(fs, &max_rev, scratch_pool));
+
+ /* Get the expected youngest revision */
+ SVN_ERR(svn_fs_x__youngest_rev(&youngest_rev, fs, scratch_pool));
+
+ /* Policy note:
+
+ Since the revprops file is written after the revs file, the true
+ maximum available revision is the youngest one for which both are
+ present. That's probably the same as the max_rev we just found,
+ but if it's not, we could, in theory, repeatedly decrement
+ max_rev until we find a revision that has both a revs and
+ revprops file, then write db/current with that.
+
+ But we choose not to. If a repository is so corrupt that it's
+ missing at least one revprops file, we shouldn't assume that the
+ youngest revision for which both the revs and revprops files are
+ present is healthy. In other words, we're willing to recover
+ from a missing or out-of-date db/current file, because db/current
+ is truly redundant -- it's basically a cache so we don't have to
+ find max_rev each time, albeit a cache with unusual semantics,
+ since it also officially defines when a revision goes live. But
+ if we're missing more than the cache, it's time to back out and
+ let the admin reconstruct things by hand: correctness at that
+ point may depend on external things like checking a commit email
+ list, looking in particular working copies, etc.
+
+ This policy matches well with a typical naive backup scenario.
+ Say you're rsyncing your FSX repository nightly to the same
+ location. Once revs and revprops are written, you've got the
+ maximum rev; if the backup should bomb before db/current is
+ written, then db/current could stay arbitrarily out-of-date, but
+ we can still recover. It's a small window, but we might as well
+ do what we can. */
+
+ /* Even if db/current were missing, it would be created with 0 by
+ get_youngest(), so this conditional remains valid. */
+ if (youngest_rev > max_rev)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Expected current rev to be <= %ld "
+ "but found %ld"), max_rev, youngest_rev);
+
+ /* Before setting current, verify that there is a revprops file
+ for the youngest revision. (Issue #2992) */
+ if (svn_fs_x__is_packed_revprop(fs, max_rev))
+ {
+ revprop_accessible
+ = svn_fs_x__packed_revprop_available(&revprop_missing, fs, max_rev,
+ scratch_pool);
+ }
+ else
+ {
+ svn_node_kind_t youngest_revprops_kind;
+ SVN_ERR(svn_io_check_path(svn_fs_x__path_revprops(fs, max_rev,
+ scratch_pool),
+ &youngest_revprops_kind, scratch_pool));
+
+ if (youngest_revprops_kind == svn_node_file)
+ {
+ revprop_missing = FALSE;
+ revprop_accessible = TRUE;
+ }
+ else if (youngest_revprops_kind != svn_node_none)
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Revision %ld has a non-file where its "
+ "revprops file should be"),
+ max_rev);
+ }
+ }
+
+ if (!revprop_accessible)
+ {
+ if (revprop_missing)
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Revision %ld has a revs file but no "
+ "revprops file"),
+ max_rev);
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Revision %ld has a revs file but the "
+ "revprops file is inaccessible"),
+ max_rev);
+ }
+ }
+
+ /* Prune younger-than-(newfound-youngest) revisions from the rep
+ cache if sharing is enabled taking care not to create the cache
+ if it does not exist. */
+ if (ffd->rep_sharing_allowed)
+ {
+ svn_boolean_t rep_cache_exists;
+
+ SVN_ERR(svn_fs_x__exists_rep_cache(&rep_cache_exists, fs,
+ scratch_pool));
+ if (rep_cache_exists)
+ SVN_ERR(svn_fs_x__del_rep_reference(fs, max_rev, scratch_pool));
+ }
+
+ /* Now store the discovered youngest revision, and the next IDs if
+ relevant, in a new 'current' file. */
+ return svn_fs_x__write_current(fs, max_rev, scratch_pool);
+}
+
+/* This implements the fs_library_vtable_t.recover() API. */
+svn_error_t *
+svn_fs_x__recover(svn_fs_t *fs,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ recover_baton_t b;
+
+ /* We have no way to take out an exclusive lock in FSX, so we're
+ restricted as to the types of recovery we can do. Luckily,
+ we just want to recreate the 'current' file, and we can do that just
+ by blocking other writers. */
+ b.fs = fs;
+ b.cancel_func = cancel_func;
+ b.cancel_baton = cancel_baton;
+ return svn_fs_x__with_all_locks(fs, recover_body, &b, scratch_pool);
+}