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