]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_fs/recovery.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_fs_fs / recovery.c
1 /* recovery.c --- FSFS 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 "index.h"
30 #include "low_level.h"
31 #include "rep-cache.h"
32 #include "revprops.h"
33 #include "util.h"
34 #include "cached_data.h"
35
36 #include "../libsvn_fs/fs-loader.h"
37
38 #include "svn_private_config.h"
39
40 /* Part of the recovery procedure.  Return the largest revision *REV in
41    filesystem FS.  Use POOL for temporary allocation. */
42 static svn_error_t *
43 recover_get_largest_revision(svn_fs_t *fs, svn_revnum_t *rev, apr_pool_t *pool)
44 {
45   /* Discovering the largest revision in the filesystem would be an
46      expensive operation if we did a readdir() or searched linearly,
47      so we'll do a form of binary search.  left is a revision that we
48      know exists, right a revision that we know does not exist. */
49   apr_pool_t *iterpool;
50   svn_revnum_t left, right = 1;
51
52   iterpool = svn_pool_create(pool);
53   /* Keep doubling right, until we find a revision that doesn't exist. */
54   while (1)
55     {
56       svn_error_t *err;
57       svn_fs_fs__revision_file_t *file;
58       svn_pool_clear(iterpool);
59
60       err = svn_fs_fs__open_pack_or_rev_file(&file, fs, right, iterpool,
61                                              iterpool);
62       if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
63         {
64           svn_error_clear(err);
65           break;
66         }
67       else
68         SVN_ERR(err);
69
70       right <<= 1;
71     }
72
73   left = right >> 1;
74
75   /* We know that left exists and right doesn't.  Do a normal bsearch to find
76      the last revision. */
77   while (left + 1 < right)
78     {
79       svn_revnum_t probe = left + ((right - left) / 2);
80       svn_error_t *err;
81       svn_fs_fs__revision_file_t *file;
82       svn_pool_clear(iterpool);
83
84       err = svn_fs_fs__open_pack_or_rev_file(&file, fs, probe, iterpool,
85                                              iterpool);
86       if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
87         {
88           svn_error_clear(err);
89           right = probe;
90         }
91       else
92         {
93           SVN_ERR(err);
94           left = probe;
95         }
96     }
97
98   svn_pool_destroy(iterpool);
99
100   /* left is now the largest revision that exists. */
101   *rev = left;
102   return SVN_NO_ERROR;
103 }
104
105 /* A baton for reading a fixed amount from an open file.  For
106    recover_find_max_ids() below. */
107 struct recover_read_from_file_baton
108 {
109   svn_stream_t *stream;
110   apr_pool_t *pool;
111   apr_off_t remaining;
112 };
113
114 /* A stream read handler used by recover_find_max_ids() below.
115    Read and return at most BATON->REMAINING bytes from the stream,
116    returning nothing after that to indicate EOF. */
117 static svn_error_t *
118 read_handler_recover(void *baton, char *buffer, apr_size_t *len)
119 {
120   struct recover_read_from_file_baton *b = baton;
121   apr_size_t bytes_to_read = *len;
122
123   if (b->remaining == 0)
124     {
125       /* Return a successful read of zero bytes to signal EOF. */
126       *len = 0;
127       return SVN_NO_ERROR;
128     }
129
130   if ((apr_int64_t)bytes_to_read > (apr_int64_t)b->remaining)
131     bytes_to_read = (apr_size_t)b->remaining;
132   b->remaining -= bytes_to_read;
133
134   return svn_stream_read_full(b->stream, buffer, &bytes_to_read);
135 }
136
137 /* Part of the recovery procedure.  Read the directory noderev at offset
138    OFFSET of file REV_FILE (the revision file of revision REV of
139    filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id
140    and copy-id of that node, if greater than the current value stored
141    in either.  Recurse into any child directories that were modified in
142    this revision.
143
144    MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE.
145
146    Perform temporary allocation in POOL. */
147 static svn_error_t *
148 recover_find_max_ids(svn_fs_t *fs,
149                      svn_revnum_t rev,
150                      svn_fs_fs__revision_file_t *rev_file,
151                      apr_off_t offset,
152                      apr_uint64_t *max_node_id,
153                      apr_uint64_t *max_copy_id,
154                      apr_pool_t *pool)
155 {
156   svn_fs_fs__rep_header_t *header;
157   struct recover_read_from_file_baton baton;
158   svn_stream_t *stream;
159   apr_hash_t *entries;
160   apr_hash_index_t *hi;
161   apr_pool_t *iterpool;
162   node_revision_t *noderev;
163   svn_error_t *err;
164
165   baton.stream = rev_file->stream;
166   SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &offset, pool));
167   SVN_ERR(svn_fs_fs__read_noderev(&noderev, baton.stream, pool, pool));
168
169   /* Check that this is a directory.  It should be. */
170   if (noderev->kind != svn_node_dir)
171     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
172                             _("Recovery encountered a non-directory node"));
173
174   /* Get the data location.  No data location indicates an empty directory. */
175   if (!noderev->data_rep)
176     return SVN_NO_ERROR;
177
178   /* If the directory's data representation wasn't changed in this revision,
179      we've already scanned the directory's contents for noderevs, so we don't
180      need to again.  This will occur if a property is changed on a directory
181      without changing the directory's contents. */
182   if (noderev->data_rep->revision != rev)
183     return SVN_NO_ERROR;
184
185   /* We could use get_dir_contents(), but this is much cheaper.  It does
186      rely on directory entries being stored as PLAIN reps, though. */
187   SVN_ERR(svn_fs_fs__item_offset(&offset, fs, rev_file, rev, NULL,
188                                  noderev->data_rep->item_index, pool));
189   SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &offset, pool));
190   SVN_ERR(svn_fs_fs__read_rep_header(&header, baton.stream, pool, pool));
191   if (header->type != svn_fs_fs__rep_plain)
192     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
193                             _("Recovery encountered a deltified directory "
194                               "representation"));
195
196   /* Now create a stream that's allowed to read only as much data as is
197      stored in the representation.  Note that this is a directory, i.e.
198      represented using the hash format on disk and can never have 0 length. */
199   baton.pool = pool;
200   baton.remaining = noderev->data_rep->expanded_size
201                   ? noderev->data_rep->expanded_size
202                   : noderev->data_rep->size;
203   stream = svn_stream_create(&baton, pool);
204   svn_stream_set_read2(stream, NULL /* only full read support */,
205                        read_handler_recover);
206
207   /* Now read the entries from that stream. */
208   entries = apr_hash_make(pool);
209   err = svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool);
210   if (err)
211     {
212       svn_string_t *id_str = svn_fs_fs__id_unparse(noderev->id, pool);
213
214       svn_error_clear(svn_stream_close(stream));
215       return svn_error_quick_wrapf(err,
216                 _("malformed representation for node-revision '%s'"),
217                 id_str->data);
218     }
219   SVN_ERR(svn_stream_close(stream));
220
221   /* Now check each of the entries in our directory to find new node and
222      copy ids, and recurse into new subdirectories. */
223   iterpool = svn_pool_create(pool);
224   for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
225     {
226       char *str_val;
227       char *str;
228       svn_node_kind_t kind;
229       const svn_fs_id_t *id;
230       const svn_fs_fs__id_part_t *rev_item;
231       apr_uint64_t node_id, copy_id;
232       apr_off_t child_dir_offset;
233       const svn_string_t *path = apr_hash_this_val(hi);
234
235       svn_pool_clear(iterpool);
236
237       str_val = apr_pstrdup(iterpool, path->data);
238
239       str = svn_cstring_tokenize(" ", &str_val);
240       if (str == NULL)
241         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
242                                 _("Directory entry corrupt"));
243
244       if (strcmp(str, SVN_FS_FS__KIND_FILE) == 0)
245         kind = svn_node_file;
246       else if (strcmp(str, SVN_FS_FS__KIND_DIR) == 0)
247         kind = svn_node_dir;
248       else
249         {
250           return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
251                                   _("Directory entry corrupt"));
252         }
253
254       str = svn_cstring_tokenize(" ", &str_val);
255       if (str == NULL)
256         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
257                                 _("Directory entry corrupt"));
258
259       SVN_ERR(svn_fs_fs__id_parse(&id, str, iterpool));
260
261       rev_item = svn_fs_fs__id_rev_item(id);
262       if (rev_item->revision != rev)
263         {
264           /* If the node wasn't modified in this revision, we've already
265              checked the node and copy id. */
266           continue;
267         }
268
269       node_id = svn_fs_fs__id_node_id(id)->number;
270       copy_id = svn_fs_fs__id_copy_id(id)->number;
271
272       if (node_id > *max_node_id)
273         *max_node_id = node_id;
274       if (copy_id > *max_copy_id)
275         *max_copy_id = copy_id;
276
277       if (kind == svn_node_file)
278         continue;
279
280       SVN_ERR(svn_fs_fs__item_offset(&child_dir_offset, fs,
281                                      rev_file, rev, NULL, rev_item->number,
282                                      iterpool));
283       SVN_ERR(recover_find_max_ids(fs, rev, rev_file, child_dir_offset,
284                                    max_node_id, max_copy_id, iterpool));
285     }
286   svn_pool_destroy(iterpool);
287
288   return SVN_NO_ERROR;
289 }
290
291 /* Part of the recovery procedure.  Given an open non-packed revision file
292    REV_FILE for REV, locate the trailer that specifies the offset to the root
293    node-id and store this offset in *ROOT_OFFSET.  Do temporary allocations in
294    POOL. */
295 static svn_error_t *
296 recover_get_root_offset(apr_off_t *root_offset,
297                         svn_revnum_t rev,
298                         svn_fs_fs__revision_file_t *rev_file,
299                         apr_pool_t *pool)
300 {
301   char buffer[64];
302   svn_stringbuf_t *trailer;
303   apr_off_t start;
304   apr_off_t end;
305   apr_size_t len;
306
307   SVN_ERR_ASSERT(!rev_file->is_packed);
308
309   /* We will assume that the last line containing the two offsets (to the root
310      node-id and to the changed path information) will never be longer than 64
311      characters. */
312   end = 0;
313   SVN_ERR(svn_io_file_seek(rev_file->file, APR_END, &end, pool));
314
315   if (end < sizeof(buffer))
316     {
317       len = (apr_size_t)end;
318       start = 0;
319     }
320   else
321     {
322       len = sizeof(buffer);
323       start = end - sizeof(buffer);
324     }
325
326   SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &start, pool));
327   SVN_ERR(svn_io_file_read_full2(rev_file->file, buffer, len,
328                                  NULL, NULL, pool));
329
330   trailer = svn_stringbuf_ncreate(buffer, len, pool);
331   SVN_ERR(svn_fs_fs__parse_revision_trailer(root_offset, NULL, trailer, rev));
332
333   return SVN_NO_ERROR;
334 }
335
336 /* Baton used for recover_body below. */
337 struct recover_baton {
338   svn_fs_t *fs;
339   svn_cancel_func_t cancel_func;
340   void *cancel_baton;
341 };
342
343 /* The work-horse for svn_fs_fs__recover, called with the FS
344    write lock.  This implements the svn_fs_fs__with_write_lock()
345    'body' callback type.  BATON is a 'struct recover_baton *'. */
346 static svn_error_t *
347 recover_body(void *baton, apr_pool_t *pool)
348 {
349   struct recover_baton *b = baton;
350   svn_fs_t *fs = b->fs;
351   fs_fs_data_t *ffd = fs->fsap_data;
352   svn_revnum_t max_rev;
353   apr_uint64_t next_node_id = 0;
354   apr_uint64_t next_copy_id = 0;
355   svn_revnum_t youngest_rev;
356   svn_node_kind_t youngest_revprops_kind;
357
358   /* The admin may have created a plain copy of this repo before attempting
359      to recover it (hotcopy may or may not work with corrupted repos).
360      Bump the instance ID. */
361   SVN_ERR(svn_fs_fs__set_uuid(fs, fs->uuid, NULL, pool));
362
363   /* We need to know the largest revision in the filesystem. */
364   SVN_ERR(recover_get_largest_revision(fs, &max_rev, pool));
365
366   /* Get the expected youngest revision */
367   SVN_ERR(svn_fs_fs__youngest_rev(&youngest_rev, fs, pool));
368
369   /* Policy note:
370
371      Since the revprops file is written after the revs file, the true
372      maximum available revision is the youngest one for which both are
373      present.  That's probably the same as the max_rev we just found,
374      but if it's not, we could, in theory, repeatedly decrement
375      max_rev until we find a revision that has both a revs and
376      revprops file, then write db/current with that.
377
378      But we choose not to.  If a repository is so corrupt that it's
379      missing at least one revprops file, we shouldn't assume that the
380      youngest revision for which both the revs and revprops files are
381      present is healthy.  In other words, we're willing to recover
382      from a missing or out-of-date db/current file, because db/current
383      is truly redundant -- it's basically a cache so we don't have to
384      find max_rev each time, albeit a cache with unusual semantics,
385      since it also officially defines when a revision goes live.  But
386      if we're missing more than the cache, it's time to back out and
387      let the admin reconstruct things by hand: correctness at that
388      point may depend on external things like checking a commit email
389      list, looking in particular working copies, etc.
390
391      This policy matches well with a typical naive backup scenario.
392      Say you're rsyncing your FSFS repository nightly to the same
393      location.  Once revs and revprops are written, you've got the
394      maximum rev; if the backup should bomb before db/current is
395      written, then db/current could stay arbitrarily out-of-date, but
396      we can still recover.  It's a small window, but we might as well
397      do what we can. */
398
399   /* Even if db/current were missing, it would be created with 0 by
400      get_youngest(), so this conditional remains valid. */
401   if (youngest_rev > max_rev)
402     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
403                              _("Expected current rev to be <= %ld "
404                                "but found %ld"), max_rev, youngest_rev);
405
406   /* We only need to search for maximum IDs for old FS formats which
407      se global ID counters. */
408   if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
409     {
410       /* Next we need to find the maximum node id and copy id in use across the
411          filesystem.  Unfortunately, the only way we can get this information
412          is to scan all the noderevs of all the revisions and keep track as
413          we go along. */
414       svn_revnum_t rev;
415       apr_pool_t *iterpool = svn_pool_create(pool);
416
417       for (rev = 0; rev <= max_rev; rev++)
418         {
419           svn_fs_fs__revision_file_t *rev_file;
420           apr_off_t root_offset;
421
422           svn_pool_clear(iterpool);
423
424           if (b->cancel_func)
425             SVN_ERR(b->cancel_func(b->cancel_baton));
426
427           SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, rev, pool,
428                                                    iterpool));
429           SVN_ERR(recover_get_root_offset(&root_offset, rev, rev_file, pool));
430           SVN_ERR(recover_find_max_ids(fs, rev, rev_file, root_offset,
431                                        &next_node_id, &next_copy_id, pool));
432           SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
433         }
434       svn_pool_destroy(iterpool);
435
436       /* Now that we finally have the maximum revision, node-id and copy-id, we
437          can bump the two ids to get the next of each. */
438       next_node_id++;
439       next_copy_id++;
440     }
441
442   /* Before setting current, verify that there is a revprops file
443      for the youngest revision.  (Issue #2992) */
444   SVN_ERR(svn_io_check_path(svn_fs_fs__path_revprops(fs, max_rev, pool),
445                             &youngest_revprops_kind, pool));
446   if (youngest_revprops_kind == svn_node_none)
447     {
448       svn_boolean_t missing = TRUE;
449       if (!svn_fs_fs__packed_revprop_available(&missing, fs, max_rev, pool))
450         {
451           if (missing)
452             {
453               return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
454                                       _("Revision %ld has a revs file but no "
455                                         "revprops file"),
456                                       max_rev);
457             }
458           else
459             {
460               return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
461                                       _("Revision %ld has a revs file but the "
462                                         "revprops file is inaccessible"),
463                                       max_rev);
464             }
465           }
466     }
467   else if (youngest_revprops_kind != svn_node_file)
468     {
469       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
470                                _("Revision %ld has a non-file where its "
471                                  "revprops file should be"),
472                                max_rev);
473     }
474
475   /* Prune younger-than-(newfound-youngest) revisions from the rep
476      cache if sharing is enabled taking care not to create the cache
477      if it does not exist. */
478   if (ffd->rep_sharing_allowed)
479     {
480       svn_boolean_t rep_cache_exists;
481
482       SVN_ERR(svn_fs_fs__exists_rep_cache(&rep_cache_exists, fs, pool));
483       if (rep_cache_exists)
484         SVN_ERR(svn_fs_fs__del_rep_reference(fs, max_rev, pool));
485     }
486
487   /* Now store the discovered youngest revision, and the next IDs if
488      relevant, in a new 'current' file. */
489   return svn_fs_fs__write_current(fs, max_rev, next_node_id, next_copy_id,
490                                   pool);
491 }
492
493 /* This implements the fs_library_vtable_t.recover() API. */
494 svn_error_t *
495 svn_fs_fs__recover(svn_fs_t *fs,
496                    svn_cancel_func_t cancel_func, void *cancel_baton,
497                    apr_pool_t *pool)
498 {
499   struct recover_baton b;
500
501   /* We have no way to take out an exclusive lock in FSFS, so we're
502      restricted as to the types of recovery we can do.  Luckily,
503      we just want to recreate the 'current' file, and we can do that just
504      by blocking other writers. */
505   b.fs = fs;
506   b.cancel_func = cancel_func;
507   b.cancel_baton = cancel_baton;
508   return svn_fs_fs__with_all_locks(fs, recover_body, &b, pool);
509 }