1 /* fs_fs.c --- filesystem operations specific to fs_fs
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 "fs_fs.h"
24 
25 #include <apr_uuid.h>
26 
27 #include "svn_private_config.h"
28 
29 #include "svn_checksum.h"
30 #include "svn_hash.h"
31 #include "svn_props.h"
32 #include "svn_time.h"
33 #include "svn_dirent_uri.h"
34 #include "svn_sorts.h"
35 #include "svn_version.h"
36 
37 #include "cached_data.h"
38 #include "id.h"
39 #include "index.h"
40 #include "low_level.h"
41 #include "rep-cache.h"
42 #include "revprops.h"
43 #include "transaction.h"
44 #include "tree.h"
45 #include "util.h"
46 
47 #include "private/svn_fs_util.h"
48 #include "private/svn_io_private.h"
49 #include "private/svn_string_private.h"
50 #include "private/svn_subr_private.h"
51 #include "../libsvn_fs/fs-loader.h"
52 
53 /* The default maximum number of files per directory to store in the
54    rev and revprops directory.  The number below is somewhat arbitrary,
55    and can be overridden by defining the macro while compiling; the
56    figure of 1000 is reasonable for VFAT filesystems, which are by far
57    the worst performers in this area. */
58 #ifndef SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR
59 #define SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR 1000
60 #endif
61 
62 /* Begin deltification after a node history exceeded this this limit.
63    Useful values are 4 to 64 with 16 being a good compromise between
64    computational overhead and repository size savings.
65    Should be a power of 2.
66    Values < 2 will result in standard skip-delta behavior. */
67 #define SVN_FS_FS_MAX_LINEAR_DELTIFICATION 16
68 
69 /* Finding a deltification base takes operations proportional to the
70    number of changes being skipped. To prevent exploding runtime
71    during commits, limit the deltification range to this value.
72    Should be a power of 2 minus one.
73    Values < 1 disable deltification. */
74 #define SVN_FS_FS_MAX_DELTIFICATION_WALK 1023
75 
76 /* Notes:
77 
78 To avoid opening and closing the rev-files all the time, it would
79 probably be advantageous to keep each rev-file open for the
80 lifetime of the transaction object.  I'll leave that as a later
81 optimization for now.
82 
83 I didn't keep track of pool lifetimes at all in this code.  There
84 are likely some errors because of that.
85 
86 */
87 
88 /* Declarations. */
89 
90 static svn_error_t *
91 get_youngest(svn_revnum_t *youngest_p, svn_fs_t *fs, apr_pool_t *pool);
92 
93 /* Pathname helper functions */
94 
95 static const char *
path_format(svn_fs_t * fs,apr_pool_t * pool)96 path_format(svn_fs_t *fs, apr_pool_t *pool)
97 {
98   return svn_dirent_join(fs->path, PATH_FORMAT, pool);
99 }
100 
101 static APR_INLINE const char *
path_uuid(svn_fs_t * fs,apr_pool_t * pool)102 path_uuid(svn_fs_t *fs, apr_pool_t *pool)
103 {
104   return svn_dirent_join(fs->path, PATH_UUID, pool);
105 }
106 
107 const char *
svn_fs_fs__path_current(svn_fs_t * fs,apr_pool_t * pool)108 svn_fs_fs__path_current(svn_fs_t *fs, apr_pool_t *pool)
109 {
110   return svn_dirent_join(fs->path, PATH_CURRENT, pool);
111 }
112 
113 
114 
115 /* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
116 static svn_error_t *
get_lock_on_filesystem(const char * lock_filename,apr_pool_t * pool)117 get_lock_on_filesystem(const char *lock_filename,
118                        apr_pool_t *pool)
119 {
120   return svn_error_trace(svn_io__file_lock_autocreate(lock_filename, pool));
121 }
122 
123 /* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID.
124    When registered with the pool holding the lock on the lock file,
125    this makes sure the flag gets reset just before we release the lock. */
126 static apr_status_t
reset_lock_flag(void * baton_void)127 reset_lock_flag(void *baton_void)
128 {
129   fs_fs_data_t *ffd = baton_void;
130   ffd->has_write_lock = FALSE;
131   return APR_SUCCESS;
132 }
133 
134 /* Structure defining a file system lock to be acquired and the function
135    to be executed while the lock is held.
136 
137    Instances of this structure may be nested to allow for multiple locks to
138    be taken out before executing the user-provided body.  In that case, BODY
139    and BATON of the outer instances will be with_lock and a with_lock_baton_t
140    instance (transparently, no special treatment is required.).  It is
141    illegal to attempt to acquire the same lock twice within the same lock
142    chain or via nesting calls using separate lock chains.
143 
144    All instances along the chain share the same LOCK_POOL such that only one
145    pool needs to be created and cleared for all locks.  We also allocate as
146    much data from that lock pool as possible to minimize memory usage in
147    caller pools. */
148 typedef struct with_lock_baton_t
149 {
150   /* The filesystem we operate on.  Same for all instances along the chain. */
151   svn_fs_t *fs;
152 
153   /* Mutex to complement the lock file in an APR threaded process.
154      No-op object for non-threaded processes but never NULL. */
155   svn_mutex__t *mutex;
156 
157   /* Path to the file to lock. */
158   const char *lock_path;
159 
160   /* If true, set FS->HAS_WRITE_LOCK after we acquired the lock. */
161   svn_boolean_t is_global_lock;
162 
163   /* Function body to execute after we acquired the lock.
164      This may be user-provided or a nested call to with_lock(). */
165   svn_error_t *(*body)(void *baton,
166                        apr_pool_t *pool);
167 
168   /* Baton to pass to BODY; possibly NULL.
169      This may be user-provided or a nested lock baton instance. */
170   void *baton;
171 
172   /* Pool for all allocations along the lock chain and BODY.  Will hold the
173      file locks and gets destroyed after the outermost BODY returned,
174      releasing all file locks.
175      Same for all instances along the chain. */
176   apr_pool_t *lock_pool;
177 
178   /* TRUE, iff BODY is the user-provided body. */
179   svn_boolean_t is_inner_most_lock;
180 
181   /* TRUE, iff this is not a nested lock.
182      Then responsible for destroying LOCK_POOL. */
183   svn_boolean_t is_outer_most_lock;
184 } with_lock_baton_t;
185 
186 /* Obtain a write lock on the file BATON->LOCK_PATH and call BATON->BODY
187    with BATON->BATON.  If this is the outermost lock call, release all file
188    locks after the body returned.  If BATON->IS_GLOBAL_LOCK is set, set the
189    HAS_WRITE_LOCK flag while we keep the write lock. */
190 static svn_error_t *
with_some_lock_file(with_lock_baton_t * baton)191 with_some_lock_file(with_lock_baton_t *baton)
192 {
193   apr_pool_t *pool = baton->lock_pool;
194   svn_error_t *err = get_lock_on_filesystem(baton->lock_path, pool);
195 
196   if (!err)
197     {
198       svn_fs_t *fs = baton->fs;
199       fs_fs_data_t *ffd = fs->fsap_data;
200 
201       if (baton->is_global_lock)
202         {
203           /* set the "got the lock" flag and register reset function */
204           apr_pool_cleanup_register(pool,
205                                     ffd,
206                                     reset_lock_flag,
207                                     apr_pool_cleanup_null);
208           ffd->has_write_lock = TRUE;
209         }
210 
211       /* nobody else will modify the repo state
212          => read HEAD & pack info once */
213       if (baton->is_inner_most_lock)
214         {
215           if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
216             err = svn_fs_fs__update_min_unpacked_rev(fs, pool);
217           if (!err)
218             err = get_youngest(&ffd->youngest_rev_cache, fs, pool);
219         }
220 
221       if (!err)
222         err = baton->body(baton->baton, pool);
223     }
224 
225   if (baton->is_outer_most_lock)
226     svn_pool_destroy(pool);
227 
228   return svn_error_trace(err);
229 }
230 
231 /* Wraps with_some_lock_file, protecting it with BATON->MUTEX.
232 
233    POOL is unused here and only provided for signature compatibility with
234    WITH_LOCK_BATON_T.BODY. */
235 static svn_error_t *
with_lock(void * baton,apr_pool_t * pool)236 with_lock(void *baton,
237           apr_pool_t *pool)
238 {
239   with_lock_baton_t *lock_baton = baton;
240   SVN_MUTEX__WITH_LOCK(lock_baton->mutex, with_some_lock_file(lock_baton));
241 
242   return SVN_NO_ERROR;
243 }
244 
245 /* Enum identifying a filesystem lock. */
246 typedef enum lock_id_t
247 {
248   write_lock,
249   txn_lock,
250   pack_lock
251 } lock_id_t;
252 
253 /* Initialize BATON->MUTEX, BATON->LOCK_PATH and BATON->IS_GLOBAL_LOCK
254    according to the LOCK_ID.  All other members of BATON must already be
255    valid. */
256 static void
init_lock_baton(with_lock_baton_t * baton,lock_id_t lock_id)257 init_lock_baton(with_lock_baton_t *baton,
258                 lock_id_t lock_id)
259 {
260   fs_fs_data_t *ffd = baton->fs->fsap_data;
261   fs_fs_shared_data_t *ffsd = ffd->shared;
262 
263   switch (lock_id)
264     {
265     case write_lock:
266       baton->mutex = ffsd->fs_write_lock;
267       baton->lock_path = svn_fs_fs__path_lock(baton->fs, baton->lock_pool);
268       baton->is_global_lock = TRUE;
269       break;
270 
271     case txn_lock:
272       baton->mutex = ffsd->txn_current_lock;
273       baton->lock_path = svn_fs_fs__path_txn_current_lock(baton->fs,
274                                                           baton->lock_pool);
275       baton->is_global_lock = FALSE;
276       break;
277 
278     case pack_lock:
279       baton->mutex = ffsd->fs_pack_lock;
280       baton->lock_path = svn_fs_fs__path_pack_lock(baton->fs,
281                                                    baton->lock_pool);
282       baton->is_global_lock = FALSE;
283       break;
284     }
285 }
286 
287 /* Return the  baton for the innermost lock of a (potential) lock chain.
288    The baton shall take out LOCK_ID from FS and execute BODY with BATON
289    while the lock is being held.  Allocate the result in a sub-pool of POOL.
290  */
291 static with_lock_baton_t *
create_lock_baton(svn_fs_t * fs,lock_id_t lock_id,svn_error_t * (* body)(void * baton,apr_pool_t * pool),void * baton,apr_pool_t * pool)292 create_lock_baton(svn_fs_t *fs,
293                   lock_id_t lock_id,
294                   svn_error_t *(*body)(void *baton,
295                                        apr_pool_t *pool),
296                   void *baton,
297                   apr_pool_t *pool)
298 {
299   /* Allocate everything along the lock chain into a single sub-pool.
300      This minimizes memory usage and cleanup overhead. */
301   apr_pool_t *lock_pool = svn_pool_create(pool);
302   with_lock_baton_t *result = apr_pcalloc(lock_pool, sizeof(*result));
303 
304   /* Store parameters. */
305   result->fs = fs;
306   result->body = body;
307   result->baton = baton;
308 
309   /* File locks etc. will use this pool as well for easy cleanup. */
310   result->lock_pool = lock_pool;
311 
312   /* Right now, we are the first, (only, ) and last struct in the chain. */
313   result->is_inner_most_lock = TRUE;
314   result->is_outer_most_lock = TRUE;
315 
316   /* Select mutex and lock file path depending on LOCK_ID.
317      Also, initialize dependent members (IS_GLOBAL_LOCK only, ATM). */
318   init_lock_baton(result, lock_id);
319 
320   return result;
321 }
322 
323 /* Return a baton that wraps NESTED and requests LOCK_ID as additional lock.
324  *
325  * That means, when you create a lock chain, start with the last / innermost
326  * lock to take out and add the first / outermost lock last.
327  */
328 static with_lock_baton_t *
chain_lock_baton(lock_id_t lock_id,with_lock_baton_t * nested)329 chain_lock_baton(lock_id_t lock_id,
330                  with_lock_baton_t *nested)
331 {
332   /* Use the same pool for batons along the lock chain. */
333   apr_pool_t *lock_pool = nested->lock_pool;
334   with_lock_baton_t *result = apr_pcalloc(lock_pool, sizeof(*result));
335 
336   /* All locks along the chain operate on the same FS. */
337   result->fs = nested->fs;
338 
339   /* Execution of this baton means acquiring the nested lock and its
340      execution. */
341   result->body = with_lock;
342   result->baton = nested;
343 
344   /* Shared among all locks along the chain. */
345   result->lock_pool = lock_pool;
346 
347   /* We are the new outermost lock but surely not the innermost lock. */
348   result->is_inner_most_lock = FALSE;
349   result->is_outer_most_lock = TRUE;
350   nested->is_outer_most_lock = FALSE;
351 
352   /* Select mutex and lock file path depending on LOCK_ID.
353      Also, initialize dependent members (IS_GLOBAL_LOCK only, ATM). */
354   init_lock_baton(result, lock_id);
355 
356   return result;
357 }
358 
359 svn_error_t *
svn_fs_fs__with_write_lock(svn_fs_t * fs,svn_error_t * (* body)(void * baton,apr_pool_t * pool),void * baton,apr_pool_t * pool)360 svn_fs_fs__with_write_lock(svn_fs_t *fs,
361                            svn_error_t *(*body)(void *baton,
362                                                 apr_pool_t *pool),
363                            void *baton,
364                            apr_pool_t *pool)
365 {
366   return svn_error_trace(
367            with_lock(create_lock_baton(fs, write_lock, body, baton, pool),
368                      pool));
369 }
370 
371 svn_error_t *
svn_fs_fs__with_pack_lock(svn_fs_t * fs,svn_error_t * (* body)(void * baton,apr_pool_t * pool),void * baton,apr_pool_t * pool)372 svn_fs_fs__with_pack_lock(svn_fs_t *fs,
373                           svn_error_t *(*body)(void *baton,
374                                                apr_pool_t *pool),
375                           void *baton,
376                           apr_pool_t *pool)
377 {
378   return svn_error_trace(
379            with_lock(create_lock_baton(fs, pack_lock, body, baton, pool),
380                      pool));
381 }
382 
383 svn_error_t *
svn_fs_fs__with_txn_current_lock(svn_fs_t * fs,svn_error_t * (* body)(void * baton,apr_pool_t * pool),void * baton,apr_pool_t * pool)384 svn_fs_fs__with_txn_current_lock(svn_fs_t *fs,
385                                  svn_error_t *(*body)(void *baton,
386                                                       apr_pool_t *pool),
387                                  void *baton,
388                                  apr_pool_t *pool)
389 {
390   return svn_error_trace(
391            with_lock(create_lock_baton(fs, txn_lock, body, baton, pool),
392                      pool));
393 }
394 
395 svn_error_t *
svn_fs_fs__with_all_locks(svn_fs_t * fs,svn_error_t * (* body)(void * baton,apr_pool_t * pool),void * baton,apr_pool_t * pool)396 svn_fs_fs__with_all_locks(svn_fs_t *fs,
397                           svn_error_t *(*body)(void *baton,
398                                                apr_pool_t *pool),
399                           void *baton,
400                           apr_pool_t *pool)
401 {
402   fs_fs_data_t *ffd = fs->fsap_data;
403 
404   /* Be sure to use the correct lock ordering as documented in
405      fs_fs_shared_data_t.  The lock chain is being created in
406      innermost (last to acquire) -> outermost (first to acquire) order. */
407   with_lock_baton_t *lock_baton
408     = create_lock_baton(fs, write_lock, body, baton, pool);
409 
410   if (ffd->format >= SVN_FS_FS__MIN_PACK_LOCK_FORMAT)
411     lock_baton = chain_lock_baton(pack_lock, lock_baton);
412 
413   if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
414     lock_baton = chain_lock_baton(txn_lock, lock_baton);
415 
416   return svn_error_trace(with_lock(lock_baton, pool));
417 }
418 
419 
420 
421 
422 
423 /* Check that BUF, a nul-terminated buffer of text from format file PATH,
424    contains only digits at OFFSET and beyond, raising an error if not.
425 
426    Uses POOL for temporary allocation. */
427 static svn_error_t *
check_format_file_buffer_numeric(const char * buf,apr_off_t offset,const char * path,apr_pool_t * pool)428 check_format_file_buffer_numeric(const char *buf, apr_off_t offset,
429                                  const char *path, apr_pool_t *pool)
430 {
431   return svn_fs_fs__check_file_buffer_numeric(buf, offset, path, "Format",
432                                               pool);
433 }
434 
435 /* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
436    number is not the same as a format number supported by this
437    Subversion. */
438 static svn_error_t *
check_format(int format)439 check_format(int format)
440 {
441   /* Blacklist.  These formats may be either younger or older than
442      SVN_FS_FS__FORMAT_NUMBER, but we don't support them. */
443   if (format == SVN_FS_FS__PACKED_REVPROP_SQLITE_DEV_FORMAT)
444     return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
445                              _("Found format '%d', only created by "
446                                "unreleased dev builds; see "
447                                "http://subversion.apache.org"
448                                "/docs/release-notes/1.7#revprop-packing"),
449                              format);
450 
451   /* We support all formats from 1-current simultaneously */
452   if (1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER)
453     return SVN_NO_ERROR;
454 
455   return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
456      _("Expected FS format between '1' and '%d'; found format '%d'"),
457      SVN_FS_FS__FORMAT_NUMBER, format);
458 }
459 
460 /* Read the format number and maximum number of files per directory
461    from PATH and return them in *PFORMAT, *MAX_FILES_PER_DIR and
462    USE_LOG_ADDRESSIONG respectively.
463 
464    *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and
465    will be set to zero if a linear scheme should be used.
466    *USE_LOG_ADDRESSIONG is obtained from the 'addressing' format option,
467    and will be set to FALSE for physical addressing.
468 
469    Use POOL for temporary allocation. */
470 static svn_error_t *
read_format(int * pformat,int * max_files_per_dir,svn_boolean_t * use_log_addressing,const char * path,apr_pool_t * pool)471 read_format(int *pformat,
472             int *max_files_per_dir,
473             svn_boolean_t *use_log_addressing,
474             const char *path,
475             apr_pool_t *pool)
476 {
477   svn_error_t *err;
478   svn_stream_t *stream;
479   svn_stringbuf_t *content;
480   svn_stringbuf_t *buf;
481   svn_boolean_t eos = FALSE;
482 
483   err = svn_stringbuf_from_file2(&content, path, pool);
484   if (err && APR_STATUS_IS_ENOENT(err->apr_err))
485     {
486       /* Treat an absent format file as format 1.  Do not try to
487          create the format file on the fly, because the repository
488          might be read-only for us, or this might be a read-only
489          operation, and the spirit of FSFS is to make no changes
490          whatseover in read-only operations.  See thread starting at
491          http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=97600
492          for more. */
493       svn_error_clear(err);
494       *pformat = 1;
495       *max_files_per_dir = 0;
496       *use_log_addressing = FALSE;
497 
498       return SVN_NO_ERROR;
499     }
500   SVN_ERR(err);
501 
502   stream = svn_stream_from_stringbuf(content, pool);
503   SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool));
504   if (buf->len == 0 && eos)
505     {
506       /* Return a more useful error message. */
507       return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
508                                _("Can't read first line of format file '%s'"),
509                                svn_dirent_local_style(path, pool));
510     }
511 
512   /* Check that the first line contains only digits. */
513   SVN_ERR(check_format_file_buffer_numeric(buf->data, 0, path, pool));
514   SVN_ERR(svn_cstring_atoi(pformat, buf->data));
515 
516   /* Check that we support this format at all */
517   SVN_ERR(check_format(*pformat));
518 
519   /* Set the default values for anything that can be set via an option. */
520   *max_files_per_dir = 0;
521   *use_log_addressing = FALSE;
522 
523   /* Read any options. */
524   while (!eos)
525     {
526       SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool));
527       if (buf->len == 0)
528         break;
529 
530       if (*pformat >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT &&
531           strncmp(buf->data, "layout ", 7) == 0)
532         {
533           if (strcmp(buf->data + 7, "linear") == 0)
534             {
535               *max_files_per_dir = 0;
536               continue;
537             }
538 
539           if (strncmp(buf->data + 7, "sharded ", 8) == 0)
540             {
541               /* Check that the argument is numeric. */
542               SVN_ERR(check_format_file_buffer_numeric(buf->data, 15, path, pool));
543               SVN_ERR(svn_cstring_atoi(max_files_per_dir, buf->data + 15));
544               continue;
545             }
546         }
547 
548       if (*pformat >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT &&
549           strncmp(buf->data, "addressing ", 11) == 0)
550         {
551           if (strcmp(buf->data + 11, "physical") == 0)
552             {
553               *use_log_addressing = FALSE;
554               continue;
555             }
556 
557           if (strcmp(buf->data + 11, "logical") == 0)
558             {
559               *use_log_addressing = TRUE;
560               continue;
561             }
562         }
563 
564       return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
565          _("'%s' contains invalid filesystem format option '%s'"),
566          svn_dirent_local_style(path, pool), buf->data);
567     }
568 
569   /* Non-sharded repositories never use logical addressing.
570    * If the format file is inconsistent in that respect, something
571    * probably went wrong.
572    */
573   if (*use_log_addressing && !*max_files_per_dir)
574     return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
575        _("'%s' specifies logical addressing for a non-sharded repository"),
576        svn_dirent_local_style(path, pool));
577 
578   return SVN_NO_ERROR;
579 }
580 
581 /* Write the format number, maximum number of files per directory and
582    the addressing scheme to a new format file in PATH, possibly expecting
583    to overwrite a previously existing file.
584 
585    Use POOL for temporary allocation. */
586 svn_error_t *
svn_fs_fs__write_format(svn_fs_t * fs,svn_boolean_t overwrite,apr_pool_t * pool)587 svn_fs_fs__write_format(svn_fs_t *fs,
588                         svn_boolean_t overwrite,
589                         apr_pool_t *pool)
590 {
591   svn_stringbuf_t *sb;
592   fs_fs_data_t *ffd = fs->fsap_data;
593   const char *path = path_format(fs, pool);
594 
595   SVN_ERR_ASSERT(1 <= ffd->format
596                  && ffd->format <= SVN_FS_FS__FORMAT_NUMBER);
597 
598   sb = svn_stringbuf_createf(pool, "%d\n", ffd->format);
599 
600   if (ffd->format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
601     {
602       if (ffd->max_files_per_dir)
603         svn_stringbuf_appendcstr(sb, apr_psprintf(pool, "layout sharded %d\n",
604                                                   ffd->max_files_per_dir));
605       else
606         svn_stringbuf_appendcstr(sb, "layout linear\n");
607     }
608 
609   if (ffd->format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT)
610     {
611       if (ffd->use_log_addressing)
612         svn_stringbuf_appendcstr(sb, "addressing logical\n");
613       else
614         svn_stringbuf_appendcstr(sb, "addressing physical\n");
615     }
616 
617   /* svn_io_write_version_file() does a load of magic to allow it to
618      replace version files that already exist.  We only need to do
619      that when we're allowed to overwrite an existing file. */
620   if (! overwrite)
621     {
622       /* Create the file */
623       SVN_ERR(svn_io_file_create(path, sb->data, pool));
624     }
625   else
626     {
627       SVN_ERR(svn_io_write_atomic2(path, sb->data, sb->len,
628                                    NULL /* copy_perms_path */,
629                                    ffd->flush_to_disk, pool));
630     }
631 
632   /* And set the perms to make it read only */
633   return svn_io_set_file_read_only(path, FALSE, pool);
634 }
635 
636 svn_boolean_t
svn_fs_fs__fs_supports_mergeinfo(svn_fs_t * fs)637 svn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs)
638 {
639   fs_fs_data_t *ffd = fs->fsap_data;
640   return ffd->format >= SVN_FS_FS__MIN_MERGEINFO_FORMAT;
641 }
642 
643 /* Check that BLOCK_SIZE is a valid block / page size, i.e. it is within
644  * the range of what the current system may address in RAM and it is a
645  * power of 2.  Assume that the element size within the block is ITEM_SIZE.
646  * Use SCRATCH_POOL for temporary allocations.
647  */
648 static svn_error_t *
verify_block_size(apr_int64_t block_size,apr_size_t item_size,const char * name,apr_pool_t * scratch_pool)649 verify_block_size(apr_int64_t block_size,
650                   apr_size_t item_size,
651                   const char *name,
652                   apr_pool_t *scratch_pool
653                  )
654 {
655   /* Limit range. */
656   if (block_size <= 0)
657     return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
658                              _("%s is too small for fsfs.conf setting '%s'."),
659                              apr_psprintf(scratch_pool,
660                                           "%" APR_INT64_T_FMT,
661                                           block_size),
662                              name);
663 
664   if (block_size > SVN_MAX_OBJECT_SIZE / item_size)
665     return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
666                              _("%s is too large for fsfs.conf setting '%s'."),
667                              apr_psprintf(scratch_pool,
668                                           "%" APR_INT64_T_FMT,
669                                           block_size),
670                              name);
671 
672   /* Ensure it is a power of two.
673    * For positive X,  X & (X-1) will reset the lowest bit set.
674    * If the result is 0, at most one bit has been set. */
675   if (0 != (block_size & (block_size - 1)))
676     return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
677                              _("%s is invalid for fsfs.conf setting '%s' "
678                                "because it is not a power of 2."),
679                              apr_psprintf(scratch_pool,
680                                           "%" APR_INT64_T_FMT,
681                                           block_size),
682                              name);
683 
684   return SVN_NO_ERROR;
685 }
686 
687 static svn_error_t *
parse_compression_option(compression_type_t * compression_type_p,int * compression_level_p,const char * value)688 parse_compression_option(compression_type_t *compression_type_p,
689                          int *compression_level_p,
690                          const char *value)
691 {
692   compression_type_t type;
693   int level;
694   svn_boolean_t is_valid = TRUE;
695 
696   /* compression = none | lz4 | zlib | zlib-1 ... zlib-9 */
697   if (strcmp(value, "none") == 0)
698     {
699       type = compression_type_none;
700       level = SVN_DELTA_COMPRESSION_LEVEL_NONE;
701     }
702   else if (strcmp(value, "lz4") == 0)
703     {
704       type = compression_type_lz4;
705       level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
706     }
707   else if (strncmp(value, "zlib", 4) == 0)
708     {
709       const char *p = value + 4;
710 
711       type = compression_type_zlib;
712       if (*p == 0)
713         {
714           level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
715         }
716       else if (*p == '-')
717         {
718           p++;
719           SVN_ERR(svn_cstring_atoi(&level, p));
720           if (level < 1 || level > 9)
721             is_valid = FALSE;
722         }
723       else
724         is_valid = FALSE;
725     }
726   else
727     {
728       is_valid = FALSE;
729     }
730 
731   if (!is_valid)
732     return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
733                            _("Invalid 'compression' value '%s' in the config"),
734                              value);
735 
736   *compression_type_p = type;
737   *compression_level_p = level;
738   return SVN_NO_ERROR;
739 }
740 
741 /* Read the configuration information of the file system at FS_PATH
742  * and set the respective values in FFD.  Use pools as usual.
743  */
744 static svn_error_t *
read_config(fs_fs_data_t * ffd,const char * fs_path,apr_pool_t * result_pool,apr_pool_t * scratch_pool)745 read_config(fs_fs_data_t *ffd,
746             const char *fs_path,
747             apr_pool_t *result_pool,
748             apr_pool_t *scratch_pool)
749 {
750   svn_config_t *config;
751 
752   SVN_ERR(svn_config_read3(&config,
753                            svn_dirent_join(fs_path, PATH_CONFIG, scratch_pool),
754                            FALSE, FALSE, FALSE, scratch_pool));
755 
756   /* Initialize ffd->rep_sharing_allowed. */
757   if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
758     SVN_ERR(svn_config_get_bool(config, &ffd->rep_sharing_allowed,
759                                 CONFIG_SECTION_REP_SHARING,
760                                 CONFIG_OPTION_ENABLE_REP_SHARING, TRUE));
761   else
762     ffd->rep_sharing_allowed = FALSE;
763 
764   /* Initialize deltification settings in ffd. */
765   if (ffd->format >= SVN_FS_FS__MIN_DELTIFICATION_FORMAT)
766     {
767       SVN_ERR(svn_config_get_bool(config, &ffd->deltify_directories,
768                                   CONFIG_SECTION_DELTIFICATION,
769                                   CONFIG_OPTION_ENABLE_DIR_DELTIFICATION,
770                                   TRUE));
771       SVN_ERR(svn_config_get_bool(config, &ffd->deltify_properties,
772                                   CONFIG_SECTION_DELTIFICATION,
773                                   CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION,
774                                   TRUE));
775       SVN_ERR(svn_config_get_int64(config, &ffd->max_deltification_walk,
776                                    CONFIG_SECTION_DELTIFICATION,
777                                    CONFIG_OPTION_MAX_DELTIFICATION_WALK,
778                                    SVN_FS_FS_MAX_DELTIFICATION_WALK));
779       SVN_ERR(svn_config_get_int64(config, &ffd->max_linear_deltification,
780                                    CONFIG_SECTION_DELTIFICATION,
781                                    CONFIG_OPTION_MAX_LINEAR_DELTIFICATION,
782                                    SVN_FS_FS_MAX_LINEAR_DELTIFICATION));
783     }
784   else
785     {
786       ffd->deltify_directories = FALSE;
787       ffd->deltify_properties = FALSE;
788       ffd->max_deltification_walk = SVN_FS_FS_MAX_DELTIFICATION_WALK;
789       ffd->max_linear_deltification = SVN_FS_FS_MAX_LINEAR_DELTIFICATION;
790     }
791 
792   /* Initialize revprop packing settings in ffd. */
793   if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
794     {
795       SVN_ERR(svn_config_get_bool(config, &ffd->compress_packed_revprops,
796                                   CONFIG_SECTION_PACKED_REVPROPS,
797                                   CONFIG_OPTION_COMPRESS_PACKED_REVPROPS,
798                                   FALSE));
799       SVN_ERR(svn_config_get_int64(config, &ffd->revprop_pack_size,
800                                    CONFIG_SECTION_PACKED_REVPROPS,
801                                    CONFIG_OPTION_REVPROP_PACK_SIZE,
802                                    ffd->compress_packed_revprops
803                                        ? 0x40
804                                        : 0x10));
805 
806       ffd->revprop_pack_size *= 1024;
807     }
808   else
809     {
810       ffd->revprop_pack_size = 0x10000;
811       ffd->compress_packed_revprops = FALSE;
812     }
813 
814   if (ffd->format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT)
815     {
816       SVN_ERR(svn_config_get_int64(config, &ffd->block_size,
817                                    CONFIG_SECTION_IO,
818                                    CONFIG_OPTION_BLOCK_SIZE,
819                                    64));
820       SVN_ERR(svn_config_get_int64(config, &ffd->l2p_page_size,
821                                    CONFIG_SECTION_IO,
822                                    CONFIG_OPTION_L2P_PAGE_SIZE,
823                                    0x2000));
824       SVN_ERR(svn_config_get_int64(config, &ffd->p2l_page_size,
825                                    CONFIG_SECTION_IO,
826                                    CONFIG_OPTION_P2L_PAGE_SIZE,
827                                    0x400));
828 
829       /* Don't accept unreasonable or illegal values.
830        * Block size and P2L page size are in kbytes;
831        * L2P blocks are arrays of apr_off_t. */
832       SVN_ERR(verify_block_size(ffd->block_size, 0x400,
833                                 CONFIG_OPTION_BLOCK_SIZE, scratch_pool));
834       SVN_ERR(verify_block_size(ffd->p2l_page_size, 0x400,
835                                 CONFIG_OPTION_P2L_PAGE_SIZE, scratch_pool));
836       SVN_ERR(verify_block_size(ffd->l2p_page_size, sizeof(apr_off_t),
837                                 CONFIG_OPTION_L2P_PAGE_SIZE, scratch_pool));
838 
839       /* convert kBytes to bytes */
840       ffd->block_size *= 0x400;
841       ffd->p2l_page_size *= 0x400;
842       /* L2P pages are in entries - not in (k)Bytes */
843     }
844   else
845     {
846       /* should be irrelevant but we initialize them anyway */
847       ffd->block_size = 0x1000; /* Matches default APR file buffer size. */
848       ffd->l2p_page_size = 0x2000;    /* Matches above default. */
849       ffd->p2l_page_size = 0x100000;  /* Matches above default in bytes. */
850     }
851 
852   if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
853     {
854       SVN_ERR(svn_config_get_bool(config, &ffd->pack_after_commit,
855                                   CONFIG_SECTION_DEBUG,
856                                   CONFIG_OPTION_PACK_AFTER_COMMIT,
857                                   FALSE));
858     }
859   else
860     {
861       ffd->pack_after_commit = FALSE;
862     }
863 
864   /* Initialize compression settings in ffd. */
865   if (ffd->format >= SVN_FS_FS__MIN_DELTIFICATION_FORMAT)
866     {
867       const char *compression_val;
868       const char *compression_level_val;
869 
870       svn_config_get(config, &compression_val,
871                      CONFIG_SECTION_DELTIFICATION,
872                      CONFIG_OPTION_COMPRESSION, NULL);
873       svn_config_get(config, &compression_level_val,
874                      CONFIG_SECTION_DELTIFICATION,
875                      CONFIG_OPTION_COMPRESSION_LEVEL, NULL);
876       if (compression_val && compression_level_val)
877         {
878           return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
879                                   _("The 'compression' and 'compression-level' "
880                                     "config options are mutually exclusive"));
881         }
882       else if (compression_val)
883         {
884           SVN_ERR(parse_compression_option(&ffd->delta_compression_type,
885                                            &ffd->delta_compression_level,
886                                            compression_val));
887           if (ffd->delta_compression_type == compression_type_lz4 &&
888               ffd->format < SVN_FS_FS__MIN_SVNDIFF2_FORMAT)
889             {
890               return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
891                                       _("Compression type 'lz4' requires "
892                                         "filesystem format 8 or higher"));
893             }
894         }
895       else if (compression_level_val)
896         {
897           /* Handle the deprecated 'compression-level' option. */
898           ffd->delta_compression_type = compression_type_zlib;
899           SVN_ERR(svn_cstring_atoi(&ffd->delta_compression_level,
900                                    compression_level_val));
901           ffd->delta_compression_level =
902             MIN(MAX(SVN_DELTA_COMPRESSION_LEVEL_NONE,
903                     ffd->delta_compression_level),
904                 SVN_DELTA_COMPRESSION_LEVEL_MAX);
905         }
906       else
907         {
908           /* Nothing specified explicitly, use the default settings:
909            * LZ4 compression for formats supporting it and zlib otherwise. */
910           if (ffd->format >= SVN_FS_FS__MIN_SVNDIFF2_FORMAT)
911             ffd->delta_compression_type = compression_type_lz4;
912           else
913             ffd->delta_compression_type = compression_type_zlib;
914 
915           ffd->delta_compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
916         }
917     }
918   else if (ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT)
919     {
920       ffd->delta_compression_type = compression_type_zlib;
921       ffd->delta_compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
922     }
923   else
924     {
925       ffd->delta_compression_type = compression_type_none;
926       ffd->delta_compression_level = SVN_DELTA_COMPRESSION_LEVEL_NONE;
927     }
928 
929 #ifdef SVN_DEBUG
930   SVN_ERR(svn_config_get_bool(config, &ffd->verify_before_commit,
931                               CONFIG_SECTION_DEBUG,
932                               CONFIG_OPTION_VERIFY_BEFORE_COMMIT,
933                               TRUE));
934 #else
935   SVN_ERR(svn_config_get_bool(config, &ffd->verify_before_commit,
936                               CONFIG_SECTION_DEBUG,
937                               CONFIG_OPTION_VERIFY_BEFORE_COMMIT,
938                               FALSE));
939 #endif
940 
941   /* memcached configuration */
942   SVN_ERR(svn_cache__make_memcache_from_config(&ffd->memcache, config,
943                                                result_pool, scratch_pool));
944 
945   SVN_ERR(svn_config_get_bool(config, &ffd->fail_stop,
946                               CONFIG_SECTION_CACHES, CONFIG_OPTION_FAIL_STOP,
947                               FALSE));
948 
949   return SVN_NO_ERROR;
950 }
951 
952 static svn_error_t *
write_config(svn_fs_t * fs,apr_pool_t * pool)953 write_config(svn_fs_t *fs,
954              apr_pool_t *pool)
955 {
956 #define NL APR_EOL_STR
957   static const char * const fsfs_conf_contents =
958 "### This file controls the configuration of the FSFS filesystem."           NL
959 ""                                                                           NL
960 "[" SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "]"                          NL
961 "### These options name memcached servers used to cache internal FSFS"       NL
962 "### data.  See http://www.danga.com/memcached/ for more information on"     NL
963 "### memcached.  To use memcached with FSFS, run one or more memcached"      NL
964 "### servers, and specify each of them as an option like so:"                NL
965 "# first-server = 127.0.0.1:11211"                                           NL
966 "# remote-memcached = mymemcached.corp.example.com:11212"                    NL
967 "### The option name is ignored; the value is of the form HOST:PORT."        NL
968 "### memcached servers can be shared between multiple repositories;"         NL
969 "### however, if you do this, you *must* ensure that repositories have"      NL
970 "### distinct UUIDs and paths, or else cached data from one repository"      NL
971 "### might be used by another accidentally.  Note also that memcached has"   NL
972 "### no authentication for reads or writes, so you must ensure that your"    NL
973 "### memcached servers are only accessible by trusted users."                NL
974 ""                                                                           NL
975 "[" CONFIG_SECTION_CACHES "]"                                                NL
976 "### When a cache-related error occurs, normally Subversion ignores it"      NL
977 "### and continues, logging an error if the server is appropriately"         NL
978 "### configured (and ignoring it with file:// access).  To make"             NL
979 "### Subversion never ignore cache errors, uncomment this line."             NL
980 "# " CONFIG_OPTION_FAIL_STOP " = true"                                       NL
981 ""                                                                           NL
982 "[" CONFIG_SECTION_REP_SHARING "]"                                           NL
983 "### To conserve space, the filesystem can optionally avoid storing"         NL
984 "### duplicate representations.  This comes at a slight cost in"             NL
985 "### performance, as maintaining a database of shared representations can"   NL
986 "### increase commit times.  The space savings are dependent upon the size"  NL
987 "### of the repository, the number of objects it contains and the amount of" NL
988 "### duplication between them, usually a function of the branching and"      NL
989 "### merging process."                                                       NL
990 "###"                                                                        NL
991 "### The following parameter enables rep-sharing in the repository.  It can" NL
992 "### be switched on and off at will, but for best space-saving results"      NL
993 "### should be enabled consistently over the life of the repository."        NL
994 "### 'svnadmin verify' will check the rep-cache regardless of this setting." NL
995 "### rep-sharing is enabled by default."                                     NL
996 "# " CONFIG_OPTION_ENABLE_REP_SHARING " = true"                              NL
997 ""                                                                           NL
998 "[" CONFIG_SECTION_DELTIFICATION "]"                                         NL
999 "### To conserve space, the filesystem stores data as differences against"   NL
1000 "### existing representations.  This comes at a slight cost in performance," NL
1001 "### as calculating differences can increase commit times.  Reading data"    NL
1002 "### will also create higher CPU load and the data will be fragmented."      NL
1003 "### Since deltification tends to save significant amounts of disk space,"   NL
1004 "### the overall I/O load can actually be lower."                            NL
1005 "###"                                                                        NL
1006 "### The options in this section allow for tuning the deltification"         NL
1007 "### strategy.  Their effects on data size and server performance may vary"  NL
1008 "### from one repository to another.  Versions prior to 1.8 will ignore"     NL
1009 "### this section."                                                          NL
1010 "###"                                                                        NL
1011 "### The following parameter enables deltification for directories. It can"  NL
1012 "### be switched on and off at will, but for best space-saving results"      NL
1013 "### should be enabled consistently over the lifetime of the repository."    NL
1014 "### Repositories containing large directories will benefit greatly."        NL
1015 "### In rarely accessed repositories, the I/O overhead may be significant"   NL
1016 "### as caches will most likely be low."                                     NL
1017 "### directory deltification is enabled by default."                         NL
1018 "# " CONFIG_OPTION_ENABLE_DIR_DELTIFICATION " = true"                        NL
1019 "###"                                                                        NL
1020 "### The following parameter enables deltification for properties on files"  NL
1021 "### and directories.  Overall, this is a minor tuning option but can save"  NL
1022 "### some disk space if you merge frequently or frequently change node"      NL
1023 "### properties.  You should not activate this if rep-sharing has been"      NL
1024 "### disabled because this may result in a net increase in repository size." NL
1025 "### property deltification is enabled by default."                          NL
1026 "# " CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION " = true"                      NL
1027 "###"                                                                        NL
1028 "### During commit, the server may need to walk the whole change history of" NL
1029 "### of a given node to find a suitable deltification base.  This linear"    NL
1030 "### process can impact commit times, svnadmin load and similar operations." NL
1031 "### This setting limits the depth of the deltification history.  If the"    NL
1032 "### threshold has been reached, the node will be stored as fulltext and a"  NL
1033 "### new deltification history begins."                                      NL
1034 "### Note, this is unrelated to svn log."                                    NL
1035 "### Very large values rarely provide significant additional savings but"    NL
1036 "### can impact performance greatly - in particular if directory"            NL
1037 "### deltification has been activated.  Very small values may be useful in"  NL
1038 "### repositories that are dominated by large, changing binaries."           NL
1039 "### Should be a power of two minus 1.  A value of 0 will effectively"       NL
1040 "### disable deltification."                                                 NL
1041 "### For 1.8, the default value is 1023; earlier versions have no limit."    NL
1042 "# " CONFIG_OPTION_MAX_DELTIFICATION_WALK " = 1023"                          NL
1043 "###"                                                                        NL
1044 "### The skip-delta scheme used by FSFS tends to repeatably store redundant" NL
1045 "### delta information where a simple delta against the latest version is"   NL
1046 "### often smaller.  By default, 1.8+ will therefore use skip deltas only"   NL
1047 "### after the linear chain of deltas has grown beyond the threshold"        NL
1048 "### specified by this setting."                                             NL
1049 "### Values up to 64 can result in some reduction in repository size for"    NL
1050 "### the cost of quickly increasing I/O and CPU costs. Similarly, smaller"   NL
1051 "### numbers can reduce those costs at the cost of more disk space.  For"    NL
1052 "### rarely read repositories or those containing larger binaries, this may" NL
1053 "### present a better trade-off."                                            NL
1054 "### Should be a power of two.  A value of 1 or smaller will cause the"      NL
1055 "### exclusive use of skip-deltas (as in pre-1.8)."                          NL
1056 "### For 1.8, the default value is 16; earlier versions use 1."              NL
1057 "# " CONFIG_OPTION_MAX_LINEAR_DELTIFICATION " = 16"                          NL
1058 "###"                                                                        NL
1059 "### After deltification, we compress the data to minimize on-disk size."    NL
1060 "### This setting controls the compression algorithm, which will be used in" NL
1061 "### future revisions.  It can be used to either disable compression or to"  NL
1062 "### select between available algorithms (zlib, lz4).  zlib is a general-"   NL
1063 "### purpose compression algorithm.  lz4 is a fast compression algorithm"    NL
1064 "### which should be preferred for repositories with large and, possibly,"   NL
1065 "### incompressible files.  Note that the compression ratio of lz4 is"       NL
1066 "### usually lower than the one provided by zlib, but using it can"          NL
1067 "### significantly speed up commits as well as reading the data."            NL
1068 "### lz4 compression algorithm is supported, starting from format 8"         NL
1069 "### repositories, available in Subversion 1.10 and higher."                 NL
1070 "### The syntax of this option is:"                                          NL
1071 "###   " CONFIG_OPTION_COMPRESSION " = none | lz4 | zlib | zlib-1 ... zlib-9" NL
1072 "### Versions prior to Subversion 1.10 will ignore this option."             NL
1073 "### The default value is 'lz4' if supported by the repository format and"   NL
1074 "### 'zlib' otherwise.  'zlib' is currently equivalent to 'zlib-5'."         NL
1075 "# " CONFIG_OPTION_COMPRESSION " = lz4"                                      NL
1076 "###"                                                                        NL
1077 "### DEPRECATED: The new '" CONFIG_OPTION_COMPRESSION "' option deprecates previously used" NL
1078 "### '" CONFIG_OPTION_COMPRESSION_LEVEL "' option, which was used to configure zlib compression." NL
1079 "### For compatibility with previous versions of Subversion, this option can"NL
1080 "### still be used (and it will result in zlib compression with the"         NL
1081 "### corresponding compression level)."                                      NL
1082 "###   " CONFIG_OPTION_COMPRESSION_LEVEL " = 0 ... 9 (default is 5)"         NL
1083 ""                                                                           NL
1084 "[" CONFIG_SECTION_PACKED_REVPROPS "]"                                       NL
1085 "### This parameter controls the size (in kBytes) of packed revprop files."  NL
1086 "### Revprops of consecutive revisions will be concatenated into a single"   NL
1087 "### file up to but not exceeding the threshold given here.  However, each"  NL
1088 "### pack file may be much smaller and revprops of a single revision may be" NL
1089 "### much larger than the limit set here.  The threshold will be applied"    NL
1090 "### before optional compression takes place."                               NL
1091 "### Large values will reduce disk space usage at the expense of increased"  NL
1092 "### latency and CPU usage reading and changing individual revprops."        NL
1093 "### Values smaller than 4 kByte will not improve latency any further and "  NL
1094 "### quickly render revprop packing ineffective."                            NL
1095 "### revprop-pack-size is 16 kBytes by default for non-compressed revprop"   NL
1096 "### pack files and 64 kBytes when compression has been enabled."            NL
1097 "# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 16"                                 NL
1098 "###"                                                                        NL
1099 "### To save disk space, packed revprop files may be compressed.  Standard"  NL
1100 "### revprops tend to allow for very effective compression.  Reading and"    NL
1101 "### even more so writing, become significantly more CPU intensive."         NL
1102 "### Compressing packed revprops is disabled by default."                    NL
1103 "# " CONFIG_OPTION_COMPRESS_PACKED_REVPROPS " = false"                       NL
1104 ""                                                                           NL
1105 "[" CONFIG_SECTION_IO "]"                                                    NL
1106 "### Parameters in this section control the data access granularity in"      NL
1107 "### format 7 repositories and later.  The defaults should translate into"   NL
1108 "### decent performance over a wide range of setups."                        NL
1109 "###"                                                                        NL
1110 "### When a specific piece of information needs to be read from disk,  a"    NL
1111 "### data block is being read at once and its contents are being cached."    NL
1112 "### If the repository is being stored on a RAID, the block size should be"  NL
1113 "### either 50% or 100% of RAID block size / granularity.  Also, your file"  NL
1114 "### system blocks/clusters should be properly aligned and sized.  In that"  NL
1115 "### setup, each access will hit only one disk (minimizes I/O load) but"     NL
1116 "### uses all the data provided by the disk in a single access."             NL
1117 "### For SSD-based storage systems, slightly lower values around 16 kB"      NL
1118 "### may improve latency while still maximizing throughput.  If block-read"  NL
1119 "### has not been enabled, this will be capped to 4 kBytes."                 NL
1120 "### Can be changed at any time but must be a power of 2."                   NL
1121 "### block-size is given in kBytes and with a default of 64 kBytes."         NL
1122 "# " CONFIG_OPTION_BLOCK_SIZE " = 64"                                        NL
1123 "###"                                                                        NL
1124 "### The log-to-phys index maps data item numbers to offsets within the"     NL
1125 "### rev or pack file.  This index is organized in pages of a fixed maximum" NL
1126 "### capacity.  To access an item, the page table and the respective page"   NL
1127 "### must be read."                                                          NL
1128 "### This parameter only affects revisions with thousands of changed paths." NL
1129 "### If you have several extremely large revisions (~1 mio changes), think"  NL
1130 "### about increasing this setting.  Reducing the value will rarely result"  NL
1131 "### in a net speedup."                                                      NL
1132 "### This is an expert setting.  Must be a power of 2."                      NL
1133 "### l2p-page-size is 8192 entries by default."                              NL
1134 "# " CONFIG_OPTION_L2P_PAGE_SIZE " = 8192"                                   NL
1135 "###"                                                                        NL
1136 "### The phys-to-log index maps positions within the rev or pack file to"    NL
1137 "### to data items,  i.e. describes what piece of information is being"      NL
1138 "### stored at any particular offset.  The index describes the rev file"     NL
1139 "### in chunks (pages) and keeps a global list of all those pages.  Large"   NL
1140 "### pages mean a shorter page table but a larger per-page description of"   NL
1141 "### data items in it.  The latency sweetspot depends on the change size"    NL
1142 "### distribution but covers a relatively wide range."                       NL
1143 "### If the repository contains very large files,  i.e. individual changes"  NL
1144 "### of tens of MB each,  increasing the page size will shorten the index"   NL
1145 "### file at the expense of a slightly increased latency in sections with"   NL
1146 "### smaller changes."                                                       NL
1147 "### For source code repositories, this should be about 16x the block-size." NL
1148 "### Must be a power of 2."                                                  NL
1149 "### p2l-page-size is given in kBytes and with a default of 1024 kBytes."    NL
1150 "# " CONFIG_OPTION_P2L_PAGE_SIZE " = 1024"                                   NL
1151 ""                                                                           NL
1152 "[" CONFIG_SECTION_DEBUG "]"                                                 NL
1153 "###"                                                                        NL
1154 "### Whether to verify each new revision immediately before finalizing"      NL
1155 "### the commit.  This is disabled by default except in maintainer-mode"     NL
1156 "### builds."                                                                NL
1157 "# " CONFIG_OPTION_VERIFY_BEFORE_COMMIT " = false"                           NL
1158 ;
1159 #undef NL
1160   return svn_io_file_create(svn_dirent_join(fs->path, PATH_CONFIG, pool),
1161                             fsfs_conf_contents, pool);
1162 }
1163 
1164 /* Read / Evaluate the global configuration in FS->CONFIG to set up
1165  * parameters in FS. */
1166 static svn_error_t *
read_global_config(svn_fs_t * fs)1167 read_global_config(svn_fs_t *fs)
1168 {
1169   fs_fs_data_t *ffd = fs->fsap_data;
1170 
1171   ffd->use_block_read = svn_hash__get_bool(fs->config,
1172                                            SVN_FS_CONFIG_FSFS_BLOCK_READ,
1173                                            FALSE);
1174   ffd->flush_to_disk = !svn_hash__get_bool(fs->config,
1175                                            SVN_FS_CONFIG_NO_FLUSH_TO_DISK,
1176                                            FALSE);
1177 
1178   /* Ignore the user-specified larger block size if we don't use block-read.
1179      Defaulting to 4k gives us the same access granularity in format 7 as in
1180      older formats. */
1181   if (!ffd->use_block_read)
1182     ffd->block_size = MIN(0x1000, ffd->block_size);
1183 
1184   return SVN_NO_ERROR;
1185 }
1186 
1187 /* Read FS's UUID file and store the data in the FS struct. */
1188 static svn_error_t *
read_uuid(svn_fs_t * fs,apr_pool_t * scratch_pool)1189 read_uuid(svn_fs_t *fs,
1190           apr_pool_t *scratch_pool)
1191 {
1192   fs_fs_data_t *ffd = fs->fsap_data;
1193   apr_file_t *uuid_file;
1194   char buf[APR_UUID_FORMATTED_LENGTH + 2];
1195   apr_size_t limit;
1196 
1197   /* Read the repository uuid. */
1198   SVN_ERR(svn_io_file_open(&uuid_file, path_uuid(fs, scratch_pool),
1199                            APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
1200                            scratch_pool));
1201 
1202   limit = sizeof(buf);
1203   SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, scratch_pool));
1204   fs->uuid = apr_pstrdup(fs->pool, buf);
1205 
1206   /* Read the instance ID. */
1207   if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT)
1208     {
1209       limit = sizeof(buf);
1210       SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit,
1211                                       scratch_pool));
1212       ffd->instance_id = apr_pstrdup(fs->pool, buf);
1213     }
1214   else
1215     {
1216       ffd->instance_id = fs->uuid;
1217     }
1218 
1219   SVN_ERR(svn_io_file_close(uuid_file, scratch_pool));
1220 
1221   return SVN_NO_ERROR;
1222 }
1223 
1224 svn_error_t *
svn_fs_fs__read_format_file(svn_fs_t * fs,apr_pool_t * scratch_pool)1225 svn_fs_fs__read_format_file(svn_fs_t *fs, apr_pool_t *scratch_pool)
1226 {
1227   fs_fs_data_t *ffd = fs->fsap_data;
1228   int format, max_files_per_dir;
1229   svn_boolean_t use_log_addressing;
1230 
1231   /* Read info from format file. */
1232   SVN_ERR(read_format(&format, &max_files_per_dir, &use_log_addressing,
1233                       path_format(fs, scratch_pool), scratch_pool));
1234 
1235   /* Now that we've got *all* info, store / update values in FFD. */
1236   ffd->format = format;
1237   ffd->max_files_per_dir = max_files_per_dir;
1238   ffd->use_log_addressing = use_log_addressing;
1239 
1240   return SVN_NO_ERROR;
1241 }
1242 
1243 svn_error_t *
svn_fs_fs__open(svn_fs_t * fs,const char * path,apr_pool_t * pool)1244 svn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool)
1245 {
1246   fs_fs_data_t *ffd = fs->fsap_data;
1247   fs->path = apr_pstrdup(fs->pool, path);
1248 
1249   /* Read the FS format file. */
1250   SVN_ERR(svn_fs_fs__read_format_file(fs, pool));
1251 
1252   /* Read in and cache the repository uuid. */
1253   SVN_ERR(read_uuid(fs, pool));
1254 
1255   /* Read the min unpacked revision. */
1256   if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1257     SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, pool));
1258 
1259   /* Read the configuration file. */
1260   SVN_ERR(read_config(ffd, fs->path, fs->pool, pool));
1261 
1262   /* Global configuration options. */
1263   SVN_ERR(read_global_config(fs));
1264 
1265   ffd->youngest_rev_cache = 0;
1266 
1267   return SVN_NO_ERROR;
1268 }
1269 
1270 /* Wrapper around svn_io_file_create which ignores EEXIST. */
1271 static svn_error_t *
create_file_ignore_eexist(const char * file,const char * contents,apr_pool_t * pool)1272 create_file_ignore_eexist(const char *file,
1273                           const char *contents,
1274                           apr_pool_t *pool)
1275 {
1276   svn_error_t *err = svn_io_file_create(file, contents, pool);
1277   if (err && APR_STATUS_IS_EEXIST(err->apr_err))
1278     {
1279       svn_error_clear(err);
1280       err = SVN_NO_ERROR;
1281     }
1282   return svn_error_trace(err);
1283 }
1284 
1285 /* Baton type bridging svn_fs_fs__upgrade and upgrade_body carrying
1286  * parameters over between them. */
1287 struct upgrade_baton_t
1288 {
1289   svn_fs_t *fs;
1290   svn_fs_upgrade_notify_t notify_func;
1291   void *notify_baton;
1292   svn_cancel_func_t cancel_func;
1293   void *cancel_baton;
1294 };
1295 
1296 static svn_error_t *
upgrade_body(void * baton,apr_pool_t * pool)1297 upgrade_body(void *baton, apr_pool_t *pool)
1298 {
1299   struct upgrade_baton_t *upgrade_baton = baton;
1300   svn_fs_t *fs = upgrade_baton->fs;
1301   fs_fs_data_t *ffd = fs->fsap_data;
1302   int format, max_files_per_dir;
1303   svn_boolean_t use_log_addressing;
1304   const char *format_path = path_format(fs, pool);
1305   svn_node_kind_t kind;
1306   svn_boolean_t needs_revprop_shard_cleanup = FALSE;
1307 
1308   /* Read the FS format number and max-files-per-dir setting. */
1309   SVN_ERR(read_format(&format, &max_files_per_dir, &use_log_addressing,
1310                       format_path, pool));
1311 
1312   /* If the config file does not exist, create one. */
1313   SVN_ERR(svn_io_check_path(svn_dirent_join(fs->path, PATH_CONFIG, pool),
1314                             &kind, pool));
1315   switch (kind)
1316     {
1317     case svn_node_none:
1318       SVN_ERR(write_config(fs, pool));
1319       break;
1320     case svn_node_file:
1321       break;
1322     default:
1323       return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
1324                                _("'%s' is not a regular file."
1325                                  " Please move it out of "
1326                                  "the way and try again"),
1327                                svn_dirent_join(fs->path, PATH_CONFIG, pool));
1328     }
1329 
1330   /* If we're already up-to-date, there's nothing else to be done here. */
1331   if (format == SVN_FS_FS__FORMAT_NUMBER)
1332     return SVN_NO_ERROR;
1333 
1334   /* If our filesystem predates the existence of the 'txn-current
1335      file', make that file and its corresponding lock file. */
1336   if (format < SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1337     {
1338       SVN_ERR(create_file_ignore_eexist(
1339                            svn_fs_fs__path_txn_current(fs, pool), "0\n",
1340                            pool));
1341       SVN_ERR(create_file_ignore_eexist(
1342                            svn_fs_fs__path_txn_current_lock(fs, pool), "",
1343                            pool));
1344     }
1345 
1346   /* If our filesystem predates the existence of the 'txn-protorevs'
1347      dir, make that directory.  */
1348   if (format < SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1349     {
1350       SVN_ERR(svn_io_make_dir_recursively(
1351           svn_fs_fs__path_txn_proto_revs(fs, pool), pool));
1352     }
1353 
1354   /* If our filesystem is new enough, write the min unpacked rev file. */
1355   if (format < SVN_FS_FS__MIN_PACKED_FORMAT)
1356     SVN_ERR(svn_io_file_create(svn_fs_fs__path_min_unpacked_rev(fs, pool),
1357                                "0\n", pool));
1358 
1359   /* If the file system supports revision packing but not revprop packing
1360      *and* the FS has been sharded, pack the revprops up to the point that
1361      revision data has been packed.  However, keep the non-packed revprop
1362      files around until after the format bump */
1363   if (   format >= SVN_FS_FS__MIN_PACKED_FORMAT
1364       && format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
1365       && max_files_per_dir > 0)
1366     {
1367       needs_revprop_shard_cleanup = TRUE;
1368       SVN_ERR(svn_fs_fs__upgrade_pack_revprops(fs,
1369                                                upgrade_baton->notify_func,
1370                                                upgrade_baton->notify_baton,
1371                                                upgrade_baton->cancel_func,
1372                                                upgrade_baton->cancel_baton,
1373                                                pool));
1374     }
1375 
1376   /* We will need the UUID info shortly ...
1377      Read it before the format bump as the UUID file still uses the old
1378      format. */
1379   SVN_ERR(read_uuid(fs, pool));
1380 
1381   /* Update the format info in the FS struct.  Upgrade steps further
1382      down will use the format from FS to create missing info. */
1383   ffd->format = SVN_FS_FS__FORMAT_NUMBER;
1384   ffd->max_files_per_dir = max_files_per_dir;
1385   ffd->use_log_addressing = use_log_addressing;
1386 
1387   /* Always add / bump the instance ID such that no form of caching
1388      accidentally uses outdated information.  Keep the UUID. */
1389   SVN_ERR(svn_fs_fs__set_uuid(fs, fs->uuid, NULL, pool));
1390 
1391   /* Bump the format file. */
1392   SVN_ERR(svn_fs_fs__write_format(fs, TRUE, pool));
1393 
1394   if (upgrade_baton->notify_func)
1395     SVN_ERR(upgrade_baton->notify_func(upgrade_baton->notify_baton,
1396                                        SVN_FS_FS__FORMAT_NUMBER,
1397                                        svn_fs_upgrade_format_bumped,
1398                                        pool));
1399 
1400   /* Now, it is safe to remove the redundant revprop files. */
1401   if (needs_revprop_shard_cleanup)
1402     SVN_ERR(svn_fs_fs__upgrade_cleanup_pack_revprops(fs,
1403                                                upgrade_baton->notify_func,
1404                                                upgrade_baton->notify_baton,
1405                                                upgrade_baton->cancel_func,
1406                                                upgrade_baton->cancel_baton,
1407                                                pool));
1408 
1409   /* Done */
1410   return SVN_NO_ERROR;
1411 }
1412 
1413 
1414 svn_error_t *
svn_fs_fs__upgrade(svn_fs_t * fs,svn_fs_upgrade_notify_t notify_func,void * notify_baton,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * pool)1415 svn_fs_fs__upgrade(svn_fs_t *fs,
1416                    svn_fs_upgrade_notify_t notify_func,
1417                    void *notify_baton,
1418                    svn_cancel_func_t cancel_func,
1419                    void *cancel_baton,
1420                    apr_pool_t *pool)
1421 {
1422   struct upgrade_baton_t baton;
1423   baton.fs = fs;
1424   baton.notify_func = notify_func;
1425   baton.notify_baton = notify_baton;
1426   baton.cancel_func = cancel_func;
1427   baton.cancel_baton = cancel_baton;
1428 
1429   return svn_fs_fs__with_all_locks(fs, upgrade_body, (void *)&baton, pool);
1430 }
1431 
1432 /* Find the youngest revision in a repository at path FS_PATH and
1433    return it in *YOUNGEST_P.  Perform temporary allocations in
1434    POOL. */
1435 static svn_error_t *
get_youngest(svn_revnum_t * youngest_p,svn_fs_t * fs,apr_pool_t * pool)1436 get_youngest(svn_revnum_t *youngest_p,
1437              svn_fs_t *fs,
1438              apr_pool_t *pool)
1439 {
1440   apr_uint64_t dummy;
1441   SVN_ERR(svn_fs_fs__read_current(youngest_p, &dummy, &dummy, fs, pool));
1442   return SVN_NO_ERROR;
1443 }
1444 
1445 
1446 svn_error_t *
svn_fs_fs__youngest_rev(svn_revnum_t * youngest_p,svn_fs_t * fs,apr_pool_t * pool)1447 svn_fs_fs__youngest_rev(svn_revnum_t *youngest_p,
1448                         svn_fs_t *fs,
1449                         apr_pool_t *pool)
1450 {
1451   fs_fs_data_t *ffd = fs->fsap_data;
1452 
1453   SVN_ERR(get_youngest(youngest_p, fs, pool));
1454   ffd->youngest_rev_cache = *youngest_p;
1455 
1456   return SVN_NO_ERROR;
1457 }
1458 
1459 int
svn_fs_fs__shard_size(svn_fs_t * fs)1460 svn_fs_fs__shard_size(svn_fs_t *fs)
1461 {
1462   fs_fs_data_t *ffd = fs->fsap_data;
1463 
1464   return ffd->max_files_per_dir;
1465 }
1466 
1467 svn_error_t *
svn_fs_fs__min_unpacked_rev(svn_revnum_t * min_unpacked,svn_fs_t * fs,apr_pool_t * pool)1468 svn_fs_fs__min_unpacked_rev(svn_revnum_t *min_unpacked,
1469                             svn_fs_t *fs,
1470                             apr_pool_t *pool)
1471 {
1472   fs_fs_data_t *ffd = fs->fsap_data;
1473 
1474   /* Calling this for pre-v4 repos is illegal. */
1475   if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1476     SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, pool));
1477 
1478   *min_unpacked = ffd->min_unpacked_rev;
1479 
1480   return SVN_NO_ERROR;
1481 }
1482 
1483 svn_error_t *
svn_fs_fs__ensure_revision_exists(svn_revnum_t rev,svn_fs_t * fs,apr_pool_t * pool)1484 svn_fs_fs__ensure_revision_exists(svn_revnum_t rev,
1485                                   svn_fs_t *fs,
1486                                   apr_pool_t *pool)
1487 {
1488   fs_fs_data_t *ffd = fs->fsap_data;
1489 
1490   if (! SVN_IS_VALID_REVNUM(rev))
1491     return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1492                              _("Invalid revision number '%ld'"), rev);
1493 
1494 
1495   /* Did the revision exist the last time we checked the current
1496      file? */
1497   if (rev <= ffd->youngest_rev_cache)
1498     return SVN_NO_ERROR;
1499 
1500   SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs, pool));
1501 
1502   /* Check again. */
1503   if (rev <= ffd->youngest_rev_cache)
1504     return SVN_NO_ERROR;
1505 
1506   return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1507                            _("No such revision %ld"), rev);
1508 }
1509 
1510 svn_error_t *
svn_fs_fs__file_length(svn_filesize_t * length,node_revision_t * noderev,apr_pool_t * pool)1511 svn_fs_fs__file_length(svn_filesize_t *length,
1512                        node_revision_t *noderev,
1513                        apr_pool_t *pool)
1514 {
1515   representation_t *data_rep = noderev->data_rep;
1516   if (!data_rep)
1517     {
1518       /* Treat "no representation" as "empty file". */
1519       *length = 0;
1520     }
1521   else
1522     {
1523       *length = data_rep->expanded_size;
1524     }
1525 
1526   return SVN_NO_ERROR;
1527 }
1528 
1529 svn_boolean_t
svn_fs_fs__noderev_same_rep_key(representation_t * a,representation_t * b)1530 svn_fs_fs__noderev_same_rep_key(representation_t *a,
1531                                 representation_t *b)
1532 {
1533   if (a == b)
1534     return TRUE;
1535 
1536   if (a == NULL || b == NULL)
1537     return FALSE;
1538 
1539   if (a->item_index != b->item_index)
1540     return FALSE;
1541 
1542   if (a->revision != b->revision)
1543     return FALSE;
1544 
1545   return memcmp(&a->uniquifier, &b->uniquifier, sizeof(a->uniquifier)) == 0;
1546 }
1547 
1548 svn_error_t *
svn_fs_fs__file_text_rep_equal(svn_boolean_t * equal,svn_fs_t * fs,node_revision_t * a,node_revision_t * b,apr_pool_t * scratch_pool)1549 svn_fs_fs__file_text_rep_equal(svn_boolean_t *equal,
1550                                svn_fs_t *fs,
1551                                node_revision_t *a,
1552                                node_revision_t *b,
1553                                apr_pool_t *scratch_pool)
1554 {
1555   svn_stream_t *contents_a, *contents_b;
1556   representation_t *rep_a = a->data_rep;
1557   representation_t *rep_b = b->data_rep;
1558   svn_boolean_t a_empty = !rep_a || rep_a->expanded_size == 0;
1559   svn_boolean_t b_empty = !rep_b || rep_b->expanded_size == 0;
1560 
1561   /* This makes sure that neither rep will be NULL later on */
1562   if (a_empty && b_empty)
1563     {
1564       *equal = TRUE;
1565       return SVN_NO_ERROR;
1566     }
1567 
1568   if (a_empty != b_empty)
1569     {
1570       *equal = FALSE;
1571       return SVN_NO_ERROR;
1572     }
1573 
1574   /* File text representations always know their checksums - even in a txn. */
1575   if (memcmp(rep_a->md5_digest, rep_b->md5_digest, sizeof(rep_a->md5_digest)))
1576     {
1577       *equal = FALSE;
1578       return SVN_NO_ERROR;
1579     }
1580 
1581   /* Paranoia. Compare SHA1 checksums because that's the level of
1582      confidence we require for e.g. the working copy. */
1583   if (rep_a->has_sha1 && rep_b->has_sha1)
1584     {
1585       *equal = memcmp(rep_a->sha1_digest, rep_b->sha1_digest,
1586                       sizeof(rep_a->sha1_digest)) == 0;
1587       return SVN_NO_ERROR;
1588     }
1589 
1590   /* Same path in same rev or txn? */
1591   if (svn_fs_fs__id_eq(a->id, b->id))
1592     {
1593       *equal = TRUE;
1594       return SVN_NO_ERROR;
1595     }
1596 
1597   SVN_ERR(svn_fs_fs__get_contents(&contents_a, fs, rep_a, TRUE,
1598                                   scratch_pool));
1599   SVN_ERR(svn_fs_fs__get_contents(&contents_b, fs, rep_b, TRUE,
1600                                   scratch_pool));
1601   SVN_ERR(svn_stream_contents_same2(equal, contents_a, contents_b,
1602                                    scratch_pool));
1603 
1604   return SVN_NO_ERROR;
1605 }
1606 
1607 svn_error_t *
svn_fs_fs__prop_rep_equal(svn_boolean_t * equal,svn_fs_t * fs,node_revision_t * a,node_revision_t * b,apr_pool_t * scratch_pool)1608 svn_fs_fs__prop_rep_equal(svn_boolean_t *equal,
1609                           svn_fs_t *fs,
1610                           node_revision_t *a,
1611                           node_revision_t *b,
1612                           apr_pool_t *scratch_pool)
1613 {
1614   representation_t *rep_a = a->prop_rep;
1615   representation_t *rep_b = b->prop_rep;
1616   apr_hash_t *proplist_a;
1617   apr_hash_t *proplist_b;
1618 
1619   /* Mainly for a==b==NULL */
1620   if (rep_a == rep_b)
1621     {
1622       *equal = TRUE;
1623       return SVN_NO_ERROR;
1624     }
1625 
1626   /* Committed property lists can be compared quickly */
1627   if (   rep_a && rep_b
1628       && !svn_fs_fs__id_txn_used(&rep_a->txn_id)
1629       && !svn_fs_fs__id_txn_used(&rep_b->txn_id))
1630     {
1631       /* Same representation? */
1632       if (   (rep_a->revision == rep_b->revision)
1633           && (rep_a->item_index == rep_b->item_index))
1634         {
1635           *equal = TRUE;
1636           return SVN_NO_ERROR;
1637         }
1638 
1639       /* Known different content? MD5 must be given. */
1640       if (memcmp(rep_a->md5_digest, rep_b->md5_digest,
1641                  sizeof(rep_a->md5_digest)))
1642         {
1643           *equal = FALSE;
1644           return SVN_NO_ERROR;
1645         }
1646     }
1647 
1648   /* Same path in same txn?
1649    *
1650    * For committed reps, IDs cannot be the same here b/c we already know
1651    * that they point to different representations. */
1652   if (svn_fs_fs__id_eq(a->id, b->id))
1653     {
1654       *equal = TRUE;
1655       return SVN_NO_ERROR;
1656     }
1657 
1658   /* At least one of the reps has been modified in a txn.
1659      Fetch and compare them. */
1660   SVN_ERR(svn_fs_fs__get_proplist(&proplist_a, fs, a, scratch_pool));
1661   SVN_ERR(svn_fs_fs__get_proplist(&proplist_b, fs, b, scratch_pool));
1662 
1663   *equal = svn_fs__prop_lists_equal(proplist_a, proplist_b, scratch_pool);
1664   return SVN_NO_ERROR;
1665 }
1666 
1667 
1668 svn_error_t *
svn_fs_fs__file_checksum(svn_checksum_t ** checksum,node_revision_t * noderev,svn_checksum_kind_t kind,apr_pool_t * pool)1669 svn_fs_fs__file_checksum(svn_checksum_t **checksum,
1670                          node_revision_t *noderev,
1671                          svn_checksum_kind_t kind,
1672                          apr_pool_t *pool)
1673 {
1674   *checksum = NULL;
1675 
1676   if (noderev->data_rep)
1677     {
1678       svn_checksum_t temp;
1679       temp.kind = kind;
1680 
1681       switch(kind)
1682         {
1683           case svn_checksum_md5:
1684             temp.digest = noderev->data_rep->md5_digest;
1685             break;
1686 
1687           case svn_checksum_sha1:
1688             if (! noderev->data_rep->has_sha1)
1689               return SVN_NO_ERROR;
1690 
1691             temp.digest = noderev->data_rep->sha1_digest;
1692             break;
1693 
1694           default:
1695             return SVN_NO_ERROR;
1696         }
1697 
1698       *checksum = svn_checksum_dup(&temp, pool);
1699     }
1700 
1701   return SVN_NO_ERROR;
1702 }
1703 
1704 representation_t *
svn_fs_fs__rep_copy(representation_t * rep,apr_pool_t * pool)1705 svn_fs_fs__rep_copy(representation_t *rep,
1706                     apr_pool_t *pool)
1707 {
1708   if (rep == NULL)
1709     return NULL;
1710 
1711   return apr_pmemdup(pool, rep, sizeof(*rep));
1712 }
1713 
1714 
1715 /* Write out the zeroth revision for filesystem FS.
1716    Perform temporary allocations in SCRATCH_POOL. */
1717 static svn_error_t *
write_revision_zero(svn_fs_t * fs,apr_pool_t * scratch_pool)1718 write_revision_zero(svn_fs_t *fs,
1719                     apr_pool_t *scratch_pool)
1720 {
1721   /* Use an explicit sub-pool to have full control over temp file lifetimes.
1722    * Since we have it, use it for everything else as well. */
1723   apr_pool_t *subpool = svn_pool_create(scratch_pool);
1724   const char *path_revision_zero = svn_fs_fs__path_rev(fs, 0, subpool);
1725   apr_hash_t *proplist;
1726   svn_string_t date;
1727 
1728   /* Write out a rev file for revision 0. */
1729   if (svn_fs_fs__use_log_addressing(fs))
1730     {
1731       apr_array_header_t *index_entries;
1732       svn_fs_fs__p2l_entry_t *entry;
1733       svn_fs_fs__revision_file_t *rev_file;
1734       const char *l2p_proto_index, *p2l_proto_index;
1735 
1736       /* Write a skeleton r0 with no indexes. */
1737       SVN_ERR(svn_io_file_create(path_revision_zero,
1738                     "PLAIN\nEND\nENDREP\n"
1739                     "id: 0.0.r0/2\n"
1740                     "type: dir\n"
1741                     "count: 0\n"
1742                     "text: 0 3 4 4 "
1743                     "2d2977d1c96f487abe4a1e202dd03b4e\n"
1744                     "cpath: /\n"
1745                     "\n\n", subpool));
1746 
1747       /* Construct the index P2L contents: describe the 3 items we have.
1748          Be sure to create them in on-disk order. */
1749       index_entries = apr_array_make(subpool, 3, sizeof(entry));
1750 
1751       entry = apr_pcalloc(subpool, sizeof(*entry));
1752       entry->offset = 0;
1753       entry->size = 17;
1754       entry->type = SVN_FS_FS__ITEM_TYPE_DIR_REP;
1755       entry->item.revision = 0;
1756       entry->item.number = SVN_FS_FS__ITEM_INDEX_FIRST_USER;
1757       APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
1758 
1759       entry = apr_pcalloc(subpool, sizeof(*entry));
1760       entry->offset = 17;
1761       entry->size = 89;
1762       entry->type = SVN_FS_FS__ITEM_TYPE_NODEREV;
1763       entry->item.revision = 0;
1764       entry->item.number = SVN_FS_FS__ITEM_INDEX_ROOT_NODE;
1765       APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
1766 
1767       entry = apr_pcalloc(subpool, sizeof(*entry));
1768       entry->offset = 106;
1769       entry->size = 1;
1770       entry->type = SVN_FS_FS__ITEM_TYPE_CHANGES;
1771       entry->item.revision = 0;
1772       entry->item.number = SVN_FS_FS__ITEM_INDEX_CHANGES;
1773       APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
1774 
1775       /* Now re-open r0, create proto-index files from our entries and
1776          rewrite the index section of r0. */
1777       SVN_ERR(svn_fs_fs__open_pack_or_rev_file_writable(&rev_file, fs, 0,
1778                                                         subpool, subpool));
1779       SVN_ERR(svn_fs_fs__p2l_index_from_p2l_entries(&p2l_proto_index, fs,
1780                                                     rev_file, index_entries,
1781                                                     subpool, subpool));
1782       SVN_ERR(svn_fs_fs__l2p_index_from_p2l_entries(&l2p_proto_index, fs,
1783                                                     index_entries,
1784                                                     subpool, subpool));
1785       SVN_ERR(svn_fs_fs__add_index_data(fs, rev_file->file, l2p_proto_index,
1786                                         p2l_proto_index, 0, subpool));
1787       SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
1788     }
1789   else
1790     SVN_ERR(svn_io_file_create(path_revision_zero,
1791                                "PLAIN\nEND\nENDREP\n"
1792                                "id: 0.0.r0/17\n"
1793                                "type: dir\n"
1794                                "count: 0\n"
1795                                "text: 0 0 4 4 "
1796                                "2d2977d1c96f487abe4a1e202dd03b4e\n"
1797                                "cpath: /\n"
1798                                "\n\n17 107\n", subpool));
1799 
1800   SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, subpool));
1801 
1802   /* Set a date on revision 0. */
1803   date.data = svn_time_to_cstring(apr_time_now(), subpool);
1804   date.len = strlen(date.data);
1805   proplist = apr_hash_make(subpool);
1806   svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date);
1807   SVN_ERR(svn_fs_fs__set_revision_proplist(fs, 0, proplist, subpool));
1808 
1809   svn_pool_destroy(subpool);
1810   return SVN_NO_ERROR;
1811 }
1812 
1813 svn_error_t *
svn_fs_fs__create_file_tree(svn_fs_t * fs,const char * path,int format,int shard_size,svn_boolean_t use_log_addressing,apr_pool_t * pool)1814 svn_fs_fs__create_file_tree(svn_fs_t *fs,
1815                             const char *path,
1816                             int format,
1817                             int shard_size,
1818                             svn_boolean_t use_log_addressing,
1819                             apr_pool_t *pool)
1820 {
1821   fs_fs_data_t *ffd = fs->fsap_data;
1822 
1823   fs->path = apr_pstrdup(fs->pool, path);
1824   ffd->format = format;
1825 
1826   /* Use an appropriate sharding mode if supported by the format. */
1827   if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
1828     ffd->max_files_per_dir = shard_size;
1829   else
1830     ffd->max_files_per_dir = 0;
1831 
1832   /* Select the addressing mode depending on the format. */
1833   if (format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT)
1834     ffd->use_log_addressing = use_log_addressing;
1835   else
1836     ffd->use_log_addressing = FALSE;
1837 
1838   /* Create the revision data directories. */
1839   if (ffd->max_files_per_dir)
1840     SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_rev_shard(fs, 0,
1841                                                                   pool),
1842                                         pool));
1843   else
1844     SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_REVS_DIR,
1845                                                         pool),
1846                                         pool));
1847 
1848   /* Create the revprops directory. */
1849   if (ffd->max_files_per_dir)
1850     SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_revprops_shard(fs, 0,
1851                                                                        pool),
1852                                         pool));
1853   else
1854     SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path,
1855                                                         PATH_REVPROPS_DIR,
1856                                                         pool),
1857                                         pool));
1858 
1859   /* Create the transaction directory. */
1860   SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_txns_dir(fs, pool),
1861                                       pool));
1862 
1863   /* Create the protorevs directory. */
1864   if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1865     SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_txn_proto_revs(fs,
1866                                                                        pool),
1867                                         pool));
1868 
1869   /* Create the 'current' file. */
1870   SVN_ERR(svn_io_file_create_empty(svn_fs_fs__path_current(fs, pool), pool));
1871   SVN_ERR(svn_fs_fs__write_current(fs, 0, 1, 1, pool));
1872 
1873   /* Create the 'uuid' file. */
1874   SVN_ERR(svn_io_file_create_empty(svn_fs_fs__path_lock(fs, pool), pool));
1875   SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, NULL, pool));
1876 
1877   /* Create the fsfs.conf file if supported.  Older server versions would
1878      simply ignore the file but that might result in a different behavior
1879      than with the later releases.  Also, hotcopy would ignore, i.e. not
1880      copy, a fsfs.conf with old formats. */
1881   if (ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
1882     SVN_ERR(write_config(fs, pool));
1883 
1884   SVN_ERR(read_config(ffd, fs->path, fs->pool, pool));
1885 
1886   /* Global configuration options. */
1887   SVN_ERR(read_global_config(fs));
1888 
1889   /* Add revision 0. */
1890   SVN_ERR(write_revision_zero(fs, pool));
1891 
1892   /* Create the min unpacked rev file. */
1893   if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1894     SVN_ERR(svn_io_file_create(svn_fs_fs__path_min_unpacked_rev(fs, pool),
1895                                "0\n", pool));
1896 
1897   /* Create the txn-current file if the repository supports
1898      the transaction sequence file. */
1899   if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1900     {
1901       SVN_ERR(svn_io_file_create(svn_fs_fs__path_txn_current(fs, pool),
1902                                  "0\n", pool));
1903       SVN_ERR(svn_io_file_create_empty(
1904                                  svn_fs_fs__path_txn_current_lock(fs, pool),
1905                                  pool));
1906     }
1907 
1908   ffd->youngest_rev_cache = 0;
1909   return SVN_NO_ERROR;
1910 }
1911 
1912 svn_error_t *
svn_fs_fs__create(svn_fs_t * fs,const char * path,apr_pool_t * pool)1913 svn_fs_fs__create(svn_fs_t *fs,
1914                   const char *path,
1915                   apr_pool_t *pool)
1916 {
1917   int format = SVN_FS_FS__FORMAT_NUMBER;
1918   int shard_size = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
1919   svn_boolean_t log_addressing;
1920 
1921   /* Process the given filesystem config. */
1922   if (fs->config)
1923     {
1924       svn_version_t *compatible_version;
1925       const char *shard_size_str;
1926       SVN_ERR(svn_fs__compatible_version(&compatible_version, fs->config,
1927                                          pool));
1928 
1929       /* select format number */
1930       switch(compatible_version->minor)
1931         {
1932           case 0: return svn_error_create(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
1933                  _("FSFS is not compatible with Subversion prior to 1.1"));
1934 
1935           case 1:
1936           case 2:
1937           case 3: format = 1;
1938                   break;
1939 
1940           case 4: format = 2;
1941                   break;
1942 
1943           case 5: format = 3;
1944                   break;
1945 
1946           case 6:
1947           case 7: format = 4;
1948                   break;
1949 
1950           case 8: format = 6;
1951                   break;
1952           case 9: format = 7;
1953                   break;
1954 
1955           default:format = SVN_FS_FS__FORMAT_NUMBER;
1956         }
1957 
1958       shard_size_str = svn_hash_gets(fs->config, SVN_FS_CONFIG_FSFS_SHARD_SIZE);
1959       if (shard_size_str)
1960         {
1961           apr_int64_t val;
1962           SVN_ERR(svn_cstring_strtoi64(&val, shard_size_str, 0,
1963                                        APR_INT32_MAX, 10));
1964 
1965           shard_size = (int) val;
1966         }
1967     }
1968 
1969   log_addressing = svn_hash__get_bool(fs->config,
1970                                       SVN_FS_CONFIG_FSFS_LOG_ADDRESSING,
1971                                       TRUE);
1972 
1973   /* Actual FS creation. */
1974   SVN_ERR(svn_fs_fs__create_file_tree(fs, path, format, shard_size,
1975                                       log_addressing, pool));
1976 
1977   /* This filesystem is ready.  Stamp it with a format number. */
1978   SVN_ERR(svn_fs_fs__write_format(fs, FALSE, pool));
1979 
1980   return SVN_NO_ERROR;
1981 }
1982 
1983 svn_error_t *
svn_fs_fs__set_uuid(svn_fs_t * fs,const char * uuid,const char * instance_id,apr_pool_t * pool)1984 svn_fs_fs__set_uuid(svn_fs_t *fs,
1985                     const char *uuid,
1986                     const char *instance_id,
1987                     apr_pool_t *pool)
1988 {
1989   fs_fs_data_t *ffd = fs->fsap_data;
1990   const char *uuid_path = path_uuid(fs, pool);
1991   svn_stringbuf_t *contents = svn_stringbuf_create_empty(pool);
1992 
1993   if (! uuid)
1994     uuid = svn_uuid_generate(pool);
1995 
1996   if (! instance_id)
1997     instance_id = svn_uuid_generate(pool);
1998 
1999   svn_stringbuf_appendcstr(contents, uuid);
2000   svn_stringbuf_appendcstr(contents, "\n");
2001 
2002   if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT)
2003     {
2004       svn_stringbuf_appendcstr(contents, instance_id);
2005       svn_stringbuf_appendcstr(contents, "\n");
2006     }
2007 
2008   /* We use the permissions of the 'current' file, because the 'uuid'
2009      file does not exist during repository creation. */
2010   SVN_ERR(svn_io_write_atomic2(uuid_path, contents->data, contents->len,
2011                                svn_fs_fs__path_current(fs, pool) /* perms */,
2012                                ffd->flush_to_disk, pool));
2013 
2014   fs->uuid = apr_pstrdup(fs->pool, uuid);
2015 
2016   if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT)
2017     ffd->instance_id = apr_pstrdup(fs->pool, instance_id);
2018   else
2019     ffd->instance_id = fs->uuid;
2020 
2021   return SVN_NO_ERROR;
2022 }
2023 
2024 /** Node origin lazy cache. */
2025 
2026 /* If directory PATH does not exist, create it and give it the same
2027    permissions as FS_path.*/
2028 svn_error_t *
svn_fs_fs__ensure_dir_exists(const char * path,const char * fs_path,apr_pool_t * pool)2029 svn_fs_fs__ensure_dir_exists(const char *path,
2030                              const char *fs_path,
2031                              apr_pool_t *pool)
2032 {
2033   svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
2034   if (err && APR_STATUS_IS_EEXIST(err->apr_err))
2035     {
2036       svn_error_clear(err);
2037       return SVN_NO_ERROR;
2038     }
2039   SVN_ERR(err);
2040 
2041   /* We successfully created a new directory.  Dup the permissions
2042      from FS->path. */
2043   return svn_io_copy_perms(fs_path, path, pool);
2044 }
2045 
2046 /* Set *NODE_ORIGINS to a hash mapping 'const char *' node IDs to
2047    'svn_string_t *' node revision IDs.  Use POOL for allocations. */
2048 static svn_error_t *
get_node_origins_from_file(svn_fs_t * fs,apr_hash_t ** node_origins,const char * node_origins_file,apr_pool_t * pool)2049 get_node_origins_from_file(svn_fs_t *fs,
2050                            apr_hash_t **node_origins,
2051                            const char *node_origins_file,
2052                            apr_pool_t *pool)
2053 {
2054   apr_file_t *fd;
2055   svn_error_t *err;
2056   svn_stream_t *stream;
2057 
2058   *node_origins = NULL;
2059   err = svn_io_file_open(&fd, node_origins_file,
2060                          APR_READ, APR_OS_DEFAULT, pool);
2061   if (err && APR_STATUS_IS_ENOENT(err->apr_err))
2062     {
2063       svn_error_clear(err);
2064       return SVN_NO_ERROR;
2065     }
2066   SVN_ERR(err);
2067 
2068   stream = svn_stream_from_aprfile2(fd, FALSE, pool);
2069   *node_origins = apr_hash_make(pool);
2070   err = svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool);
2071   if (err)
2072     return svn_error_quick_wrapf(err, _("malformed node origin data in '%s'"),
2073                                  node_origins_file);
2074   return svn_stream_close(stream);
2075 }
2076 
2077 svn_error_t *
svn_fs_fs__get_node_origin(const svn_fs_id_t ** origin_id,svn_fs_t * fs,const svn_fs_fs__id_part_t * node_id,apr_pool_t * pool)2078 svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
2079                            svn_fs_t *fs,
2080                            const svn_fs_fs__id_part_t *node_id,
2081                            apr_pool_t *pool)
2082 {
2083   apr_hash_t *node_origins;
2084 
2085   *origin_id = NULL;
2086   SVN_ERR(get_node_origins_from_file(fs, &node_origins,
2087                                      svn_fs_fs__path_node_origin(fs, node_id,
2088                                                                  pool),
2089                                      pool));
2090   if (node_origins)
2091     {
2092       char node_id_ptr[SVN_INT64_BUFFER_SIZE];
2093       apr_size_t len = svn__ui64tobase36(node_id_ptr, node_id->number);
2094       svn_string_t *origin_id_str
2095         = apr_hash_get(node_origins, node_id_ptr, len);
2096 
2097       if (origin_id_str)
2098         SVN_ERR(svn_fs_fs__id_parse(origin_id,
2099                                     apr_pstrdup(pool, origin_id_str->data),
2100                                     pool));
2101     }
2102   return SVN_NO_ERROR;
2103 }
2104 
2105 
2106 /* Helper for svn_fs_fs__set_node_origin.  Takes a NODE_ID/NODE_REV_ID
2107    pair and adds it to the NODE_ORIGINS_PATH file.  */
2108 static svn_error_t *
set_node_origins_for_file(svn_fs_t * fs,const char * node_origins_path,const svn_fs_fs__id_part_t * node_id,svn_string_t * node_rev_id,apr_pool_t * pool)2109 set_node_origins_for_file(svn_fs_t *fs,
2110                           const char *node_origins_path,
2111                           const svn_fs_fs__id_part_t *node_id,
2112                           svn_string_t *node_rev_id,
2113                           apr_pool_t *pool)
2114 {
2115   const char *path_tmp;
2116   svn_stream_t *stream;
2117   apr_hash_t *origins_hash;
2118   svn_string_t *old_node_rev_id;
2119 
2120   /* the hash serialization functions require strings as keys */
2121   char node_id_ptr[SVN_INT64_BUFFER_SIZE];
2122   apr_size_t len = svn__ui64tobase36(node_id_ptr, node_id->number);
2123 
2124   SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs->path,
2125                                                        PATH_NODE_ORIGINS_DIR,
2126                                                        pool),
2127                                        fs->path, pool));
2128 
2129   /* Read the previously existing origins (if any), and merge our
2130      update with it. */
2131   SVN_ERR(get_node_origins_from_file(fs, &origins_hash,
2132                                      node_origins_path, pool));
2133   if (! origins_hash)
2134     origins_hash = apr_hash_make(pool);
2135 
2136   old_node_rev_id = apr_hash_get(origins_hash, node_id_ptr, len);
2137 
2138   if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id))
2139     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2140                              _("Node origin for '%s' exists with a different "
2141                                "value (%s) than what we were about to store "
2142                                "(%s)"),
2143                              node_id_ptr, old_node_rev_id->data,
2144                              node_rev_id->data);
2145 
2146   apr_hash_set(origins_hash, node_id_ptr, len, node_rev_id);
2147 
2148   /* Sure, there's a race condition here.  Two processes could be
2149      trying to add different cache elements to the same file at the
2150      same time, and the entries added by the first one to write will
2151      be lost.  But this is just a cache of reconstructible data, so
2152      we'll accept this problem in return for not having to deal with
2153      locking overhead. */
2154 
2155   /* Create a temporary file, write out our hash, and close the file. */
2156   SVN_ERR(svn_stream_open_unique(&stream, &path_tmp,
2157                                  svn_dirent_dirname(node_origins_path, pool),
2158                                  svn_io_file_del_none, pool, pool));
2159   SVN_ERR(svn_hash_write2(origins_hash, stream, SVN_HASH_TERMINATOR, pool));
2160   SVN_ERR(svn_stream_close(stream));
2161 
2162   /* Rename the temp file as the real destination */
2163   return svn_io_file_rename2(path_tmp, node_origins_path, FALSE, pool);
2164 }
2165 
2166 
2167 svn_error_t *
svn_fs_fs__set_node_origin(svn_fs_t * fs,const svn_fs_fs__id_part_t * node_id,const svn_fs_id_t * node_rev_id,apr_pool_t * pool)2168 svn_fs_fs__set_node_origin(svn_fs_t *fs,
2169                            const svn_fs_fs__id_part_t *node_id,
2170                            const svn_fs_id_t *node_rev_id,
2171                            apr_pool_t *pool)
2172 {
2173   svn_error_t *err;
2174   const char *filename = svn_fs_fs__path_node_origin(fs, node_id, pool);
2175 
2176   err = set_node_origins_for_file(fs, filename,
2177                                   node_id,
2178                                   svn_fs_fs__id_unparse(node_rev_id, pool),
2179                                   pool);
2180   if (err && APR_STATUS_IS_EACCES(err->apr_err))
2181     {
2182       /* It's just a cache; stop trying if I can't write. */
2183       svn_error_clear(err);
2184       err = NULL;
2185     }
2186   return svn_error_trace(err);
2187 }
2188 
2189 
2190 
2191 /*** Revisions ***/
2192 
2193 svn_error_t *
svn_fs_fs__revision_prop(svn_string_t ** value_p,svn_fs_t * fs,svn_revnum_t rev,const char * propname,svn_boolean_t refresh,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2194 svn_fs_fs__revision_prop(svn_string_t **value_p,
2195                          svn_fs_t *fs,
2196                          svn_revnum_t rev,
2197                          const char *propname,
2198                          svn_boolean_t refresh,
2199                          apr_pool_t *result_pool,
2200                          apr_pool_t *scratch_pool)
2201 {
2202   apr_hash_t *table;
2203 
2204   SVN_ERR(svn_fs__check_fs(fs, TRUE));
2205   SVN_ERR(svn_fs_fs__get_revision_proplist(&table, fs, rev, refresh,
2206                                            scratch_pool, scratch_pool));
2207 
2208   *value_p = svn_string_dup(svn_hash_gets(table, propname), result_pool);
2209 
2210   return SVN_NO_ERROR;
2211 }
2212 
2213 
2214 /* Baton used for change_rev_prop_body below. */
2215 struct change_rev_prop_baton {
2216   svn_fs_t *fs;
2217   svn_revnum_t rev;
2218   const char *name;
2219   const svn_string_t *const *old_value_p;
2220   const svn_string_t *value;
2221 };
2222 
2223 /* The work-horse for svn_fs_fs__change_rev_prop, called with the FS
2224    write lock.  This implements the svn_fs_fs__with_write_lock()
2225    'body' callback type.  BATON is a 'struct change_rev_prop_baton *'. */
2226 static svn_error_t *
change_rev_prop_body(void * baton,apr_pool_t * pool)2227 change_rev_prop_body(void *baton, apr_pool_t *pool)
2228 {
2229   struct change_rev_prop_baton *cb = baton;
2230   apr_hash_t *table;
2231   const svn_string_t *present_value;
2232 
2233   /* We always need to read the current revprops from disk.
2234    * Hence, always "refresh" here. */
2235   SVN_ERR(svn_fs_fs__get_revision_proplist(&table, cb->fs, cb->rev, TRUE,
2236                                            pool, pool));
2237   present_value = svn_hash_gets(table, cb->name);
2238 
2239   if (cb->old_value_p)
2240     {
2241       const svn_string_t *wanted_value = *cb->old_value_p;
2242       if ((!wanted_value != !present_value)
2243           || (wanted_value && present_value
2244               && !svn_string_compare(wanted_value, present_value)))
2245         {
2246           /* What we expected isn't what we found. */
2247           return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL,
2248                                    _("revprop '%s' has unexpected value in "
2249                                      "filesystem"),
2250                                    cb->name);
2251         }
2252       /* Fall through. */
2253     }
2254 
2255   /* If the prop-set is a no-op, skip the actual write. */
2256   if ((!present_value && !cb->value)
2257       || (present_value && cb->value
2258           && svn_string_compare(present_value, cb->value)))
2259     return SVN_NO_ERROR;
2260 
2261   svn_hash_sets(table, cb->name, cb->value);
2262 
2263   return svn_fs_fs__set_revision_proplist(cb->fs, cb->rev, table, pool);
2264 }
2265 
2266 svn_error_t *
svn_fs_fs__change_rev_prop(svn_fs_t * fs,svn_revnum_t rev,const char * name,const svn_string_t * const * old_value_p,const svn_string_t * value,apr_pool_t * pool)2267 svn_fs_fs__change_rev_prop(svn_fs_t *fs,
2268                            svn_revnum_t rev,
2269                            const char *name,
2270                            const svn_string_t *const *old_value_p,
2271                            const svn_string_t *value,
2272                            apr_pool_t *pool)
2273 {
2274   struct change_rev_prop_baton cb;
2275 
2276   SVN_ERR(svn_fs__check_fs(fs, TRUE));
2277 
2278   cb.fs = fs;
2279   cb.rev = rev;
2280   cb.name = name;
2281   cb.old_value_p = old_value_p;
2282   cb.value = value;
2283 
2284   return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool);
2285 }
2286 
2287 
2288 svn_error_t *
svn_fs_fs__info_format(int * fs_format,svn_version_t ** supports_version,svn_fs_t * fs,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2289 svn_fs_fs__info_format(int *fs_format,
2290                        svn_version_t **supports_version,
2291                        svn_fs_t *fs,
2292                        apr_pool_t *result_pool,
2293                        apr_pool_t *scratch_pool)
2294 {
2295   fs_fs_data_t *ffd = fs->fsap_data;
2296   *fs_format = ffd->format;
2297   *supports_version = apr_palloc(result_pool, sizeof(svn_version_t));
2298 
2299   (*supports_version)->major = SVN_VER_MAJOR;
2300   (*supports_version)->minor = 1;
2301   (*supports_version)->patch = 0;
2302   (*supports_version)->tag = "";
2303 
2304   switch (ffd->format)
2305     {
2306     case 1:
2307       break;
2308     case 2:
2309       (*supports_version)->minor = 4;
2310       break;
2311     case 3:
2312       (*supports_version)->minor = 5;
2313       break;
2314     case 4:
2315       (*supports_version)->minor = 6;
2316       break;
2317     case 6:
2318       (*supports_version)->minor = 8;
2319       break;
2320     case 7:
2321       (*supports_version)->minor = 9;
2322       break;
2323     case 8:
2324       (*supports_version)->minor = 10;
2325       break;
2326 #ifdef SVN_DEBUG
2327 # if SVN_FS_FS__FORMAT_NUMBER != 8
2328 #  error "Need to add a 'case' statement here"
2329 # endif
2330 #endif
2331     }
2332 
2333   return SVN_NO_ERROR;
2334 }
2335 
2336 svn_error_t *
svn_fs_fs__info_config_files(apr_array_header_t ** files,svn_fs_t * fs,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2337 svn_fs_fs__info_config_files(apr_array_header_t **files,
2338                              svn_fs_t *fs,
2339                              apr_pool_t *result_pool,
2340                              apr_pool_t *scratch_pool)
2341 {
2342   *files = apr_array_make(result_pool, 1, sizeof(const char *));
2343   APR_ARRAY_PUSH(*files, const char *) = svn_dirent_join(fs->path, PATH_CONFIG,
2344                                                          result_pool);
2345   return SVN_NO_ERROR;
2346 }
2347 
2348 static svn_error_t *
ensure_representation_sha1(svn_fs_t * fs,representation_t * rep,apr_pool_t * pool)2349 ensure_representation_sha1(svn_fs_t *fs,
2350                            representation_t *rep,
2351                            apr_pool_t *pool)
2352 {
2353   if (!rep->has_sha1)
2354     {
2355       svn_stream_t *contents;
2356       svn_checksum_t *checksum;
2357 
2358       SVN_ERR(svn_fs_fs__get_contents(&contents, fs, rep, FALSE, pool));
2359       SVN_ERR(svn_stream_contents_checksum(&checksum, contents,
2360                                            svn_checksum_sha1, pool, pool));
2361 
2362       memcpy(rep->sha1_digest, checksum->digest, APR_SHA1_DIGESTSIZE);
2363       rep->has_sha1 = TRUE;
2364     }
2365 
2366   return SVN_NO_ERROR;
2367 }
2368 
2369 static svn_error_t *
reindex_node(svn_fs_t * fs,const svn_fs_id_t * id,svn_revnum_t rev,svn_fs_fs__revision_file_t * rev_file,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * pool)2370 reindex_node(svn_fs_t *fs,
2371              const svn_fs_id_t *id,
2372              svn_revnum_t rev,
2373              svn_fs_fs__revision_file_t *rev_file,
2374              svn_cancel_func_t cancel_func,
2375              void *cancel_baton,
2376              apr_pool_t *pool)
2377 {
2378   node_revision_t *noderev;
2379   apr_off_t offset;
2380 
2381   if (svn_fs_fs__id_rev(id) != rev)
2382     {
2383       return SVN_NO_ERROR;
2384     }
2385 
2386   if (cancel_func)
2387     SVN_ERR(cancel_func(cancel_baton));
2388 
2389   SVN_ERR(svn_fs_fs__item_offset(&offset, fs, rev_file, rev, NULL,
2390                                  svn_fs_fs__id_item(id), pool));
2391 
2392   SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &offset, pool));
2393   SVN_ERR(svn_fs_fs__read_noderev(&noderev, rev_file->stream,
2394                                   pool, pool));
2395 
2396   /* Make sure EXPANDED_SIZE has the correct value for every rep. */
2397   SVN_ERR(svn_fs_fs__fixup_expanded_size(fs, noderev->data_rep, pool));
2398   SVN_ERR(svn_fs_fs__fixup_expanded_size(fs, noderev->prop_rep, pool));
2399 
2400   /* First reindex sub-directory to match write_final_rev() behavior. */
2401   if (noderev->kind == svn_node_dir)
2402     {
2403       apr_array_header_t *entries;
2404 
2405       SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool, pool));
2406 
2407       if (entries->nelts > 0)
2408         {
2409           int i;
2410           apr_pool_t *iterpool;
2411 
2412           iterpool = svn_pool_create(pool);
2413           for (i = 0; i < entries->nelts; i++)
2414             {
2415               const svn_fs_dirent_t *dirent;
2416 
2417               svn_pool_clear(iterpool);
2418 
2419               dirent = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
2420 
2421               SVN_ERR(reindex_node(fs, dirent->id, rev, rev_file,
2422                                    cancel_func, cancel_baton, iterpool));
2423             }
2424           svn_pool_destroy(iterpool);
2425         }
2426     }
2427 
2428   if (noderev->data_rep && noderev->data_rep->revision == rev &&
2429       noderev->kind == svn_node_file)
2430     {
2431       SVN_ERR(ensure_representation_sha1(fs, noderev->data_rep, pool));
2432       SVN_ERR(svn_fs_fs__set_rep_reference(fs, noderev->data_rep, pool));
2433     }
2434 
2435   if (noderev->prop_rep && noderev->prop_rep->revision == rev)
2436     {
2437       SVN_ERR(ensure_representation_sha1(fs, noderev->prop_rep, pool));
2438       SVN_ERR(svn_fs_fs__set_rep_reference(fs, noderev->prop_rep, pool));
2439     }
2440 
2441   return SVN_NO_ERROR;
2442 }
2443 
2444 svn_error_t *
svn_fs_fs__build_rep_cache(svn_fs_t * fs,svn_revnum_t start_rev,svn_revnum_t end_rev,svn_fs_progress_notify_func_t progress_func,void * progress_baton,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * pool)2445 svn_fs_fs__build_rep_cache(svn_fs_t *fs,
2446                            svn_revnum_t start_rev,
2447                            svn_revnum_t end_rev,
2448                            svn_fs_progress_notify_func_t progress_func,
2449                            void *progress_baton,
2450                            svn_cancel_func_t cancel_func,
2451                            void *cancel_baton,
2452                            apr_pool_t *pool)
2453 {
2454   fs_fs_data_t *ffd = fs->fsap_data;
2455   apr_pool_t *iterpool;
2456   svn_revnum_t rev;
2457 
2458   if (ffd->format < SVN_FS_FS__MIN_REP_SHARING_FORMAT)
2459     {
2460       return svn_error_createf(SVN_ERR_FS_REP_SHARING_NOT_SUPPORTED, NULL,
2461                                _("FSFS format (%d) too old for rep-sharing; "
2462                                  "please upgrade the filesystem."),
2463                                ffd->format);
2464     }
2465 
2466   if (!ffd->rep_sharing_allowed)
2467     {
2468       return svn_error_create(SVN_ERR_FS_REP_SHARING_NOT_ALLOWED, NULL,
2469                               _("Filesystem does not allow rep-sharing."));
2470     }
2471 
2472   /* Do not build rep-cache for revision zero to match
2473    * svn_fs_fs__create() behavior. */
2474   if (start_rev == SVN_INVALID_REVNUM)
2475     start_rev = 1;
2476 
2477   if (end_rev == SVN_INVALID_REVNUM)
2478     SVN_ERR(svn_fs_fs__youngest_rev(&end_rev, fs, pool));
2479 
2480   /* Do nothing for empty FS. */
2481   if (start_rev > end_rev)
2482     {
2483       return SVN_NO_ERROR;
2484     }
2485 
2486   if (!ffd->rep_cache_db)
2487     SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
2488 
2489   iterpool = svn_pool_create(pool);
2490   for (rev = start_rev; rev <= end_rev; rev++)
2491     {
2492       svn_fs_id_t *root_id;
2493       svn_fs_fs__revision_file_t *file;
2494       svn_error_t *err;
2495 
2496       svn_pool_clear(iterpool);
2497 
2498       if (progress_func)
2499         progress_func(rev, progress_baton, iterpool);
2500 
2501       SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&file, fs, rev,
2502                                                iterpool, iterpool));
2503       SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, iterpool, iterpool));
2504 
2505       SVN_ERR(svn_sqlite__begin_transaction(ffd->rep_cache_db));
2506       err = reindex_node(fs, root_id, rev, file, cancel_func, cancel_baton, iterpool);
2507       SVN_ERR(svn_sqlite__finish_transaction(ffd->rep_cache_db, err));
2508 
2509       SVN_ERR(svn_fs_fs__close_revision_file(file));
2510     }
2511 
2512   svn_pool_destroy(iterpool);
2513 
2514   return SVN_NO_ERROR;
2515 }
2516