1 /* recovery.c --- FSX recovery functionality
3 * ====================================================================
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
20 * ====================================================================
26 #include "svn_pools.h"
27 #include "private/svn_string_private.h"
29 #include "low_level.h"
30 #include "rep-cache.h"
32 #include "transaction.h"
34 #include "cached_data.h"
37 #include "../libsvn_fs/fs-loader.h"
39 #include "svn_private_config.h"
41 /* Part of the recovery procedure. Return the largest revision *REV in
42 filesystem FS. Use SCRATCH_POOL for temporary allocation. */
44 recover_get_largest_revision(svn_fs_t *fs,
46 apr_pool_t *scratch_pool)
48 /* Discovering the largest revision in the filesystem would be an
49 expensive operation if we did a readdir() or searched linearly,
50 so we'll do a form of binary search. left is a revision that we
51 know exists, right a revision that we know does not exist. */
53 svn_revnum_t left, right = 1;
55 iterpool = svn_pool_create(scratch_pool);
56 /* Keep doubling right, until we find a revision that doesn't exist. */
60 svn_fs_x__revision_file_t *file;
61 svn_pool_clear(iterpool);
63 err = svn_fs_x__open_pack_or_rev_file(&file, fs, right, iterpool,
65 if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
78 /* We know that left exists and right doesn't. Do a normal bsearch to find
80 while (left + 1 < right)
82 svn_revnum_t probe = left + ((right - left) / 2);
84 svn_fs_x__revision_file_t *file;
85 svn_pool_clear(iterpool);
87 err = svn_fs_x__open_pack_or_rev_file(&file, fs, probe, iterpool,
89 if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
101 svn_pool_destroy(iterpool);
103 /* left is now the largest revision that exists. */
108 /* Baton used for recover_body below. */
109 typedef struct recover_baton_t {
111 svn_cancel_func_t cancel_func;
115 /* The work-horse for svn_fs_x__recover, called with the FS
116 write lock. This implements the svn_fs_x__with_write_lock()
117 'body' callback type. BATON is a 'recover_baton_t *'. */
119 recover_body(void *baton,
120 apr_pool_t *scratch_pool)
122 recover_baton_t *b = baton;
123 svn_fs_t *fs = b->fs;
124 svn_fs_x__data_t *ffd = fs->fsap_data;
125 svn_revnum_t max_rev;
126 svn_revnum_t youngest_rev;
127 svn_boolean_t revprop_missing = TRUE;
128 svn_boolean_t revprop_accessible = FALSE;
130 /* Lose potentially corrupted data in temp files */
131 SVN_ERR(svn_fs_x__reset_revprop_generation_file(fs, scratch_pool));
133 /* The admin may have created a plain copy of this repo before attempting
134 to recover it (hotcopy may or may not work with corrupted repos).
135 Bump the instance ID. */
136 SVN_ERR(svn_fs_x__set_uuid(fs, fs->uuid, NULL, scratch_pool));
138 /* We need to know the largest revision in the filesystem. */
139 SVN_ERR(recover_get_largest_revision(fs, &max_rev, scratch_pool));
141 /* Get the expected youngest revision */
142 SVN_ERR(svn_fs_x__youngest_rev(&youngest_rev, fs, scratch_pool));
146 Since the revprops file is written after the revs file, the true
147 maximum available revision is the youngest one for which both are
148 present. That's probably the same as the max_rev we just found,
149 but if it's not, we could, in theory, repeatedly decrement
150 max_rev until we find a revision that has both a revs and
151 revprops file, then write db/current with that.
153 But we choose not to. If a repository is so corrupt that it's
154 missing at least one revprops file, we shouldn't assume that the
155 youngest revision for which both the revs and revprops files are
156 present is healthy. In other words, we're willing to recover
157 from a missing or out-of-date db/current file, because db/current
158 is truly redundant -- it's basically a cache so we don't have to
159 find max_rev each time, albeit a cache with unusual semantics,
160 since it also officially defines when a revision goes live. But
161 if we're missing more than the cache, it's time to back out and
162 let the admin reconstruct things by hand: correctness at that
163 point may depend on external things like checking a commit email
164 list, looking in particular working copies, etc.
166 This policy matches well with a typical naive backup scenario.
167 Say you're rsyncing your FSX repository nightly to the same
168 location. Once revs and revprops are written, you've got the
169 maximum rev; if the backup should bomb before db/current is
170 written, then db/current could stay arbitrarily out-of-date, but
171 we can still recover. It's a small window, but we might as well
174 /* Even if db/current were missing, it would be created with 0 by
175 get_youngest(), so this conditional remains valid. */
176 if (youngest_rev > max_rev)
177 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
178 _("Expected current rev to be <= %ld "
179 "but found %ld"), max_rev, youngest_rev);
181 /* Before setting current, verify that there is a revprops file
182 for the youngest revision. (Issue #2992) */
183 if (svn_fs_x__is_packed_revprop(fs, max_rev))
186 = svn_fs_x__packed_revprop_available(&revprop_missing, fs, max_rev,
191 svn_node_kind_t youngest_revprops_kind;
192 SVN_ERR(svn_io_check_path(svn_fs_x__path_revprops(fs, max_rev,
194 &youngest_revprops_kind, scratch_pool));
196 if (youngest_revprops_kind == svn_node_file)
198 revprop_missing = FALSE;
199 revprop_accessible = TRUE;
201 else if (youngest_revprops_kind != svn_node_none)
203 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
204 _("Revision %ld has a non-file where its "
205 "revprops file should be"),
210 if (!revprop_accessible)
214 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
215 _("Revision %ld has a revs file but no "
221 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
222 _("Revision %ld has a revs file but the "
223 "revprops file is inaccessible"),
228 /* Prune younger-than-(newfound-youngest) revisions from the rep
229 cache if sharing is enabled taking care not to create the cache
230 if it does not exist. */
231 if (ffd->rep_sharing_allowed)
233 svn_boolean_t rep_cache_exists;
235 SVN_ERR(svn_fs_x__exists_rep_cache(&rep_cache_exists, fs,
237 if (rep_cache_exists)
238 SVN_ERR(svn_fs_x__del_rep_reference(fs, max_rev, scratch_pool));
241 /* Now store the discovered youngest revision, and the next IDs if
242 relevant, in a new 'current' file. */
243 return svn_fs_x__write_current(fs, max_rev, scratch_pool);
246 /* This implements the fs_library_vtable_t.recover() API. */
248 svn_fs_x__recover(svn_fs_t *fs,
249 svn_cancel_func_t cancel_func,
251 apr_pool_t *scratch_pool)
255 /* We have no way to take out an exclusive lock in FSX, so we're
256 restricted as to the types of recovery we can do. Luckily,
257 we just want to recreate the 'current' file, and we can do that just
258 by blocking other writers. */
260 b.cancel_func = cancel_func;
261 b.cancel_baton = cancel_baton;
262 return svn_fs_x__with_all_locks(fs, recover_body, &b, scratch_pool);