fs_fs.c revision 362181
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
78To avoid opening and closing the rev-files all the time, it would
79probably be advantageous to keep each rev-file open for the
80lifetime of the transaction object.  I'll leave that as a later
81optimization for now.
82
83I didn't keep track of pool lifetimes at all in this code.  There
84are likely some errors because of that.
85
86*/
87
88/* Declarations. */
89
90static svn_error_t *
91get_youngest(svn_revnum_t *youngest_p, svn_fs_t *fs, apr_pool_t *pool);
92
93/* Pathname helper functions */
94
95static const char *
96path_format(svn_fs_t *fs, apr_pool_t *pool)
97{
98  return svn_dirent_join(fs->path, PATH_FORMAT, pool);
99}
100
101static APR_INLINE const char *
102path_uuid(svn_fs_t *fs, apr_pool_t *pool)
103{
104  return svn_dirent_join(fs->path, PATH_UUID, pool);
105}
106
107const char *
108svn_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. */
116static svn_error_t *
117get_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. */
126static apr_status_t
127reset_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. */
148typedef 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. */
190static svn_error_t *
191with_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. */
235static svn_error_t *
236with_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. */
246typedef 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. */
256static void
257init_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 */
291static with_lock_baton_t *
292create_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 */
328static with_lock_baton_t *
329chain_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
359svn_error_t *
360svn_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
371svn_error_t *
372svn_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
383svn_error_t *
384svn_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
395svn_error_t *
396svn_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. */
427static svn_error_t *
428check_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. */
438static svn_error_t *
439check_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. */
470static svn_error_t *
471read_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. */
586svn_error_t *
587svn_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
636svn_boolean_t
637svn_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 */
648static svn_error_t *
649verify_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
687static svn_error_t *
688parse_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 */
744static svn_error_t *
745read_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
952static svn_error_t *
953write_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. */
1166static svn_error_t *
1167read_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. */
1188static svn_error_t *
1189read_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
1224svn_error_t *
1225svn_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
1243svn_error_t *
1244svn_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. */
1271static svn_error_t *
1272create_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. */
1287struct 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
1296static svn_error_t *
1297upgrade_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
1414svn_error_t *
1415svn_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. */
1435static svn_error_t *
1436get_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
1446svn_error_t *
1447svn_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
1459int
1460svn_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
1467svn_error_t *
1468svn_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
1483svn_error_t *
1484svn_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
1510svn_error_t *
1511svn_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
1529svn_boolean_t
1530svn_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
1548svn_error_t *
1549svn_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
1607svn_error_t *
1608svn_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
1668svn_error_t *
1669svn_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
1704representation_t *
1705svn_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. */
1717static svn_error_t *
1718write_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
1813svn_error_t *
1814svn_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
1912svn_error_t *
1913svn_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
1983svn_error_t *
1984svn_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.*/
2028svn_error_t *
2029svn_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. */
2048static svn_error_t *
2049get_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
2077svn_error_t *
2078svn_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.  */
2108static svn_error_t *
2109set_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
2167svn_error_t *
2168svn_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
2193svn_error_t *
2194svn_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. */
2215struct 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 *'. */
2226static svn_error_t *
2227change_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
2266svn_error_t *
2267svn_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
2288svn_error_t *
2289svn_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
2336svn_error_t *
2337svn_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
2348static svn_error_t *
2349ensure_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
2369static svn_error_t *
2370reindex_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
2444svn_error_t *
2445svn_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