]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_x/recovery.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_fs_x / recovery.c
1 /* recovery.c --- FSX recovery functionality
2 *
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
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
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
19  *    under the License.
20  * ====================================================================
21  */
22
23 #include "recovery.h"
24
25 #include "svn_hash.h"
26 #include "svn_pools.h"
27 #include "private/svn_string_private.h"
28
29 #include "low_level.h"
30 #include "rep-cache.h"
31 #include "revprops.h"
32 #include "transaction.h"
33 #include "util.h"
34 #include "cached_data.h"
35 #include "index.h"
36
37 #include "../libsvn_fs/fs-loader.h"
38
39 #include "svn_private_config.h"
40
41 /* Part of the recovery procedure.  Return the largest revision *REV in
42    filesystem FS.  Use SCRATCH_POOL for temporary allocation. */
43 static svn_error_t *
44 recover_get_largest_revision(svn_fs_t *fs,
45                              svn_revnum_t *rev,
46                              apr_pool_t *scratch_pool)
47 {
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. */
52   apr_pool_t *iterpool;
53   svn_revnum_t left, right = 1;
54
55   iterpool = svn_pool_create(scratch_pool);
56   /* Keep doubling right, until we find a revision that doesn't exist. */
57   while (1)
58     {
59       svn_error_t *err;
60       svn_fs_x__revision_file_t *file;
61       svn_pool_clear(iterpool);
62
63       err = svn_fs_x__open_pack_or_rev_file(&file, fs, right, iterpool,
64                                             iterpool);
65       if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
66         {
67           svn_error_clear(err);
68           break;
69         }
70       else
71         SVN_ERR(err);
72
73       right <<= 1;
74     }
75
76   left = right >> 1;
77
78   /* We know that left exists and right doesn't.  Do a normal bsearch to find
79      the last revision. */
80   while (left + 1 < right)
81     {
82       svn_revnum_t probe = left + ((right - left) / 2);
83       svn_error_t *err;
84       svn_fs_x__revision_file_t *file;
85       svn_pool_clear(iterpool);
86
87       err = svn_fs_x__open_pack_or_rev_file(&file, fs, probe, iterpool,
88                                             iterpool);
89       if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
90         {
91           svn_error_clear(err);
92           right = probe;
93         }
94       else
95         {
96           SVN_ERR(err);
97           left = probe;
98         }
99     }
100
101   svn_pool_destroy(iterpool);
102
103   /* left is now the largest revision that exists. */
104   *rev = left;
105   return SVN_NO_ERROR;
106 }
107
108 /* Baton used for recover_body below. */
109 typedef struct recover_baton_t {
110   svn_fs_t *fs;
111   svn_cancel_func_t cancel_func;
112   void *cancel_baton;
113 } recover_baton_t;
114
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 *'. */
118 static svn_error_t *
119 recover_body(void *baton,
120              apr_pool_t *scratch_pool)
121 {
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;
129
130   /* Lose potentially corrupted data in temp files */
131   SVN_ERR(svn_fs_x__reset_revprop_generation_file(fs, scratch_pool));
132
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));
137
138   /* We need to know the largest revision in the filesystem. */
139   SVN_ERR(recover_get_largest_revision(fs, &max_rev, scratch_pool));
140
141   /* Get the expected youngest revision */
142   SVN_ERR(svn_fs_x__youngest_rev(&youngest_rev, fs, scratch_pool));
143
144   /* Policy note:
145
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.
152
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.
165
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
172      do what we can. */
173
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);
180
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))
184     {
185       revprop_accessible
186         = svn_fs_x__packed_revprop_available(&revprop_missing, fs, max_rev,
187                                              scratch_pool);
188     }
189   else
190     {
191       svn_node_kind_t youngest_revprops_kind;
192       SVN_ERR(svn_io_check_path(svn_fs_x__path_revprops(fs, max_rev,
193                                                         scratch_pool),
194                                 &youngest_revprops_kind, scratch_pool));
195
196       if (youngest_revprops_kind == svn_node_file)
197         {
198           revprop_missing = FALSE;
199           revprop_accessible = TRUE;
200         }
201       else if (youngest_revprops_kind != svn_node_none)
202         {
203           return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
204                                   _("Revision %ld has a non-file where its "
205                                     "revprops file should be"),
206                                   max_rev);
207         }
208     }
209
210   if (!revprop_accessible)
211     {
212       if (revprop_missing)
213         {
214           return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
215                                   _("Revision %ld has a revs file but no "
216                                     "revprops file"),
217                                   max_rev);
218         }
219       else
220         {
221           return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
222                                   _("Revision %ld has a revs file but the "
223                                     "revprops file is inaccessible"),
224                                   max_rev);
225         }
226     }
227
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)
232     {
233       svn_boolean_t rep_cache_exists;
234
235       SVN_ERR(svn_fs_x__exists_rep_cache(&rep_cache_exists, fs,
236                                          scratch_pool));
237       if (rep_cache_exists)
238         SVN_ERR(svn_fs_x__del_rep_reference(fs, max_rev, scratch_pool));
239     }
240
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);
244 }
245
246 /* This implements the fs_library_vtable_t.recover() API. */
247 svn_error_t *
248 svn_fs_x__recover(svn_fs_t *fs,
249                   svn_cancel_func_t cancel_func,
250                   void *cancel_baton,
251                   apr_pool_t *scratch_pool)
252 {
253   recover_baton_t b;
254
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. */
259   b.fs = fs;
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);
263 }