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 * ====================================================================
25 #include "svn_dirent_uri.h"
27 #include "svn_pools.h"
28 #include "private/svn_string_private.h"
30 #include "low_level.h"
31 #include "rep-cache.h"
33 #include "transaction.h"
35 #include "cached_data.h"
38 #include "../libsvn_fs/fs-loader.h"
40 #include "svn_private_config.h"
42 /* Set *EXISTS to TRUE, if the revision / pack file for REV exists in FS.
43 Use SCRATCH_POOL for temporary allocations. */
45 revision_file_exists(svn_boolean_t *exists,
48 apr_pool_t *scratch_pool)
51 const char *path = svn_fs_x__path_rev_absolute(fs, rev, scratch_pool);
52 SVN_ERR(svn_io_check_path(path, &kind, scratch_pool));
54 *exists = kind == svn_node_file;
58 /* Part of the recovery procedure. Return the largest revision *REV in
59 filesystem FS. Use SCRATCH_POOL for temporary allocation. */
61 recover_get_largest_revision(svn_fs_t *fs,
63 apr_pool_t *scratch_pool)
65 /* Discovering the largest revision in the filesystem would be an
66 expensive operation if we did a readdir() or searched linearly,
67 so we'll do a form of binary search. left is a revision that we
68 know exists, right a revision that we know does not exist. */
70 svn_revnum_t left, right = 1;
72 iterpool = svn_pool_create(scratch_pool);
73 /* Keep doubling right, until we find a revision that doesn't exist. */
77 svn_pool_clear(iterpool);
79 SVN_ERR(revision_file_exists(&exists, fs, right, iterpool));
88 /* We know that left exists and right doesn't. Do a normal bsearch to find
90 while (left + 1 < right)
92 svn_revnum_t probe = left + ((right - left) / 2);
94 svn_pool_clear(iterpool);
96 SVN_ERR(revision_file_exists(&exists, fs, probe, iterpool));
103 svn_pool_destroy(iterpool);
105 /* left is now the largest revision that exists. */
110 /* Delete all files and sub-directories (recursively) of DIR_PATH but
111 leave DIR_PATH itself in place. Use SCRATCH_POOL for temporaries. */
113 clear_directory(const char *dir_path,
114 apr_pool_t *scratch_pool)
117 apr_hash_index_t *hi;
118 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
120 SVN_ERR(svn_io_get_dirents3(&dirents, dir_path, TRUE, scratch_pool,
123 for (hi = apr_hash_first(scratch_pool, dirents);
125 hi = apr_hash_next(hi))
129 svn_dirent_t *dirent;
131 svn_pool_clear(iterpool);
132 apr_hash_this(hi, (const void **)&name, NULL, (void **)&dirent);
134 path = svn_dirent_join(dir_path, name, iterpool);
135 if (dirent->kind == svn_node_dir)
136 SVN_ERR(svn_io_remove_dir2(path, TRUE, NULL, NULL, iterpool));
138 SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
141 svn_pool_destroy(iterpool);
146 /* Delete all uncommitted transaction data from FS.
147 Use SCRATCH_POOL for temporaries. */
149 discard_transactions(svn_fs_t *fs,
150 apr_pool_t *scratch_pool)
152 svn_fs_x__data_t *ffd = fs->fsap_data;
153 svn_fs_x__shared_data_t *ffsd = ffd->shared;
155 /* In case this FS has been opened more than once in this process,
156 we should purge their shared transaction data as well. We do the
157 same as abort_txn would, except that we don't expect all txn files
158 to be complete on disk. */
161 svn_fs_x__shared_txn_data_t *txn = ffsd->txns;
162 ffsd->txns = txn->next;
164 svn_pool_destroy(txn->pool);
167 /* Remove anything from the transaction folders. */
168 SVN_ERR(clear_directory(svn_fs_x__path_txns_dir(fs, scratch_pool),
170 SVN_ERR(clear_directory(svn_fs_x__path_txn_proto_revs(fs, scratch_pool),
176 /* Reset txn-current in FS. Use SCRATCH_POOL for temporaries. */
178 reset_txn_number(svn_fs_t *fs,
179 apr_pool_t *scratch_pool)
181 const char *initial_txn = "0\n";
182 SVN_ERR(svn_io_write_atomic2(svn_fs_x__path_txn_current(fs, scratch_pool),
183 initial_txn, strlen(initial_txn),
184 svn_fs_x__path_uuid(fs, scratch_pool),
185 FALSE, scratch_pool));
190 /* Baton used for recover_body below. */
191 typedef struct recover_baton_t {
193 svn_cancel_func_t cancel_func;
197 /* The work-horse for svn_fs_x__recover, called with the FS
198 write lock. This implements the svn_fs_x__with_write_lock()
199 'body' callback type. BATON is a 'recover_baton_t *'. */
201 recover_body(void *baton,
202 apr_pool_t *scratch_pool)
204 recover_baton_t *b = baton;
205 svn_fs_t *fs = b->fs;
206 svn_fs_x__data_t *ffd = fs->fsap_data;
207 svn_revnum_t max_rev;
208 svn_revnum_t youngest_rev;
209 svn_boolean_t revprop_missing = TRUE;
210 svn_boolean_t revprop_accessible = FALSE;
212 /* Lose potentially corrupted data in temp files */
213 SVN_ERR(svn_fs_x__reset_revprop_generation_file(fs, scratch_pool));
215 /* The admin may have created a plain copy of this repo before attempting
216 to recover it (hotcopy may or may not work with corrupted repos).
217 Bump the instance ID. */
218 SVN_ERR(svn_fs_x__set_uuid(fs, fs->uuid, NULL, TRUE, scratch_pool));
220 /* Because transactions are not resilient against system crashes,
221 any existing transaction is suspect (and would probably not be
222 reopened anyway). Get rid of those. */
223 SVN_ERR(discard_transactions(fs, scratch_pool));
224 SVN_ERR(reset_txn_number(fs, scratch_pool));
226 /* We need to know the largest revision in the filesystem. */
227 SVN_ERR(recover_get_largest_revision(fs, &max_rev, scratch_pool));
229 /* Get the expected youngest revision */
230 SVN_ERR(svn_fs_x__youngest_rev(&youngest_rev, fs, scratch_pool));
234 Since the revprops file is written after the revs file, the true
235 maximum available revision is the youngest one for which both are
236 present. That's probably the same as the max_rev we just found,
237 but if it's not, we could, in theory, repeatedly decrement
238 max_rev until we find a revision that has both a revs and
239 revprops file, then write db/current with that.
241 But we choose not to. If a repository is so corrupt that it's
242 missing at least one revprops file, we shouldn't assume that the
243 youngest revision for which both the revs and revprops files are
244 present is healthy. In other words, we're willing to recover
245 from a missing or out-of-date db/current file, because db/current
246 is truly redundant -- it's basically a cache so we don't have to
247 find max_rev each time, albeit a cache with unusual semantics,
248 since it also officially defines when a revision goes live. But
249 if we're missing more than the cache, it's time to back out and
250 let the admin reconstruct things by hand: correctness at that
251 point may depend on external things like checking a commit email
252 list, looking in particular working copies, etc.
254 This policy matches well with a typical naive backup scenario.
255 Say you're rsyncing your FSX repository nightly to the same
256 location. Once revs and revprops are written, you've got the
257 maximum rev; if the backup should bomb before db/current is
258 written, then db/current could stay arbitrarily out-of-date, but
259 we can still recover. It's a small window, but we might as well
262 /* Even if db/current were missing, it would be created with 0 by
263 get_youngest(), so this conditional remains valid. */
264 if (youngest_rev > max_rev)
265 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
266 _("Expected current rev to be <= %ld "
267 "but found %ld"), max_rev, youngest_rev);
269 /* Before setting current, verify that there is a revprops file
270 for the youngest revision. (Issue #2992) */
271 if (svn_fs_x__is_packed_revprop(fs, max_rev))
274 = svn_fs_x__packed_revprop_available(&revprop_missing, fs, max_rev,
279 svn_node_kind_t youngest_revprops_kind;
280 SVN_ERR(svn_io_check_path(svn_fs_x__path_revprops(fs, max_rev,
282 &youngest_revprops_kind, scratch_pool));
284 if (youngest_revprops_kind == svn_node_file)
286 revprop_missing = FALSE;
287 revprop_accessible = TRUE;
289 else if (youngest_revprops_kind != svn_node_none)
291 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
292 _("Revision %ld has a non-file where its "
293 "revprops file should be"),
298 if (!revprop_accessible)
302 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
303 _("Revision %ld has a revs file but no "
309 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
310 _("Revision %ld has a revs file but the "
311 "revprops file is inaccessible"),
316 /* Prune younger-than-(newfound-youngest) revisions from the rep
317 cache if sharing is enabled taking care not to create the cache
318 if it does not exist. */
319 if (ffd->rep_sharing_allowed)
321 svn_boolean_t rep_cache_exists;
323 SVN_ERR(svn_fs_x__exists_rep_cache(&rep_cache_exists, fs,
325 if (rep_cache_exists)
326 SVN_ERR(svn_fs_x__del_rep_reference(fs, max_rev, scratch_pool));
329 /* Now store the discovered youngest revision, and the next IDs if
330 relevant, in a new 'current' file. */
331 return svn_fs_x__write_current(fs, max_rev, scratch_pool);
334 /* This implements the fs_library_vtable_t.recover() API. */
336 svn_fs_x__recover(svn_fs_t *fs,
337 svn_cancel_func_t cancel_func,
339 apr_pool_t *scratch_pool)
343 /* We have no way to take out an exclusive lock in FSX, so we're
344 restricted as to the types of recovery we can do. Luckily,
345 we just want to recreate the 'current' file, and we can do that just
346 by blocking other writers. */
348 b.cancel_func = cancel_func;
349 b.cancel_baton = cancel_baton;
350 return svn_fs_x__with_all_locks(fs, recover_body, &b, scratch_pool);