1251881Speter/* fs_fs.c --- filesystem operations specific to fs_fs
2251881Speter *
3251881Speter * ====================================================================
4251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
5251881Speter *    or more contributor license agreements.  See the NOTICE file
6251881Speter *    distributed with this work for additional information
7251881Speter *    regarding copyright ownership.  The ASF licenses this file
8251881Speter *    to you under the Apache License, Version 2.0 (the
9251881Speter *    "License"); you may not use this file except in compliance
10251881Speter *    with the License.  You may obtain a copy of the License at
11251881Speter *
12251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
13251881Speter *
14251881Speter *    Unless required by applicable law or agreed to in writing,
15251881Speter *    software distributed under the License is distributed on an
16251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17251881Speter *    KIND, either express or implied.  See the License for the
18251881Speter *    specific language governing permissions and limitations
19251881Speter *    under the License.
20251881Speter * ====================================================================
21251881Speter */
22251881Speter
23299742Sdim#include "fs_fs.h"
24251881Speter
25251881Speter#include <apr_uuid.h>
26251881Speter
27299742Sdim#include "svn_private_config.h"
28299742Sdim
29299742Sdim#include "svn_checksum.h"
30251881Speter#include "svn_hash.h"
31251881Speter#include "svn_props.h"
32299742Sdim#include "svn_time.h"
33299742Sdim#include "svn_dirent_uri.h"
34251881Speter#include "svn_sorts.h"
35251881Speter#include "svn_version.h"
36251881Speter
37299742Sdim#include "cached_data.h"
38251881Speter#include "id.h"
39299742Sdim#include "index.h"
40251881Speter#include "rep-cache.h"
41299742Sdim#include "revprops.h"
42299742Sdim#include "transaction.h"
43299742Sdim#include "tree.h"
44299742Sdim#include "util.h"
45251881Speter
46299742Sdim#include "private/svn_fs_util.h"
47299742Sdim#include "private/svn_io_private.h"
48251881Speter#include "private/svn_string_private.h"
49251881Speter#include "private/svn_subr_private.h"
50251881Speter#include "../libsvn_fs/fs-loader.h"
51251881Speter
52251881Speter/* The default maximum number of files per directory to store in the
53251881Speter   rev and revprops directory.  The number below is somewhat arbitrary,
54251881Speter   and can be overridden by defining the macro while compiling; the
55251881Speter   figure of 1000 is reasonable for VFAT filesystems, which are by far
56251881Speter   the worst performers in this area. */
57251881Speter#ifndef SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR
58251881Speter#define SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR 1000
59251881Speter#endif
60251881Speter
61251881Speter/* Begin deltification after a node history exceeded this this limit.
62251881Speter   Useful values are 4 to 64 with 16 being a good compromise between
63251881Speter   computational overhead and repository size savings.
64251881Speter   Should be a power of 2.
65251881Speter   Values < 2 will result in standard skip-delta behavior. */
66251881Speter#define SVN_FS_FS_MAX_LINEAR_DELTIFICATION 16
67251881Speter
68251881Speter/* Finding a deltification base takes operations proportional to the
69251881Speter   number of changes being skipped. To prevent exploding runtime
70251881Speter   during commits, limit the deltification range to this value.
71251881Speter   Should be a power of 2 minus one.
72251881Speter   Values < 1 disable deltification. */
73251881Speter#define SVN_FS_FS_MAX_DELTIFICATION_WALK 1023
74251881Speter
75251881Speter/* Notes:
76251881Speter
77251881SpeterTo avoid opening and closing the rev-files all the time, it would
78251881Speterprobably be advantageous to keep each rev-file open for the
79251881Speterlifetime of the transaction object.  I'll leave that as a later
80251881Speteroptimization for now.
81251881Speter
82251881SpeterI didn't keep track of pool lifetimes at all in this code.  There
83251881Speterare likely some errors because of that.
84251881Speter
85251881Speter*/
86251881Speter
87251881Speter/* Declarations. */
88251881Speter
89251881Speterstatic svn_error_t *
90299742Sdimget_youngest(svn_revnum_t *youngest_p, svn_fs_t *fs, apr_pool_t *pool);
91251881Speter
92251881Speter/* Pathname helper functions */
93251881Speter
94251881Speterstatic const char *
95251881Speterpath_format(svn_fs_t *fs, apr_pool_t *pool)
96251881Speter{
97251881Speter  return svn_dirent_join(fs->path, PATH_FORMAT, pool);
98251881Speter}
99251881Speter
100251881Speterstatic APR_INLINE const char *
101251881Speterpath_uuid(svn_fs_t *fs, apr_pool_t *pool)
102251881Speter{
103251881Speter  return svn_dirent_join(fs->path, PATH_UUID, pool);
104251881Speter}
105251881Speter
106251881Speterconst char *
107251881Spetersvn_fs_fs__path_current(svn_fs_t *fs, apr_pool_t *pool)
108251881Speter{
109251881Speter  return svn_dirent_join(fs->path, PATH_CURRENT, pool);
110251881Speter}
111251881Speter
112251881Speter
113299742Sdim
114299742Sdim/* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
115299742Sdimstatic svn_error_t *
116299742Sdimget_lock_on_filesystem(const char *lock_filename,
117299742Sdim                       apr_pool_t *pool)
118251881Speter{
119299742Sdim  return svn_error_trace(svn_io__file_lock_autocreate(lock_filename, pool));
120251881Speter}
121251881Speter
122299742Sdim/* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID.
123299742Sdim   When registered with the pool holding the lock on the lock file,
124299742Sdim   this makes sure the flag gets reset just before we release the lock. */
125299742Sdimstatic apr_status_t
126299742Sdimreset_lock_flag(void *baton_void)
127251881Speter{
128299742Sdim  fs_fs_data_t *ffd = baton_void;
129299742Sdim  ffd->has_write_lock = FALSE;
130299742Sdim  return APR_SUCCESS;
131251881Speter}
132251881Speter
133299742Sdim/* Structure defining a file system lock to be acquired and the function
134299742Sdim   to be executed while the lock is held.
135251881Speter
136299742Sdim   Instances of this structure may be nested to allow for multiple locks to
137299742Sdim   be taken out before executing the user-provided body.  In that case, BODY
138299742Sdim   and BATON of the outer instances will be with_lock and a with_lock_baton_t
139299742Sdim   instance (transparently, no special treatment is required.).  It is
140299742Sdim   illegal to attempt to acquire the same lock twice within the same lock
141299742Sdim   chain or via nesting calls using separate lock chains.
142299742Sdim
143299742Sdim   All instances along the chain share the same LOCK_POOL such that only one
144299742Sdim   pool needs to be created and cleared for all locks.  We also allocate as
145299742Sdim   much data from that lock pool as possible to minimize memory usage in
146299742Sdim   caller pools. */
147299742Sdimtypedef struct with_lock_baton_t
148251881Speter{
149299742Sdim  /* The filesystem we operate on.  Same for all instances along the chain. */
150299742Sdim  svn_fs_t *fs;
151251881Speter
152299742Sdim  /* Mutex to complement the lock file in an APR threaded process.
153299742Sdim     No-op object for non-threaded processes but never NULL. */
154299742Sdim  svn_mutex__t *mutex;
155251881Speter
156299742Sdim  /* Path to the file to lock. */
157299742Sdim  const char *lock_path;
158251881Speter
159299742Sdim  /* If true, set FS->HAS_WRITE_LOCK after we acquired the lock. */
160299742Sdim  svn_boolean_t is_global_lock;
161251881Speter
162299742Sdim  /* Function body to execute after we acquired the lock.
163299742Sdim     This may be user-provided or a nested call to with_lock(). */
164299742Sdim  svn_error_t *(*body)(void *baton,
165299742Sdim                       apr_pool_t *pool);
166251881Speter
167299742Sdim  /* Baton to pass to BODY; possibly NULL.
168299742Sdim     This may be user-provided or a nested lock baton instance. */
169299742Sdim  void *baton;
170251881Speter
171299742Sdim  /* Pool for all allocations along the lock chain and BODY.  Will hold the
172299742Sdim     file locks and gets destroyed after the outermost BODY returned,
173299742Sdim     releasing all file locks.
174299742Sdim     Same for all instances along the chain. */
175299742Sdim  apr_pool_t *lock_pool;
176251881Speter
177299742Sdim  /* TRUE, iff BODY is the user-provided body. */
178299742Sdim  svn_boolean_t is_inner_most_lock;
179251881Speter
180299742Sdim  /* TRUE, iff this is not a nested lock.
181299742Sdim     Then responsible for destroying LOCK_POOL. */
182299742Sdim  svn_boolean_t is_outer_most_lock;
183299742Sdim} with_lock_baton_t;
184251881Speter
185299742Sdim/* Obtain a write lock on the file BATON->LOCK_PATH and call BATON->BODY
186299742Sdim   with BATON->BATON.  If this is the outermost lock call, release all file
187299742Sdim   locks after the body returned.  If BATON->IS_GLOBAL_LOCK is set, set the
188299742Sdim   HAS_WRITE_LOCK flag while we keep the write lock. */
189299742Sdimstatic svn_error_t *
190299742Sdimwith_some_lock_file(with_lock_baton_t *baton)
191251881Speter{
192299742Sdim  apr_pool_t *pool = baton->lock_pool;
193299742Sdim  svn_error_t *err = get_lock_on_filesystem(baton->lock_path, pool);
194251881Speter
195299742Sdim  if (!err)
196251881Speter    {
197299742Sdim      svn_fs_t *fs = baton->fs;
198299742Sdim      fs_fs_data_t *ffd = fs->fsap_data;
199251881Speter
200299742Sdim      if (baton->is_global_lock)
201299742Sdim        {
202299742Sdim          /* set the "got the lock" flag and register reset function */
203299742Sdim          apr_pool_cleanup_register(pool,
204299742Sdim                                    ffd,
205299742Sdim                                    reset_lock_flag,
206299742Sdim                                    apr_pool_cleanup_null);
207299742Sdim          ffd->has_write_lock = TRUE;
208299742Sdim        }
209251881Speter
210299742Sdim      /* nobody else will modify the repo state
211299742Sdim         => read HEAD & pack info once */
212299742Sdim      if (baton->is_inner_most_lock)
213299742Sdim        {
214299742Sdim          if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
215299742Sdim            err = svn_fs_fs__update_min_unpacked_rev(fs, pool);
216299742Sdim          if (!err)
217299742Sdim            err = get_youngest(&ffd->youngest_rev_cache, fs, pool);
218299742Sdim        }
219251881Speter
220299742Sdim      if (!err)
221299742Sdim        err = baton->body(baton->baton, pool);
222251881Speter    }
223251881Speter
224299742Sdim  if (baton->is_outer_most_lock)
225299742Sdim    svn_pool_destroy(pool);
226251881Speter
227299742Sdim  return svn_error_trace(err);
228251881Speter}
229251881Speter
230299742Sdim/* Wraps with_some_lock_file, protecting it with BATON->MUTEX.
231251881Speter
232299742Sdim   POOL is unused here and only provided for signature compatibility with
233299742Sdim   WITH_LOCK_BATON_T.BODY. */
234299742Sdimstatic svn_error_t *
235299742Sdimwith_lock(void *baton,
236299742Sdim          apr_pool_t *pool)
237251881Speter{
238299742Sdim  with_lock_baton_t *lock_baton = baton;
239299742Sdim  SVN_MUTEX__WITH_LOCK(lock_baton->mutex, with_some_lock_file(lock_baton));
240251881Speter
241299742Sdim  return SVN_NO_ERROR;
242251881Speter}
243251881Speter
244299742Sdim/* Enum identifying a filesystem lock. */
245299742Sdimtypedef enum lock_id_t
246251881Speter{
247299742Sdim  write_lock,
248299742Sdim  txn_lock,
249299742Sdim  pack_lock
250299742Sdim} lock_id_t;
251251881Speter
252299742Sdim/* Initialize BATON->MUTEX, BATON->LOCK_PATH and BATON->IS_GLOBAL_LOCK
253299742Sdim   according to the LOCK_ID.  All other members of BATON must already be
254299742Sdim   valid. */
255299742Sdimstatic void
256299742Sdiminit_lock_baton(with_lock_baton_t *baton,
257299742Sdim                lock_id_t lock_id)
258251881Speter{
259299742Sdim  fs_fs_data_t *ffd = baton->fs->fsap_data;
260251881Speter  fs_fs_shared_data_t *ffsd = ffd->shared;
261251881Speter
262299742Sdim  switch (lock_id)
263299742Sdim    {
264299742Sdim    case write_lock:
265299742Sdim      baton->mutex = ffsd->fs_write_lock;
266299742Sdim      baton->lock_path = svn_fs_fs__path_lock(baton->fs, baton->lock_pool);
267299742Sdim      baton->is_global_lock = TRUE;
268251881Speter      break;
269251881Speter
270299742Sdim    case txn_lock:
271299742Sdim      baton->mutex = ffsd->txn_current_lock;
272299742Sdim      baton->lock_path = svn_fs_fs__path_txn_current_lock(baton->fs,
273299742Sdim                                                          baton->lock_pool);
274299742Sdim      baton->is_global_lock = FALSE;
275299742Sdim      break;
276251881Speter
277299742Sdim    case pack_lock:
278299742Sdim      baton->mutex = ffsd->fs_pack_lock;
279299742Sdim      baton->lock_path = svn_fs_fs__path_pack_lock(baton->fs,
280299742Sdim                                                   baton->lock_pool);
281299742Sdim      baton->is_global_lock = FALSE;
282299742Sdim      break;
283251881Speter    }
284251881Speter}
285251881Speter
286299742Sdim/* Return the  baton for the innermost lock of a (potential) lock chain.
287299742Sdim   The baton shall take out LOCK_ID from FS and execute BODY with BATON
288299742Sdim   while the lock is being held.  Allocate the result in a sub-pool of POOL.
289299742Sdim */
290299742Sdimstatic with_lock_baton_t *
291299742Sdimcreate_lock_baton(svn_fs_t *fs,
292299742Sdim                  lock_id_t lock_id,
293299742Sdim                  svn_error_t *(*body)(void *baton,
294251881Speter                                       apr_pool_t *pool),
295299742Sdim                  void *baton,
296251881Speter                  apr_pool_t *pool)
297251881Speter{
298299742Sdim  /* Allocate everything along the lock chain into a single sub-pool.
299299742Sdim     This minimizes memory usage and cleanup overhead. */
300299742Sdim  apr_pool_t *lock_pool = svn_pool_create(pool);
301299742Sdim  with_lock_baton_t *result = apr_pcalloc(lock_pool, sizeof(*result));
302251881Speter
303299742Sdim  /* Store parameters. */
304299742Sdim  result->fs = fs;
305299742Sdim  result->body = body;
306299742Sdim  result->baton = baton;
307251881Speter
308299742Sdim  /* File locks etc. will use this pool as well for easy cleanup. */
309299742Sdim  result->lock_pool = lock_pool;
310251881Speter
311299742Sdim  /* Right now, we are the first, (only, ) and last struct in the chain. */
312299742Sdim  result->is_inner_most_lock = TRUE;
313299742Sdim  result->is_outer_most_lock = TRUE;
314251881Speter
315299742Sdim  /* Select mutex and lock file path depending on LOCK_ID.
316299742Sdim     Also, initialize dependent members (IS_GLOBAL_LOCK only, ATM). */
317299742Sdim  init_lock_baton(result, lock_id);
318251881Speter
319299742Sdim  return result;
320251881Speter}
321251881Speter
322299742Sdim/* Return a baton that wraps NESTED and requests LOCK_ID as additional lock.
323299742Sdim *
324299742Sdim * That means, when you create a lock chain, start with the last / innermost
325299742Sdim * lock to take out and add the first / outermost lock last.
326299742Sdim */
327299742Sdimstatic with_lock_baton_t *
328299742Sdimchain_lock_baton(lock_id_t lock_id,
329299742Sdim                 with_lock_baton_t *nested)
330251881Speter{
331299742Sdim  /* Use the same pool for batons along the lock chain. */
332299742Sdim  apr_pool_t *lock_pool = nested->lock_pool;
333299742Sdim  with_lock_baton_t *result = apr_pcalloc(lock_pool, sizeof(*result));
334251881Speter
335299742Sdim  /* All locks along the chain operate on the same FS. */
336299742Sdim  result->fs = nested->fs;
337251881Speter
338299742Sdim  /* Execution of this baton means acquiring the nested lock and its
339299742Sdim     execution. */
340299742Sdim  result->body = with_lock;
341299742Sdim  result->baton = nested;
342251881Speter
343299742Sdim  /* Shared among all locks along the chain. */
344299742Sdim  result->lock_pool = lock_pool;
345251881Speter
346299742Sdim  /* We are the new outermost lock but surely not the innermost lock. */
347299742Sdim  result->is_inner_most_lock = FALSE;
348299742Sdim  result->is_outer_most_lock = TRUE;
349299742Sdim  nested->is_outer_most_lock = FALSE;
350251881Speter
351299742Sdim  /* Select mutex and lock file path depending on LOCK_ID.
352299742Sdim     Also, initialize dependent members (IS_GLOBAL_LOCK only, ATM). */
353299742Sdim  init_lock_baton(result, lock_id);
354251881Speter
355299742Sdim  return result;
356251881Speter}
357251881Speter
358251881Spetersvn_error_t *
359251881Spetersvn_fs_fs__with_write_lock(svn_fs_t *fs,
360251881Speter                           svn_error_t *(*body)(void *baton,
361251881Speter                                                apr_pool_t *pool),
362251881Speter                           void *baton,
363251881Speter                           apr_pool_t *pool)
364251881Speter{
365299742Sdim  return svn_error_trace(
366299742Sdim           with_lock(create_lock_baton(fs, write_lock, body, baton, pool),
367299742Sdim                     pool));
368251881Speter}
369251881Speter
370299742Sdimsvn_error_t *
371299742Sdimsvn_fs_fs__with_pack_lock(svn_fs_t *fs,
372299742Sdim                          svn_error_t *(*body)(void *baton,
373299742Sdim                                               apr_pool_t *pool),
374299742Sdim                          void *baton,
375299742Sdim                          apr_pool_t *pool)
376251881Speter{
377299742Sdim  return svn_error_trace(
378299742Sdim           with_lock(create_lock_baton(fs, pack_lock, body, baton, pool),
379299742Sdim                     pool));
380251881Speter}
381251881Speter
382299742Sdimsvn_error_t *
383299742Sdimsvn_fs_fs__with_txn_current_lock(svn_fs_t *fs,
384299742Sdim                                 svn_error_t *(*body)(void *baton,
385299742Sdim                                                      apr_pool_t *pool),
386299742Sdim                                 void *baton,
387299742Sdim                                 apr_pool_t *pool)
388251881Speter{
389299742Sdim  return svn_error_trace(
390299742Sdim           with_lock(create_lock_baton(fs, txn_lock, body, baton, pool),
391299742Sdim                     pool));
392251881Speter}
393251881Speter
394299742Sdimsvn_error_t *
395299742Sdimsvn_fs_fs__with_all_locks(svn_fs_t *fs,
396299742Sdim                          svn_error_t *(*body)(void *baton,
397299742Sdim                                               apr_pool_t *pool),
398299742Sdim                          void *baton,
399299742Sdim                          apr_pool_t *pool)
400251881Speter{
401299742Sdim  fs_fs_data_t *ffd = fs->fsap_data;
402251881Speter
403299742Sdim  /* Be sure to use the correct lock ordering as documented in
404299742Sdim     fs_fs_shared_data_t.  The lock chain is being created in
405299742Sdim     innermost (last to acquire) -> outermost (first to acquire) order. */
406299742Sdim  with_lock_baton_t *lock_baton
407299742Sdim    = create_lock_baton(fs, write_lock, body, baton, pool);
408251881Speter
409299742Sdim  if (ffd->format >= SVN_FS_FS__MIN_PACK_LOCK_FORMAT)
410299742Sdim    lock_baton = chain_lock_baton(pack_lock, lock_baton);
411251881Speter
412299742Sdim  if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
413299742Sdim    lock_baton = chain_lock_baton(txn_lock, lock_baton);
414251881Speter
415299742Sdim  return svn_error_trace(with_lock(lock_baton, pool));
416251881Speter}
417251881Speter
418251881Speter
419251881Speter
420251881Speter
421251881Speter
422251881Speter/* Check that BUF, a nul-terminated buffer of text from format file PATH,
423251881Speter   contains only digits at OFFSET and beyond, raising an error if not.
424251881Speter
425251881Speter   Uses POOL for temporary allocation. */
426251881Speterstatic svn_error_t *
427251881Spetercheck_format_file_buffer_numeric(const char *buf, apr_off_t offset,
428251881Speter                                 const char *path, apr_pool_t *pool)
429251881Speter{
430299742Sdim  return svn_fs_fs__check_file_buffer_numeric(buf, offset, path, "Format",
431299742Sdim                                              pool);
432251881Speter}
433251881Speter
434262253Speter/* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
435262253Speter   number is not the same as a format number supported by this
436262253Speter   Subversion. */
437262253Speterstatic svn_error_t *
438262253Spetercheck_format(int format)
439262253Speter{
440262253Speter  /* Blacklist.  These formats may be either younger or older than
441262253Speter     SVN_FS_FS__FORMAT_NUMBER, but we don't support them. */
442262253Speter  if (format == SVN_FS_FS__PACKED_REVPROP_SQLITE_DEV_FORMAT)
443262253Speter    return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
444262253Speter                             _("Found format '%d', only created by "
445262253Speter                               "unreleased dev builds; see "
446262253Speter                               "http://subversion.apache.org"
447262253Speter                               "/docs/release-notes/1.7#revprop-packing"),
448262253Speter                             format);
449262253Speter
450262253Speter  /* We support all formats from 1-current simultaneously */
451262253Speter  if (1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER)
452262253Speter    return SVN_NO_ERROR;
453262253Speter
454262253Speter  return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
455262253Speter     _("Expected FS format between '1' and '%d'; found format '%d'"),
456262253Speter     SVN_FS_FS__FORMAT_NUMBER, format);
457262253Speter}
458262253Speter
459251881Speter/* Read the format number and maximum number of files per directory
460299742Sdim   from PATH and return them in *PFORMAT, *MAX_FILES_PER_DIR and
461299742Sdim   USE_LOG_ADDRESSIONG respectively.
462251881Speter
463251881Speter   *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and
464251881Speter   will be set to zero if a linear scheme should be used.
465299742Sdim   *USE_LOG_ADDRESSIONG is obtained from the 'addressing' format option,
466299742Sdim   and will be set to FALSE for physical addressing.
467251881Speter
468251881Speter   Use POOL for temporary allocation. */
469251881Speterstatic svn_error_t *
470299742Sdimread_format(int *pformat,
471299742Sdim            int *max_files_per_dir,
472299742Sdim            svn_boolean_t *use_log_addressing,
473299742Sdim            const char *path,
474299742Sdim            apr_pool_t *pool)
475251881Speter{
476251881Speter  svn_error_t *err;
477251881Speter  svn_stream_t *stream;
478251881Speter  svn_stringbuf_t *content;
479251881Speter  svn_stringbuf_t *buf;
480251881Speter  svn_boolean_t eos = FALSE;
481251881Speter
482251881Speter  err = svn_stringbuf_from_file2(&content, path, pool);
483251881Speter  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
484251881Speter    {
485251881Speter      /* Treat an absent format file as format 1.  Do not try to
486251881Speter         create the format file on the fly, because the repository
487251881Speter         might be read-only for us, or this might be a read-only
488251881Speter         operation, and the spirit of FSFS is to make no changes
489251881Speter         whatseover in read-only operations.  See thread starting at
490251881Speter         http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=97600
491251881Speter         for more. */
492251881Speter      svn_error_clear(err);
493251881Speter      *pformat = 1;
494251881Speter      *max_files_per_dir = 0;
495309512Speter      *use_log_addressing = FALSE;
496251881Speter
497251881Speter      return SVN_NO_ERROR;
498251881Speter    }
499251881Speter  SVN_ERR(err);
500251881Speter
501251881Speter  stream = svn_stream_from_stringbuf(content, pool);
502251881Speter  SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool));
503251881Speter  if (buf->len == 0 && eos)
504251881Speter    {
505251881Speter      /* Return a more useful error message. */
506251881Speter      return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
507251881Speter                               _("Can't read first line of format file '%s'"),
508251881Speter                               svn_dirent_local_style(path, pool));
509251881Speter    }
510251881Speter
511251881Speter  /* Check that the first line contains only digits. */
512251881Speter  SVN_ERR(check_format_file_buffer_numeric(buf->data, 0, path, pool));
513251881Speter  SVN_ERR(svn_cstring_atoi(pformat, buf->data));
514251881Speter
515262253Speter  /* Check that we support this format at all */
516262253Speter  SVN_ERR(check_format(*pformat));
517262253Speter
518251881Speter  /* Set the default values for anything that can be set via an option. */
519251881Speter  *max_files_per_dir = 0;
520299742Sdim  *use_log_addressing = FALSE;
521251881Speter
522251881Speter  /* Read any options. */
523251881Speter  while (!eos)
524251881Speter    {
525251881Speter      SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool));
526251881Speter      if (buf->len == 0)
527251881Speter        break;
528251881Speter
529251881Speter      if (*pformat >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT &&
530251881Speter          strncmp(buf->data, "layout ", 7) == 0)
531251881Speter        {
532251881Speter          if (strcmp(buf->data + 7, "linear") == 0)
533251881Speter            {
534251881Speter              *max_files_per_dir = 0;
535251881Speter              continue;
536251881Speter            }
537251881Speter
538251881Speter          if (strncmp(buf->data + 7, "sharded ", 8) == 0)
539251881Speter            {
540251881Speter              /* Check that the argument is numeric. */
541251881Speter              SVN_ERR(check_format_file_buffer_numeric(buf->data, 15, path, pool));
542251881Speter              SVN_ERR(svn_cstring_atoi(max_files_per_dir, buf->data + 15));
543251881Speter              continue;
544251881Speter            }
545251881Speter        }
546251881Speter
547299742Sdim      if (*pformat >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT &&
548299742Sdim          strncmp(buf->data, "addressing ", 11) == 0)
549299742Sdim        {
550299742Sdim          if (strcmp(buf->data + 11, "physical") == 0)
551299742Sdim            {
552299742Sdim              *use_log_addressing = FALSE;
553299742Sdim              continue;
554299742Sdim            }
555299742Sdim
556299742Sdim          if (strcmp(buf->data + 11, "logical") == 0)
557299742Sdim            {
558299742Sdim              *use_log_addressing = TRUE;
559299742Sdim              continue;
560299742Sdim            }
561299742Sdim        }
562299742Sdim
563251881Speter      return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
564251881Speter         _("'%s' contains invalid filesystem format option '%s'"),
565251881Speter         svn_dirent_local_style(path, pool), buf->data);
566251881Speter    }
567251881Speter
568299742Sdim  /* Non-sharded repositories never use logical addressing.
569299742Sdim   * If the format file is inconsistent in that respect, something
570299742Sdim   * probably went wrong.
571299742Sdim   */
572299742Sdim  if (*use_log_addressing && !*max_files_per_dir)
573299742Sdim    return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
574299742Sdim       _("'%s' specifies logical addressing for a non-sharded repository"),
575299742Sdim       svn_dirent_local_style(path, pool));
576299742Sdim
577251881Speter  return SVN_NO_ERROR;
578251881Speter}
579251881Speter
580299742Sdim/* Write the format number, maximum number of files per directory and
581299742Sdim   the addressing scheme to a new format file in PATH, possibly expecting
582299742Sdim   to overwrite a previously existing file.
583251881Speter
584251881Speter   Use POOL for temporary allocation. */
585299742Sdimsvn_error_t *
586299742Sdimsvn_fs_fs__write_format(svn_fs_t *fs,
587299742Sdim                        svn_boolean_t overwrite,
588299742Sdim                        apr_pool_t *pool)
589251881Speter{
590251881Speter  svn_stringbuf_t *sb;
591299742Sdim  fs_fs_data_t *ffd = fs->fsap_data;
592299742Sdim  const char *path = path_format(fs, pool);
593251881Speter
594299742Sdim  SVN_ERR_ASSERT(1 <= ffd->format
595299742Sdim                 && ffd->format <= SVN_FS_FS__FORMAT_NUMBER);
596251881Speter
597299742Sdim  sb = svn_stringbuf_createf(pool, "%d\n", ffd->format);
598251881Speter
599299742Sdim  if (ffd->format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
600251881Speter    {
601299742Sdim      if (ffd->max_files_per_dir)
602251881Speter        svn_stringbuf_appendcstr(sb, apr_psprintf(pool, "layout sharded %d\n",
603299742Sdim                                                  ffd->max_files_per_dir));
604251881Speter      else
605251881Speter        svn_stringbuf_appendcstr(sb, "layout linear\n");
606251881Speter    }
607251881Speter
608299742Sdim  if (ffd->format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT)
609299742Sdim    {
610299742Sdim      if (ffd->use_log_addressing)
611299742Sdim        svn_stringbuf_appendcstr(sb, "addressing logical\n");
612299742Sdim      else
613299742Sdim        svn_stringbuf_appendcstr(sb, "addressing physical\n");
614299742Sdim    }
615299742Sdim
616251881Speter  /* svn_io_write_version_file() does a load of magic to allow it to
617251881Speter     replace version files that already exist.  We only need to do
618251881Speter     that when we're allowed to overwrite an existing file. */
619251881Speter  if (! overwrite)
620251881Speter    {
621251881Speter      /* Create the file */
622251881Speter      SVN_ERR(svn_io_file_create(path, sb->data, pool));
623251881Speter    }
624251881Speter  else
625251881Speter    {
626299742Sdim      SVN_ERR(svn_io_write_atomic(path, sb->data, sb->len,
627299742Sdim                                  NULL /* copy_perms_path */, pool));
628251881Speter    }
629251881Speter
630251881Speter  /* And set the perms to make it read only */
631251881Speter  return svn_io_set_file_read_only(path, FALSE, pool);
632251881Speter}
633251881Speter
634251881Spetersvn_boolean_t
635251881Spetersvn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs)
636251881Speter{
637251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
638251881Speter  return ffd->format >= SVN_FS_FS__MIN_MERGEINFO_FORMAT;
639251881Speter}
640251881Speter
641299742Sdim/* Check that BLOCK_SIZE is a valid block / page size, i.e. it is within
642299742Sdim * the range of what the current system may address in RAM and it is a
643299742Sdim * power of 2.  Assume that the element size within the block is ITEM_SIZE.
644299742Sdim * Use SCRATCH_POOL for temporary allocations.
645299742Sdim */
646299742Sdimstatic svn_error_t *
647299742Sdimverify_block_size(apr_int64_t block_size,
648299742Sdim                  apr_size_t item_size,
649299742Sdim                  const char *name,
650299742Sdim                  apr_pool_t *scratch_pool
651299742Sdim                 )
652299742Sdim{
653299742Sdim  /* Limit range. */
654299742Sdim  if (block_size <= 0)
655299742Sdim    return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
656299742Sdim                             _("%s is too small for fsfs.conf setting '%s'."),
657299742Sdim                             apr_psprintf(scratch_pool,
658299742Sdim                                          "%" APR_INT64_T_FMT,
659299742Sdim                                          block_size),
660299742Sdim                             name);
661299742Sdim
662299742Sdim  if (block_size > SVN_MAX_OBJECT_SIZE / item_size)
663299742Sdim    return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
664299742Sdim                             _("%s is too large for fsfs.conf setting '%s'."),
665299742Sdim                             apr_psprintf(scratch_pool,
666299742Sdim                                          "%" APR_INT64_T_FMT,
667299742Sdim                                          block_size),
668299742Sdim                             name);
669299742Sdim
670299742Sdim  /* Ensure it is a power of two.
671299742Sdim   * For positive X,  X & (X-1) will reset the lowest bit set.
672299742Sdim   * If the result is 0, at most one bit has been set. */
673299742Sdim  if (0 != (block_size & (block_size - 1)))
674299742Sdim    return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
675299742Sdim                             _("%s is invalid for fsfs.conf setting '%s' "
676299742Sdim                               "because it is not a power of 2."),
677299742Sdim                             apr_psprintf(scratch_pool,
678299742Sdim                                          "%" APR_INT64_T_FMT,
679299742Sdim                                          block_size),
680299742Sdim                             name);
681299742Sdim
682299742Sdim  return SVN_NO_ERROR;
683299742Sdim}
684299742Sdim
685251881Speter/* Read the configuration information of the file system at FS_PATH
686299742Sdim * and set the respective values in FFD.  Use pools as usual.
687251881Speter */
688251881Speterstatic svn_error_t *
689251881Speterread_config(fs_fs_data_t *ffd,
690251881Speter            const char *fs_path,
691299742Sdim            apr_pool_t *result_pool,
692299742Sdim            apr_pool_t *scratch_pool)
693251881Speter{
694299742Sdim  svn_config_t *config;
695251881Speter
696299742Sdim  SVN_ERR(svn_config_read3(&config,
697299742Sdim                           svn_dirent_join(fs_path, PATH_CONFIG, scratch_pool),
698299742Sdim                           FALSE, FALSE, FALSE, scratch_pool));
699299742Sdim
700251881Speter  /* Initialize ffd->rep_sharing_allowed. */
701251881Speter  if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
702299742Sdim    SVN_ERR(svn_config_get_bool(config, &ffd->rep_sharing_allowed,
703251881Speter                                CONFIG_SECTION_REP_SHARING,
704251881Speter                                CONFIG_OPTION_ENABLE_REP_SHARING, TRUE));
705251881Speter  else
706251881Speter    ffd->rep_sharing_allowed = FALSE;
707251881Speter
708251881Speter  /* Initialize deltification settings in ffd. */
709251881Speter  if (ffd->format >= SVN_FS_FS__MIN_DELTIFICATION_FORMAT)
710251881Speter    {
711299742Sdim      apr_int64_t compression_level;
712299742Sdim
713299742Sdim      SVN_ERR(svn_config_get_bool(config, &ffd->deltify_directories,
714251881Speter                                  CONFIG_SECTION_DELTIFICATION,
715251881Speter                                  CONFIG_OPTION_ENABLE_DIR_DELTIFICATION,
716299742Sdim                                  TRUE));
717299742Sdim      SVN_ERR(svn_config_get_bool(config, &ffd->deltify_properties,
718251881Speter                                  CONFIG_SECTION_DELTIFICATION,
719251881Speter                                  CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION,
720299742Sdim                                  TRUE));
721299742Sdim      SVN_ERR(svn_config_get_int64(config, &ffd->max_deltification_walk,
722251881Speter                                   CONFIG_SECTION_DELTIFICATION,
723251881Speter                                   CONFIG_OPTION_MAX_DELTIFICATION_WALK,
724251881Speter                                   SVN_FS_FS_MAX_DELTIFICATION_WALK));
725299742Sdim      SVN_ERR(svn_config_get_int64(config, &ffd->max_linear_deltification,
726251881Speter                                   CONFIG_SECTION_DELTIFICATION,
727251881Speter                                   CONFIG_OPTION_MAX_LINEAR_DELTIFICATION,
728251881Speter                                   SVN_FS_FS_MAX_LINEAR_DELTIFICATION));
729299742Sdim
730299742Sdim      SVN_ERR(svn_config_get_int64(config, &compression_level,
731299742Sdim                                   CONFIG_SECTION_DELTIFICATION,
732299742Sdim                                   CONFIG_OPTION_COMPRESSION_LEVEL,
733299742Sdim                                   SVN_DELTA_COMPRESSION_LEVEL_DEFAULT));
734299742Sdim      ffd->delta_compression_level
735299742Sdim        = (int)MIN(MAX(SVN_DELTA_COMPRESSION_LEVEL_NONE, compression_level),
736299742Sdim                   SVN_DELTA_COMPRESSION_LEVEL_MAX);
737251881Speter    }
738251881Speter  else
739251881Speter    {
740251881Speter      ffd->deltify_directories = FALSE;
741251881Speter      ffd->deltify_properties = FALSE;
742251881Speter      ffd->max_deltification_walk = SVN_FS_FS_MAX_DELTIFICATION_WALK;
743251881Speter      ffd->max_linear_deltification = SVN_FS_FS_MAX_LINEAR_DELTIFICATION;
744299742Sdim      ffd->delta_compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
745251881Speter    }
746251881Speter
747251881Speter  /* Initialize revprop packing settings in ffd. */
748251881Speter  if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
749251881Speter    {
750299742Sdim      SVN_ERR(svn_config_get_bool(config, &ffd->compress_packed_revprops,
751251881Speter                                  CONFIG_SECTION_PACKED_REVPROPS,
752251881Speter                                  CONFIG_OPTION_COMPRESS_PACKED_REVPROPS,
753251881Speter                                  FALSE));
754299742Sdim      SVN_ERR(svn_config_get_int64(config, &ffd->revprop_pack_size,
755251881Speter                                   CONFIG_SECTION_PACKED_REVPROPS,
756251881Speter                                   CONFIG_OPTION_REVPROP_PACK_SIZE,
757251881Speter                                   ffd->compress_packed_revprops
758299742Sdim                                       ? 0x10
759299742Sdim                                       : 0x4));
760251881Speter
761251881Speter      ffd->revprop_pack_size *= 1024;
762251881Speter    }
763251881Speter  else
764251881Speter    {
765251881Speter      ffd->revprop_pack_size = 0x10000;
766251881Speter      ffd->compress_packed_revprops = FALSE;
767251881Speter    }
768251881Speter
769299742Sdim  if (ffd->format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT)
770299742Sdim    {
771299742Sdim      SVN_ERR(svn_config_get_int64(config, &ffd->block_size,
772299742Sdim                                   CONFIG_SECTION_IO,
773299742Sdim                                   CONFIG_OPTION_BLOCK_SIZE,
774299742Sdim                                   64));
775299742Sdim      SVN_ERR(svn_config_get_int64(config, &ffd->l2p_page_size,
776299742Sdim                                   CONFIG_SECTION_IO,
777299742Sdim                                   CONFIG_OPTION_L2P_PAGE_SIZE,
778299742Sdim                                   0x2000));
779299742Sdim      SVN_ERR(svn_config_get_int64(config, &ffd->p2l_page_size,
780299742Sdim                                   CONFIG_SECTION_IO,
781299742Sdim                                   CONFIG_OPTION_P2L_PAGE_SIZE,
782299742Sdim                                   0x400));
783299742Sdim
784299742Sdim      /* Don't accept unreasonable or illegal values.
785299742Sdim       * Block size and P2L page size are in kbytes;
786299742Sdim       * L2P blocks are arrays of apr_off_t. */
787299742Sdim      SVN_ERR(verify_block_size(ffd->block_size, 0x400,
788299742Sdim                                CONFIG_OPTION_BLOCK_SIZE, scratch_pool));
789299742Sdim      SVN_ERR(verify_block_size(ffd->p2l_page_size, 0x400,
790299742Sdim                                CONFIG_OPTION_P2L_PAGE_SIZE, scratch_pool));
791299742Sdim      SVN_ERR(verify_block_size(ffd->l2p_page_size, sizeof(apr_off_t),
792299742Sdim                                CONFIG_OPTION_L2P_PAGE_SIZE, scratch_pool));
793299742Sdim
794299742Sdim      /* convert kBytes to bytes */
795299742Sdim      ffd->block_size *= 0x400;
796299742Sdim      ffd->p2l_page_size *= 0x400;
797299742Sdim      /* L2P pages are in entries - not in (k)Bytes */
798299742Sdim    }
799299742Sdim  else
800299742Sdim    {
801299742Sdim      /* should be irrelevant but we initialize them anyway */
802299742Sdim      ffd->block_size = 0x1000; /* Matches default APR file buffer size. */
803299742Sdim      ffd->l2p_page_size = 0x2000;    /* Matches above default. */
804299742Sdim      ffd->p2l_page_size = 0x100000;  /* Matches above default in bytes. */
805299742Sdim    }
806299742Sdim
807299742Sdim  if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
808299742Sdim    {
809299742Sdim      SVN_ERR(svn_config_get_bool(config, &ffd->pack_after_commit,
810299742Sdim                                  CONFIG_SECTION_DEBUG,
811299742Sdim                                  CONFIG_OPTION_PACK_AFTER_COMMIT,
812299742Sdim                                  FALSE));
813299742Sdim    }
814299742Sdim  else
815299742Sdim    {
816299742Sdim      ffd->pack_after_commit = FALSE;
817299742Sdim    }
818299742Sdim
819299742Sdim  /* memcached configuration */
820299742Sdim  SVN_ERR(svn_cache__make_memcache_from_config(&ffd->memcache, config,
821299742Sdim                                               result_pool, scratch_pool));
822299742Sdim
823299742Sdim  SVN_ERR(svn_config_get_bool(config, &ffd->fail_stop,
824299742Sdim                              CONFIG_SECTION_CACHES, CONFIG_OPTION_FAIL_STOP,
825299742Sdim                              FALSE));
826299742Sdim
827251881Speter  return SVN_NO_ERROR;
828251881Speter}
829251881Speter
830251881Speterstatic svn_error_t *
831251881Speterwrite_config(svn_fs_t *fs,
832251881Speter             apr_pool_t *pool)
833251881Speter{
834251881Speter#define NL APR_EOL_STR
835251881Speter  static const char * const fsfs_conf_contents =
836251881Speter"### This file controls the configuration of the FSFS filesystem."           NL
837251881Speter""                                                                           NL
838251881Speter"[" SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "]"                          NL
839251881Speter"### These options name memcached servers used to cache internal FSFS"       NL
840251881Speter"### data.  See http://www.danga.com/memcached/ for more information on"     NL
841251881Speter"### memcached.  To use memcached with FSFS, run one or more memcached"      NL
842251881Speter"### servers, and specify each of them as an option like so:"                NL
843251881Speter"# first-server = 127.0.0.1:11211"                                           NL
844251881Speter"# remote-memcached = mymemcached.corp.example.com:11212"                    NL
845251881Speter"### The option name is ignored; the value is of the form HOST:PORT."        NL
846251881Speter"### memcached servers can be shared between multiple repositories;"         NL
847251881Speter"### however, if you do this, you *must* ensure that repositories have"      NL
848251881Speter"### distinct UUIDs and paths, or else cached data from one repository"      NL
849251881Speter"### might be used by another accidentally.  Note also that memcached has"   NL
850251881Speter"### no authentication for reads or writes, so you must ensure that your"    NL
851251881Speter"### memcached servers are only accessible by trusted users."                NL
852251881Speter""                                                                           NL
853251881Speter"[" CONFIG_SECTION_CACHES "]"                                                NL
854251881Speter"### When a cache-related error occurs, normally Subversion ignores it"      NL
855251881Speter"### and continues, logging an error if the server is appropriately"         NL
856251881Speter"### configured (and ignoring it with file:// access).  To make"             NL
857251881Speter"### Subversion never ignore cache errors, uncomment this line."             NL
858251881Speter"# " CONFIG_OPTION_FAIL_STOP " = true"                                       NL
859251881Speter""                                                                           NL
860251881Speter"[" CONFIG_SECTION_REP_SHARING "]"                                           NL
861251881Speter"### To conserve space, the filesystem can optionally avoid storing"         NL
862251881Speter"### duplicate representations.  This comes at a slight cost in"             NL
863251881Speter"### performance, as maintaining a database of shared representations can"   NL
864251881Speter"### increase commit times.  The space savings are dependent upon the size"  NL
865251881Speter"### of the repository, the number of objects it contains and the amount of" NL
866251881Speter"### duplication between them, usually a function of the branching and"      NL
867251881Speter"### merging process."                                                       NL
868251881Speter"###"                                                                        NL
869251881Speter"### The following parameter enables rep-sharing in the repository.  It can" NL
870251881Speter"### be switched on and off at will, but for best space-saving results"      NL
871251881Speter"### should be enabled consistently over the life of the repository."        NL
872251881Speter"### 'svnadmin verify' will check the rep-cache regardless of this setting." NL
873251881Speter"### rep-sharing is enabled by default."                                     NL
874251881Speter"# " CONFIG_OPTION_ENABLE_REP_SHARING " = true"                              NL
875251881Speter""                                                                           NL
876251881Speter"[" CONFIG_SECTION_DELTIFICATION "]"                                         NL
877251881Speter"### To conserve space, the filesystem stores data as differences against"   NL
878251881Speter"### existing representations.  This comes at a slight cost in performance," NL
879251881Speter"### as calculating differences can increase commit times.  Reading data"    NL
880251881Speter"### will also create higher CPU load and the data will be fragmented."      NL
881251881Speter"### Since deltification tends to save significant amounts of disk space,"   NL
882251881Speter"### the overall I/O load can actually be lower."                            NL
883251881Speter"###"                                                                        NL
884251881Speter"### The options in this section allow for tuning the deltification"         NL
885251881Speter"### strategy.  Their effects on data size and server performance may vary"  NL
886251881Speter"### from one repository to another.  Versions prior to 1.8 will ignore"     NL
887251881Speter"### this section."                                                          NL
888251881Speter"###"                                                                        NL
889251881Speter"### The following parameter enables deltification for directories. It can"  NL
890251881Speter"### be switched on and off at will, but for best space-saving results"      NL
891299742Sdim"### should be enabled consistently over the lifetime of the repository."    NL
892251881Speter"### Repositories containing large directories will benefit greatly."        NL
893299742Sdim"### In rarely accessed repositories, the I/O overhead may be significant"   NL
894299742Sdim"### as caches will most likely be low."                                     NL
895299742Sdim"### directory deltification is enabled by default."                         NL
896299742Sdim"# " CONFIG_OPTION_ENABLE_DIR_DELTIFICATION " = true"                        NL
897251881Speter"###"                                                                        NL
898251881Speter"### The following parameter enables deltification for properties on files"  NL
899251881Speter"### and directories.  Overall, this is a minor tuning option but can save"  NL
900251881Speter"### some disk space if you merge frequently or frequently change node"      NL
901251881Speter"### properties.  You should not activate this if rep-sharing has been"      NL
902251881Speter"### disabled because this may result in a net increase in repository size." NL
903299742Sdim"### property deltification is enabled by default."                          NL
904299742Sdim"# " CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION " = true"                      NL
905251881Speter"###"                                                                        NL
906251881Speter"### During commit, the server may need to walk the whole change history of" NL
907251881Speter"### of a given node to find a suitable deltification base.  This linear"    NL
908251881Speter"### process can impact commit times, svnadmin load and similar operations." NL
909251881Speter"### This setting limits the depth of the deltification history.  If the"    NL
910251881Speter"### threshold has been reached, the node will be stored as fulltext and a"  NL
911251881Speter"### new deltification history begins."                                      NL
912251881Speter"### Note, this is unrelated to svn log."                                    NL
913251881Speter"### Very large values rarely provide significant additional savings but"    NL
914251881Speter"### can impact performance greatly - in particular if directory"            NL
915251881Speter"### deltification has been activated.  Very small values may be useful in"  NL
916251881Speter"### repositories that are dominated by large, changing binaries."           NL
917251881Speter"### Should be a power of two minus 1.  A value of 0 will effectively"       NL
918251881Speter"### disable deltification."                                                 NL
919251881Speter"### For 1.8, the default value is 1023; earlier versions have no limit."    NL
920251881Speter"# " CONFIG_OPTION_MAX_DELTIFICATION_WALK " = 1023"                          NL
921251881Speter"###"                                                                        NL
922251881Speter"### The skip-delta scheme used by FSFS tends to repeatably store redundant" NL
923251881Speter"### delta information where a simple delta against the latest version is"   NL
924251881Speter"### often smaller.  By default, 1.8+ will therefore use skip deltas only"   NL
925251881Speter"### after the linear chain of deltas has grown beyond the threshold"        NL
926251881Speter"### specified by this setting."                                             NL
927251881Speter"### Values up to 64 can result in some reduction in repository size for"    NL
928251881Speter"### the cost of quickly increasing I/O and CPU costs. Similarly, smaller"   NL
929251881Speter"### numbers can reduce those costs at the cost of more disk space.  For"    NL
930251881Speter"### rarely read repositories or those containing larger binaries, this may" NL
931251881Speter"### present a better trade-off."                                            NL
932251881Speter"### Should be a power of two.  A value of 1 or smaller will cause the"      NL
933251881Speter"### exclusive use of skip-deltas (as in pre-1.8)."                          NL
934251881Speter"### For 1.8, the default value is 16; earlier versions use 1."              NL
935251881Speter"# " CONFIG_OPTION_MAX_LINEAR_DELTIFICATION " = 16"                          NL
936299742Sdim"###"                                                                        NL
937299742Sdim"### After deltification, we compress the data through zlib to minimize on-" NL
938299742Sdim"### disk size.  That can be an expensive and ineffective process.  This"    NL
939299742Sdim"### setting controls the usage of zlib in future revisions."                NL
940299742Sdim"### Revisions with highly compressible data in them may shrink in size"     NL
941299742Sdim"### if the setting is increased but may take much longer to commit.  The"   NL
942299742Sdim"### time taken to uncompress that data again is widely independent of the"  NL
943299742Sdim"### compression level."                                                     NL
944299742Sdim"### Compression will be ineffective if the incoming content is already"     NL
945299742Sdim"### highly compressed.  In that case, disabling the compression entirely"   NL
946299742Sdim"### will speed up commits as well as reading the data.  Repositories with"  NL
947299742Sdim"### many small compressible files (source code) but also a high percentage" NL
948299742Sdim"### of large incompressible ones (artwork) may benefit from compression"    NL
949299742Sdim"### levels lowered to e.g. 1."                                              NL
950299742Sdim"### Valid values are 0 to 9 with 9 providing the highest compression ratio" NL
951299742Sdim"### and 0 disabling it altogether."                                         NL
952299742Sdim"### The default value is 5."                                                NL
953299742Sdim"# " CONFIG_OPTION_COMPRESSION_LEVEL " = 5"                                  NL
954251881Speter""                                                                           NL
955251881Speter"[" CONFIG_SECTION_PACKED_REVPROPS "]"                                       NL
956251881Speter"### This parameter controls the size (in kBytes) of packed revprop files."  NL
957251881Speter"### Revprops of consecutive revisions will be concatenated into a single"   NL
958251881Speter"### file up to but not exceeding the threshold given here.  However, each"  NL
959251881Speter"### pack file may be much smaller and revprops of a single revision may be" NL
960251881Speter"### much larger than the limit set here.  The threshold will be applied"    NL
961251881Speter"### before optional compression takes place."                               NL
962251881Speter"### Large values will reduce disk space usage at the expense of increased"  NL
963299742Sdim"### latency and CPU usage reading and changing individual revprops."        NL
964299742Sdim"### Values smaller than 4 kByte will not improve latency any further and "  NL
965299742Sdim"### quickly render revprop packing ineffective."                            NL
966299742Sdim"### revprop-pack-size is 4 kBytes by default for non-compressed revprop"    NL
967299742Sdim"### pack files and 16 kBytes when compression has been enabled."            NL
968299742Sdim"# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 4"                                  NL
969251881Speter"###"                                                                        NL
970251881Speter"### To save disk space, packed revprop files may be compressed.  Standard"  NL
971251881Speter"### revprops tend to allow for very effective compression.  Reading and"    NL
972299742Sdim"### even more so writing, become significantly more CPU intensive."         NL
973251881Speter"### Compressing packed revprops is disabled by default."                    NL
974251881Speter"# " CONFIG_OPTION_COMPRESS_PACKED_REVPROPS " = false"                       NL
975299742Sdim""                                                                           NL
976299742Sdim"[" CONFIG_SECTION_IO "]"                                                    NL
977299742Sdim"### Parameters in this section control the data access granularity in"      NL
978299742Sdim"### format 7 repositories and later.  The defaults should translate into"   NL
979299742Sdim"### decent performance over a wide range of setups."                        NL
980299742Sdim"###"                                                                        NL
981299742Sdim"### When a specific piece of information needs to be read from disk,  a"    NL
982299742Sdim"### data block is being read at once and its contents are being cached."    NL
983299742Sdim"### If the repository is being stored on a RAID, the block size should be"  NL
984299742Sdim"### either 50% or 100% of RAID block size / granularity.  Also, your file"  NL
985299742Sdim"### system blocks/clusters should be properly aligned and sized.  In that"  NL
986299742Sdim"### setup, each access will hit only one disk (minimizes I/O load) but"     NL
987299742Sdim"### uses all the data provided by the disk in a single access."             NL
988299742Sdim"### For SSD-based storage systems, slightly lower values around 16 kB"      NL
989299742Sdim"### may improve latency while still maximizing throughput.  If block-read"  NL
990299742Sdim"### has not been enabled, this will be capped to 4 kBytes."                 NL
991299742Sdim"### Can be changed at any time but must be a power of 2."                   NL
992299742Sdim"### block-size is given in kBytes and with a default of 64 kBytes."         NL
993299742Sdim"# " CONFIG_OPTION_BLOCK_SIZE " = 64"                                        NL
994299742Sdim"###"                                                                        NL
995299742Sdim"### The log-to-phys index maps data item numbers to offsets within the"     NL
996299742Sdim"### rev or pack file.  This index is organized in pages of a fixed maximum" NL
997299742Sdim"### capacity.  To access an item, the page table and the respective page"   NL
998299742Sdim"### must be read."                                                          NL
999299742Sdim"### This parameter only affects revisions with thousands of changed paths." NL
1000299742Sdim"### If you have several extremely large revisions (~1 mio changes), think"  NL
1001299742Sdim"### about increasing this setting.  Reducing the value will rarely result"  NL
1002299742Sdim"### in a net speedup."                                                      NL
1003299742Sdim"### This is an expert setting.  Must be a power of 2."                      NL
1004299742Sdim"### l2p-page-size is 8192 entries by default."                              NL
1005299742Sdim"# " CONFIG_OPTION_L2P_PAGE_SIZE " = 8192"                                   NL
1006299742Sdim"###"                                                                        NL
1007299742Sdim"### The phys-to-log index maps positions within the rev or pack file to"    NL
1008299742Sdim"### to data items,  i.e. describes what piece of information is being"      NL
1009299742Sdim"### stored at any particular offset.  The index describes the rev file"     NL
1010299742Sdim"### in chunks (pages) and keeps a global list of all those pages.  Large"   NL
1011299742Sdim"### pages mean a shorter page table but a larger per-page description of"   NL
1012299742Sdim"### data items in it.  The latency sweetspot depends on the change size"    NL
1013299742Sdim"### distribution but covers a relatively wide range."                       NL
1014299742Sdim"### If the repository contains very large files,  i.e. individual changes"  NL
1015299742Sdim"### of tens of MB each,  increasing the page size will shorten the index"   NL
1016299742Sdim"### file at the expense of a slightly increased latency in sections with"   NL
1017299742Sdim"### smaller changes."                                                       NL
1018299742Sdim"### For source code repositories, this should be about 16x the block-size." NL
1019299742Sdim"### Must be a power of 2."                                                  NL
1020299742Sdim"### p2l-page-size is given in kBytes and with a default of 1024 kBytes."    NL
1021299742Sdim"# " CONFIG_OPTION_P2L_PAGE_SIZE " = 1024"                                   NL
1022251881Speter;
1023251881Speter#undef NL
1024251881Speter  return svn_io_file_create(svn_dirent_join(fs->path, PATH_CONFIG, pool),
1025251881Speter                            fsfs_conf_contents, pool);
1026251881Speter}
1027251881Speter
1028299742Sdim/* Read / Evaluate the global configuration in FS->CONFIG to set up
1029299742Sdim * parameters in FS. */
1030251881Speterstatic svn_error_t *
1031299742Sdimread_global_config(svn_fs_t *fs)
1032251881Speter{
1033299742Sdim  fs_fs_data_t *ffd = fs->fsap_data;
1034251881Speter
1035299742Sdim  /* Providing a config hash is optional. */
1036299742Sdim  if (fs->config)
1037299742Sdim    ffd->use_block_read = svn_hash__get_bool(fs->config,
1038299742Sdim                                             SVN_FS_CONFIG_FSFS_BLOCK_READ,
1039299742Sdim                                             FALSE);
1040299742Sdim  else
1041299742Sdim    ffd->use_block_read = FALSE;
1042251881Speter
1043299742Sdim  /* Ignore the user-specified larger block size if we don't use block-read.
1044299742Sdim     Defaulting to 4k gives us the same access granularity in format 7 as in
1045299742Sdim     older formats. */
1046299742Sdim  if (!ffd->use_block_read)
1047299742Sdim    ffd->block_size = MIN(0x1000, ffd->block_size);
1048299742Sdim
1049251881Speter  return SVN_NO_ERROR;
1050251881Speter}
1051251881Speter
1052299742Sdim/* Read FS's UUID file and store the data in the FS struct. */
1053251881Speterstatic svn_error_t *
1054299742Sdimread_uuid(svn_fs_t *fs,
1055299742Sdim          apr_pool_t *scratch_pool)
1056251881Speter{
1057251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
1058299742Sdim  apr_file_t *uuid_file;
1059299742Sdim  char buf[APR_UUID_FORMATTED_LENGTH + 2];
1060299742Sdim  apr_size_t limit;
1061251881Speter
1062299742Sdim  /* Read the repository uuid. */
1063299742Sdim  SVN_ERR(svn_io_file_open(&uuid_file, path_uuid(fs, scratch_pool),
1064299742Sdim                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
1065299742Sdim                           scratch_pool));
1066251881Speter
1067299742Sdim  limit = sizeof(buf);
1068299742Sdim  SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, scratch_pool));
1069299742Sdim  fs->uuid = apr_pstrdup(fs->pool, buf);
1070299742Sdim
1071299742Sdim  /* Read the instance ID. */
1072299742Sdim  if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT)
1073299742Sdim    {
1074299742Sdim      limit = sizeof(buf);
1075299742Sdim      SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit,
1076299742Sdim                                      scratch_pool));
1077299742Sdim      ffd->instance_id = apr_pstrdup(fs->pool, buf);
1078299742Sdim    }
1079299742Sdim  else
1080299742Sdim    {
1081299742Sdim      ffd->instance_id = fs->uuid;
1082299742Sdim    }
1083299742Sdim
1084299742Sdim  SVN_ERR(svn_io_file_close(uuid_file, scratch_pool));
1085299742Sdim
1086299742Sdim  return SVN_NO_ERROR;
1087251881Speter}
1088251881Speter
1089251881Spetersvn_error_t *
1090299742Sdimsvn_fs_fs__read_format_file(svn_fs_t *fs, apr_pool_t *scratch_pool)
1091251881Speter{
1092251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
1093251881Speter  int format, max_files_per_dir;
1094299742Sdim  svn_boolean_t use_log_addressing;
1095251881Speter
1096299742Sdim  /* Read info from format file. */
1097299742Sdim  SVN_ERR(read_format(&format, &max_files_per_dir, &use_log_addressing,
1098299742Sdim                      path_format(fs, scratch_pool), scratch_pool));
1099251881Speter
1100299742Sdim  /* Now that we've got *all* info, store / update values in FFD. */
1101251881Speter  ffd->format = format;
1102251881Speter  ffd->max_files_per_dir = max_files_per_dir;
1103299742Sdim  ffd->use_log_addressing = use_log_addressing;
1104251881Speter
1105299742Sdim  return SVN_NO_ERROR;
1106299742Sdim}
1107251881Speter
1108299742Sdimsvn_error_t *
1109299742Sdimsvn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool)
1110299742Sdim{
1111299742Sdim  fs_fs_data_t *ffd = fs->fsap_data;
1112299742Sdim  fs->path = apr_pstrdup(fs->pool, path);
1113251881Speter
1114299742Sdim  /* Read the FS format file. */
1115299742Sdim  SVN_ERR(svn_fs_fs__read_format_file(fs, pool));
1116251881Speter
1117299742Sdim  /* Read in and cache the repository uuid. */
1118299742Sdim  SVN_ERR(read_uuid(fs, pool));
1119299742Sdim
1120251881Speter  /* Read the min unpacked revision. */
1121251881Speter  if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1122299742Sdim    SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, pool));
1123251881Speter
1124251881Speter  /* Read the configuration file. */
1125299742Sdim  SVN_ERR(read_config(ffd, fs->path, fs->pool, pool));
1126251881Speter
1127299742Sdim  /* Global configuration options. */
1128299742Sdim  SVN_ERR(read_global_config(fs));
1129299742Sdim
1130299742Sdim  return get_youngest(&(ffd->youngest_rev_cache), fs, pool);
1131251881Speter}
1132251881Speter
1133251881Speter/* Wrapper around svn_io_file_create which ignores EEXIST. */
1134251881Speterstatic svn_error_t *
1135251881Spetercreate_file_ignore_eexist(const char *file,
1136251881Speter                          const char *contents,
1137251881Speter                          apr_pool_t *pool)
1138251881Speter{
1139251881Speter  svn_error_t *err = svn_io_file_create(file, contents, pool);
1140251881Speter  if (err && APR_STATUS_IS_EEXIST(err->apr_err))
1141251881Speter    {
1142251881Speter      svn_error_clear(err);
1143251881Speter      err = SVN_NO_ERROR;
1144251881Speter    }
1145251881Speter  return svn_error_trace(err);
1146251881Speter}
1147251881Speter
1148299742Sdim/* Baton type bridging svn_fs_fs__upgrade and upgrade_body carrying
1149299742Sdim * parameters over between them. */
1150299742Sdimstruct upgrade_baton_t
1151251881Speter{
1152299742Sdim  svn_fs_t *fs;
1153299742Sdim  svn_fs_upgrade_notify_t notify_func;
1154299742Sdim  void *notify_baton;
1155299742Sdim  svn_cancel_func_t cancel_func;
1156299742Sdim  void *cancel_baton;
1157299742Sdim};
1158251881Speter
1159253734Speterstatic svn_error_t *
1160299742Sdimupgrade_body(void *baton, apr_pool_t *pool)
1161253734Speter{
1162299742Sdim  struct upgrade_baton_t *upgrade_baton = baton;
1163299742Sdim  svn_fs_t *fs = upgrade_baton->fs;
1164253734Speter  fs_fs_data_t *ffd = fs->fsap_data;
1165251881Speter  int format, max_files_per_dir;
1166299742Sdim  svn_boolean_t use_log_addressing;
1167251881Speter  const char *format_path = path_format(fs, pool);
1168251881Speter  svn_node_kind_t kind;
1169253734Speter  svn_boolean_t needs_revprop_shard_cleanup = FALSE;
1170251881Speter
1171251881Speter  /* Read the FS format number and max-files-per-dir setting. */
1172299742Sdim  SVN_ERR(read_format(&format, &max_files_per_dir, &use_log_addressing,
1173299742Sdim                      format_path, pool));
1174251881Speter
1175251881Speter  /* If the config file does not exist, create one. */
1176251881Speter  SVN_ERR(svn_io_check_path(svn_dirent_join(fs->path, PATH_CONFIG, pool),
1177251881Speter                            &kind, pool));
1178251881Speter  switch (kind)
1179251881Speter    {
1180251881Speter    case svn_node_none:
1181251881Speter      SVN_ERR(write_config(fs, pool));
1182251881Speter      break;
1183251881Speter    case svn_node_file:
1184251881Speter      break;
1185251881Speter    default:
1186251881Speter      return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
1187251881Speter                               _("'%s' is not a regular file."
1188251881Speter                                 " Please move it out of "
1189251881Speter                                 "the way and try again"),
1190251881Speter                               svn_dirent_join(fs->path, PATH_CONFIG, pool));
1191251881Speter    }
1192251881Speter
1193251881Speter  /* If we're already up-to-date, there's nothing else to be done here. */
1194251881Speter  if (format == SVN_FS_FS__FORMAT_NUMBER)
1195251881Speter    return SVN_NO_ERROR;
1196251881Speter
1197299742Sdim  /* If our filesystem predates the existence of the 'txn-current
1198251881Speter     file', make that file and its corresponding lock file. */
1199251881Speter  if (format < SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1200251881Speter    {
1201299742Sdim      SVN_ERR(create_file_ignore_eexist(
1202299742Sdim                           svn_fs_fs__path_txn_current(fs, pool), "0\n",
1203299742Sdim                           pool));
1204299742Sdim      SVN_ERR(create_file_ignore_eexist(
1205299742Sdim                           svn_fs_fs__path_txn_current_lock(fs, pool), "",
1206299742Sdim                           pool));
1207251881Speter    }
1208251881Speter
1209299742Sdim  /* If our filesystem predates the existence of the 'txn-protorevs'
1210251881Speter     dir, make that directory.  */
1211251881Speter  if (format < SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1212251881Speter    {
1213251881Speter      SVN_ERR(svn_io_make_dir_recursively(
1214299742Sdim          svn_fs_fs__path_txn_proto_revs(fs, pool), pool));
1215251881Speter    }
1216251881Speter
1217251881Speter  /* If our filesystem is new enough, write the min unpacked rev file. */
1218251881Speter  if (format < SVN_FS_FS__MIN_PACKED_FORMAT)
1219299742Sdim    SVN_ERR(svn_io_file_create(svn_fs_fs__path_min_unpacked_rev(fs, pool),
1220299742Sdim                               "0\n", pool));
1221251881Speter
1222253734Speter  /* If the file system supports revision packing but not revprop packing
1223253734Speter     *and* the FS has been sharded, pack the revprops up to the point that
1224253734Speter     revision data has been packed.  However, keep the non-packed revprop
1225253734Speter     files around until after the format bump */
1226251881Speter  if (   format >= SVN_FS_FS__MIN_PACKED_FORMAT
1227253734Speter      && format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
1228253734Speter      && max_files_per_dir > 0)
1229253734Speter    {
1230253734Speter      needs_revprop_shard_cleanup = TRUE;
1231299742Sdim      SVN_ERR(svn_fs_fs__upgrade_pack_revprops(fs,
1232299742Sdim                                               upgrade_baton->notify_func,
1233299742Sdim                                               upgrade_baton->notify_baton,
1234299742Sdim                                               upgrade_baton->cancel_func,
1235299742Sdim                                               upgrade_baton->cancel_baton,
1236299742Sdim                                               pool));
1237253734Speter    }
1238251881Speter
1239299742Sdim  /* We will need the UUID info shortly ...
1240299742Sdim     Read it before the format bump as the UUID file still uses the old
1241299742Sdim     format. */
1242299742Sdim  SVN_ERR(read_uuid(fs, pool));
1243299742Sdim
1244299742Sdim  /* Update the format info in the FS struct.  Upgrade steps further
1245299742Sdim     down will use the format from FS to create missing info. */
1246299742Sdim  ffd->format = SVN_FS_FS__FORMAT_NUMBER;
1247299742Sdim  ffd->max_files_per_dir = max_files_per_dir;
1248299742Sdim  ffd->use_log_addressing = use_log_addressing;
1249299742Sdim
1250299742Sdim  /* Always add / bump the instance ID such that no form of caching
1251299742Sdim     accidentally uses outdated information.  Keep the UUID. */
1252299742Sdim  SVN_ERR(svn_fs_fs__set_uuid(fs, fs->uuid, NULL, pool));
1253299742Sdim
1254251881Speter  /* Bump the format file. */
1255299742Sdim  SVN_ERR(svn_fs_fs__write_format(fs, TRUE, pool));
1256253734Speter
1257299742Sdim  if (upgrade_baton->notify_func)
1258299742Sdim    SVN_ERR(upgrade_baton->notify_func(upgrade_baton->notify_baton,
1259299742Sdim                                       SVN_FS_FS__FORMAT_NUMBER,
1260299742Sdim                                       svn_fs_upgrade_format_bumped,
1261299742Sdim                                       pool));
1262299742Sdim
1263253734Speter  /* Now, it is safe to remove the redundant revprop files. */
1264253734Speter  if (needs_revprop_shard_cleanup)
1265299742Sdim    SVN_ERR(svn_fs_fs__upgrade_cleanup_pack_revprops(fs,
1266299742Sdim                                               upgrade_baton->notify_func,
1267299742Sdim                                               upgrade_baton->notify_baton,
1268299742Sdim                                               upgrade_baton->cancel_func,
1269299742Sdim                                               upgrade_baton->cancel_baton,
1270299742Sdim                                               pool));
1271253734Speter
1272253734Speter  /* Done */
1273253734Speter  return SVN_NO_ERROR;
1274251881Speter}
1275251881Speter
1276251881Speter
1277251881Spetersvn_error_t *
1278299742Sdimsvn_fs_fs__upgrade(svn_fs_t *fs,
1279299742Sdim                   svn_fs_upgrade_notify_t notify_func,
1280299742Sdim                   void *notify_baton,
1281299742Sdim                   svn_cancel_func_t cancel_func,
1282299742Sdim                   void *cancel_baton,
1283299742Sdim                   apr_pool_t *pool)
1284251881Speter{
1285299742Sdim  struct upgrade_baton_t baton;
1286299742Sdim  baton.fs = fs;
1287299742Sdim  baton.notify_func = notify_func;
1288299742Sdim  baton.notify_baton = notify_baton;
1289299742Sdim  baton.cancel_func = cancel_func;
1290299742Sdim  baton.cancel_baton = cancel_baton;
1291251881Speter
1292299742Sdim  return svn_fs_fs__with_all_locks(fs, upgrade_body, (void *)&baton, pool);
1293251881Speter}
1294251881Speter
1295251881Speter/* Find the youngest revision in a repository at path FS_PATH and
1296251881Speter   return it in *YOUNGEST_P.  Perform temporary allocations in
1297251881Speter   POOL. */
1298251881Speterstatic svn_error_t *
1299251881Speterget_youngest(svn_revnum_t *youngest_p,
1300299742Sdim             svn_fs_t *fs,
1301251881Speter             apr_pool_t *pool)
1302251881Speter{
1303299742Sdim  apr_uint64_t dummy;
1304299742Sdim  SVN_ERR(svn_fs_fs__read_current(youngest_p, &dummy, &dummy, fs, pool));
1305251881Speter  return SVN_NO_ERROR;
1306251881Speter}
1307251881Speter
1308251881Speter
1309251881Spetersvn_error_t *
1310251881Spetersvn_fs_fs__youngest_rev(svn_revnum_t *youngest_p,
1311251881Speter                        svn_fs_t *fs,
1312251881Speter                        apr_pool_t *pool)
1313251881Speter{
1314251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
1315251881Speter
1316299742Sdim  SVN_ERR(get_youngest(youngest_p, fs, pool));
1317251881Speter  ffd->youngest_rev_cache = *youngest_p;
1318251881Speter
1319251881Speter  return SVN_NO_ERROR;
1320251881Speter}
1321251881Speter
1322299742Sdimint
1323299742Sdimsvn_fs_fs__shard_size(svn_fs_t *fs)
1324251881Speter{
1325299742Sdim  fs_fs_data_t *ffd = fs->fsap_data;
1326251881Speter
1327299742Sdim  return ffd->max_files_per_dir;
1328299742Sdim}
1329251881Speter
1330299742Sdimsvn_error_t *
1331299742Sdimsvn_fs_fs__min_unpacked_rev(svn_revnum_t *min_unpacked,
1332299742Sdim                            svn_fs_t *fs,
1333299742Sdim                            apr_pool_t *pool)
1334299742Sdim{
1335299742Sdim  fs_fs_data_t *ffd = fs->fsap_data;
1336251881Speter
1337299742Sdim  SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, pool));
1338299742Sdim  *min_unpacked = ffd->min_unpacked_rev;
1339251881Speter
1340251881Speter  return SVN_NO_ERROR;
1341251881Speter}
1342251881Speter
1343299742Sdimsvn_error_t *
1344299742Sdimsvn_fs_fs__ensure_revision_exists(svn_revnum_t rev,
1345299742Sdim                                  svn_fs_t *fs,
1346299742Sdim                                  apr_pool_t *pool)
1347251881Speter{
1348251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
1349251881Speter
1350251881Speter  if (! SVN_IS_VALID_REVNUM(rev))
1351251881Speter    return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1352251881Speter                             _("Invalid revision number '%ld'"), rev);
1353251881Speter
1354251881Speter
1355251881Speter  /* Did the revision exist the last time we checked the current
1356251881Speter     file? */
1357251881Speter  if (rev <= ffd->youngest_rev_cache)
1358251881Speter    return SVN_NO_ERROR;
1359251881Speter
1360299742Sdim  SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs, pool));
1361251881Speter
1362251881Speter  /* Check again. */
1363251881Speter  if (rev <= ffd->youngest_rev_cache)
1364251881Speter    return SVN_NO_ERROR;
1365251881Speter
1366251881Speter  return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1367251881Speter                           _("No such revision %ld"), rev);
1368251881Speter}
1369251881Speter
1370251881Spetersvn_error_t *
1371299742Sdimsvn_fs_fs__file_length(svn_filesize_t *length,
1372251881Speter                       node_revision_t *noderev,
1373251881Speter                       apr_pool_t *pool)
1374251881Speter{
1375299742Sdim  representation_t *data_rep = noderev->data_rep;
1376299742Sdim  if (!data_rep)
1377251881Speter    {
1378299742Sdim      /* Treat "no representation" as "empty file". */
1379299742Sdim      *length = 0;
1380251881Speter    }
1381299742Sdim  else if (data_rep->expanded_size)
1382251881Speter    {
1383299742Sdim      /* Standard case: a non-empty file. */
1384299742Sdim      *length = data_rep->expanded_size;
1385251881Speter    }
1386251881Speter  else
1387251881Speter    {
1388299742Sdim      /* Work around a FSFS format quirk (see issue #4554).
1389251881Speter
1390299742Sdim         A plain representation may specify its EXPANDED LENGTH as "0"
1391299742Sdim         in which case, the SIZE value is what we want.
1392251881Speter
1393299742Sdim         Because EXPANDED_LENGTH will also be 0 for empty files, while
1394299742Sdim         SIZE is non-null, we need to check wether the content is
1395299742Sdim         actually empty.  We simply compare with the MD5 checksum of
1396299742Sdim         empty content (sha-1 is not always available).
1397251881Speter       */
1398299742Sdim      svn_checksum_t *empty_md5
1399299742Sdim        = svn_checksum_empty_checksum(svn_checksum_md5, pool);
1400251881Speter
1401299742Sdim      if (memcmp(empty_md5->digest, data_rep->md5_digest,
1402299742Sdim                 sizeof(data_rep->md5_digest)))
1403251881Speter        {
1404299742Sdim          /* Contents is not empty, i.e. EXPANDED_LENGTH cannot be the
1405299742Sdim             actual file length. */
1406299742Sdim          *length = data_rep->size;
1407251881Speter        }
1408251881Speter      else
1409251881Speter        {
1410299742Sdim          /* Contents is empty. */
1411299742Sdim          *length = 0;
1412251881Speter        }
1413251881Speter    }
1414251881Speter
1415251881Speter  return SVN_NO_ERROR;
1416251881Speter}
1417251881Speter
1418251881Spetersvn_boolean_t
1419251881Spetersvn_fs_fs__noderev_same_rep_key(representation_t *a,
1420251881Speter                                representation_t *b)
1421251881Speter{
1422251881Speter  if (a == b)
1423251881Speter    return TRUE;
1424251881Speter
1425251881Speter  if (a == NULL || b == NULL)
1426251881Speter    return FALSE;
1427251881Speter
1428299742Sdim  if (a->item_index != b->item_index)
1429251881Speter    return FALSE;
1430251881Speter
1431251881Speter  if (a->revision != b->revision)
1432251881Speter    return FALSE;
1433251881Speter
1434299742Sdim  return memcmp(&a->uniquifier, &b->uniquifier, sizeof(a->uniquifier)) == 0;
1435251881Speter}
1436251881Speter
1437251881Spetersvn_error_t *
1438299742Sdimsvn_fs_fs__file_text_rep_equal(svn_boolean_t *equal,
1439299742Sdim                               svn_fs_t *fs,
1440299742Sdim                               node_revision_t *a,
1441299742Sdim                               node_revision_t *b,
1442299742Sdim                               apr_pool_t *scratch_pool)
1443251881Speter{
1444299742Sdim  svn_stream_t *contents_a, *contents_b;
1445299742Sdim  representation_t *rep_a = a->data_rep;
1446299742Sdim  representation_t *rep_b = b->data_rep;
1447299742Sdim  svn_boolean_t a_empty = !rep_a;
1448299742Sdim  svn_boolean_t b_empty = !rep_b;
1449251881Speter
1450299742Sdim  /* This makes sure that neither rep will be NULL later on */
1451299742Sdim  if (a_empty && b_empty)
1452251881Speter    {
1453299742Sdim      *equal = TRUE;
1454299742Sdim      return SVN_NO_ERROR;
1455251881Speter    }
1456251881Speter
1457299742Sdim  /* Same path in same rev or txn? */
1458299742Sdim  if (svn_fs_fs__id_eq(a->id, b->id))
1459251881Speter    {
1460299742Sdim      *equal = TRUE;
1461299742Sdim      return SVN_NO_ERROR;
1462251881Speter    }
1463251881Speter
1464299742Sdim  /* Beware of the combination NULL rep and possibly empty rep.
1465299742Sdim   * Due to EXPANDED_SIZE not being reliable, we can't easily detect empty
1466299742Sdim   * reps. So, we can only take further shortcuts if both reps are given. */
1467299742Sdim  if (!a_empty && !b_empty)
1468251881Speter    {
1469299742Sdim      /* File text representations always know their checksums -
1470299742Sdim       * even in a txn. */
1471299742Sdim      if (memcmp(rep_a->md5_digest, rep_b->md5_digest,
1472299742Sdim                 sizeof(rep_a->md5_digest)))
1473251881Speter        {
1474299742Sdim          *equal = FALSE;
1475251881Speter          return SVN_NO_ERROR;
1476251881Speter        }
1477251881Speter
1478299742Sdim      /* Paranoia. Compare SHA1 checksums because that's the level of
1479299742Sdim         confidence we require for e.g. the working copy. */
1480299742Sdim      if (rep_a->has_sha1 && rep_b->has_sha1)
1481251881Speter        {
1482299742Sdim          *equal = memcmp(rep_a->sha1_digest, rep_b->sha1_digest,
1483299742Sdim                          sizeof(rep_a->sha1_digest)) == 0;
1484251881Speter          return SVN_NO_ERROR;
1485251881Speter        }
1486251881Speter    }
1487251881Speter
1488299742Sdim  SVN_ERR(svn_fs_fs__get_contents(&contents_a, fs, rep_a, TRUE,
1489299742Sdim                                  scratch_pool));
1490299742Sdim  SVN_ERR(svn_fs_fs__get_contents(&contents_b, fs, rep_b, TRUE,
1491299742Sdim                                  scratch_pool));
1492299742Sdim  SVN_ERR(svn_stream_contents_same2(equal, contents_a, contents_b,
1493299742Sdim                                   scratch_pool));
1494251881Speter
1495251881Speter  return SVN_NO_ERROR;
1496251881Speter}
1497251881Speter
1498251881Spetersvn_error_t *
1499299742Sdimsvn_fs_fs__prop_rep_equal(svn_boolean_t *equal,
1500299742Sdim                          svn_fs_t *fs,
1501299742Sdim                          node_revision_t *a,
1502299742Sdim                          node_revision_t *b,
1503299742Sdim                          apr_pool_t *scratch_pool)
1504251881Speter{
1505299742Sdim  representation_t *rep_a = a->prop_rep;
1506299742Sdim  representation_t *rep_b = b->prop_rep;
1507299742Sdim  apr_hash_t *proplist_a;
1508299742Sdim  apr_hash_t *proplist_b;
1509251881Speter
1510299742Sdim  /* Mainly for a==b==NULL */
1511299742Sdim  if (rep_a == rep_b)
1512251881Speter    {
1513299742Sdim      *equal = TRUE;
1514299742Sdim      return SVN_NO_ERROR;
1515251881Speter    }
1516251881Speter
1517299742Sdim  /* Committed property lists can be compared quickly */
1518299742Sdim  if (   rep_a && rep_b
1519299742Sdim      && !svn_fs_fs__id_txn_used(&rep_a->txn_id)
1520299742Sdim      && !svn_fs_fs__id_txn_used(&rep_b->txn_id))
1521251881Speter    {
1522299742Sdim      /* MD5 must be given. Having the same checksum is good enough for
1523299742Sdim         accepting the prop lists as equal. */
1524299742Sdim      *equal = memcmp(rep_a->md5_digest, rep_b->md5_digest,
1525299742Sdim                      sizeof(rep_a->md5_digest)) == 0;
1526251881Speter      return SVN_NO_ERROR;
1527251881Speter    }
1528251881Speter
1529299742Sdim  /* Same path in same txn? */
1530299742Sdim  if (svn_fs_fs__id_eq(a->id, b->id))
1531251881Speter    {
1532299742Sdim      *equal = TRUE;
1533251881Speter      return SVN_NO_ERROR;
1534251881Speter    }
1535251881Speter
1536299742Sdim  /* At least one of the reps has been modified in a txn.
1537299742Sdim     Fetch and compare them. */
1538299742Sdim  SVN_ERR(svn_fs_fs__get_proplist(&proplist_a, fs, a, scratch_pool));
1539299742Sdim  SVN_ERR(svn_fs_fs__get_proplist(&proplist_b, fs, b, scratch_pool));
1540251881Speter
1541299742Sdim  *equal = svn_fs__prop_lists_equal(proplist_a, proplist_b, scratch_pool);
1542251881Speter  return SVN_NO_ERROR;
1543251881Speter}
1544251881Speter
1545251881Speter
1546251881Spetersvn_error_t *
1547299742Sdimsvn_fs_fs__file_checksum(svn_checksum_t **checksum,
1548299742Sdim                         node_revision_t *noderev,
1549299742Sdim                         svn_checksum_kind_t kind,
1550299742Sdim                         apr_pool_t *pool)
1551251881Speter{
1552299742Sdim  *checksum = NULL;
1553251881Speter
1554299742Sdim  if (noderev->data_rep)
1555251881Speter    {
1556299742Sdim      svn_checksum_t temp;
1557299742Sdim      temp.kind = kind;
1558251881Speter
1559299742Sdim      switch(kind)
1560251881Speter        {
1561299742Sdim          case svn_checksum_md5:
1562299742Sdim            temp.digest = noderev->data_rep->md5_digest;
1563299742Sdim            break;
1564251881Speter
1565299742Sdim          case svn_checksum_sha1:
1566299742Sdim            if (! noderev->data_rep->has_sha1)
1567299742Sdim              return SVN_NO_ERROR;
1568251881Speter
1569299742Sdim            temp.digest = noderev->data_rep->sha1_digest;
1570299742Sdim            break;
1571251881Speter
1572299742Sdim          default:
1573299742Sdim            return SVN_NO_ERROR;
1574251881Speter        }
1575251881Speter
1576299742Sdim      *checksum = svn_checksum_dup(&temp, pool);
1577251881Speter    }
1578251881Speter
1579251881Speter  return SVN_NO_ERROR;
1580251881Speter}
1581251881Speter
1582299742Sdimrepresentation_t *
1583299742Sdimsvn_fs_fs__rep_copy(representation_t *rep,
1584251881Speter                    apr_pool_t *pool)
1585251881Speter{
1586299742Sdim  if (rep == NULL)
1587299742Sdim    return NULL;
1588251881Speter
1589299742Sdim  return apr_pmemdup(pool, rep, sizeof(*rep));
1590251881Speter}
1591251881Speter
1592251881Speter
1593299742Sdim/* Write out the zeroth revision for filesystem FS.
1594299742Sdim   Perform temporary allocations in SCRATCH_POOL. */
1595251881Speterstatic svn_error_t *
1596299742Sdimwrite_revision_zero(svn_fs_t *fs,
1597299742Sdim                    apr_pool_t *scratch_pool)
1598251881Speter{
1599299742Sdim  /* Use an explicit sub-pool to have full control over temp file lifetimes.
1600299742Sdim   * Since we have it, use it for everything else as well. */
1601299742Sdim  apr_pool_t *subpool = svn_pool_create(scratch_pool);
1602299742Sdim  const char *path_revision_zero = svn_fs_fs__path_rev(fs, 0, subpool);
1603299742Sdim  apr_hash_t *proplist;
1604251881Speter  svn_string_t date;
1605251881Speter
1606299742Sdim  /* Write out a rev file for revision 0. */
1607299742Sdim  if (svn_fs_fs__use_log_addressing(fs))
1608251881Speter    {
1609299742Sdim      apr_array_header_t *index_entries;
1610299742Sdim      svn_fs_fs__p2l_entry_t *entry;
1611299742Sdim      svn_fs_fs__revision_file_t *rev_file;
1612299742Sdim      const char *l2p_proto_index, *p2l_proto_index;
1613251881Speter
1614299742Sdim      /* Write a skeleton r0 with no indexes. */
1615299742Sdim      SVN_ERR(svn_io_file_create(path_revision_zero,
1616299742Sdim                    "PLAIN\nEND\nENDREP\n"
1617299742Sdim                    "id: 0.0.r0/2\n"
1618299742Sdim                    "type: dir\n"
1619299742Sdim                    "count: 0\n"
1620299742Sdim                    "text: 0 3 4 4 "
1621299742Sdim                    "2d2977d1c96f487abe4a1e202dd03b4e\n"
1622299742Sdim                    "cpath: /\n"
1623299742Sdim                    "\n\n", subpool));
1624251881Speter
1625299742Sdim      /* Construct the index P2L contents: describe the 3 items we have.
1626299742Sdim         Be sure to create them in on-disk order. */
1627299742Sdim      index_entries = apr_array_make(subpool, 3, sizeof(entry));
1628251881Speter
1629299742Sdim      entry = apr_pcalloc(subpool, sizeof(*entry));
1630299742Sdim      entry->offset = 0;
1631299742Sdim      entry->size = 17;
1632299742Sdim      entry->type = SVN_FS_FS__ITEM_TYPE_DIR_REP;
1633299742Sdim      entry->item.revision = 0;
1634299742Sdim      entry->item.number = SVN_FS_FS__ITEM_INDEX_FIRST_USER;
1635299742Sdim      APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
1636251881Speter
1637299742Sdim      entry = apr_pcalloc(subpool, sizeof(*entry));
1638299742Sdim      entry->offset = 17;
1639299742Sdim      entry->size = 89;
1640299742Sdim      entry->type = SVN_FS_FS__ITEM_TYPE_NODEREV;
1641299742Sdim      entry->item.revision = 0;
1642299742Sdim      entry->item.number = SVN_FS_FS__ITEM_INDEX_ROOT_NODE;
1643299742Sdim      APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
1644251881Speter
1645299742Sdim      entry = apr_pcalloc(subpool, sizeof(*entry));
1646299742Sdim      entry->offset = 106;
1647299742Sdim      entry->size = 1;
1648299742Sdim      entry->type = SVN_FS_FS__ITEM_TYPE_CHANGES;
1649299742Sdim      entry->item.revision = 0;
1650299742Sdim      entry->item.number = SVN_FS_FS__ITEM_INDEX_CHANGES;
1651299742Sdim      APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
1652251881Speter
1653299742Sdim      /* Now re-open r0, create proto-index files from our entries and
1654299742Sdim         rewrite the index section of r0. */
1655299742Sdim      SVN_ERR(svn_fs_fs__open_pack_or_rev_file_writable(&rev_file, fs, 0,
1656299742Sdim                                                        subpool, subpool));
1657299742Sdim      SVN_ERR(svn_fs_fs__p2l_index_from_p2l_entries(&p2l_proto_index, fs,
1658299742Sdim                                                    rev_file, index_entries,
1659299742Sdim                                                    subpool, subpool));
1660299742Sdim      SVN_ERR(svn_fs_fs__l2p_index_from_p2l_entries(&l2p_proto_index, fs,
1661299742Sdim                                                    index_entries,
1662299742Sdim                                                    subpool, subpool));
1663299742Sdim      SVN_ERR(svn_fs_fs__add_index_data(fs, rev_file->file, l2p_proto_index,
1664299742Sdim                                        p2l_proto_index, 0, subpool));
1665299742Sdim      SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
1666251881Speter    }
1667251881Speter  else
1668299742Sdim    SVN_ERR(svn_io_file_create(path_revision_zero,
1669299742Sdim                               "PLAIN\nEND\nENDREP\n"
1670299742Sdim                               "id: 0.0.r0/17\n"
1671299742Sdim                               "type: dir\n"
1672299742Sdim                               "count: 0\n"
1673299742Sdim                               "text: 0 0 4 4 "
1674299742Sdim                               "2d2977d1c96f487abe4a1e202dd03b4e\n"
1675299742Sdim                               "cpath: /\n"
1676299742Sdim                               "\n\n17 107\n", subpool));
1677251881Speter
1678299742Sdim  SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, subpool));
1679251881Speter
1680251881Speter  /* Set a date on revision 0. */
1681299742Sdim  date.data = svn_time_to_cstring(apr_time_now(), subpool);
1682251881Speter  date.len = strlen(date.data);
1683299742Sdim  proplist = apr_hash_make(subpool);
1684251881Speter  svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date);
1685299742Sdim  SVN_ERR(svn_fs_fs__set_revision_proplist(fs, 0, proplist, subpool));
1686299742Sdim
1687299742Sdim  svn_pool_destroy(subpool);
1688299742Sdim  return SVN_NO_ERROR;
1689251881Speter}
1690251881Speter
1691251881Spetersvn_error_t *
1692299742Sdimsvn_fs_fs__create_file_tree(svn_fs_t *fs,
1693299742Sdim                            const char *path,
1694299742Sdim                            int format,
1695299742Sdim                            int shard_size,
1696299742Sdim                            svn_boolean_t use_log_addressing,
1697299742Sdim                            apr_pool_t *pool)
1698251881Speter{
1699251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
1700251881Speter
1701299742Sdim  fs->path = apr_pstrdup(fs->pool, path);
1702251881Speter  ffd->format = format;
1703251881Speter
1704299742Sdim  /* Use an appropriate sharding mode if supported by the format. */
1705251881Speter  if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
1706299742Sdim    ffd->max_files_per_dir = shard_size;
1707299742Sdim  else
1708299742Sdim    ffd->max_files_per_dir = 0;
1709251881Speter
1710299742Sdim  /* Select the addressing mode depending on the format. */
1711299742Sdim  if (format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT)
1712299742Sdim    ffd->use_log_addressing = use_log_addressing;
1713299742Sdim  else
1714299742Sdim    ffd->use_log_addressing = FALSE;
1715299742Sdim
1716251881Speter  /* Create the revision data directories. */
1717251881Speter  if (ffd->max_files_per_dir)
1718299742Sdim    SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_rev_shard(fs, 0,
1719299742Sdim                                                                  pool),
1720299742Sdim                                        pool));
1721251881Speter  else
1722251881Speter    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_REVS_DIR,
1723251881Speter                                                        pool),
1724251881Speter                                        pool));
1725251881Speter
1726251881Speter  /* Create the revprops directory. */
1727251881Speter  if (ffd->max_files_per_dir)
1728299742Sdim    SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_revprops_shard(fs, 0,
1729299742Sdim                                                                       pool),
1730251881Speter                                        pool));
1731251881Speter  else
1732251881Speter    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path,
1733251881Speter                                                        PATH_REVPROPS_DIR,
1734251881Speter                                                        pool),
1735251881Speter                                        pool));
1736251881Speter
1737251881Speter  /* Create the transaction directory. */
1738299742Sdim  SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_txns_dir(fs, pool),
1739251881Speter                                      pool));
1740251881Speter
1741251881Speter  /* Create the protorevs directory. */
1742251881Speter  if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1743299742Sdim    SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_txn_proto_revs(fs,
1744299742Sdim                                                                       pool),
1745251881Speter                                        pool));
1746251881Speter
1747251881Speter  /* Create the 'current' file. */
1748299742Sdim  SVN_ERR(svn_io_file_create_empty(svn_fs_fs__path_current(fs, pool), pool));
1749299742Sdim  SVN_ERR(svn_fs_fs__write_current(fs, 0, 1, 1, pool));
1750251881Speter
1751299742Sdim  /* Create the 'uuid' file. */
1752299742Sdim  SVN_ERR(svn_io_file_create_empty(svn_fs_fs__path_lock(fs, pool), pool));
1753299742Sdim  SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, NULL, pool));
1754251881Speter
1755269847Speter  /* Create the fsfs.conf file if supported.  Older server versions would
1756269847Speter     simply ignore the file but that might result in a different behavior
1757269847Speter     than with the later releases.  Also, hotcopy would ignore, i.e. not
1758269847Speter     copy, a fsfs.conf with old formats. */
1759269847Speter  if (ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
1760269847Speter    SVN_ERR(write_config(fs, pool));
1761251881Speter
1762299742Sdim  SVN_ERR(read_config(ffd, fs->path, fs->pool, pool));
1763251881Speter
1764299742Sdim  /* Global configuration options. */
1765299742Sdim  SVN_ERR(read_global_config(fs));
1766299742Sdim
1767299742Sdim  /* Add revision 0. */
1768299742Sdim  SVN_ERR(write_revision_zero(fs, pool));
1769299742Sdim
1770251881Speter  /* Create the min unpacked rev file. */
1771251881Speter  if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1772299742Sdim    SVN_ERR(svn_io_file_create(svn_fs_fs__path_min_unpacked_rev(fs, pool),
1773299742Sdim                               "0\n", pool));
1774251881Speter
1775251881Speter  /* Create the txn-current file if the repository supports
1776251881Speter     the transaction sequence file. */
1777251881Speter  if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1778251881Speter    {
1779299742Sdim      SVN_ERR(svn_io_file_create(svn_fs_fs__path_txn_current(fs, pool),
1780251881Speter                                 "0\n", pool));
1781299742Sdim      SVN_ERR(svn_io_file_create_empty(
1782299742Sdim                                 svn_fs_fs__path_txn_current_lock(fs, pool),
1783299742Sdim                                 pool));
1784251881Speter    }
1785251881Speter
1786251881Speter  ffd->youngest_rev_cache = 0;
1787251881Speter  return SVN_NO_ERROR;
1788251881Speter}
1789251881Speter
1790299742Sdimsvn_error_t *
1791299742Sdimsvn_fs_fs__create(svn_fs_t *fs,
1792299742Sdim                  const char *path,
1793299742Sdim                  apr_pool_t *pool)
1794251881Speter{
1795299742Sdim  int format = SVN_FS_FS__FORMAT_NUMBER;
1796299742Sdim  int shard_size = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
1797299742Sdim  svn_boolean_t log_addressing;
1798251881Speter
1799299742Sdim  /* Process the given filesystem config. */
1800299742Sdim  if (fs->config)
1801251881Speter    {
1802299742Sdim      svn_version_t *compatible_version;
1803299742Sdim      const char *shard_size_str;
1804299742Sdim      SVN_ERR(svn_fs__compatible_version(&compatible_version, fs->config,
1805299742Sdim                                         pool));
1806251881Speter
1807299742Sdim      /* select format number */
1808299742Sdim      switch(compatible_version->minor)
1809251881Speter        {
1810299742Sdim          case 0: return svn_error_create(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
1811299742Sdim                 _("FSFS is not compatible with Subversion prior to 1.1"));
1812251881Speter
1813299742Sdim          case 1:
1814299742Sdim          case 2:
1815299742Sdim          case 3: format = 1;
1816299742Sdim                  break;
1817251881Speter
1818299742Sdim          case 4: format = 2;
1819299742Sdim                  break;
1820251881Speter
1821299742Sdim          case 5: format = 3;
1822299742Sdim                  break;
1823251881Speter
1824299742Sdim          case 6:
1825299742Sdim          case 7: format = 4;
1826299742Sdim                  break;
1827251881Speter
1828299742Sdim          case 8: format = 6;
1829299742Sdim                  break;
1830251881Speter
1831299742Sdim          default:format = SVN_FS_FS__FORMAT_NUMBER;
1832251881Speter        }
1833251881Speter
1834299742Sdim      shard_size_str = svn_hash_gets(fs->config, SVN_FS_CONFIG_FSFS_SHARD_SIZE);
1835299742Sdim      if (shard_size_str)
1836251881Speter        {
1837299742Sdim          apr_int64_t val;
1838299742Sdim          SVN_ERR(svn_cstring_strtoi64(&val, shard_size_str, 0,
1839299742Sdim                                       APR_INT32_MAX, 10));
1840251881Speter
1841299742Sdim          shard_size = (int) val;
1842251881Speter        }
1843251881Speter    }
1844251881Speter
1845299742Sdim  log_addressing = svn_hash__get_bool(fs->config,
1846299742Sdim                                      SVN_FS_CONFIG_FSFS_LOG_ADDRESSING,
1847299742Sdim                                      TRUE);
1848251881Speter
1849299742Sdim  /* Actual FS creation. */
1850299742Sdim  SVN_ERR(svn_fs_fs__create_file_tree(fs, path, format, shard_size,
1851299742Sdim                                      log_addressing, pool));
1852251881Speter
1853299742Sdim  /* This filesystem is ready.  Stamp it with a format number. */
1854299742Sdim  SVN_ERR(svn_fs_fs__write_format(fs, FALSE, pool));
1855251881Speter
1856299742Sdim  return SVN_NO_ERROR;
1857251881Speter}
1858251881Speter
1859251881Spetersvn_error_t *
1860251881Spetersvn_fs_fs__set_uuid(svn_fs_t *fs,
1861251881Speter                    const char *uuid,
1862299742Sdim                    const char *instance_id,
1863251881Speter                    apr_pool_t *pool)
1864251881Speter{
1865299742Sdim  fs_fs_data_t *ffd = fs->fsap_data;
1866251881Speter  const char *uuid_path = path_uuid(fs, pool);
1867299742Sdim  svn_stringbuf_t *contents = svn_stringbuf_create_empty(pool);
1868251881Speter
1869251881Speter  if (! uuid)
1870251881Speter    uuid = svn_uuid_generate(pool);
1871251881Speter
1872299742Sdim  if (! instance_id)
1873299742Sdim    instance_id = svn_uuid_generate(pool);
1874251881Speter
1875299742Sdim  svn_stringbuf_appendcstr(contents, uuid);
1876299742Sdim  svn_stringbuf_appendcstr(contents, "\n");
1877251881Speter
1878299742Sdim  if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT)
1879299742Sdim    {
1880299742Sdim      svn_stringbuf_appendcstr(contents, instance_id);
1881299742Sdim      svn_stringbuf_appendcstr(contents, "\n");
1882299742Sdim    }
1883299742Sdim
1884251881Speter  /* We use the permissions of the 'current' file, because the 'uuid'
1885251881Speter     file does not exist during repository creation. */
1886299742Sdim  SVN_ERR(svn_io_write_atomic(uuid_path, contents->data, contents->len,
1887299742Sdim                              svn_fs_fs__path_current(fs, pool) /* perms */,
1888299742Sdim                              pool));
1889251881Speter
1890299742Sdim  fs->uuid = apr_pstrdup(fs->pool, uuid);
1891251881Speter
1892299742Sdim  if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT)
1893299742Sdim    ffd->instance_id = apr_pstrdup(fs->pool, instance_id);
1894299742Sdim  else
1895299742Sdim    ffd->instance_id = fs->uuid;
1896299742Sdim
1897251881Speter  return SVN_NO_ERROR;
1898251881Speter}
1899251881Speter
1900251881Speter/** Node origin lazy cache. */
1901251881Speter
1902251881Speter/* If directory PATH does not exist, create it and give it the same
1903251881Speter   permissions as FS_path.*/
1904251881Spetersvn_error_t *
1905251881Spetersvn_fs_fs__ensure_dir_exists(const char *path,
1906251881Speter                             const char *fs_path,
1907251881Speter                             apr_pool_t *pool)
1908251881Speter{
1909251881Speter  svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
1910251881Speter  if (err && APR_STATUS_IS_EEXIST(err->apr_err))
1911251881Speter    {
1912251881Speter      svn_error_clear(err);
1913251881Speter      return SVN_NO_ERROR;
1914251881Speter    }
1915251881Speter  SVN_ERR(err);
1916251881Speter
1917251881Speter  /* We successfully created a new directory.  Dup the permissions
1918251881Speter     from FS->path. */
1919251881Speter  return svn_io_copy_perms(fs_path, path, pool);
1920251881Speter}
1921251881Speter
1922251881Speter/* Set *NODE_ORIGINS to a hash mapping 'const char *' node IDs to
1923251881Speter   'svn_string_t *' node revision IDs.  Use POOL for allocations. */
1924251881Speterstatic svn_error_t *
1925251881Speterget_node_origins_from_file(svn_fs_t *fs,
1926251881Speter                           apr_hash_t **node_origins,
1927251881Speter                           const char *node_origins_file,
1928251881Speter                           apr_pool_t *pool)
1929251881Speter{
1930251881Speter  apr_file_t *fd;
1931251881Speter  svn_error_t *err;
1932251881Speter  svn_stream_t *stream;
1933251881Speter
1934251881Speter  *node_origins = NULL;
1935251881Speter  err = svn_io_file_open(&fd, node_origins_file,
1936251881Speter                         APR_READ, APR_OS_DEFAULT, pool);
1937251881Speter  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
1938251881Speter    {
1939251881Speter      svn_error_clear(err);
1940251881Speter      return SVN_NO_ERROR;
1941251881Speter    }
1942251881Speter  SVN_ERR(err);
1943251881Speter
1944251881Speter  stream = svn_stream_from_aprfile2(fd, FALSE, pool);
1945251881Speter  *node_origins = apr_hash_make(pool);
1946299742Sdim  err = svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool);
1947299742Sdim  if (err)
1948299742Sdim    return svn_error_quick_wrapf(err, _("malformed node origin data in '%s'"),
1949299742Sdim                                 node_origins_file);
1950251881Speter  return svn_stream_close(stream);
1951251881Speter}
1952251881Speter
1953251881Spetersvn_error_t *
1954251881Spetersvn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
1955251881Speter                           svn_fs_t *fs,
1956299742Sdim                           const svn_fs_fs__id_part_t *node_id,
1957251881Speter                           apr_pool_t *pool)
1958251881Speter{
1959251881Speter  apr_hash_t *node_origins;
1960251881Speter
1961251881Speter  *origin_id = NULL;
1962251881Speter  SVN_ERR(get_node_origins_from_file(fs, &node_origins,
1963299742Sdim                                     svn_fs_fs__path_node_origin(fs, node_id,
1964299742Sdim                                                                 pool),
1965251881Speter                                     pool));
1966251881Speter  if (node_origins)
1967251881Speter    {
1968299742Sdim      char node_id_ptr[SVN_INT64_BUFFER_SIZE];
1969299742Sdim      apr_size_t len = svn__ui64tobase36(node_id_ptr, node_id->number);
1970299742Sdim      svn_string_t *origin_id_str
1971299742Sdim        = apr_hash_get(node_origins, node_id_ptr, len);
1972299742Sdim
1973251881Speter      if (origin_id_str)
1974299742Sdim        SVN_ERR(svn_fs_fs__id_parse(origin_id,
1975299742Sdim                                    apr_pstrdup(pool, origin_id_str->data),
1976299742Sdim                                    pool));
1977251881Speter    }
1978251881Speter  return SVN_NO_ERROR;
1979251881Speter}
1980251881Speter
1981251881Speter
1982251881Speter/* Helper for svn_fs_fs__set_node_origin.  Takes a NODE_ID/NODE_REV_ID
1983251881Speter   pair and adds it to the NODE_ORIGINS_PATH file.  */
1984251881Speterstatic svn_error_t *
1985251881Speterset_node_origins_for_file(svn_fs_t *fs,
1986251881Speter                          const char *node_origins_path,
1987299742Sdim                          const svn_fs_fs__id_part_t *node_id,
1988251881Speter                          svn_string_t *node_rev_id,
1989251881Speter                          apr_pool_t *pool)
1990251881Speter{
1991251881Speter  const char *path_tmp;
1992251881Speter  svn_stream_t *stream;
1993251881Speter  apr_hash_t *origins_hash;
1994251881Speter  svn_string_t *old_node_rev_id;
1995251881Speter
1996299742Sdim  /* the hash serialization functions require strings as keys */
1997299742Sdim  char node_id_ptr[SVN_INT64_BUFFER_SIZE];
1998299742Sdim  apr_size_t len = svn__ui64tobase36(node_id_ptr, node_id->number);
1999299742Sdim
2000251881Speter  SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs->path,
2001251881Speter                                                       PATH_NODE_ORIGINS_DIR,
2002251881Speter                                                       pool),
2003251881Speter                                       fs->path, pool));
2004251881Speter
2005251881Speter  /* Read the previously existing origins (if any), and merge our
2006251881Speter     update with it. */
2007251881Speter  SVN_ERR(get_node_origins_from_file(fs, &origins_hash,
2008251881Speter                                     node_origins_path, pool));
2009251881Speter  if (! origins_hash)
2010251881Speter    origins_hash = apr_hash_make(pool);
2011251881Speter
2012299742Sdim  old_node_rev_id = apr_hash_get(origins_hash, node_id_ptr, len);
2013251881Speter
2014251881Speter  if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id))
2015251881Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2016251881Speter                             _("Node origin for '%s' exists with a different "
2017251881Speter                               "value (%s) than what we were about to store "
2018251881Speter                               "(%s)"),
2019299742Sdim                             node_id_ptr, old_node_rev_id->data,
2020299742Sdim                             node_rev_id->data);
2021251881Speter
2022299742Sdim  apr_hash_set(origins_hash, node_id_ptr, len, node_rev_id);
2023251881Speter
2024251881Speter  /* Sure, there's a race condition here.  Two processes could be
2025251881Speter     trying to add different cache elements to the same file at the
2026251881Speter     same time, and the entries added by the first one to write will
2027251881Speter     be lost.  But this is just a cache of reconstructible data, so
2028251881Speter     we'll accept this problem in return for not having to deal with
2029251881Speter     locking overhead. */
2030251881Speter
2031251881Speter  /* Create a temporary file, write out our hash, and close the file. */
2032251881Speter  SVN_ERR(svn_stream_open_unique(&stream, &path_tmp,
2033251881Speter                                 svn_dirent_dirname(node_origins_path, pool),
2034251881Speter                                 svn_io_file_del_none, pool, pool));
2035251881Speter  SVN_ERR(svn_hash_write2(origins_hash, stream, SVN_HASH_TERMINATOR, pool));
2036251881Speter  SVN_ERR(svn_stream_close(stream));
2037251881Speter
2038251881Speter  /* Rename the temp file as the real destination */
2039251881Speter  return svn_io_file_rename(path_tmp, node_origins_path, pool);
2040251881Speter}
2041251881Speter
2042251881Speter
2043251881Spetersvn_error_t *
2044251881Spetersvn_fs_fs__set_node_origin(svn_fs_t *fs,
2045299742Sdim                           const svn_fs_fs__id_part_t *node_id,
2046251881Speter                           const svn_fs_id_t *node_rev_id,
2047251881Speter                           apr_pool_t *pool)
2048251881Speter{
2049251881Speter  svn_error_t *err;
2050299742Sdim  const char *filename = svn_fs_fs__path_node_origin(fs, node_id, pool);
2051251881Speter
2052251881Speter  err = set_node_origins_for_file(fs, filename,
2053251881Speter                                  node_id,
2054251881Speter                                  svn_fs_fs__id_unparse(node_rev_id, pool),
2055251881Speter                                  pool);
2056251881Speter  if (err && APR_STATUS_IS_EACCES(err->apr_err))
2057251881Speter    {
2058251881Speter      /* It's just a cache; stop trying if I can't write. */
2059251881Speter      svn_error_clear(err);
2060251881Speter      err = NULL;
2061251881Speter    }
2062251881Speter  return svn_error_trace(err);
2063251881Speter}
2064251881Speter
2065251881Speter
2066251881Speter
2067251881Speter/*** Revisions ***/
2068251881Speter
2069251881Spetersvn_error_t *
2070251881Spetersvn_fs_fs__revision_prop(svn_string_t **value_p,
2071251881Speter                         svn_fs_t *fs,
2072251881Speter                         svn_revnum_t rev,
2073251881Speter                         const char *propname,
2074251881Speter                         apr_pool_t *pool)
2075251881Speter{
2076251881Speter  apr_hash_t *table;
2077251881Speter
2078251881Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
2079299742Sdim  SVN_ERR(svn_fs_fs__get_revision_proplist(&table, fs, rev, pool));
2080251881Speter
2081251881Speter  *value_p = svn_hash_gets(table, propname);
2082251881Speter
2083251881Speter  return SVN_NO_ERROR;
2084251881Speter}
2085251881Speter
2086251881Speter
2087251881Speter/* Baton used for change_rev_prop_body below. */
2088251881Speterstruct change_rev_prop_baton {
2089251881Speter  svn_fs_t *fs;
2090251881Speter  svn_revnum_t rev;
2091251881Speter  const char *name;
2092251881Speter  const svn_string_t *const *old_value_p;
2093251881Speter  const svn_string_t *value;
2094251881Speter};
2095251881Speter
2096251881Speter/* The work-horse for svn_fs_fs__change_rev_prop, called with the FS
2097251881Speter   write lock.  This implements the svn_fs_fs__with_write_lock()
2098251881Speter   'body' callback type.  BATON is a 'struct change_rev_prop_baton *'. */
2099251881Speterstatic svn_error_t *
2100251881Speterchange_rev_prop_body(void *baton, apr_pool_t *pool)
2101251881Speter{
2102251881Speter  struct change_rev_prop_baton *cb = baton;
2103251881Speter  apr_hash_t *table;
2104251881Speter
2105299742Sdim  SVN_ERR(svn_fs_fs__get_revision_proplist(&table, cb->fs, cb->rev, pool));
2106251881Speter
2107251881Speter  if (cb->old_value_p)
2108251881Speter    {
2109251881Speter      const svn_string_t *wanted_value = *cb->old_value_p;
2110251881Speter      const svn_string_t *present_value = svn_hash_gets(table, cb->name);
2111251881Speter      if ((!wanted_value != !present_value)
2112251881Speter          || (wanted_value && present_value
2113251881Speter              && !svn_string_compare(wanted_value, present_value)))
2114251881Speter        {
2115251881Speter          /* What we expected isn't what we found. */
2116251881Speter          return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL,
2117251881Speter                                   _("revprop '%s' has unexpected value in "
2118251881Speter                                     "filesystem"),
2119251881Speter                                   cb->name);
2120251881Speter        }
2121251881Speter      /* Fall through. */
2122251881Speter    }
2123251881Speter  svn_hash_sets(table, cb->name, cb->value);
2124251881Speter
2125299742Sdim  return svn_fs_fs__set_revision_proplist(cb->fs, cb->rev, table, pool);
2126251881Speter}
2127251881Speter
2128251881Spetersvn_error_t *
2129251881Spetersvn_fs_fs__change_rev_prop(svn_fs_t *fs,
2130251881Speter                           svn_revnum_t rev,
2131251881Speter                           const char *name,
2132251881Speter                           const svn_string_t *const *old_value_p,
2133251881Speter                           const svn_string_t *value,
2134251881Speter                           apr_pool_t *pool)
2135251881Speter{
2136251881Speter  struct change_rev_prop_baton cb;
2137251881Speter
2138251881Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
2139251881Speter
2140251881Speter  cb.fs = fs;
2141251881Speter  cb.rev = rev;
2142251881Speter  cb.name = name;
2143251881Speter  cb.old_value_p = old_value_p;
2144251881Speter  cb.value = value;
2145251881Speter
2146251881Speter  return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool);
2147251881Speter}
2148251881Speter
2149251881Speter
2150251881Spetersvn_error_t *
2151299742Sdimsvn_fs_fs__info_format(int *fs_format,
2152299742Sdim                       svn_version_t **supports_version,
2153251881Speter                       svn_fs_t *fs,
2154299742Sdim                       apr_pool_t *result_pool,
2155299742Sdim                       apr_pool_t *scratch_pool)
2156251881Speter{
2157299742Sdim  fs_fs_data_t *ffd = fs->fsap_data;
2158299742Sdim  *fs_format = ffd->format;
2159299742Sdim  *supports_version = apr_palloc(result_pool, sizeof(svn_version_t));
2160251881Speter
2161299742Sdim  (*supports_version)->major = SVN_VER_MAJOR;
2162299742Sdim  (*supports_version)->minor = 1;
2163299742Sdim  (*supports_version)->patch = 0;
2164299742Sdim  (*supports_version)->tag = "";
2165251881Speter
2166299742Sdim  switch (ffd->format)
2167251881Speter    {
2168299742Sdim    case 1:
2169299742Sdim      break;
2170299742Sdim    case 2:
2171299742Sdim      (*supports_version)->minor = 4;
2172299742Sdim      break;
2173299742Sdim    case 3:
2174299742Sdim      (*supports_version)->minor = 5;
2175299742Sdim      break;
2176299742Sdim    case 4:
2177299742Sdim      (*supports_version)->minor = 6;
2178299742Sdim      break;
2179299742Sdim    case 6:
2180299742Sdim      (*supports_version)->minor = 8;
2181299742Sdim      break;
2182299742Sdim    case 7:
2183299742Sdim      (*supports_version)->minor = 9;
2184299742Sdim      break;
2185299742Sdim#ifdef SVN_DEBUG
2186299742Sdim# if SVN_FS_FS__FORMAT_NUMBER != 7
2187299742Sdim#  error "Need to add a 'case' statement here"
2188299742Sdim# endif
2189299742Sdim#endif
2190251881Speter    }
2191251881Speter
2192251881Speter  return SVN_NO_ERROR;
2193251881Speter}
2194251881Speter
2195251881Spetersvn_error_t *
2196299742Sdimsvn_fs_fs__info_config_files(apr_array_header_t **files,
2197299742Sdim                             svn_fs_t *fs,
2198299742Sdim                             apr_pool_t *result_pool,
2199262253Speter                             apr_pool_t *scratch_pool)
2200262253Speter{
2201299742Sdim  *files = apr_array_make(result_pool, 1, sizeof(const char *));
2202299742Sdim  APR_ARRAY_PUSH(*files, const char *) = svn_dirent_join(fs->path, PATH_CONFIG,
2203299742Sdim                                                         result_pool);
2204262253Speter  return SVN_NO_ERROR;
2205262253Speter}
2206