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
77To avoid opening and closing the rev-files all the time, it would
78probably be advantageous to keep each rev-file open for the
79lifetime of the transaction object.  I'll leave that as a later
80optimization for now.
81
82I didn't keep track of pool lifetimes at all in this code.  There
83are likely some errors because of that.
84
85*/
86
87/* Declarations. */
88
89static svn_error_t *
90get_youngest(svn_revnum_t *youngest_p, svn_fs_t *fs, apr_pool_t *pool);
91
92/* Pathname helper functions */
93
94static const char *
95path_format(svn_fs_t *fs, apr_pool_t *pool)
96{
97  return svn_dirent_join(fs->path, PATH_FORMAT, pool);
98}
99
100static APR_INLINE const char *
101path_uuid(svn_fs_t *fs, apr_pool_t *pool)
102{
103  return svn_dirent_join(fs->path, PATH_UUID, pool);
104}
105
106const char *
107svn_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
114/* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
115static svn_error_t *
116get_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. */
125static apr_status_t
126reset_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. */
147typedef 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. */
189static svn_error_t *
190with_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. */
234static svn_error_t *
235with_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. */
245typedef 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. */
255static void
256init_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 */
290static with_lock_baton_t *
291create_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 */
327static with_lock_baton_t *
328chain_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
358svn_error_t *
359svn_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
370svn_error_t *
371svn_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
382svn_error_t *
383svn_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
394svn_error_t *
395svn_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
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. */
426static svn_error_t *
427check_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. */
437static svn_error_t *
438check_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. */
469static svn_error_t *
470read_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. */
585svn_error_t *
586svn_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_atomic(path, sb->data, sb->len,
627                                  NULL /* copy_perms_path */, pool));
628    }
629
630  /* And set the perms to make it read only */
631  return svn_io_set_file_read_only(path, FALSE, pool);
632}
633
634svn_boolean_t
635svn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs)
636{
637  fs_fs_data_t *ffd = fs->fsap_data;
638  return ffd->format >= SVN_FS_FS__MIN_MERGEINFO_FORMAT;
639}
640
641/* Check that BLOCK_SIZE is a valid block / page size, i.e. it is within
642 * the range of what the current system may address in RAM and it is a
643 * power of 2.  Assume that the element size within the block is ITEM_SIZE.
644 * Use SCRATCH_POOL for temporary allocations.
645 */
646static svn_error_t *
647verify_block_size(apr_int64_t block_size,
648                  apr_size_t item_size,
649                  const char *name,
650                  apr_pool_t *scratch_pool
651                 )
652{
653  /* Limit range. */
654  if (block_size <= 0)
655    return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
656                             _("%s is too small for fsfs.conf setting '%s'."),
657                             apr_psprintf(scratch_pool,
658                                          "%" APR_INT64_T_FMT,
659                                          block_size),
660                             name);
661
662  if (block_size > SVN_MAX_OBJECT_SIZE / item_size)
663    return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
664                             _("%s is too large for fsfs.conf setting '%s'."),
665                             apr_psprintf(scratch_pool,
666                                          "%" APR_INT64_T_FMT,
667                                          block_size),
668                             name);
669
670  /* Ensure it is a power of two.
671   * For positive X,  X & (X-1) will reset the lowest bit set.
672   * If the result is 0, at most one bit has been set. */
673  if (0 != (block_size & (block_size - 1)))
674    return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
675                             _("%s is invalid for fsfs.conf setting '%s' "
676                               "because it is not a power of 2."),
677                             apr_psprintf(scratch_pool,
678                                          "%" APR_INT64_T_FMT,
679                                          block_size),
680                             name);
681
682  return SVN_NO_ERROR;
683}
684
685/* Read the configuration information of the file system at FS_PATH
686 * and set the respective values in FFD.  Use pools as usual.
687 */
688static svn_error_t *
689read_config(fs_fs_data_t *ffd,
690            const char *fs_path,
691            apr_pool_t *result_pool,
692            apr_pool_t *scratch_pool)
693{
694  svn_config_t *config;
695
696  SVN_ERR(svn_config_read3(&config,
697                           svn_dirent_join(fs_path, PATH_CONFIG, scratch_pool),
698                           FALSE, FALSE, FALSE, scratch_pool));
699
700  /* Initialize ffd->rep_sharing_allowed. */
701  if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
702    SVN_ERR(svn_config_get_bool(config, &ffd->rep_sharing_allowed,
703                                CONFIG_SECTION_REP_SHARING,
704                                CONFIG_OPTION_ENABLE_REP_SHARING, TRUE));
705  else
706    ffd->rep_sharing_allowed = FALSE;
707
708  /* Initialize deltification settings in ffd. */
709  if (ffd->format >= SVN_FS_FS__MIN_DELTIFICATION_FORMAT)
710    {
711      apr_int64_t compression_level;
712
713      SVN_ERR(svn_config_get_bool(config, &ffd->deltify_directories,
714                                  CONFIG_SECTION_DELTIFICATION,
715                                  CONFIG_OPTION_ENABLE_DIR_DELTIFICATION,
716                                  TRUE));
717      SVN_ERR(svn_config_get_bool(config, &ffd->deltify_properties,
718                                  CONFIG_SECTION_DELTIFICATION,
719                                  CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION,
720                                  TRUE));
721      SVN_ERR(svn_config_get_int64(config, &ffd->max_deltification_walk,
722                                   CONFIG_SECTION_DELTIFICATION,
723                                   CONFIG_OPTION_MAX_DELTIFICATION_WALK,
724                                   SVN_FS_FS_MAX_DELTIFICATION_WALK));
725      SVN_ERR(svn_config_get_int64(config, &ffd->max_linear_deltification,
726                                   CONFIG_SECTION_DELTIFICATION,
727                                   CONFIG_OPTION_MAX_LINEAR_DELTIFICATION,
728                                   SVN_FS_FS_MAX_LINEAR_DELTIFICATION));
729
730      SVN_ERR(svn_config_get_int64(config, &compression_level,
731                                   CONFIG_SECTION_DELTIFICATION,
732                                   CONFIG_OPTION_COMPRESSION_LEVEL,
733                                   SVN_DELTA_COMPRESSION_LEVEL_DEFAULT));
734      ffd->delta_compression_level
735        = (int)MIN(MAX(SVN_DELTA_COMPRESSION_LEVEL_NONE, compression_level),
736                   SVN_DELTA_COMPRESSION_LEVEL_MAX);
737    }
738  else
739    {
740      ffd->deltify_directories = FALSE;
741      ffd->deltify_properties = FALSE;
742      ffd->max_deltification_walk = SVN_FS_FS_MAX_DELTIFICATION_WALK;
743      ffd->max_linear_deltification = SVN_FS_FS_MAX_LINEAR_DELTIFICATION;
744      ffd->delta_compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
745    }
746
747  /* Initialize revprop packing settings in ffd. */
748  if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
749    {
750      SVN_ERR(svn_config_get_bool(config, &ffd->compress_packed_revprops,
751                                  CONFIG_SECTION_PACKED_REVPROPS,
752                                  CONFIG_OPTION_COMPRESS_PACKED_REVPROPS,
753                                  FALSE));
754      SVN_ERR(svn_config_get_int64(config, &ffd->revprop_pack_size,
755                                   CONFIG_SECTION_PACKED_REVPROPS,
756                                   CONFIG_OPTION_REVPROP_PACK_SIZE,
757                                   ffd->compress_packed_revprops
758                                       ? 0x10
759                                       : 0x4));
760
761      ffd->revprop_pack_size *= 1024;
762    }
763  else
764    {
765      ffd->revprop_pack_size = 0x10000;
766      ffd->compress_packed_revprops = FALSE;
767    }
768
769  if (ffd->format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT)
770    {
771      SVN_ERR(svn_config_get_int64(config, &ffd->block_size,
772                                   CONFIG_SECTION_IO,
773                                   CONFIG_OPTION_BLOCK_SIZE,
774                                   64));
775      SVN_ERR(svn_config_get_int64(config, &ffd->l2p_page_size,
776                                   CONFIG_SECTION_IO,
777                                   CONFIG_OPTION_L2P_PAGE_SIZE,
778                                   0x2000));
779      SVN_ERR(svn_config_get_int64(config, &ffd->p2l_page_size,
780                                   CONFIG_SECTION_IO,
781                                   CONFIG_OPTION_P2L_PAGE_SIZE,
782                                   0x400));
783
784      /* Don't accept unreasonable or illegal values.
785       * Block size and P2L page size are in kbytes;
786       * L2P blocks are arrays of apr_off_t. */
787      SVN_ERR(verify_block_size(ffd->block_size, 0x400,
788                                CONFIG_OPTION_BLOCK_SIZE, scratch_pool));
789      SVN_ERR(verify_block_size(ffd->p2l_page_size, 0x400,
790                                CONFIG_OPTION_P2L_PAGE_SIZE, scratch_pool));
791      SVN_ERR(verify_block_size(ffd->l2p_page_size, sizeof(apr_off_t),
792                                CONFIG_OPTION_L2P_PAGE_SIZE, scratch_pool));
793
794      /* convert kBytes to bytes */
795      ffd->block_size *= 0x400;
796      ffd->p2l_page_size *= 0x400;
797      /* L2P pages are in entries - not in (k)Bytes */
798    }
799  else
800    {
801      /* should be irrelevant but we initialize them anyway */
802      ffd->block_size = 0x1000; /* Matches default APR file buffer size. */
803      ffd->l2p_page_size = 0x2000;    /* Matches above default. */
804      ffd->p2l_page_size = 0x100000;  /* Matches above default in bytes. */
805    }
806
807  if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
808    {
809      SVN_ERR(svn_config_get_bool(config, &ffd->pack_after_commit,
810                                  CONFIG_SECTION_DEBUG,
811                                  CONFIG_OPTION_PACK_AFTER_COMMIT,
812                                  FALSE));
813    }
814  else
815    {
816      ffd->pack_after_commit = FALSE;
817    }
818
819  /* memcached configuration */
820  SVN_ERR(svn_cache__make_memcache_from_config(&ffd->memcache, config,
821                                               result_pool, scratch_pool));
822
823  SVN_ERR(svn_config_get_bool(config, &ffd->fail_stop,
824                              CONFIG_SECTION_CACHES, CONFIG_OPTION_FAIL_STOP,
825                              FALSE));
826
827  return SVN_NO_ERROR;
828}
829
830static svn_error_t *
831write_config(svn_fs_t *fs,
832             apr_pool_t *pool)
833{
834#define NL APR_EOL_STR
835  static const char * const fsfs_conf_contents =
836"### This file controls the configuration of the FSFS filesystem."           NL
837""                                                                           NL
838"[" SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "]"                          NL
839"### These options name memcached servers used to cache internal FSFS"       NL
840"### data.  See http://www.danga.com/memcached/ for more information on"     NL
841"### memcached.  To use memcached with FSFS, run one or more memcached"      NL
842"### servers, and specify each of them as an option like so:"                NL
843"# first-server = 127.0.0.1:11211"                                           NL
844"# remote-memcached = mymemcached.corp.example.com:11212"                    NL
845"### The option name is ignored; the value is of the form HOST:PORT."        NL
846"### memcached servers can be shared between multiple repositories;"         NL
847"### however, if you do this, you *must* ensure that repositories have"      NL
848"### distinct UUIDs and paths, or else cached data from one repository"      NL
849"### might be used by another accidentally.  Note also that memcached has"   NL
850"### no authentication for reads or writes, so you must ensure that your"    NL
851"### memcached servers are only accessible by trusted users."                NL
852""                                                                           NL
853"[" CONFIG_SECTION_CACHES "]"                                                NL
854"### When a cache-related error occurs, normally Subversion ignores it"      NL
855"### and continues, logging an error if the server is appropriately"         NL
856"### configured (and ignoring it with file:// access).  To make"             NL
857"### Subversion never ignore cache errors, uncomment this line."             NL
858"# " CONFIG_OPTION_FAIL_STOP " = true"                                       NL
859""                                                                           NL
860"[" CONFIG_SECTION_REP_SHARING "]"                                           NL
861"### To conserve space, the filesystem can optionally avoid storing"         NL
862"### duplicate representations.  This comes at a slight cost in"             NL
863"### performance, as maintaining a database of shared representations can"   NL
864"### increase commit times.  The space savings are dependent upon the size"  NL
865"### of the repository, the number of objects it contains and the amount of" NL
866"### duplication between them, usually a function of the branching and"      NL
867"### merging process."                                                       NL
868"###"                                                                        NL
869"### The following parameter enables rep-sharing in the repository.  It can" NL
870"### be switched on and off at will, but for best space-saving results"      NL
871"### should be enabled consistently over the life of the repository."        NL
872"### 'svnadmin verify' will check the rep-cache regardless of this setting." NL
873"### rep-sharing is enabled by default."                                     NL
874"# " CONFIG_OPTION_ENABLE_REP_SHARING " = true"                              NL
875""                                                                           NL
876"[" CONFIG_SECTION_DELTIFICATION "]"                                         NL
877"### To conserve space, the filesystem stores data as differences against"   NL
878"### existing representations.  This comes at a slight cost in performance," NL
879"### as calculating differences can increase commit times.  Reading data"    NL
880"### will also create higher CPU load and the data will be fragmented."      NL
881"### Since deltification tends to save significant amounts of disk space,"   NL
882"### the overall I/O load can actually be lower."                            NL
883"###"                                                                        NL
884"### The options in this section allow for tuning the deltification"         NL
885"### strategy.  Their effects on data size and server performance may vary"  NL
886"### from one repository to another.  Versions prior to 1.8 will ignore"     NL
887"### this section."                                                          NL
888"###"                                                                        NL
889"### The following parameter enables deltification for directories. It can"  NL
890"### be switched on and off at will, but for best space-saving results"      NL
891"### should be enabled consistently over the lifetime of the repository."    NL
892"### Repositories containing large directories will benefit greatly."        NL
893"### In rarely accessed repositories, the I/O overhead may be significant"   NL
894"### as caches will most likely be low."                                     NL
895"### directory deltification is enabled by default."                         NL
896"# " CONFIG_OPTION_ENABLE_DIR_DELTIFICATION " = true"                        NL
897"###"                                                                        NL
898"### The following parameter enables deltification for properties on files"  NL
899"### and directories.  Overall, this is a minor tuning option but can save"  NL
900"### some disk space if you merge frequently or frequently change node"      NL
901"### properties.  You should not activate this if rep-sharing has been"      NL
902"### disabled because this may result in a net increase in repository size." NL
903"### property deltification is enabled by default."                          NL
904"# " CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION " = true"                      NL
905"###"                                                                        NL
906"### During commit, the server may need to walk the whole change history of" NL
907"### of a given node to find a suitable deltification base.  This linear"    NL
908"### process can impact commit times, svnadmin load and similar operations." NL
909"### This setting limits the depth of the deltification history.  If the"    NL
910"### threshold has been reached, the node will be stored as fulltext and a"  NL
911"### new deltification history begins."                                      NL
912"### Note, this is unrelated to svn log."                                    NL
913"### Very large values rarely provide significant additional savings but"    NL
914"### can impact performance greatly - in particular if directory"            NL
915"### deltification has been activated.  Very small values may be useful in"  NL
916"### repositories that are dominated by large, changing binaries."           NL
917"### Should be a power of two minus 1.  A value of 0 will effectively"       NL
918"### disable deltification."                                                 NL
919"### For 1.8, the default value is 1023; earlier versions have no limit."    NL
920"# " CONFIG_OPTION_MAX_DELTIFICATION_WALK " = 1023"                          NL
921"###"                                                                        NL
922"### The skip-delta scheme used by FSFS tends to repeatably store redundant" NL
923"### delta information where a simple delta against the latest version is"   NL
924"### often smaller.  By default, 1.8+ will therefore use skip deltas only"   NL
925"### after the linear chain of deltas has grown beyond the threshold"        NL
926"### specified by this setting."                                             NL
927"### Values up to 64 can result in some reduction in repository size for"    NL
928"### the cost of quickly increasing I/O and CPU costs. Similarly, smaller"   NL
929"### numbers can reduce those costs at the cost of more disk space.  For"    NL
930"### rarely read repositories or those containing larger binaries, this may" NL
931"### present a better trade-off."                                            NL
932"### Should be a power of two.  A value of 1 or smaller will cause the"      NL
933"### exclusive use of skip-deltas (as in pre-1.8)."                          NL
934"### For 1.8, the default value is 16; earlier versions use 1."              NL
935"# " CONFIG_OPTION_MAX_LINEAR_DELTIFICATION " = 16"                          NL
936"###"                                                                        NL
937"### After deltification, we compress the data through zlib to minimize on-" NL
938"### disk size.  That can be an expensive and ineffective process.  This"    NL
939"### setting controls the usage of zlib in future revisions."                NL
940"### Revisions with highly compressible data in them may shrink in size"     NL
941"### if the setting is increased but may take much longer to commit.  The"   NL
942"### time taken to uncompress that data again is widely independent of the"  NL
943"### compression level."                                                     NL
944"### Compression will be ineffective if the incoming content is already"     NL
945"### highly compressed.  In that case, disabling the compression entirely"   NL
946"### will speed up commits as well as reading the data.  Repositories with"  NL
947"### many small compressible files (source code) but also a high percentage" NL
948"### of large incompressible ones (artwork) may benefit from compression"    NL
949"### levels lowered to e.g. 1."                                              NL
950"### Valid values are 0 to 9 with 9 providing the highest compression ratio" NL
951"### and 0 disabling it altogether."                                         NL
952"### The default value is 5."                                                NL
953"# " CONFIG_OPTION_COMPRESSION_LEVEL " = 5"                                  NL
954""                                                                           NL
955"[" CONFIG_SECTION_PACKED_REVPROPS "]"                                       NL
956"### This parameter controls the size (in kBytes) of packed revprop files."  NL
957"### Revprops of consecutive revisions will be concatenated into a single"   NL
958"### file up to but not exceeding the threshold given here.  However, each"  NL
959"### pack file may be much smaller and revprops of a single revision may be" NL
960"### much larger than the limit set here.  The threshold will be applied"    NL
961"### before optional compression takes place."                               NL
962"### Large values will reduce disk space usage at the expense of increased"  NL
963"### latency and CPU usage reading and changing individual revprops."        NL
964"### Values smaller than 4 kByte will not improve latency any further and "  NL
965"### quickly render revprop packing ineffective."                            NL
966"### revprop-pack-size is 4 kBytes by default for non-compressed revprop"    NL
967"### pack files and 16 kBytes when compression has been enabled."            NL
968"# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 4"                                  NL
969"###"                                                                        NL
970"### To save disk space, packed revprop files may be compressed.  Standard"  NL
971"### revprops tend to allow for very effective compression.  Reading and"    NL
972"### even more so writing, become significantly more CPU intensive."         NL
973"### Compressing packed revprops is disabled by default."                    NL
974"# " CONFIG_OPTION_COMPRESS_PACKED_REVPROPS " = false"                       NL
975""                                                                           NL
976"[" CONFIG_SECTION_IO "]"                                                    NL
977"### Parameters in this section control the data access granularity in"      NL
978"### format 7 repositories and later.  The defaults should translate into"   NL
979"### decent performance over a wide range of setups."                        NL
980"###"                                                                        NL
981"### When a specific piece of information needs to be read from disk,  a"    NL
982"### data block is being read at once and its contents are being cached."    NL
983"### If the repository is being stored on a RAID, the block size should be"  NL
984"### either 50% or 100% of RAID block size / granularity.  Also, your file"  NL
985"### system blocks/clusters should be properly aligned and sized.  In that"  NL
986"### setup, each access will hit only one disk (minimizes I/O load) but"     NL
987"### uses all the data provided by the disk in a single access."             NL
988"### For SSD-based storage systems, slightly lower values around 16 kB"      NL
989"### may improve latency while still maximizing throughput.  If block-read"  NL
990"### has not been enabled, this will be capped to 4 kBytes."                 NL
991"### Can be changed at any time but must be a power of 2."                   NL
992"### block-size is given in kBytes and with a default of 64 kBytes."         NL
993"# " CONFIG_OPTION_BLOCK_SIZE " = 64"                                        NL
994"###"                                                                        NL
995"### The log-to-phys index maps data item numbers to offsets within the"     NL
996"### rev or pack file.  This index is organized in pages of a fixed maximum" NL
997"### capacity.  To access an item, the page table and the respective page"   NL
998"### must be read."                                                          NL
999"### This parameter only affects revisions with thousands of changed paths." NL
1000"### If you have several extremely large revisions (~1 mio changes), think"  NL
1001"### about increasing this setting.  Reducing the value will rarely result"  NL
1002"### in a net speedup."                                                      NL
1003"### This is an expert setting.  Must be a power of 2."                      NL
1004"### l2p-page-size is 8192 entries by default."                              NL
1005"# " CONFIG_OPTION_L2P_PAGE_SIZE " = 8192"                                   NL
1006"###"                                                                        NL
1007"### The phys-to-log index maps positions within the rev or pack file to"    NL
1008"### to data items,  i.e. describes what piece of information is being"      NL
1009"### stored at any particular offset.  The index describes the rev file"     NL
1010"### in chunks (pages) and keeps a global list of all those pages.  Large"   NL
1011"### pages mean a shorter page table but a larger per-page description of"   NL
1012"### data items in it.  The latency sweetspot depends on the change size"    NL
1013"### distribution but covers a relatively wide range."                       NL
1014"### If the repository contains very large files,  i.e. individual changes"  NL
1015"### of tens of MB each,  increasing the page size will shorten the index"   NL
1016"### file at the expense of a slightly increased latency in sections with"   NL
1017"### smaller changes."                                                       NL
1018"### For source code repositories, this should be about 16x the block-size." NL
1019"### Must be a power of 2."                                                  NL
1020"### p2l-page-size is given in kBytes and with a default of 1024 kBytes."    NL
1021"# " CONFIG_OPTION_P2L_PAGE_SIZE " = 1024"                                   NL
1022;
1023#undef NL
1024  return svn_io_file_create(svn_dirent_join(fs->path, PATH_CONFIG, pool),
1025                            fsfs_conf_contents, pool);
1026}
1027
1028/* Read / Evaluate the global configuration in FS->CONFIG to set up
1029 * parameters in FS. */
1030static svn_error_t *
1031read_global_config(svn_fs_t *fs)
1032{
1033  fs_fs_data_t *ffd = fs->fsap_data;
1034
1035  /* Providing a config hash is optional. */
1036  if (fs->config)
1037    ffd->use_block_read = svn_hash__get_bool(fs->config,
1038                                             SVN_FS_CONFIG_FSFS_BLOCK_READ,
1039                                             FALSE);
1040  else
1041    ffd->use_block_read = FALSE;
1042
1043  /* Ignore the user-specified larger block size if we don't use block-read.
1044     Defaulting to 4k gives us the same access granularity in format 7 as in
1045     older formats. */
1046  if (!ffd->use_block_read)
1047    ffd->block_size = MIN(0x1000, ffd->block_size);
1048
1049  return SVN_NO_ERROR;
1050}
1051
1052/* Read FS's UUID file and store the data in the FS struct. */
1053static svn_error_t *
1054read_uuid(svn_fs_t *fs,
1055          apr_pool_t *scratch_pool)
1056{
1057  fs_fs_data_t *ffd = fs->fsap_data;
1058  apr_file_t *uuid_file;
1059  char buf[APR_UUID_FORMATTED_LENGTH + 2];
1060  apr_size_t limit;
1061
1062  /* Read the repository uuid. */
1063  SVN_ERR(svn_io_file_open(&uuid_file, path_uuid(fs, scratch_pool),
1064                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
1065                           scratch_pool));
1066
1067  limit = sizeof(buf);
1068  SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, scratch_pool));
1069  fs->uuid = apr_pstrdup(fs->pool, buf);
1070
1071  /* Read the instance ID. */
1072  if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT)
1073    {
1074      limit = sizeof(buf);
1075      SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit,
1076                                      scratch_pool));
1077      ffd->instance_id = apr_pstrdup(fs->pool, buf);
1078    }
1079  else
1080    {
1081      ffd->instance_id = fs->uuid;
1082    }
1083
1084  SVN_ERR(svn_io_file_close(uuid_file, scratch_pool));
1085
1086  return SVN_NO_ERROR;
1087}
1088
1089svn_error_t *
1090svn_fs_fs__read_format_file(svn_fs_t *fs, apr_pool_t *scratch_pool)
1091{
1092  fs_fs_data_t *ffd = fs->fsap_data;
1093  int format, max_files_per_dir;
1094  svn_boolean_t use_log_addressing;
1095
1096  /* Read info from format file. */
1097  SVN_ERR(read_format(&format, &max_files_per_dir, &use_log_addressing,
1098                      path_format(fs, scratch_pool), scratch_pool));
1099
1100  /* Now that we've got *all* info, store / update values in FFD. */
1101  ffd->format = format;
1102  ffd->max_files_per_dir = max_files_per_dir;
1103  ffd->use_log_addressing = use_log_addressing;
1104
1105  return SVN_NO_ERROR;
1106}
1107
1108svn_error_t *
1109svn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool)
1110{
1111  fs_fs_data_t *ffd = fs->fsap_data;
1112  fs->path = apr_pstrdup(fs->pool, path);
1113
1114  /* Read the FS format file. */
1115  SVN_ERR(svn_fs_fs__read_format_file(fs, pool));
1116
1117  /* Read in and cache the repository uuid. */
1118  SVN_ERR(read_uuid(fs, pool));
1119
1120  /* Read the min unpacked revision. */
1121  if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1122    SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, pool));
1123
1124  /* Read the configuration file. */
1125  SVN_ERR(read_config(ffd, fs->path, fs->pool, pool));
1126
1127  /* Global configuration options. */
1128  SVN_ERR(read_global_config(fs));
1129
1130  return get_youngest(&(ffd->youngest_rev_cache), fs, pool);
1131}
1132
1133/* Wrapper around svn_io_file_create which ignores EEXIST. */
1134static svn_error_t *
1135create_file_ignore_eexist(const char *file,
1136                          const char *contents,
1137                          apr_pool_t *pool)
1138{
1139  svn_error_t *err = svn_io_file_create(file, contents, pool);
1140  if (err && APR_STATUS_IS_EEXIST(err->apr_err))
1141    {
1142      svn_error_clear(err);
1143      err = SVN_NO_ERROR;
1144    }
1145  return svn_error_trace(err);
1146}
1147
1148/* Baton type bridging svn_fs_fs__upgrade and upgrade_body carrying
1149 * parameters over between them. */
1150struct upgrade_baton_t
1151{
1152  svn_fs_t *fs;
1153  svn_fs_upgrade_notify_t notify_func;
1154  void *notify_baton;
1155  svn_cancel_func_t cancel_func;
1156  void *cancel_baton;
1157};
1158
1159static svn_error_t *
1160upgrade_body(void *baton, apr_pool_t *pool)
1161{
1162  struct upgrade_baton_t *upgrade_baton = baton;
1163  svn_fs_t *fs = upgrade_baton->fs;
1164  fs_fs_data_t *ffd = fs->fsap_data;
1165  int format, max_files_per_dir;
1166  svn_boolean_t use_log_addressing;
1167  const char *format_path = path_format(fs, pool);
1168  svn_node_kind_t kind;
1169  svn_boolean_t needs_revprop_shard_cleanup = FALSE;
1170
1171  /* Read the FS format number and max-files-per-dir setting. */
1172  SVN_ERR(read_format(&format, &max_files_per_dir, &use_log_addressing,
1173                      format_path, pool));
1174
1175  /* If the config file does not exist, create one. */
1176  SVN_ERR(svn_io_check_path(svn_dirent_join(fs->path, PATH_CONFIG, pool),
1177                            &kind, pool));
1178  switch (kind)
1179    {
1180    case svn_node_none:
1181      SVN_ERR(write_config(fs, pool));
1182      break;
1183    case svn_node_file:
1184      break;
1185    default:
1186      return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
1187                               _("'%s' is not a regular file."
1188                                 " Please move it out of "
1189                                 "the way and try again"),
1190                               svn_dirent_join(fs->path, PATH_CONFIG, pool));
1191    }
1192
1193  /* If we're already up-to-date, there's nothing else to be done here. */
1194  if (format == SVN_FS_FS__FORMAT_NUMBER)
1195    return SVN_NO_ERROR;
1196
1197  /* If our filesystem predates the existence of the 'txn-current
1198     file', make that file and its corresponding lock file. */
1199  if (format < SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1200    {
1201      SVN_ERR(create_file_ignore_eexist(
1202                           svn_fs_fs__path_txn_current(fs, pool), "0\n",
1203                           pool));
1204      SVN_ERR(create_file_ignore_eexist(
1205                           svn_fs_fs__path_txn_current_lock(fs, pool), "",
1206                           pool));
1207    }
1208
1209  /* If our filesystem predates the existence of the 'txn-protorevs'
1210     dir, make that directory.  */
1211  if (format < SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1212    {
1213      SVN_ERR(svn_io_make_dir_recursively(
1214          svn_fs_fs__path_txn_proto_revs(fs, pool), pool));
1215    }
1216
1217  /* If our filesystem is new enough, write the min unpacked rev file. */
1218  if (format < SVN_FS_FS__MIN_PACKED_FORMAT)
1219    SVN_ERR(svn_io_file_create(svn_fs_fs__path_min_unpacked_rev(fs, pool),
1220                               "0\n", pool));
1221
1222  /* If the file system supports revision packing but not revprop packing
1223     *and* the FS has been sharded, pack the revprops up to the point that
1224     revision data has been packed.  However, keep the non-packed revprop
1225     files around until after the format bump */
1226  if (   format >= SVN_FS_FS__MIN_PACKED_FORMAT
1227      && format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
1228      && max_files_per_dir > 0)
1229    {
1230      needs_revprop_shard_cleanup = TRUE;
1231      SVN_ERR(svn_fs_fs__upgrade_pack_revprops(fs,
1232                                               upgrade_baton->notify_func,
1233                                               upgrade_baton->notify_baton,
1234                                               upgrade_baton->cancel_func,
1235                                               upgrade_baton->cancel_baton,
1236                                               pool));
1237    }
1238
1239  /* We will need the UUID info shortly ...
1240     Read it before the format bump as the UUID file still uses the old
1241     format. */
1242  SVN_ERR(read_uuid(fs, pool));
1243
1244  /* Update the format info in the FS struct.  Upgrade steps further
1245     down will use the format from FS to create missing info. */
1246  ffd->format = SVN_FS_FS__FORMAT_NUMBER;
1247  ffd->max_files_per_dir = max_files_per_dir;
1248  ffd->use_log_addressing = use_log_addressing;
1249
1250  /* Always add / bump the instance ID such that no form of caching
1251     accidentally uses outdated information.  Keep the UUID. */
1252  SVN_ERR(svn_fs_fs__set_uuid(fs, fs->uuid, NULL, pool));
1253
1254  /* Bump the format file. */
1255  SVN_ERR(svn_fs_fs__write_format(fs, TRUE, pool));
1256
1257  if (upgrade_baton->notify_func)
1258    SVN_ERR(upgrade_baton->notify_func(upgrade_baton->notify_baton,
1259                                       SVN_FS_FS__FORMAT_NUMBER,
1260                                       svn_fs_upgrade_format_bumped,
1261                                       pool));
1262
1263  /* Now, it is safe to remove the redundant revprop files. */
1264  if (needs_revprop_shard_cleanup)
1265    SVN_ERR(svn_fs_fs__upgrade_cleanup_pack_revprops(fs,
1266                                               upgrade_baton->notify_func,
1267                                               upgrade_baton->notify_baton,
1268                                               upgrade_baton->cancel_func,
1269                                               upgrade_baton->cancel_baton,
1270                                               pool));
1271
1272  /* Done */
1273  return SVN_NO_ERROR;
1274}
1275
1276
1277svn_error_t *
1278svn_fs_fs__upgrade(svn_fs_t *fs,
1279                   svn_fs_upgrade_notify_t notify_func,
1280                   void *notify_baton,
1281                   svn_cancel_func_t cancel_func,
1282                   void *cancel_baton,
1283                   apr_pool_t *pool)
1284{
1285  struct upgrade_baton_t baton;
1286  baton.fs = fs;
1287  baton.notify_func = notify_func;
1288  baton.notify_baton = notify_baton;
1289  baton.cancel_func = cancel_func;
1290  baton.cancel_baton = cancel_baton;
1291
1292  return svn_fs_fs__with_all_locks(fs, upgrade_body, (void *)&baton, pool);
1293}
1294
1295/* Find the youngest revision in a repository at path FS_PATH and
1296   return it in *YOUNGEST_P.  Perform temporary allocations in
1297   POOL. */
1298static svn_error_t *
1299get_youngest(svn_revnum_t *youngest_p,
1300             svn_fs_t *fs,
1301             apr_pool_t *pool)
1302{
1303  apr_uint64_t dummy;
1304  SVN_ERR(svn_fs_fs__read_current(youngest_p, &dummy, &dummy, fs, pool));
1305  return SVN_NO_ERROR;
1306}
1307
1308
1309svn_error_t *
1310svn_fs_fs__youngest_rev(svn_revnum_t *youngest_p,
1311                        svn_fs_t *fs,
1312                        apr_pool_t *pool)
1313{
1314  fs_fs_data_t *ffd = fs->fsap_data;
1315
1316  SVN_ERR(get_youngest(youngest_p, fs, pool));
1317  ffd->youngest_rev_cache = *youngest_p;
1318
1319  return SVN_NO_ERROR;
1320}
1321
1322int
1323svn_fs_fs__shard_size(svn_fs_t *fs)
1324{
1325  fs_fs_data_t *ffd = fs->fsap_data;
1326
1327  return ffd->max_files_per_dir;
1328}
1329
1330svn_error_t *
1331svn_fs_fs__min_unpacked_rev(svn_revnum_t *min_unpacked,
1332                            svn_fs_t *fs,
1333                            apr_pool_t *pool)
1334{
1335  fs_fs_data_t *ffd = fs->fsap_data;
1336
1337  SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, pool));
1338  *min_unpacked = ffd->min_unpacked_rev;
1339
1340  return SVN_NO_ERROR;
1341}
1342
1343svn_error_t *
1344svn_fs_fs__ensure_revision_exists(svn_revnum_t rev,
1345                                  svn_fs_t *fs,
1346                                  apr_pool_t *pool)
1347{
1348  fs_fs_data_t *ffd = fs->fsap_data;
1349
1350  if (! SVN_IS_VALID_REVNUM(rev))
1351    return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1352                             _("Invalid revision number '%ld'"), rev);
1353
1354
1355  /* Did the revision exist the last time we checked the current
1356     file? */
1357  if (rev <= ffd->youngest_rev_cache)
1358    return SVN_NO_ERROR;
1359
1360  SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs, pool));
1361
1362  /* Check again. */
1363  if (rev <= ffd->youngest_rev_cache)
1364    return SVN_NO_ERROR;
1365
1366  return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1367                           _("No such revision %ld"), rev);
1368}
1369
1370svn_error_t *
1371svn_fs_fs__file_length(svn_filesize_t *length,
1372                       node_revision_t *noderev,
1373                       apr_pool_t *pool)
1374{
1375  representation_t *data_rep = noderev->data_rep;
1376  if (!data_rep)
1377    {
1378      /* Treat "no representation" as "empty file". */
1379      *length = 0;
1380    }
1381  else if (data_rep->expanded_size)
1382    {
1383      /* Standard case: a non-empty file. */
1384      *length = data_rep->expanded_size;
1385    }
1386  else
1387    {
1388      /* Work around a FSFS format quirk (see issue #4554).
1389
1390         A plain representation may specify its EXPANDED LENGTH as "0"
1391         in which case, the SIZE value is what we want.
1392
1393         Because EXPANDED_LENGTH will also be 0 for empty files, while
1394         SIZE is non-null, we need to check wether the content is
1395         actually empty.  We simply compare with the MD5 checksum of
1396         empty content (sha-1 is not always available).
1397       */
1398      svn_checksum_t *empty_md5
1399        = svn_checksum_empty_checksum(svn_checksum_md5, pool);
1400
1401      if (memcmp(empty_md5->digest, data_rep->md5_digest,
1402                 sizeof(data_rep->md5_digest)))
1403        {
1404          /* Contents is not empty, i.e. EXPANDED_LENGTH cannot be the
1405             actual file length. */
1406          *length = data_rep->size;
1407        }
1408      else
1409        {
1410          /* Contents is empty. */
1411          *length = 0;
1412        }
1413    }
1414
1415  return SVN_NO_ERROR;
1416}
1417
1418svn_boolean_t
1419svn_fs_fs__noderev_same_rep_key(representation_t *a,
1420                                representation_t *b)
1421{
1422  if (a == b)
1423    return TRUE;
1424
1425  if (a == NULL || b == NULL)
1426    return FALSE;
1427
1428  if (a->item_index != b->item_index)
1429    return FALSE;
1430
1431  if (a->revision != b->revision)
1432    return FALSE;
1433
1434  return memcmp(&a->uniquifier, &b->uniquifier, sizeof(a->uniquifier)) == 0;
1435}
1436
1437svn_error_t *
1438svn_fs_fs__file_text_rep_equal(svn_boolean_t *equal,
1439                               svn_fs_t *fs,
1440                               node_revision_t *a,
1441                               node_revision_t *b,
1442                               apr_pool_t *scratch_pool)
1443{
1444  svn_stream_t *contents_a, *contents_b;
1445  representation_t *rep_a = a->data_rep;
1446  representation_t *rep_b = b->data_rep;
1447  svn_boolean_t a_empty = !rep_a;
1448  svn_boolean_t b_empty = !rep_b;
1449
1450  /* This makes sure that neither rep will be NULL later on */
1451  if (a_empty && b_empty)
1452    {
1453      *equal = TRUE;
1454      return SVN_NO_ERROR;
1455    }
1456
1457  /* Same path in same rev or txn? */
1458  if (svn_fs_fs__id_eq(a->id, b->id))
1459    {
1460      *equal = TRUE;
1461      return SVN_NO_ERROR;
1462    }
1463
1464  /* Beware of the combination NULL rep and possibly empty rep.
1465   * Due to EXPANDED_SIZE not being reliable, we can't easily detect empty
1466   * reps. So, we can only take further shortcuts if both reps are given. */
1467  if (!a_empty && !b_empty)
1468    {
1469      /* File text representations always know their checksums -
1470       * even in a txn. */
1471      if (memcmp(rep_a->md5_digest, rep_b->md5_digest,
1472                 sizeof(rep_a->md5_digest)))
1473        {
1474          *equal = FALSE;
1475          return SVN_NO_ERROR;
1476        }
1477
1478      /* Paranoia. Compare SHA1 checksums because that's the level of
1479         confidence we require for e.g. the working copy. */
1480      if (rep_a->has_sha1 && rep_b->has_sha1)
1481        {
1482          *equal = memcmp(rep_a->sha1_digest, rep_b->sha1_digest,
1483                          sizeof(rep_a->sha1_digest)) == 0;
1484          return SVN_NO_ERROR;
1485        }
1486    }
1487
1488  SVN_ERR(svn_fs_fs__get_contents(&contents_a, fs, rep_a, TRUE,
1489                                  scratch_pool));
1490  SVN_ERR(svn_fs_fs__get_contents(&contents_b, fs, rep_b, TRUE,
1491                                  scratch_pool));
1492  SVN_ERR(svn_stream_contents_same2(equal, contents_a, contents_b,
1493                                   scratch_pool));
1494
1495  return SVN_NO_ERROR;
1496}
1497
1498svn_error_t *
1499svn_fs_fs__prop_rep_equal(svn_boolean_t *equal,
1500                          svn_fs_t *fs,
1501                          node_revision_t *a,
1502                          node_revision_t *b,
1503                          apr_pool_t *scratch_pool)
1504{
1505  representation_t *rep_a = a->prop_rep;
1506  representation_t *rep_b = b->prop_rep;
1507  apr_hash_t *proplist_a;
1508  apr_hash_t *proplist_b;
1509
1510  /* Mainly for a==b==NULL */
1511  if (rep_a == rep_b)
1512    {
1513      *equal = TRUE;
1514      return SVN_NO_ERROR;
1515    }
1516
1517  /* Committed property lists can be compared quickly */
1518  if (   rep_a && rep_b
1519      && !svn_fs_fs__id_txn_used(&rep_a->txn_id)
1520      && !svn_fs_fs__id_txn_used(&rep_b->txn_id))
1521    {
1522      /* MD5 must be given. Having the same checksum is good enough for
1523         accepting the prop lists as equal. */
1524      *equal = memcmp(rep_a->md5_digest, rep_b->md5_digest,
1525                      sizeof(rep_a->md5_digest)) == 0;
1526      return SVN_NO_ERROR;
1527    }
1528
1529  /* Same path in same txn? */
1530  if (svn_fs_fs__id_eq(a->id, b->id))
1531    {
1532      *equal = TRUE;
1533      return SVN_NO_ERROR;
1534    }
1535
1536  /* At least one of the reps has been modified in a txn.
1537     Fetch and compare them. */
1538  SVN_ERR(svn_fs_fs__get_proplist(&proplist_a, fs, a, scratch_pool));
1539  SVN_ERR(svn_fs_fs__get_proplist(&proplist_b, fs, b, scratch_pool));
1540
1541  *equal = svn_fs__prop_lists_equal(proplist_a, proplist_b, scratch_pool);
1542  return SVN_NO_ERROR;
1543}
1544
1545
1546svn_error_t *
1547svn_fs_fs__file_checksum(svn_checksum_t **checksum,
1548                         node_revision_t *noderev,
1549                         svn_checksum_kind_t kind,
1550                         apr_pool_t *pool)
1551{
1552  *checksum = NULL;
1553
1554  if (noderev->data_rep)
1555    {
1556      svn_checksum_t temp;
1557      temp.kind = kind;
1558
1559      switch(kind)
1560        {
1561          case svn_checksum_md5:
1562            temp.digest = noderev->data_rep->md5_digest;
1563            break;
1564
1565          case svn_checksum_sha1:
1566            if (! noderev->data_rep->has_sha1)
1567              return SVN_NO_ERROR;
1568
1569            temp.digest = noderev->data_rep->sha1_digest;
1570            break;
1571
1572          default:
1573            return SVN_NO_ERROR;
1574        }
1575
1576      *checksum = svn_checksum_dup(&temp, pool);
1577    }
1578
1579  return SVN_NO_ERROR;
1580}
1581
1582representation_t *
1583svn_fs_fs__rep_copy(representation_t *rep,
1584                    apr_pool_t *pool)
1585{
1586  if (rep == NULL)
1587    return NULL;
1588
1589  return apr_pmemdup(pool, rep, sizeof(*rep));
1590}
1591
1592
1593/* Write out the zeroth revision for filesystem FS.
1594   Perform temporary allocations in SCRATCH_POOL. */
1595static svn_error_t *
1596write_revision_zero(svn_fs_t *fs,
1597                    apr_pool_t *scratch_pool)
1598{
1599  /* Use an explicit sub-pool to have full control over temp file lifetimes.
1600   * Since we have it, use it for everything else as well. */
1601  apr_pool_t *subpool = svn_pool_create(scratch_pool);
1602  const char *path_revision_zero = svn_fs_fs__path_rev(fs, 0, subpool);
1603  apr_hash_t *proplist;
1604  svn_string_t date;
1605
1606  /* Write out a rev file for revision 0. */
1607  if (svn_fs_fs__use_log_addressing(fs))
1608    {
1609      apr_array_header_t *index_entries;
1610      svn_fs_fs__p2l_entry_t *entry;
1611      svn_fs_fs__revision_file_t *rev_file;
1612      const char *l2p_proto_index, *p2l_proto_index;
1613
1614      /* Write a skeleton r0 with no indexes. */
1615      SVN_ERR(svn_io_file_create(path_revision_zero,
1616                    "PLAIN\nEND\nENDREP\n"
1617                    "id: 0.0.r0/2\n"
1618                    "type: dir\n"
1619                    "count: 0\n"
1620                    "text: 0 3 4 4 "
1621                    "2d2977d1c96f487abe4a1e202dd03b4e\n"
1622                    "cpath: /\n"
1623                    "\n\n", subpool));
1624
1625      /* Construct the index P2L contents: describe the 3 items we have.
1626         Be sure to create them in on-disk order. */
1627      index_entries = apr_array_make(subpool, 3, sizeof(entry));
1628
1629      entry = apr_pcalloc(subpool, sizeof(*entry));
1630      entry->offset = 0;
1631      entry->size = 17;
1632      entry->type = SVN_FS_FS__ITEM_TYPE_DIR_REP;
1633      entry->item.revision = 0;
1634      entry->item.number = SVN_FS_FS__ITEM_INDEX_FIRST_USER;
1635      APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
1636
1637      entry = apr_pcalloc(subpool, sizeof(*entry));
1638      entry->offset = 17;
1639      entry->size = 89;
1640      entry->type = SVN_FS_FS__ITEM_TYPE_NODEREV;
1641      entry->item.revision = 0;
1642      entry->item.number = SVN_FS_FS__ITEM_INDEX_ROOT_NODE;
1643      APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
1644
1645      entry = apr_pcalloc(subpool, sizeof(*entry));
1646      entry->offset = 106;
1647      entry->size = 1;
1648      entry->type = SVN_FS_FS__ITEM_TYPE_CHANGES;
1649      entry->item.revision = 0;
1650      entry->item.number = SVN_FS_FS__ITEM_INDEX_CHANGES;
1651      APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
1652
1653      /* Now re-open r0, create proto-index files from our entries and
1654         rewrite the index section of r0. */
1655      SVN_ERR(svn_fs_fs__open_pack_or_rev_file_writable(&rev_file, fs, 0,
1656                                                        subpool, subpool));
1657      SVN_ERR(svn_fs_fs__p2l_index_from_p2l_entries(&p2l_proto_index, fs,
1658                                                    rev_file, index_entries,
1659                                                    subpool, subpool));
1660      SVN_ERR(svn_fs_fs__l2p_index_from_p2l_entries(&l2p_proto_index, fs,
1661                                                    index_entries,
1662                                                    subpool, subpool));
1663      SVN_ERR(svn_fs_fs__add_index_data(fs, rev_file->file, l2p_proto_index,
1664                                        p2l_proto_index, 0, subpool));
1665      SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
1666    }
1667  else
1668    SVN_ERR(svn_io_file_create(path_revision_zero,
1669                               "PLAIN\nEND\nENDREP\n"
1670                               "id: 0.0.r0/17\n"
1671                               "type: dir\n"
1672                               "count: 0\n"
1673                               "text: 0 0 4 4 "
1674                               "2d2977d1c96f487abe4a1e202dd03b4e\n"
1675                               "cpath: /\n"
1676                               "\n\n17 107\n", subpool));
1677
1678  SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, subpool));
1679
1680  /* Set a date on revision 0. */
1681  date.data = svn_time_to_cstring(apr_time_now(), subpool);
1682  date.len = strlen(date.data);
1683  proplist = apr_hash_make(subpool);
1684  svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date);
1685  SVN_ERR(svn_fs_fs__set_revision_proplist(fs, 0, proplist, subpool));
1686
1687  svn_pool_destroy(subpool);
1688  return SVN_NO_ERROR;
1689}
1690
1691svn_error_t *
1692svn_fs_fs__create_file_tree(svn_fs_t *fs,
1693                            const char *path,
1694                            int format,
1695                            int shard_size,
1696                            svn_boolean_t use_log_addressing,
1697                            apr_pool_t *pool)
1698{
1699  fs_fs_data_t *ffd = fs->fsap_data;
1700
1701  fs->path = apr_pstrdup(fs->pool, path);
1702  ffd->format = format;
1703
1704  /* Use an appropriate sharding mode if supported by the format. */
1705  if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
1706    ffd->max_files_per_dir = shard_size;
1707  else
1708    ffd->max_files_per_dir = 0;
1709
1710  /* Select the addressing mode depending on the format. */
1711  if (format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT)
1712    ffd->use_log_addressing = use_log_addressing;
1713  else
1714    ffd->use_log_addressing = FALSE;
1715
1716  /* Create the revision data directories. */
1717  if (ffd->max_files_per_dir)
1718    SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_rev_shard(fs, 0,
1719                                                                  pool),
1720                                        pool));
1721  else
1722    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_REVS_DIR,
1723                                                        pool),
1724                                        pool));
1725
1726  /* Create the revprops directory. */
1727  if (ffd->max_files_per_dir)
1728    SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_revprops_shard(fs, 0,
1729                                                                       pool),
1730                                        pool));
1731  else
1732    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path,
1733                                                        PATH_REVPROPS_DIR,
1734                                                        pool),
1735                                        pool));
1736
1737  /* Create the transaction directory. */
1738  SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_txns_dir(fs, pool),
1739                                      pool));
1740
1741  /* Create the protorevs directory. */
1742  if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1743    SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_txn_proto_revs(fs,
1744                                                                       pool),
1745                                        pool));
1746
1747  /* Create the 'current' file. */
1748  SVN_ERR(svn_io_file_create_empty(svn_fs_fs__path_current(fs, pool), pool));
1749  SVN_ERR(svn_fs_fs__write_current(fs, 0, 1, 1, pool));
1750
1751  /* Create the 'uuid' file. */
1752  SVN_ERR(svn_io_file_create_empty(svn_fs_fs__path_lock(fs, pool), pool));
1753  SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, NULL, pool));
1754
1755  /* Create the fsfs.conf file if supported.  Older server versions would
1756     simply ignore the file but that might result in a different behavior
1757     than with the later releases.  Also, hotcopy would ignore, i.e. not
1758     copy, a fsfs.conf with old formats. */
1759  if (ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
1760    SVN_ERR(write_config(fs, pool));
1761
1762  SVN_ERR(read_config(ffd, fs->path, fs->pool, pool));
1763
1764  /* Global configuration options. */
1765  SVN_ERR(read_global_config(fs));
1766
1767  /* Add revision 0. */
1768  SVN_ERR(write_revision_zero(fs, pool));
1769
1770  /* Create the min unpacked rev file. */
1771  if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1772    SVN_ERR(svn_io_file_create(svn_fs_fs__path_min_unpacked_rev(fs, pool),
1773                               "0\n", pool));
1774
1775  /* Create the txn-current file if the repository supports
1776     the transaction sequence file. */
1777  if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1778    {
1779      SVN_ERR(svn_io_file_create(svn_fs_fs__path_txn_current(fs, pool),
1780                                 "0\n", pool));
1781      SVN_ERR(svn_io_file_create_empty(
1782                                 svn_fs_fs__path_txn_current_lock(fs, pool),
1783                                 pool));
1784    }
1785
1786  ffd->youngest_rev_cache = 0;
1787  return SVN_NO_ERROR;
1788}
1789
1790svn_error_t *
1791svn_fs_fs__create(svn_fs_t *fs,
1792                  const char *path,
1793                  apr_pool_t *pool)
1794{
1795  int format = SVN_FS_FS__FORMAT_NUMBER;
1796  int shard_size = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
1797  svn_boolean_t log_addressing;
1798
1799  /* Process the given filesystem config. */
1800  if (fs->config)
1801    {
1802      svn_version_t *compatible_version;
1803      const char *shard_size_str;
1804      SVN_ERR(svn_fs__compatible_version(&compatible_version, fs->config,
1805                                         pool));
1806
1807      /* select format number */
1808      switch(compatible_version->minor)
1809        {
1810          case 0: return svn_error_create(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
1811                 _("FSFS is not compatible with Subversion prior to 1.1"));
1812
1813          case 1:
1814          case 2:
1815          case 3: format = 1;
1816                  break;
1817
1818          case 4: format = 2;
1819                  break;
1820
1821          case 5: format = 3;
1822                  break;
1823
1824          case 6:
1825          case 7: format = 4;
1826                  break;
1827
1828          case 8: format = 6;
1829                  break;
1830
1831          default:format = SVN_FS_FS__FORMAT_NUMBER;
1832        }
1833
1834      shard_size_str = svn_hash_gets(fs->config, SVN_FS_CONFIG_FSFS_SHARD_SIZE);
1835      if (shard_size_str)
1836        {
1837          apr_int64_t val;
1838          SVN_ERR(svn_cstring_strtoi64(&val, shard_size_str, 0,
1839                                       APR_INT32_MAX, 10));
1840
1841          shard_size = (int) val;
1842        }
1843    }
1844
1845  log_addressing = svn_hash__get_bool(fs->config,
1846                                      SVN_FS_CONFIG_FSFS_LOG_ADDRESSING,
1847                                      TRUE);
1848
1849  /* Actual FS creation. */
1850  SVN_ERR(svn_fs_fs__create_file_tree(fs, path, format, shard_size,
1851                                      log_addressing, pool));
1852
1853  /* This filesystem is ready.  Stamp it with a format number. */
1854  SVN_ERR(svn_fs_fs__write_format(fs, FALSE, pool));
1855
1856  return SVN_NO_ERROR;
1857}
1858
1859svn_error_t *
1860svn_fs_fs__set_uuid(svn_fs_t *fs,
1861                    const char *uuid,
1862                    const char *instance_id,
1863                    apr_pool_t *pool)
1864{
1865  fs_fs_data_t *ffd = fs->fsap_data;
1866  const char *uuid_path = path_uuid(fs, pool);
1867  svn_stringbuf_t *contents = svn_stringbuf_create_empty(pool);
1868
1869  if (! uuid)
1870    uuid = svn_uuid_generate(pool);
1871
1872  if (! instance_id)
1873    instance_id = svn_uuid_generate(pool);
1874
1875  svn_stringbuf_appendcstr(contents, uuid);
1876  svn_stringbuf_appendcstr(contents, "\n");
1877
1878  if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT)
1879    {
1880      svn_stringbuf_appendcstr(contents, instance_id);
1881      svn_stringbuf_appendcstr(contents, "\n");
1882    }
1883
1884  /* We use the permissions of the 'current' file, because the 'uuid'
1885     file does not exist during repository creation. */
1886  SVN_ERR(svn_io_write_atomic(uuid_path, contents->data, contents->len,
1887                              svn_fs_fs__path_current(fs, pool) /* perms */,
1888                              pool));
1889
1890  fs->uuid = apr_pstrdup(fs->pool, uuid);
1891
1892  if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT)
1893    ffd->instance_id = apr_pstrdup(fs->pool, instance_id);
1894  else
1895    ffd->instance_id = fs->uuid;
1896
1897  return SVN_NO_ERROR;
1898}
1899
1900/** Node origin lazy cache. */
1901
1902/* If directory PATH does not exist, create it and give it the same
1903   permissions as FS_path.*/
1904svn_error_t *
1905svn_fs_fs__ensure_dir_exists(const char *path,
1906                             const char *fs_path,
1907                             apr_pool_t *pool)
1908{
1909  svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
1910  if (err && APR_STATUS_IS_EEXIST(err->apr_err))
1911    {
1912      svn_error_clear(err);
1913      return SVN_NO_ERROR;
1914    }
1915  SVN_ERR(err);
1916
1917  /* We successfully created a new directory.  Dup the permissions
1918     from FS->path. */
1919  return svn_io_copy_perms(fs_path, path, pool);
1920}
1921
1922/* Set *NODE_ORIGINS to a hash mapping 'const char *' node IDs to
1923   'svn_string_t *' node revision IDs.  Use POOL for allocations. */
1924static svn_error_t *
1925get_node_origins_from_file(svn_fs_t *fs,
1926                           apr_hash_t **node_origins,
1927                           const char *node_origins_file,
1928                           apr_pool_t *pool)
1929{
1930  apr_file_t *fd;
1931  svn_error_t *err;
1932  svn_stream_t *stream;
1933
1934  *node_origins = NULL;
1935  err = svn_io_file_open(&fd, node_origins_file,
1936                         APR_READ, APR_OS_DEFAULT, pool);
1937  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
1938    {
1939      svn_error_clear(err);
1940      return SVN_NO_ERROR;
1941    }
1942  SVN_ERR(err);
1943
1944  stream = svn_stream_from_aprfile2(fd, FALSE, pool);
1945  *node_origins = apr_hash_make(pool);
1946  err = svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool);
1947  if (err)
1948    return svn_error_quick_wrapf(err, _("malformed node origin data in '%s'"),
1949                                 node_origins_file);
1950  return svn_stream_close(stream);
1951}
1952
1953svn_error_t *
1954svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
1955                           svn_fs_t *fs,
1956                           const svn_fs_fs__id_part_t *node_id,
1957                           apr_pool_t *pool)
1958{
1959  apr_hash_t *node_origins;
1960
1961  *origin_id = NULL;
1962  SVN_ERR(get_node_origins_from_file(fs, &node_origins,
1963                                     svn_fs_fs__path_node_origin(fs, node_id,
1964                                                                 pool),
1965                                     pool));
1966  if (node_origins)
1967    {
1968      char node_id_ptr[SVN_INT64_BUFFER_SIZE];
1969      apr_size_t len = svn__ui64tobase36(node_id_ptr, node_id->number);
1970      svn_string_t *origin_id_str
1971        = apr_hash_get(node_origins, node_id_ptr, len);
1972
1973      if (origin_id_str)
1974        SVN_ERR(svn_fs_fs__id_parse(origin_id,
1975                                    apr_pstrdup(pool, origin_id_str->data),
1976                                    pool));
1977    }
1978  return SVN_NO_ERROR;
1979}
1980
1981
1982/* Helper for svn_fs_fs__set_node_origin.  Takes a NODE_ID/NODE_REV_ID
1983   pair and adds it to the NODE_ORIGINS_PATH file.  */
1984static svn_error_t *
1985set_node_origins_for_file(svn_fs_t *fs,
1986                          const char *node_origins_path,
1987                          const svn_fs_fs__id_part_t *node_id,
1988                          svn_string_t *node_rev_id,
1989                          apr_pool_t *pool)
1990{
1991  const char *path_tmp;
1992  svn_stream_t *stream;
1993  apr_hash_t *origins_hash;
1994  svn_string_t *old_node_rev_id;
1995
1996  /* the hash serialization functions require strings as keys */
1997  char node_id_ptr[SVN_INT64_BUFFER_SIZE];
1998  apr_size_t len = svn__ui64tobase36(node_id_ptr, node_id->number);
1999
2000  SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs->path,
2001                                                       PATH_NODE_ORIGINS_DIR,
2002                                                       pool),
2003                                       fs->path, pool));
2004
2005  /* Read the previously existing origins (if any), and merge our
2006     update with it. */
2007  SVN_ERR(get_node_origins_from_file(fs, &origins_hash,
2008                                     node_origins_path, pool));
2009  if (! origins_hash)
2010    origins_hash = apr_hash_make(pool);
2011
2012  old_node_rev_id = apr_hash_get(origins_hash, node_id_ptr, len);
2013
2014  if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id))
2015    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2016                             _("Node origin for '%s' exists with a different "
2017                               "value (%s) than what we were about to store "
2018                               "(%s)"),
2019                             node_id_ptr, old_node_rev_id->data,
2020                             node_rev_id->data);
2021
2022  apr_hash_set(origins_hash, node_id_ptr, len, node_rev_id);
2023
2024  /* Sure, there's a race condition here.  Two processes could be
2025     trying to add different cache elements to the same file at the
2026     same time, and the entries added by the first one to write will
2027     be lost.  But this is just a cache of reconstructible data, so
2028     we'll accept this problem in return for not having to deal with
2029     locking overhead. */
2030
2031  /* Create a temporary file, write out our hash, and close the file. */
2032  SVN_ERR(svn_stream_open_unique(&stream, &path_tmp,
2033                                 svn_dirent_dirname(node_origins_path, pool),
2034                                 svn_io_file_del_none, pool, pool));
2035  SVN_ERR(svn_hash_write2(origins_hash, stream, SVN_HASH_TERMINATOR, pool));
2036  SVN_ERR(svn_stream_close(stream));
2037
2038  /* Rename the temp file as the real destination */
2039  return svn_io_file_rename(path_tmp, node_origins_path, pool);
2040}
2041
2042
2043svn_error_t *
2044svn_fs_fs__set_node_origin(svn_fs_t *fs,
2045                           const svn_fs_fs__id_part_t *node_id,
2046                           const svn_fs_id_t *node_rev_id,
2047                           apr_pool_t *pool)
2048{
2049  svn_error_t *err;
2050  const char *filename = svn_fs_fs__path_node_origin(fs, node_id, pool);
2051
2052  err = set_node_origins_for_file(fs, filename,
2053                                  node_id,
2054                                  svn_fs_fs__id_unparse(node_rev_id, pool),
2055                                  pool);
2056  if (err && APR_STATUS_IS_EACCES(err->apr_err))
2057    {
2058      /* It's just a cache; stop trying if I can't write. */
2059      svn_error_clear(err);
2060      err = NULL;
2061    }
2062  return svn_error_trace(err);
2063}
2064
2065
2066
2067/*** Revisions ***/
2068
2069svn_error_t *
2070svn_fs_fs__revision_prop(svn_string_t **value_p,
2071                         svn_fs_t *fs,
2072                         svn_revnum_t rev,
2073                         const char *propname,
2074                         apr_pool_t *pool)
2075{
2076  apr_hash_t *table;
2077
2078  SVN_ERR(svn_fs__check_fs(fs, TRUE));
2079  SVN_ERR(svn_fs_fs__get_revision_proplist(&table, fs, rev, pool));
2080
2081  *value_p = svn_hash_gets(table, propname);
2082
2083  return SVN_NO_ERROR;
2084}
2085
2086
2087/* Baton used for change_rev_prop_body below. */
2088struct change_rev_prop_baton {
2089  svn_fs_t *fs;
2090  svn_revnum_t rev;
2091  const char *name;
2092  const svn_string_t *const *old_value_p;
2093  const svn_string_t *value;
2094};
2095
2096/* The work-horse for svn_fs_fs__change_rev_prop, called with the FS
2097   write lock.  This implements the svn_fs_fs__with_write_lock()
2098   'body' callback type.  BATON is a 'struct change_rev_prop_baton *'. */
2099static svn_error_t *
2100change_rev_prop_body(void *baton, apr_pool_t *pool)
2101{
2102  struct change_rev_prop_baton *cb = baton;
2103  apr_hash_t *table;
2104
2105  SVN_ERR(svn_fs_fs__get_revision_proplist(&table, cb->fs, cb->rev, pool));
2106
2107  if (cb->old_value_p)
2108    {
2109      const svn_string_t *wanted_value = *cb->old_value_p;
2110      const svn_string_t *present_value = svn_hash_gets(table, cb->name);
2111      if ((!wanted_value != !present_value)
2112          || (wanted_value && present_value
2113              && !svn_string_compare(wanted_value, present_value)))
2114        {
2115          /* What we expected isn't what we found. */
2116          return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL,
2117                                   _("revprop '%s' has unexpected value in "
2118                                     "filesystem"),
2119                                   cb->name);
2120        }
2121      /* Fall through. */
2122    }
2123  svn_hash_sets(table, cb->name, cb->value);
2124
2125  return svn_fs_fs__set_revision_proplist(cb->fs, cb->rev, table, pool);
2126}
2127
2128svn_error_t *
2129svn_fs_fs__change_rev_prop(svn_fs_t *fs,
2130                           svn_revnum_t rev,
2131                           const char *name,
2132                           const svn_string_t *const *old_value_p,
2133                           const svn_string_t *value,
2134                           apr_pool_t *pool)
2135{
2136  struct change_rev_prop_baton cb;
2137
2138  SVN_ERR(svn_fs__check_fs(fs, TRUE));
2139
2140  cb.fs = fs;
2141  cb.rev = rev;
2142  cb.name = name;
2143  cb.old_value_p = old_value_p;
2144  cb.value = value;
2145
2146  return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool);
2147}
2148
2149
2150svn_error_t *
2151svn_fs_fs__info_format(int *fs_format,
2152                       svn_version_t **supports_version,
2153                       svn_fs_t *fs,
2154                       apr_pool_t *result_pool,
2155                       apr_pool_t *scratch_pool)
2156{
2157  fs_fs_data_t *ffd = fs->fsap_data;
2158  *fs_format = ffd->format;
2159  *supports_version = apr_palloc(result_pool, sizeof(svn_version_t));
2160
2161  (*supports_version)->major = SVN_VER_MAJOR;
2162  (*supports_version)->minor = 1;
2163  (*supports_version)->patch = 0;
2164  (*supports_version)->tag = "";
2165
2166  switch (ffd->format)
2167    {
2168    case 1:
2169      break;
2170    case 2:
2171      (*supports_version)->minor = 4;
2172      break;
2173    case 3:
2174      (*supports_version)->minor = 5;
2175      break;
2176    case 4:
2177      (*supports_version)->minor = 6;
2178      break;
2179    case 6:
2180      (*supports_version)->minor = 8;
2181      break;
2182    case 7:
2183      (*supports_version)->minor = 9;
2184      break;
2185#ifdef SVN_DEBUG
2186# if SVN_FS_FS__FORMAT_NUMBER != 7
2187#  error "Need to add a 'case' statement here"
2188# endif
2189#endif
2190    }
2191
2192  return SVN_NO_ERROR;
2193}
2194
2195svn_error_t *
2196svn_fs_fs__info_config_files(apr_array_header_t **files,
2197                             svn_fs_t *fs,
2198                             apr_pool_t *result_pool,
2199                             apr_pool_t *scratch_pool)
2200{
2201  *files = apr_array_make(result_pool, 1, sizeof(const char *));
2202  APR_ARRAY_PUSH(*files, const char *) = svn_dirent_join(fs->path, PATH_CONFIG,
2203                                                         result_pool);
2204  return SVN_NO_ERROR;
2205}
2206