fs_fs.c revision 269847
1/* fs_fs.c --- filesystem operations specific to fs_fs
2 *
3 * ====================================================================
4 *    Licensed to the Apache Software Foundation (ASF) under one
5 *    or more contributor license agreements.  See the NOTICE file
6 *    distributed with this work for additional information
7 *    regarding copyright ownership.  The ASF licenses this file
8 *    to you under the Apache License, Version 2.0 (the
9 *    "License"); you may not use this file except in compliance
10 *    with the License.  You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 *    Unless required by applicable law or agreed to in writing,
15 *    software distributed under the License is distributed on an
16 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 *    KIND, either express or implied.  See the License for the
18 *    specific language governing permissions and limitations
19 *    under the License.
20 * ====================================================================
21 */
22
23#include <stdlib.h>
24#include <stdio.h>
25#include <string.h>
26#include <ctype.h>
27#include <assert.h>
28#include <errno.h>
29
30#include <apr_general.h>
31#include <apr_pools.h>
32#include <apr_file_io.h>
33#include <apr_uuid.h>
34#include <apr_lib.h>
35#include <apr_md5.h>
36#include <apr_sha1.h>
37#include <apr_strings.h>
38#include <apr_thread_mutex.h>
39
40#include "svn_pools.h"
41#include "svn_fs.h"
42#include "svn_dirent_uri.h"
43#include "svn_path.h"
44#include "svn_hash.h"
45#include "svn_props.h"
46#include "svn_sorts.h"
47#include "svn_string.h"
48#include "svn_time.h"
49#include "svn_mergeinfo.h"
50#include "svn_config.h"
51#include "svn_ctype.h"
52#include "svn_version.h"
53
54#include "fs.h"
55#include "tree.h"
56#include "lock.h"
57#include "key-gen.h"
58#include "fs_fs.h"
59#include "id.h"
60#include "rep-cache.h"
61#include "temp_serializer.h"
62
63#include "private/svn_string_private.h"
64#include "private/svn_fs_util.h"
65#include "private/svn_subr_private.h"
66#include "private/svn_delta_private.h"
67#include "../libsvn_fs/fs-loader.h"
68
69#include "svn_private_config.h"
70#include "temp_serializer.h"
71
72/* An arbitrary maximum path length, so clients can't run us out of memory
73 * by giving us arbitrarily large paths. */
74#define FSFS_MAX_PATH_LEN 4096
75
76/* The default maximum number of files per directory to store in the
77   rev and revprops directory.  The number below is somewhat arbitrary,
78   and can be overridden by defining the macro while compiling; the
79   figure of 1000 is reasonable for VFAT filesystems, which are by far
80   the worst performers in this area. */
81#ifndef SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR
82#define SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR 1000
83#endif
84
85/* Begin deltification after a node history exceeded this this limit.
86   Useful values are 4 to 64 with 16 being a good compromise between
87   computational overhead and repository size savings.
88   Should be a power of 2.
89   Values < 2 will result in standard skip-delta behavior. */
90#define SVN_FS_FS_MAX_LINEAR_DELTIFICATION 16
91
92/* Finding a deltification base takes operations proportional to the
93   number of changes being skipped. To prevent exploding runtime
94   during commits, limit the deltification range to this value.
95   Should be a power of 2 minus one.
96   Values < 1 disable deltification. */
97#define SVN_FS_FS_MAX_DELTIFICATION_WALK 1023
98
99/* Give writing processes 10 seconds to replace an existing revprop
100   file with a new one. After that time, we assume that the writing
101   process got aborted and that we have re-read revprops. */
102#define REVPROP_CHANGE_TIMEOUT (10 * 1000000)
103
104/* The following are names of atomics that will be used to communicate
105 * revprop updates across all processes on this machine. */
106#define ATOMIC_REVPROP_GENERATION "rev-prop-generation"
107#define ATOMIC_REVPROP_TIMEOUT    "rev-prop-timeout"
108#define ATOMIC_REVPROP_NAMESPACE  "rev-prop-atomics"
109
110/* Following are defines that specify the textual elements of the
111   native filesystem directories and revision files. */
112
113/* Headers used to describe node-revision in the revision file. */
114#define HEADER_ID          "id"
115#define HEADER_TYPE        "type"
116#define HEADER_COUNT       "count"
117#define HEADER_PROPS       "props"
118#define HEADER_TEXT        "text"
119#define HEADER_CPATH       "cpath"
120#define HEADER_PRED        "pred"
121#define HEADER_COPYFROM    "copyfrom"
122#define HEADER_COPYROOT    "copyroot"
123#define HEADER_FRESHTXNRT  "is-fresh-txn-root"
124#define HEADER_MINFO_HERE  "minfo-here"
125#define HEADER_MINFO_CNT   "minfo-cnt"
126
127/* Kinds that a change can be. */
128#define ACTION_MODIFY      "modify"
129#define ACTION_ADD         "add"
130#define ACTION_DELETE      "delete"
131#define ACTION_REPLACE     "replace"
132#define ACTION_RESET       "reset"
133
134/* True and False flags. */
135#define FLAG_TRUE          "true"
136#define FLAG_FALSE         "false"
137
138/* Kinds that a node-rev can be. */
139#define KIND_FILE          "file"
140#define KIND_DIR           "dir"
141
142/* Kinds of representation. */
143#define REP_PLAIN          "PLAIN"
144#define REP_DELTA          "DELTA"
145
146/* Notes:
147
148To avoid opening and closing the rev-files all the time, it would
149probably be advantageous to keep each rev-file open for the
150lifetime of the transaction object.  I'll leave that as a later
151optimization for now.
152
153I didn't keep track of pool lifetimes at all in this code.  There
154are likely some errors because of that.
155
156*/
157
158/* The vtable associated with an open transaction object. */
159static txn_vtable_t txn_vtable = {
160  svn_fs_fs__commit_txn,
161  svn_fs_fs__abort_txn,
162  svn_fs_fs__txn_prop,
163  svn_fs_fs__txn_proplist,
164  svn_fs_fs__change_txn_prop,
165  svn_fs_fs__txn_root,
166  svn_fs_fs__change_txn_props
167};
168
169/* Declarations. */
170
171static svn_error_t *
172read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
173                      const char *path,
174                      apr_pool_t *pool);
175
176static svn_error_t *
177update_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool);
178
179static svn_error_t *
180get_youngest(svn_revnum_t *youngest_p, const char *fs_path, apr_pool_t *pool);
181
182static svn_error_t *
183verify_walker(representation_t *rep,
184              void *baton,
185              svn_fs_t *fs,
186              apr_pool_t *scratch_pool);
187
188/* Pathname helper functions */
189
190/* Return TRUE is REV is packed in FS, FALSE otherwise. */
191static svn_boolean_t
192is_packed_rev(svn_fs_t *fs, svn_revnum_t rev)
193{
194  fs_fs_data_t *ffd = fs->fsap_data;
195
196  return (rev < ffd->min_unpacked_rev);
197}
198
199/* Return TRUE is REV is packed in FS, FALSE otherwise. */
200static svn_boolean_t
201is_packed_revprop(svn_fs_t *fs, svn_revnum_t rev)
202{
203  fs_fs_data_t *ffd = fs->fsap_data;
204
205  /* rev 0 will not be packed */
206  return (rev < ffd->min_unpacked_rev)
207      && (rev != 0)
208      && (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT);
209}
210
211static const char *
212path_format(svn_fs_t *fs, apr_pool_t *pool)
213{
214  return svn_dirent_join(fs->path, PATH_FORMAT, pool);
215}
216
217static APR_INLINE const char *
218path_uuid(svn_fs_t *fs, apr_pool_t *pool)
219{
220  return svn_dirent_join(fs->path, PATH_UUID, pool);
221}
222
223const char *
224svn_fs_fs__path_current(svn_fs_t *fs, apr_pool_t *pool)
225{
226  return svn_dirent_join(fs->path, PATH_CURRENT, pool);
227}
228
229static APR_INLINE const char *
230path_txn_current(svn_fs_t *fs, apr_pool_t *pool)
231{
232  return svn_dirent_join(fs->path, PATH_TXN_CURRENT, pool);
233}
234
235static APR_INLINE const char *
236path_txn_current_lock(svn_fs_t *fs, apr_pool_t *pool)
237{
238  return svn_dirent_join(fs->path, PATH_TXN_CURRENT_LOCK, pool);
239}
240
241static APR_INLINE const char *
242path_lock(svn_fs_t *fs, apr_pool_t *pool)
243{
244  return svn_dirent_join(fs->path, PATH_LOCK_FILE, pool);
245}
246
247static const char *
248path_revprop_generation(svn_fs_t *fs, apr_pool_t *pool)
249{
250  return svn_dirent_join(fs->path, PATH_REVPROP_GENERATION, pool);
251}
252
253static const char *
254path_rev_packed(svn_fs_t *fs, svn_revnum_t rev, const char *kind,
255                apr_pool_t *pool)
256{
257  fs_fs_data_t *ffd = fs->fsap_data;
258
259  assert(ffd->max_files_per_dir);
260  assert(is_packed_rev(fs, rev));
261
262  return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
263                              apr_psprintf(pool,
264                                           "%ld" PATH_EXT_PACKED_SHARD,
265                                           rev / ffd->max_files_per_dir),
266                              kind, NULL);
267}
268
269static const char *
270path_rev_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
271{
272  fs_fs_data_t *ffd = fs->fsap_data;
273
274  assert(ffd->max_files_per_dir);
275  return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
276                              apr_psprintf(pool, "%ld",
277                                                 rev / ffd->max_files_per_dir),
278                              NULL);
279}
280
281static const char *
282path_rev(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
283{
284  fs_fs_data_t *ffd = fs->fsap_data;
285
286  assert(! is_packed_rev(fs, rev));
287
288  if (ffd->max_files_per_dir)
289    {
290      return svn_dirent_join(path_rev_shard(fs, rev, pool),
291                             apr_psprintf(pool, "%ld", rev),
292                             pool);
293    }
294
295  return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
296                              apr_psprintf(pool, "%ld", rev), NULL);
297}
298
299svn_error_t *
300svn_fs_fs__path_rev_absolute(const char **path,
301                             svn_fs_t *fs,
302                             svn_revnum_t rev,
303                             apr_pool_t *pool)
304{
305  fs_fs_data_t *ffd = fs->fsap_data;
306
307  if (ffd->format < SVN_FS_FS__MIN_PACKED_FORMAT
308      || ! is_packed_rev(fs, rev))
309    {
310      *path = path_rev(fs, rev, pool);
311    }
312  else
313    {
314      *path = path_rev_packed(fs, rev, PATH_PACKED, pool);
315    }
316
317  return SVN_NO_ERROR;
318}
319
320static const char *
321path_revprops_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
322{
323  fs_fs_data_t *ffd = fs->fsap_data;
324
325  assert(ffd->max_files_per_dir);
326  return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
327                              apr_psprintf(pool, "%ld",
328                                           rev / ffd->max_files_per_dir),
329                              NULL);
330}
331
332static const char *
333path_revprops_pack_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
334{
335  fs_fs_data_t *ffd = fs->fsap_data;
336
337  assert(ffd->max_files_per_dir);
338  return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
339                              apr_psprintf(pool, "%ld" PATH_EXT_PACKED_SHARD,
340                                           rev / ffd->max_files_per_dir),
341                              NULL);
342}
343
344static const char *
345path_revprops(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
346{
347  fs_fs_data_t *ffd = fs->fsap_data;
348
349  if (ffd->max_files_per_dir)
350    {
351      return svn_dirent_join(path_revprops_shard(fs, rev, pool),
352                             apr_psprintf(pool, "%ld", rev),
353                             pool);
354    }
355
356  return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
357                              apr_psprintf(pool, "%ld", rev), NULL);
358}
359
360static APR_INLINE const char *
361path_txn_dir(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
362{
363  SVN_ERR_ASSERT_NO_RETURN(txn_id != NULL);
364  return svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR,
365                              apr_pstrcat(pool, txn_id, PATH_EXT_TXN,
366                                          (char *)NULL),
367                              NULL);
368}
369
370/* Return the name of the sha1->rep mapping file in transaction TXN_ID
371 * within FS for the given SHA1 checksum.  Use POOL for allocations.
372 */
373static APR_INLINE const char *
374path_txn_sha1(svn_fs_t *fs, const char *txn_id, svn_checksum_t *sha1,
375              apr_pool_t *pool)
376{
377  return svn_dirent_join(path_txn_dir(fs, txn_id, pool),
378                         svn_checksum_to_cstring(sha1, pool),
379                         pool);
380}
381
382static APR_INLINE const char *
383path_txn_changes(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
384{
385  return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_CHANGES, pool);
386}
387
388static APR_INLINE const char *
389path_txn_props(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
390{
391  return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_TXN_PROPS, pool);
392}
393
394static APR_INLINE const char *
395path_txn_next_ids(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
396{
397  return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_NEXT_IDS, pool);
398}
399
400static APR_INLINE const char *
401path_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool)
402{
403  return svn_dirent_join(fs->path, PATH_MIN_UNPACKED_REV, pool);
404}
405
406
407static APR_INLINE const char *
408path_txn_proto_rev(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
409{
410  fs_fs_data_t *ffd = fs->fsap_data;
411  if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
412    return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
413                                apr_pstrcat(pool, txn_id, PATH_EXT_REV,
414                                            (char *)NULL),
415                                NULL);
416  else
417    return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV, pool);
418}
419
420static APR_INLINE const char *
421path_txn_proto_rev_lock(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
422{
423  fs_fs_data_t *ffd = fs->fsap_data;
424  if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
425    return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
426                                apr_pstrcat(pool, txn_id, PATH_EXT_REV_LOCK,
427                                            (char *)NULL),
428                                NULL);
429  else
430    return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV_LOCK,
431                           pool);
432}
433
434static const char *
435path_txn_node_rev(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
436{
437  const char *txn_id = svn_fs_fs__id_txn_id(id);
438  const char *node_id = svn_fs_fs__id_node_id(id);
439  const char *copy_id = svn_fs_fs__id_copy_id(id);
440  const char *name = apr_psprintf(pool, PATH_PREFIX_NODE "%s.%s",
441                                  node_id, copy_id);
442
443  return svn_dirent_join(path_txn_dir(fs, txn_id, pool), name, pool);
444}
445
446static APR_INLINE const char *
447path_txn_node_props(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
448{
449  return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool), PATH_EXT_PROPS,
450                     (char *)NULL);
451}
452
453static APR_INLINE const char *
454path_txn_node_children(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
455{
456  return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool),
457                     PATH_EXT_CHILDREN, (char *)NULL);
458}
459
460static APR_INLINE const char *
461path_node_origin(svn_fs_t *fs, const char *node_id, apr_pool_t *pool)
462{
463  size_t len = strlen(node_id);
464  const char *node_id_minus_last_char =
465    (len == 1) ? "0" : apr_pstrmemdup(pool, node_id, len - 1);
466  return svn_dirent_join_many(pool, fs->path, PATH_NODE_ORIGINS_DIR,
467                              node_id_minus_last_char, NULL);
468}
469
470static APR_INLINE const char *
471path_and_offset_of(apr_file_t *file, apr_pool_t *pool)
472{
473  const char *path;
474  apr_off_t offset = 0;
475
476  if (apr_file_name_get(&path, file) != APR_SUCCESS)
477    path = "(unknown)";
478
479  if (apr_file_seek(file, APR_CUR, &offset) != APR_SUCCESS)
480    offset = -1;
481
482  return apr_psprintf(pool, "%s:%" APR_OFF_T_FMT, path, offset);
483}
484
485
486
487/* Functions for working with shared transaction data. */
488
489/* Return the transaction object for transaction TXN_ID from the
490   transaction list of filesystem FS (which must already be locked via the
491   txn_list_lock mutex).  If the transaction does not exist in the list,
492   then create a new transaction object and return it (if CREATE_NEW is
493   true) or return NULL (otherwise). */
494static fs_fs_shared_txn_data_t *
495get_shared_txn(svn_fs_t *fs, const char *txn_id, svn_boolean_t create_new)
496{
497  fs_fs_data_t *ffd = fs->fsap_data;
498  fs_fs_shared_data_t *ffsd = ffd->shared;
499  fs_fs_shared_txn_data_t *txn;
500
501  for (txn = ffsd->txns; txn; txn = txn->next)
502    if (strcmp(txn->txn_id, txn_id) == 0)
503      break;
504
505  if (txn || !create_new)
506    return txn;
507
508  /* Use the transaction object from the (single-object) freelist,
509     if one is available, or otherwise create a new object. */
510  if (ffsd->free_txn)
511    {
512      txn = ffsd->free_txn;
513      ffsd->free_txn = NULL;
514    }
515  else
516    {
517      apr_pool_t *subpool = svn_pool_create(ffsd->common_pool);
518      txn = apr_palloc(subpool, sizeof(*txn));
519      txn->pool = subpool;
520    }
521
522  assert(strlen(txn_id) < sizeof(txn->txn_id));
523  apr_cpystrn(txn->txn_id, txn_id, sizeof(txn->txn_id));
524  txn->being_written = FALSE;
525
526  /* Link this transaction into the head of the list.  We will typically
527     be dealing with only one active transaction at a time, so it makes
528     sense for searches through the transaction list to look at the
529     newest transactions first.  */
530  txn->next = ffsd->txns;
531  ffsd->txns = txn;
532
533  return txn;
534}
535
536/* Free the transaction object for transaction TXN_ID, and remove it
537   from the transaction list of filesystem FS (which must already be
538   locked via the txn_list_lock mutex).  Do nothing if the transaction
539   does not exist. */
540static void
541free_shared_txn(svn_fs_t *fs, const char *txn_id)
542{
543  fs_fs_data_t *ffd = fs->fsap_data;
544  fs_fs_shared_data_t *ffsd = ffd->shared;
545  fs_fs_shared_txn_data_t *txn, *prev = NULL;
546
547  for (txn = ffsd->txns; txn; prev = txn, txn = txn->next)
548    if (strcmp(txn->txn_id, txn_id) == 0)
549      break;
550
551  if (!txn)
552    return;
553
554  if (prev)
555    prev->next = txn->next;
556  else
557    ffsd->txns = txn->next;
558
559  /* As we typically will be dealing with one transaction after another,
560     we will maintain a single-object free list so that we can hopefully
561     keep reusing the same transaction object. */
562  if (!ffsd->free_txn)
563    ffsd->free_txn = txn;
564  else
565    svn_pool_destroy(txn->pool);
566}
567
568
569/* Obtain a lock on the transaction list of filesystem FS, call BODY
570   with FS, BATON, and POOL, and then unlock the transaction list.
571   Return what BODY returned. */
572static svn_error_t *
573with_txnlist_lock(svn_fs_t *fs,
574                  svn_error_t *(*body)(svn_fs_t *fs,
575                                       const void *baton,
576                                       apr_pool_t *pool),
577                  const void *baton,
578                  apr_pool_t *pool)
579{
580  fs_fs_data_t *ffd = fs->fsap_data;
581  fs_fs_shared_data_t *ffsd = ffd->shared;
582
583  SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock,
584                       body(fs, baton, pool));
585
586  return SVN_NO_ERROR;
587}
588
589
590/* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
591static svn_error_t *
592get_lock_on_filesystem(const char *lock_filename,
593                       apr_pool_t *pool)
594{
595  svn_error_t *err = svn_io_file_lock2(lock_filename, TRUE, FALSE, pool);
596
597  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
598    {
599      /* No lock file?  No big deal; these are just empty files
600         anyway.  Create it and try again. */
601      svn_error_clear(err);
602      err = NULL;
603
604      SVN_ERR(svn_io_file_create(lock_filename, "", pool));
605      SVN_ERR(svn_io_file_lock2(lock_filename, TRUE, FALSE, pool));
606    }
607
608  return svn_error_trace(err);
609}
610
611/* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID.
612   When registered with the pool holding the lock on the lock file,
613   this makes sure the flag gets reset just before we release the lock. */
614static apr_status_t
615reset_lock_flag(void *baton_void)
616{
617  fs_fs_data_t *ffd = baton_void;
618  ffd->has_write_lock = FALSE;
619  return APR_SUCCESS;
620}
621
622/* Obtain a write lock on the file LOCK_FILENAME (protecting with
623   LOCK_MUTEX if APR is threaded) in a subpool of POOL, call BODY with
624   BATON and that subpool, destroy the subpool (releasing the write
625   lock) and return what BODY returned.  If IS_GLOBAL_LOCK is set,
626   set the HAS_WRITE_LOCK flag while we keep the write lock. */
627static svn_error_t *
628with_some_lock_file(svn_fs_t *fs,
629                    svn_error_t *(*body)(void *baton,
630                                         apr_pool_t *pool),
631                    void *baton,
632                    const char *lock_filename,
633                    svn_boolean_t is_global_lock,
634                    apr_pool_t *pool)
635{
636  apr_pool_t *subpool = svn_pool_create(pool);
637  svn_error_t *err = get_lock_on_filesystem(lock_filename, subpool);
638
639  if (!err)
640    {
641      fs_fs_data_t *ffd = fs->fsap_data;
642
643      if (is_global_lock)
644        {
645          /* set the "got the lock" flag and register reset function */
646          apr_pool_cleanup_register(subpool,
647                                    ffd,
648                                    reset_lock_flag,
649                                    apr_pool_cleanup_null);
650          ffd->has_write_lock = TRUE;
651        }
652
653      /* nobody else will modify the repo state
654         => read HEAD & pack info once */
655      if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
656        SVN_ERR(update_min_unpacked_rev(fs, pool));
657      SVN_ERR(get_youngest(&ffd->youngest_rev_cache, fs->path,
658                           pool));
659      err = body(baton, subpool);
660    }
661
662  svn_pool_destroy(subpool);
663
664  return svn_error_trace(err);
665}
666
667svn_error_t *
668svn_fs_fs__with_write_lock(svn_fs_t *fs,
669                           svn_error_t *(*body)(void *baton,
670                                                apr_pool_t *pool),
671                           void *baton,
672                           apr_pool_t *pool)
673{
674  fs_fs_data_t *ffd = fs->fsap_data;
675  fs_fs_shared_data_t *ffsd = ffd->shared;
676
677  SVN_MUTEX__WITH_LOCK(ffsd->fs_write_lock,
678                       with_some_lock_file(fs, body, baton,
679                                           path_lock(fs, pool),
680                                           TRUE,
681                                           pool));
682
683  return SVN_NO_ERROR;
684}
685
686/* Run BODY (with BATON and POOL) while the txn-current file
687   of FS is locked. */
688static svn_error_t *
689with_txn_current_lock(svn_fs_t *fs,
690                      svn_error_t *(*body)(void *baton,
691                                           apr_pool_t *pool),
692                      void *baton,
693                      apr_pool_t *pool)
694{
695  fs_fs_data_t *ffd = fs->fsap_data;
696  fs_fs_shared_data_t *ffsd = ffd->shared;
697
698  SVN_MUTEX__WITH_LOCK(ffsd->txn_current_lock,
699                       with_some_lock_file(fs, body, baton,
700                                           path_txn_current_lock(fs, pool),
701                                           FALSE,
702                                           pool));
703
704  return SVN_NO_ERROR;
705}
706
707/* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
708   which see. */
709struct unlock_proto_rev_baton
710{
711  const char *txn_id;
712  void *lockcookie;
713};
714
715/* Callback used in the implementation of unlock_proto_rev(). */
716static svn_error_t *
717unlock_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
718{
719  const struct unlock_proto_rev_baton *b = baton;
720  const char *txn_id = b->txn_id;
721  apr_file_t *lockfile = b->lockcookie;
722  fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, FALSE);
723  apr_status_t apr_err;
724
725  if (!txn)
726    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
727                             _("Can't unlock unknown transaction '%s'"),
728                             txn_id);
729  if (!txn->being_written)
730    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
731                             _("Can't unlock nonlocked transaction '%s'"),
732                             txn_id);
733
734  apr_err = apr_file_unlock(lockfile);
735  if (apr_err)
736    return svn_error_wrap_apr
737      (apr_err,
738       _("Can't unlock prototype revision lockfile for transaction '%s'"),
739       txn_id);
740  apr_err = apr_file_close(lockfile);
741  if (apr_err)
742    return svn_error_wrap_apr
743      (apr_err,
744       _("Can't close prototype revision lockfile for transaction '%s'"),
745       txn_id);
746
747  txn->being_written = FALSE;
748
749  return SVN_NO_ERROR;
750}
751
752/* Unlock the prototype revision file for transaction TXN_ID in filesystem
753   FS using cookie LOCKCOOKIE.  The original prototype revision file must
754   have been closed _before_ calling this function.
755
756   Perform temporary allocations in POOL. */
757static svn_error_t *
758unlock_proto_rev(svn_fs_t *fs, const char *txn_id, void *lockcookie,
759                 apr_pool_t *pool)
760{
761  struct unlock_proto_rev_baton b;
762
763  b.txn_id = txn_id;
764  b.lockcookie = lockcookie;
765  return with_txnlist_lock(fs, unlock_proto_rev_body, &b, pool);
766}
767
768/* Same as unlock_proto_rev(), but requires that the transaction list
769   lock is already held. */
770static svn_error_t *
771unlock_proto_rev_list_locked(svn_fs_t *fs, const char *txn_id,
772                             void *lockcookie,
773                             apr_pool_t *pool)
774{
775  struct unlock_proto_rev_baton b;
776
777  b.txn_id = txn_id;
778  b.lockcookie = lockcookie;
779  return unlock_proto_rev_body(fs, &b, pool);
780}
781
782/* A structure used by get_writable_proto_rev() and
783   get_writable_proto_rev_body(), which see. */
784struct get_writable_proto_rev_baton
785{
786  apr_file_t **file;
787  void **lockcookie;
788  const char *txn_id;
789};
790
791/* Callback used in the implementation of get_writable_proto_rev(). */
792static svn_error_t *
793get_writable_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
794{
795  const struct get_writable_proto_rev_baton *b = baton;
796  apr_file_t **file = b->file;
797  void **lockcookie = b->lockcookie;
798  const char *txn_id = b->txn_id;
799  svn_error_t *err;
800  fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, TRUE);
801
802  /* First, ensure that no thread in this process (including this one)
803     is currently writing to this transaction's proto-rev file. */
804  if (txn->being_written)
805    return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
806                             _("Cannot write to the prototype revision file "
807                               "of transaction '%s' because a previous "
808                               "representation is currently being written by "
809                               "this process"),
810                             txn_id);
811
812
813  /* We know that no thread in this process is writing to the proto-rev
814     file, and by extension, that no thread in this process is holding a
815     lock on the prototype revision lock file.  It is therefore safe
816     for us to attempt to lock this file, to see if any other process
817     is holding a lock. */
818
819  {
820    apr_file_t *lockfile;
821    apr_status_t apr_err;
822    const char *lockfile_path = path_txn_proto_rev_lock(fs, txn_id, pool);
823
824    /* Open the proto-rev lockfile, creating it if necessary, as it may
825       not exist if the transaction dates from before the lockfiles were
826       introduced.
827
828       ### We'd also like to use something like svn_io_file_lock2(), but
829           that forces us to create a subpool just to be able to unlock
830           the file, which seems a waste. */
831    SVN_ERR(svn_io_file_open(&lockfile, lockfile_path,
832                             APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
833
834    apr_err = apr_file_lock(lockfile,
835                            APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK);
836    if (apr_err)
837      {
838        svn_error_clear(svn_io_file_close(lockfile, pool));
839
840        if (APR_STATUS_IS_EAGAIN(apr_err))
841          return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
842                                   _("Cannot write to the prototype revision "
843                                     "file of transaction '%s' because a "
844                                     "previous representation is currently "
845                                     "being written by another process"),
846                                   txn_id);
847
848        return svn_error_wrap_apr(apr_err,
849                                  _("Can't get exclusive lock on file '%s'"),
850                                  svn_dirent_local_style(lockfile_path, pool));
851      }
852
853    *lockcookie = lockfile;
854  }
855
856  /* We've successfully locked the transaction; mark it as such. */
857  txn->being_written = TRUE;
858
859
860  /* Now open the prototype revision file and seek to the end. */
861  err = svn_io_file_open(file, path_txn_proto_rev(fs, txn_id, pool),
862                         APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT, pool);
863
864  /* You might expect that we could dispense with the following seek
865     and achieve the same thing by opening the file using APR_APPEND.
866     Unfortunately, APR's buffered file implementation unconditionally
867     places its initial file pointer at the start of the file (even for
868     files opened with APR_APPEND), so we need this seek to reconcile
869     the APR file pointer to the OS file pointer (since we need to be
870     able to read the current file position later). */
871  if (!err)
872    {
873      apr_off_t offset = 0;
874      err = svn_io_file_seek(*file, APR_END, &offset, pool);
875    }
876
877  if (err)
878    {
879      err = svn_error_compose_create(
880              err,
881              unlock_proto_rev_list_locked(fs, txn_id, *lockcookie, pool));
882
883      *lockcookie = NULL;
884    }
885
886  return svn_error_trace(err);
887}
888
889/* Get a handle to the prototype revision file for transaction TXN_ID in
890   filesystem FS, and lock it for writing.  Return FILE, a file handle
891   positioned at the end of the file, and LOCKCOOKIE, a cookie that
892   should be passed to unlock_proto_rev() to unlock the file once FILE
893   has been closed.
894
895   If the prototype revision file is already locked, return error
896   SVN_ERR_FS_REP_BEING_WRITTEN.
897
898   Perform all allocations in POOL. */
899static svn_error_t *
900get_writable_proto_rev(apr_file_t **file,
901                       void **lockcookie,
902                       svn_fs_t *fs, const char *txn_id,
903                       apr_pool_t *pool)
904{
905  struct get_writable_proto_rev_baton b;
906
907  b.file = file;
908  b.lockcookie = lockcookie;
909  b.txn_id = txn_id;
910
911  return with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool);
912}
913
914/* Callback used in the implementation of purge_shared_txn(). */
915static svn_error_t *
916purge_shared_txn_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
917{
918  const char *txn_id = baton;
919
920  free_shared_txn(fs, txn_id);
921  svn_fs_fs__reset_txn_caches(fs);
922
923  return SVN_NO_ERROR;
924}
925
926/* Purge the shared data for transaction TXN_ID in filesystem FS.
927   Perform all allocations in POOL. */
928static svn_error_t *
929purge_shared_txn(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
930{
931  return with_txnlist_lock(fs, purge_shared_txn_body, txn_id, pool);
932}
933
934
935
936/* Fetch the current offset of FILE into *OFFSET_P. */
937static svn_error_t *
938get_file_offset(apr_off_t *offset_p, apr_file_t *file, apr_pool_t *pool)
939{
940  apr_off_t offset;
941
942  /* Note that, for buffered files, one (possibly surprising) side-effect
943     of this call is to flush any unwritten data to disk. */
944  offset = 0;
945  SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool));
946  *offset_p = offset;
947
948  return SVN_NO_ERROR;
949}
950
951
952/* Check that BUF, a nul-terminated buffer of text from file PATH,
953   contains only digits at OFFSET and beyond, raising an error if not.
954   TITLE contains a user-visible description of the file, usually the
955   short file name.
956
957   Uses POOL for temporary allocation. */
958static svn_error_t *
959check_file_buffer_numeric(const char *buf, apr_off_t offset,
960                          const char *path, const char *title,
961                          apr_pool_t *pool)
962{
963  const char *p;
964
965  for (p = buf + offset; *p; p++)
966    if (!svn_ctype_isdigit(*p))
967      return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
968        _("%s file '%s' contains unexpected non-digit '%c' within '%s'"),
969        title, svn_dirent_local_style(path, pool), *p, buf);
970
971  return SVN_NO_ERROR;
972}
973
974/* Check that BUF, a nul-terminated buffer of text from format file PATH,
975   contains only digits at OFFSET and beyond, raising an error if not.
976
977   Uses POOL for temporary allocation. */
978static svn_error_t *
979check_format_file_buffer_numeric(const char *buf, apr_off_t offset,
980                                 const char *path, apr_pool_t *pool)
981{
982  return check_file_buffer_numeric(buf, offset, path, "Format", pool);
983}
984
985/* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
986   number is not the same as a format number supported by this
987   Subversion. */
988static svn_error_t *
989check_format(int format)
990{
991  /* Blacklist.  These formats may be either younger or older than
992     SVN_FS_FS__FORMAT_NUMBER, but we don't support them. */
993  if (format == SVN_FS_FS__PACKED_REVPROP_SQLITE_DEV_FORMAT)
994    return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
995                             _("Found format '%d', only created by "
996                               "unreleased dev builds; see "
997                               "http://subversion.apache.org"
998                               "/docs/release-notes/1.7#revprop-packing"),
999                             format);
1000
1001  /* We support all formats from 1-current simultaneously */
1002  if (1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER)
1003    return SVN_NO_ERROR;
1004
1005  return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
1006     _("Expected FS format between '1' and '%d'; found format '%d'"),
1007     SVN_FS_FS__FORMAT_NUMBER, format);
1008}
1009
1010/* Read the format number and maximum number of files per directory
1011   from PATH and return them in *PFORMAT and *MAX_FILES_PER_DIR
1012   respectively.
1013
1014   *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and
1015   will be set to zero if a linear scheme should be used.
1016
1017   Use POOL for temporary allocation. */
1018static svn_error_t *
1019read_format(int *pformat, int *max_files_per_dir,
1020            const char *path, apr_pool_t *pool)
1021{
1022  svn_error_t *err;
1023  svn_stream_t *stream;
1024  svn_stringbuf_t *content;
1025  svn_stringbuf_t *buf;
1026  svn_boolean_t eos = FALSE;
1027
1028  err = svn_stringbuf_from_file2(&content, path, pool);
1029  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
1030    {
1031      /* Treat an absent format file as format 1.  Do not try to
1032         create the format file on the fly, because the repository
1033         might be read-only for us, or this might be a read-only
1034         operation, and the spirit of FSFS is to make no changes
1035         whatseover in read-only operations.  See thread starting at
1036         http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=97600
1037         for more. */
1038      svn_error_clear(err);
1039      *pformat = 1;
1040      *max_files_per_dir = 0;
1041
1042      return SVN_NO_ERROR;
1043    }
1044  SVN_ERR(err);
1045
1046  stream = svn_stream_from_stringbuf(content, pool);
1047  SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool));
1048  if (buf->len == 0 && eos)
1049    {
1050      /* Return a more useful error message. */
1051      return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
1052                               _("Can't read first line of format file '%s'"),
1053                               svn_dirent_local_style(path, pool));
1054    }
1055
1056  /* Check that the first line contains only digits. */
1057  SVN_ERR(check_format_file_buffer_numeric(buf->data, 0, path, pool));
1058  SVN_ERR(svn_cstring_atoi(pformat, buf->data));
1059
1060  /* Check that we support this format at all */
1061  SVN_ERR(check_format(*pformat));
1062
1063  /* Set the default values for anything that can be set via an option. */
1064  *max_files_per_dir = 0;
1065
1066  /* Read any options. */
1067  while (!eos)
1068    {
1069      SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool));
1070      if (buf->len == 0)
1071        break;
1072
1073      if (*pformat >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT &&
1074          strncmp(buf->data, "layout ", 7) == 0)
1075        {
1076          if (strcmp(buf->data + 7, "linear") == 0)
1077            {
1078              *max_files_per_dir = 0;
1079              continue;
1080            }
1081
1082          if (strncmp(buf->data + 7, "sharded ", 8) == 0)
1083            {
1084              /* Check that the argument is numeric. */
1085              SVN_ERR(check_format_file_buffer_numeric(buf->data, 15, path, pool));
1086              SVN_ERR(svn_cstring_atoi(max_files_per_dir, buf->data + 15));
1087              continue;
1088            }
1089        }
1090
1091      return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
1092         _("'%s' contains invalid filesystem format option '%s'"),
1093         svn_dirent_local_style(path, pool), buf->data);
1094    }
1095
1096  return SVN_NO_ERROR;
1097}
1098
1099/* Write the format number and maximum number of files per directory
1100   to a new format file in PATH, possibly expecting to overwrite a
1101   previously existing file.
1102
1103   Use POOL for temporary allocation. */
1104static svn_error_t *
1105write_format(const char *path, int format, int max_files_per_dir,
1106             svn_boolean_t overwrite, apr_pool_t *pool)
1107{
1108  svn_stringbuf_t *sb;
1109
1110  SVN_ERR_ASSERT(1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER);
1111
1112  sb = svn_stringbuf_createf(pool, "%d\n", format);
1113
1114  if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
1115    {
1116      if (max_files_per_dir)
1117        svn_stringbuf_appendcstr(sb, apr_psprintf(pool, "layout sharded %d\n",
1118                                                  max_files_per_dir));
1119      else
1120        svn_stringbuf_appendcstr(sb, "layout linear\n");
1121    }
1122
1123  /* svn_io_write_version_file() does a load of magic to allow it to
1124     replace version files that already exist.  We only need to do
1125     that when we're allowed to overwrite an existing file. */
1126  if (! overwrite)
1127    {
1128      /* Create the file */
1129      SVN_ERR(svn_io_file_create(path, sb->data, pool));
1130    }
1131  else
1132    {
1133      const char *path_tmp;
1134
1135      SVN_ERR(svn_io_write_unique(&path_tmp,
1136                                  svn_dirent_dirname(path, pool),
1137                                  sb->data, sb->len,
1138                                  svn_io_file_del_none, pool));
1139
1140      /* rename the temp file as the real destination */
1141      SVN_ERR(svn_io_file_rename(path_tmp, path, pool));
1142    }
1143
1144  /* And set the perms to make it read only */
1145  return svn_io_set_file_read_only(path, FALSE, pool);
1146}
1147
1148svn_boolean_t
1149svn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs)
1150{
1151  fs_fs_data_t *ffd = fs->fsap_data;
1152  return ffd->format >= SVN_FS_FS__MIN_MERGEINFO_FORMAT;
1153}
1154
1155/* Read the configuration information of the file system at FS_PATH
1156 * and set the respective values in FFD.  Use POOL for allocations.
1157 */
1158static svn_error_t *
1159read_config(fs_fs_data_t *ffd,
1160            const char *fs_path,
1161            apr_pool_t *pool)
1162{
1163  SVN_ERR(svn_config_read3(&ffd->config,
1164                           svn_dirent_join(fs_path, PATH_CONFIG, pool),
1165                           FALSE, FALSE, FALSE, pool));
1166
1167  /* Initialize ffd->rep_sharing_allowed. */
1168  if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
1169    SVN_ERR(svn_config_get_bool(ffd->config, &ffd->rep_sharing_allowed,
1170                                CONFIG_SECTION_REP_SHARING,
1171                                CONFIG_OPTION_ENABLE_REP_SHARING, TRUE));
1172  else
1173    ffd->rep_sharing_allowed = FALSE;
1174
1175  /* Initialize deltification settings in ffd. */
1176  if (ffd->format >= SVN_FS_FS__MIN_DELTIFICATION_FORMAT)
1177    {
1178      SVN_ERR(svn_config_get_bool(ffd->config, &ffd->deltify_directories,
1179                                  CONFIG_SECTION_DELTIFICATION,
1180                                  CONFIG_OPTION_ENABLE_DIR_DELTIFICATION,
1181                                  FALSE));
1182      SVN_ERR(svn_config_get_bool(ffd->config, &ffd->deltify_properties,
1183                                  CONFIG_SECTION_DELTIFICATION,
1184                                  CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION,
1185                                  FALSE));
1186      SVN_ERR(svn_config_get_int64(ffd->config, &ffd->max_deltification_walk,
1187                                   CONFIG_SECTION_DELTIFICATION,
1188                                   CONFIG_OPTION_MAX_DELTIFICATION_WALK,
1189                                   SVN_FS_FS_MAX_DELTIFICATION_WALK));
1190      SVN_ERR(svn_config_get_int64(ffd->config, &ffd->max_linear_deltification,
1191                                   CONFIG_SECTION_DELTIFICATION,
1192                                   CONFIG_OPTION_MAX_LINEAR_DELTIFICATION,
1193                                   SVN_FS_FS_MAX_LINEAR_DELTIFICATION));
1194    }
1195  else
1196    {
1197      ffd->deltify_directories = FALSE;
1198      ffd->deltify_properties = FALSE;
1199      ffd->max_deltification_walk = SVN_FS_FS_MAX_DELTIFICATION_WALK;
1200      ffd->max_linear_deltification = SVN_FS_FS_MAX_LINEAR_DELTIFICATION;
1201    }
1202
1203  /* Initialize revprop packing settings in ffd. */
1204  if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
1205    {
1206      SVN_ERR(svn_config_get_bool(ffd->config, &ffd->compress_packed_revprops,
1207                                  CONFIG_SECTION_PACKED_REVPROPS,
1208                                  CONFIG_OPTION_COMPRESS_PACKED_REVPROPS,
1209                                  FALSE));
1210      SVN_ERR(svn_config_get_int64(ffd->config, &ffd->revprop_pack_size,
1211                                   CONFIG_SECTION_PACKED_REVPROPS,
1212                                   CONFIG_OPTION_REVPROP_PACK_SIZE,
1213                                   ffd->compress_packed_revprops
1214                                       ? 0x100
1215                                       : 0x40));
1216
1217      ffd->revprop_pack_size *= 1024;
1218    }
1219  else
1220    {
1221      ffd->revprop_pack_size = 0x10000;
1222      ffd->compress_packed_revprops = FALSE;
1223    }
1224
1225  return SVN_NO_ERROR;
1226}
1227
1228static svn_error_t *
1229write_config(svn_fs_t *fs,
1230             apr_pool_t *pool)
1231{
1232#define NL APR_EOL_STR
1233  static const char * const fsfs_conf_contents =
1234"### This file controls the configuration of the FSFS filesystem."           NL
1235""                                                                           NL
1236"[" SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "]"                          NL
1237"### These options name memcached servers used to cache internal FSFS"       NL
1238"### data.  See http://www.danga.com/memcached/ for more information on"     NL
1239"### memcached.  To use memcached with FSFS, run one or more memcached"      NL
1240"### servers, and specify each of them as an option like so:"                NL
1241"# first-server = 127.0.0.1:11211"                                           NL
1242"# remote-memcached = mymemcached.corp.example.com:11212"                    NL
1243"### The option name is ignored; the value is of the form HOST:PORT."        NL
1244"### memcached servers can be shared between multiple repositories;"         NL
1245"### however, if you do this, you *must* ensure that repositories have"      NL
1246"### distinct UUIDs and paths, or else cached data from one repository"      NL
1247"### might be used by another accidentally.  Note also that memcached has"   NL
1248"### no authentication for reads or writes, so you must ensure that your"    NL
1249"### memcached servers are only accessible by trusted users."                NL
1250""                                                                           NL
1251"[" CONFIG_SECTION_CACHES "]"                                                NL
1252"### When a cache-related error occurs, normally Subversion ignores it"      NL
1253"### and continues, logging an error if the server is appropriately"         NL
1254"### configured (and ignoring it with file:// access).  To make"             NL
1255"### Subversion never ignore cache errors, uncomment this line."             NL
1256"# " CONFIG_OPTION_FAIL_STOP " = true"                                       NL
1257""                                                                           NL
1258"[" CONFIG_SECTION_REP_SHARING "]"                                           NL
1259"### To conserve space, the filesystem can optionally avoid storing"         NL
1260"### duplicate representations.  This comes at a slight cost in"             NL
1261"### performance, as maintaining a database of shared representations can"   NL
1262"### increase commit times.  The space savings are dependent upon the size"  NL
1263"### of the repository, the number of objects it contains and the amount of" NL
1264"### duplication between them, usually a function of the branching and"      NL
1265"### merging process."                                                       NL
1266"###"                                                                        NL
1267"### The following parameter enables rep-sharing in the repository.  It can" NL
1268"### be switched on and off at will, but for best space-saving results"      NL
1269"### should be enabled consistently over the life of the repository."        NL
1270"### 'svnadmin verify' will check the rep-cache regardless of this setting." NL
1271"### rep-sharing is enabled by default."                                     NL
1272"# " CONFIG_OPTION_ENABLE_REP_SHARING " = true"                              NL
1273""                                                                           NL
1274"[" CONFIG_SECTION_DELTIFICATION "]"                                         NL
1275"### To conserve space, the filesystem stores data as differences against"   NL
1276"### existing representations.  This comes at a slight cost in performance," NL
1277"### as calculating differences can increase commit times.  Reading data"    NL
1278"### will also create higher CPU load and the data will be fragmented."      NL
1279"### Since deltification tends to save significant amounts of disk space,"   NL
1280"### the overall I/O load can actually be lower."                            NL
1281"###"                                                                        NL
1282"### The options in this section allow for tuning the deltification"         NL
1283"### strategy.  Their effects on data size and server performance may vary"  NL
1284"### from one repository to another.  Versions prior to 1.8 will ignore"     NL
1285"### this section."                                                          NL
1286"###"                                                                        NL
1287"### The following parameter enables deltification for directories. It can"  NL
1288"### be switched on and off at will, but for best space-saving results"      NL
1289"### should be enabled consistently over the life of the repository."        NL
1290"### Repositories containing large directories will benefit greatly."        NL
1291"### In rarely read repositories, the I/O overhead may be significant as"    NL
1292"### cache hit rates will most likely be low"                                NL
1293"### directory deltification is disabled by default."                        NL
1294"# " CONFIG_OPTION_ENABLE_DIR_DELTIFICATION " = false"                       NL
1295"###"                                                                        NL
1296"### The following parameter enables deltification for properties on files"  NL
1297"### and directories.  Overall, this is a minor tuning option but can save"  NL
1298"### some disk space if you merge frequently or frequently change node"      NL
1299"### properties.  You should not activate this if rep-sharing has been"      NL
1300"### disabled because this may result in a net increase in repository size." NL
1301"### property deltification is disabled by default."                         NL
1302"# " CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION " = false"                     NL
1303"###"                                                                        NL
1304"### During commit, the server may need to walk the whole change history of" NL
1305"### of a given node to find a suitable deltification base.  This linear"    NL
1306"### process can impact commit times, svnadmin load and similar operations." NL
1307"### This setting limits the depth of the deltification history.  If the"    NL
1308"### threshold has been reached, the node will be stored as fulltext and a"  NL
1309"### new deltification history begins."                                      NL
1310"### Note, this is unrelated to svn log."                                    NL
1311"### Very large values rarely provide significant additional savings but"    NL
1312"### can impact performance greatly - in particular if directory"            NL
1313"### deltification has been activated.  Very small values may be useful in"  NL
1314"### repositories that are dominated by large, changing binaries."           NL
1315"### Should be a power of two minus 1.  A value of 0 will effectively"       NL
1316"### disable deltification."                                                 NL
1317"### For 1.8, the default value is 1023; earlier versions have no limit."    NL
1318"# " CONFIG_OPTION_MAX_DELTIFICATION_WALK " = 1023"                          NL
1319"###"                                                                        NL
1320"### The skip-delta scheme used by FSFS tends to repeatably store redundant" NL
1321"### delta information where a simple delta against the latest version is"   NL
1322"### often smaller.  By default, 1.8+ will therefore use skip deltas only"   NL
1323"### after the linear chain of deltas has grown beyond the threshold"        NL
1324"### specified by this setting."                                             NL
1325"### Values up to 64 can result in some reduction in repository size for"    NL
1326"### the cost of quickly increasing I/O and CPU costs. Similarly, smaller"   NL
1327"### numbers can reduce those costs at the cost of more disk space.  For"    NL
1328"### rarely read repositories or those containing larger binaries, this may" NL
1329"### present a better trade-off."                                            NL
1330"### Should be a power of two.  A value of 1 or smaller will cause the"      NL
1331"### exclusive use of skip-deltas (as in pre-1.8)."                          NL
1332"### For 1.8, the default value is 16; earlier versions use 1."              NL
1333"# " CONFIG_OPTION_MAX_LINEAR_DELTIFICATION " = 16"                          NL
1334""                                                                           NL
1335"[" CONFIG_SECTION_PACKED_REVPROPS "]"                                       NL
1336"### This parameter controls the size (in kBytes) of packed revprop files."  NL
1337"### Revprops of consecutive revisions will be concatenated into a single"   NL
1338"### file up to but not exceeding the threshold given here.  However, each"  NL
1339"### pack file may be much smaller and revprops of a single revision may be" NL
1340"### much larger than the limit set here.  The threshold will be applied"    NL
1341"### before optional compression takes place."                               NL
1342"### Large values will reduce disk space usage at the expense of increased"  NL
1343"### latency and CPU usage reading and changing individual revprops.  They"  NL
1344"### become an advantage when revprop caching has been enabled because a"    NL
1345"### lot of data can be read in one go.  Values smaller than 4 kByte will"   NL
1346"### not improve latency any further and quickly render revprop packing"     NL
1347"### ineffective."                                                           NL
1348"### revprop-pack-size is 64 kBytes by default for non-compressed revprop"   NL
1349"### pack files and 256 kBytes when compression has been enabled."           NL
1350"# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 64"                                 NL
1351"###"                                                                        NL
1352"### To save disk space, packed revprop files may be compressed.  Standard"  NL
1353"### revprops tend to allow for very effective compression.  Reading and"    NL
1354"### even more so writing, become significantly more CPU intensive.  With"   NL
1355"### revprop caching enabled, the overhead can be offset by reduced I/O"     NL
1356"### unless you often modify revprops after packing."                        NL
1357"### Compressing packed revprops is disabled by default."                    NL
1358"# " CONFIG_OPTION_COMPRESS_PACKED_REVPROPS " = false"                       NL
1359;
1360#undef NL
1361  return svn_io_file_create(svn_dirent_join(fs->path, PATH_CONFIG, pool),
1362                            fsfs_conf_contents, pool);
1363}
1364
1365static svn_error_t *
1366read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
1367                      const char *path,
1368                      apr_pool_t *pool)
1369{
1370  char buf[80];
1371  apr_file_t *file;
1372  apr_size_t len;
1373
1374  SVN_ERR(svn_io_file_open(&file, path, APR_READ | APR_BUFFERED,
1375                           APR_OS_DEFAULT, pool));
1376  len = sizeof(buf);
1377  SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
1378  SVN_ERR(svn_io_file_close(file, pool));
1379
1380  *min_unpacked_rev = SVN_STR_TO_REV(buf);
1381  return SVN_NO_ERROR;
1382}
1383
1384static svn_error_t *
1385update_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool)
1386{
1387  fs_fs_data_t *ffd = fs->fsap_data;
1388
1389  SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT);
1390
1391  return read_min_unpacked_rev(&ffd->min_unpacked_rev,
1392                               path_min_unpacked_rev(fs, pool),
1393                               pool);
1394}
1395
1396svn_error_t *
1397svn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool)
1398{
1399  fs_fs_data_t *ffd = fs->fsap_data;
1400  apr_file_t *uuid_file;
1401  int format, max_files_per_dir;
1402  char buf[APR_UUID_FORMATTED_LENGTH + 2];
1403  apr_size_t limit;
1404
1405  fs->path = apr_pstrdup(fs->pool, path);
1406
1407  /* Read the FS format number. */
1408  SVN_ERR(read_format(&format, &max_files_per_dir,
1409                      path_format(fs, pool), pool));
1410
1411  /* Now we've got a format number no matter what. */
1412  ffd->format = format;
1413  ffd->max_files_per_dir = max_files_per_dir;
1414
1415  /* Read in and cache the repository uuid. */
1416  SVN_ERR(svn_io_file_open(&uuid_file, path_uuid(fs, pool),
1417                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
1418
1419  limit = sizeof(buf);
1420  SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, pool));
1421  fs->uuid = apr_pstrdup(fs->pool, buf);
1422
1423  SVN_ERR(svn_io_file_close(uuid_file, pool));
1424
1425  /* Read the min unpacked revision. */
1426  if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1427    SVN_ERR(update_min_unpacked_rev(fs, pool));
1428
1429  /* Read the configuration file. */
1430  SVN_ERR(read_config(ffd, fs->path, pool));
1431
1432  return get_youngest(&(ffd->youngest_rev_cache), path, pool);
1433}
1434
1435/* Wrapper around svn_io_file_create which ignores EEXIST. */
1436static svn_error_t *
1437create_file_ignore_eexist(const char *file,
1438                          const char *contents,
1439                          apr_pool_t *pool)
1440{
1441  svn_error_t *err = svn_io_file_create(file, contents, pool);
1442  if (err && APR_STATUS_IS_EEXIST(err->apr_err))
1443    {
1444      svn_error_clear(err);
1445      err = SVN_NO_ERROR;
1446    }
1447  return svn_error_trace(err);
1448}
1449
1450/* forward declarations */
1451
1452static svn_error_t *
1453pack_revprops_shard(const char *pack_file_dir,
1454                    const char *shard_path,
1455                    apr_int64_t shard,
1456                    int max_files_per_dir,
1457                    apr_off_t max_pack_size,
1458                    int compression_level,
1459                    svn_cancel_func_t cancel_func,
1460                    void *cancel_baton,
1461                    apr_pool_t *scratch_pool);
1462
1463static svn_error_t *
1464delete_revprops_shard(const char *shard_path,
1465                      apr_int64_t shard,
1466                      int max_files_per_dir,
1467                      svn_cancel_func_t cancel_func,
1468                      void *cancel_baton,
1469                      apr_pool_t *scratch_pool);
1470
1471/* In the filesystem FS, pack all revprop shards up to min_unpacked_rev.
1472 *
1473 * NOTE: Keep the old non-packed shards around until after the format bump.
1474 * Otherwise, re-running upgrade will drop the packed revprop shard but
1475 * have no unpacked data anymore.  Call upgrade_cleanup_pack_revprops after
1476 * the bump.
1477 *
1478 * Use SCRATCH_POOL for temporary allocations.
1479 */
1480static svn_error_t *
1481upgrade_pack_revprops(svn_fs_t *fs,
1482                      apr_pool_t *scratch_pool)
1483{
1484  fs_fs_data_t *ffd = fs->fsap_data;
1485  const char *revprops_shard_path;
1486  const char *revprops_pack_file_dir;
1487  apr_int64_t shard;
1488  apr_int64_t first_unpacked_shard
1489    =  ffd->min_unpacked_rev / ffd->max_files_per_dir;
1490
1491  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1492  const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
1493                                              scratch_pool);
1494  int compression_level = ffd->compress_packed_revprops
1495                           ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
1496                           : SVN_DELTA_COMPRESSION_LEVEL_NONE;
1497
1498  /* first, pack all revprops shards to match the packed revision shards */
1499  for (shard = 0; shard < first_unpacked_shard; ++shard)
1500    {
1501      revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
1502                   apr_psprintf(iterpool,
1503                                "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
1504                                shard),
1505                   iterpool);
1506      revprops_shard_path = svn_dirent_join(revsprops_dir,
1507                       apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
1508                       iterpool);
1509
1510      SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path,
1511                                  shard, ffd->max_files_per_dir,
1512                                  (int)(0.9 * ffd->revprop_pack_size),
1513                                  compression_level,
1514                                  NULL, NULL, iterpool));
1515      svn_pool_clear(iterpool);
1516    }
1517
1518  svn_pool_destroy(iterpool);
1519
1520  return SVN_NO_ERROR;
1521}
1522
1523/* In the filesystem FS, remove all non-packed revprop shards up to
1524 * min_unpacked_rev.  Use SCRATCH_POOL for temporary allocations.
1525 * See upgrade_pack_revprops for more info.
1526 */
1527static svn_error_t *
1528upgrade_cleanup_pack_revprops(svn_fs_t *fs,
1529                              apr_pool_t *scratch_pool)
1530{
1531  fs_fs_data_t *ffd = fs->fsap_data;
1532  const char *revprops_shard_path;
1533  apr_int64_t shard;
1534  apr_int64_t first_unpacked_shard
1535    =  ffd->min_unpacked_rev / ffd->max_files_per_dir;
1536
1537  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1538  const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
1539                                              scratch_pool);
1540
1541  /* delete the non-packed revprops shards afterwards */
1542  for (shard = 0; shard < first_unpacked_shard; ++shard)
1543    {
1544      revprops_shard_path = svn_dirent_join(revsprops_dir,
1545                       apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
1546                       iterpool);
1547      SVN_ERR(delete_revprops_shard(revprops_shard_path,
1548                                    shard, ffd->max_files_per_dir,
1549                                    NULL, NULL, iterpool));
1550      svn_pool_clear(iterpool);
1551    }
1552
1553  svn_pool_destroy(iterpool);
1554
1555  return SVN_NO_ERROR;
1556}
1557
1558static svn_error_t *
1559upgrade_body(void *baton, apr_pool_t *pool)
1560{
1561  svn_fs_t *fs = baton;
1562  int format, max_files_per_dir;
1563  const char *format_path = path_format(fs, pool);
1564  svn_node_kind_t kind;
1565  svn_boolean_t needs_revprop_shard_cleanup = FALSE;
1566
1567  /* Read the FS format number and max-files-per-dir setting. */
1568  SVN_ERR(read_format(&format, &max_files_per_dir, format_path, pool));
1569
1570  /* If the config file does not exist, create one. */
1571  SVN_ERR(svn_io_check_path(svn_dirent_join(fs->path, PATH_CONFIG, pool),
1572                            &kind, pool));
1573  switch (kind)
1574    {
1575    case svn_node_none:
1576      SVN_ERR(write_config(fs, pool));
1577      break;
1578    case svn_node_file:
1579      break;
1580    default:
1581      return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
1582                               _("'%s' is not a regular file."
1583                                 " Please move it out of "
1584                                 "the way and try again"),
1585                               svn_dirent_join(fs->path, PATH_CONFIG, pool));
1586    }
1587
1588  /* If we're already up-to-date, there's nothing else to be done here. */
1589  if (format == SVN_FS_FS__FORMAT_NUMBER)
1590    return SVN_NO_ERROR;
1591
1592  /* If our filesystem predates the existance of the 'txn-current
1593     file', make that file and its corresponding lock file. */
1594  if (format < SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1595    {
1596      SVN_ERR(create_file_ignore_eexist(path_txn_current(fs, pool), "0\n",
1597                                        pool));
1598      SVN_ERR(create_file_ignore_eexist(path_txn_current_lock(fs, pool), "",
1599                                        pool));
1600    }
1601
1602  /* If our filesystem predates the existance of the 'txn-protorevs'
1603     dir, make that directory.  */
1604  if (format < SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1605    {
1606      /* We don't use path_txn_proto_rev() here because it expects
1607         we've already bumped our format. */
1608      SVN_ERR(svn_io_make_dir_recursively(
1609          svn_dirent_join(fs->path, PATH_TXN_PROTOS_DIR, pool), pool));
1610    }
1611
1612  /* If our filesystem is new enough, write the min unpacked rev file. */
1613  if (format < SVN_FS_FS__MIN_PACKED_FORMAT)
1614    SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool));
1615
1616  /* If the file system supports revision packing but not revprop packing
1617     *and* the FS has been sharded, pack the revprops up to the point that
1618     revision data has been packed.  However, keep the non-packed revprop
1619     files around until after the format bump */
1620  if (   format >= SVN_FS_FS__MIN_PACKED_FORMAT
1621      && format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
1622      && max_files_per_dir > 0)
1623    {
1624      needs_revprop_shard_cleanup = TRUE;
1625      SVN_ERR(upgrade_pack_revprops(fs, pool));
1626    }
1627
1628  /* Bump the format file. */
1629  SVN_ERR(write_format(format_path, SVN_FS_FS__FORMAT_NUMBER,
1630                       max_files_per_dir, TRUE, pool));
1631
1632  /* Now, it is safe to remove the redundant revprop files. */
1633  if (needs_revprop_shard_cleanup)
1634    SVN_ERR(upgrade_cleanup_pack_revprops(fs, pool));
1635
1636  /* Done */
1637  return SVN_NO_ERROR;
1638}
1639
1640
1641svn_error_t *
1642svn_fs_fs__upgrade(svn_fs_t *fs, apr_pool_t *pool)
1643{
1644  return svn_fs_fs__with_write_lock(fs, upgrade_body, (void *)fs, pool);
1645}
1646
1647
1648/* Functions for dealing with recoverable errors on mutable files
1649 *
1650 * Revprops, current, and txn-current files are mutable; that is, they
1651 * change as part of normal fsfs operation, in constrat to revs files, or
1652 * the format file, which are written once at create (or upgrade) time.
1653 * When more than one host writes to the same repository, we will
1654 * sometimes see these recoverable errors when accesssing these files.
1655 *
1656 * These errors all relate to NFS, and thus we only use this retry code if
1657 * ESTALE is defined.
1658 *
1659 ** ESTALE
1660 *
1661 * In NFS v3 and under, the server doesn't track opened files.  If you
1662 * unlink(2) or rename(2) a file held open by another process *on the
1663 * same host*, that host's kernel typically renames the file to
1664 * .nfsXXXX and automatically deletes that when it's no longer open,
1665 * but this behavior is not required.
1666 *
1667 * For obvious reasons, this does not work *across hosts*.  No one
1668 * knows about the opened file; not the server, and not the deleting
1669 * client.  So the file vanishes, and the reader gets stale NFS file
1670 * handle.
1671 *
1672 ** EIO, ENOENT
1673 *
1674 * Some client implementations (at least the 2.6.18.5 kernel that ships
1675 * with Ubuntu Dapper) sometimes give spurious ENOENT (only on open) or
1676 * even EIO errors when trying to read these files that have been renamed
1677 * over on some other host.
1678 *
1679 ** Solution
1680 *
1681 * Try open and read of such files in try_stringbuf_from_file().  Call
1682 * this function within a loop of RECOVERABLE_RETRY_COUNT iterations
1683 * (though, realistically, the second try will succeed).
1684 */
1685
1686#define RECOVERABLE_RETRY_COUNT 10
1687
1688/* Read the file at PATH and return its content in *CONTENT. *CONTENT will
1689 * not be modified unless the whole file was read successfully.
1690 *
1691 * ESTALE, EIO and ENOENT will not cause this function to return an error
1692 * unless LAST_ATTEMPT has been set.  If MISSING is not NULL, indicate
1693 * missing files (ENOENT) there.
1694 *
1695 * Use POOL for allocations.
1696 */
1697static svn_error_t *
1698try_stringbuf_from_file(svn_stringbuf_t **content,
1699                        svn_boolean_t *missing,
1700                        const char *path,
1701                        svn_boolean_t last_attempt,
1702                        apr_pool_t *pool)
1703{
1704  svn_error_t *err = svn_stringbuf_from_file2(content, path, pool);
1705  if (missing)
1706    *missing = FALSE;
1707
1708  if (err)
1709    {
1710      *content = NULL;
1711
1712      if (APR_STATUS_IS_ENOENT(err->apr_err))
1713        {
1714          if (!last_attempt)
1715            {
1716              svn_error_clear(err);
1717              if (missing)
1718                *missing = TRUE;
1719              return SVN_NO_ERROR;
1720            }
1721        }
1722#ifdef ESTALE
1723      else if (APR_TO_OS_ERROR(err->apr_err) == ESTALE
1724                || APR_TO_OS_ERROR(err->apr_err) == EIO)
1725        {
1726          if (!last_attempt)
1727            {
1728              svn_error_clear(err);
1729              return SVN_NO_ERROR;
1730            }
1731        }
1732#endif
1733    }
1734
1735  return svn_error_trace(err);
1736}
1737
1738/* Read the 'current' file FNAME and store the contents in *BUF.
1739   Allocations are performed in POOL. */
1740static svn_error_t *
1741read_content(svn_stringbuf_t **content, const char *fname, apr_pool_t *pool)
1742{
1743  int i;
1744  *content = NULL;
1745
1746  for (i = 0; !*content && (i < RECOVERABLE_RETRY_COUNT); ++i)
1747    SVN_ERR(try_stringbuf_from_file(content, NULL,
1748                                    fname, i + 1 < RECOVERABLE_RETRY_COUNT,
1749                                    pool));
1750
1751  if (!*content)
1752    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1753                             _("Can't read '%s'"),
1754                             svn_dirent_local_style(fname, pool));
1755
1756  return SVN_NO_ERROR;
1757}
1758
1759/* Find the youngest revision in a repository at path FS_PATH and
1760   return it in *YOUNGEST_P.  Perform temporary allocations in
1761   POOL. */
1762static svn_error_t *
1763get_youngest(svn_revnum_t *youngest_p,
1764             const char *fs_path,
1765             apr_pool_t *pool)
1766{
1767  svn_stringbuf_t *buf;
1768  SVN_ERR(read_content(&buf, svn_dirent_join(fs_path, PATH_CURRENT, pool),
1769                       pool));
1770
1771  *youngest_p = SVN_STR_TO_REV(buf->data);
1772
1773  return SVN_NO_ERROR;
1774}
1775
1776
1777svn_error_t *
1778svn_fs_fs__youngest_rev(svn_revnum_t *youngest_p,
1779                        svn_fs_t *fs,
1780                        apr_pool_t *pool)
1781{
1782  fs_fs_data_t *ffd = fs->fsap_data;
1783
1784  SVN_ERR(get_youngest(youngest_p, fs->path, pool));
1785  ffd->youngest_rev_cache = *youngest_p;
1786
1787  return SVN_NO_ERROR;
1788}
1789
1790/* Given a revision file FILE that has been pre-positioned at the
1791   beginning of a Node-Rev header block, read in that header block and
1792   store it in the apr_hash_t HEADERS.  All allocations will be from
1793   POOL. */
1794static svn_error_t * read_header_block(apr_hash_t **headers,
1795                                       svn_stream_t *stream,
1796                                       apr_pool_t *pool)
1797{
1798  *headers = apr_hash_make(pool);
1799
1800  while (1)
1801    {
1802      svn_stringbuf_t *header_str;
1803      const char *name, *value;
1804      apr_size_t i = 0;
1805      svn_boolean_t eof;
1806
1807      SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof, pool));
1808
1809      if (eof || header_str->len == 0)
1810        break; /* end of header block */
1811
1812      while (header_str->data[i] != ':')
1813        {
1814          if (header_str->data[i] == '\0')
1815            return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1816                                     _("Found malformed header '%s' in "
1817                                       "revision file"),
1818                                     header_str->data);
1819          i++;
1820        }
1821
1822      /* Create a 'name' string and point to it. */
1823      header_str->data[i] = '\0';
1824      name = header_str->data;
1825
1826      /* Skip over the NULL byte and the space following it. */
1827      i += 2;
1828
1829      if (i > header_str->len)
1830        {
1831          /* Restore the original line for the error. */
1832          i -= 2;
1833          header_str->data[i] = ':';
1834          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1835                                   _("Found malformed header '%s' in "
1836                                     "revision file"),
1837                                   header_str->data);
1838        }
1839
1840      value = header_str->data + i;
1841
1842      /* header_str is safely in our pool, so we can use bits of it as
1843         key and value. */
1844      svn_hash_sets(*headers, name, value);
1845    }
1846
1847  return SVN_NO_ERROR;
1848}
1849
1850/* Return SVN_ERR_FS_NO_SUCH_REVISION if the given revision is newer
1851   than the current youngest revision or is simply not a valid
1852   revision number, else return success.
1853
1854   FSFS is based around the concept that commits only take effect when
1855   the number in "current" is bumped.  Thus if there happens to be a rev
1856   or revprops file installed for a revision higher than the one recorded
1857   in "current" (because a commit failed between installing the rev file
1858   and bumping "current", or because an administrator rolled back the
1859   repository by resetting "current" without deleting rev files, etc), it
1860   ought to be completely ignored.  This function provides the check
1861   by which callers can make that decision. */
1862static svn_error_t *
1863ensure_revision_exists(svn_fs_t *fs,
1864                       svn_revnum_t rev,
1865                       apr_pool_t *pool)
1866{
1867  fs_fs_data_t *ffd = fs->fsap_data;
1868
1869  if (! SVN_IS_VALID_REVNUM(rev))
1870    return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1871                             _("Invalid revision number '%ld'"), rev);
1872
1873
1874  /* Did the revision exist the last time we checked the current
1875     file? */
1876  if (rev <= ffd->youngest_rev_cache)
1877    return SVN_NO_ERROR;
1878
1879  SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs->path, pool));
1880
1881  /* Check again. */
1882  if (rev <= ffd->youngest_rev_cache)
1883    return SVN_NO_ERROR;
1884
1885  return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1886                           _("No such revision %ld"), rev);
1887}
1888
1889svn_error_t *
1890svn_fs_fs__revision_exists(svn_revnum_t rev,
1891                           svn_fs_t *fs,
1892                           apr_pool_t *pool)
1893{
1894  /* Different order of parameters. */
1895  SVN_ERR(ensure_revision_exists(fs, rev, pool));
1896  return SVN_NO_ERROR;
1897}
1898
1899/* Open the correct revision file for REV.  If the filesystem FS has
1900   been packed, *FILE will be set to the packed file; otherwise, set *FILE
1901   to the revision file for REV.  Return SVN_ERR_FS_NO_SUCH_REVISION if the
1902   file doesn't exist.
1903
1904   TODO: Consider returning an indication of whether this is a packed rev
1905         file, so the caller need not rely on is_packed_rev() which in turn
1906         relies on the cached FFD->min_unpacked_rev value not having changed
1907         since the rev file was opened.
1908
1909   Use POOL for allocations. */
1910static svn_error_t *
1911open_pack_or_rev_file(apr_file_t **file,
1912                      svn_fs_t *fs,
1913                      svn_revnum_t rev,
1914                      apr_pool_t *pool)
1915{
1916  fs_fs_data_t *ffd = fs->fsap_data;
1917  svn_error_t *err;
1918  const char *path;
1919  svn_boolean_t retry = FALSE;
1920
1921  do
1922    {
1923      err = svn_fs_fs__path_rev_absolute(&path, fs, rev, pool);
1924
1925      /* open the revision file in buffered r/o mode */
1926      if (! err)
1927        err = svn_io_file_open(file, path,
1928                              APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
1929
1930      if (err && APR_STATUS_IS_ENOENT(err->apr_err))
1931        {
1932          if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1933            {
1934              /* Could not open the file. This may happen if the
1935               * file once existed but got packed later. */
1936              svn_error_clear(err);
1937
1938              /* if that was our 2nd attempt, leave it at that. */
1939              if (retry)
1940                return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1941                                         _("No such revision %ld"), rev);
1942
1943              /* We failed for the first time. Refresh cache & retry. */
1944              SVN_ERR(update_min_unpacked_rev(fs, pool));
1945
1946              retry = TRUE;
1947            }
1948          else
1949            {
1950              svn_error_clear(err);
1951              return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1952                                       _("No such revision %ld"), rev);
1953            }
1954        }
1955      else
1956        {
1957          retry = FALSE;
1958        }
1959    }
1960  while (retry);
1961
1962  return svn_error_trace(err);
1963}
1964
1965/* Reads a line from STREAM and converts it to a 64 bit integer to be
1966 * returned in *RESULT.  If we encounter eof, set *HIT_EOF and leave
1967 * *RESULT unchanged.  If HIT_EOF is NULL, EOF causes an "corrupt FS"
1968 * error return.
1969 * SCRATCH_POOL is used for temporary allocations.
1970 */
1971static svn_error_t *
1972read_number_from_stream(apr_int64_t *result,
1973                        svn_boolean_t *hit_eof,
1974                        svn_stream_t *stream,
1975                        apr_pool_t *scratch_pool)
1976{
1977  svn_stringbuf_t *sb;
1978  svn_boolean_t eof;
1979  svn_error_t *err;
1980
1981  SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, scratch_pool));
1982  if (hit_eof)
1983    *hit_eof = eof;
1984  else
1985    if (eof)
1986      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Unexpected EOF"));
1987
1988  if (!eof)
1989    {
1990      err = svn_cstring_atoi64(result, sb->data);
1991      if (err)
1992        return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
1993                                 _("Number '%s' invalid or too large"),
1994                                 sb->data);
1995    }
1996
1997  return SVN_NO_ERROR;
1998}
1999
2000/* Given REV in FS, set *REV_OFFSET to REV's offset in the packed file.
2001   Use POOL for temporary allocations. */
2002static svn_error_t *
2003get_packed_offset(apr_off_t *rev_offset,
2004                  svn_fs_t *fs,
2005                  svn_revnum_t rev,
2006                  apr_pool_t *pool)
2007{
2008  fs_fs_data_t *ffd = fs->fsap_data;
2009  svn_stream_t *manifest_stream;
2010  svn_boolean_t is_cached;
2011  svn_revnum_t shard;
2012  apr_int64_t shard_pos;
2013  apr_array_header_t *manifest;
2014  apr_pool_t *iterpool;
2015
2016  shard = rev / ffd->max_files_per_dir;
2017
2018  /* position of the shard within the manifest */
2019  shard_pos = rev % ffd->max_files_per_dir;
2020
2021  /* fetch exactly that element into *rev_offset, if the manifest is found
2022     in the cache */
2023  SVN_ERR(svn_cache__get_partial((void **) rev_offset, &is_cached,
2024                                 ffd->packed_offset_cache, &shard,
2025                                 svn_fs_fs__get_sharded_offset, &shard_pos,
2026                                 pool));
2027
2028  if (is_cached)
2029      return SVN_NO_ERROR;
2030
2031  /* Open the manifest file. */
2032  SVN_ERR(svn_stream_open_readonly(&manifest_stream,
2033                                   path_rev_packed(fs, rev, PATH_MANIFEST,
2034                                                   pool),
2035                                   pool, pool));
2036
2037  /* While we're here, let's just read the entire manifest file into an array,
2038     so we can cache the entire thing. */
2039  iterpool = svn_pool_create(pool);
2040  manifest = apr_array_make(pool, ffd->max_files_per_dir, sizeof(apr_off_t));
2041  while (1)
2042    {
2043      svn_boolean_t eof;
2044      apr_int64_t val;
2045
2046      svn_pool_clear(iterpool);
2047      SVN_ERR(read_number_from_stream(&val, &eof, manifest_stream, iterpool));
2048      if (eof)
2049        break;
2050
2051      APR_ARRAY_PUSH(manifest, apr_off_t) = (apr_off_t)val;
2052    }
2053  svn_pool_destroy(iterpool);
2054
2055  *rev_offset = APR_ARRAY_IDX(manifest, rev % ffd->max_files_per_dir,
2056                              apr_off_t);
2057
2058  /* Close up shop and cache the array. */
2059  SVN_ERR(svn_stream_close(manifest_stream));
2060  return svn_cache__set(ffd->packed_offset_cache, &shard, manifest, pool);
2061}
2062
2063/* Open the revision file for revision REV in filesystem FS and store
2064   the newly opened file in FILE.  Seek to location OFFSET before
2065   returning.  Perform temporary allocations in POOL. */
2066static svn_error_t *
2067open_and_seek_revision(apr_file_t **file,
2068                       svn_fs_t *fs,
2069                       svn_revnum_t rev,
2070                       apr_off_t offset,
2071                       apr_pool_t *pool)
2072{
2073  apr_file_t *rev_file;
2074
2075  SVN_ERR(ensure_revision_exists(fs, rev, pool));
2076
2077  SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, pool));
2078
2079  if (is_packed_rev(fs, rev))
2080    {
2081      apr_off_t rev_offset;
2082
2083      SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool));
2084      offset += rev_offset;
2085    }
2086
2087  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2088
2089  *file = rev_file;
2090
2091  return SVN_NO_ERROR;
2092}
2093
2094/* Open the representation for a node-revision in transaction TXN_ID
2095   in filesystem FS and store the newly opened file in FILE.  Seek to
2096   location OFFSET before returning.  Perform temporary allocations in
2097   POOL.  Only appropriate for file contents, nor props or directory
2098   contents. */
2099static svn_error_t *
2100open_and_seek_transaction(apr_file_t **file,
2101                          svn_fs_t *fs,
2102                          const char *txn_id,
2103                          representation_t *rep,
2104                          apr_pool_t *pool)
2105{
2106  apr_file_t *rev_file;
2107  apr_off_t offset;
2108
2109  SVN_ERR(svn_io_file_open(&rev_file, path_txn_proto_rev(fs, txn_id, pool),
2110                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
2111
2112  offset = rep->offset;
2113  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2114
2115  *file = rev_file;
2116
2117  return SVN_NO_ERROR;
2118}
2119
2120/* Given a node-id ID, and a representation REP in filesystem FS, open
2121   the correct file and seek to the correction location.  Store this
2122   file in *FILE_P.  Perform any allocations in POOL. */
2123static svn_error_t *
2124open_and_seek_representation(apr_file_t **file_p,
2125                             svn_fs_t *fs,
2126                             representation_t *rep,
2127                             apr_pool_t *pool)
2128{
2129  if (! rep->txn_id)
2130    return open_and_seek_revision(file_p, fs, rep->revision, rep->offset,
2131                                  pool);
2132  else
2133    return open_and_seek_transaction(file_p, fs, rep->txn_id, rep, pool);
2134}
2135
2136/* Parse the description of a representation from STRING and store it
2137   into *REP_P.  If the representation is mutable (the revision is
2138   given as -1), then use TXN_ID for the representation's txn_id
2139   field.  If MUTABLE_REP_TRUNCATED is true, then this representation
2140   is for property or directory contents, and no information will be
2141   expected except the "-1" revision number for a mutable
2142   representation.  Allocate *REP_P in POOL. */
2143static svn_error_t *
2144read_rep_offsets_body(representation_t **rep_p,
2145                      char *string,
2146                      const char *txn_id,
2147                      svn_boolean_t mutable_rep_truncated,
2148                      apr_pool_t *pool)
2149{
2150  representation_t *rep;
2151  char *str;
2152  apr_int64_t val;
2153
2154  rep = apr_pcalloc(pool, sizeof(*rep));
2155  *rep_p = rep;
2156
2157  str = svn_cstring_tokenize(" ", &string);
2158  if (str == NULL)
2159    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2160                            _("Malformed text representation offset line in node-rev"));
2161
2162
2163  rep->revision = SVN_STR_TO_REV(str);
2164  if (rep->revision == SVN_INVALID_REVNUM)
2165    {
2166      rep->txn_id = txn_id;
2167      if (mutable_rep_truncated)
2168        return SVN_NO_ERROR;
2169    }
2170
2171  str = svn_cstring_tokenize(" ", &string);
2172  if (str == NULL)
2173    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2174                            _("Malformed text representation offset line in node-rev"));
2175
2176  SVN_ERR(svn_cstring_atoi64(&val, str));
2177  rep->offset = (apr_off_t)val;
2178
2179  str = svn_cstring_tokenize(" ", &string);
2180  if (str == NULL)
2181    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2182                            _("Malformed text representation offset line in node-rev"));
2183
2184  SVN_ERR(svn_cstring_atoi64(&val, str));
2185  rep->size = (svn_filesize_t)val;
2186
2187  str = svn_cstring_tokenize(" ", &string);
2188  if (str == NULL)
2189    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2190                            _("Malformed text representation offset line in node-rev"));
2191
2192  SVN_ERR(svn_cstring_atoi64(&val, str));
2193  rep->expanded_size = (svn_filesize_t)val;
2194
2195  /* Read in the MD5 hash. */
2196  str = svn_cstring_tokenize(" ", &string);
2197  if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
2198    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2199                            _("Malformed text representation offset line in node-rev"));
2200
2201  SVN_ERR(svn_checksum_parse_hex(&rep->md5_checksum, svn_checksum_md5, str,
2202                                 pool));
2203
2204  /* The remaining fields are only used for formats >= 4, so check that. */
2205  str = svn_cstring_tokenize(" ", &string);
2206  if (str == NULL)
2207    return SVN_NO_ERROR;
2208
2209  /* Read the SHA1 hash. */
2210  if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
2211    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2212                            _("Malformed text representation offset line in node-rev"));
2213
2214  SVN_ERR(svn_checksum_parse_hex(&rep->sha1_checksum, svn_checksum_sha1, str,
2215                                 pool));
2216
2217  /* Read the uniquifier. */
2218  str = svn_cstring_tokenize(" ", &string);
2219  if (str == NULL)
2220    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2221                            _("Malformed text representation offset line in node-rev"));
2222
2223  rep->uniquifier = apr_pstrdup(pool, str);
2224
2225  return SVN_NO_ERROR;
2226}
2227
2228/* Wrap read_rep_offsets_body(), extracting its TXN_ID from our NODEREV_ID,
2229   and adding an error message. */
2230static svn_error_t *
2231read_rep_offsets(representation_t **rep_p,
2232                 char *string,
2233                 const svn_fs_id_t *noderev_id,
2234                 svn_boolean_t mutable_rep_truncated,
2235                 apr_pool_t *pool)
2236{
2237  svn_error_t *err;
2238  const char *txn_id;
2239
2240  if (noderev_id)
2241    txn_id = svn_fs_fs__id_txn_id(noderev_id);
2242  else
2243    txn_id = NULL;
2244
2245  err = read_rep_offsets_body(rep_p, string, txn_id, mutable_rep_truncated,
2246                              pool);
2247  if (err)
2248    {
2249      const svn_string_t *id_unparsed = svn_fs_fs__id_unparse(noderev_id, pool);
2250      const char *where;
2251      where = apr_psprintf(pool,
2252                           _("While reading representation offsets "
2253                             "for node-revision '%s':"),
2254                           noderev_id ? id_unparsed->data : "(null)");
2255
2256      return svn_error_quick_wrap(err, where);
2257    }
2258  else
2259    return SVN_NO_ERROR;
2260}
2261
2262static svn_error_t *
2263err_dangling_id(svn_fs_t *fs, const svn_fs_id_t *id)
2264{
2265  svn_string_t *id_str = svn_fs_fs__id_unparse(id, fs->pool);
2266  return svn_error_createf
2267    (SVN_ERR_FS_ID_NOT_FOUND, 0,
2268     _("Reference to non-existent node '%s' in filesystem '%s'"),
2269     id_str->data, fs->path);
2270}
2271
2272/* Look up the NODEREV_P for ID in FS' node revsion cache. If noderev
2273 * caching has been enabled and the data can be found, IS_CACHED will
2274 * be set to TRUE. The noderev will be allocated from POOL.
2275 *
2276 * Non-permanent ids (e.g. ids within a TXN) will not be cached.
2277 */
2278static svn_error_t *
2279get_cached_node_revision_body(node_revision_t **noderev_p,
2280                              svn_fs_t *fs,
2281                              const svn_fs_id_t *id,
2282                              svn_boolean_t *is_cached,
2283                              apr_pool_t *pool)
2284{
2285  fs_fs_data_t *ffd = fs->fsap_data;
2286  if (! ffd->node_revision_cache || svn_fs_fs__id_txn_id(id))
2287    {
2288      *is_cached = FALSE;
2289    }
2290  else
2291    {
2292      pair_cache_key_t key = { 0 };
2293
2294      key.revision = svn_fs_fs__id_rev(id);
2295      key.second = svn_fs_fs__id_offset(id);
2296      SVN_ERR(svn_cache__get((void **) noderev_p,
2297                            is_cached,
2298                            ffd->node_revision_cache,
2299                            &key,
2300                            pool));
2301    }
2302
2303  return SVN_NO_ERROR;
2304}
2305
2306/* If noderev caching has been enabled, store the NODEREV_P for the given ID
2307 * in FS' node revsion cache. SCRATCH_POOL is used for temporary allcations.
2308 *
2309 * Non-permanent ids (e.g. ids within a TXN) will not be cached.
2310 */
2311static svn_error_t *
2312set_cached_node_revision_body(node_revision_t *noderev_p,
2313                              svn_fs_t *fs,
2314                              const svn_fs_id_t *id,
2315                              apr_pool_t *scratch_pool)
2316{
2317  fs_fs_data_t *ffd = fs->fsap_data;
2318
2319  if (ffd->node_revision_cache && !svn_fs_fs__id_txn_id(id))
2320    {
2321      pair_cache_key_t key = { 0 };
2322
2323      key.revision = svn_fs_fs__id_rev(id);
2324      key.second = svn_fs_fs__id_offset(id);
2325      return svn_cache__set(ffd->node_revision_cache,
2326                            &key,
2327                            noderev_p,
2328                            scratch_pool);
2329    }
2330
2331  return SVN_NO_ERROR;
2332}
2333
2334/* Get the node-revision for the node ID in FS.
2335   Set *NODEREV_P to the new node-revision structure, allocated in POOL.
2336   See svn_fs_fs__get_node_revision, which wraps this and adds another
2337   error. */
2338static svn_error_t *
2339get_node_revision_body(node_revision_t **noderev_p,
2340                       svn_fs_t *fs,
2341                       const svn_fs_id_t *id,
2342                       apr_pool_t *pool)
2343{
2344  apr_file_t *revision_file;
2345  svn_error_t *err;
2346  svn_boolean_t is_cached = FALSE;
2347
2348  /* First, try a cache lookup. If that succeeds, we are done here. */
2349  SVN_ERR(get_cached_node_revision_body(noderev_p, fs, id, &is_cached, pool));
2350  if (is_cached)
2351    return SVN_NO_ERROR;
2352
2353  if (svn_fs_fs__id_txn_id(id))
2354    {
2355      /* This is a transaction node-rev. */
2356      err = svn_io_file_open(&revision_file, path_txn_node_rev(fs, id, pool),
2357                             APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
2358    }
2359  else
2360    {
2361      /* This is a revision node-rev. */
2362      err = open_and_seek_revision(&revision_file, fs,
2363                                   svn_fs_fs__id_rev(id),
2364                                   svn_fs_fs__id_offset(id),
2365                                   pool);
2366    }
2367
2368  if (err)
2369    {
2370      if (APR_STATUS_IS_ENOENT(err->apr_err))
2371        {
2372          svn_error_clear(err);
2373          return svn_error_trace(err_dangling_id(fs, id));
2374        }
2375
2376      return svn_error_trace(err);
2377    }
2378
2379  SVN_ERR(svn_fs_fs__read_noderev(noderev_p,
2380                                  svn_stream_from_aprfile2(revision_file, FALSE,
2381                                                           pool),
2382                                  pool));
2383
2384  /* The noderev is not in cache, yet. Add it, if caching has been enabled. */
2385  return set_cached_node_revision_body(*noderev_p, fs, id, pool);
2386}
2387
2388svn_error_t *
2389svn_fs_fs__read_noderev(node_revision_t **noderev_p,
2390                        svn_stream_t *stream,
2391                        apr_pool_t *pool)
2392{
2393  apr_hash_t *headers;
2394  node_revision_t *noderev;
2395  char *value;
2396  const char *noderev_id;
2397
2398  SVN_ERR(read_header_block(&headers, stream, pool));
2399
2400  noderev = apr_pcalloc(pool, sizeof(*noderev));
2401
2402  /* Read the node-rev id. */
2403  value = svn_hash_gets(headers, HEADER_ID);
2404  if (value == NULL)
2405      /* ### More information: filename/offset coordinates */
2406      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2407                              _("Missing id field in node-rev"));
2408
2409  SVN_ERR(svn_stream_close(stream));
2410
2411  noderev->id = svn_fs_fs__id_parse(value, strlen(value), pool);
2412  noderev_id = value; /* for error messages later */
2413
2414  /* Read the type. */
2415  value = svn_hash_gets(headers, HEADER_TYPE);
2416
2417  if ((value == NULL) ||
2418      (strcmp(value, KIND_FILE) != 0 && strcmp(value, KIND_DIR)))
2419    /* ### s/kind/type/ */
2420    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2421                             _("Missing kind field in node-rev '%s'"),
2422                             noderev_id);
2423
2424  noderev->kind = (strcmp(value, KIND_FILE) == 0) ? svn_node_file
2425    : svn_node_dir;
2426
2427  /* Read the 'count' field. */
2428  value = svn_hash_gets(headers, HEADER_COUNT);
2429  if (value)
2430    SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
2431  else
2432    noderev->predecessor_count = 0;
2433
2434  /* Get the properties location. */
2435  value = svn_hash_gets(headers, HEADER_PROPS);
2436  if (value)
2437    {
2438      SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
2439                               noderev->id, TRUE, pool));
2440    }
2441
2442  /* Get the data location. */
2443  value = svn_hash_gets(headers, HEADER_TEXT);
2444  if (value)
2445    {
2446      SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
2447                               noderev->id,
2448                               (noderev->kind == svn_node_dir), pool));
2449    }
2450
2451  /* Get the created path. */
2452  value = svn_hash_gets(headers, HEADER_CPATH);
2453  if (value == NULL)
2454    {
2455      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2456                               _("Missing cpath field in node-rev '%s'"),
2457                               noderev_id);
2458    }
2459  else
2460    {
2461      noderev->created_path = apr_pstrdup(pool, value);
2462    }
2463
2464  /* Get the predecessor ID. */
2465  value = svn_hash_gets(headers, HEADER_PRED);
2466  if (value)
2467    noderev->predecessor_id = svn_fs_fs__id_parse(value, strlen(value),
2468                                                  pool);
2469
2470  /* Get the copyroot. */
2471  value = svn_hash_gets(headers, HEADER_COPYROOT);
2472  if (value == NULL)
2473    {
2474      noderev->copyroot_path = apr_pstrdup(pool, noderev->created_path);
2475      noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id);
2476    }
2477  else
2478    {
2479      char *str;
2480
2481      str = svn_cstring_tokenize(" ", &value);
2482      if (str == NULL)
2483        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2484                                 _("Malformed copyroot line in node-rev '%s'"),
2485                                 noderev_id);
2486
2487      noderev->copyroot_rev = SVN_STR_TO_REV(str);
2488
2489      if (*value == '\0')
2490        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2491                                 _("Malformed copyroot line in node-rev '%s'"),
2492                                 noderev_id);
2493      noderev->copyroot_path = apr_pstrdup(pool, value);
2494    }
2495
2496  /* Get the copyfrom. */
2497  value = svn_hash_gets(headers, HEADER_COPYFROM);
2498  if (value == NULL)
2499    {
2500      noderev->copyfrom_path = NULL;
2501      noderev->copyfrom_rev = SVN_INVALID_REVNUM;
2502    }
2503  else
2504    {
2505      char *str = svn_cstring_tokenize(" ", &value);
2506      if (str == NULL)
2507        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2508                                 _("Malformed copyfrom line in node-rev '%s'"),
2509                                 noderev_id);
2510
2511      noderev->copyfrom_rev = SVN_STR_TO_REV(str);
2512
2513      if (*value == 0)
2514        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2515                                 _("Malformed copyfrom line in node-rev '%s'"),
2516                                 noderev_id);
2517      noderev->copyfrom_path = apr_pstrdup(pool, value);
2518    }
2519
2520  /* Get whether this is a fresh txn root. */
2521  value = svn_hash_gets(headers, HEADER_FRESHTXNRT);
2522  noderev->is_fresh_txn_root = (value != NULL);
2523
2524  /* Get the mergeinfo count. */
2525  value = svn_hash_gets(headers, HEADER_MINFO_CNT);
2526  if (value)
2527    SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value));
2528  else
2529    noderev->mergeinfo_count = 0;
2530
2531  /* Get whether *this* node has mergeinfo. */
2532  value = svn_hash_gets(headers, HEADER_MINFO_HERE);
2533  noderev->has_mergeinfo = (value != NULL);
2534
2535  *noderev_p = noderev;
2536
2537  return SVN_NO_ERROR;
2538}
2539
2540svn_error_t *
2541svn_fs_fs__get_node_revision(node_revision_t **noderev_p,
2542                             svn_fs_t *fs,
2543                             const svn_fs_id_t *id,
2544                             apr_pool_t *pool)
2545{
2546  svn_error_t *err = get_node_revision_body(noderev_p, fs, id, pool);
2547  if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
2548    {
2549      svn_string_t *id_string = svn_fs_fs__id_unparse(id, pool);
2550      return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
2551                               "Corrupt node-revision '%s'",
2552                               id_string->data);
2553    }
2554  return svn_error_trace(err);
2555}
2556
2557
2558/* Return a formatted string, compatible with filesystem format FORMAT,
2559   that represents the location of representation REP.  If
2560   MUTABLE_REP_TRUNCATED is given, the rep is for props or dir contents,
2561   and only a "-1" revision number will be given for a mutable rep.
2562   If MAY_BE_CORRUPT is true, guard for NULL when constructing the string.
2563   Perform the allocation from POOL.  */
2564static const char *
2565representation_string(representation_t *rep,
2566                      int format,
2567                      svn_boolean_t mutable_rep_truncated,
2568                      svn_boolean_t may_be_corrupt,
2569                      apr_pool_t *pool)
2570{
2571  if (rep->txn_id && mutable_rep_truncated)
2572    return "-1";
2573
2574#define DISPLAY_MAYBE_NULL_CHECKSUM(checksum)          \
2575  ((!may_be_corrupt || (checksum) != NULL)     \
2576   ? svn_checksum_to_cstring_display((checksum), pool) \
2577   : "(null)")
2578
2579  if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT || rep->sha1_checksum == NULL)
2580    return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
2581                        " %" SVN_FILESIZE_T_FMT " %s",
2582                        rep->revision, rep->offset, rep->size,
2583                        rep->expanded_size,
2584                        DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum));
2585
2586  return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
2587                      " %" SVN_FILESIZE_T_FMT " %s %s %s",
2588                      rep->revision, rep->offset, rep->size,
2589                      rep->expanded_size,
2590                      DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum),
2591                      DISPLAY_MAYBE_NULL_CHECKSUM(rep->sha1_checksum),
2592                      rep->uniquifier);
2593
2594#undef DISPLAY_MAYBE_NULL_CHECKSUM
2595
2596}
2597
2598
2599svn_error_t *
2600svn_fs_fs__write_noderev(svn_stream_t *outfile,
2601                         node_revision_t *noderev,
2602                         int format,
2603                         svn_boolean_t include_mergeinfo,
2604                         apr_pool_t *pool)
2605{
2606  SVN_ERR(svn_stream_printf(outfile, pool, HEADER_ID ": %s\n",
2607                            svn_fs_fs__id_unparse(noderev->id,
2608                                                  pool)->data));
2609
2610  SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TYPE ": %s\n",
2611                            (noderev->kind == svn_node_file) ?
2612                            KIND_FILE : KIND_DIR));
2613
2614  if (noderev->predecessor_id)
2615    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PRED ": %s\n",
2616                              svn_fs_fs__id_unparse(noderev->predecessor_id,
2617                                                    pool)->data));
2618
2619  SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COUNT ": %d\n",
2620                            noderev->predecessor_count));
2621
2622  if (noderev->data_rep)
2623    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TEXT ": %s\n",
2624                              representation_string(noderev->data_rep,
2625                                                    format,
2626                                                    (noderev->kind
2627                                                     == svn_node_dir),
2628                                                    FALSE,
2629                                                    pool)));
2630
2631  if (noderev->prop_rep)
2632    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PROPS ": %s\n",
2633                              representation_string(noderev->prop_rep, format,
2634                                                    TRUE, FALSE, pool)));
2635
2636  SVN_ERR(svn_stream_printf(outfile, pool, HEADER_CPATH ": %s\n",
2637                            noderev->created_path));
2638
2639  if (noderev->copyfrom_path)
2640    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYFROM ": %ld"
2641                              " %s\n",
2642                              noderev->copyfrom_rev,
2643                              noderev->copyfrom_path));
2644
2645  if ((noderev->copyroot_rev != svn_fs_fs__id_rev(noderev->id)) ||
2646      (strcmp(noderev->copyroot_path, noderev->created_path) != 0))
2647    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYROOT ": %ld"
2648                              " %s\n",
2649                              noderev->copyroot_rev,
2650                              noderev->copyroot_path));
2651
2652  if (noderev->is_fresh_txn_root)
2653    SVN_ERR(svn_stream_puts(outfile, HEADER_FRESHTXNRT ": y\n"));
2654
2655  if (include_mergeinfo)
2656    {
2657      if (noderev->mergeinfo_count > 0)
2658        SVN_ERR(svn_stream_printf(outfile, pool, HEADER_MINFO_CNT ": %"
2659                                  APR_INT64_T_FMT "\n",
2660                                  noderev->mergeinfo_count));
2661
2662      if (noderev->has_mergeinfo)
2663        SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n"));
2664    }
2665
2666  return svn_stream_puts(outfile, "\n");
2667}
2668
2669svn_error_t *
2670svn_fs_fs__put_node_revision(svn_fs_t *fs,
2671                             const svn_fs_id_t *id,
2672                             node_revision_t *noderev,
2673                             svn_boolean_t fresh_txn_root,
2674                             apr_pool_t *pool)
2675{
2676  fs_fs_data_t *ffd = fs->fsap_data;
2677  apr_file_t *noderev_file;
2678  const char *txn_id = svn_fs_fs__id_txn_id(id);
2679
2680  noderev->is_fresh_txn_root = fresh_txn_root;
2681
2682  if (! txn_id)
2683    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2684                             _("Attempted to write to non-transaction '%s'"),
2685                             svn_fs_fs__id_unparse(id, pool)->data);
2686
2687  SVN_ERR(svn_io_file_open(&noderev_file, path_txn_node_rev(fs, id, pool),
2688                           APR_WRITE | APR_CREATE | APR_TRUNCATE
2689                           | APR_BUFFERED, APR_OS_DEFAULT, pool));
2690
2691  SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE,
2692                                                            pool),
2693                                   noderev, ffd->format,
2694                                   svn_fs_fs__fs_supports_mergeinfo(fs),
2695                                   pool));
2696
2697  SVN_ERR(svn_io_file_close(noderev_file, pool));
2698
2699  return SVN_NO_ERROR;
2700}
2701
2702/* For the in-transaction NODEREV within FS, write the sha1->rep mapping
2703 * file in the respective transaction, if rep sharing has been enabled etc.
2704 * Use POOL for temporary allocations.
2705 */
2706static svn_error_t *
2707store_sha1_rep_mapping(svn_fs_t *fs,
2708                       node_revision_t *noderev,
2709                       apr_pool_t *pool)
2710{
2711  fs_fs_data_t *ffd = fs->fsap_data;
2712
2713  /* if rep sharing has been enabled and the noderev has a data rep and
2714   * its SHA-1 is known, store the rep struct under its SHA1. */
2715  if (   ffd->rep_sharing_allowed
2716      && noderev->data_rep
2717      && noderev->data_rep->sha1_checksum)
2718    {
2719      apr_file_t *rep_file;
2720      const char *file_name = path_txn_sha1(fs,
2721                                            svn_fs_fs__id_txn_id(noderev->id),
2722                                            noderev->data_rep->sha1_checksum,
2723                                            pool);
2724      const char *rep_string = representation_string(noderev->data_rep,
2725                                                     ffd->format,
2726                                                     (noderev->kind
2727                                                      == svn_node_dir),
2728                                                     FALSE,
2729                                                     pool);
2730      SVN_ERR(svn_io_file_open(&rep_file, file_name,
2731                               APR_WRITE | APR_CREATE | APR_TRUNCATE
2732                               | APR_BUFFERED, APR_OS_DEFAULT, pool));
2733
2734      SVN_ERR(svn_io_file_write_full(rep_file, rep_string,
2735                                     strlen(rep_string), NULL, pool));
2736
2737      SVN_ERR(svn_io_file_close(rep_file, pool));
2738    }
2739
2740  return SVN_NO_ERROR;
2741}
2742
2743
2744/* This structure is used to hold the information associated with a
2745   REP line. */
2746struct rep_args
2747{
2748  svn_boolean_t is_delta;
2749  svn_boolean_t is_delta_vs_empty;
2750
2751  svn_revnum_t base_revision;
2752  apr_off_t base_offset;
2753  svn_filesize_t base_length;
2754};
2755
2756/* Read the next line from file FILE and parse it as a text
2757   representation entry.  Return the parsed entry in *REP_ARGS_P.
2758   Perform all allocations in POOL. */
2759static svn_error_t *
2760read_rep_line(struct rep_args **rep_args_p,
2761              apr_file_t *file,
2762              apr_pool_t *pool)
2763{
2764  char buffer[160];
2765  apr_size_t limit;
2766  struct rep_args *rep_args;
2767  char *str, *last_str = buffer;
2768  apr_int64_t val;
2769
2770  limit = sizeof(buffer);
2771  SVN_ERR(svn_io_read_length_line(file, buffer, &limit, pool));
2772
2773  rep_args = apr_pcalloc(pool, sizeof(*rep_args));
2774  rep_args->is_delta = FALSE;
2775
2776  if (strcmp(buffer, REP_PLAIN) == 0)
2777    {
2778      *rep_args_p = rep_args;
2779      return SVN_NO_ERROR;
2780    }
2781
2782  if (strcmp(buffer, REP_DELTA) == 0)
2783    {
2784      /* This is a delta against the empty stream. */
2785      rep_args->is_delta = TRUE;
2786      rep_args->is_delta_vs_empty = TRUE;
2787      *rep_args_p = rep_args;
2788      return SVN_NO_ERROR;
2789    }
2790
2791  rep_args->is_delta = TRUE;
2792  rep_args->is_delta_vs_empty = FALSE;
2793
2794  /* We have hopefully a DELTA vs. a non-empty base revision. */
2795  str = svn_cstring_tokenize(" ", &last_str);
2796  if (! str || (strcmp(str, REP_DELTA) != 0))
2797    goto error;
2798
2799  str = svn_cstring_tokenize(" ", &last_str);
2800  if (! str)
2801    goto error;
2802  rep_args->base_revision = SVN_STR_TO_REV(str);
2803
2804  str = svn_cstring_tokenize(" ", &last_str);
2805  if (! str)
2806    goto error;
2807  SVN_ERR(svn_cstring_atoi64(&val, str));
2808  rep_args->base_offset = (apr_off_t)val;
2809
2810  str = svn_cstring_tokenize(" ", &last_str);
2811  if (! str)
2812    goto error;
2813  SVN_ERR(svn_cstring_atoi64(&val, str));
2814  rep_args->base_length = (svn_filesize_t)val;
2815
2816  *rep_args_p = rep_args;
2817  return SVN_NO_ERROR;
2818
2819 error:
2820  return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2821                           _("Malformed representation header at %s"),
2822                           path_and_offset_of(file, pool));
2823}
2824
2825/* Given a revision file REV_FILE, opened to REV in FS, find the Node-ID
2826   of the header located at OFFSET and store it in *ID_P.  Allocate
2827   temporary variables from POOL. */
2828static svn_error_t *
2829get_fs_id_at_offset(svn_fs_id_t **id_p,
2830                    apr_file_t *rev_file,
2831                    svn_fs_t *fs,
2832                    svn_revnum_t rev,
2833                    apr_off_t offset,
2834                    apr_pool_t *pool)
2835{
2836  svn_fs_id_t *id;
2837  apr_hash_t *headers;
2838  const char *node_id_str;
2839
2840  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2841
2842  SVN_ERR(read_header_block(&headers,
2843                            svn_stream_from_aprfile2(rev_file, TRUE, pool),
2844                            pool));
2845
2846  /* In error messages, the offset is relative to the pack file,
2847     not to the rev file. */
2848
2849  node_id_str = svn_hash_gets(headers, HEADER_ID);
2850
2851  if (node_id_str == NULL)
2852    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2853                             _("Missing node-id in node-rev at r%ld "
2854                             "(offset %s)"),
2855                             rev,
2856                             apr_psprintf(pool, "%" APR_OFF_T_FMT, offset));
2857
2858  id = svn_fs_fs__id_parse(node_id_str, strlen(node_id_str), pool);
2859
2860  if (id == NULL)
2861    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2862                             _("Corrupt node-id '%s' in node-rev at r%ld "
2863                               "(offset %s)"),
2864                             node_id_str, rev,
2865                             apr_psprintf(pool, "%" APR_OFF_T_FMT, offset));
2866
2867  *id_p = id;
2868
2869  /* ### assert that the txn_id is REV/OFFSET ? */
2870
2871  return SVN_NO_ERROR;
2872}
2873
2874
2875/* Given an open revision file REV_FILE in FS for REV, locate the trailer that
2876   specifies the offset to the root node-id and to the changed path
2877   information.  Store the root node offset in *ROOT_OFFSET and the
2878   changed path offset in *CHANGES_OFFSET.  If either of these
2879   pointers is NULL, do nothing with it.
2880
2881   If PACKED is true, REV_FILE should be a packed shard file.
2882   ### There is currently no such parameter.  This function assumes that
2883       is_packed_rev(FS, REV) will indicate whether REV_FILE is a packed
2884       file.  Therefore FS->fsap_data->min_unpacked_rev must not have been
2885       refreshed since REV_FILE was opened if there is a possibility that
2886       revision REV may have become packed since then.
2887       TODO: Take an IS_PACKED parameter instead, in order to remove this
2888       requirement.
2889
2890   Allocate temporary variables from POOL. */
2891static svn_error_t *
2892get_root_changes_offset(apr_off_t *root_offset,
2893                        apr_off_t *changes_offset,
2894                        apr_file_t *rev_file,
2895                        svn_fs_t *fs,
2896                        svn_revnum_t rev,
2897                        apr_pool_t *pool)
2898{
2899  fs_fs_data_t *ffd = fs->fsap_data;
2900  apr_off_t offset;
2901  apr_off_t rev_offset;
2902  char buf[64];
2903  int i, num_bytes;
2904  const char *str;
2905  apr_size_t len;
2906  apr_seek_where_t seek_relative;
2907
2908  /* Determine where to seek to in the file.
2909
2910     If we've got a pack file, we want to seek to the end of the desired
2911     revision.  But we don't track that, so we seek to the beginning of the
2912     next revision.
2913
2914     Unless the next revision is in a different file, in which case, we can
2915     just seek to the end of the pack file -- just like we do in the
2916     non-packed case. */
2917  if (is_packed_rev(fs, rev) && ((rev + 1) % ffd->max_files_per_dir != 0))
2918    {
2919      SVN_ERR(get_packed_offset(&offset, fs, rev + 1, pool));
2920      seek_relative = APR_SET;
2921    }
2922  else
2923    {
2924      seek_relative = APR_END;
2925      offset = 0;
2926    }
2927
2928  /* Offset of the revision from the start of the pack file, if applicable. */
2929  if (is_packed_rev(fs, rev))
2930    SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool));
2931  else
2932    rev_offset = 0;
2933
2934  /* We will assume that the last line containing the two offsets
2935     will never be longer than 64 characters. */
2936  SVN_ERR(svn_io_file_seek(rev_file, seek_relative, &offset, pool));
2937
2938  offset -= sizeof(buf);
2939  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2940
2941  /* Read in this last block, from which we will identify the last line. */
2942  len = sizeof(buf);
2943  SVN_ERR(svn_io_file_read(rev_file, buf, &len, pool));
2944
2945  /* This cast should be safe since the maximum amount read, 64, will
2946     never be bigger than the size of an int. */
2947  num_bytes = (int) len;
2948
2949  /* The last byte should be a newline. */
2950  if (buf[num_bytes - 1] != '\n')
2951    {
2952      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2953                               _("Revision file (r%ld) lacks trailing newline"),
2954                               rev);
2955    }
2956
2957  /* Look for the next previous newline. */
2958  for (i = num_bytes - 2; i >= 0; i--)
2959    {
2960      if (buf[i] == '\n')
2961        break;
2962    }
2963
2964  if (i < 0)
2965    {
2966      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2967                               _("Final line in revision file (r%ld) longer "
2968                                 "than 64 characters"),
2969                               rev);
2970    }
2971
2972  i++;
2973  str = &buf[i];
2974
2975  /* find the next space */
2976  for ( ; i < (num_bytes - 2) ; i++)
2977    if (buf[i] == ' ')
2978      break;
2979
2980  if (i == (num_bytes - 2))
2981    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2982                             _("Final line in revision file r%ld missing space"),
2983                             rev);
2984
2985  if (root_offset)
2986    {
2987      apr_int64_t val;
2988
2989      buf[i] = '\0';
2990      SVN_ERR(svn_cstring_atoi64(&val, str));
2991      *root_offset = rev_offset + (apr_off_t)val;
2992    }
2993
2994  i++;
2995  str = &buf[i];
2996
2997  /* find the next newline */
2998  for ( ; i < num_bytes; i++)
2999    if (buf[i] == '\n')
3000      break;
3001
3002  if (changes_offset)
3003    {
3004      apr_int64_t val;
3005
3006      buf[i] = '\0';
3007      SVN_ERR(svn_cstring_atoi64(&val, str));
3008      *changes_offset = rev_offset + (apr_off_t)val;
3009    }
3010
3011  return SVN_NO_ERROR;
3012}
3013
3014/* Move a file into place from OLD_FILENAME in the transactions
3015   directory to its final location NEW_FILENAME in the repository.  On
3016   Unix, match the permissions of the new file to the permissions of
3017   PERMS_REFERENCE.  Temporary allocations are from POOL.
3018
3019   This function almost duplicates svn_io_file_move(), but it tries to
3020   guarantee a flush. */
3021static svn_error_t *
3022move_into_place(const char *old_filename,
3023                const char *new_filename,
3024                const char *perms_reference,
3025                apr_pool_t *pool)
3026{
3027  svn_error_t *err;
3028
3029  SVN_ERR(svn_io_copy_perms(perms_reference, old_filename, pool));
3030
3031  /* Move the file into place. */
3032  err = svn_io_file_rename(old_filename, new_filename, pool);
3033  if (err && APR_STATUS_IS_EXDEV(err->apr_err))
3034    {
3035      apr_file_t *file;
3036
3037      /* Can't rename across devices; fall back to copying. */
3038      svn_error_clear(err);
3039      err = SVN_NO_ERROR;
3040      SVN_ERR(svn_io_copy_file(old_filename, new_filename, TRUE, pool));
3041
3042      /* Flush the target of the copy to disk. */
3043      SVN_ERR(svn_io_file_open(&file, new_filename, APR_READ,
3044                               APR_OS_DEFAULT, pool));
3045      /* ### BH: Does this really guarantee a flush of the data written
3046         ### via a completely different handle on all operating systems?
3047         ###
3048         ### Maybe we should perform the copy ourselves instead of making
3049         ### apr do that and flush the real handle? */
3050      SVN_ERR(svn_io_file_flush_to_disk(file, pool));
3051      SVN_ERR(svn_io_file_close(file, pool));
3052    }
3053  if (err)
3054    return svn_error_trace(err);
3055
3056#ifdef __linux__
3057  {
3058    /* Linux has the unusual feature that fsync() on a file is not
3059       enough to ensure that a file's directory entries have been
3060       flushed to disk; you have to fsync the directory as well.
3061       On other operating systems, we'd only be asking for trouble
3062       by trying to open and fsync a directory. */
3063    const char *dirname;
3064    apr_file_t *file;
3065
3066    dirname = svn_dirent_dirname(new_filename, pool);
3067    SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT,
3068                             pool));
3069    SVN_ERR(svn_io_file_flush_to_disk(file, pool));
3070    SVN_ERR(svn_io_file_close(file, pool));
3071  }
3072#endif
3073
3074  return SVN_NO_ERROR;
3075}
3076
3077svn_error_t *
3078svn_fs_fs__rev_get_root(svn_fs_id_t **root_id_p,
3079                        svn_fs_t *fs,
3080                        svn_revnum_t rev,
3081                        apr_pool_t *pool)
3082{
3083  fs_fs_data_t *ffd = fs->fsap_data;
3084  apr_file_t *revision_file;
3085  apr_off_t root_offset;
3086  svn_fs_id_t *root_id = NULL;
3087  svn_boolean_t is_cached;
3088
3089  SVN_ERR(ensure_revision_exists(fs, rev, pool));
3090
3091  SVN_ERR(svn_cache__get((void **) root_id_p, &is_cached,
3092                         ffd->rev_root_id_cache, &rev, pool));
3093  if (is_cached)
3094    return SVN_NO_ERROR;
3095
3096  SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool));
3097  SVN_ERR(get_root_changes_offset(&root_offset, NULL, revision_file, fs, rev,
3098                                  pool));
3099
3100  SVN_ERR(get_fs_id_at_offset(&root_id, revision_file, fs, rev,
3101                              root_offset, pool));
3102
3103  SVN_ERR(svn_io_file_close(revision_file, pool));
3104
3105  SVN_ERR(svn_cache__set(ffd->rev_root_id_cache, &rev, root_id, pool));
3106
3107  *root_id_p = root_id;
3108
3109  return SVN_NO_ERROR;
3110}
3111
3112/* Revprop caching management.
3113 *
3114 * Mechanism:
3115 * ----------
3116 *
3117 * Revprop caching needs to be activated and will be deactivated for the
3118 * respective FS instance if the necessary infrastructure could not be
3119 * initialized.  In deactivated mode, there is almost no runtime overhead
3120 * associated with revprop caching.  As long as no revprops are being read
3121 * or changed, revprop caching imposes no overhead.
3122 *
3123 * When activated, we cache revprops using (revision, generation) pairs
3124 * as keys with the generation being incremented upon every revprop change.
3125 * Since the cache is process-local, the generation needs to be tracked
3126 * for at least as long as the process lives but may be reset afterwards.
3127 *
3128 * To track the revprop generation, we use two-layer approach. On the lower
3129 * level, we use named atomics to have a system-wide consistent value for
3130 * the current revprop generation.  However, those named atomics will only
3131 * remain valid for as long as at least one process / thread in the system
3132 * accesses revprops in the respective repository.  The underlying shared
3133 * memory gets cleaned up afterwards.
3134 *
3135 * On the second level, we will use a persistent file to track the latest
3136 * revprop generation.  It will be written upon each revprop change but
3137 * only be read if we are the first process to initialize the named atomics
3138 * with that value.
3139 *
3140 * The overhead for the second and following accesses to revprops is
3141 * almost zero on most systems.
3142 *
3143 *
3144 * Tech aspects:
3145 * -------------
3146 *
3147 * A problem is that we need to provide a globally available file name to
3148 * back the SHM implementation on OSes that need it.  We can only assume
3149 * write access to some file within the respective repositories.  Because
3150 * a given server process may access thousands of repositories during its
3151 * lifetime, keeping the SHM data alive for all of them is also not an
3152 * option.
3153 *
3154 * So, we store the new revprop generation on disk as part of each
3155 * setrevprop call, i.e. this write will be serialized and the write order
3156 * be guaranteed by the repository write lock.
3157 *
3158 * The only racy situation occurs when the data is being read again by two
3159 * processes concurrently but in that situation, the first process to
3160 * finish that procedure is guaranteed to be the only one that initializes
3161 * the SHM data.  Since even writers will first go through that
3162 * initialization phase, they will never operate on stale data.
3163 */
3164
3165/* Read revprop generation as stored on disk for repository FS. The result
3166 * is returned in *CURRENT. Default to 2 if no such file is available.
3167 */
3168static svn_error_t *
3169read_revprop_generation_file(apr_int64_t *current,
3170                             svn_fs_t *fs,
3171                             apr_pool_t *pool)
3172{
3173  svn_error_t *err;
3174  apr_file_t *file;
3175  char buf[80];
3176  apr_size_t len;
3177  const char *path = path_revprop_generation(fs, pool);
3178
3179  err = svn_io_file_open(&file, path,
3180                         APR_READ | APR_BUFFERED,
3181                         APR_OS_DEFAULT, pool);
3182  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
3183    {
3184      svn_error_clear(err);
3185      *current = 2;
3186
3187      return SVN_NO_ERROR;
3188    }
3189  SVN_ERR(err);
3190
3191  len = sizeof(buf);
3192  SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
3193
3194  /* Check that the first line contains only digits. */
3195  SVN_ERR(check_file_buffer_numeric(buf, 0, path,
3196                                    "Revprop Generation", pool));
3197  SVN_ERR(svn_cstring_atoi64(current, buf));
3198
3199  return svn_io_file_close(file, pool);
3200}
3201
3202/* Write the CURRENT revprop generation to disk for repository FS.
3203 */
3204static svn_error_t *
3205write_revprop_generation_file(svn_fs_t *fs,
3206                              apr_int64_t current,
3207                              apr_pool_t *pool)
3208{
3209  apr_file_t *file;
3210  const char *tmp_path;
3211
3212  char buf[SVN_INT64_BUFFER_SIZE];
3213  apr_size_t len = svn__i64toa(buf, current);
3214  buf[len] = '\n';
3215
3216  SVN_ERR(svn_io_open_unique_file3(&file, &tmp_path, fs->path,
3217                                   svn_io_file_del_none, pool, pool));
3218  SVN_ERR(svn_io_file_write_full(file, buf, len + 1, NULL, pool));
3219  SVN_ERR(svn_io_file_close(file, pool));
3220
3221  return move_into_place(tmp_path, path_revprop_generation(fs, pool),
3222                         tmp_path, pool);
3223}
3224
3225/* Make sure the revprop_namespace member in FS is set. */
3226static svn_error_t *
3227ensure_revprop_namespace(svn_fs_t *fs)
3228{
3229  fs_fs_data_t *ffd = fs->fsap_data;
3230
3231  return ffd->revprop_namespace == NULL
3232    ? svn_atomic_namespace__create(&ffd->revprop_namespace,
3233                                   svn_dirent_join(fs->path,
3234                                                   ATOMIC_REVPROP_NAMESPACE,
3235                                                   fs->pool),
3236                                   fs->pool)
3237    : SVN_NO_ERROR;
3238}
3239
3240/* Make sure the revprop_namespace member in FS is set. */
3241static svn_error_t *
3242cleanup_revprop_namespace(svn_fs_t *fs)
3243{
3244  const char *name = svn_dirent_join(fs->path,
3245                                     ATOMIC_REVPROP_NAMESPACE,
3246                                     fs->pool);
3247  return svn_error_trace(svn_atomic_namespace__cleanup(name, fs->pool));
3248}
3249
3250/* Make sure the revprop_generation member in FS is set and, if necessary,
3251 * initialized with the latest value stored on disk.
3252 */
3253static svn_error_t *
3254ensure_revprop_generation(svn_fs_t *fs, apr_pool_t *pool)
3255{
3256  fs_fs_data_t *ffd = fs->fsap_data;
3257
3258  SVN_ERR(ensure_revprop_namespace(fs));
3259  if (ffd->revprop_generation == NULL)
3260    {
3261      apr_int64_t current = 0;
3262
3263      SVN_ERR(svn_named_atomic__get(&ffd->revprop_generation,
3264                                    ffd->revprop_namespace,
3265                                    ATOMIC_REVPROP_GENERATION,
3266                                    TRUE));
3267
3268      /* If the generation is at 0, we just created a new namespace
3269       * (it would be at least 2 otherwise). Read the latest generation
3270       * from disk and if we are the first one to initialize the atomic
3271       * (i.e. is still 0), set it to the value just gotten.
3272       */
3273      SVN_ERR(svn_named_atomic__read(&current, ffd->revprop_generation));
3274      if (current == 0)
3275        {
3276          SVN_ERR(read_revprop_generation_file(&current, fs, pool));
3277          SVN_ERR(svn_named_atomic__cmpxchg(NULL, current, 0,
3278                                            ffd->revprop_generation));
3279        }
3280    }
3281
3282  return SVN_NO_ERROR;
3283}
3284
3285/* Make sure the revprop_timeout member in FS is set. */
3286static svn_error_t *
3287ensure_revprop_timeout(svn_fs_t *fs)
3288{
3289  fs_fs_data_t *ffd = fs->fsap_data;
3290
3291  SVN_ERR(ensure_revprop_namespace(fs));
3292  return ffd->revprop_timeout == NULL
3293    ? svn_named_atomic__get(&ffd->revprop_timeout,
3294                            ffd->revprop_namespace,
3295                            ATOMIC_REVPROP_TIMEOUT,
3296                            TRUE)
3297    : SVN_NO_ERROR;
3298}
3299
3300/* Create an error object with the given MESSAGE and pass it to the
3301   WARNING member of FS. */
3302static void
3303log_revprop_cache_init_warning(svn_fs_t *fs,
3304                               svn_error_t *underlying_err,
3305                               const char *message)
3306{
3307  svn_error_t *err = svn_error_createf(SVN_ERR_FS_REVPROP_CACHE_INIT_FAILURE,
3308                                       underlying_err,
3309                                       message, fs->path);
3310
3311  if (fs->warning)
3312    (fs->warning)(fs->warning_baton, err);
3313
3314  svn_error_clear(err);
3315}
3316
3317/* Test whether revprop cache and necessary infrastructure are
3318   available in FS. */
3319static svn_boolean_t
3320has_revprop_cache(svn_fs_t *fs, apr_pool_t *pool)
3321{
3322  fs_fs_data_t *ffd = fs->fsap_data;
3323  svn_error_t *error;
3324
3325  /* is the cache (still) enabled? */
3326  if (ffd->revprop_cache == NULL)
3327    return FALSE;
3328
3329  /* is it efficient? */
3330  if (!svn_named_atomic__is_efficient())
3331    {
3332      /* access to it would be quite slow
3333       * -> disable the revprop cache for good
3334       */
3335      ffd->revprop_cache = NULL;
3336      log_revprop_cache_init_warning(fs, NULL,
3337                                     "Revprop caching for '%s' disabled"
3338                                     " because it would be inefficient.");
3339
3340      return FALSE;
3341    }
3342
3343  /* try to access our SHM-backed infrastructure */
3344  error = ensure_revprop_generation(fs, pool);
3345  if (error)
3346    {
3347      /* failure -> disable revprop cache for good */
3348
3349      ffd->revprop_cache = NULL;
3350      log_revprop_cache_init_warning(fs, error,
3351                                     "Revprop caching for '%s' disabled "
3352                                     "because SHM infrastructure for revprop "
3353                                     "caching failed to initialize.");
3354
3355      return FALSE;
3356    }
3357
3358  return TRUE;
3359}
3360
3361/* Baton structure for revprop_generation_fixup. */
3362typedef struct revprop_generation_fixup_t
3363{
3364  /* revprop generation to read */
3365  apr_int64_t *generation;
3366
3367  /* containing the revprop_generation member to query */
3368  fs_fs_data_t *ffd;
3369} revprop_generation_upgrade_t;
3370
3371/* If the revprop generation has an odd value, it means the original writer
3372   of the revprop got killed. We don't know whether that process as able
3373   to change the revprop data but we assume that it was. Therefore, we
3374   increase the generation in that case to basically invalidate everyones
3375   cache content.
3376   Execute this onlx while holding the write lock to the repo in baton->FFD.
3377 */
3378static svn_error_t *
3379revprop_generation_fixup(void *void_baton,
3380                         apr_pool_t *pool)
3381{
3382  revprop_generation_upgrade_t *baton = void_baton;
3383  assert(baton->ffd->has_write_lock);
3384
3385  /* Maybe, either the original revprop writer or some other reader has
3386     already corrected / bumped the revprop generation.  Thus, we need
3387     to read it again. */
3388  SVN_ERR(svn_named_atomic__read(baton->generation,
3389                                 baton->ffd->revprop_generation));
3390
3391  /* Cause everyone to re-read revprops upon their next access, if the
3392     last revprop write did not complete properly. */
3393  while (*baton->generation % 2)
3394    SVN_ERR(svn_named_atomic__add(baton->generation,
3395                                  1,
3396                                  baton->ffd->revprop_generation));
3397
3398  return SVN_NO_ERROR;
3399}
3400
3401/* Read the current revprop generation and return it in *GENERATION.
3402   Also, detect aborted / crashed writers and recover from that.
3403   Use the access object in FS to set the shared mem values. */
3404static svn_error_t *
3405read_revprop_generation(apr_int64_t *generation,
3406                        svn_fs_t *fs,
3407                        apr_pool_t *pool)
3408{
3409  apr_int64_t current = 0;
3410  fs_fs_data_t *ffd = fs->fsap_data;
3411
3412  /* read the current revprop generation number */
3413  SVN_ERR(ensure_revprop_generation(fs, pool));
3414  SVN_ERR(svn_named_atomic__read(&current, ffd->revprop_generation));
3415
3416  /* is an unfinished revprop write under the way? */
3417  if (current % 2)
3418    {
3419      apr_int64_t timeout = 0;
3420
3421      /* read timeout for the write operation */
3422      SVN_ERR(ensure_revprop_timeout(fs));
3423      SVN_ERR(svn_named_atomic__read(&timeout, ffd->revprop_timeout));
3424
3425      /* has the writer process been aborted,
3426       * i.e. has the timeout been reached?
3427       */
3428      if (apr_time_now() > timeout)
3429        {
3430          revprop_generation_upgrade_t baton;
3431          baton.generation = &current;
3432          baton.ffd = ffd;
3433
3434          /* Ensure that the original writer process no longer exists by
3435           * acquiring the write lock to this repository.  Then, fix up
3436           * the revprop generation.
3437           */
3438          if (ffd->has_write_lock)
3439            SVN_ERR(revprop_generation_fixup(&baton, pool));
3440          else
3441            SVN_ERR(svn_fs_fs__with_write_lock(fs, revprop_generation_fixup,
3442                                               &baton, pool));
3443        }
3444    }
3445
3446  /* return the value we just got */
3447  *generation = current;
3448  return SVN_NO_ERROR;
3449}
3450
3451/* Set the revprop generation to the next odd number to indicate that
3452   there is a revprop write process under way. If that times out,
3453   readers shall recover from that state & re-read revprops.
3454   Use the access object in FS to set the shared mem value. */
3455static svn_error_t *
3456begin_revprop_change(svn_fs_t *fs, apr_pool_t *pool)
3457{
3458  apr_int64_t current;
3459  fs_fs_data_t *ffd = fs->fsap_data;
3460
3461  /* set the timeout for the write operation */
3462  SVN_ERR(ensure_revprop_timeout(fs));
3463  SVN_ERR(svn_named_atomic__write(NULL,
3464                                  apr_time_now() + REVPROP_CHANGE_TIMEOUT,
3465                                  ffd->revprop_timeout));
3466
3467  /* set the revprop generation to an odd value to indicate
3468   * that a write is in progress
3469   */
3470  SVN_ERR(ensure_revprop_generation(fs, pool));
3471  do
3472    {
3473      SVN_ERR(svn_named_atomic__add(&current,
3474                                    1,
3475                                    ffd->revprop_generation));
3476    }
3477  while (current % 2 == 0);
3478
3479  return SVN_NO_ERROR;
3480}
3481
3482/* Set the revprop generation to the next even number to indicate that
3483   a) readers shall re-read revprops, and
3484   b) the write process has been completed (no recovery required)
3485   Use the access object in FS to set the shared mem value. */
3486static svn_error_t *
3487end_revprop_change(svn_fs_t *fs, apr_pool_t *pool)
3488{
3489  apr_int64_t current = 1;
3490  fs_fs_data_t *ffd = fs->fsap_data;
3491
3492  /* set the revprop generation to an even value to indicate
3493   * that a write has been completed
3494   */
3495  SVN_ERR(ensure_revprop_generation(fs, pool));
3496  do
3497    {
3498      SVN_ERR(svn_named_atomic__add(&current,
3499                                    1,
3500                                    ffd->revprop_generation));
3501    }
3502  while (current % 2);
3503
3504  /* Save the latest generation to disk. FS is currently in a "locked"
3505   * state such that we can be sure the be the only ones to write that
3506   * file.
3507   */
3508  return write_revprop_generation_file(fs, current, pool);
3509}
3510
3511/* Container for all data required to access the packed revprop file
3512 * for a given REVISION.  This structure will be filled incrementally
3513 * by read_pack_revprops() its sub-routines.
3514 */
3515typedef struct packed_revprops_t
3516{
3517  /* revision number to read (not necessarily the first in the pack) */
3518  svn_revnum_t revision;
3519
3520  /* current revprop generation. Used when populating the revprop cache */
3521  apr_int64_t generation;
3522
3523  /* the actual revision properties */
3524  apr_hash_t *properties;
3525
3526  /* their size when serialized to a single string
3527   * (as found in PACKED_REVPROPS) */
3528  apr_size_t serialized_size;
3529
3530
3531  /* name of the pack file (without folder path) */
3532  const char *filename;
3533
3534  /* packed shard folder path */
3535  const char *folder;
3536
3537  /* sum of values in SIZES */
3538  apr_size_t total_size;
3539
3540  /* first revision in the pack (>= MANIFEST_START) */
3541  svn_revnum_t start_revision;
3542
3543  /* size of the revprops in PACKED_REVPROPS */
3544  apr_array_header_t *sizes;
3545
3546  /* offset of the revprops in PACKED_REVPROPS */
3547  apr_array_header_t *offsets;
3548
3549
3550  /* concatenation of the serialized representation of all revprops
3551   * in the pack, i.e. the pack content without header and compression */
3552  svn_stringbuf_t *packed_revprops;
3553
3554  /* First revision covered by MANIFEST.
3555   * Will equal the shard start revision or 1, for the 1st shard. */
3556  svn_revnum_t manifest_start;
3557
3558  /* content of the manifest.
3559   * Maps long(rev - MANIFEST_START) to const char* pack file name */
3560  apr_array_header_t *manifest;
3561} packed_revprops_t;
3562
3563/* Parse the serialized revprops in CONTENT and return them in *PROPERTIES.
3564 * Also, put them into the revprop cache, if activated, for future use.
3565 * Three more parameters are being used to update the revprop cache: FS is
3566 * our file system, the revprops belong to REVISION and the global revprop
3567 * GENERATION is used as well.
3568 *
3569 * The returned hash will be allocated in POOL, SCRATCH_POOL is being used
3570 * for temporary allocations.
3571 */
3572static svn_error_t *
3573parse_revprop(apr_hash_t **properties,
3574              svn_fs_t *fs,
3575              svn_revnum_t revision,
3576              apr_int64_t generation,
3577              svn_string_t *content,
3578              apr_pool_t *pool,
3579              apr_pool_t *scratch_pool)
3580{
3581  svn_stream_t *stream = svn_stream_from_string(content, scratch_pool);
3582  *properties = apr_hash_make(pool);
3583
3584  SVN_ERR(svn_hash_read2(*properties, stream, SVN_HASH_TERMINATOR, pool));
3585  if (has_revprop_cache(fs, pool))
3586    {
3587      fs_fs_data_t *ffd = fs->fsap_data;
3588      pair_cache_key_t key = { 0 };
3589
3590      key.revision = revision;
3591      key.second = generation;
3592      SVN_ERR(svn_cache__set(ffd->revprop_cache, &key, *properties,
3593                             scratch_pool));
3594    }
3595
3596  return SVN_NO_ERROR;
3597}
3598
3599/* Read the non-packed revprops for revision REV in FS, put them into the
3600 * revprop cache if activated and return them in *PROPERTIES.  GENERATION
3601 * is the current revprop generation.
3602 *
3603 * If the data could not be read due to an otherwise recoverable error,
3604 * leave *PROPERTIES unchanged. No error will be returned in that case.
3605 *
3606 * Allocations will be done in POOL.
3607 */
3608static svn_error_t *
3609read_non_packed_revprop(apr_hash_t **properties,
3610                        svn_fs_t *fs,
3611                        svn_revnum_t rev,
3612                        apr_int64_t generation,
3613                        apr_pool_t *pool)
3614{
3615  svn_stringbuf_t *content = NULL;
3616  apr_pool_t *iterpool = svn_pool_create(pool);
3617  svn_boolean_t missing = FALSE;
3618  int i;
3619
3620  for (i = 0; i < RECOVERABLE_RETRY_COUNT && !missing && !content; ++i)
3621    {
3622      svn_pool_clear(iterpool);
3623      SVN_ERR(try_stringbuf_from_file(&content,
3624                                      &missing,
3625                                      path_revprops(fs, rev, iterpool),
3626                                      i + 1 < RECOVERABLE_RETRY_COUNT,
3627                                      iterpool));
3628    }
3629
3630  if (content)
3631    SVN_ERR(parse_revprop(properties, fs, rev, generation,
3632                          svn_stringbuf__morph_into_string(content),
3633                          pool, iterpool));
3634
3635  svn_pool_clear(iterpool);
3636
3637  return SVN_NO_ERROR;
3638}
3639
3640/* Given FS and REVPROPS->REVISION, fill the FILENAME, FOLDER and MANIFEST
3641 * members. Use POOL for allocating results and SCRATCH_POOL for temporaries.
3642 */
3643static svn_error_t *
3644get_revprop_packname(svn_fs_t *fs,
3645                     packed_revprops_t *revprops,
3646                     apr_pool_t *pool,
3647                     apr_pool_t *scratch_pool)
3648{
3649  fs_fs_data_t *ffd = fs->fsap_data;
3650  svn_stringbuf_t *content = NULL;
3651  const char *manifest_file_path;
3652  int idx;
3653
3654  /* read content of the manifest file */
3655  revprops->folder = path_revprops_pack_shard(fs, revprops->revision, pool);
3656  manifest_file_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
3657
3658  SVN_ERR(read_content(&content, manifest_file_path, pool));
3659
3660  /* parse the manifest. Every line is a file name */
3661  revprops->manifest = apr_array_make(pool, ffd->max_files_per_dir,
3662                                      sizeof(const char*));
3663
3664  /* Read all lines.  Since the last line ends with a newline, we will
3665     end up with a valid but empty string after the last entry. */
3666  while (content->data && *content->data)
3667    {
3668      APR_ARRAY_PUSH(revprops->manifest, const char*) = content->data;
3669      content->data = strchr(content->data, '\n');
3670      if (content->data)
3671        {
3672          *content->data = 0;
3673          content->data++;
3674        }
3675    }
3676
3677  /* Index for our revision. Rev 0 is excluded from the first shard. */
3678  revprops->manifest_start = revprops->revision
3679                           - (revprops->revision % ffd->max_files_per_dir);
3680  if (revprops->manifest_start == 0)
3681    ++revprops->manifest_start;
3682  idx = (int)(revprops->revision - revprops->manifest_start);
3683
3684  if (revprops->manifest->nelts <= idx)
3685    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3686                             _("Packed revprop manifest for r%ld too "
3687                               "small"), revprops->revision);
3688
3689  /* Now get the file name */
3690  revprops->filename = APR_ARRAY_IDX(revprops->manifest, idx, const char*);
3691
3692  return SVN_NO_ERROR;
3693}
3694
3695/* Return TRUE, if revision R1 and R2 refer to the same shard in FS.
3696 */
3697static svn_boolean_t
3698same_shard(svn_fs_t *fs,
3699           svn_revnum_t r1,
3700           svn_revnum_t r2)
3701{
3702  fs_fs_data_t *ffd = fs->fsap_data;
3703  return (r1 / ffd->max_files_per_dir) == (r2 / ffd->max_files_per_dir);
3704}
3705
3706/* Given FS and the full packed file content in REVPROPS->PACKED_REVPROPS,
3707 * fill the START_REVISION, SIZES, OFFSETS members. Also, make
3708 * PACKED_REVPROPS point to the first serialized revprop.
3709 *
3710 * Parse the revprops for REVPROPS->REVISION and set the PROPERTIES as
3711 * well as the SERIALIZED_SIZE member.  If revprop caching has been
3712 * enabled, parse all revprops in the pack and cache them.
3713 */
3714static svn_error_t *
3715parse_packed_revprops(svn_fs_t *fs,
3716                      packed_revprops_t *revprops,
3717                      apr_pool_t *pool,
3718                      apr_pool_t *scratch_pool)
3719{
3720  svn_stream_t *stream;
3721  apr_int64_t first_rev, count, i;
3722  apr_off_t offset;
3723  const char *header_end;
3724  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3725
3726  /* decompress (even if the data is only "stored", there is still a
3727   * length header to remove) */
3728  svn_string_t *compressed
3729      = svn_stringbuf__morph_into_string(revprops->packed_revprops);
3730  svn_stringbuf_t *uncompressed = svn_stringbuf_create_empty(pool);
3731  SVN_ERR(svn__decompress(compressed, uncompressed, APR_SIZE_MAX));
3732
3733  /* read first revision number and number of revisions in the pack */
3734  stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
3735  SVN_ERR(read_number_from_stream(&first_rev, NULL, stream, iterpool));
3736  SVN_ERR(read_number_from_stream(&count, NULL, stream, iterpool));
3737
3738  /* Check revision range for validity. */
3739  if (   !same_shard(fs, revprops->revision, first_rev)
3740      || !same_shard(fs, revprops->revision, first_rev + count - 1)
3741      || count < 1)
3742    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3743                             _("Revprop pack for revision r%ld"
3744                               " contains revprops for r%ld .. r%ld"),
3745                             revprops->revision,
3746                             (svn_revnum_t)first_rev,
3747                             (svn_revnum_t)(first_rev + count -1));
3748
3749  /* Since start & end are in the same shard, it is enough to just test
3750   * the FIRST_REV for being actually packed.  That will also cover the
3751   * special case of rev 0 never being packed. */
3752  if (!is_packed_revprop(fs, first_rev))
3753    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3754                             _("Revprop pack for revision r%ld"
3755                               " starts at non-packed revisions r%ld"),
3756                             revprops->revision, (svn_revnum_t)first_rev);
3757
3758  /* make PACKED_REVPROPS point to the first char after the header.
3759   * This is where the serialized revprops are. */
3760  header_end = strstr(uncompressed->data, "\n\n");
3761  if (header_end == NULL)
3762    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3763                            _("Header end not found"));
3764
3765  offset = header_end - uncompressed->data + 2;
3766
3767  revprops->packed_revprops = svn_stringbuf_create_empty(pool);
3768  revprops->packed_revprops->data = uncompressed->data + offset;
3769  revprops->packed_revprops->len = (apr_size_t)(uncompressed->len - offset);
3770  revprops->packed_revprops->blocksize = (apr_size_t)(uncompressed->blocksize - offset);
3771
3772  /* STREAM still points to the first entry in the sizes list.
3773   * Init / construct REVPROPS members. */
3774  revprops->start_revision = (svn_revnum_t)first_rev;
3775  revprops->sizes = apr_array_make(pool, (int)count, sizeof(offset));
3776  revprops->offsets = apr_array_make(pool, (int)count, sizeof(offset));
3777
3778  /* Now parse, revision by revision, the size and content of each
3779   * revisions' revprops. */
3780  for (i = 0, offset = 0, revprops->total_size = 0; i < count; ++i)
3781    {
3782      apr_int64_t size;
3783      svn_string_t serialized;
3784      apr_hash_t *properties;
3785      svn_revnum_t revision = (svn_revnum_t)(first_rev + i);
3786
3787      /* read & check the serialized size */
3788      SVN_ERR(read_number_from_stream(&size, NULL, stream, iterpool));
3789      if (size + offset > (apr_int64_t)revprops->packed_revprops->len)
3790        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3791                        _("Packed revprop size exceeds pack file size"));
3792
3793      /* Parse this revprops list, if necessary */
3794      serialized.data = revprops->packed_revprops->data + offset;
3795      serialized.len = (apr_size_t)size;
3796
3797      if (revision == revprops->revision)
3798        {
3799          SVN_ERR(parse_revprop(&revprops->properties, fs, revision,
3800                                revprops->generation, &serialized,
3801                                pool, iterpool));
3802          revprops->serialized_size = serialized.len;
3803        }
3804      else
3805        {
3806          /* If revprop caching is enabled, parse any revprops.
3807           * They will get cached as a side-effect of this. */
3808          if (has_revprop_cache(fs, pool))
3809            SVN_ERR(parse_revprop(&properties, fs, revision,
3810                                  revprops->generation, &serialized,
3811                                  iterpool, iterpool));
3812        }
3813
3814      /* fill REVPROPS data structures */
3815      APR_ARRAY_PUSH(revprops->sizes, apr_off_t) = serialized.len;
3816      APR_ARRAY_PUSH(revprops->offsets, apr_off_t) = offset;
3817      revprops->total_size += serialized.len;
3818
3819      offset += serialized.len;
3820
3821      svn_pool_clear(iterpool);
3822    }
3823
3824  return SVN_NO_ERROR;
3825}
3826
3827/* In filesystem FS, read the packed revprops for revision REV into
3828 * *REVPROPS.  Use GENERATION to populate the revprop cache, if enabled.
3829 * Allocate data in POOL.
3830 */
3831static svn_error_t *
3832read_pack_revprop(packed_revprops_t **revprops,
3833                  svn_fs_t *fs,
3834                  svn_revnum_t rev,
3835                  apr_int64_t generation,
3836                  apr_pool_t *pool)
3837{
3838  apr_pool_t *iterpool = svn_pool_create(pool);
3839  svn_boolean_t missing = FALSE;
3840  svn_error_t *err;
3841  packed_revprops_t *result;
3842  int i;
3843
3844  /* someone insisted that REV is packed. Double-check if necessary */
3845  if (!is_packed_revprop(fs, rev))
3846     SVN_ERR(update_min_unpacked_rev(fs, iterpool));
3847
3848  if (!is_packed_revprop(fs, rev))
3849    return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
3850                              _("No such packed revision %ld"), rev);
3851
3852  /* initialize the result data structure */
3853  result = apr_pcalloc(pool, sizeof(*result));
3854  result->revision = rev;
3855  result->generation = generation;
3856
3857  /* try to read the packed revprops. This may require retries if we have
3858   * concurrent writers. */
3859  for (i = 0; i < RECOVERABLE_RETRY_COUNT && !result->packed_revprops; ++i)
3860    {
3861      const char *file_path;
3862
3863      /* there might have been concurrent writes.
3864       * Re-read the manifest and the pack file.
3865       */
3866      SVN_ERR(get_revprop_packname(fs, result, pool, iterpool));
3867      file_path  = svn_dirent_join(result->folder,
3868                                   result->filename,
3869                                   iterpool);
3870      SVN_ERR(try_stringbuf_from_file(&result->packed_revprops,
3871                                      &missing,
3872                                      file_path,
3873                                      i + 1 < RECOVERABLE_RETRY_COUNT,
3874                                      pool));
3875
3876      /* If we could not find the file, there was a write.
3877       * So, we should refresh our revprop generation info as well such
3878       * that others may find data we will put into the cache.  They would
3879       * consider it outdated, otherwise.
3880       */
3881      if (missing && has_revprop_cache(fs, pool))
3882        SVN_ERR(read_revprop_generation(&result->generation, fs, pool));
3883
3884      svn_pool_clear(iterpool);
3885    }
3886
3887  /* the file content should be available now */
3888  if (!result->packed_revprops)
3889    return svn_error_createf(SVN_ERR_FS_PACKED_REVPROP_READ_FAILURE, NULL,
3890                  _("Failed to read revprop pack file for r%ld"), rev);
3891
3892  /* parse it. RESULT will be complete afterwards. */
3893  err = parse_packed_revprops(fs, result, pool, iterpool);
3894  svn_pool_destroy(iterpool);
3895  if (err)
3896    return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
3897                  _("Revprop pack file for r%ld is corrupt"), rev);
3898
3899  *revprops = result;
3900
3901  return SVN_NO_ERROR;
3902}
3903
3904/* Read the revprops for revision REV in FS and return them in *PROPERTIES_P.
3905 *
3906 * Allocations will be done in POOL.
3907 */
3908static svn_error_t *
3909get_revision_proplist(apr_hash_t **proplist_p,
3910                      svn_fs_t *fs,
3911                      svn_revnum_t rev,
3912                      apr_pool_t *pool)
3913{
3914  fs_fs_data_t *ffd = fs->fsap_data;
3915  apr_int64_t generation = 0;
3916
3917  /* not found, yet */
3918  *proplist_p = NULL;
3919
3920  /* should they be available at all? */
3921  SVN_ERR(ensure_revision_exists(fs, rev, pool));
3922
3923  /* Try cache lookup first. */
3924  if (has_revprop_cache(fs, pool))
3925    {
3926      svn_boolean_t is_cached;
3927      pair_cache_key_t key = { 0 };
3928
3929      SVN_ERR(read_revprop_generation(&generation, fs, pool));
3930
3931      key.revision = rev;
3932      key.second = generation;
3933      SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
3934                             ffd->revprop_cache, &key, pool));
3935      if (is_cached)
3936        return SVN_NO_ERROR;
3937    }
3938
3939  /* if REV had not been packed when we began, try reading it from the
3940   * non-packed shard.  If that fails, we will fall through to packed
3941   * shard reads. */
3942  if (!is_packed_revprop(fs, rev))
3943    {
3944      svn_error_t *err = read_non_packed_revprop(proplist_p, fs, rev,
3945                                                 generation, pool);
3946      if (err)
3947        {
3948          if (!APR_STATUS_IS_ENOENT(err->apr_err)
3949              || ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
3950            return svn_error_trace(err);
3951
3952          svn_error_clear(err);
3953          *proplist_p = NULL; /* in case read_non_packed_revprop changed it */
3954        }
3955    }
3956
3957  /* if revprop packing is available and we have not read the revprops, yet,
3958   * try reading them from a packed shard.  If that fails, REV is most
3959   * likely invalid (or its revprops highly contested). */
3960  if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT && !*proplist_p)
3961    {
3962      packed_revprops_t *packed_revprops;
3963      SVN_ERR(read_pack_revprop(&packed_revprops, fs, rev, generation, pool));
3964      *proplist_p = packed_revprops->properties;
3965    }
3966
3967  /* The revprops should have been there. Did we get them? */
3968  if (!*proplist_p)
3969    return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
3970                             _("Could not read revprops for revision %ld"),
3971                             rev);
3972
3973  return SVN_NO_ERROR;
3974}
3975
3976/* Serialize the revision property list PROPLIST of revision REV in
3977 * filesystem FS to a non-packed file.  Return the name of that temporary
3978 * file in *TMP_PATH and the file path that it must be moved to in
3979 * *FINAL_PATH.
3980 *
3981 * Use POOL for allocations.
3982 */
3983static svn_error_t *
3984write_non_packed_revprop(const char **final_path,
3985                         const char **tmp_path,
3986                         svn_fs_t *fs,
3987                         svn_revnum_t rev,
3988                         apr_hash_t *proplist,
3989                         apr_pool_t *pool)
3990{
3991  svn_stream_t *stream;
3992  *final_path = path_revprops(fs, rev, pool);
3993
3994  /* ### do we have a directory sitting around already? we really shouldn't
3995     ### have to get the dirname here. */
3996  SVN_ERR(svn_stream_open_unique(&stream, tmp_path,
3997                                 svn_dirent_dirname(*final_path, pool),
3998                                 svn_io_file_del_none, pool, pool));
3999  SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
4000  SVN_ERR(svn_stream_close(stream));
4001
4002  return SVN_NO_ERROR;
4003}
4004
4005/* After writing the new revprop file(s), call this function to move the
4006 * file at TMP_PATH to FINAL_PATH and give it the permissions from
4007 * PERMS_REFERENCE.
4008 *
4009 * If indicated in BUMP_GENERATION, increase FS' revprop generation.
4010 * Finally, delete all the temporary files given in FILES_TO_DELETE.
4011 * The latter may be NULL.
4012 *
4013 * Use POOL for temporary allocations.
4014 */
4015static svn_error_t *
4016switch_to_new_revprop(svn_fs_t *fs,
4017                      const char *final_path,
4018                      const char *tmp_path,
4019                      const char *perms_reference,
4020                      apr_array_header_t *files_to_delete,
4021                      svn_boolean_t bump_generation,
4022                      apr_pool_t *pool)
4023{
4024  /* Now, we may actually be replacing revprops. Make sure that all other
4025     threads and processes will know about this. */
4026  if (bump_generation)
4027    SVN_ERR(begin_revprop_change(fs, pool));
4028
4029  SVN_ERR(move_into_place(tmp_path, final_path, perms_reference, pool));
4030
4031  /* Indicate that the update (if relevant) has been completed. */
4032  if (bump_generation)
4033    SVN_ERR(end_revprop_change(fs, pool));
4034
4035  /* Clean up temporary files, if necessary. */
4036  if (files_to_delete)
4037    {
4038      apr_pool_t *iterpool = svn_pool_create(pool);
4039      int i;
4040
4041      for (i = 0; i < files_to_delete->nelts; ++i)
4042        {
4043          const char *path = APR_ARRAY_IDX(files_to_delete, i, const char*);
4044          SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
4045          svn_pool_clear(iterpool);
4046        }
4047
4048      svn_pool_destroy(iterpool);
4049    }
4050  return SVN_NO_ERROR;
4051}
4052
4053/* Write a pack file header to STREAM that starts at revision START_REVISION
4054 * and contains the indexes [START,END) of SIZES.
4055 */
4056static svn_error_t *
4057serialize_revprops_header(svn_stream_t *stream,
4058                          svn_revnum_t start_revision,
4059                          apr_array_header_t *sizes,
4060                          int start,
4061                          int end,
4062                          apr_pool_t *pool)
4063{
4064  apr_pool_t *iterpool = svn_pool_create(pool);
4065  int i;
4066
4067  SVN_ERR_ASSERT(start < end);
4068
4069  /* start revision and entry count */
4070  SVN_ERR(svn_stream_printf(stream, pool, "%ld\n", start_revision));
4071  SVN_ERR(svn_stream_printf(stream, pool, "%d\n", end - start));
4072
4073  /* the sizes array */
4074  for (i = start; i < end; ++i)
4075    {
4076      apr_off_t size = APR_ARRAY_IDX(sizes, i, apr_off_t);
4077      SVN_ERR(svn_stream_printf(stream, iterpool, "%" APR_OFF_T_FMT "\n",
4078                                size));
4079    }
4080
4081  /* the double newline char indicates the end of the header */
4082  SVN_ERR(svn_stream_printf(stream, iterpool, "\n"));
4083
4084  svn_pool_clear(iterpool);
4085  return SVN_NO_ERROR;
4086}
4087
4088/* Writes the a pack file to FILE_STREAM.  It copies the serialized data
4089 * from REVPROPS for the indexes [START,END) except for index CHANGED_INDEX.
4090 *
4091 * The data for the latter is taken from NEW_SERIALIZED.  Note, that
4092 * CHANGED_INDEX may be outside the [START,END) range, i.e. no new data is
4093 * taken in that case but only a subset of the old data will be copied.
4094 *
4095 * NEW_TOTAL_SIZE is a hint for pre-allocating buffers of appropriate size.
4096 * POOL is used for temporary allocations.
4097 */
4098static svn_error_t *
4099repack_revprops(svn_fs_t *fs,
4100                packed_revprops_t *revprops,
4101                int start,
4102                int end,
4103                int changed_index,
4104                svn_stringbuf_t *new_serialized,
4105                apr_off_t new_total_size,
4106                svn_stream_t *file_stream,
4107                apr_pool_t *pool)
4108{
4109  fs_fs_data_t *ffd = fs->fsap_data;
4110  svn_stream_t *stream;
4111  int i;
4112
4113  /* create data empty buffers and the stream object */
4114  svn_stringbuf_t *uncompressed
4115    = svn_stringbuf_create_ensure((apr_size_t)new_total_size, pool);
4116  svn_stringbuf_t *compressed
4117    = svn_stringbuf_create_empty(pool);
4118  stream = svn_stream_from_stringbuf(uncompressed, pool);
4119
4120  /* write the header*/
4121  SVN_ERR(serialize_revprops_header(stream, revprops->start_revision + start,
4122                                    revprops->sizes, start, end, pool));
4123
4124  /* append the serialized revprops */
4125  for (i = start; i < end; ++i)
4126    if (i == changed_index)
4127      {
4128        SVN_ERR(svn_stream_write(stream,
4129                                 new_serialized->data,
4130                                 &new_serialized->len));
4131      }
4132    else
4133      {
4134        apr_size_t size
4135            = (apr_size_t)APR_ARRAY_IDX(revprops->sizes, i, apr_off_t);
4136        apr_size_t offset
4137            = (apr_size_t)APR_ARRAY_IDX(revprops->offsets, i, apr_off_t);
4138
4139        SVN_ERR(svn_stream_write(stream,
4140                                 revprops->packed_revprops->data + offset,
4141                                 &size));
4142      }
4143
4144  /* flush the stream buffer (if any) to our underlying data buffer */
4145  SVN_ERR(svn_stream_close(stream));
4146
4147  /* compress / store the data */
4148  SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed),
4149                        compressed,
4150                        ffd->compress_packed_revprops
4151                          ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
4152                          : SVN_DELTA_COMPRESSION_LEVEL_NONE));
4153
4154  /* finally, write the content to the target stream and close it */
4155  SVN_ERR(svn_stream_write(file_stream, compressed->data, &compressed->len));
4156  SVN_ERR(svn_stream_close(file_stream));
4157
4158  return SVN_NO_ERROR;
4159}
4160
4161/* Allocate a new pack file name for revisions
4162 *     [REVPROPS->START_REVISION + START, REVPROPS->START_REVISION + END - 1]
4163 * of REVPROPS->MANIFEST.  Add the name of old file to FILES_TO_DELETE,
4164 * auto-create that array if necessary.  Return an open file stream to
4165 * the new file in *STREAM allocated in POOL.
4166 */
4167static svn_error_t *
4168repack_stream_open(svn_stream_t **stream,
4169                   svn_fs_t *fs,
4170                   packed_revprops_t *revprops,
4171                   int start,
4172                   int end,
4173                   apr_array_header_t **files_to_delete,
4174                   apr_pool_t *pool)
4175{
4176  apr_int64_t tag;
4177  const char *tag_string;
4178  svn_string_t *new_filename;
4179  int i;
4180  apr_file_t *file;
4181  int manifest_offset
4182    = (int)(revprops->start_revision - revprops->manifest_start);
4183
4184  /* get the old (= current) file name and enlist it for later deletion */
4185  const char *old_filename = APR_ARRAY_IDX(revprops->manifest,
4186                                           start + manifest_offset,
4187                                           const char*);
4188
4189  if (*files_to_delete == NULL)
4190    *files_to_delete = apr_array_make(pool, 3, sizeof(const char*));
4191
4192  APR_ARRAY_PUSH(*files_to_delete, const char*)
4193    = svn_dirent_join(revprops->folder, old_filename, pool);
4194
4195  /* increase the tag part, i.e. the counter after the dot */
4196  tag_string = strchr(old_filename, '.');
4197  if (tag_string == NULL)
4198    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4199                             _("Packed file '%s' misses a tag"),
4200                             old_filename);
4201
4202  SVN_ERR(svn_cstring_atoi64(&tag, tag_string + 1));
4203  new_filename = svn_string_createf(pool, "%ld.%" APR_INT64_T_FMT,
4204                                    revprops->start_revision + start,
4205                                    ++tag);
4206
4207  /* update the manifest to point to the new file */
4208  for (i = start; i < end; ++i)
4209    APR_ARRAY_IDX(revprops->manifest, i + manifest_offset, const char*)
4210      = new_filename->data;
4211
4212  /* create a file stream for the new file */
4213  SVN_ERR(svn_io_file_open(&file, svn_dirent_join(revprops->folder,
4214                                                  new_filename->data,
4215                                                  pool),
4216                           APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
4217  *stream = svn_stream_from_aprfile2(file, FALSE, pool);
4218
4219  return SVN_NO_ERROR;
4220}
4221
4222/* For revision REV in filesystem FS, set the revision properties to
4223 * PROPLIST.  Return a new file in *TMP_PATH that the caller shall move
4224 * to *FINAL_PATH to make the change visible.  Files to be deleted will
4225 * be listed in *FILES_TO_DELETE which may remain unchanged / unallocated.
4226 * Use POOL for allocations.
4227 */
4228static svn_error_t *
4229write_packed_revprop(const char **final_path,
4230                     const char **tmp_path,
4231                     apr_array_header_t **files_to_delete,
4232                     svn_fs_t *fs,
4233                     svn_revnum_t rev,
4234                     apr_hash_t *proplist,
4235                     apr_pool_t *pool)
4236{
4237  fs_fs_data_t *ffd = fs->fsap_data;
4238  packed_revprops_t *revprops;
4239  apr_int64_t generation = 0;
4240  svn_stream_t *stream;
4241  svn_stringbuf_t *serialized;
4242  apr_off_t new_total_size;
4243  int changed_index;
4244
4245  /* read the current revprop generation. This value will not change
4246   * while we hold the global write lock to this FS. */
4247  if (has_revprop_cache(fs, pool))
4248    SVN_ERR(read_revprop_generation(&generation, fs, pool));
4249
4250  /* read contents of the current pack file */
4251  SVN_ERR(read_pack_revprop(&revprops, fs, rev, generation, pool));
4252
4253  /* serialize the new revprops */
4254  serialized = svn_stringbuf_create_empty(pool);
4255  stream = svn_stream_from_stringbuf(serialized, pool);
4256  SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
4257  SVN_ERR(svn_stream_close(stream));
4258
4259  /* calculate the size of the new data */
4260  changed_index = (int)(rev - revprops->start_revision);
4261  new_total_size = revprops->total_size - revprops->serialized_size
4262                 + serialized->len
4263                 + (revprops->offsets->nelts + 2) * SVN_INT64_BUFFER_SIZE;
4264
4265  APR_ARRAY_IDX(revprops->sizes, changed_index, apr_off_t) = serialized->len;
4266
4267  /* can we put the new data into the same pack as the before? */
4268  if (   new_total_size < ffd->revprop_pack_size
4269      || revprops->sizes->nelts == 1)
4270    {
4271      /* simply replace the old pack file with new content as we do it
4272       * in the non-packed case */
4273
4274      *final_path = svn_dirent_join(revprops->folder, revprops->filename,
4275                                    pool);
4276      SVN_ERR(svn_stream_open_unique(&stream, tmp_path, revprops->folder,
4277                                     svn_io_file_del_none, pool, pool));
4278      SVN_ERR(repack_revprops(fs, revprops, 0, revprops->sizes->nelts,
4279                              changed_index, serialized, new_total_size,
4280                              stream, pool));
4281    }
4282  else
4283    {
4284      /* split the pack file into two of roughly equal size */
4285      int right_count, left_count, i;
4286
4287      int left = 0;
4288      int right = revprops->sizes->nelts - 1;
4289      apr_off_t left_size = 2 * SVN_INT64_BUFFER_SIZE;
4290      apr_off_t right_size = 2 * SVN_INT64_BUFFER_SIZE;
4291
4292      /* let left and right side grow such that their size difference
4293       * is minimal after each step. */
4294      while (left <= right)
4295        if (  left_size + APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
4296            < right_size + APR_ARRAY_IDX(revprops->sizes, right, apr_off_t))
4297          {
4298            left_size += APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
4299                      + SVN_INT64_BUFFER_SIZE;
4300            ++left;
4301          }
4302        else
4303          {
4304            right_size += APR_ARRAY_IDX(revprops->sizes, right, apr_off_t)
4305                        + SVN_INT64_BUFFER_SIZE;
4306            --right;
4307          }
4308
4309       /* since the items need much less than SVN_INT64_BUFFER_SIZE
4310        * bytes to represent their length, the split may not be optimal */
4311      left_count = left;
4312      right_count = revprops->sizes->nelts - left;
4313
4314      /* if new_size is large, one side may exceed the pack size limit.
4315       * In that case, split before and after the modified revprop.*/
4316      if (   left_size > ffd->revprop_pack_size
4317          || right_size > ffd->revprop_pack_size)
4318        {
4319          left_count = changed_index;
4320          right_count = revprops->sizes->nelts - left_count - 1;
4321        }
4322
4323      /* write the new, split files */
4324      if (left_count)
4325        {
4326          SVN_ERR(repack_stream_open(&stream, fs, revprops, 0,
4327                                     left_count, files_to_delete, pool));
4328          SVN_ERR(repack_revprops(fs, revprops, 0, left_count,
4329                                  changed_index, serialized, new_total_size,
4330                                  stream, pool));
4331        }
4332
4333      if (left_count + right_count < revprops->sizes->nelts)
4334        {
4335          SVN_ERR(repack_stream_open(&stream, fs, revprops, changed_index,
4336                                     changed_index + 1, files_to_delete,
4337                                     pool));
4338          SVN_ERR(repack_revprops(fs, revprops, changed_index,
4339                                  changed_index + 1,
4340                                  changed_index, serialized, new_total_size,
4341                                  stream, pool));
4342        }
4343
4344      if (right_count)
4345        {
4346          SVN_ERR(repack_stream_open(&stream, fs, revprops,
4347                                     revprops->sizes->nelts - right_count,
4348                                     revprops->sizes->nelts,
4349                                     files_to_delete, pool));
4350          SVN_ERR(repack_revprops(fs, revprops,
4351                                  revprops->sizes->nelts - right_count,
4352                                  revprops->sizes->nelts, changed_index,
4353                                  serialized, new_total_size, stream,
4354                                  pool));
4355        }
4356
4357      /* write the new manifest */
4358      *final_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
4359      SVN_ERR(svn_stream_open_unique(&stream, tmp_path, revprops->folder,
4360                                     svn_io_file_del_none, pool, pool));
4361
4362      for (i = 0; i < revprops->manifest->nelts; ++i)
4363        {
4364          const char *filename = APR_ARRAY_IDX(revprops->manifest, i,
4365                                               const char*);
4366          SVN_ERR(svn_stream_printf(stream, pool, "%s\n", filename));
4367        }
4368
4369      SVN_ERR(svn_stream_close(stream));
4370    }
4371
4372  return SVN_NO_ERROR;
4373}
4374
4375/* Set the revision property list of revision REV in filesystem FS to
4376   PROPLIST.  Use POOL for temporary allocations. */
4377static svn_error_t *
4378set_revision_proplist(svn_fs_t *fs,
4379                      svn_revnum_t rev,
4380                      apr_hash_t *proplist,
4381                      apr_pool_t *pool)
4382{
4383  svn_boolean_t is_packed;
4384  svn_boolean_t bump_generation = FALSE;
4385  const char *final_path;
4386  const char *tmp_path;
4387  const char *perms_reference;
4388  apr_array_header_t *files_to_delete = NULL;
4389
4390  SVN_ERR(ensure_revision_exists(fs, rev, pool));
4391
4392  /* this info will not change while we hold the global FS write lock */
4393  is_packed = is_packed_revprop(fs, rev);
4394
4395  /* Test whether revprops already exist for this revision.
4396   * Only then will we need to bump the revprop generation. */
4397  if (has_revprop_cache(fs, pool))
4398    {
4399      if (is_packed)
4400        {
4401          bump_generation = TRUE;
4402        }
4403      else
4404        {
4405          svn_node_kind_t kind;
4406          SVN_ERR(svn_io_check_path(path_revprops(fs, rev, pool), &kind,
4407                                    pool));
4408          bump_generation = kind != svn_node_none;
4409        }
4410    }
4411
4412  /* Serialize the new revprop data */
4413  if (is_packed)
4414    SVN_ERR(write_packed_revprop(&final_path, &tmp_path, &files_to_delete,
4415                                 fs, rev, proplist, pool));
4416  else
4417    SVN_ERR(write_non_packed_revprop(&final_path, &tmp_path,
4418                                     fs, rev, proplist, pool));
4419
4420  /* We use the rev file of this revision as the perms reference,
4421   * because when setting revprops for the first time, the revprop
4422   * file won't exist and therefore can't serve as its own reference.
4423   * (Whereas the rev file should already exist at this point.)
4424   */
4425  SVN_ERR(svn_fs_fs__path_rev_absolute(&perms_reference, fs, rev, pool));
4426
4427  /* Now, switch to the new revprop data. */
4428  SVN_ERR(switch_to_new_revprop(fs, final_path, tmp_path, perms_reference,
4429                                files_to_delete, bump_generation, pool));
4430
4431  return SVN_NO_ERROR;
4432}
4433
4434svn_error_t *
4435svn_fs_fs__revision_proplist(apr_hash_t **proplist_p,
4436                             svn_fs_t *fs,
4437                             svn_revnum_t rev,
4438                             apr_pool_t *pool)
4439{
4440  SVN_ERR(get_revision_proplist(proplist_p, fs, rev, pool));
4441
4442  return SVN_NO_ERROR;
4443}
4444
4445/* Represents where in the current svndiff data block each
4446   representation is. */
4447struct rep_state
4448{
4449  apr_file_t *file;
4450                    /* The txdelta window cache to use or NULL. */
4451  svn_cache__t *window_cache;
4452                    /* Caches un-deltified windows. May be NULL. */
4453  svn_cache__t *combined_cache;
4454  apr_off_t start;  /* The starting offset for the raw
4455                       svndiff/plaintext data minus header. */
4456  apr_off_t off;    /* The current offset into the file. */
4457  apr_off_t end;    /* The end offset of the raw data. */
4458  int ver;          /* If a delta, what svndiff version? */
4459  int chunk_index;
4460};
4461
4462/* See create_rep_state, which wraps this and adds another error. */
4463static svn_error_t *
4464create_rep_state_body(struct rep_state **rep_state,
4465                      struct rep_args **rep_args,
4466                      apr_file_t **file_hint,
4467                      svn_revnum_t *rev_hint,
4468                      representation_t *rep,
4469                      svn_fs_t *fs,
4470                      apr_pool_t *pool)
4471{
4472  fs_fs_data_t *ffd = fs->fsap_data;
4473  struct rep_state *rs = apr_pcalloc(pool, sizeof(*rs));
4474  struct rep_args *ra;
4475  unsigned char buf[4];
4476
4477  /* If the hint is
4478   * - given,
4479   * - refers to a valid revision,
4480   * - refers to a packed revision,
4481   * - as does the rep we want to read, and
4482   * - refers to the same pack file as the rep
4483   * ...
4484   */
4485  if (   file_hint && rev_hint && *file_hint
4486      && SVN_IS_VALID_REVNUM(*rev_hint)
4487      && *rev_hint < ffd->min_unpacked_rev
4488      && rep->revision < ffd->min_unpacked_rev
4489      && (   (*rev_hint / ffd->max_files_per_dir)
4490          == (rep->revision / ffd->max_files_per_dir)))
4491    {
4492      /* ... we can re-use the same, already open file object
4493       */
4494      apr_off_t offset;
4495      SVN_ERR(get_packed_offset(&offset, fs, rep->revision, pool));
4496
4497      offset += rep->offset;
4498      SVN_ERR(svn_io_file_seek(*file_hint, APR_SET, &offset, pool));
4499
4500      rs->file = *file_hint;
4501    }
4502  else
4503    {
4504      /* otherwise, create a new file object
4505       */
4506      SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool));
4507    }
4508
4509  /* remember the current file, if suggested by the caller */
4510  if (file_hint)
4511    *file_hint = rs->file;
4512  if (rev_hint)
4513    *rev_hint = rep->revision;
4514
4515  /* continue constructing RS and RA */
4516  rs->window_cache = ffd->txdelta_window_cache;
4517  rs->combined_cache = ffd->combined_window_cache;
4518
4519  SVN_ERR(read_rep_line(&ra, rs->file, pool));
4520  SVN_ERR(get_file_offset(&rs->start, rs->file, pool));
4521  rs->off = rs->start;
4522  rs->end = rs->start + rep->size;
4523  *rep_state = rs;
4524  *rep_args = ra;
4525
4526  if (!ra->is_delta)
4527    /* This is a plaintext, so just return the current rep_state. */
4528    return SVN_NO_ERROR;
4529
4530  /* We are dealing with a delta, find out what version. */
4531  SVN_ERR(svn_io_file_read_full2(rs->file, buf, sizeof(buf),
4532                                 NULL, NULL, pool));
4533  /* ### Layering violation */
4534  if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N')))
4535    return svn_error_create
4536      (SVN_ERR_FS_CORRUPT, NULL,
4537       _("Malformed svndiff data in representation"));
4538  rs->ver = buf[3];
4539  rs->chunk_index = 0;
4540  rs->off += 4;
4541
4542  return SVN_NO_ERROR;
4543}
4544
4545/* Read the rep args for REP in filesystem FS and create a rep_state
4546   for reading the representation.  Return the rep_state in *REP_STATE
4547   and the rep args in *REP_ARGS, both allocated in POOL.
4548
4549   When reading multiple reps, i.e. a skip delta chain, you may provide
4550   non-NULL FILE_HINT and REV_HINT.  (If FILE_HINT is not NULL, in the first
4551   call it should be a pointer to NULL.)  The function will use these variables
4552   to store the previous call results and tries to re-use them.  This may
4553   result in significant savings in I/O for packed files.
4554 */
4555static svn_error_t *
4556create_rep_state(struct rep_state **rep_state,
4557                 struct rep_args **rep_args,
4558                 apr_file_t **file_hint,
4559                 svn_revnum_t *rev_hint,
4560                 representation_t *rep,
4561                 svn_fs_t *fs,
4562                 apr_pool_t *pool)
4563{
4564  svn_error_t *err = create_rep_state_body(rep_state, rep_args,
4565                                           file_hint, rev_hint,
4566                                           rep, fs, pool);
4567  if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
4568    {
4569      fs_fs_data_t *ffd = fs->fsap_data;
4570
4571      /* ### This always returns "-1" for transaction reps, because
4572         ### this particular bit of code doesn't know if the rep is
4573         ### stored in the protorev or in the mutable area (for props
4574         ### or dir contents).  It is pretty rare for FSFS to *read*
4575         ### from the protorev file, though, so this is probably OK.
4576         ### And anyone going to debug corruption errors is probably
4577         ### going to jump straight to this comment anyway! */
4578      return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
4579                               "Corrupt representation '%s'",
4580                               rep
4581                               ? representation_string(rep, ffd->format, TRUE,
4582                                                       TRUE, pool)
4583                               : "(null)");
4584    }
4585  /* ### Call representation_string() ? */
4586  return svn_error_trace(err);
4587}
4588
4589struct rep_read_baton
4590{
4591  /* The FS from which we're reading. */
4592  svn_fs_t *fs;
4593
4594  /* If not NULL, this is the base for the first delta window in rs_list */
4595  svn_stringbuf_t *base_window;
4596
4597  /* The state of all prior delta representations. */
4598  apr_array_header_t *rs_list;
4599
4600  /* The plaintext state, if there is a plaintext. */
4601  struct rep_state *src_state;
4602
4603  /* The index of the current delta chunk, if we are reading a delta. */
4604  int chunk_index;
4605
4606  /* The buffer where we store undeltified data. */
4607  char *buf;
4608  apr_size_t buf_pos;
4609  apr_size_t buf_len;
4610
4611  /* A checksum context for summing the data read in order to verify it.
4612     Note: we don't need to use the sha1 checksum because we're only doing
4613     data verification, for which md5 is perfectly safe.  */
4614  svn_checksum_ctx_t *md5_checksum_ctx;
4615
4616  svn_boolean_t checksum_finalized;
4617
4618  /* The stored checksum of the representation we are reading, its
4619     length, and the amount we've read so far.  Some of this
4620     information is redundant with rs_list and src_state, but it's
4621     convenient for the checksumming code to have it here. */
4622  svn_checksum_t *md5_checksum;
4623
4624  svn_filesize_t len;
4625  svn_filesize_t off;
4626
4627  /* The key for the fulltext cache for this rep, if there is a
4628     fulltext cache. */
4629  pair_cache_key_t fulltext_cache_key;
4630  /* The text we've been reading, if we're going to cache it. */
4631  svn_stringbuf_t *current_fulltext;
4632
4633  /* Used for temporary allocations during the read. */
4634  apr_pool_t *pool;
4635
4636  /* Pool used to store file handles and other data that is persistant
4637     for the entire stream read. */
4638  apr_pool_t *filehandle_pool;
4639};
4640
4641/* Combine the name of the rev file in RS with the given OFFSET to form
4642 * a cache lookup key.  Allocations will be made from POOL.  May return
4643 * NULL if the key cannot be constructed. */
4644static const char*
4645get_window_key(struct rep_state *rs, apr_off_t offset, apr_pool_t *pool)
4646{
4647  const char *name;
4648  const char *last_part;
4649  const char *name_last;
4650
4651  /* the rev file name containing the txdelta window.
4652   * If this fails we are in serious trouble anyways.
4653   * And if nobody else detects the problems, the file content checksum
4654   * comparison _will_ find them.
4655   */
4656  if (apr_file_name_get(&name, rs->file))
4657    return NULL;
4658
4659  /* Handle packed files as well by scanning backwards until we find the
4660   * revision or pack number. */
4661  name_last = name + strlen(name) - 1;
4662  while (! svn_ctype_isdigit(*name_last))
4663    --name_last;
4664
4665  last_part = name_last;
4666  while (svn_ctype_isdigit(*last_part))
4667    --last_part;
4668
4669  /* We must differentiate between packed files (as of today, the number
4670   * is being followed by a dot) and non-packed files (followed by \0).
4671   * Otherwise, there might be overlaps in the numbering range if the
4672   * repo gets packed after caching the txdeltas of non-packed revs.
4673   * => add the first non-digit char to the packed number. */
4674  if (name_last[1] != '\0')
4675    ++name_last;
4676
4677  /* copy one char MORE than the actual number to mark packed files,
4678   * i.e. packed revision file content uses different key space then
4679   * non-packed ones: keys for packed rev file content ends with a dot
4680   * for non-packed rev files they end with a digit. */
4681  name = apr_pstrndup(pool, last_part + 1, name_last - last_part);
4682  return svn_fs_fs__combine_number_and_string(offset, name, pool);
4683}
4684
4685/* Read the WINDOW_P for the rep state RS from the current FSFS session's
4686 * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
4687 * cache has been given. If a cache is available IS_CACHED will inform
4688 * the caller about the success of the lookup. Allocations (of the window
4689 * in particualar) will be made from POOL.
4690 *
4691 * If the information could be found, put RS and the position within the
4692 * rev file into the same state as if the data had just been read from it.
4693 */
4694static svn_error_t *
4695get_cached_window(svn_txdelta_window_t **window_p,
4696                  struct rep_state *rs,
4697                  svn_boolean_t *is_cached,
4698                  apr_pool_t *pool)
4699{
4700  if (! rs->window_cache)
4701    {
4702      /* txdelta window has not been enabled */
4703      *is_cached = FALSE;
4704    }
4705  else
4706    {
4707      /* ask the cache for the desired txdelta window */
4708      svn_fs_fs__txdelta_cached_window_t *cached_window;
4709      SVN_ERR(svn_cache__get((void **) &cached_window,
4710                             is_cached,
4711                             rs->window_cache,
4712                             get_window_key(rs, rs->off, pool),
4713                             pool));
4714
4715      if (*is_cached)
4716        {
4717          /* found it. Pass it back to the caller. */
4718          *window_p = cached_window->window;
4719
4720          /* manipulate the RS as if we just read the data */
4721          rs->chunk_index++;
4722          rs->off = cached_window->end_offset;
4723
4724          /* manipulate the rev file as if we just read from it */
4725          SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
4726        }
4727    }
4728
4729  return SVN_NO_ERROR;
4730}
4731
4732/* Store the WINDOW read at OFFSET for the rep state RS in the current
4733 * FSFS session's cache. This will be a no-op if no cache has been given.
4734 * Temporary allocations will be made from SCRATCH_POOL. */
4735static svn_error_t *
4736set_cached_window(svn_txdelta_window_t *window,
4737                  struct rep_state *rs,
4738                  apr_off_t offset,
4739                  apr_pool_t *scratch_pool)
4740{
4741  if (rs->window_cache)
4742    {
4743      /* store the window and the first offset _past_ it */
4744      svn_fs_fs__txdelta_cached_window_t cached_window;
4745
4746      cached_window.window = window;
4747      cached_window.end_offset = rs->off;
4748
4749      /* but key it with the start offset because that is the known state
4750       * when we will look it up */
4751      return svn_cache__set(rs->window_cache,
4752                            get_window_key(rs, offset, scratch_pool),
4753                            &cached_window,
4754                            scratch_pool);
4755    }
4756
4757  return SVN_NO_ERROR;
4758}
4759
4760/* Read the WINDOW_P for the rep state RS from the current FSFS session's
4761 * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
4762 * cache has been given. If a cache is available IS_CACHED will inform
4763 * the caller about the success of the lookup. Allocations (of the window
4764 * in particualar) will be made from POOL.
4765 */
4766static svn_error_t *
4767get_cached_combined_window(svn_stringbuf_t **window_p,
4768                           struct rep_state *rs,
4769                           svn_boolean_t *is_cached,
4770                           apr_pool_t *pool)
4771{
4772  if (! rs->combined_cache)
4773    {
4774      /* txdelta window has not been enabled */
4775      *is_cached = FALSE;
4776    }
4777  else
4778    {
4779      /* ask the cache for the desired txdelta window */
4780      return svn_cache__get((void **)window_p,
4781                            is_cached,
4782                            rs->combined_cache,
4783                            get_window_key(rs, rs->start, pool),
4784                            pool);
4785    }
4786
4787  return SVN_NO_ERROR;
4788}
4789
4790/* Store the WINDOW read at OFFSET for the rep state RS in the current
4791 * FSFS session's cache. This will be a no-op if no cache has been given.
4792 * Temporary allocations will be made from SCRATCH_POOL. */
4793static svn_error_t *
4794set_cached_combined_window(svn_stringbuf_t *window,
4795                           struct rep_state *rs,
4796                           apr_off_t offset,
4797                           apr_pool_t *scratch_pool)
4798{
4799  if (rs->combined_cache)
4800    {
4801      /* but key it with the start offset because that is the known state
4802       * when we will look it up */
4803      return svn_cache__set(rs->combined_cache,
4804                            get_window_key(rs, offset, scratch_pool),
4805                            window,
4806                            scratch_pool);
4807    }
4808
4809  return SVN_NO_ERROR;
4810}
4811
4812/* Build an array of rep_state structures in *LIST giving the delta
4813   reps from first_rep to a plain-text or self-compressed rep.  Set
4814   *SRC_STATE to the plain-text rep we find at the end of the chain,
4815   or to NULL if the final delta representation is self-compressed.
4816   The representation to start from is designated by filesystem FS, id
4817   ID, and representation REP.
4818   Also, set *WINDOW_P to the base window content for *LIST, if it
4819   could be found in cache. Otherwise, *LIST will contain the base
4820   representation for the whole delta chain.
4821   Finally, return the expanded size of the representation in
4822   *EXPANDED_SIZE. It will take care of cases where only the on-disk
4823   size is known.  */
4824static svn_error_t *
4825build_rep_list(apr_array_header_t **list,
4826               svn_stringbuf_t **window_p,
4827               struct rep_state **src_state,
4828               svn_filesize_t *expanded_size,
4829               svn_fs_t *fs,
4830               representation_t *first_rep,
4831               apr_pool_t *pool)
4832{
4833  representation_t rep;
4834  struct rep_state *rs = NULL;
4835  struct rep_args *rep_args;
4836  svn_boolean_t is_cached = FALSE;
4837  apr_file_t *last_file = NULL;
4838  svn_revnum_t last_revision;
4839
4840  *list = apr_array_make(pool, 1, sizeof(struct rep_state *));
4841  rep = *first_rep;
4842
4843  /* The value as stored in the data struct.
4844     0 is either for unknown length or actually zero length. */
4845  *expanded_size = first_rep->expanded_size;
4846
4847  /* for the top-level rep, we need the rep_args */
4848  SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
4849                           &last_revision, &rep, fs, pool));
4850
4851  /* Unknown size or empty representation?
4852     That implies the this being the first iteration.
4853     Usually size equals on-disk size, except for empty,
4854     compressed representations (delta, size = 4).
4855     Please note that for all non-empty deltas have
4856     a 4-byte header _plus_ some data. */
4857  if (*expanded_size == 0)
4858    if (! rep_args->is_delta || first_rep->size != 4)
4859      *expanded_size = first_rep->size;
4860
4861  while (1)
4862    {
4863      /* fetch state, if that has not been done already */
4864      if (!rs)
4865        SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
4866                                &last_revision, &rep, fs, pool));
4867
4868      SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool));
4869      if (is_cached)
4870        {
4871          /* We already have a reconstructed window in our cache.
4872             Write a pseudo rep_state with the full length. */
4873          rs->off = rs->start;
4874          rs->end = rs->start + (*window_p)->len;
4875          *src_state = rs;
4876          return SVN_NO_ERROR;
4877        }
4878
4879      if (!rep_args->is_delta)
4880        {
4881          /* This is a plaintext, so just return the current rep_state. */
4882          *src_state = rs;
4883          return SVN_NO_ERROR;
4884        }
4885
4886      /* Push this rep onto the list.  If it's self-compressed, we're done. */
4887      APR_ARRAY_PUSH(*list, struct rep_state *) = rs;
4888      if (rep_args->is_delta_vs_empty)
4889        {
4890          *src_state = NULL;
4891          return SVN_NO_ERROR;
4892        }
4893
4894      rep.revision = rep_args->base_revision;
4895      rep.offset = rep_args->base_offset;
4896      rep.size = rep_args->base_length;
4897      rep.txn_id = NULL;
4898
4899      rs = NULL;
4900    }
4901}
4902
4903
4904/* Create a rep_read_baton structure for node revision NODEREV in
4905   filesystem FS and store it in *RB_P.  If FULLTEXT_CACHE_KEY is not
4906   NULL, it is the rep's key in the fulltext cache, and a stringbuf
4907   must be allocated to store the text.  Perform all allocations in
4908   POOL.  If rep is mutable, it must be for file contents. */
4909static svn_error_t *
4910rep_read_get_baton(struct rep_read_baton **rb_p,
4911                   svn_fs_t *fs,
4912                   representation_t *rep,
4913                   pair_cache_key_t fulltext_cache_key,
4914                   apr_pool_t *pool)
4915{
4916  struct rep_read_baton *b;
4917
4918  b = apr_pcalloc(pool, sizeof(*b));
4919  b->fs = fs;
4920  b->base_window = NULL;
4921  b->chunk_index = 0;
4922  b->buf = NULL;
4923  b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
4924  b->checksum_finalized = FALSE;
4925  b->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
4926  b->len = rep->expanded_size;
4927  b->off = 0;
4928  b->fulltext_cache_key = fulltext_cache_key;
4929  b->pool = svn_pool_create(pool);
4930  b->filehandle_pool = svn_pool_create(pool);
4931
4932  SVN_ERR(build_rep_list(&b->rs_list, &b->base_window,
4933                         &b->src_state, &b->len, fs, rep,
4934                         b->filehandle_pool));
4935
4936  if (SVN_IS_VALID_REVNUM(fulltext_cache_key.revision))
4937    b->current_fulltext = svn_stringbuf_create_ensure
4938                            ((apr_size_t)b->len,
4939                             b->filehandle_pool);
4940  else
4941    b->current_fulltext = NULL;
4942
4943  /* Save our output baton. */
4944  *rb_p = b;
4945
4946  return SVN_NO_ERROR;
4947}
4948
4949/* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
4950   window into *NWIN. */
4951static svn_error_t *
4952read_delta_window(svn_txdelta_window_t **nwin, int this_chunk,
4953                  struct rep_state *rs, apr_pool_t *pool)
4954{
4955  svn_stream_t *stream;
4956  svn_boolean_t is_cached;
4957  apr_off_t old_offset;
4958
4959  SVN_ERR_ASSERT(rs->chunk_index <= this_chunk);
4960
4961  /* RS->FILE may be shared between RS instances -> make sure we point
4962   * to the right data. */
4963  SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
4964
4965  /* Skip windows to reach the current chunk if we aren't there yet. */
4966  while (rs->chunk_index < this_chunk)
4967    {
4968      SVN_ERR(svn_txdelta_skip_svndiff_window(rs->file, rs->ver, pool));
4969      rs->chunk_index++;
4970      SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
4971      if (rs->off >= rs->end)
4972        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4973                                _("Reading one svndiff window read "
4974                                  "beyond the end of the "
4975                                  "representation"));
4976    }
4977
4978  /* Read the next window. But first, try to find it in the cache. */
4979  SVN_ERR(get_cached_window(nwin, rs, &is_cached, pool));
4980  if (is_cached)
4981    return SVN_NO_ERROR;
4982
4983  /* Actually read the next window. */
4984  old_offset = rs->off;
4985  stream = svn_stream_from_aprfile2(rs->file, TRUE, pool);
4986  SVN_ERR(svn_txdelta_read_svndiff_window(nwin, stream, rs->ver, pool));
4987  rs->chunk_index++;
4988  SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
4989
4990  if (rs->off > rs->end)
4991    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4992                            _("Reading one svndiff window read beyond "
4993                              "the end of the representation"));
4994
4995  /* the window has not been cached before, thus cache it now
4996   * (if caching is used for them at all) */
4997  return set_cached_window(*nwin, rs, old_offset, pool);
4998}
4999
5000/* Read SIZE bytes from the representation RS and return it in *NWIN. */
5001static svn_error_t *
5002read_plain_window(svn_stringbuf_t **nwin, struct rep_state *rs,
5003                  apr_size_t size, apr_pool_t *pool)
5004{
5005  /* RS->FILE may be shared between RS instances -> make sure we point
5006   * to the right data. */
5007  SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
5008
5009  /* Read the plain data. */
5010  *nwin = svn_stringbuf_create_ensure(size, pool);
5011  SVN_ERR(svn_io_file_read_full2(rs->file, (*nwin)->data, size, NULL, NULL,
5012                                 pool));
5013  (*nwin)->data[size] = 0;
5014
5015  /* Update RS. */
5016  rs->off += (apr_off_t)size;
5017
5018  return SVN_NO_ERROR;
5019}
5020
5021/* Get the undeltified window that is a result of combining all deltas
5022   from the current desired representation identified in *RB with its
5023   base representation.  Store the window in *RESULT. */
5024static svn_error_t *
5025get_combined_window(svn_stringbuf_t **result,
5026                    struct rep_read_baton *rb)
5027{
5028  apr_pool_t *pool, *new_pool, *window_pool;
5029  int i;
5030  svn_txdelta_window_t *window;
5031  apr_array_header_t *windows;
5032  svn_stringbuf_t *source, *buf = rb->base_window;
5033  struct rep_state *rs;
5034
5035  /* Read all windows that we need to combine. This is fine because
5036     the size of each window is relatively small (100kB) and skip-
5037     delta limits the number of deltas in a chain to well under 100.
5038     Stop early if one of them does not depend on its predecessors. */
5039  window_pool = svn_pool_create(rb->pool);
5040  windows = apr_array_make(window_pool, 0, sizeof(svn_txdelta_window_t *));
5041  for (i = 0; i < rb->rs_list->nelts; ++i)
5042    {
5043      rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
5044      SVN_ERR(read_delta_window(&window, rb->chunk_index, rs, window_pool));
5045
5046      APR_ARRAY_PUSH(windows, svn_txdelta_window_t *) = window;
5047      if (window->src_ops == 0)
5048        {
5049          ++i;
5050          break;
5051        }
5052    }
5053
5054  /* Combine in the windows from the other delta reps. */
5055  pool = svn_pool_create(rb->pool);
5056  for (--i; i >= 0; --i)
5057    {
5058
5059      rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
5060      window = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *);
5061
5062      /* Maybe, we've got a PLAIN start representation.  If we do, read
5063         as much data from it as the needed for the txdelta window's source
5064         view.
5065         Note that BUF / SOURCE may only be NULL in the first iteration. */
5066      source = buf;
5067      if (source == NULL && rb->src_state != NULL)
5068        SVN_ERR(read_plain_window(&source, rb->src_state, window->sview_len,
5069                                  pool));
5070
5071      /* Combine this window with the current one. */
5072      new_pool = svn_pool_create(rb->pool);
5073      buf = svn_stringbuf_create_ensure(window->tview_len, new_pool);
5074      buf->len = window->tview_len;
5075
5076      svn_txdelta_apply_instructions(window, source ? source->data : NULL,
5077                                     buf->data, &buf->len);
5078      if (buf->len != window->tview_len)
5079        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5080                                _("svndiff window length is "
5081                                  "corrupt"));
5082
5083      /* Cache windows only if the whole rep content could be read as a
5084         single chunk.  Only then will no other chunk need a deeper RS
5085         list than the cached chunk. */
5086      if ((rb->chunk_index == 0) && (rs->off == rs->end))
5087        SVN_ERR(set_cached_combined_window(buf, rs, rs->start, new_pool));
5088
5089      /* Cycle pools so that we only need to hold three windows at a time. */
5090      svn_pool_destroy(pool);
5091      pool = new_pool;
5092    }
5093
5094  svn_pool_destroy(window_pool);
5095
5096  *result = buf;
5097  return SVN_NO_ERROR;
5098}
5099
5100/* Returns whether or not the expanded fulltext of the file is cachable
5101 * based on its size SIZE.  The decision depends on the cache used by RB.
5102 */
5103static svn_boolean_t
5104fulltext_size_is_cachable(fs_fs_data_t *ffd, svn_filesize_t size)
5105{
5106  return (size < APR_SIZE_MAX)
5107      && svn_cache__is_cachable(ffd->fulltext_cache, (apr_size_t)size);
5108}
5109
5110/* Close method used on streams returned by read_representation().
5111 */
5112static svn_error_t *
5113rep_read_contents_close(void *baton)
5114{
5115  struct rep_read_baton *rb = baton;
5116
5117  svn_pool_destroy(rb->pool);
5118  svn_pool_destroy(rb->filehandle_pool);
5119
5120  return SVN_NO_ERROR;
5121}
5122
5123/* Return the next *LEN bytes of the rep and store them in *BUF. */
5124static svn_error_t *
5125get_contents(struct rep_read_baton *rb,
5126             char *buf,
5127             apr_size_t *len)
5128{
5129  apr_size_t copy_len, remaining = *len;
5130  char *cur = buf;
5131  struct rep_state *rs;
5132
5133  /* Special case for when there are no delta reps, only a plain
5134     text. */
5135  if (rb->rs_list->nelts == 0)
5136    {
5137      copy_len = remaining;
5138      rs = rb->src_state;
5139
5140      if (rb->base_window != NULL)
5141        {
5142          /* We got the desired rep directly from the cache.
5143             This is where we need the pseudo rep_state created
5144             by build_rep_list(). */
5145          apr_size_t offset = (apr_size_t)(rs->off - rs->start);
5146          if (copy_len + offset > rb->base_window->len)
5147            copy_len = offset < rb->base_window->len
5148                     ? rb->base_window->len - offset
5149                     : 0ul;
5150
5151          memcpy (cur, rb->base_window->data + offset, copy_len);
5152        }
5153      else
5154        {
5155          if (((apr_off_t) copy_len) > rs->end - rs->off)
5156            copy_len = (apr_size_t) (rs->end - rs->off);
5157          SVN_ERR(svn_io_file_read_full2(rs->file, cur, copy_len, NULL,
5158                                         NULL, rb->pool));
5159        }
5160
5161      rs->off += copy_len;
5162      *len = copy_len;
5163      return SVN_NO_ERROR;
5164    }
5165
5166  while (remaining > 0)
5167    {
5168      /* If we have buffered data from a previous chunk, use that. */
5169      if (rb->buf)
5170        {
5171          /* Determine how much to copy from the buffer. */
5172          copy_len = rb->buf_len - rb->buf_pos;
5173          if (copy_len > remaining)
5174            copy_len = remaining;
5175
5176          /* Actually copy the data. */
5177          memcpy(cur, rb->buf + rb->buf_pos, copy_len);
5178          rb->buf_pos += copy_len;
5179          cur += copy_len;
5180          remaining -= copy_len;
5181
5182          /* If the buffer is all used up, clear it and empty the
5183             local pool. */
5184          if (rb->buf_pos == rb->buf_len)
5185            {
5186              svn_pool_clear(rb->pool);
5187              rb->buf = NULL;
5188            }
5189        }
5190      else
5191        {
5192          svn_stringbuf_t *sbuf = NULL;
5193
5194          rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *);
5195          if (rs->off == rs->end)
5196            break;
5197
5198          /* Get more buffered data by evaluating a chunk. */
5199          SVN_ERR(get_combined_window(&sbuf, rb));
5200
5201          rb->chunk_index++;
5202          rb->buf_len = sbuf->len;
5203          rb->buf = sbuf->data;
5204          rb->buf_pos = 0;
5205        }
5206    }
5207
5208  *len = cur - buf;
5209
5210  return SVN_NO_ERROR;
5211}
5212
5213/* BATON is of type `rep_read_baton'; read the next *LEN bytes of the
5214   representation and store them in *BUF.  Sum as we read and verify
5215   the MD5 sum at the end. */
5216static svn_error_t *
5217rep_read_contents(void *baton,
5218                  char *buf,
5219                  apr_size_t *len)
5220{
5221  struct rep_read_baton *rb = baton;
5222
5223  /* Get the next block of data. */
5224  SVN_ERR(get_contents(rb, buf, len));
5225
5226  if (rb->current_fulltext)
5227    svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len);
5228
5229  /* Perform checksumming.  We want to check the checksum as soon as
5230     the last byte of data is read, in case the caller never performs
5231     a short read, but we don't want to finalize the MD5 context
5232     twice. */
5233  if (!rb->checksum_finalized)
5234    {
5235      SVN_ERR(svn_checksum_update(rb->md5_checksum_ctx, buf, *len));
5236      rb->off += *len;
5237      if (rb->off == rb->len)
5238        {
5239          svn_checksum_t *md5_checksum;
5240
5241          rb->checksum_finalized = TRUE;
5242          SVN_ERR(svn_checksum_final(&md5_checksum, rb->md5_checksum_ctx,
5243                                     rb->pool));
5244          if (!svn_checksum_match(md5_checksum, rb->md5_checksum))
5245            return svn_error_create(SVN_ERR_FS_CORRUPT,
5246                    svn_checksum_mismatch_err(rb->md5_checksum, md5_checksum,
5247                        rb->pool,
5248                        _("Checksum mismatch while reading representation")),
5249                    NULL);
5250        }
5251    }
5252
5253  if (rb->off == rb->len && rb->current_fulltext)
5254    {
5255      fs_fs_data_t *ffd = rb->fs->fsap_data;
5256      SVN_ERR(svn_cache__set(ffd->fulltext_cache, &rb->fulltext_cache_key,
5257                             rb->current_fulltext, rb->pool));
5258      rb->current_fulltext = NULL;
5259    }
5260
5261  return SVN_NO_ERROR;
5262}
5263
5264
5265/* Return a stream in *CONTENTS_P that will read the contents of a
5266   representation stored at the location given by REP.  Appropriate
5267   for any kind of immutable representation, but only for file
5268   contents (not props or directory contents) in mutable
5269   representations.
5270
5271   If REP is NULL, the representation is assumed to be empty, and the
5272   empty stream is returned.
5273*/
5274static svn_error_t *
5275read_representation(svn_stream_t **contents_p,
5276                    svn_fs_t *fs,
5277                    representation_t *rep,
5278                    apr_pool_t *pool)
5279{
5280  if (! rep)
5281    {
5282      *contents_p = svn_stream_empty(pool);
5283    }
5284  else
5285    {
5286      fs_fs_data_t *ffd = fs->fsap_data;
5287      pair_cache_key_t fulltext_cache_key = { 0 };
5288      svn_filesize_t len = rep->expanded_size ? rep->expanded_size : rep->size;
5289      struct rep_read_baton *rb;
5290
5291      fulltext_cache_key.revision = rep->revision;
5292      fulltext_cache_key.second = rep->offset;
5293      if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
5294          && fulltext_size_is_cachable(ffd, len))
5295        {
5296          svn_stringbuf_t *fulltext;
5297          svn_boolean_t is_cached;
5298          SVN_ERR(svn_cache__get((void **) &fulltext, &is_cached,
5299                                 ffd->fulltext_cache, &fulltext_cache_key,
5300                                 pool));
5301          if (is_cached)
5302            {
5303              *contents_p = svn_stream_from_stringbuf(fulltext, pool);
5304              return SVN_NO_ERROR;
5305            }
5306        }
5307      else
5308        fulltext_cache_key.revision = SVN_INVALID_REVNUM;
5309
5310      SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool));
5311
5312      *contents_p = svn_stream_create(rb, pool);
5313      svn_stream_set_read(*contents_p, rep_read_contents);
5314      svn_stream_set_close(*contents_p, rep_read_contents_close);
5315    }
5316
5317  return SVN_NO_ERROR;
5318}
5319
5320svn_error_t *
5321svn_fs_fs__get_contents(svn_stream_t **contents_p,
5322                        svn_fs_t *fs,
5323                        node_revision_t *noderev,
5324                        apr_pool_t *pool)
5325{
5326  return read_representation(contents_p, fs, noderev->data_rep, pool);
5327}
5328
5329/* Baton used when reading delta windows. */
5330struct delta_read_baton
5331{
5332  struct rep_state *rs;
5333  svn_checksum_t *checksum;
5334};
5335
5336/* This implements the svn_txdelta_next_window_fn_t interface. */
5337static svn_error_t *
5338delta_read_next_window(svn_txdelta_window_t **window, void *baton,
5339                       apr_pool_t *pool)
5340{
5341  struct delta_read_baton *drb = baton;
5342
5343  if (drb->rs->off == drb->rs->end)
5344    {
5345      *window = NULL;
5346      return SVN_NO_ERROR;
5347    }
5348
5349  return read_delta_window(window, drb->rs->chunk_index, drb->rs, pool);
5350}
5351
5352/* This implements the svn_txdelta_md5_digest_fn_t interface. */
5353static const unsigned char *
5354delta_read_md5_digest(void *baton)
5355{
5356  struct delta_read_baton *drb = baton;
5357
5358  if (drb->checksum->kind == svn_checksum_md5)
5359    return drb->checksum->digest;
5360  else
5361    return NULL;
5362}
5363
5364svn_error_t *
5365svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
5366                                 svn_fs_t *fs,
5367                                 node_revision_t *source,
5368                                 node_revision_t *target,
5369                                 apr_pool_t *pool)
5370{
5371  svn_stream_t *source_stream, *target_stream;
5372
5373  /* Try a shortcut: if the target is stored as a delta against the source,
5374     then just use that delta. */
5375  if (source && source->data_rep && target->data_rep)
5376    {
5377      struct rep_state *rep_state;
5378      struct rep_args *rep_args;
5379
5380      /* Read target's base rep if any. */
5381      SVN_ERR(create_rep_state(&rep_state, &rep_args, NULL, NULL,
5382                               target->data_rep, fs, pool));
5383
5384      /* If that matches source, then use this delta as is.
5385         Note that we want an actual delta here.  E.g. a self-delta would
5386         not be good enough. */
5387      if (rep_args->is_delta
5388          && rep_args->base_revision == source->data_rep->revision
5389          && rep_args->base_offset == source->data_rep->offset)
5390        {
5391          /* Create the delta read baton. */
5392          struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb));
5393          drb->rs = rep_state;
5394          drb->checksum = svn_checksum_dup(target->data_rep->md5_checksum,
5395                                           pool);
5396          *stream_p = svn_txdelta_stream_create(drb, delta_read_next_window,
5397                                                delta_read_md5_digest, pool);
5398          return SVN_NO_ERROR;
5399        }
5400      else
5401        SVN_ERR(svn_io_file_close(rep_state->file, pool));
5402    }
5403
5404  /* Read both fulltexts and construct a delta. */
5405  if (source)
5406    SVN_ERR(read_representation(&source_stream, fs, source->data_rep, pool));
5407  else
5408    source_stream = svn_stream_empty(pool);
5409  SVN_ERR(read_representation(&target_stream, fs, target->data_rep, pool));
5410
5411  /* Because source and target stream will already verify their content,
5412   * there is no need to do this once more.  In particular if the stream
5413   * content is being fetched from cache. */
5414  svn_txdelta2(stream_p, source_stream, target_stream, FALSE, pool);
5415
5416  return SVN_NO_ERROR;
5417}
5418
5419/* Baton for cache_access_wrapper. Wraps the original parameters of
5420 * svn_fs_fs__try_process_file_content().
5421 */
5422typedef struct cache_access_wrapper_baton_t
5423{
5424  svn_fs_process_contents_func_t func;
5425  void* baton;
5426} cache_access_wrapper_baton_t;
5427
5428/* Wrapper to translate between svn_fs_process_contents_func_t and
5429 * svn_cache__partial_getter_func_t.
5430 */
5431static svn_error_t *
5432cache_access_wrapper(void **out,
5433                     const void *data,
5434                     apr_size_t data_len,
5435                     void *baton,
5436                     apr_pool_t *pool)
5437{
5438  cache_access_wrapper_baton_t *wrapper_baton = baton;
5439
5440  SVN_ERR(wrapper_baton->func((const unsigned char *)data,
5441                              data_len - 1, /* cache adds terminating 0 */
5442                              wrapper_baton->baton,
5443                              pool));
5444
5445  /* non-NULL value to signal the calling cache that all went well */
5446  *out = baton;
5447
5448  return SVN_NO_ERROR;
5449}
5450
5451svn_error_t *
5452svn_fs_fs__try_process_file_contents(svn_boolean_t *success,
5453                                     svn_fs_t *fs,
5454                                     node_revision_t *noderev,
5455                                     svn_fs_process_contents_func_t processor,
5456                                     void* baton,
5457                                     apr_pool_t *pool)
5458{
5459  representation_t *rep = noderev->data_rep;
5460  if (rep)
5461    {
5462      fs_fs_data_t *ffd = fs->fsap_data;
5463      pair_cache_key_t fulltext_cache_key = { 0 };
5464
5465      fulltext_cache_key.revision = rep->revision;
5466      fulltext_cache_key.second = rep->offset;
5467      if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
5468          && fulltext_size_is_cachable(ffd, rep->expanded_size))
5469        {
5470          cache_access_wrapper_baton_t wrapper_baton;
5471          void *dummy = NULL;
5472
5473          wrapper_baton.func = processor;
5474          wrapper_baton.baton = baton;
5475          return svn_cache__get_partial(&dummy, success,
5476                                        ffd->fulltext_cache,
5477                                        &fulltext_cache_key,
5478                                        cache_access_wrapper,
5479                                        &wrapper_baton,
5480                                        pool);
5481        }
5482    }
5483
5484  *success = FALSE;
5485  return SVN_NO_ERROR;
5486}
5487
5488/* Fetch the contents of a directory into ENTRIES.  Values are stored
5489   as filename to string mappings; further conversion is necessary to
5490   convert them into svn_fs_dirent_t values. */
5491static svn_error_t *
5492get_dir_contents(apr_hash_t *entries,
5493                 svn_fs_t *fs,
5494                 node_revision_t *noderev,
5495                 apr_pool_t *pool)
5496{
5497  svn_stream_t *contents;
5498
5499  if (noderev->data_rep && noderev->data_rep->txn_id)
5500    {
5501      const char *filename = path_txn_node_children(fs, noderev->id, pool);
5502
5503      /* The representation is mutable.  Read the old directory
5504         contents from the mutable children file, followed by the
5505         changes we've made in this transaction. */
5506      SVN_ERR(svn_stream_open_readonly(&contents, filename, pool, pool));
5507      SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
5508      SVN_ERR(svn_hash_read_incremental(entries, contents, NULL, pool));
5509      SVN_ERR(svn_stream_close(contents));
5510    }
5511  else if (noderev->data_rep)
5512    {
5513      /* use a temporary pool for temp objects.
5514       * Also undeltify content before parsing it. Otherwise, we could only
5515       * parse it byte-by-byte.
5516       */
5517      apr_pool_t *text_pool = svn_pool_create(pool);
5518      apr_size_t len = noderev->data_rep->expanded_size
5519                     ? (apr_size_t)noderev->data_rep->expanded_size
5520                     : (apr_size_t)noderev->data_rep->size;
5521      svn_stringbuf_t *text = svn_stringbuf_create_ensure(len, text_pool);
5522      text->len = len;
5523
5524      /* The representation is immutable.  Read it normally. */
5525      SVN_ERR(read_representation(&contents, fs, noderev->data_rep, text_pool));
5526      SVN_ERR(svn_stream_read(contents, text->data, &text->len));
5527      SVN_ERR(svn_stream_close(contents));
5528
5529      /* de-serialize hash */
5530      contents = svn_stream_from_stringbuf(text, text_pool);
5531      SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
5532
5533      svn_pool_destroy(text_pool);
5534    }
5535
5536  return SVN_NO_ERROR;
5537}
5538
5539
5540static const char *
5541unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id,
5542                  apr_pool_t *pool)
5543{
5544  return apr_psprintf(pool, "%s %s",
5545                      (kind == svn_node_file) ? KIND_FILE : KIND_DIR,
5546                      svn_fs_fs__id_unparse(id, pool)->data);
5547}
5548
5549/* Given a hash ENTRIES of dirent structions, return a hash in
5550   *STR_ENTRIES_P, that has svn_string_t as the values in the format
5551   specified by the fs_fs directory contents file.  Perform
5552   allocations in POOL. */
5553static svn_error_t *
5554unparse_dir_entries(apr_hash_t **str_entries_p,
5555                    apr_hash_t *entries,
5556                    apr_pool_t *pool)
5557{
5558  apr_hash_index_t *hi;
5559
5560  /* For now, we use a our own hash function to ensure that we get a
5561   * (largely) stable order when serializing the data.  It also gives
5562   * us some performance improvement.
5563   *
5564   * ### TODO ###
5565   * Use some sorted or other fixed order data container.
5566   */
5567  *str_entries_p = svn_hash__make(pool);
5568
5569  for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
5570    {
5571      const void *key;
5572      apr_ssize_t klen;
5573      svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
5574      const char *new_val;
5575
5576      apr_hash_this(hi, &key, &klen, NULL);
5577      new_val = unparse_dir_entry(dirent->kind, dirent->id, pool);
5578      apr_hash_set(*str_entries_p, key, klen,
5579                   svn_string_create(new_val, pool));
5580    }
5581
5582  return SVN_NO_ERROR;
5583}
5584
5585
5586/* Given a hash STR_ENTRIES with values as svn_string_t as specified
5587   in an FSFS directory contents listing, return a hash of dirents in
5588   *ENTRIES_P.  Perform allocations in POOL. */
5589static svn_error_t *
5590parse_dir_entries(apr_hash_t **entries_p,
5591                  apr_hash_t *str_entries,
5592                  const char *unparsed_id,
5593                  apr_pool_t *pool)
5594{
5595  apr_hash_index_t *hi;
5596
5597  *entries_p = apr_hash_make(pool);
5598
5599  /* Translate the string dir entries into real entries. */
5600  for (hi = apr_hash_first(pool, str_entries); hi; hi = apr_hash_next(hi))
5601    {
5602      const char *name = svn__apr_hash_index_key(hi);
5603      svn_string_t *str_val = svn__apr_hash_index_val(hi);
5604      char *str, *last_str;
5605      svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent));
5606
5607      last_str = apr_pstrdup(pool, str_val->data);
5608      dirent->name = apr_pstrdup(pool, name);
5609
5610      str = svn_cstring_tokenize(" ", &last_str);
5611      if (str == NULL)
5612        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5613                                 _("Directory entry corrupt in '%s'"),
5614                                 unparsed_id);
5615
5616      if (strcmp(str, KIND_FILE) == 0)
5617        {
5618          dirent->kind = svn_node_file;
5619        }
5620      else if (strcmp(str, KIND_DIR) == 0)
5621        {
5622          dirent->kind = svn_node_dir;
5623        }
5624      else
5625        {
5626          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5627                                   _("Directory entry corrupt in '%s'"),
5628                                   unparsed_id);
5629        }
5630
5631      str = svn_cstring_tokenize(" ", &last_str);
5632      if (str == NULL)
5633          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5634                                   _("Directory entry corrupt in '%s'"),
5635                                   unparsed_id);
5636
5637      dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool);
5638
5639      svn_hash_sets(*entries_p, dirent->name, dirent);
5640    }
5641
5642  return SVN_NO_ERROR;
5643}
5644
5645/* Return the cache object in FS responsible to storing the directory
5646 * the NODEREV. If none exists, return NULL. */
5647static svn_cache__t *
5648locate_dir_cache(svn_fs_t *fs,
5649                 node_revision_t *noderev)
5650{
5651  fs_fs_data_t *ffd = fs->fsap_data;
5652  return svn_fs_fs__id_txn_id(noderev->id)
5653      ? ffd->txn_dir_cache
5654      : ffd->dir_cache;
5655}
5656
5657svn_error_t *
5658svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
5659                            svn_fs_t *fs,
5660                            node_revision_t *noderev,
5661                            apr_pool_t *pool)
5662{
5663  const char *unparsed_id = NULL;
5664  apr_hash_t *unparsed_entries, *parsed_entries;
5665
5666  /* find the cache we may use */
5667  svn_cache__t *cache = locate_dir_cache(fs, noderev);
5668  if (cache)
5669    {
5670      svn_boolean_t found;
5671
5672      unparsed_id = svn_fs_fs__id_unparse(noderev->id, pool)->data;
5673      SVN_ERR(svn_cache__get((void **) entries_p, &found, cache,
5674                             unparsed_id, pool));
5675      if (found)
5676        return SVN_NO_ERROR;
5677    }
5678
5679  /* Read in the directory hash. */
5680  unparsed_entries = apr_hash_make(pool);
5681  SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool));
5682  SVN_ERR(parse_dir_entries(&parsed_entries, unparsed_entries,
5683                            unparsed_id, pool));
5684
5685  /* Update the cache, if we are to use one. */
5686  if (cache)
5687    SVN_ERR(svn_cache__set(cache, unparsed_id, parsed_entries, pool));
5688
5689  *entries_p = parsed_entries;
5690  return SVN_NO_ERROR;
5691}
5692
5693svn_error_t *
5694svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
5695                                  svn_fs_t *fs,
5696                                  node_revision_t *noderev,
5697                                  const char *name,
5698                                  apr_pool_t *result_pool,
5699                                  apr_pool_t *scratch_pool)
5700{
5701  svn_boolean_t found = FALSE;
5702
5703  /* find the cache we may use */
5704  svn_cache__t *cache = locate_dir_cache(fs, noderev);
5705  if (cache)
5706    {
5707      const char *unparsed_id =
5708        svn_fs_fs__id_unparse(noderev->id, scratch_pool)->data;
5709
5710      /* Cache lookup. */
5711      SVN_ERR(svn_cache__get_partial((void **)dirent,
5712                                     &found,
5713                                     cache,
5714                                     unparsed_id,
5715                                     svn_fs_fs__extract_dir_entry,
5716                                     (void*)name,
5717                                     result_pool));
5718    }
5719
5720  /* fetch data from disk if we did not find it in the cache */
5721  if (! found)
5722    {
5723      apr_hash_t *entries;
5724      svn_fs_dirent_t *entry;
5725      svn_fs_dirent_t *entry_copy = NULL;
5726
5727      /* read the dir from the file system. It will probably be put it
5728         into the cache for faster lookup in future calls. */
5729      SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev,
5730                                          scratch_pool));
5731
5732      /* find desired entry and return a copy in POOL, if found */
5733      entry = svn_hash_gets(entries, name);
5734      if (entry != NULL)
5735        {
5736          entry_copy = apr_palloc(result_pool, sizeof(*entry_copy));
5737          entry_copy->name = apr_pstrdup(result_pool, entry->name);
5738          entry_copy->id = svn_fs_fs__id_copy(entry->id, result_pool);
5739          entry_copy->kind = entry->kind;
5740        }
5741
5742      *dirent = entry_copy;
5743    }
5744
5745  return SVN_NO_ERROR;
5746}
5747
5748svn_error_t *
5749svn_fs_fs__get_proplist(apr_hash_t **proplist_p,
5750                        svn_fs_t *fs,
5751                        node_revision_t *noderev,
5752                        apr_pool_t *pool)
5753{
5754  apr_hash_t *proplist;
5755  svn_stream_t *stream;
5756
5757  if (noderev->prop_rep && noderev->prop_rep->txn_id)
5758    {
5759      const char *filename = path_txn_node_props(fs, noderev->id, pool);
5760      proplist = apr_hash_make(pool);
5761
5762      SVN_ERR(svn_stream_open_readonly(&stream, filename, pool, pool));
5763      SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
5764      SVN_ERR(svn_stream_close(stream));
5765    }
5766  else if (noderev->prop_rep)
5767    {
5768      fs_fs_data_t *ffd = fs->fsap_data;
5769      representation_t *rep = noderev->prop_rep;
5770      pair_cache_key_t key = { 0 };
5771
5772      key.revision = rep->revision;
5773      key.second = rep->offset;
5774      if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
5775        {
5776          svn_boolean_t is_cached;
5777          SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
5778                                 ffd->properties_cache, &key, pool));
5779          if (is_cached)
5780            return SVN_NO_ERROR;
5781        }
5782
5783      proplist = apr_hash_make(pool);
5784      SVN_ERR(read_representation(&stream, fs, noderev->prop_rep, pool));
5785      SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
5786      SVN_ERR(svn_stream_close(stream));
5787
5788      if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
5789        SVN_ERR(svn_cache__set(ffd->properties_cache, &key, proplist, pool));
5790    }
5791  else
5792    {
5793      /* return an empty prop list if the node doesn't have any props */
5794      proplist = apr_hash_make(pool);
5795    }
5796
5797  *proplist_p = proplist;
5798
5799  return SVN_NO_ERROR;
5800}
5801
5802svn_error_t *
5803svn_fs_fs__file_length(svn_filesize_t *length,
5804                       node_revision_t *noderev,
5805                       apr_pool_t *pool)
5806{
5807  if (noderev->data_rep)
5808    *length = noderev->data_rep->expanded_size;
5809  else
5810    *length = 0;
5811
5812  return SVN_NO_ERROR;
5813}
5814
5815svn_boolean_t
5816svn_fs_fs__noderev_same_rep_key(representation_t *a,
5817                                representation_t *b)
5818{
5819  if (a == b)
5820    return TRUE;
5821
5822  if (a == NULL || b == NULL)
5823    return FALSE;
5824
5825  if (a->offset != b->offset)
5826    return FALSE;
5827
5828  if (a->revision != b->revision)
5829    return FALSE;
5830
5831  if (a->uniquifier == b->uniquifier)
5832    return TRUE;
5833
5834  if (a->uniquifier == NULL || b->uniquifier == NULL)
5835    return FALSE;
5836
5837  return strcmp(a->uniquifier, b->uniquifier) == 0;
5838}
5839
5840svn_error_t *
5841svn_fs_fs__file_checksum(svn_checksum_t **checksum,
5842                         node_revision_t *noderev,
5843                         svn_checksum_kind_t kind,
5844                         apr_pool_t *pool)
5845{
5846  if (noderev->data_rep)
5847    {
5848      switch(kind)
5849        {
5850          case svn_checksum_md5:
5851            *checksum = svn_checksum_dup(noderev->data_rep->md5_checksum,
5852                                         pool);
5853            break;
5854          case svn_checksum_sha1:
5855            *checksum = svn_checksum_dup(noderev->data_rep->sha1_checksum,
5856                                         pool);
5857            break;
5858          default:
5859            *checksum = NULL;
5860        }
5861    }
5862  else
5863    *checksum = NULL;
5864
5865  return SVN_NO_ERROR;
5866}
5867
5868representation_t *
5869svn_fs_fs__rep_copy(representation_t *rep,
5870                    apr_pool_t *pool)
5871{
5872  representation_t *rep_new;
5873
5874  if (rep == NULL)
5875    return NULL;
5876
5877  rep_new = apr_pcalloc(pool, sizeof(*rep_new));
5878
5879  memcpy(rep_new, rep, sizeof(*rep_new));
5880  rep_new->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
5881  rep_new->sha1_checksum = svn_checksum_dup(rep->sha1_checksum, pool);
5882  rep_new->uniquifier = apr_pstrdup(pool, rep->uniquifier);
5883
5884  return rep_new;
5885}
5886
5887/* Merge the internal-use-only CHANGE into a hash of public-FS
5888   svn_fs_path_change2_t CHANGES, collapsing multiple changes into a
5889   single summarical (is that real word?) change per path.  Also keep
5890   the COPYFROM_CACHE up to date with new adds and replaces.  */
5891static svn_error_t *
5892fold_change(apr_hash_t *changes,
5893            const change_t *change,
5894            apr_hash_t *copyfrom_cache)
5895{
5896  apr_pool_t *pool = apr_hash_pool_get(changes);
5897  svn_fs_path_change2_t *old_change, *new_change;
5898  const char *path;
5899  apr_size_t path_len = strlen(change->path);
5900
5901  if ((old_change = apr_hash_get(changes, change->path, path_len)))
5902    {
5903      /* This path already exists in the hash, so we have to merge
5904         this change into the already existing one. */
5905
5906      /* Sanity check:  only allow NULL node revision ID in the
5907         `reset' case. */
5908      if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset))
5909        return svn_error_create
5910          (SVN_ERR_FS_CORRUPT, NULL,
5911           _("Missing required node revision ID"));
5912
5913      /* Sanity check: we should be talking about the same node
5914         revision ID as our last change except where the last change
5915         was a deletion. */
5916      if (change->noderev_id
5917          && (! svn_fs_fs__id_eq(old_change->node_rev_id, change->noderev_id))
5918          && (old_change->change_kind != svn_fs_path_change_delete))
5919        return svn_error_create
5920          (SVN_ERR_FS_CORRUPT, NULL,
5921           _("Invalid change ordering: new node revision ID "
5922             "without delete"));
5923
5924      /* Sanity check: an add, replacement, or reset must be the first
5925         thing to follow a deletion. */
5926      if ((old_change->change_kind == svn_fs_path_change_delete)
5927          && (! ((change->kind == svn_fs_path_change_replace)
5928                 || (change->kind == svn_fs_path_change_reset)
5929                 || (change->kind == svn_fs_path_change_add))))
5930        return svn_error_create
5931          (SVN_ERR_FS_CORRUPT, NULL,
5932           _("Invalid change ordering: non-add change on deleted path"));
5933
5934      /* Sanity check: an add can't follow anything except
5935         a delete or reset.  */
5936      if ((change->kind == svn_fs_path_change_add)
5937          && (old_change->change_kind != svn_fs_path_change_delete)
5938          && (old_change->change_kind != svn_fs_path_change_reset))
5939        return svn_error_create
5940          (SVN_ERR_FS_CORRUPT, NULL,
5941           _("Invalid change ordering: add change on preexisting path"));
5942
5943      /* Now, merge that change in. */
5944      switch (change->kind)
5945        {
5946        case svn_fs_path_change_reset:
5947          /* A reset here will simply remove the path change from the
5948             hash. */
5949          old_change = NULL;
5950          break;
5951
5952        case svn_fs_path_change_delete:
5953          if (old_change->change_kind == svn_fs_path_change_add)
5954            {
5955              /* If the path was introduced in this transaction via an
5956                 add, and we are deleting it, just remove the path
5957                 altogether. */
5958              old_change = NULL;
5959            }
5960          else
5961            {
5962              /* A deletion overrules all previous changes. */
5963              old_change->change_kind = svn_fs_path_change_delete;
5964              old_change->text_mod = change->text_mod;
5965              old_change->prop_mod = change->prop_mod;
5966              old_change->copyfrom_rev = SVN_INVALID_REVNUM;
5967              old_change->copyfrom_path = NULL;
5968            }
5969          break;
5970
5971        case svn_fs_path_change_add:
5972        case svn_fs_path_change_replace:
5973          /* An add at this point must be following a previous delete,
5974             so treat it just like a replace. */
5975          old_change->change_kind = svn_fs_path_change_replace;
5976          old_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id,
5977                                                       pool);
5978          old_change->text_mod = change->text_mod;
5979          old_change->prop_mod = change->prop_mod;
5980          if (change->copyfrom_rev == SVN_INVALID_REVNUM)
5981            {
5982              old_change->copyfrom_rev = SVN_INVALID_REVNUM;
5983              old_change->copyfrom_path = NULL;
5984            }
5985          else
5986            {
5987              old_change->copyfrom_rev = change->copyfrom_rev;
5988              old_change->copyfrom_path = apr_pstrdup(pool,
5989                                                      change->copyfrom_path);
5990            }
5991          break;
5992
5993        case svn_fs_path_change_modify:
5994        default:
5995          if (change->text_mod)
5996            old_change->text_mod = TRUE;
5997          if (change->prop_mod)
5998            old_change->prop_mod = TRUE;
5999          break;
6000        }
6001
6002      /* Point our new_change to our (possibly modified) old_change. */
6003      new_change = old_change;
6004    }
6005  else
6006    {
6007      /* This change is new to the hash, so make a new public change
6008         structure from the internal one (in the hash's pool), and dup
6009         the path into the hash's pool, too. */
6010      new_change = apr_pcalloc(pool, sizeof(*new_change));
6011      new_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id, pool);
6012      new_change->change_kind = change->kind;
6013      new_change->text_mod = change->text_mod;
6014      new_change->prop_mod = change->prop_mod;
6015      /* In FSFS, copyfrom_known is *always* true, since we've always
6016       * stored copyfroms in changed paths lists. */
6017      new_change->copyfrom_known = TRUE;
6018      if (change->copyfrom_rev != SVN_INVALID_REVNUM)
6019        {
6020          new_change->copyfrom_rev = change->copyfrom_rev;
6021          new_change->copyfrom_path = apr_pstrdup(pool, change->copyfrom_path);
6022        }
6023      else
6024        {
6025          new_change->copyfrom_rev = SVN_INVALID_REVNUM;
6026          new_change->copyfrom_path = NULL;
6027        }
6028    }
6029
6030  if (new_change)
6031    new_change->node_kind = change->node_kind;
6032
6033  /* Add (or update) this path.
6034
6035     Note: this key might already be present, and it would be nice to
6036     re-use its value, but there is no way to fetch it. The API makes no
6037     guarantees that this (new) key will not be retained. Thus, we (again)
6038     copy the key into the target pool to ensure a proper lifetime.  */
6039  path = apr_pstrmemdup(pool, change->path, path_len);
6040  apr_hash_set(changes, path, path_len, new_change);
6041
6042  /* Update the copyfrom cache, if any. */
6043  if (copyfrom_cache)
6044    {
6045      apr_pool_t *copyfrom_pool = apr_hash_pool_get(copyfrom_cache);
6046      const char *copyfrom_string = NULL, *copyfrom_key = path;
6047      if (new_change)
6048        {
6049          if (SVN_IS_VALID_REVNUM(new_change->copyfrom_rev))
6050            copyfrom_string = apr_psprintf(copyfrom_pool, "%ld %s",
6051                                           new_change->copyfrom_rev,
6052                                           new_change->copyfrom_path);
6053          else
6054            copyfrom_string = "";
6055        }
6056      /* We need to allocate a copy of the key in the copyfrom_pool if
6057       * we're not doing a deletion and if it isn't already there. */
6058      if (   copyfrom_string
6059          && (   ! apr_hash_count(copyfrom_cache)
6060              || ! apr_hash_get(copyfrom_cache, copyfrom_key, path_len)))
6061        copyfrom_key = apr_pstrmemdup(copyfrom_pool, copyfrom_key, path_len);
6062
6063      apr_hash_set(copyfrom_cache, copyfrom_key, path_len,
6064                   copyfrom_string);
6065    }
6066
6067  return SVN_NO_ERROR;
6068}
6069
6070/* The 256 is an arbitrary size large enough to hold the node id and the
6071 * various flags. */
6072#define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
6073
6074/* Read the next entry in the changes record from file FILE and store
6075   the resulting change in *CHANGE_P.  If there is no next record,
6076   store NULL there.  Perform all allocations from POOL. */
6077static svn_error_t *
6078read_change(change_t **change_p,
6079            apr_file_t *file,
6080            apr_pool_t *pool)
6081{
6082  char buf[MAX_CHANGE_LINE_LEN];
6083  apr_size_t len = sizeof(buf);
6084  change_t *change;
6085  char *str, *last_str = buf, *kind_str;
6086  svn_error_t *err;
6087
6088  /* Default return value. */
6089  *change_p = NULL;
6090
6091  err = svn_io_read_length_line(file, buf, &len, pool);
6092
6093  /* Check for a blank line. */
6094  if (err || (len == 0))
6095    {
6096      if (err && APR_STATUS_IS_EOF(err->apr_err))
6097        {
6098          svn_error_clear(err);
6099          return SVN_NO_ERROR;
6100        }
6101      if ((len == 0) && (! err))
6102        return SVN_NO_ERROR;
6103      return svn_error_trace(err);
6104    }
6105
6106  change = apr_pcalloc(pool, sizeof(*change));
6107
6108  /* Get the node-id of the change. */
6109  str = svn_cstring_tokenize(" ", &last_str);
6110  if (str == NULL)
6111    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6112                            _("Invalid changes line in rev-file"));
6113
6114  change->noderev_id = svn_fs_fs__id_parse(str, strlen(str), pool);
6115  if (change->noderev_id == NULL)
6116    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6117                            _("Invalid changes line in rev-file"));
6118
6119  /* Get the change type. */
6120  str = svn_cstring_tokenize(" ", &last_str);
6121  if (str == NULL)
6122    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6123                            _("Invalid changes line in rev-file"));
6124
6125  /* Don't bother to check the format number before looking for
6126   * node-kinds: just read them if you find them. */
6127  change->node_kind = svn_node_unknown;
6128  kind_str = strchr(str, '-');
6129  if (kind_str)
6130    {
6131      /* Cap off the end of "str" (the action). */
6132      *kind_str = '\0';
6133      kind_str++;
6134      if (strcmp(kind_str, KIND_FILE) == 0)
6135        change->node_kind = svn_node_file;
6136      else if (strcmp(kind_str, KIND_DIR) == 0)
6137        change->node_kind = svn_node_dir;
6138      else
6139        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6140                                _("Invalid changes line in rev-file"));
6141    }
6142
6143  if (strcmp(str, ACTION_MODIFY) == 0)
6144    {
6145      change->kind = svn_fs_path_change_modify;
6146    }
6147  else if (strcmp(str, ACTION_ADD) == 0)
6148    {
6149      change->kind = svn_fs_path_change_add;
6150    }
6151  else if (strcmp(str, ACTION_DELETE) == 0)
6152    {
6153      change->kind = svn_fs_path_change_delete;
6154    }
6155  else if (strcmp(str, ACTION_REPLACE) == 0)
6156    {
6157      change->kind = svn_fs_path_change_replace;
6158    }
6159  else if (strcmp(str, ACTION_RESET) == 0)
6160    {
6161      change->kind = svn_fs_path_change_reset;
6162    }
6163  else
6164    {
6165      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6166                              _("Invalid change kind in rev file"));
6167    }
6168
6169  /* Get the text-mod flag. */
6170  str = svn_cstring_tokenize(" ", &last_str);
6171  if (str == NULL)
6172    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6173                            _("Invalid changes line in rev-file"));
6174
6175  if (strcmp(str, FLAG_TRUE) == 0)
6176    {
6177      change->text_mod = TRUE;
6178    }
6179  else if (strcmp(str, FLAG_FALSE) == 0)
6180    {
6181      change->text_mod = FALSE;
6182    }
6183  else
6184    {
6185      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6186                              _("Invalid text-mod flag in rev-file"));
6187    }
6188
6189  /* Get the prop-mod flag. */
6190  str = svn_cstring_tokenize(" ", &last_str);
6191  if (str == NULL)
6192    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6193                            _("Invalid changes line in rev-file"));
6194
6195  if (strcmp(str, FLAG_TRUE) == 0)
6196    {
6197      change->prop_mod = TRUE;
6198    }
6199  else if (strcmp(str, FLAG_FALSE) == 0)
6200    {
6201      change->prop_mod = FALSE;
6202    }
6203  else
6204    {
6205      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6206                              _("Invalid prop-mod flag in rev-file"));
6207    }
6208
6209  /* Get the changed path. */
6210  change->path = apr_pstrdup(pool, last_str);
6211
6212
6213  /* Read the next line, the copyfrom line. */
6214  len = sizeof(buf);
6215  SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
6216
6217  if (len == 0)
6218    {
6219      change->copyfrom_rev = SVN_INVALID_REVNUM;
6220      change->copyfrom_path = NULL;
6221    }
6222  else
6223    {
6224      last_str = buf;
6225      str = svn_cstring_tokenize(" ", &last_str);
6226      if (! str)
6227        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6228                                _("Invalid changes line in rev-file"));
6229      change->copyfrom_rev = SVN_STR_TO_REV(str);
6230
6231      if (! last_str)
6232        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6233                                _("Invalid changes line in rev-file"));
6234
6235      change->copyfrom_path = apr_pstrdup(pool, last_str);
6236    }
6237
6238  *change_p = change;
6239
6240  return SVN_NO_ERROR;
6241}
6242
6243/* Examine all the changed path entries in CHANGES and store them in
6244   *CHANGED_PATHS.  Folding is done to remove redundant or unnecessary
6245   *data.  Store a hash of paths to copyfrom "REV PATH" strings in
6246   COPYFROM_HASH if it is non-NULL.  If PREFOLDED is true, assume that
6247   the changed-path entries have already been folded (by
6248   write_final_changed_path_info) and may be out of order, so we shouldn't
6249   remove children of replaced or deleted directories.  Do all
6250   allocations in POOL. */
6251static svn_error_t *
6252process_changes(apr_hash_t *changed_paths,
6253                apr_hash_t *copyfrom_cache,
6254                apr_array_header_t *changes,
6255                svn_boolean_t prefolded,
6256                apr_pool_t *pool)
6257{
6258  apr_pool_t *iterpool = svn_pool_create(pool);
6259  int i;
6260
6261  /* Read in the changes one by one, folding them into our local hash
6262     as necessary. */
6263
6264  for (i = 0; i < changes->nelts; ++i)
6265    {
6266      change_t *change = APR_ARRAY_IDX(changes, i, change_t *);
6267
6268      SVN_ERR(fold_change(changed_paths, change, copyfrom_cache));
6269
6270      /* Now, if our change was a deletion or replacement, we have to
6271         blow away any changes thus far on paths that are (or, were)
6272         children of this path.
6273         ### i won't bother with another iteration pool here -- at
6274         most we talking about a few extra dups of paths into what
6275         is already a temporary subpool.
6276      */
6277
6278      if (((change->kind == svn_fs_path_change_delete)
6279           || (change->kind == svn_fs_path_change_replace))
6280          && ! prefolded)
6281        {
6282          apr_hash_index_t *hi;
6283
6284          /* a potential child path must contain at least 2 more chars
6285             (the path separator plus at least one char for the name).
6286             Also, we should not assume that all paths have been normalized
6287             i.e. some might have trailing path separators.
6288          */
6289          apr_ssize_t change_path_len = strlen(change->path);
6290          apr_ssize_t min_child_len = change_path_len == 0
6291                                    ? 1
6292                                    : change->path[change_path_len-1] == '/'
6293                                        ? change_path_len + 1
6294                                        : change_path_len + 2;
6295
6296          /* CAUTION: This is the inner loop of an O(n^2) algorithm.
6297             The number of changes to process may be >> 1000.
6298             Therefore, keep the inner loop as tight as possible.
6299          */
6300          for (hi = apr_hash_first(iterpool, changed_paths);
6301               hi;
6302               hi = apr_hash_next(hi))
6303            {
6304              /* KEY is the path. */
6305              const void *path;
6306              apr_ssize_t klen;
6307              apr_hash_this(hi, &path, &klen, NULL);
6308
6309              /* If we come across a child of our path, remove it.
6310                 Call svn_dirent_is_child only if there is a chance that
6311                 this is actually a sub-path.
6312               */
6313              if (   klen >= min_child_len
6314                  && svn_dirent_is_child(change->path, path, iterpool))
6315                apr_hash_set(changed_paths, path, klen, NULL);
6316            }
6317        }
6318
6319      /* Clear the per-iteration subpool. */
6320      svn_pool_clear(iterpool);
6321    }
6322
6323  /* Destroy the per-iteration subpool. */
6324  svn_pool_destroy(iterpool);
6325
6326  return SVN_NO_ERROR;
6327}
6328
6329/* Fetch all the changes from FILE and store them in *CHANGES.  Do all
6330   allocations in POOL. */
6331static svn_error_t *
6332read_all_changes(apr_array_header_t **changes,
6333                 apr_file_t *file,
6334                 apr_pool_t *pool)
6335{
6336  change_t *change;
6337
6338  /* pre-allocate enough room for most change lists
6339     (will be auto-expanded as necessary) */
6340  *changes = apr_array_make(pool, 30, sizeof(change_t *));
6341
6342  SVN_ERR(read_change(&change, file, pool));
6343  while (change)
6344    {
6345      APR_ARRAY_PUSH(*changes, change_t*) = change;
6346      SVN_ERR(read_change(&change, file, pool));
6347    }
6348
6349  return SVN_NO_ERROR;
6350}
6351
6352svn_error_t *
6353svn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p,
6354                             svn_fs_t *fs,
6355                             const char *txn_id,
6356                             apr_pool_t *pool)
6357{
6358  apr_file_t *file;
6359  apr_hash_t *changed_paths = apr_hash_make(pool);
6360  apr_array_header_t *changes;
6361  apr_pool_t *scratch_pool = svn_pool_create(pool);
6362
6363  SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
6364                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
6365
6366  SVN_ERR(read_all_changes(&changes, file, scratch_pool));
6367  SVN_ERR(process_changes(changed_paths, NULL, changes, FALSE, pool));
6368  svn_pool_destroy(scratch_pool);
6369
6370  SVN_ERR(svn_io_file_close(file, pool));
6371
6372  *changed_paths_p = changed_paths;
6373
6374  return SVN_NO_ERROR;
6375}
6376
6377/* Fetch the list of change in revision REV in FS and return it in *CHANGES.
6378 * Allocate the result in POOL.
6379 */
6380static svn_error_t *
6381get_changes(apr_array_header_t **changes,
6382            svn_fs_t *fs,
6383            svn_revnum_t rev,
6384            apr_pool_t *pool)
6385{
6386  apr_off_t changes_offset;
6387  apr_file_t *revision_file;
6388  svn_boolean_t found;
6389  fs_fs_data_t *ffd = fs->fsap_data;
6390
6391  /* try cache lookup first */
6392
6393  if (ffd->changes_cache)
6394    {
6395      SVN_ERR(svn_cache__get((void **) changes, &found, ffd->changes_cache,
6396                             &rev, pool));
6397      if (found)
6398        return SVN_NO_ERROR;
6399    }
6400
6401  /* read changes from revision file */
6402
6403  SVN_ERR(ensure_revision_exists(fs, rev, pool));
6404
6405  SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool));
6406
6407  SVN_ERR(get_root_changes_offset(NULL, &changes_offset, revision_file, fs,
6408                                  rev, pool));
6409
6410  SVN_ERR(svn_io_file_seek(revision_file, APR_SET, &changes_offset, pool));
6411  SVN_ERR(read_all_changes(changes, revision_file, pool));
6412
6413  SVN_ERR(svn_io_file_close(revision_file, pool));
6414
6415  /* cache for future reference */
6416
6417  if (ffd->changes_cache)
6418    SVN_ERR(svn_cache__set(ffd->changes_cache, &rev, *changes, pool));
6419
6420  return SVN_NO_ERROR;
6421}
6422
6423
6424svn_error_t *
6425svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
6426                         svn_fs_t *fs,
6427                         svn_revnum_t rev,
6428                         apr_hash_t *copyfrom_cache,
6429                         apr_pool_t *pool)
6430{
6431  apr_hash_t *changed_paths;
6432  apr_array_header_t *changes;
6433  apr_pool_t *scratch_pool = svn_pool_create(pool);
6434
6435  SVN_ERR(get_changes(&changes, fs, rev, scratch_pool));
6436
6437  changed_paths = svn_hash__make(pool);
6438
6439  SVN_ERR(process_changes(changed_paths, copyfrom_cache, changes,
6440                          TRUE, pool));
6441  svn_pool_destroy(scratch_pool);
6442
6443  *changed_paths_p = changed_paths;
6444
6445  return SVN_NO_ERROR;
6446}
6447
6448/* Copy a revision node-rev SRC into the current transaction TXN_ID in
6449   the filesystem FS.  This is only used to create the root of a transaction.
6450   Allocations are from POOL.  */
6451static svn_error_t *
6452create_new_txn_noderev_from_rev(svn_fs_t *fs,
6453                                const char *txn_id,
6454                                svn_fs_id_t *src,
6455                                apr_pool_t *pool)
6456{
6457  node_revision_t *noderev;
6458  const char *node_id, *copy_id;
6459
6460  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool));
6461
6462  if (svn_fs_fs__id_txn_id(noderev->id))
6463    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6464                            _("Copying from transactions not allowed"));
6465
6466  noderev->predecessor_id = noderev->id;
6467  noderev->predecessor_count++;
6468  noderev->copyfrom_path = NULL;
6469  noderev->copyfrom_rev = SVN_INVALID_REVNUM;
6470
6471  /* For the transaction root, the copyroot never changes. */
6472
6473  node_id = svn_fs_fs__id_node_id(noderev->id);
6474  copy_id = svn_fs_fs__id_copy_id(noderev->id);
6475  noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
6476
6477  return svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool);
6478}
6479
6480/* A structure used by get_and_increment_txn_key_body(). */
6481struct get_and_increment_txn_key_baton {
6482  svn_fs_t *fs;
6483  char *txn_id;
6484  apr_pool_t *pool;
6485};
6486
6487/* Callback used in the implementation of create_txn_dir().  This gets
6488   the current base 36 value in PATH_TXN_CURRENT and increments it.
6489   It returns the original value by the baton. */
6490static svn_error_t *
6491get_and_increment_txn_key_body(void *baton, apr_pool_t *pool)
6492{
6493  struct get_and_increment_txn_key_baton *cb = baton;
6494  const char *txn_current_filename = path_txn_current(cb->fs, pool);
6495  const char *tmp_filename;
6496  char next_txn_id[MAX_KEY_SIZE+3];
6497  apr_size_t len;
6498
6499  svn_stringbuf_t *buf;
6500  SVN_ERR(read_content(&buf, txn_current_filename, cb->pool));
6501
6502  /* remove trailing newlines */
6503  svn_stringbuf_strip_whitespace(buf);
6504  cb->txn_id = buf->data;
6505  len = buf->len;
6506
6507  /* Increment the key and add a trailing \n to the string so the
6508     txn-current file has a newline in it. */
6509  svn_fs_fs__next_key(cb->txn_id, &len, next_txn_id);
6510  next_txn_id[len] = '\n';
6511  ++len;
6512  next_txn_id[len] = '\0';
6513
6514  SVN_ERR(svn_io_write_unique(&tmp_filename,
6515                              svn_dirent_dirname(txn_current_filename, pool),
6516                              next_txn_id, len, svn_io_file_del_none, pool));
6517  SVN_ERR(move_into_place(tmp_filename, txn_current_filename,
6518                          txn_current_filename, pool));
6519
6520  return SVN_NO_ERROR;
6521}
6522
6523/* Create a unique directory for a transaction in FS based on revision
6524   REV.  Return the ID for this transaction in *ID_P.  Use a sequence
6525   value in the transaction ID to prevent reuse of transaction IDs. */
6526static svn_error_t *
6527create_txn_dir(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
6528               apr_pool_t *pool)
6529{
6530  struct get_and_increment_txn_key_baton cb;
6531  const char *txn_dir;
6532
6533  /* Get the current transaction sequence value, which is a base-36
6534     number, from the txn-current file, and write an
6535     incremented value back out to the file.  Place the revision
6536     number the transaction is based off into the transaction id. */
6537  cb.pool = pool;
6538  cb.fs = fs;
6539  SVN_ERR(with_txn_current_lock(fs,
6540                                get_and_increment_txn_key_body,
6541                                &cb,
6542                                pool));
6543  *id_p = apr_psprintf(pool, "%ld-%s", rev, cb.txn_id);
6544
6545  txn_dir = svn_dirent_join_many(pool,
6546                                 fs->path,
6547                                 PATH_TXNS_DIR,
6548                                 apr_pstrcat(pool, *id_p, PATH_EXT_TXN,
6549                                             (char *)NULL),
6550                                 NULL);
6551
6552  return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool);
6553}
6554
6555/* Create a unique directory for a transaction in FS based on revision
6556   REV.  Return the ID for this transaction in *ID_P.  This
6557   implementation is used in svn 1.4 and earlier repositories and is
6558   kept in 1.5 and greater to support the --pre-1.4-compatible and
6559   --pre-1.5-compatible repository creation options.  Reused
6560   transaction IDs are possible with this implementation. */
6561static svn_error_t *
6562create_txn_dir_pre_1_5(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
6563                       apr_pool_t *pool)
6564{
6565  unsigned int i;
6566  apr_pool_t *subpool;
6567  const char *unique_path, *prefix;
6568
6569  /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
6570  prefix = svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR,
6571                                apr_psprintf(pool, "%ld", rev), NULL);
6572
6573  subpool = svn_pool_create(pool);
6574  for (i = 1; i <= 99999; i++)
6575    {
6576      svn_error_t *err;
6577
6578      svn_pool_clear(subpool);
6579      unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i);
6580      err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool);
6581      if (! err)
6582        {
6583          /* We succeeded.  Return the basename minus the ".txn" extension. */
6584          const char *name = svn_dirent_basename(unique_path, subpool);
6585          *id_p = apr_pstrndup(pool, name,
6586                               strlen(name) - strlen(PATH_EXT_TXN));
6587          svn_pool_destroy(subpool);
6588          return SVN_NO_ERROR;
6589        }
6590      if (! APR_STATUS_IS_EEXIST(err->apr_err))
6591        return svn_error_trace(err);
6592      svn_error_clear(err);
6593    }
6594
6595  return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
6596                           NULL,
6597                           _("Unable to create transaction directory "
6598                             "in '%s' for revision %ld"),
6599                           svn_dirent_local_style(fs->path, pool),
6600                           rev);
6601}
6602
6603svn_error_t *
6604svn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
6605                      svn_fs_t *fs,
6606                      svn_revnum_t rev,
6607                      apr_pool_t *pool)
6608{
6609  fs_fs_data_t *ffd = fs->fsap_data;
6610  svn_fs_txn_t *txn;
6611  svn_fs_id_t *root_id;
6612
6613  txn = apr_pcalloc(pool, sizeof(*txn));
6614
6615  /* Get the txn_id. */
6616  if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
6617    SVN_ERR(create_txn_dir(&txn->id, fs, rev, pool));
6618  else
6619    SVN_ERR(create_txn_dir_pre_1_5(&txn->id, fs, rev, pool));
6620
6621  txn->fs = fs;
6622  txn->base_rev = rev;
6623
6624  txn->vtable = &txn_vtable;
6625  *txn_p = txn;
6626
6627  /* Create a new root node for this transaction. */
6628  SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool));
6629  SVN_ERR(create_new_txn_noderev_from_rev(fs, txn->id, root_id, pool));
6630
6631  /* Create an empty rev file. */
6632  SVN_ERR(svn_io_file_create(path_txn_proto_rev(fs, txn->id, pool), "",
6633                             pool));
6634
6635  /* Create an empty rev-lock file. */
6636  SVN_ERR(svn_io_file_create(path_txn_proto_rev_lock(fs, txn->id, pool), "",
6637                             pool));
6638
6639  /* Create an empty changes file. */
6640  SVN_ERR(svn_io_file_create(path_txn_changes(fs, txn->id, pool), "",
6641                             pool));
6642
6643  /* Create the next-ids file. */
6644  return svn_io_file_create(path_txn_next_ids(fs, txn->id, pool), "0 0\n",
6645                            pool);
6646}
6647
6648/* Store the property list for transaction TXN_ID in PROPLIST.
6649   Perform temporary allocations in POOL. */
6650static svn_error_t *
6651get_txn_proplist(apr_hash_t *proplist,
6652                 svn_fs_t *fs,
6653                 const char *txn_id,
6654                 apr_pool_t *pool)
6655{
6656  svn_stream_t *stream;
6657
6658  /* Check for issue #3696. (When we find and fix the cause, we can change
6659   * this to an assertion.) */
6660  if (txn_id == NULL)
6661    return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
6662                            _("Internal error: a null transaction id was "
6663                              "passed to get_txn_proplist()"));
6664
6665  /* Open the transaction properties file. */
6666  SVN_ERR(svn_stream_open_readonly(&stream, path_txn_props(fs, txn_id, pool),
6667                                   pool, pool));
6668
6669  /* Read in the property list. */
6670  SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
6671
6672  return svn_stream_close(stream);
6673}
6674
6675svn_error_t *
6676svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
6677                           const char *name,
6678                           const svn_string_t *value,
6679                           apr_pool_t *pool)
6680{
6681  apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
6682  svn_prop_t prop;
6683
6684  prop.name = name;
6685  prop.value = value;
6686  APR_ARRAY_PUSH(props, svn_prop_t) = prop;
6687
6688  return svn_fs_fs__change_txn_props(txn, props, pool);
6689}
6690
6691svn_error_t *
6692svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
6693                            const apr_array_header_t *props,
6694                            apr_pool_t *pool)
6695{
6696  const char *txn_prop_filename;
6697  svn_stringbuf_t *buf;
6698  svn_stream_t *stream;
6699  apr_hash_t *txn_prop = apr_hash_make(pool);
6700  int i;
6701  svn_error_t *err;
6702
6703  err = get_txn_proplist(txn_prop, txn->fs, txn->id, pool);
6704  /* Here - and here only - we need to deal with the possibility that the
6705     transaction property file doesn't yet exist.  The rest of the
6706     implementation assumes that the file exists, but we're called to set the
6707     initial transaction properties as the transaction is being created. */
6708  if (err && (APR_STATUS_IS_ENOENT(err->apr_err)))
6709    svn_error_clear(err);
6710  else if (err)
6711    return svn_error_trace(err);
6712
6713  for (i = 0; i < props->nelts; i++)
6714    {
6715      svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
6716
6717      svn_hash_sets(txn_prop, prop->name, prop->value);
6718    }
6719
6720  /* Create a new version of the file and write out the new props. */
6721  /* Open the transaction properties file. */
6722  buf = svn_stringbuf_create_ensure(1024, pool);
6723  stream = svn_stream_from_stringbuf(buf, pool);
6724  SVN_ERR(svn_hash_write2(txn_prop, stream, SVN_HASH_TERMINATOR, pool));
6725  SVN_ERR(svn_stream_close(stream));
6726  SVN_ERR(svn_io_write_unique(&txn_prop_filename,
6727                              path_txn_dir(txn->fs, txn->id, pool),
6728                              buf->data,
6729                              buf->len,
6730                              svn_io_file_del_none,
6731                              pool));
6732  return svn_io_file_rename(txn_prop_filename,
6733                            path_txn_props(txn->fs, txn->id, pool),
6734                            pool);
6735}
6736
6737svn_error_t *
6738svn_fs_fs__get_txn(transaction_t **txn_p,
6739                   svn_fs_t *fs,
6740                   const char *txn_id,
6741                   apr_pool_t *pool)
6742{
6743  transaction_t *txn;
6744  node_revision_t *noderev;
6745  svn_fs_id_t *root_id;
6746
6747  txn = apr_pcalloc(pool, sizeof(*txn));
6748  txn->proplist = apr_hash_make(pool);
6749
6750  SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool));
6751  root_id = svn_fs_fs__id_txn_create("0", "0", txn_id, pool);
6752
6753  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool));
6754
6755  txn->root_id = svn_fs_fs__id_copy(noderev->id, pool);
6756  txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
6757  txn->copies = NULL;
6758
6759  *txn_p = txn;
6760
6761  return SVN_NO_ERROR;
6762}
6763
6764/* Write out the currently available next node_id NODE_ID and copy_id
6765   COPY_ID for transaction TXN_ID in filesystem FS.  The next node-id is
6766   used both for creating new unique nodes for the given transaction, as
6767   well as uniquifying representations.  Perform temporary allocations in
6768   POOL. */
6769static svn_error_t *
6770write_next_ids(svn_fs_t *fs,
6771               const char *txn_id,
6772               const char *node_id,
6773               const char *copy_id,
6774               apr_pool_t *pool)
6775{
6776  apr_file_t *file;
6777  svn_stream_t *out_stream;
6778
6779  SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
6780                           APR_WRITE | APR_TRUNCATE,
6781                           APR_OS_DEFAULT, pool));
6782
6783  out_stream = svn_stream_from_aprfile2(file, TRUE, pool);
6784
6785  SVN_ERR(svn_stream_printf(out_stream, pool, "%s %s\n", node_id, copy_id));
6786
6787  SVN_ERR(svn_stream_close(out_stream));
6788  return svn_io_file_close(file, pool);
6789}
6790
6791/* Find out what the next unique node-id and copy-id are for
6792   transaction TXN_ID in filesystem FS.  Store the results in *NODE_ID
6793   and *COPY_ID.  The next node-id is used both for creating new unique
6794   nodes for the given transaction, as well as uniquifying representations.
6795   Perform all allocations in POOL. */
6796static svn_error_t *
6797read_next_ids(const char **node_id,
6798              const char **copy_id,
6799              svn_fs_t *fs,
6800              const char *txn_id,
6801              apr_pool_t *pool)
6802{
6803  apr_file_t *file;
6804  char buf[MAX_KEY_SIZE*2+3];
6805  apr_size_t limit;
6806  char *str, *last_str = buf;
6807
6808  SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
6809                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
6810
6811  limit = sizeof(buf);
6812  SVN_ERR(svn_io_read_length_line(file, buf, &limit, pool));
6813
6814  SVN_ERR(svn_io_file_close(file, pool));
6815
6816  /* Parse this into two separate strings. */
6817
6818  str = svn_cstring_tokenize(" ", &last_str);
6819  if (! str)
6820    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6821                            _("next-id file corrupt"));
6822
6823  *node_id = apr_pstrdup(pool, str);
6824
6825  str = svn_cstring_tokenize(" ", &last_str);
6826  if (! str)
6827    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6828                            _("next-id file corrupt"));
6829
6830  *copy_id = apr_pstrdup(pool, str);
6831
6832  return SVN_NO_ERROR;
6833}
6834
6835/* Get a new and unique to this transaction node-id for transaction
6836   TXN_ID in filesystem FS.  Store the new node-id in *NODE_ID_P.
6837   Node-ids are guaranteed to be unique to this transction, but may
6838   not necessarily be sequential.  Perform all allocations in POOL. */
6839static svn_error_t *
6840get_new_txn_node_id(const char **node_id_p,
6841                    svn_fs_t *fs,
6842                    const char *txn_id,
6843                    apr_pool_t *pool)
6844{
6845  const char *cur_node_id, *cur_copy_id;
6846  char *node_id;
6847  apr_size_t len;
6848
6849  /* First read in the current next-ids file. */
6850  SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
6851
6852  node_id = apr_pcalloc(pool, strlen(cur_node_id) + 2);
6853
6854  len = strlen(cur_node_id);
6855  svn_fs_fs__next_key(cur_node_id, &len, node_id);
6856
6857  SVN_ERR(write_next_ids(fs, txn_id, node_id, cur_copy_id, pool));
6858
6859  *node_id_p = apr_pstrcat(pool, "_", cur_node_id, (char *)NULL);
6860
6861  return SVN_NO_ERROR;
6862}
6863
6864svn_error_t *
6865svn_fs_fs__create_node(const svn_fs_id_t **id_p,
6866                       svn_fs_t *fs,
6867                       node_revision_t *noderev,
6868                       const char *copy_id,
6869                       const char *txn_id,
6870                       apr_pool_t *pool)
6871{
6872  const char *node_id;
6873  const svn_fs_id_t *id;
6874
6875  /* Get a new node-id for this node. */
6876  SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool));
6877
6878  id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
6879
6880  noderev->id = id;
6881
6882  SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
6883
6884  *id_p = id;
6885
6886  return SVN_NO_ERROR;
6887}
6888
6889svn_error_t *
6890svn_fs_fs__purge_txn(svn_fs_t *fs,
6891                     const char *txn_id,
6892                     apr_pool_t *pool)
6893{
6894  fs_fs_data_t *ffd = fs->fsap_data;
6895
6896  /* Remove the shared transaction object associated with this transaction. */
6897  SVN_ERR(purge_shared_txn(fs, txn_id, pool));
6898  /* Remove the directory associated with this transaction. */
6899  SVN_ERR(svn_io_remove_dir2(path_txn_dir(fs, txn_id, pool), FALSE,
6900                             NULL, NULL, pool));
6901  if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
6902    {
6903      /* Delete protorev and its lock, which aren't in the txn
6904         directory.  It's OK if they don't exist (for example, if this
6905         is post-commit and the proto-rev has been moved into
6906         place). */
6907      SVN_ERR(svn_io_remove_file2(path_txn_proto_rev(fs, txn_id, pool),
6908                                  TRUE, pool));
6909      SVN_ERR(svn_io_remove_file2(path_txn_proto_rev_lock(fs, txn_id, pool),
6910                                  TRUE, pool));
6911    }
6912  return SVN_NO_ERROR;
6913}
6914
6915
6916svn_error_t *
6917svn_fs_fs__abort_txn(svn_fs_txn_t *txn,
6918                     apr_pool_t *pool)
6919{
6920  SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
6921
6922  /* Now, purge the transaction. */
6923  SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool),
6924            apr_psprintf(pool, _("Transaction '%s' cleanup failed"),
6925                         txn->id));
6926
6927  return SVN_NO_ERROR;
6928}
6929
6930
6931svn_error_t *
6932svn_fs_fs__set_entry(svn_fs_t *fs,
6933                     const char *txn_id,
6934                     node_revision_t *parent_noderev,
6935                     const char *name,
6936                     const svn_fs_id_t *id,
6937                     svn_node_kind_t kind,
6938                     apr_pool_t *pool)
6939{
6940  representation_t *rep = parent_noderev->data_rep;
6941  const char *filename = path_txn_node_children(fs, parent_noderev->id, pool);
6942  apr_file_t *file;
6943  svn_stream_t *out;
6944  fs_fs_data_t *ffd = fs->fsap_data;
6945  apr_pool_t *subpool = svn_pool_create(pool);
6946
6947  if (!rep || !rep->txn_id)
6948    {
6949      const char *unique_suffix;
6950      apr_hash_t *entries;
6951
6952      /* Before we can modify the directory, we need to dump its old
6953         contents into a mutable representation file. */
6954      SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
6955                                          subpool));
6956      SVN_ERR(unparse_dir_entries(&entries, entries, subpool));
6957      SVN_ERR(svn_io_file_open(&file, filename,
6958                               APR_WRITE | APR_CREATE | APR_BUFFERED,
6959                               APR_OS_DEFAULT, pool));
6960      out = svn_stream_from_aprfile2(file, TRUE, pool);
6961      SVN_ERR(svn_hash_write2(entries, out, SVN_HASH_TERMINATOR, subpool));
6962
6963      svn_pool_clear(subpool);
6964
6965      /* Mark the node-rev's data rep as mutable. */
6966      rep = apr_pcalloc(pool, sizeof(*rep));
6967      rep->revision = SVN_INVALID_REVNUM;
6968      rep->txn_id = txn_id;
6969      SVN_ERR(get_new_txn_node_id(&unique_suffix, fs, txn_id, pool));
6970      rep->uniquifier = apr_psprintf(pool, "%s/%s", txn_id, unique_suffix);
6971      parent_noderev->data_rep = rep;
6972      SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id,
6973                                           parent_noderev, FALSE, pool));
6974    }
6975  else
6976    {
6977      /* The directory rep is already mutable, so just open it for append. */
6978      SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
6979                               APR_OS_DEFAULT, pool));
6980      out = svn_stream_from_aprfile2(file, TRUE, pool);
6981    }
6982
6983  /* if we have a directory cache for this transaction, update it */
6984  if (ffd->txn_dir_cache)
6985    {
6986      /* build parameters: (name, new entry) pair */
6987      const char *key =
6988          svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data;
6989      replace_baton_t baton;
6990
6991      baton.name = name;
6992      baton.new_entry = NULL;
6993
6994      if (id)
6995        {
6996          baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry));
6997          baton.new_entry->name = name;
6998          baton.new_entry->kind = kind;
6999          baton.new_entry->id = id;
7000        }
7001
7002      /* actually update the cached directory (if cached) */
7003      SVN_ERR(svn_cache__set_partial(ffd->txn_dir_cache, key,
7004                                     svn_fs_fs__replace_dir_entry, &baton,
7005                                     subpool));
7006    }
7007  svn_pool_clear(subpool);
7008
7009  /* Append an incremental hash entry for the entry change. */
7010  if (id)
7011    {
7012      const char *val = unparse_dir_entry(kind, id, subpool);
7013
7014      SVN_ERR(svn_stream_printf(out, subpool, "K %" APR_SIZE_T_FMT "\n%s\n"
7015                                "V %" APR_SIZE_T_FMT "\n%s\n",
7016                                strlen(name), name,
7017                                strlen(val), val));
7018    }
7019  else
7020    {
7021      SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
7022                                strlen(name), name));
7023    }
7024
7025  SVN_ERR(svn_io_file_close(file, subpool));
7026  svn_pool_destroy(subpool);
7027  return SVN_NO_ERROR;
7028}
7029
7030/* Write a single change entry, path PATH, change CHANGE, and copyfrom
7031   string COPYFROM, into the file specified by FILE.  Only include the
7032   node kind field if INCLUDE_NODE_KIND is true.  All temporary
7033   allocations are in POOL. */
7034static svn_error_t *
7035write_change_entry(apr_file_t *file,
7036                   const char *path,
7037                   svn_fs_path_change2_t *change,
7038                   svn_boolean_t include_node_kind,
7039                   apr_pool_t *pool)
7040{
7041  const char *idstr, *buf;
7042  const char *change_string = NULL;
7043  const char *kind_string = "";
7044
7045  switch (change->change_kind)
7046    {
7047    case svn_fs_path_change_modify:
7048      change_string = ACTION_MODIFY;
7049      break;
7050    case svn_fs_path_change_add:
7051      change_string = ACTION_ADD;
7052      break;
7053    case svn_fs_path_change_delete:
7054      change_string = ACTION_DELETE;
7055      break;
7056    case svn_fs_path_change_replace:
7057      change_string = ACTION_REPLACE;
7058      break;
7059    case svn_fs_path_change_reset:
7060      change_string = ACTION_RESET;
7061      break;
7062    default:
7063      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
7064                               _("Invalid change type %d"),
7065                               change->change_kind);
7066    }
7067
7068  if (change->node_rev_id)
7069    idstr = svn_fs_fs__id_unparse(change->node_rev_id, pool)->data;
7070  else
7071    idstr = ACTION_RESET;
7072
7073  if (include_node_kind)
7074    {
7075      SVN_ERR_ASSERT(change->node_kind == svn_node_dir
7076                     || change->node_kind == svn_node_file);
7077      kind_string = apr_psprintf(pool, "-%s",
7078                                 change->node_kind == svn_node_dir
7079                                 ? KIND_DIR : KIND_FILE);
7080    }
7081  buf = apr_psprintf(pool, "%s %s%s %s %s %s\n",
7082                     idstr, change_string, kind_string,
7083                     change->text_mod ? FLAG_TRUE : FLAG_FALSE,
7084                     change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
7085                     path);
7086
7087  SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
7088
7089  if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
7090    {
7091      buf = apr_psprintf(pool, "%ld %s", change->copyfrom_rev,
7092                         change->copyfrom_path);
7093      SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
7094    }
7095
7096  return svn_io_file_write_full(file, "\n", 1, NULL, pool);
7097}
7098
7099svn_error_t *
7100svn_fs_fs__add_change(svn_fs_t *fs,
7101                      const char *txn_id,
7102                      const char *path,
7103                      const svn_fs_id_t *id,
7104                      svn_fs_path_change_kind_t change_kind,
7105                      svn_boolean_t text_mod,
7106                      svn_boolean_t prop_mod,
7107                      svn_node_kind_t node_kind,
7108                      svn_revnum_t copyfrom_rev,
7109                      const char *copyfrom_path,
7110                      apr_pool_t *pool)
7111{
7112  apr_file_t *file;
7113  svn_fs_path_change2_t *change;
7114
7115  SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
7116                           APR_APPEND | APR_WRITE | APR_CREATE
7117                           | APR_BUFFERED, APR_OS_DEFAULT, pool));
7118
7119  change = svn_fs__path_change_create_internal(id, change_kind, pool);
7120  change->text_mod = text_mod;
7121  change->prop_mod = prop_mod;
7122  change->node_kind = node_kind;
7123  change->copyfrom_rev = copyfrom_rev;
7124  change->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
7125
7126  SVN_ERR(write_change_entry(file, path, change, TRUE, pool));
7127
7128  return svn_io_file_close(file, pool);
7129}
7130
7131/* This baton is used by the representation writing streams.  It keeps
7132   track of the checksum information as well as the total size of the
7133   representation so far. */
7134struct rep_write_baton
7135{
7136  /* The FS we are writing to. */
7137  svn_fs_t *fs;
7138
7139  /* Actual file to which we are writing. */
7140  svn_stream_t *rep_stream;
7141
7142  /* A stream from the delta combiner.  Data written here gets
7143     deltified, then eventually written to rep_stream. */
7144  svn_stream_t *delta_stream;
7145
7146  /* Where is this representation header stored. */
7147  apr_off_t rep_offset;
7148
7149  /* Start of the actual data. */
7150  apr_off_t delta_start;
7151
7152  /* How many bytes have been written to this rep already. */
7153  svn_filesize_t rep_size;
7154
7155  /* The node revision for which we're writing out info. */
7156  node_revision_t *noderev;
7157
7158  /* Actual output file. */
7159  apr_file_t *file;
7160  /* Lock 'cookie' used to unlock the output file once we've finished
7161     writing to it. */
7162  void *lockcookie;
7163
7164  svn_checksum_ctx_t *md5_checksum_ctx;
7165  svn_checksum_ctx_t *sha1_checksum_ctx;
7166
7167  apr_pool_t *pool;
7168
7169  apr_pool_t *parent_pool;
7170};
7171
7172/* Handler for the write method of the representation writable stream.
7173   BATON is a rep_write_baton, DATA is the data to write, and *LEN is
7174   the length of this data. */
7175static svn_error_t *
7176rep_write_contents(void *baton,
7177                   const char *data,
7178                   apr_size_t *len)
7179{
7180  struct rep_write_baton *b = baton;
7181
7182  SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len));
7183  SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len));
7184  b->rep_size += *len;
7185
7186  /* If we are writing a delta, use that stream. */
7187  if (b->delta_stream)
7188    return svn_stream_write(b->delta_stream, data, len);
7189  else
7190    return svn_stream_write(b->rep_stream, data, len);
7191}
7192
7193/* Given a node-revision NODEREV in filesystem FS, return the
7194   representation in *REP to use as the base for a text representation
7195   delta if PROPS is FALSE.  If PROPS has been set, a suitable props
7196   base representation will be returned.  Perform temporary allocations
7197   in *POOL. */
7198static svn_error_t *
7199choose_delta_base(representation_t **rep,
7200                  svn_fs_t *fs,
7201                  node_revision_t *noderev,
7202                  svn_boolean_t props,
7203                  apr_pool_t *pool)
7204{
7205  int count;
7206  int walk;
7207  node_revision_t *base;
7208  fs_fs_data_t *ffd = fs->fsap_data;
7209  svn_boolean_t maybe_shared_rep = FALSE;
7210
7211  /* If we have no predecessors, then use the empty stream as a
7212     base. */
7213  if (! noderev->predecessor_count)
7214    {
7215      *rep = NULL;
7216      return SVN_NO_ERROR;
7217    }
7218
7219  /* Flip the rightmost '1' bit of the predecessor count to determine
7220     which file rev (counting from 0) we want to use.  (To see why
7221     count & (count - 1) unsets the rightmost set bit, think about how
7222     you decrement a binary number.) */
7223  count = noderev->predecessor_count;
7224  count = count & (count - 1);
7225
7226  /* We use skip delta for limiting the number of delta operations
7227     along very long node histories.  Close to HEAD however, we create
7228     a linear history to minimize delta size.  */
7229  walk = noderev->predecessor_count - count;
7230  if (walk < (int)ffd->max_linear_deltification)
7231    count = noderev->predecessor_count - 1;
7232
7233  /* Finding the delta base over a very long distance can become extremely
7234     expensive for very deep histories, possibly causing client timeouts etc.
7235     OTOH, this is a rare operation and its gains are minimal. Lets simply
7236     start deltification anew close every other 1000 changes or so.  */
7237  if (walk > (int)ffd->max_deltification_walk)
7238    {
7239      *rep = NULL;
7240      return SVN_NO_ERROR;
7241    }
7242
7243  /* Walk back a number of predecessors equal to the difference
7244     between count and the original predecessor count.  (For example,
7245     if noderev has ten predecessors and we want the eighth file rev,
7246     walk back two predecessors.) */
7247  base = noderev;
7248  while ((count++) < noderev->predecessor_count)
7249    {
7250      SVN_ERR(svn_fs_fs__get_node_revision(&base, fs,
7251                                           base->predecessor_id, pool));
7252
7253      /* If there is a shared rep along the way, we need to limit the
7254       * length of the deltification chain.
7255       *
7256       * Please note that copied nodes - such as branch directories - will
7257       * look the same (false positive) while reps shared within the same
7258       * revision will not be caught (false negative).
7259       */
7260      if (props)
7261        {
7262          if (   base->prop_rep
7263              && svn_fs_fs__id_rev(base->id) > base->prop_rep->revision)
7264            maybe_shared_rep = TRUE;
7265        }
7266      else
7267        {
7268          if (   base->data_rep
7269              && svn_fs_fs__id_rev(base->id) > base->data_rep->revision)
7270            maybe_shared_rep = TRUE;
7271        }
7272    }
7273
7274  /* return a suitable base representation */
7275  *rep = props ? base->prop_rep : base->data_rep;
7276
7277  /* if we encountered a shared rep, it's parent chain may be different
7278   * from the node-rev parent chain. */
7279  if (*rep && maybe_shared_rep)
7280    {
7281      /* Check whether the length of the deltification chain is acceptable.
7282       * Otherwise, shared reps may form a non-skipping delta chain in
7283       * extreme cases. */
7284      apr_pool_t *sub_pool = svn_pool_create(pool);
7285      representation_t base_rep = **rep;
7286
7287      /* Some reasonable limit, depending on how acceptable longer linear
7288       * chains are in this repo.  Also, allow for some minimal chain. */
7289      int max_chain_length = 2 * (int)ffd->max_linear_deltification + 2;
7290
7291      /* re-use open files between iterations */
7292      svn_revnum_t rev_hint = SVN_INVALID_REVNUM;
7293      apr_file_t *file_hint = NULL;
7294
7295      /* follow the delta chain towards the end but for at most
7296       * MAX_CHAIN_LENGTH steps. */
7297      for (; max_chain_length; --max_chain_length)
7298        {
7299          struct rep_state *rep_state;
7300          struct rep_args *rep_args;
7301
7302          SVN_ERR(create_rep_state_body(&rep_state,
7303                                        &rep_args,
7304                                        &file_hint,
7305                                        &rev_hint,
7306                                        &base_rep,
7307                                        fs,
7308                                        sub_pool));
7309          if (!rep_args->is_delta  || !rep_args->base_revision)
7310            break;
7311
7312          base_rep.revision = rep_args->base_revision;
7313          base_rep.offset = rep_args->base_offset;
7314          base_rep.size = rep_args->base_length;
7315          base_rep.txn_id = NULL;
7316        }
7317
7318      /* start new delta chain if the current one has grown too long */
7319      if (max_chain_length == 0)
7320        *rep = NULL;
7321
7322      svn_pool_destroy(sub_pool);
7323    }
7324
7325  /* verify that the reps don't form a degenerated '*/
7326  return SVN_NO_ERROR;
7327}
7328
7329/* Something went wrong and the pool for the rep write is being
7330   cleared before we've finished writing the rep.  So we need
7331   to remove the rep from the protorevfile and we need to unlock
7332   the protorevfile. */
7333static apr_status_t
7334rep_write_cleanup(void *data)
7335{
7336  struct rep_write_baton *b = data;
7337  const char *txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
7338  svn_error_t *err;
7339
7340  /* Truncate and close the protorevfile. */
7341  err = svn_io_file_trunc(b->file, b->rep_offset, b->pool);
7342  err = svn_error_compose_create(err, svn_io_file_close(b->file, b->pool));
7343
7344  /* Remove our lock regardless of any preceeding errors so that the
7345     being_written flag is always removed and stays consistent with the
7346     file lock which will be removed no matter what since the pool is
7347     going away. */
7348  err = svn_error_compose_create(err, unlock_proto_rev(b->fs, txn_id,
7349                                                       b->lockcookie, b->pool));
7350  if (err)
7351    {
7352      apr_status_t rc = err->apr_err;
7353      svn_error_clear(err);
7354      return rc;
7355    }
7356
7357  return APR_SUCCESS;
7358}
7359
7360
7361/* Get a rep_write_baton and store it in *WB_P for the representation
7362   indicated by NODEREV in filesystem FS.  Perform allocations in
7363   POOL.  Only appropriate for file contents, not for props or
7364   directory contents. */
7365static svn_error_t *
7366rep_write_get_baton(struct rep_write_baton **wb_p,
7367                    svn_fs_t *fs,
7368                    node_revision_t *noderev,
7369                    apr_pool_t *pool)
7370{
7371  struct rep_write_baton *b;
7372  apr_file_t *file;
7373  representation_t *base_rep;
7374  svn_stream_t *source;
7375  const char *header;
7376  svn_txdelta_window_handler_t wh;
7377  void *whb;
7378  fs_fs_data_t *ffd = fs->fsap_data;
7379  int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
7380
7381  b = apr_pcalloc(pool, sizeof(*b));
7382
7383  b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7384  b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7385
7386  b->fs = fs;
7387  b->parent_pool = pool;
7388  b->pool = svn_pool_create(pool);
7389  b->rep_size = 0;
7390  b->noderev = noderev;
7391
7392  /* Open the prototype rev file and seek to its end. */
7393  SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie,
7394                                 fs, svn_fs_fs__id_txn_id(noderev->id),
7395                                 b->pool));
7396
7397  b->file = file;
7398  b->rep_stream = svn_stream_from_aprfile2(file, TRUE, b->pool);
7399
7400  SVN_ERR(get_file_offset(&b->rep_offset, file, b->pool));
7401
7402  /* Get the base for this delta. */
7403  SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->pool));
7404  SVN_ERR(read_representation(&source, fs, base_rep, b->pool));
7405
7406  /* Write out the rep header. */
7407  if (base_rep)
7408    {
7409      header = apr_psprintf(b->pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
7410                            SVN_FILESIZE_T_FMT "\n",
7411                            base_rep->revision, base_rep->offset,
7412                            base_rep->size);
7413    }
7414  else
7415    {
7416      header = REP_DELTA "\n";
7417    }
7418  SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
7419                                 b->pool));
7420
7421  /* Now determine the offset of the actual svndiff data. */
7422  SVN_ERR(get_file_offset(&b->delta_start, file, b->pool));
7423
7424  /* Cleanup in case something goes wrong. */
7425  apr_pool_cleanup_register(b->pool, b, rep_write_cleanup,
7426                            apr_pool_cleanup_null);
7427
7428  /* Prepare to write the svndiff data. */
7429  svn_txdelta_to_svndiff3(&wh,
7430                          &whb,
7431                          b->rep_stream,
7432                          diff_version,
7433                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
7434                          pool);
7435
7436  b->delta_stream = svn_txdelta_target_push(wh, whb, source, b->pool);
7437
7438  *wb_p = b;
7439
7440  return SVN_NO_ERROR;
7441}
7442
7443/* For the hash REP->SHA1, try to find an already existing representation
7444   in FS and return it in *OUT_REP.  If no such representation exists or
7445   if rep sharing has been disabled for FS, NULL will be returned.  Since
7446   there may be new duplicate representations within the same uncommitted
7447   revision, those can be passed in REPS_HASH (maps a sha1 digest onto
7448   representation_t*), otherwise pass in NULL for REPS_HASH.
7449   POOL will be used for allocations. The lifetime of the returned rep is
7450   limited by both, POOL and REP lifetime.
7451 */
7452static svn_error_t *
7453get_shared_rep(representation_t **old_rep,
7454               svn_fs_t *fs,
7455               representation_t *rep,
7456               apr_hash_t *reps_hash,
7457               apr_pool_t *pool)
7458{
7459  svn_error_t *err;
7460  fs_fs_data_t *ffd = fs->fsap_data;
7461
7462  /* Return NULL, if rep sharing has been disabled. */
7463  *old_rep = NULL;
7464  if (!ffd->rep_sharing_allowed)
7465    return SVN_NO_ERROR;
7466
7467  /* Check and see if we already have a representation somewhere that's
7468     identical to the one we just wrote out.  Start with the hash lookup
7469     because it is cheepest. */
7470  if (reps_hash)
7471    *old_rep = apr_hash_get(reps_hash,
7472                            rep->sha1_checksum->digest,
7473                            APR_SHA1_DIGESTSIZE);
7474
7475  /* If we haven't found anything yet, try harder and consult our DB. */
7476  if (*old_rep == NULL)
7477    {
7478      err = svn_fs_fs__get_rep_reference(old_rep, fs, rep->sha1_checksum,
7479                                         pool);
7480      /* ### Other error codes that we shouldn't mask out? */
7481      if (err == SVN_NO_ERROR)
7482        {
7483          if (*old_rep)
7484            SVN_ERR(verify_walker(*old_rep, NULL, fs, pool));
7485        }
7486      else if (err->apr_err == SVN_ERR_FS_CORRUPT
7487               || SVN_ERROR_IN_CATEGORY(err->apr_err,
7488                                        SVN_ERR_MALFUNC_CATEGORY_START))
7489        {
7490          /* Fatal error; don't mask it.
7491
7492             In particular, this block is triggered when the rep-cache refers
7493             to revisions in the future.  We signal that as a corruption situation
7494             since, once those revisions are less than youngest (because of more
7495             commits), the rep-cache would be invalid.
7496           */
7497          SVN_ERR(err);
7498        }
7499      else
7500        {
7501          /* Something's wrong with the rep-sharing index.  We can continue
7502             without rep-sharing, but warn.
7503           */
7504          (fs->warning)(fs->warning_baton, err);
7505          svn_error_clear(err);
7506          *old_rep = NULL;
7507        }
7508    }
7509
7510  /* look for intra-revision matches (usually data reps but not limited
7511     to them in case props happen to look like some data rep)
7512   */
7513  if (*old_rep == NULL && rep->txn_id)
7514    {
7515      svn_node_kind_t kind;
7516      const char *file_name
7517        = path_txn_sha1(fs, rep->txn_id, rep->sha1_checksum, pool);
7518
7519      /* in our txn, is there a rep file named with the wanted SHA1?
7520         If so, read it and use that rep.
7521       */
7522      SVN_ERR(svn_io_check_path(file_name, &kind, pool));
7523      if (kind == svn_node_file)
7524        {
7525          svn_stringbuf_t *rep_string;
7526          SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name, pool));
7527          SVN_ERR(read_rep_offsets_body(old_rep, rep_string->data,
7528                                        rep->txn_id, FALSE, pool));
7529        }
7530    }
7531
7532  /* Add information that is missing in the cached data. */
7533  if (*old_rep)
7534    {
7535      /* Use the old rep for this content. */
7536      (*old_rep)->md5_checksum = rep->md5_checksum;
7537      (*old_rep)->uniquifier = rep->uniquifier;
7538    }
7539
7540  return SVN_NO_ERROR;
7541}
7542
7543/* Close handler for the representation write stream.  BATON is a
7544   rep_write_baton.  Writes out a new node-rev that correctly
7545   references the representation we just finished writing. */
7546static svn_error_t *
7547rep_write_contents_close(void *baton)
7548{
7549  struct rep_write_baton *b = baton;
7550  const char *unique_suffix;
7551  representation_t *rep;
7552  representation_t *old_rep;
7553  apr_off_t offset;
7554
7555  rep = apr_pcalloc(b->parent_pool, sizeof(*rep));
7556  rep->offset = b->rep_offset;
7557
7558  /* Close our delta stream so the last bits of svndiff are written
7559     out. */
7560  if (b->delta_stream)
7561    SVN_ERR(svn_stream_close(b->delta_stream));
7562
7563  /* Determine the length of the svndiff data. */
7564  SVN_ERR(get_file_offset(&offset, b->file, b->pool));
7565  rep->size = offset - b->delta_start;
7566
7567  /* Fill in the rest of the representation field. */
7568  rep->expanded_size = b->rep_size;
7569  rep->txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
7570  SVN_ERR(get_new_txn_node_id(&unique_suffix, b->fs, rep->txn_id, b->pool));
7571  rep->uniquifier = apr_psprintf(b->parent_pool, "%s/%s", rep->txn_id,
7572                                 unique_suffix);
7573  rep->revision = SVN_INVALID_REVNUM;
7574
7575  /* Finalize the checksum. */
7576  SVN_ERR(svn_checksum_final(&rep->md5_checksum, b->md5_checksum_ctx,
7577                              b->parent_pool));
7578  SVN_ERR(svn_checksum_final(&rep->sha1_checksum, b->sha1_checksum_ctx,
7579                              b->parent_pool));
7580
7581  /* Check and see if we already have a representation somewhere that's
7582     identical to the one we just wrote out. */
7583  SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->parent_pool));
7584
7585  if (old_rep)
7586    {
7587      /* We need to erase from the protorev the data we just wrote. */
7588      SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->pool));
7589
7590      /* Use the old rep for this content. */
7591      b->noderev->data_rep = old_rep;
7592    }
7593  else
7594    {
7595      /* Write out our cosmetic end marker. */
7596      SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n"));
7597
7598      b->noderev->data_rep = rep;
7599    }
7600
7601  /* Remove cleanup callback. */
7602  apr_pool_cleanup_kill(b->pool, b, rep_write_cleanup);
7603
7604  /* Write out the new node-rev information. */
7605  SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev, FALSE,
7606                                       b->pool));
7607  if (!old_rep)
7608    SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->pool));
7609
7610  SVN_ERR(svn_io_file_close(b->file, b->pool));
7611  SVN_ERR(unlock_proto_rev(b->fs, rep->txn_id, b->lockcookie, b->pool));
7612  svn_pool_destroy(b->pool);
7613
7614  return SVN_NO_ERROR;
7615}
7616
7617/* Store a writable stream in *CONTENTS_P that will receive all data
7618   written and store it as the file data representation referenced by
7619   NODEREV in filesystem FS.  Perform temporary allocations in
7620   POOL.  Only appropriate for file data, not props or directory
7621   contents. */
7622static svn_error_t *
7623set_representation(svn_stream_t **contents_p,
7624                   svn_fs_t *fs,
7625                   node_revision_t *noderev,
7626                   apr_pool_t *pool)
7627{
7628  struct rep_write_baton *wb;
7629
7630  if (! svn_fs_fs__id_txn_id(noderev->id))
7631    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
7632                             _("Attempted to write to non-transaction '%s'"),
7633                             svn_fs_fs__id_unparse(noderev->id, pool)->data);
7634
7635  SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool));
7636
7637  *contents_p = svn_stream_create(wb, pool);
7638  svn_stream_set_write(*contents_p, rep_write_contents);
7639  svn_stream_set_close(*contents_p, rep_write_contents_close);
7640
7641  return SVN_NO_ERROR;
7642}
7643
7644svn_error_t *
7645svn_fs_fs__set_contents(svn_stream_t **stream,
7646                        svn_fs_t *fs,
7647                        node_revision_t *noderev,
7648                        apr_pool_t *pool)
7649{
7650  if (noderev->kind != svn_node_file)
7651    return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
7652                            _("Can't set text contents of a directory"));
7653
7654  return set_representation(stream, fs, noderev, pool);
7655}
7656
7657svn_error_t *
7658svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
7659                            svn_fs_t *fs,
7660                            const svn_fs_id_t *old_idp,
7661                            node_revision_t *new_noderev,
7662                            const char *copy_id,
7663                            const char *txn_id,
7664                            apr_pool_t *pool)
7665{
7666  const svn_fs_id_t *id;
7667
7668  if (! copy_id)
7669    copy_id = svn_fs_fs__id_copy_id(old_idp);
7670  id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id,
7671                                txn_id, pool);
7672
7673  new_noderev->id = id;
7674
7675  if (! new_noderev->copyroot_path)
7676    {
7677      new_noderev->copyroot_path = apr_pstrdup(pool,
7678                                               new_noderev->created_path);
7679      new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id);
7680    }
7681
7682  SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE,
7683                                       pool));
7684
7685  *new_id_p = id;
7686
7687  return SVN_NO_ERROR;
7688}
7689
7690svn_error_t *
7691svn_fs_fs__set_proplist(svn_fs_t *fs,
7692                        node_revision_t *noderev,
7693                        apr_hash_t *proplist,
7694                        apr_pool_t *pool)
7695{
7696  const char *filename = path_txn_node_props(fs, noderev->id, pool);
7697  apr_file_t *file;
7698  svn_stream_t *out;
7699
7700  /* Dump the property list to the mutable property file. */
7701  SVN_ERR(svn_io_file_open(&file, filename,
7702                           APR_WRITE | APR_CREATE | APR_TRUNCATE
7703                           | APR_BUFFERED, APR_OS_DEFAULT, pool));
7704  out = svn_stream_from_aprfile2(file, TRUE, pool);
7705  SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool));
7706  SVN_ERR(svn_io_file_close(file, pool));
7707
7708  /* Mark the node-rev's prop rep as mutable, if not already done. */
7709  if (!noderev->prop_rep || !noderev->prop_rep->txn_id)
7710    {
7711      noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep));
7712      noderev->prop_rep->txn_id = svn_fs_fs__id_txn_id(noderev->id);
7713      SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
7714    }
7715
7716  return SVN_NO_ERROR;
7717}
7718
7719/* Read the 'current' file for filesystem FS and store the next
7720   available node id in *NODE_ID, and the next available copy id in
7721   *COPY_ID.  Allocations are performed from POOL. */
7722static svn_error_t *
7723get_next_revision_ids(const char **node_id,
7724                      const char **copy_id,
7725                      svn_fs_t *fs,
7726                      apr_pool_t *pool)
7727{
7728  char *buf;
7729  char *str;
7730  svn_stringbuf_t *content;
7731
7732  SVN_ERR(read_content(&content, svn_fs_fs__path_current(fs, pool), pool));
7733  buf = content->data;
7734
7735  str = svn_cstring_tokenize(" ", &buf);
7736  if (! str)
7737    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7738                            _("Corrupt 'current' file"));
7739
7740  str = svn_cstring_tokenize(" ", &buf);
7741  if (! str)
7742    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7743                            _("Corrupt 'current' file"));
7744
7745  *node_id = apr_pstrdup(pool, str);
7746
7747  str = svn_cstring_tokenize(" \n", &buf);
7748  if (! str)
7749    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7750                            _("Corrupt 'current' file"));
7751
7752  *copy_id = apr_pstrdup(pool, str);
7753
7754  return SVN_NO_ERROR;
7755}
7756
7757/* This baton is used by the stream created for write_hash_rep. */
7758struct write_hash_baton
7759{
7760  svn_stream_t *stream;
7761
7762  apr_size_t size;
7763
7764  svn_checksum_ctx_t *md5_ctx;
7765  svn_checksum_ctx_t *sha1_ctx;
7766};
7767
7768/* The handler for the write_hash_rep stream.  BATON is a
7769   write_hash_baton, DATA has the data to write and *LEN is the number
7770   of bytes to write. */
7771static svn_error_t *
7772write_hash_handler(void *baton,
7773                   const char *data,
7774                   apr_size_t *len)
7775{
7776  struct write_hash_baton *whb = baton;
7777
7778  SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
7779  SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
7780
7781  SVN_ERR(svn_stream_write(whb->stream, data, len));
7782  whb->size += *len;
7783
7784  return SVN_NO_ERROR;
7785}
7786
7787/* Write out the hash HASH as a text representation to file FILE.  In
7788   the process, record position, the total size of the dump and MD5 as
7789   well as SHA1 in REP.   If rep sharing has been enabled and REPS_HASH
7790   is not NULL, it will be used in addition to the on-disk cache to find
7791   earlier reps with the same content.  When such existing reps can be
7792   found, we will truncate the one just written from the file and return
7793   the existing rep.  Perform temporary allocations in POOL. */
7794static svn_error_t *
7795write_hash_rep(representation_t *rep,
7796               apr_file_t *file,
7797               apr_hash_t *hash,
7798               svn_fs_t *fs,
7799               apr_hash_t *reps_hash,
7800               apr_pool_t *pool)
7801{
7802  svn_stream_t *stream;
7803  struct write_hash_baton *whb;
7804  representation_t *old_rep;
7805
7806  SVN_ERR(get_file_offset(&rep->offset, file, pool));
7807
7808  whb = apr_pcalloc(pool, sizeof(*whb));
7809
7810  whb->stream = svn_stream_from_aprfile2(file, TRUE, pool);
7811  whb->size = 0;
7812  whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7813  whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7814
7815  stream = svn_stream_create(whb, pool);
7816  svn_stream_set_write(stream, write_hash_handler);
7817
7818  SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n"));
7819
7820  SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
7821
7822  /* Store the results. */
7823  SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
7824  SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
7825
7826  /* Check and see if we already have a representation somewhere that's
7827     identical to the one we just wrote out. */
7828  SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
7829
7830  if (old_rep)
7831    {
7832      /* We need to erase from the protorev the data we just wrote. */
7833      SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
7834
7835      /* Use the old rep for this content. */
7836      memcpy(rep, old_rep, sizeof (*rep));
7837    }
7838  else
7839    {
7840      /* Write out our cosmetic end marker. */
7841      SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n"));
7842
7843      /* update the representation */
7844      rep->size = whb->size;
7845      rep->expanded_size = 0;
7846    }
7847
7848  return SVN_NO_ERROR;
7849}
7850
7851/* Write out the hash HASH pertaining to the NODEREV in FS as a deltified
7852   text representation to file FILE.  In the process, record the total size
7853   and the md5 digest in REP.  If rep sharing has been enabled and REPS_HASH
7854   is not NULL, it will be used in addition to the on-disk cache to find
7855   earlier reps with the same content.  When such existing reps can be found,
7856   we will truncate the one just written from the file and return the existing
7857   rep.  If PROPS is set, assume that we want to a props representation as
7858   the base for our delta.  Perform temporary allocations in POOL. */
7859static svn_error_t *
7860write_hash_delta_rep(representation_t *rep,
7861                     apr_file_t *file,
7862                     apr_hash_t *hash,
7863                     svn_fs_t *fs,
7864                     node_revision_t *noderev,
7865                     apr_hash_t *reps_hash,
7866                     svn_boolean_t props,
7867                     apr_pool_t *pool)
7868{
7869  svn_txdelta_window_handler_t diff_wh;
7870  void *diff_whb;
7871
7872  svn_stream_t *file_stream;
7873  svn_stream_t *stream;
7874  representation_t *base_rep;
7875  representation_t *old_rep;
7876  svn_stream_t *source;
7877  const char *header;
7878
7879  apr_off_t rep_end = 0;
7880  apr_off_t delta_start = 0;
7881
7882  struct write_hash_baton *whb;
7883  fs_fs_data_t *ffd = fs->fsap_data;
7884  int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
7885
7886  /* Get the base for this delta. */
7887  SVN_ERR(choose_delta_base(&base_rep, fs, noderev, props, pool));
7888  SVN_ERR(read_representation(&source, fs, base_rep, pool));
7889
7890  SVN_ERR(get_file_offset(&rep->offset, file, pool));
7891
7892  /* Write out the rep header. */
7893  if (base_rep)
7894    {
7895      header = apr_psprintf(pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
7896                            SVN_FILESIZE_T_FMT "\n",
7897                            base_rep->revision, base_rep->offset,
7898                            base_rep->size);
7899    }
7900  else
7901    {
7902      header = REP_DELTA "\n";
7903    }
7904  SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
7905                                 pool));
7906
7907  SVN_ERR(get_file_offset(&delta_start, file, pool));
7908  file_stream = svn_stream_from_aprfile2(file, TRUE, pool);
7909
7910  /* Prepare to write the svndiff data. */
7911  svn_txdelta_to_svndiff3(&diff_wh,
7912                          &diff_whb,
7913                          file_stream,
7914                          diff_version,
7915                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
7916                          pool);
7917
7918  whb = apr_pcalloc(pool, sizeof(*whb));
7919  whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source, pool);
7920  whb->size = 0;
7921  whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7922  whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7923
7924  /* serialize the hash */
7925  stream = svn_stream_create(whb, pool);
7926  svn_stream_set_write(stream, write_hash_handler);
7927
7928  SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
7929  SVN_ERR(svn_stream_close(whb->stream));
7930
7931  /* Store the results. */
7932  SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
7933  SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
7934
7935  /* Check and see if we already have a representation somewhere that's
7936     identical to the one we just wrote out. */
7937  SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
7938
7939  if (old_rep)
7940    {
7941      /* We need to erase from the protorev the data we just wrote. */
7942      SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
7943
7944      /* Use the old rep for this content. */
7945      memcpy(rep, old_rep, sizeof (*rep));
7946    }
7947  else
7948    {
7949      /* Write out our cosmetic end marker. */
7950      SVN_ERR(get_file_offset(&rep_end, file, pool));
7951      SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n"));
7952
7953      /* update the representation */
7954      rep->expanded_size = whb->size;
7955      rep->size = rep_end - delta_start;
7956    }
7957
7958  return SVN_NO_ERROR;
7959}
7960
7961/* Sanity check ROOT_NODEREV, a candidate for being the root node-revision
7962   of (not yet committed) revision REV in FS.  Use POOL for temporary
7963   allocations.
7964
7965   If you change this function, consider updating svn_fs_fs__verify() too.
7966 */
7967static svn_error_t *
7968validate_root_noderev(svn_fs_t *fs,
7969                      node_revision_t *root_noderev,
7970                      svn_revnum_t rev,
7971                      apr_pool_t *pool)
7972{
7973  svn_revnum_t head_revnum = rev-1;
7974  int head_predecessor_count;
7975
7976  SVN_ERR_ASSERT(rev > 0);
7977
7978  /* Compute HEAD_PREDECESSOR_COUNT. */
7979  {
7980    svn_fs_root_t *head_revision;
7981    const svn_fs_id_t *head_root_id;
7982    node_revision_t *head_root_noderev;
7983
7984    /* Get /@HEAD's noderev. */
7985    SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool));
7986    SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool));
7987    SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id,
7988                                         pool));
7989
7990    head_predecessor_count = head_root_noderev->predecessor_count;
7991  }
7992
7993  /* Check that the root noderev's predecessor count equals REV.
7994
7995     This kind of corruption was seen on svn.apache.org (both on
7996     the root noderev and on other fspaths' noderevs); see
7997     issue #4129.
7998
7999     Normally (rev == root_noderev->predecessor_count), but here we
8000     use a more roundabout check that should only trigger on new instances
8001     of the corruption, rather then trigger on each and every new commit
8002     to a repository that has triggered the bug somewhere in its root
8003     noderev's history.
8004   */
8005  if (root_noderev->predecessor_count != -1
8006      && (root_noderev->predecessor_count - head_predecessor_count)
8007         != (rev - head_revnum))
8008    {
8009      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
8010                               _("predecessor count for "
8011                                 "the root node-revision is wrong: "
8012                                 "found (%d+%ld != %d), committing r%ld"),
8013                                 head_predecessor_count,
8014                                 rev - head_revnum, /* This is equal to 1. */
8015                                 root_noderev->predecessor_count,
8016                                 rev);
8017    }
8018
8019  return SVN_NO_ERROR;
8020}
8021
8022/* Copy a node-revision specified by id ID in fileystem FS from a
8023   transaction into the proto-rev-file FILE.  Set *NEW_ID_P to a
8024   pointer to the new node-id which will be allocated in POOL.
8025   If this is a directory, copy all children as well.
8026
8027   START_NODE_ID and START_COPY_ID are
8028   the first available node and copy ids for this filesystem, for older
8029   FS formats.
8030
8031   REV is the revision number that this proto-rev-file will represent.
8032
8033   INITIAL_OFFSET is the offset of the proto-rev-file on entry to
8034   commit_body.
8035
8036   If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
8037   REPS_POOL) of each data rep that is new in this revision.
8038
8039   If REPS_HASH is not NULL, append copies (allocated in REPS_POOL)
8040   of the representations of each property rep that is new in this
8041   revision.
8042
8043   AT_ROOT is true if the node revision being written is the root
8044   node-revision.  It is only controls additional sanity checking
8045   logic.
8046
8047   Temporary allocations are also from POOL. */
8048static svn_error_t *
8049write_final_rev(const svn_fs_id_t **new_id_p,
8050                apr_file_t *file,
8051                svn_revnum_t rev,
8052                svn_fs_t *fs,
8053                const svn_fs_id_t *id,
8054                const char *start_node_id,
8055                const char *start_copy_id,
8056                apr_off_t initial_offset,
8057                apr_array_header_t *reps_to_cache,
8058                apr_hash_t *reps_hash,
8059                apr_pool_t *reps_pool,
8060                svn_boolean_t at_root,
8061                apr_pool_t *pool)
8062{
8063  node_revision_t *noderev;
8064  apr_off_t my_offset;
8065  char my_node_id_buf[MAX_KEY_SIZE + 2];
8066  char my_copy_id_buf[MAX_KEY_SIZE + 2];
8067  const svn_fs_id_t *new_id;
8068  const char *node_id, *copy_id, *my_node_id, *my_copy_id;
8069  fs_fs_data_t *ffd = fs->fsap_data;
8070
8071  *new_id_p = NULL;
8072
8073  /* Check to see if this is a transaction node. */
8074  if (! svn_fs_fs__id_txn_id(id))
8075    return SVN_NO_ERROR;
8076
8077  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
8078
8079  if (noderev->kind == svn_node_dir)
8080    {
8081      apr_pool_t *subpool;
8082      apr_hash_t *entries, *str_entries;
8083      apr_array_header_t *sorted_entries;
8084      int i;
8085
8086      /* This is a directory.  Write out all the children first. */
8087      subpool = svn_pool_create(pool);
8088
8089      SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool));
8090      /* For the sake of the repository administrator sort the entries
8091         so that the final file is deterministic and repeatable,
8092         however the rest of the FSFS code doesn't require any
8093         particular order here. */
8094      sorted_entries = svn_sort__hash(entries, svn_sort_compare_items_lexically,
8095                                      pool);
8096      for (i = 0; i < sorted_entries->nelts; ++i)
8097        {
8098          svn_fs_dirent_t *dirent = APR_ARRAY_IDX(sorted_entries, i,
8099                                                  svn_sort__item_t).value;
8100
8101          svn_pool_clear(subpool);
8102          SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
8103                                  start_node_id, start_copy_id, initial_offset,
8104                                  reps_to_cache, reps_hash, reps_pool, FALSE,
8105                                  subpool));
8106          if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
8107            dirent->id = svn_fs_fs__id_copy(new_id, pool);
8108        }
8109      svn_pool_destroy(subpool);
8110
8111      if (noderev->data_rep && noderev->data_rep->txn_id)
8112        {
8113          /* Write out the contents of this directory as a text rep. */
8114          SVN_ERR(unparse_dir_entries(&str_entries, entries, pool));
8115
8116          noderev->data_rep->txn_id = NULL;
8117          noderev->data_rep->revision = rev;
8118
8119          if (ffd->deltify_directories)
8120            SVN_ERR(write_hash_delta_rep(noderev->data_rep, file,
8121                                         str_entries, fs, noderev, NULL,
8122                                         FALSE, pool));
8123          else
8124            SVN_ERR(write_hash_rep(noderev->data_rep, file, str_entries,
8125                                   fs, NULL, pool));
8126        }
8127    }
8128  else
8129    {
8130      /* This is a file.  We should make sure the data rep, if it
8131         exists in a "this" state, gets rewritten to our new revision
8132         num. */
8133
8134      if (noderev->data_rep && noderev->data_rep->txn_id)
8135        {
8136          noderev->data_rep->txn_id = NULL;
8137          noderev->data_rep->revision = rev;
8138
8139          /* See issue 3845.  Some unknown mechanism caused the
8140             protorev file to get truncated, so check for that
8141             here.  */
8142          if (noderev->data_rep->offset + noderev->data_rep->size
8143              > initial_offset)
8144            return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
8145                                    _("Truncated protorev file detected"));
8146        }
8147    }
8148
8149  /* Fix up the property reps. */
8150  if (noderev->prop_rep && noderev->prop_rep->txn_id)
8151    {
8152      apr_hash_t *proplist;
8153      SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
8154
8155      noderev->prop_rep->txn_id = NULL;
8156      noderev->prop_rep->revision = rev;
8157
8158      if (ffd->deltify_properties)
8159        SVN_ERR(write_hash_delta_rep(noderev->prop_rep, file,
8160                                     proplist, fs, noderev, reps_hash,
8161                                     TRUE, pool));
8162      else
8163        SVN_ERR(write_hash_rep(noderev->prop_rep, file, proplist,
8164                               fs, reps_hash, pool));
8165    }
8166
8167
8168  /* Convert our temporary ID into a permanent revision one. */
8169  SVN_ERR(get_file_offset(&my_offset, file, pool));
8170
8171  node_id = svn_fs_fs__id_node_id(noderev->id);
8172  if (*node_id == '_')
8173    {
8174      if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8175        my_node_id = apr_psprintf(pool, "%s-%ld", node_id + 1, rev);
8176      else
8177        {
8178          svn_fs_fs__add_keys(start_node_id, node_id + 1, my_node_id_buf);
8179          my_node_id = my_node_id_buf;
8180        }
8181    }
8182  else
8183    my_node_id = node_id;
8184
8185  copy_id = svn_fs_fs__id_copy_id(noderev->id);
8186  if (*copy_id == '_')
8187    {
8188      if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8189        my_copy_id = apr_psprintf(pool, "%s-%ld", copy_id + 1, rev);
8190      else
8191        {
8192          svn_fs_fs__add_keys(start_copy_id, copy_id + 1, my_copy_id_buf);
8193          my_copy_id = my_copy_id_buf;
8194        }
8195    }
8196  else
8197    my_copy_id = copy_id;
8198
8199  if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
8200    noderev->copyroot_rev = rev;
8201
8202  new_id = svn_fs_fs__id_rev_create(my_node_id, my_copy_id, rev, my_offset,
8203                                    pool);
8204
8205  noderev->id = new_id;
8206
8207  if (ffd->rep_sharing_allowed)
8208    {
8209      /* Save the data representation's hash in the rep cache. */
8210      if (   noderev->data_rep && noderev->kind == svn_node_file
8211          && noderev->data_rep->revision == rev)
8212        {
8213          SVN_ERR_ASSERT(reps_to_cache && reps_pool);
8214          APR_ARRAY_PUSH(reps_to_cache, representation_t *)
8215            = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool);
8216        }
8217
8218      if (noderev->prop_rep && noderev->prop_rep->revision == rev)
8219        {
8220          /* Add new property reps to hash and on-disk cache. */
8221          representation_t *copy
8222            = svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool);
8223
8224          SVN_ERR_ASSERT(reps_to_cache && reps_pool);
8225          APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy;
8226
8227          apr_hash_set(reps_hash,
8228                        copy->sha1_checksum->digest,
8229                        APR_SHA1_DIGESTSIZE,
8230                        copy);
8231        }
8232    }
8233
8234  /* don't serialize SHA1 for dirs to disk (waste of space) */
8235  if (noderev->data_rep && noderev->kind == svn_node_dir)
8236    noderev->data_rep->sha1_checksum = NULL;
8237
8238  /* don't serialize SHA1 for props to disk (waste of space) */
8239  if (noderev->prop_rep)
8240    noderev->prop_rep->sha1_checksum = NULL;
8241
8242  /* Workaround issue #4031: is-fresh-txn-root in revision files. */
8243  noderev->is_fresh_txn_root = FALSE;
8244
8245  /* Write out our new node-revision. */
8246  if (at_root)
8247    SVN_ERR(validate_root_noderev(fs, noderev, rev, pool));
8248
8249  SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(file, TRUE, pool),
8250                                   noderev, ffd->format,
8251                                   svn_fs_fs__fs_supports_mergeinfo(fs),
8252                                   pool));
8253
8254  /* Return our ID that references the revision file. */
8255  *new_id_p = noderev->id;
8256
8257  return SVN_NO_ERROR;
8258}
8259
8260/* Write the changed path info from transaction TXN_ID in filesystem
8261   FS to the permanent rev-file FILE.  *OFFSET_P is set the to offset
8262   in the file of the beginning of this information.  Perform
8263   temporary allocations in POOL. */
8264static svn_error_t *
8265write_final_changed_path_info(apr_off_t *offset_p,
8266                              apr_file_t *file,
8267                              svn_fs_t *fs,
8268                              const char *txn_id,
8269                              apr_pool_t *pool)
8270{
8271  apr_hash_t *changed_paths;
8272  apr_off_t offset;
8273  apr_pool_t *iterpool = svn_pool_create(pool);
8274  fs_fs_data_t *ffd = fs->fsap_data;
8275  svn_boolean_t include_node_kinds =
8276      ffd->format >= SVN_FS_FS__MIN_KIND_IN_CHANGED_FORMAT;
8277  apr_array_header_t *sorted_changed_paths;
8278  int i;
8279
8280  SVN_ERR(get_file_offset(&offset, file, pool));
8281
8282  SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, fs, txn_id, pool));
8283  /* For the sake of the repository administrator sort the changes so
8284     that the final file is deterministic and repeatable, however the
8285     rest of the FSFS code doesn't require any particular order here. */
8286  sorted_changed_paths = svn_sort__hash(changed_paths,
8287                                        svn_sort_compare_items_lexically, pool);
8288
8289  /* Iterate through the changed paths one at a time, and convert the
8290     temporary node-id into a permanent one for each change entry. */
8291  for (i = 0; i < sorted_changed_paths->nelts; ++i)
8292    {
8293      node_revision_t *noderev;
8294      const svn_fs_id_t *id;
8295      svn_fs_path_change2_t *change;
8296      const char *path;
8297
8298      svn_pool_clear(iterpool);
8299
8300      change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value;
8301      path = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).key;
8302
8303      id = change->node_rev_id;
8304
8305      /* If this was a delete of a mutable node, then it is OK to
8306         leave the change entry pointing to the non-existent temporary
8307         node, since it will never be used. */
8308      if ((change->change_kind != svn_fs_path_change_delete) &&
8309          (! svn_fs_fs__id_txn_id(id)))
8310        {
8311          SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, iterpool));
8312
8313          /* noderev has the permanent node-id at this point, so we just
8314             substitute it for the temporary one. */
8315          change->node_rev_id = noderev->id;
8316        }
8317
8318      /* Write out the new entry into the final rev-file. */
8319      SVN_ERR(write_change_entry(file, path, change, include_node_kinds,
8320                                 iterpool));
8321    }
8322
8323  svn_pool_destroy(iterpool);
8324
8325  *offset_p = offset;
8326
8327  return SVN_NO_ERROR;
8328}
8329
8330/* Atomically update the 'current' file to hold the specifed REV,
8331   NEXT_NODE_ID, and NEXT_COPY_ID.  (The two next-ID parameters are
8332   ignored and may be NULL if the FS format does not use them.)
8333   Perform temporary allocations in POOL. */
8334static svn_error_t *
8335write_current(svn_fs_t *fs, svn_revnum_t rev, const char *next_node_id,
8336              const char *next_copy_id, apr_pool_t *pool)
8337{
8338  char *buf;
8339  const char *tmp_name, *name;
8340  fs_fs_data_t *ffd = fs->fsap_data;
8341
8342  /* Now we can just write out this line. */
8343  if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8344    buf = apr_psprintf(pool, "%ld\n", rev);
8345  else
8346    buf = apr_psprintf(pool, "%ld %s %s\n", rev, next_node_id, next_copy_id);
8347
8348  name = svn_fs_fs__path_current(fs, pool);
8349  SVN_ERR(svn_io_write_unique(&tmp_name,
8350                              svn_dirent_dirname(name, pool),
8351                              buf, strlen(buf),
8352                              svn_io_file_del_none, pool));
8353
8354  return move_into_place(tmp_name, name, name, pool);
8355}
8356
8357/* Open a new svn_fs_t handle to FS, set that handle's concept of "current
8358   youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on
8359   NEW_REV's revision root.
8360
8361   Intended to be called as the very last step in a commit before 'current'
8362   is bumped.  This implies that we are holding the write lock. */
8363static svn_error_t *
8364verify_as_revision_before_current_plus_plus(svn_fs_t *fs,
8365                                            svn_revnum_t new_rev,
8366                                            apr_pool_t *pool)
8367{
8368#ifdef SVN_DEBUG
8369  fs_fs_data_t *ffd = fs->fsap_data;
8370  svn_fs_t *ft; /* fs++ == ft */
8371  svn_fs_root_t *root;
8372  fs_fs_data_t *ft_ffd;
8373  apr_hash_t *fs_config;
8374
8375  SVN_ERR_ASSERT(ffd->svn_fs_open_);
8376
8377  /* make sure FT does not simply return data cached by other instances
8378   * but actually retrieves it from disk at least once.
8379   */
8380  fs_config = apr_hash_make(pool);
8381  svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
8382                           svn_uuid_generate(pool));
8383  SVN_ERR(ffd->svn_fs_open_(&ft, fs->path,
8384                            fs_config,
8385                            pool));
8386  ft_ffd = ft->fsap_data;
8387  /* Don't let FT consult rep-cache.db, either. */
8388  ft_ffd->rep_sharing_allowed = FALSE;
8389
8390  /* Time travel! */
8391  ft_ffd->youngest_rev_cache = new_rev;
8392
8393  SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool));
8394  SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev);
8395  SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev);
8396  SVN_ERR(svn_fs_fs__verify_root(root, pool));
8397#endif /* SVN_DEBUG */
8398
8399  return SVN_NO_ERROR;
8400}
8401
8402/* Update the 'current' file to hold the correct next node and copy_ids
8403   from transaction TXN_ID in filesystem FS.  The current revision is
8404   set to REV.  Perform temporary allocations in POOL. */
8405static svn_error_t *
8406write_final_current(svn_fs_t *fs,
8407                    const char *txn_id,
8408                    svn_revnum_t rev,
8409                    const char *start_node_id,
8410                    const char *start_copy_id,
8411                    apr_pool_t *pool)
8412{
8413  const char *txn_node_id, *txn_copy_id;
8414  char new_node_id[MAX_KEY_SIZE + 2];
8415  char new_copy_id[MAX_KEY_SIZE + 2];
8416  fs_fs_data_t *ffd = fs->fsap_data;
8417
8418  if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8419    return write_current(fs, rev, NULL, NULL, pool);
8420
8421  /* To find the next available ids, we add the id that used to be in
8422     the 'current' file, to the next ids from the transaction file. */
8423  SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool));
8424
8425  svn_fs_fs__add_keys(start_node_id, txn_node_id, new_node_id);
8426  svn_fs_fs__add_keys(start_copy_id, txn_copy_id, new_copy_id);
8427
8428  return write_current(fs, rev, new_node_id, new_copy_id, pool);
8429}
8430
8431/* Verify that the user registed with FS has all the locks necessary to
8432   permit all the changes associate with TXN_NAME.
8433   The FS write lock is assumed to be held by the caller. */
8434static svn_error_t *
8435verify_locks(svn_fs_t *fs,
8436             const char *txn_name,
8437             apr_pool_t *pool)
8438{
8439  apr_pool_t *subpool = svn_pool_create(pool);
8440  apr_hash_t *changes;
8441  apr_hash_index_t *hi;
8442  apr_array_header_t *changed_paths;
8443  svn_stringbuf_t *last_recursed = NULL;
8444  int i;
8445
8446  /* Fetch the changes for this transaction. */
8447  SVN_ERR(svn_fs_fs__txn_changes_fetch(&changes, fs, txn_name, pool));
8448
8449  /* Make an array of the changed paths, and sort them depth-first-ily.  */
8450  changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1,
8451                                 sizeof(const char *));
8452  for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
8453    APR_ARRAY_PUSH(changed_paths, const char *) = svn__apr_hash_index_key(hi);
8454  qsort(changed_paths->elts, changed_paths->nelts,
8455        changed_paths->elt_size, svn_sort_compare_paths);
8456
8457  /* Now, traverse the array of changed paths, verify locks.  Note
8458     that if we need to do a recursive verification a path, we'll skip
8459     over children of that path when we get to them. */
8460  for (i = 0; i < changed_paths->nelts; i++)
8461    {
8462      const char *path;
8463      svn_fs_path_change2_t *change;
8464      svn_boolean_t recurse = TRUE;
8465
8466      svn_pool_clear(subpool);
8467      path = APR_ARRAY_IDX(changed_paths, i, const char *);
8468
8469      /* If this path has already been verified as part of a recursive
8470         check of one of its parents, no need to do it again.  */
8471      if (last_recursed
8472          && svn_dirent_is_child(last_recursed->data, path, subpool))
8473        continue;
8474
8475      /* Fetch the change associated with our path.  */
8476      change = svn_hash_gets(changes, path);
8477
8478      /* What does it mean to succeed at lock verification for a given
8479         path?  For an existing file or directory getting modified
8480         (text, props), it means we hold the lock on the file or
8481         directory.  For paths being added or removed, we need to hold
8482         the locks for that path and any children of that path.
8483
8484         WHEW!  We have no reliable way to determine the node kind
8485         of deleted items, but fortunately we are going to do a
8486         recursive check on deleted paths regardless of their kind.  */
8487      if (change->change_kind == svn_fs_path_change_modify)
8488        recurse = FALSE;
8489      SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE,
8490                                                subpool));
8491
8492      /* If we just did a recursive check, remember the path we
8493         checked (so children can be skipped).  */
8494      if (recurse)
8495        {
8496          if (! last_recursed)
8497            last_recursed = svn_stringbuf_create(path, pool);
8498          else
8499            svn_stringbuf_set(last_recursed, path);
8500        }
8501    }
8502  svn_pool_destroy(subpool);
8503  return SVN_NO_ERROR;
8504}
8505
8506/* Baton used for commit_body below. */
8507struct commit_baton {
8508  svn_revnum_t *new_rev_p;
8509  svn_fs_t *fs;
8510  svn_fs_txn_t *txn;
8511  apr_array_header_t *reps_to_cache;
8512  apr_hash_t *reps_hash;
8513  apr_pool_t *reps_pool;
8514};
8515
8516/* The work-horse for svn_fs_fs__commit, called with the FS write lock.
8517   This implements the svn_fs_fs__with_write_lock() 'body' callback
8518   type.  BATON is a 'struct commit_baton *'. */
8519static svn_error_t *
8520commit_body(void *baton, apr_pool_t *pool)
8521{
8522  struct commit_baton *cb = baton;
8523  fs_fs_data_t *ffd = cb->fs->fsap_data;
8524  const char *old_rev_filename, *rev_filename, *proto_filename;
8525  const char *revprop_filename, *final_revprop;
8526  const svn_fs_id_t *root_id, *new_root_id;
8527  const char *start_node_id = NULL, *start_copy_id = NULL;
8528  svn_revnum_t old_rev, new_rev;
8529  apr_file_t *proto_file;
8530  void *proto_file_lockcookie;
8531  apr_off_t initial_offset, changed_path_offset;
8532  char *buf;
8533  apr_hash_t *txnprops;
8534  apr_array_header_t *txnprop_list;
8535  svn_prop_t prop;
8536  svn_string_t date;
8537
8538  /* Get the current youngest revision. */
8539  SVN_ERR(svn_fs_fs__youngest_rev(&old_rev, cb->fs, pool));
8540
8541  /* Check to make sure this transaction is based off the most recent
8542     revision. */
8543  if (cb->txn->base_rev != old_rev)
8544    return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
8545                            _("Transaction out of date"));
8546
8547  /* Locks may have been added (or stolen) between the calling of
8548     previous svn_fs.h functions and svn_fs_commit_txn(), so we need
8549     to re-examine every changed-path in the txn and re-verify all
8550     discovered locks. */
8551  SVN_ERR(verify_locks(cb->fs, cb->txn->id, pool));
8552
8553  /* Get the next node_id and copy_id to use. */
8554  if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8555    SVN_ERR(get_next_revision_ids(&start_node_id, &start_copy_id, cb->fs,
8556                                  pool));
8557
8558  /* We are going to be one better than this puny old revision. */
8559  new_rev = old_rev + 1;
8560
8561  /* Get a write handle on the proto revision file. */
8562  SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
8563                                 cb->fs, cb->txn->id, pool));
8564  SVN_ERR(get_file_offset(&initial_offset, proto_file, pool));
8565
8566  /* Write out all the node-revisions and directory contents. */
8567  root_id = svn_fs_fs__id_txn_create("0", "0", cb->txn->id, pool);
8568  SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
8569                          start_node_id, start_copy_id, initial_offset,
8570                          cb->reps_to_cache, cb->reps_hash, cb->reps_pool,
8571                          TRUE, pool));
8572
8573  /* Write the changed-path information. */
8574  SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
8575                                        cb->fs, cb->txn->id, pool));
8576
8577  /* Write the final line. */
8578  buf = apr_psprintf(pool, "\n%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
8579                     svn_fs_fs__id_offset(new_root_id),
8580                     changed_path_offset);
8581  SVN_ERR(svn_io_file_write_full(proto_file, buf, strlen(buf), NULL,
8582                                 pool));
8583  SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
8584  SVN_ERR(svn_io_file_close(proto_file, pool));
8585
8586  /* We don't unlock the prototype revision file immediately to avoid a
8587     race with another caller writing to the prototype revision file
8588     before we commit it. */
8589
8590  /* Remove any temporary txn props representing 'flags'. */
8591  SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, cb->txn, pool));
8592  txnprop_list = apr_array_make(pool, 3, sizeof(svn_prop_t));
8593  prop.value = NULL;
8594
8595  if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
8596    {
8597      prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
8598      APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop;
8599    }
8600
8601  if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
8602    {
8603      prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
8604      APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop;
8605    }
8606
8607  if (! apr_is_empty_array(txnprop_list))
8608    SVN_ERR(svn_fs_fs__change_txn_props(cb->txn, txnprop_list, pool));
8609
8610  /* Create the shard for the rev and revprop file, if we're sharding and
8611     this is the first revision of a new shard.  We don't care if this
8612     fails because the shard already existed for some reason. */
8613  if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0)
8614    {
8615      /* Create the revs shard. */
8616        {
8617          const char *new_dir = path_rev_shard(cb->fs, new_rev, pool);
8618          svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
8619          if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
8620            return svn_error_trace(err);
8621          svn_error_clear(err);
8622          SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
8623                                                    PATH_REVS_DIR,
8624                                                    pool),
8625                                    new_dir, pool));
8626        }
8627
8628      /* Create the revprops shard. */
8629      SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev));
8630        {
8631          const char *new_dir = path_revprops_shard(cb->fs, new_rev, pool);
8632          svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
8633          if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
8634            return svn_error_trace(err);
8635          svn_error_clear(err);
8636          SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
8637                                                    PATH_REVPROPS_DIR,
8638                                                    pool),
8639                                    new_dir, pool));
8640        }
8641    }
8642
8643  /* Move the finished rev file into place. */
8644  SVN_ERR(svn_fs_fs__path_rev_absolute(&old_rev_filename,
8645                                       cb->fs, old_rev, pool));
8646  rev_filename = path_rev(cb->fs, new_rev, pool);
8647  proto_filename = path_txn_proto_rev(cb->fs, cb->txn->id, pool);
8648  SVN_ERR(move_into_place(proto_filename, rev_filename, old_rev_filename,
8649                          pool));
8650
8651  /* Now that we've moved the prototype revision file out of the way,
8652     we can unlock it (since further attempts to write to the file
8653     will fail as it no longer exists).  We must do this so that we can
8654     remove the transaction directory later. */
8655  SVN_ERR(unlock_proto_rev(cb->fs, cb->txn->id, proto_file_lockcookie, pool));
8656
8657  /* Update commit time to ensure that svn:date revprops remain ordered. */
8658  date.data = svn_time_to_cstring(apr_time_now(), pool);
8659  date.len = strlen(date.data);
8660
8661  SVN_ERR(svn_fs_fs__change_txn_prop(cb->txn, SVN_PROP_REVISION_DATE,
8662                                     &date, pool));
8663
8664  /* Move the revprops file into place. */
8665  SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev));
8666  revprop_filename = path_txn_props(cb->fs, cb->txn->id, pool);
8667  final_revprop = path_revprops(cb->fs, new_rev, pool);
8668  SVN_ERR(move_into_place(revprop_filename, final_revprop,
8669                          old_rev_filename, pool));
8670
8671  /* Update the 'current' file. */
8672  SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool));
8673  SVN_ERR(write_final_current(cb->fs, cb->txn->id, new_rev, start_node_id,
8674                              start_copy_id, pool));
8675
8676  /* At this point the new revision is committed and globally visible
8677     so let the caller know it succeeded by giving it the new revision
8678     number, which fulfills svn_fs_commit_txn() contract.  Any errors
8679     after this point do not change the fact that a new revision was
8680     created. */
8681  *cb->new_rev_p = new_rev;
8682
8683  ffd->youngest_rev_cache = new_rev;
8684
8685  /* Remove this transaction directory. */
8686  SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool));
8687
8688  return SVN_NO_ERROR;
8689}
8690
8691/* Add the representations in REPS_TO_CACHE (an array of representation_t *)
8692 * to the rep-cache database of FS. */
8693static svn_error_t *
8694write_reps_to_cache(svn_fs_t *fs,
8695                    const apr_array_header_t *reps_to_cache,
8696                    apr_pool_t *scratch_pool)
8697{
8698  int i;
8699
8700  for (i = 0; i < reps_to_cache->nelts; i++)
8701    {
8702      representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *);
8703
8704      /* FALSE because we don't care if another parallel commit happened to
8705       * collide with us.  (Non-parallel collisions will not be detected.) */
8706      SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, FALSE, scratch_pool));
8707    }
8708
8709  return SVN_NO_ERROR;
8710}
8711
8712svn_error_t *
8713svn_fs_fs__commit(svn_revnum_t *new_rev_p,
8714                  svn_fs_t *fs,
8715                  svn_fs_txn_t *txn,
8716                  apr_pool_t *pool)
8717{
8718  struct commit_baton cb;
8719  fs_fs_data_t *ffd = fs->fsap_data;
8720
8721  cb.new_rev_p = new_rev_p;
8722  cb.fs = fs;
8723  cb.txn = txn;
8724
8725  if (ffd->rep_sharing_allowed)
8726    {
8727      cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *));
8728      cb.reps_hash = apr_hash_make(pool);
8729      cb.reps_pool = pool;
8730    }
8731  else
8732    {
8733      cb.reps_to_cache = NULL;
8734      cb.reps_hash = NULL;
8735      cb.reps_pool = NULL;
8736    }
8737
8738  SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool));
8739
8740  /* At this point, *NEW_REV_P has been set, so errors below won't affect
8741     the success of the commit.  (See svn_fs_commit_txn().)  */
8742
8743  if (ffd->rep_sharing_allowed)
8744    {
8745      SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
8746
8747      /* Write new entries to the rep-sharing database.
8748       *
8749       * We use an sqlite transaction to speed things up;
8750       * see <http://www.sqlite.org/faq.html#q19>.
8751       */
8752      SVN_SQLITE__WITH_TXN(
8753        write_reps_to_cache(fs, cb.reps_to_cache, pool),
8754        ffd->rep_cache_db);
8755    }
8756
8757  return SVN_NO_ERROR;
8758}
8759
8760
8761svn_error_t *
8762svn_fs_fs__reserve_copy_id(const char **copy_id_p,
8763                           svn_fs_t *fs,
8764                           const char *txn_id,
8765                           apr_pool_t *pool)
8766{
8767  const char *cur_node_id, *cur_copy_id;
8768  char *copy_id;
8769  apr_size_t len;
8770
8771  /* First read in the current next-ids file. */
8772  SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
8773
8774  copy_id = apr_pcalloc(pool, strlen(cur_copy_id) + 2);
8775
8776  len = strlen(cur_copy_id);
8777  svn_fs_fs__next_key(cur_copy_id, &len, copy_id);
8778
8779  SVN_ERR(write_next_ids(fs, txn_id, cur_node_id, copy_id, pool));
8780
8781  *copy_id_p = apr_pstrcat(pool, "_", cur_copy_id, (char *)NULL);
8782
8783  return SVN_NO_ERROR;
8784}
8785
8786/* Write out the zeroth revision for filesystem FS. */
8787static svn_error_t *
8788write_revision_zero(svn_fs_t *fs)
8789{
8790  const char *path_revision_zero = path_rev(fs, 0, fs->pool);
8791  apr_hash_t *proplist;
8792  svn_string_t date;
8793
8794  /* Write out a rev file for revision 0. */
8795  SVN_ERR(svn_io_file_create(path_revision_zero,
8796                             "PLAIN\nEND\nENDREP\n"
8797                             "id: 0.0.r0/17\n"
8798                             "type: dir\n"
8799                             "count: 0\n"
8800                             "text: 0 0 4 4 "
8801                             "2d2977d1c96f487abe4a1e202dd03b4e\n"
8802                             "cpath: /\n"
8803                             "\n\n17 107\n", fs->pool));
8804  SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, fs->pool));
8805
8806  /* Set a date on revision 0. */
8807  date.data = svn_time_to_cstring(apr_time_now(), fs->pool);
8808  date.len = strlen(date.data);
8809  proplist = apr_hash_make(fs->pool);
8810  svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date);
8811  return set_revision_proplist(fs, 0, proplist, fs->pool);
8812}
8813
8814svn_error_t *
8815svn_fs_fs__create(svn_fs_t *fs,
8816                  const char *path,
8817                  apr_pool_t *pool)
8818{
8819  int format = SVN_FS_FS__FORMAT_NUMBER;
8820  fs_fs_data_t *ffd = fs->fsap_data;
8821
8822  fs->path = apr_pstrdup(pool, path);
8823  /* See if compatibility with older versions was explicitly requested. */
8824  if (fs->config)
8825    {
8826      if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE))
8827        format = 1;
8828      else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE))
8829        format = 2;
8830      else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE))
8831        format = 3;
8832      else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE))
8833        format = 4;
8834    }
8835  ffd->format = format;
8836
8837  /* Override the default linear layout if this is a new-enough format. */
8838  if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
8839    ffd->max_files_per_dir = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
8840
8841  /* Create the revision data directories. */
8842  if (ffd->max_files_per_dir)
8843    SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(fs, 0, pool), pool));
8844  else
8845    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_REVS_DIR,
8846                                                        pool),
8847                                        pool));
8848
8849  /* Create the revprops directory. */
8850  if (ffd->max_files_per_dir)
8851    SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(fs, 0, pool),
8852                                        pool));
8853  else
8854    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path,
8855                                                        PATH_REVPROPS_DIR,
8856                                                        pool),
8857                                        pool));
8858
8859  /* Create the transaction directory. */
8860  SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXNS_DIR,
8861                                                      pool),
8862                                      pool));
8863
8864  /* Create the protorevs directory. */
8865  if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
8866    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXN_PROTOS_DIR,
8867                                                      pool),
8868                                        pool));
8869
8870  /* Create the 'current' file. */
8871  SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(fs, pool),
8872                             (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
8873                              ? "0\n" : "0 1 1\n"),
8874                             pool));
8875  SVN_ERR(svn_io_file_create(path_lock(fs, pool), "", pool));
8876  SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, pool));
8877
8878  SVN_ERR(write_revision_zero(fs));
8879
8880  /* Create the fsfs.conf file if supported.  Older server versions would
8881     simply ignore the file but that might result in a different behavior
8882     than with the later releases.  Also, hotcopy would ignore, i.e. not
8883     copy, a fsfs.conf with old formats. */
8884  if (ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
8885    SVN_ERR(write_config(fs, pool));
8886
8887  SVN_ERR(read_config(ffd, fs->path, pool));
8888
8889  /* Create the min unpacked rev file. */
8890  if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
8891    SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool));
8892
8893  /* Create the txn-current file if the repository supports
8894     the transaction sequence file. */
8895  if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
8896    {
8897      SVN_ERR(svn_io_file_create(path_txn_current(fs, pool),
8898                                 "0\n", pool));
8899      SVN_ERR(svn_io_file_create(path_txn_current_lock(fs, pool),
8900                                 "", pool));
8901    }
8902
8903  /* This filesystem is ready.  Stamp it with a format number. */
8904  SVN_ERR(write_format(path_format(fs, pool),
8905                       ffd->format, ffd->max_files_per_dir, FALSE, pool));
8906
8907  ffd->youngest_rev_cache = 0;
8908  return SVN_NO_ERROR;
8909}
8910
8911/* Part of the recovery procedure.  Return the largest revision *REV in
8912   filesystem FS.  Use POOL for temporary allocation. */
8913static svn_error_t *
8914recover_get_largest_revision(svn_fs_t *fs, svn_revnum_t *rev, apr_pool_t *pool)
8915{
8916  /* Discovering the largest revision in the filesystem would be an
8917     expensive operation if we did a readdir() or searched linearly,
8918     so we'll do a form of binary search.  left is a revision that we
8919     know exists, right a revision that we know does not exist. */
8920  apr_pool_t *iterpool;
8921  svn_revnum_t left, right = 1;
8922
8923  iterpool = svn_pool_create(pool);
8924  /* Keep doubling right, until we find a revision that doesn't exist. */
8925  while (1)
8926    {
8927      svn_error_t *err;
8928      apr_file_t *file;
8929
8930      err = open_pack_or_rev_file(&file, fs, right, iterpool);
8931      svn_pool_clear(iterpool);
8932
8933      if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
8934        {
8935          svn_error_clear(err);
8936          break;
8937        }
8938      else
8939        SVN_ERR(err);
8940
8941      right <<= 1;
8942    }
8943
8944  left = right >> 1;
8945
8946  /* We know that left exists and right doesn't.  Do a normal bsearch to find
8947     the last revision. */
8948  while (left + 1 < right)
8949    {
8950      svn_revnum_t probe = left + ((right - left) / 2);
8951      svn_error_t *err;
8952      apr_file_t *file;
8953
8954      err = open_pack_or_rev_file(&file, fs, probe, iterpool);
8955      svn_pool_clear(iterpool);
8956
8957      if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
8958        {
8959          svn_error_clear(err);
8960          right = probe;
8961        }
8962      else
8963        {
8964          SVN_ERR(err);
8965          left = probe;
8966        }
8967    }
8968
8969  svn_pool_destroy(iterpool);
8970
8971  /* left is now the largest revision that exists. */
8972  *rev = left;
8973  return SVN_NO_ERROR;
8974}
8975
8976/* A baton for reading a fixed amount from an open file.  For
8977   recover_find_max_ids() below. */
8978struct recover_read_from_file_baton
8979{
8980  apr_file_t *file;
8981  apr_pool_t *pool;
8982  apr_off_t remaining;
8983};
8984
8985/* A stream read handler used by recover_find_max_ids() below.
8986   Read and return at most BATON->REMAINING bytes from the stream,
8987   returning nothing after that to indicate EOF. */
8988static svn_error_t *
8989read_handler_recover(void *baton, char *buffer, apr_size_t *len)
8990{
8991  struct recover_read_from_file_baton *b = baton;
8992  svn_filesize_t bytes_to_read = *len;
8993
8994  if (b->remaining == 0)
8995    {
8996      /* Return a successful read of zero bytes to signal EOF. */
8997      *len = 0;
8998      return SVN_NO_ERROR;
8999    }
9000
9001  if (bytes_to_read > b->remaining)
9002    bytes_to_read = b->remaining;
9003  b->remaining -= bytes_to_read;
9004
9005  return svn_io_file_read_full2(b->file, buffer, (apr_size_t) bytes_to_read,
9006                                len, NULL, b->pool);
9007}
9008
9009/* Part of the recovery procedure.  Read the directory noderev at offset
9010   OFFSET of file REV_FILE (the revision file of revision REV of
9011   filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id
9012   and copy-id of that node, if greater than the current value stored
9013   in either.  Recurse into any child directories that were modified in
9014   this revision.
9015
9016   MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE.
9017
9018   Perform temporary allocation in POOL. */
9019static svn_error_t *
9020recover_find_max_ids(svn_fs_t *fs, svn_revnum_t rev,
9021                     apr_file_t *rev_file, apr_off_t offset,
9022                     char *max_node_id, char *max_copy_id,
9023                     apr_pool_t *pool)
9024{
9025  apr_hash_t *headers;
9026  char *value;
9027  representation_t *data_rep;
9028  struct rep_args *ra;
9029  struct recover_read_from_file_baton baton;
9030  svn_stream_t *stream;
9031  apr_hash_t *entries;
9032  apr_hash_index_t *hi;
9033  apr_pool_t *iterpool;
9034
9035  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
9036  SVN_ERR(read_header_block(&headers, svn_stream_from_aprfile2(rev_file, TRUE,
9037                                                               pool),
9038                            pool));
9039
9040  /* Check that this is a directory.  It should be. */
9041  value = svn_hash_gets(headers, HEADER_TYPE);
9042  if (value == NULL || strcmp(value, KIND_DIR) != 0)
9043    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9044                            _("Recovery encountered a non-directory node"));
9045
9046  /* Get the data location.  No data location indicates an empty directory. */
9047  value = svn_hash_gets(headers, HEADER_TEXT);
9048  if (!value)
9049    return SVN_NO_ERROR;
9050  SVN_ERR(read_rep_offsets(&data_rep, value, NULL, FALSE, pool));
9051
9052  /* If the directory's data representation wasn't changed in this revision,
9053     we've already scanned the directory's contents for noderevs, so we don't
9054     need to again.  This will occur if a property is changed on a directory
9055     without changing the directory's contents. */
9056  if (data_rep->revision != rev)
9057    return SVN_NO_ERROR;
9058
9059  /* We could use get_dir_contents(), but this is much cheaper.  It does
9060     rely on directory entries being stored as PLAIN reps, though. */
9061  offset = data_rep->offset;
9062  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
9063  SVN_ERR(read_rep_line(&ra, rev_file, pool));
9064  if (ra->is_delta)
9065    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9066                            _("Recovery encountered a deltified directory "
9067                              "representation"));
9068
9069  /* Now create a stream that's allowed to read only as much data as is
9070     stored in the representation. */
9071  baton.file = rev_file;
9072  baton.pool = pool;
9073  baton.remaining = data_rep->expanded_size;
9074  stream = svn_stream_create(&baton, pool);
9075  svn_stream_set_read(stream, read_handler_recover);
9076
9077  /* Now read the entries from that stream. */
9078  entries = apr_hash_make(pool);
9079  SVN_ERR(svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool));
9080  SVN_ERR(svn_stream_close(stream));
9081
9082  /* Now check each of the entries in our directory to find new node and
9083     copy ids, and recurse into new subdirectories. */
9084  iterpool = svn_pool_create(pool);
9085  for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
9086    {
9087      char *str_val;
9088      char *str;
9089      svn_node_kind_t kind;
9090      svn_fs_id_t *id;
9091      const char *node_id, *copy_id;
9092      apr_off_t child_dir_offset;
9093      const svn_string_t *path = svn__apr_hash_index_val(hi);
9094
9095      svn_pool_clear(iterpool);
9096
9097      str_val = apr_pstrdup(iterpool, path->data);
9098
9099      str = svn_cstring_tokenize(" ", &str_val);
9100      if (str == NULL)
9101        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9102                                _("Directory entry corrupt"));
9103
9104      if (strcmp(str, KIND_FILE) == 0)
9105        kind = svn_node_file;
9106      else if (strcmp(str, KIND_DIR) == 0)
9107        kind = svn_node_dir;
9108      else
9109        {
9110          return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9111                                  _("Directory entry corrupt"));
9112        }
9113
9114      str = svn_cstring_tokenize(" ", &str_val);
9115      if (str == NULL)
9116        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9117                                _("Directory entry corrupt"));
9118
9119      id = svn_fs_fs__id_parse(str, strlen(str), iterpool);
9120
9121      if (svn_fs_fs__id_rev(id) != rev)
9122        {
9123          /* If the node wasn't modified in this revision, we've already
9124             checked the node and copy id. */
9125          continue;
9126        }
9127
9128      node_id = svn_fs_fs__id_node_id(id);
9129      copy_id = svn_fs_fs__id_copy_id(id);
9130
9131      if (svn_fs_fs__key_compare(node_id, max_node_id) > 0)
9132        {
9133          SVN_ERR_ASSERT(strlen(node_id) < MAX_KEY_SIZE);
9134          apr_cpystrn(max_node_id, node_id, MAX_KEY_SIZE);
9135        }
9136      if (svn_fs_fs__key_compare(copy_id, max_copy_id) > 0)
9137        {
9138          SVN_ERR_ASSERT(strlen(copy_id) < MAX_KEY_SIZE);
9139          apr_cpystrn(max_copy_id, copy_id, MAX_KEY_SIZE);
9140        }
9141
9142      if (kind == svn_node_file)
9143        continue;
9144
9145      child_dir_offset = svn_fs_fs__id_offset(id);
9146      SVN_ERR(recover_find_max_ids(fs, rev, rev_file, child_dir_offset,
9147                                   max_node_id, max_copy_id, iterpool));
9148    }
9149  svn_pool_destroy(iterpool);
9150
9151  return SVN_NO_ERROR;
9152}
9153
9154/* Return TRUE, if for REVISION in FS, we can find the revprop pack file.
9155 * Use POOL for temporary allocations.
9156 * Set *MISSING, if the reason is a missing manifest or pack file.
9157 */
9158static svn_boolean_t
9159packed_revprop_available(svn_boolean_t *missing,
9160                         svn_fs_t *fs,
9161                         svn_revnum_t revision,
9162                         apr_pool_t *pool)
9163{
9164  fs_fs_data_t *ffd = fs->fsap_data;
9165  svn_stringbuf_t *content = NULL;
9166
9167  /* try to read the manifest file */
9168  const char *folder = path_revprops_pack_shard(fs, revision, pool);
9169  const char *manifest_path = svn_dirent_join(folder, PATH_MANIFEST, pool);
9170
9171  svn_error_t *err = try_stringbuf_from_file(&content,
9172                                             missing,
9173                                             manifest_path,
9174                                             FALSE,
9175                                             pool);
9176
9177  /* if the manifest cannot be read, consider the pack files inaccessible
9178   * even if the file itself exists. */
9179  if (err)
9180    {
9181      svn_error_clear(err);
9182      return FALSE;
9183    }
9184
9185  if (*missing)
9186    return FALSE;
9187
9188  /* parse manifest content until we find the entry for REVISION.
9189   * Revision 0 is never packed. */
9190  revision = revision < ffd->max_files_per_dir
9191           ? revision - 1
9192           : revision % ffd->max_files_per_dir;
9193  while (content->data)
9194    {
9195      char *next = strchr(content->data, '\n');
9196      if (next)
9197        {
9198          *next = 0;
9199          ++next;
9200        }
9201
9202      if (revision-- == 0)
9203        {
9204          /* the respective pack file must exist (and be a file) */
9205          svn_node_kind_t kind;
9206          err = svn_io_check_path(svn_dirent_join(folder, content->data,
9207                                                  pool),
9208                                  &kind, pool);
9209          if (err)
9210            {
9211              svn_error_clear(err);
9212              return FALSE;
9213            }
9214
9215          *missing = kind == svn_node_none;
9216          return kind == svn_node_file;
9217        }
9218
9219      content->data = next;
9220    }
9221
9222  return FALSE;
9223}
9224
9225/* Baton used for recover_body below. */
9226struct recover_baton {
9227  svn_fs_t *fs;
9228  svn_cancel_func_t cancel_func;
9229  void *cancel_baton;
9230};
9231
9232/* The work-horse for svn_fs_fs__recover, called with the FS
9233   write lock.  This implements the svn_fs_fs__with_write_lock()
9234   'body' callback type.  BATON is a 'struct recover_baton *'. */
9235static svn_error_t *
9236recover_body(void *baton, apr_pool_t *pool)
9237{
9238  struct recover_baton *b = baton;
9239  svn_fs_t *fs = b->fs;
9240  fs_fs_data_t *ffd = fs->fsap_data;
9241  svn_revnum_t max_rev;
9242  char next_node_id_buf[MAX_KEY_SIZE], next_copy_id_buf[MAX_KEY_SIZE];
9243  char *next_node_id = NULL, *next_copy_id = NULL;
9244  svn_revnum_t youngest_rev;
9245  svn_node_kind_t youngest_revprops_kind;
9246
9247  /* Lose potentially corrupted data in temp files */
9248  SVN_ERR(cleanup_revprop_namespace(fs));
9249
9250  /* We need to know the largest revision in the filesystem. */
9251  SVN_ERR(recover_get_largest_revision(fs, &max_rev, pool));
9252
9253  /* Get the expected youngest revision */
9254  SVN_ERR(get_youngest(&youngest_rev, fs->path, pool));
9255
9256  /* Policy note:
9257
9258     Since the revprops file is written after the revs file, the true
9259     maximum available revision is the youngest one for which both are
9260     present.  That's probably the same as the max_rev we just found,
9261     but if it's not, we could, in theory, repeatedly decrement
9262     max_rev until we find a revision that has both a revs and
9263     revprops file, then write db/current with that.
9264
9265     But we choose not to.  If a repository is so corrupt that it's
9266     missing at least one revprops file, we shouldn't assume that the
9267     youngest revision for which both the revs and revprops files are
9268     present is healthy.  In other words, we're willing to recover
9269     from a missing or out-of-date db/current file, because db/current
9270     is truly redundant -- it's basically a cache so we don't have to
9271     find max_rev each time, albeit a cache with unusual semantics,
9272     since it also officially defines when a revision goes live.  But
9273     if we're missing more than the cache, it's time to back out and
9274     let the admin reconstruct things by hand: correctness at that
9275     point may depend on external things like checking a commit email
9276     list, looking in particular working copies, etc.
9277
9278     This policy matches well with a typical naive backup scenario.
9279     Say you're rsyncing your FSFS repository nightly to the same
9280     location.  Once revs and revprops are written, you've got the
9281     maximum rev; if the backup should bomb before db/current is
9282     written, then db/current could stay arbitrarily out-of-date, but
9283     we can still recover.  It's a small window, but we might as well
9284     do what we can. */
9285
9286  /* Even if db/current were missing, it would be created with 0 by
9287     get_youngest(), so this conditional remains valid. */
9288  if (youngest_rev > max_rev)
9289    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9290                             _("Expected current rev to be <= %ld "
9291                               "but found %ld"), max_rev, youngest_rev);
9292
9293  /* We only need to search for maximum IDs for old FS formats which
9294     se global ID counters. */
9295  if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
9296    {
9297      /* Next we need to find the maximum node id and copy id in use across the
9298         filesystem.  Unfortunately, the only way we can get this information
9299         is to scan all the noderevs of all the revisions and keep track as
9300         we go along. */
9301      svn_revnum_t rev;
9302      apr_pool_t *iterpool = svn_pool_create(pool);
9303      char max_node_id[MAX_KEY_SIZE] = "0", max_copy_id[MAX_KEY_SIZE] = "0";
9304      apr_size_t len;
9305
9306      for (rev = 0; rev <= max_rev; rev++)
9307        {
9308          apr_file_t *rev_file;
9309          apr_off_t root_offset;
9310
9311          svn_pool_clear(iterpool);
9312
9313          if (b->cancel_func)
9314            SVN_ERR(b->cancel_func(b->cancel_baton));
9315
9316          SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, iterpool));
9317          SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file, fs, rev,
9318                                          iterpool));
9319          SVN_ERR(recover_find_max_ids(fs, rev, rev_file, root_offset,
9320                                       max_node_id, max_copy_id, iterpool));
9321          SVN_ERR(svn_io_file_close(rev_file, iterpool));
9322        }
9323      svn_pool_destroy(iterpool);
9324
9325      /* Now that we finally have the maximum revision, node-id and copy-id, we
9326         can bump the two ids to get the next of each. */
9327      len = strlen(max_node_id);
9328      svn_fs_fs__next_key(max_node_id, &len, next_node_id_buf);
9329      next_node_id = next_node_id_buf;
9330      len = strlen(max_copy_id);
9331      svn_fs_fs__next_key(max_copy_id, &len, next_copy_id_buf);
9332      next_copy_id = next_copy_id_buf;
9333    }
9334
9335  /* Before setting current, verify that there is a revprops file
9336     for the youngest revision.  (Issue #2992) */
9337  SVN_ERR(svn_io_check_path(path_revprops(fs, max_rev, pool),
9338                            &youngest_revprops_kind, pool));
9339  if (youngest_revprops_kind == svn_node_none)
9340    {
9341      svn_boolean_t missing = TRUE;
9342      if (!packed_revprop_available(&missing, fs, max_rev, pool))
9343        {
9344          if (missing)
9345            {
9346              return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9347                                      _("Revision %ld has a revs file but no "
9348                                        "revprops file"),
9349                                      max_rev);
9350            }
9351          else
9352            {
9353              return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9354                                      _("Revision %ld has a revs file but the "
9355                                        "revprops file is inaccessible"),
9356                                      max_rev);
9357            }
9358          }
9359    }
9360  else if (youngest_revprops_kind != svn_node_file)
9361    {
9362      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9363                               _("Revision %ld has a non-file where its "
9364                                 "revprops file should be"),
9365                               max_rev);
9366    }
9367
9368  /* Prune younger-than-(newfound-youngest) revisions from the rep
9369     cache if sharing is enabled taking care not to create the cache
9370     if it does not exist. */
9371  if (ffd->rep_sharing_allowed)
9372    {
9373      svn_boolean_t rep_cache_exists;
9374
9375      SVN_ERR(svn_fs_fs__exists_rep_cache(&rep_cache_exists, fs, pool));
9376      if (rep_cache_exists)
9377        SVN_ERR(svn_fs_fs__del_rep_reference(fs, max_rev, pool));
9378    }
9379
9380  /* Now store the discovered youngest revision, and the next IDs if
9381     relevant, in a new 'current' file. */
9382  return write_current(fs, max_rev, next_node_id, next_copy_id, pool);
9383}
9384
9385/* This implements the fs_library_vtable_t.recover() API. */
9386svn_error_t *
9387svn_fs_fs__recover(svn_fs_t *fs,
9388                   svn_cancel_func_t cancel_func, void *cancel_baton,
9389                   apr_pool_t *pool)
9390{
9391  struct recover_baton b;
9392
9393  /* We have no way to take out an exclusive lock in FSFS, so we're
9394     restricted as to the types of recovery we can do.  Luckily,
9395     we just want to recreate the 'current' file, and we can do that just
9396     by blocking other writers. */
9397  b.fs = fs;
9398  b.cancel_func = cancel_func;
9399  b.cancel_baton = cancel_baton;
9400  return svn_fs_fs__with_write_lock(fs, recover_body, &b, pool);
9401}
9402
9403svn_error_t *
9404svn_fs_fs__set_uuid(svn_fs_t *fs,
9405                    const char *uuid,
9406                    apr_pool_t *pool)
9407{
9408  char *my_uuid;
9409  apr_size_t my_uuid_len;
9410  const char *tmp_path;
9411  const char *uuid_path = path_uuid(fs, pool);
9412
9413  if (! uuid)
9414    uuid = svn_uuid_generate(pool);
9415
9416  /* Make sure we have a copy in FS->POOL, and append a newline. */
9417  my_uuid = apr_pstrcat(fs->pool, uuid, "\n", (char *)NULL);
9418  my_uuid_len = strlen(my_uuid);
9419
9420  SVN_ERR(svn_io_write_unique(&tmp_path,
9421                              svn_dirent_dirname(uuid_path, pool),
9422                              my_uuid, my_uuid_len,
9423                              svn_io_file_del_none, pool));
9424
9425  /* We use the permissions of the 'current' file, because the 'uuid'
9426     file does not exist during repository creation. */
9427  SVN_ERR(move_into_place(tmp_path, uuid_path,
9428                          svn_fs_fs__path_current(fs, pool), pool));
9429
9430  /* Remove the newline we added, and stash the UUID. */
9431  my_uuid[my_uuid_len - 1] = '\0';
9432  fs->uuid = my_uuid;
9433
9434  return SVN_NO_ERROR;
9435}
9436
9437/** Node origin lazy cache. */
9438
9439/* If directory PATH does not exist, create it and give it the same
9440   permissions as FS_path.*/
9441svn_error_t *
9442svn_fs_fs__ensure_dir_exists(const char *path,
9443                             const char *fs_path,
9444                             apr_pool_t *pool)
9445{
9446  svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
9447  if (err && APR_STATUS_IS_EEXIST(err->apr_err))
9448    {
9449      svn_error_clear(err);
9450      return SVN_NO_ERROR;
9451    }
9452  SVN_ERR(err);
9453
9454  /* We successfully created a new directory.  Dup the permissions
9455     from FS->path. */
9456  return svn_io_copy_perms(fs_path, path, pool);
9457}
9458
9459/* Set *NODE_ORIGINS to a hash mapping 'const char *' node IDs to
9460   'svn_string_t *' node revision IDs.  Use POOL for allocations. */
9461static svn_error_t *
9462get_node_origins_from_file(svn_fs_t *fs,
9463                           apr_hash_t **node_origins,
9464                           const char *node_origins_file,
9465                           apr_pool_t *pool)
9466{
9467  apr_file_t *fd;
9468  svn_error_t *err;
9469  svn_stream_t *stream;
9470
9471  *node_origins = NULL;
9472  err = svn_io_file_open(&fd, node_origins_file,
9473                         APR_READ, APR_OS_DEFAULT, pool);
9474  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
9475    {
9476      svn_error_clear(err);
9477      return SVN_NO_ERROR;
9478    }
9479  SVN_ERR(err);
9480
9481  stream = svn_stream_from_aprfile2(fd, FALSE, pool);
9482  *node_origins = apr_hash_make(pool);
9483  SVN_ERR(svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool));
9484  return svn_stream_close(stream);
9485}
9486
9487svn_error_t *
9488svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
9489                           svn_fs_t *fs,
9490                           const char *node_id,
9491                           apr_pool_t *pool)
9492{
9493  apr_hash_t *node_origins;
9494
9495  *origin_id = NULL;
9496  SVN_ERR(get_node_origins_from_file(fs, &node_origins,
9497                                     path_node_origin(fs, node_id, pool),
9498                                     pool));
9499  if (node_origins)
9500    {
9501      svn_string_t *origin_id_str =
9502        svn_hash_gets(node_origins, node_id);
9503      if (origin_id_str)
9504        *origin_id = svn_fs_fs__id_parse(origin_id_str->data,
9505                                         origin_id_str->len, pool);
9506    }
9507  return SVN_NO_ERROR;
9508}
9509
9510
9511/* Helper for svn_fs_fs__set_node_origin.  Takes a NODE_ID/NODE_REV_ID
9512   pair and adds it to the NODE_ORIGINS_PATH file.  */
9513static svn_error_t *
9514set_node_origins_for_file(svn_fs_t *fs,
9515                          const char *node_origins_path,
9516                          const char *node_id,
9517                          svn_string_t *node_rev_id,
9518                          apr_pool_t *pool)
9519{
9520  const char *path_tmp;
9521  svn_stream_t *stream;
9522  apr_hash_t *origins_hash;
9523  svn_string_t *old_node_rev_id;
9524
9525  SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs->path,
9526                                                       PATH_NODE_ORIGINS_DIR,
9527                                                       pool),
9528                                       fs->path, pool));
9529
9530  /* Read the previously existing origins (if any), and merge our
9531     update with it. */
9532  SVN_ERR(get_node_origins_from_file(fs, &origins_hash,
9533                                     node_origins_path, pool));
9534  if (! origins_hash)
9535    origins_hash = apr_hash_make(pool);
9536
9537  old_node_rev_id = svn_hash_gets(origins_hash, node_id);
9538
9539  if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id))
9540    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9541                             _("Node origin for '%s' exists with a different "
9542                               "value (%s) than what we were about to store "
9543                               "(%s)"),
9544                             node_id, old_node_rev_id->data, node_rev_id->data);
9545
9546  svn_hash_sets(origins_hash, node_id, node_rev_id);
9547
9548  /* Sure, there's a race condition here.  Two processes could be
9549     trying to add different cache elements to the same file at the
9550     same time, and the entries added by the first one to write will
9551     be lost.  But this is just a cache of reconstructible data, so
9552     we'll accept this problem in return for not having to deal with
9553     locking overhead. */
9554
9555  /* Create a temporary file, write out our hash, and close the file. */
9556  SVN_ERR(svn_stream_open_unique(&stream, &path_tmp,
9557                                 svn_dirent_dirname(node_origins_path, pool),
9558                                 svn_io_file_del_none, pool, pool));
9559  SVN_ERR(svn_hash_write2(origins_hash, stream, SVN_HASH_TERMINATOR, pool));
9560  SVN_ERR(svn_stream_close(stream));
9561
9562  /* Rename the temp file as the real destination */
9563  return svn_io_file_rename(path_tmp, node_origins_path, pool);
9564}
9565
9566
9567svn_error_t *
9568svn_fs_fs__set_node_origin(svn_fs_t *fs,
9569                           const char *node_id,
9570                           const svn_fs_id_t *node_rev_id,
9571                           apr_pool_t *pool)
9572{
9573  svn_error_t *err;
9574  const char *filename = path_node_origin(fs, node_id, pool);
9575
9576  err = set_node_origins_for_file(fs, filename,
9577                                  node_id,
9578                                  svn_fs_fs__id_unparse(node_rev_id, pool),
9579                                  pool);
9580  if (err && APR_STATUS_IS_EACCES(err->apr_err))
9581    {
9582      /* It's just a cache; stop trying if I can't write. */
9583      svn_error_clear(err);
9584      err = NULL;
9585    }
9586  return svn_error_trace(err);
9587}
9588
9589
9590svn_error_t *
9591svn_fs_fs__list_transactions(apr_array_header_t **names_p,
9592                             svn_fs_t *fs,
9593                             apr_pool_t *pool)
9594{
9595  const char *txn_dir;
9596  apr_hash_t *dirents;
9597  apr_hash_index_t *hi;
9598  apr_array_header_t *names;
9599  apr_size_t ext_len = strlen(PATH_EXT_TXN);
9600
9601  names = apr_array_make(pool, 1, sizeof(const char *));
9602
9603  /* Get the transactions directory. */
9604  txn_dir = svn_dirent_join(fs->path, PATH_TXNS_DIR, pool);
9605
9606  /* Now find a listing of this directory. */
9607  SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool));
9608
9609  /* Loop through all the entries and return anything that ends with '.txn'. */
9610  for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
9611    {
9612      const char *name = svn__apr_hash_index_key(hi);
9613      apr_ssize_t klen = svn__apr_hash_index_klen(hi);
9614      const char *id;
9615
9616      /* The name must end with ".txn" to be considered a transaction. */
9617      if ((apr_size_t) klen <= ext_len
9618          || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0)
9619        continue;
9620
9621      /* Truncate the ".txn" extension and store the ID. */
9622      id = apr_pstrndup(pool, name, strlen(name) - ext_len);
9623      APR_ARRAY_PUSH(names, const char *) = id;
9624    }
9625
9626  *names_p = names;
9627
9628  return SVN_NO_ERROR;
9629}
9630
9631svn_error_t *
9632svn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
9633                    svn_fs_t *fs,
9634                    const char *name,
9635                    apr_pool_t *pool)
9636{
9637  svn_fs_txn_t *txn;
9638  svn_node_kind_t kind;
9639  transaction_t *local_txn;
9640
9641  /* First check to see if the directory exists. */
9642  SVN_ERR(svn_io_check_path(path_txn_dir(fs, name, pool), &kind, pool));
9643
9644  /* Did we find it? */
9645  if (kind != svn_node_dir)
9646    return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL,
9647                             _("No such transaction '%s'"),
9648                             name);
9649
9650  txn = apr_pcalloc(pool, sizeof(*txn));
9651
9652  /* Read in the root node of this transaction. */
9653  txn->id = apr_pstrdup(pool, name);
9654  txn->fs = fs;
9655
9656  SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, name, pool));
9657
9658  txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id);
9659
9660  txn->vtable = &txn_vtable;
9661  *txn_p = txn;
9662
9663  return SVN_NO_ERROR;
9664}
9665
9666svn_error_t *
9667svn_fs_fs__txn_proplist(apr_hash_t **table_p,
9668                        svn_fs_txn_t *txn,
9669                        apr_pool_t *pool)
9670{
9671  apr_hash_t *proplist = apr_hash_make(pool);
9672  SVN_ERR(get_txn_proplist(proplist, txn->fs, txn->id, pool));
9673  *table_p = proplist;
9674
9675  return SVN_NO_ERROR;
9676}
9677
9678svn_error_t *
9679svn_fs_fs__delete_node_revision(svn_fs_t *fs,
9680                                const svn_fs_id_t *id,
9681                                apr_pool_t *pool)
9682{
9683  node_revision_t *noderev;
9684
9685  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
9686
9687  /* Delete any mutable property representation. */
9688  if (noderev->prop_rep && noderev->prop_rep->txn_id)
9689    SVN_ERR(svn_io_remove_file2(path_txn_node_props(fs, id, pool), FALSE,
9690                                pool));
9691
9692  /* Delete any mutable data representation. */
9693  if (noderev->data_rep && noderev->data_rep->txn_id
9694      && noderev->kind == svn_node_dir)
9695    {
9696      fs_fs_data_t *ffd = fs->fsap_data;
9697      SVN_ERR(svn_io_remove_file2(path_txn_node_children(fs, id, pool), FALSE,
9698                                  pool));
9699
9700      /* remove the corresponding entry from the cache, if such exists */
9701      if (ffd->txn_dir_cache)
9702        {
9703          const char *key = svn_fs_fs__id_unparse(id, pool)->data;
9704          SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL, pool));
9705        }
9706    }
9707
9708  return svn_io_remove_file2(path_txn_node_rev(fs, id, pool), FALSE, pool);
9709}
9710
9711
9712
9713/*** Revisions ***/
9714
9715svn_error_t *
9716svn_fs_fs__revision_prop(svn_string_t **value_p,
9717                         svn_fs_t *fs,
9718                         svn_revnum_t rev,
9719                         const char *propname,
9720                         apr_pool_t *pool)
9721{
9722  apr_hash_t *table;
9723
9724  SVN_ERR(svn_fs__check_fs(fs, TRUE));
9725  SVN_ERR(svn_fs_fs__revision_proplist(&table, fs, rev, pool));
9726
9727  *value_p = svn_hash_gets(table, propname);
9728
9729  return SVN_NO_ERROR;
9730}
9731
9732
9733/* Baton used for change_rev_prop_body below. */
9734struct change_rev_prop_baton {
9735  svn_fs_t *fs;
9736  svn_revnum_t rev;
9737  const char *name;
9738  const svn_string_t *const *old_value_p;
9739  const svn_string_t *value;
9740};
9741
9742/* The work-horse for svn_fs_fs__change_rev_prop, called with the FS
9743   write lock.  This implements the svn_fs_fs__with_write_lock()
9744   'body' callback type.  BATON is a 'struct change_rev_prop_baton *'. */
9745static svn_error_t *
9746change_rev_prop_body(void *baton, apr_pool_t *pool)
9747{
9748  struct change_rev_prop_baton *cb = baton;
9749  apr_hash_t *table;
9750
9751  SVN_ERR(svn_fs_fs__revision_proplist(&table, cb->fs, cb->rev, pool));
9752
9753  if (cb->old_value_p)
9754    {
9755      const svn_string_t *wanted_value = *cb->old_value_p;
9756      const svn_string_t *present_value = svn_hash_gets(table, cb->name);
9757      if ((!wanted_value != !present_value)
9758          || (wanted_value && present_value
9759              && !svn_string_compare(wanted_value, present_value)))
9760        {
9761          /* What we expected isn't what we found. */
9762          return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL,
9763                                   _("revprop '%s' has unexpected value in "
9764                                     "filesystem"),
9765                                   cb->name);
9766        }
9767      /* Fall through. */
9768    }
9769  svn_hash_sets(table, cb->name, cb->value);
9770
9771  return set_revision_proplist(cb->fs, cb->rev, table, pool);
9772}
9773
9774svn_error_t *
9775svn_fs_fs__change_rev_prop(svn_fs_t *fs,
9776                           svn_revnum_t rev,
9777                           const char *name,
9778                           const svn_string_t *const *old_value_p,
9779                           const svn_string_t *value,
9780                           apr_pool_t *pool)
9781{
9782  struct change_rev_prop_baton cb;
9783
9784  SVN_ERR(svn_fs__check_fs(fs, TRUE));
9785
9786  cb.fs = fs;
9787  cb.rev = rev;
9788  cb.name = name;
9789  cb.old_value_p = old_value_p;
9790  cb.value = value;
9791
9792  return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool);
9793}
9794
9795
9796
9797/*** Transactions ***/
9798
9799svn_error_t *
9800svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
9801                       const svn_fs_id_t **base_root_id_p,
9802                       svn_fs_t *fs,
9803                       const char *txn_name,
9804                       apr_pool_t *pool)
9805{
9806  transaction_t *txn;
9807  SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_name, pool));
9808  *root_id_p = txn->root_id;
9809  *base_root_id_p = txn->base_id;
9810  return SVN_NO_ERROR;
9811}
9812
9813
9814/* Generic transaction operations.  */
9815
9816svn_error_t *
9817svn_fs_fs__txn_prop(svn_string_t **value_p,
9818                    svn_fs_txn_t *txn,
9819                    const char *propname,
9820                    apr_pool_t *pool)
9821{
9822  apr_hash_t *table;
9823  svn_fs_t *fs = txn->fs;
9824
9825  SVN_ERR(svn_fs__check_fs(fs, TRUE));
9826  SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool));
9827
9828  *value_p = svn_hash_gets(table, propname);
9829
9830  return SVN_NO_ERROR;
9831}
9832
9833svn_error_t *
9834svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
9835                     svn_fs_t *fs,
9836                     svn_revnum_t rev,
9837                     apr_uint32_t flags,
9838                     apr_pool_t *pool)
9839{
9840  svn_string_t date;
9841  svn_prop_t prop;
9842  apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t));
9843
9844  SVN_ERR(svn_fs__check_fs(fs, TRUE));
9845
9846  SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool));
9847
9848  /* Put a datestamp on the newly created txn, so we always know
9849     exactly how old it is.  (This will help sysadmins identify
9850     long-abandoned txns that may need to be manually removed.)  When
9851     a txn is promoted to a revision, this property will be
9852     automatically overwritten with a revision datestamp. */
9853  date.data = svn_time_to_cstring(apr_time_now(), pool);
9854  date.len = strlen(date.data);
9855
9856  prop.name = SVN_PROP_REVISION_DATE;
9857  prop.value = &date;
9858  APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9859
9860  /* Set temporary txn props that represent the requested 'flags'
9861     behaviors. */
9862  if (flags & SVN_FS_TXN_CHECK_OOD)
9863    {
9864      prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
9865      prop.value = svn_string_create("true", pool);
9866      APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9867    }
9868
9869  if (flags & SVN_FS_TXN_CHECK_LOCKS)
9870    {
9871      prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
9872      prop.value = svn_string_create("true", pool);
9873      APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9874    }
9875
9876  return svn_fs_fs__change_txn_props(*txn_p, props, pool);
9877}
9878
9879
9880/****** Packing FSFS shards *********/
9881
9882/* Write a file FILENAME in directory FS_PATH, containing a single line
9883 * with the number REVNUM in ASCII decimal.  Move the file into place
9884 * atomically, overwriting any existing file.
9885 *
9886 * Similar to write_current(). */
9887static svn_error_t *
9888write_revnum_file(const char *fs_path,
9889                  const char *filename,
9890                  svn_revnum_t revnum,
9891                  apr_pool_t *scratch_pool)
9892{
9893  const char *final_path, *tmp_path;
9894  svn_stream_t *tmp_stream;
9895
9896  final_path = svn_dirent_join(fs_path, filename, scratch_pool);
9897  SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_path, fs_path,
9898                                   svn_io_file_del_none,
9899                                   scratch_pool, scratch_pool));
9900  SVN_ERR(svn_stream_printf(tmp_stream, scratch_pool, "%ld\n", revnum));
9901  SVN_ERR(svn_stream_close(tmp_stream));
9902  SVN_ERR(move_into_place(tmp_path, final_path, final_path, scratch_pool));
9903  return SVN_NO_ERROR;
9904}
9905
9906/* Pack the revision SHARD containing exactly MAX_FILES_PER_DIR revisions
9907 * from SHARD_PATH into the PACK_FILE_DIR, using POOL for allocations.
9908 * CANCEL_FUNC and CANCEL_BATON are what you think they are.
9909 *
9910 * If for some reason we detect a partial packing already performed, we
9911 * remove the pack file and start again.
9912 */
9913static svn_error_t *
9914pack_rev_shard(const char *pack_file_dir,
9915               const char *shard_path,
9916               apr_int64_t shard,
9917               int max_files_per_dir,
9918               svn_cancel_func_t cancel_func,
9919               void *cancel_baton,
9920               apr_pool_t *pool)
9921{
9922  const char *pack_file_path, *manifest_file_path;
9923  svn_stream_t *pack_stream, *manifest_stream;
9924  svn_revnum_t start_rev, end_rev, rev;
9925  apr_off_t next_offset;
9926  apr_pool_t *iterpool;
9927
9928  /* Some useful paths. */
9929  pack_file_path = svn_dirent_join(pack_file_dir, PATH_PACKED, pool);
9930  manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST, pool);
9931
9932  /* Remove any existing pack file for this shard, since it is incomplete. */
9933  SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
9934                             pool));
9935
9936  /* Create the new directory and pack and manifest files. */
9937  SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, pool));
9938  SVN_ERR(svn_stream_open_writable(&pack_stream, pack_file_path, pool,
9939                                    pool));
9940  SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
9941                                   pool, pool));
9942
9943  start_rev = (svn_revnum_t) (shard * max_files_per_dir);
9944  end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
9945  next_offset = 0;
9946  iterpool = svn_pool_create(pool);
9947
9948  /* Iterate over the revisions in this shard, squashing them together. */
9949  for (rev = start_rev; rev <= end_rev; rev++)
9950    {
9951      svn_stream_t *rev_stream;
9952      apr_finfo_t finfo;
9953      const char *path;
9954
9955      svn_pool_clear(iterpool);
9956
9957      /* Get the size of the file. */
9958      path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
9959                             iterpool);
9960      SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
9961
9962      /* Update the manifest. */
9963      SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%" APR_OFF_T_FMT
9964                                "\n", next_offset));
9965      next_offset += finfo.size;
9966
9967      /* Copy all the bits from the rev file to the end of the pack file. */
9968      SVN_ERR(svn_stream_open_readonly(&rev_stream, path, iterpool, iterpool));
9969      SVN_ERR(svn_stream_copy3(rev_stream, svn_stream_disown(pack_stream,
9970                                                             iterpool),
9971                          cancel_func, cancel_baton, iterpool));
9972    }
9973
9974  SVN_ERR(svn_stream_close(manifest_stream));
9975  SVN_ERR(svn_stream_close(pack_stream));
9976  SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
9977  SVN_ERR(svn_io_set_file_read_only(pack_file_path, FALSE, iterpool));
9978  SVN_ERR(svn_io_set_file_read_only(manifest_file_path, FALSE, iterpool));
9979
9980  svn_pool_destroy(iterpool);
9981
9982  return SVN_NO_ERROR;
9983}
9984
9985/* Copy revprop files for revisions [START_REV, END_REV) from SHARD_PATH
9986 * to the pack file at PACK_FILE_NAME in PACK_FILE_DIR.
9987 *
9988 * The file sizes have already been determined and written to SIZES.
9989 * Please note that this function will be executed while the filesystem
9990 * has been locked and that revprops files will therefore not be modified
9991 * while the pack is in progress.
9992 *
9993 * COMPRESSION_LEVEL defines how well the resulting pack file shall be
9994 * compressed or whether is shall be compressed at all.  TOTAL_SIZE is
9995 * a hint on which initial buffer size we should use to hold the pack file
9996 * content.
9997 *
9998 * CANCEL_FUNC and CANCEL_BATON are used as usual. Temporary allocations
9999 * are done in SCRATCH_POOL.
10000 */
10001static svn_error_t *
10002copy_revprops(const char *pack_file_dir,
10003              const char *pack_filename,
10004              const char *shard_path,
10005              svn_revnum_t start_rev,
10006              svn_revnum_t end_rev,
10007              apr_array_header_t *sizes,
10008              apr_size_t total_size,
10009              int compression_level,
10010              svn_cancel_func_t cancel_func,
10011              void *cancel_baton,
10012              apr_pool_t *scratch_pool)
10013{
10014  svn_stream_t *pack_stream;
10015  apr_file_t *pack_file;
10016  svn_revnum_t rev;
10017  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10018  svn_stream_t *stream;
10019
10020  /* create empty data buffer and a write stream on top of it */
10021  svn_stringbuf_t *uncompressed
10022    = svn_stringbuf_create_ensure(total_size, scratch_pool);
10023  svn_stringbuf_t *compressed
10024    = svn_stringbuf_create_empty(scratch_pool);
10025  pack_stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
10026
10027  /* write the pack file header */
10028  SVN_ERR(serialize_revprops_header(pack_stream, start_rev, sizes, 0,
10029                                    sizes->nelts, iterpool));
10030
10031  /* Some useful paths. */
10032  SVN_ERR(svn_io_file_open(&pack_file, svn_dirent_join(pack_file_dir,
10033                                                       pack_filename,
10034                                                       scratch_pool),
10035                           APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
10036                           scratch_pool));
10037
10038  /* Iterate over the revisions in this shard, squashing them together. */
10039  for (rev = start_rev; rev <= end_rev; rev++)
10040    {
10041      const char *path;
10042
10043      svn_pool_clear(iterpool);
10044
10045      /* Construct the file name. */
10046      path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
10047                             iterpool);
10048
10049      /* Copy all the bits from the non-packed revprop file to the end of
10050       * the pack file. */
10051      SVN_ERR(svn_stream_open_readonly(&stream, path, iterpool, iterpool));
10052      SVN_ERR(svn_stream_copy3(stream, pack_stream,
10053                               cancel_func, cancel_baton, iterpool));
10054    }
10055
10056  /* flush stream buffers to content buffer */
10057  SVN_ERR(svn_stream_close(pack_stream));
10058
10059  /* compress the content (or just store it for COMPRESSION_LEVEL 0) */
10060  SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed),
10061                        compressed, compression_level));
10062
10063  /* write the pack file content to disk */
10064  stream = svn_stream_from_aprfile2(pack_file, FALSE, scratch_pool);
10065  SVN_ERR(svn_stream_write(stream, compressed->data, &compressed->len));
10066  SVN_ERR(svn_stream_close(stream));
10067
10068  svn_pool_destroy(iterpool);
10069
10070  return SVN_NO_ERROR;
10071}
10072
10073/* For the revprop SHARD at SHARD_PATH with exactly MAX_FILES_PER_DIR
10074 * revprop files in it, create a packed shared at PACK_FILE_DIR.
10075 *
10076 * COMPRESSION_LEVEL defines how well the resulting pack file shall be
10077 * compressed or whether is shall be compressed at all.  Individual pack
10078 * file containing more than one revision will be limited to a size of
10079 * MAX_PACK_SIZE bytes before compression.
10080 *
10081 * CANCEL_FUNC and CANCEL_BATON are used in the usual way.  Temporary
10082 * allocations are done in SCRATCH_POOL.
10083 */
10084static svn_error_t *
10085pack_revprops_shard(const char *pack_file_dir,
10086                    const char *shard_path,
10087                    apr_int64_t shard,
10088                    int max_files_per_dir,
10089                    apr_off_t max_pack_size,
10090                    int compression_level,
10091                    svn_cancel_func_t cancel_func,
10092                    void *cancel_baton,
10093                    apr_pool_t *scratch_pool)
10094{
10095  const char *manifest_file_path, *pack_filename = NULL;
10096  svn_stream_t *manifest_stream;
10097  svn_revnum_t start_rev, end_rev, rev;
10098  apr_off_t total_size;
10099  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10100  apr_array_header_t *sizes;
10101
10102  /* Some useful paths. */
10103  manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST,
10104                                       scratch_pool);
10105
10106  /* Remove any existing pack file for this shard, since it is incomplete. */
10107  SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
10108                             scratch_pool));
10109
10110  /* Create the new directory and manifest file stream. */
10111  SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, scratch_pool));
10112  SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
10113                                   scratch_pool, scratch_pool));
10114
10115  /* revisions to handle. Special case: revision 0 */
10116  start_rev = (svn_revnum_t) (shard * max_files_per_dir);
10117  end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
10118  if (start_rev == 0)
10119    ++start_rev;
10120
10121  /* initialize the revprop size info */
10122  sizes = apr_array_make(scratch_pool, max_files_per_dir, sizeof(apr_off_t));
10123  total_size = 2 * SVN_INT64_BUFFER_SIZE;
10124
10125  /* Iterate over the revisions in this shard, determine their size and
10126   * squashing them together into pack files. */
10127  for (rev = start_rev; rev <= end_rev; rev++)
10128    {
10129      apr_finfo_t finfo;
10130      const char *path;
10131
10132      svn_pool_clear(iterpool);
10133
10134      /* Get the size of the file. */
10135      path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
10136                             iterpool);
10137      SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
10138
10139      /* if we already have started a pack file and this revprop cannot be
10140       * appended to it, write the previous pack file. */
10141      if (sizes->nelts != 0 &&
10142          total_size + SVN_INT64_BUFFER_SIZE + finfo.size > max_pack_size)
10143        {
10144          SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
10145                                start_rev, rev-1, sizes, (apr_size_t)total_size,
10146                                compression_level, cancel_func, cancel_baton,
10147                                iterpool));
10148
10149          /* next pack file starts empty again */
10150          apr_array_clear(sizes);
10151          total_size = 2 * SVN_INT64_BUFFER_SIZE;
10152          start_rev = rev;
10153        }
10154
10155      /* Update the manifest. Allocate a file name for the current pack
10156       * file if it is a new one */
10157      if (sizes->nelts == 0)
10158        pack_filename = apr_psprintf(scratch_pool, "%ld.0", rev);
10159
10160      SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%s\n",
10161                                pack_filename));
10162
10163      /* add to list of files to put into the current pack file */
10164      APR_ARRAY_PUSH(sizes, apr_off_t) = finfo.size;
10165      total_size += SVN_INT64_BUFFER_SIZE + finfo.size;
10166    }
10167
10168  /* write the last pack file */
10169  if (sizes->nelts != 0)
10170    SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
10171                          start_rev, rev-1, sizes, (apr_size_t)total_size,
10172                          compression_level, cancel_func, cancel_baton,
10173                          iterpool));
10174
10175  /* flush the manifest file and update permissions */
10176  SVN_ERR(svn_stream_close(manifest_stream));
10177  SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
10178
10179  svn_pool_destroy(iterpool);
10180
10181  return SVN_NO_ERROR;
10182}
10183
10184/* Delete the non-packed revprop SHARD at SHARD_PATH with exactly
10185 * MAX_FILES_PER_DIR revprop files in it.  If this is shard 0, keep the
10186 * revprop file for revision 0.
10187 *
10188 * CANCEL_FUNC and CANCEL_BATON are used in the usual way.  Temporary
10189 * allocations are done in SCRATCH_POOL.
10190 */
10191static svn_error_t *
10192delete_revprops_shard(const char *shard_path,
10193                      apr_int64_t shard,
10194                      int max_files_per_dir,
10195                      svn_cancel_func_t cancel_func,
10196                      void *cancel_baton,
10197                      apr_pool_t *scratch_pool)
10198{
10199  if (shard == 0)
10200    {
10201      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10202      int i;
10203
10204      /* delete all files except the one for revision 0 */
10205      for (i = 1; i < max_files_per_dir; ++i)
10206        {
10207          const char *path = svn_dirent_join(shard_path,
10208                                       apr_psprintf(iterpool, "%d", i),
10209                                       iterpool);
10210          if (cancel_func)
10211            SVN_ERR((*cancel_func)(cancel_baton));
10212
10213          SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
10214          svn_pool_clear(iterpool);
10215        }
10216
10217      svn_pool_destroy(iterpool);
10218    }
10219  else
10220    SVN_ERR(svn_io_remove_dir2(shard_path, TRUE,
10221                               cancel_func, cancel_baton, scratch_pool));
10222
10223  return SVN_NO_ERROR;
10224}
10225
10226/* In the file system at FS_PATH, pack the SHARD in REVS_DIR and
10227 * REVPROPS_DIR containing exactly MAX_FILES_PER_DIR revisions, using POOL
10228 * for allocations.  REVPROPS_DIR will be NULL if revprop packing is not
10229 * supported.  COMPRESSION_LEVEL and MAX_PACK_SIZE will be ignored in that
10230 * case.
10231 *
10232 * CANCEL_FUNC and CANCEL_BATON are what you think they are; similarly
10233 * NOTIFY_FUNC and NOTIFY_BATON.
10234 *
10235 * If for some reason we detect a partial packing already performed, we
10236 * remove the pack file and start again.
10237 */
10238static svn_error_t *
10239pack_shard(const char *revs_dir,
10240           const char *revsprops_dir,
10241           const char *fs_path,
10242           apr_int64_t shard,
10243           int max_files_per_dir,
10244           apr_off_t max_pack_size,
10245           int compression_level,
10246           svn_fs_pack_notify_t notify_func,
10247           void *notify_baton,
10248           svn_cancel_func_t cancel_func,
10249           void *cancel_baton,
10250           apr_pool_t *pool)
10251{
10252  const char *rev_shard_path, *rev_pack_file_dir;
10253  const char *revprops_shard_path, *revprops_pack_file_dir;
10254
10255  /* Notify caller we're starting to pack this shard. */
10256  if (notify_func)
10257    SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_start,
10258                        pool));
10259
10260  /* Some useful paths. */
10261  rev_pack_file_dir = svn_dirent_join(revs_dir,
10262                  apr_psprintf(pool,
10263                               "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
10264                               shard),
10265                  pool);
10266  rev_shard_path = svn_dirent_join(revs_dir,
10267                           apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
10268                           pool);
10269
10270  /* pack the revision content */
10271  SVN_ERR(pack_rev_shard(rev_pack_file_dir, rev_shard_path,
10272                         shard, max_files_per_dir,
10273                         cancel_func, cancel_baton, pool));
10274
10275  /* if enabled, pack the revprops in an equivalent way */
10276  if (revsprops_dir)
10277    {
10278      revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
10279                   apr_psprintf(pool,
10280                                "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
10281                                shard),
10282                   pool);
10283      revprops_shard_path = svn_dirent_join(revsprops_dir,
10284                           apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
10285                           pool);
10286
10287      SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path,
10288                                  shard, max_files_per_dir,
10289                                  (int)(0.9 * max_pack_size),
10290                                  compression_level,
10291                                  cancel_func, cancel_baton, pool));
10292    }
10293
10294  /* Update the min-unpacked-rev file to reflect our newly packed shard.
10295   * (This doesn't update ffd->min_unpacked_rev.  That will be updated by
10296   * update_min_unpacked_rev() when necessary.) */
10297  SVN_ERR(write_revnum_file(fs_path, PATH_MIN_UNPACKED_REV,
10298                            (svn_revnum_t)((shard + 1) * max_files_per_dir),
10299                            pool));
10300
10301  /* Finally, remove the existing shard directories. */
10302  SVN_ERR(svn_io_remove_dir2(rev_shard_path, TRUE,
10303                             cancel_func, cancel_baton, pool));
10304  if (revsprops_dir)
10305    SVN_ERR(delete_revprops_shard(revprops_shard_path,
10306                                  shard, max_files_per_dir,
10307                                  cancel_func, cancel_baton, pool));
10308
10309  /* Notify caller we're starting to pack this shard. */
10310  if (notify_func)
10311    SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_end,
10312                        pool));
10313
10314  return SVN_NO_ERROR;
10315}
10316
10317struct pack_baton
10318{
10319  svn_fs_t *fs;
10320  svn_fs_pack_notify_t notify_func;
10321  void *notify_baton;
10322  svn_cancel_func_t cancel_func;
10323  void *cancel_baton;
10324};
10325
10326
10327/* The work-horse for svn_fs_fs__pack, called with the FS write lock.
10328   This implements the svn_fs_fs__with_write_lock() 'body' callback
10329   type.  BATON is a 'struct pack_baton *'.
10330
10331   WARNING: if you add a call to this function, please note:
10332     The code currently assumes that any piece of code running with
10333     the write-lock set can rely on the ffd->min_unpacked_rev and
10334     ffd->min_unpacked_revprop caches to be up-to-date (and, by
10335     extension, on not having to use a retry when calling
10336     svn_fs_fs__path_rev_absolute() and friends).  If you add a call
10337     to this function, consider whether you have to call
10338     update_min_unpacked_rev().
10339     See this thread: http://thread.gmane.org/1291206765.3782.3309.camel@edith
10340 */
10341static svn_error_t *
10342pack_body(void *baton,
10343          apr_pool_t *pool)
10344{
10345  struct pack_baton *pb = baton;
10346  fs_fs_data_t ffd = {0};
10347  apr_int64_t completed_shards;
10348  apr_int64_t i;
10349  svn_revnum_t youngest;
10350  apr_pool_t *iterpool;
10351  const char *rev_data_path;
10352  const char *revprops_data_path = NULL;
10353
10354  /* read repository settings */
10355  SVN_ERR(read_format(&ffd.format, &ffd.max_files_per_dir,
10356                      path_format(pb->fs, pool), pool));
10357  SVN_ERR(check_format(ffd.format));
10358  SVN_ERR(read_config(&ffd, pb->fs->path, pool));
10359
10360  /* If the repository isn't a new enough format, we don't support packing.
10361     Return a friendly error to that effect. */
10362  if (ffd.format < SVN_FS_FS__MIN_PACKED_FORMAT)
10363    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
10364      _("FSFS format (%d) too old to pack; please upgrade the filesystem."),
10365      ffd.format);
10366
10367  /* If we aren't using sharding, we can't do any packing, so quit. */
10368  if (!ffd.max_files_per_dir)
10369    return SVN_NO_ERROR;
10370
10371  SVN_ERR(read_min_unpacked_rev(&ffd.min_unpacked_rev,
10372                                path_min_unpacked_rev(pb->fs, pool),
10373                                pool));
10374
10375  SVN_ERR(get_youngest(&youngest, pb->fs->path, pool));
10376  completed_shards = (youngest + 1) / ffd.max_files_per_dir;
10377
10378  /* See if we've already completed all possible shards thus far. */
10379  if (ffd.min_unpacked_rev == (completed_shards * ffd.max_files_per_dir))
10380    return SVN_NO_ERROR;
10381
10382  rev_data_path = svn_dirent_join(pb->fs->path, PATH_REVS_DIR, pool);
10383  if (ffd.format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
10384    revprops_data_path = svn_dirent_join(pb->fs->path, PATH_REVPROPS_DIR,
10385                                         pool);
10386
10387  iterpool = svn_pool_create(pool);
10388  for (i = ffd.min_unpacked_rev / ffd.max_files_per_dir;
10389       i < completed_shards;
10390       i++)
10391    {
10392      svn_pool_clear(iterpool);
10393
10394      if (pb->cancel_func)
10395        SVN_ERR(pb->cancel_func(pb->cancel_baton));
10396
10397      SVN_ERR(pack_shard(rev_data_path, revprops_data_path,
10398                         pb->fs->path, i, ffd.max_files_per_dir,
10399                         ffd.revprop_pack_size,
10400                         ffd.compress_packed_revprops
10401                           ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
10402                           : SVN_DELTA_COMPRESSION_LEVEL_NONE,
10403                         pb->notify_func, pb->notify_baton,
10404                         pb->cancel_func, pb->cancel_baton, iterpool));
10405    }
10406
10407  svn_pool_destroy(iterpool);
10408  return SVN_NO_ERROR;
10409}
10410
10411svn_error_t *
10412svn_fs_fs__pack(svn_fs_t *fs,
10413                svn_fs_pack_notify_t notify_func,
10414                void *notify_baton,
10415                svn_cancel_func_t cancel_func,
10416                void *cancel_baton,
10417                apr_pool_t *pool)
10418{
10419  struct pack_baton pb = { 0 };
10420  pb.fs = fs;
10421  pb.notify_func = notify_func;
10422  pb.notify_baton = notify_baton;
10423  pb.cancel_func = cancel_func;
10424  pb.cancel_baton = cancel_baton;
10425  return svn_fs_fs__with_write_lock(fs, pack_body, &pb, pool);
10426}
10427
10428
10429/** Verifying. **/
10430
10431/* Baton type expected by verify_walker().  The purpose is to reuse open
10432 * rev / pack file handles between calls.  Its contents need to be cleaned
10433 * periodically to limit resource usage.
10434 */
10435typedef struct verify_walker_baton_t
10436{
10437  /* number of calls to verify_walker() since the last clean */
10438  int iteration_count;
10439
10440  /* number of files opened since the last clean */
10441  int file_count;
10442
10443  /* progress notification callback to invoke periodically (may be NULL) */
10444  svn_fs_progress_notify_func_t notify_func;
10445
10446  /* baton to use with NOTIFY_FUNC */
10447  void *notify_baton;
10448
10449  /* remember the last revision for which we called notify_func */
10450  svn_revnum_t last_notified_revision;
10451
10452  /* current file handle (or NULL) */
10453  apr_file_t *file_hint;
10454
10455  /* corresponding revision (or SVN_INVALID_REVNUM) */
10456  svn_revnum_t rev_hint;
10457
10458  /* pool to use for the file handles etc. */
10459  apr_pool_t *pool;
10460} verify_walker_baton_t;
10461
10462/* Used by svn_fs_fs__verify().
10463   Implements svn_fs_fs__walk_rep_reference().walker.  */
10464static svn_error_t *
10465verify_walker(representation_t *rep,
10466              void *baton,
10467              svn_fs_t *fs,
10468              apr_pool_t *scratch_pool)
10469{
10470  struct rep_state *rs;
10471  struct rep_args *rep_args;
10472
10473  if (baton)
10474    {
10475      verify_walker_baton_t *walker_baton = baton;
10476      apr_file_t * previous_file;
10477
10478      /* notify and free resources periodically */
10479      if (   walker_baton->iteration_count > 1000
10480          || walker_baton->file_count > 16)
10481        {
10482          if (   walker_baton->notify_func
10483              && rep->revision != walker_baton->last_notified_revision)
10484            {
10485              walker_baton->notify_func(rep->revision,
10486                                        walker_baton->notify_baton,
10487                                        scratch_pool);
10488              walker_baton->last_notified_revision = rep->revision;
10489            }
10490
10491          svn_pool_clear(walker_baton->pool);
10492
10493          walker_baton->iteration_count = 0;
10494          walker_baton->file_count = 0;
10495          walker_baton->file_hint = NULL;
10496          walker_baton->rev_hint = SVN_INVALID_REVNUM;
10497        }
10498
10499      /* access the repo data */
10500      previous_file = walker_baton->file_hint;
10501      SVN_ERR(create_rep_state(&rs, &rep_args, &walker_baton->file_hint,
10502                               &walker_baton->rev_hint, rep, fs,
10503                               walker_baton->pool));
10504
10505      /* update resource usage counters */
10506      walker_baton->iteration_count++;
10507      if (previous_file != walker_baton->file_hint)
10508        walker_baton->file_count++;
10509    }
10510  else
10511    {
10512      /* ### Should this be using read_rep_line() directly? */
10513      SVN_ERR(create_rep_state(&rs, &rep_args, NULL, NULL, rep, fs,
10514                               scratch_pool));
10515    }
10516
10517  return SVN_NO_ERROR;
10518}
10519
10520svn_error_t *
10521svn_fs_fs__verify(svn_fs_t *fs,
10522                  svn_revnum_t start,
10523                  svn_revnum_t end,
10524                  svn_fs_progress_notify_func_t notify_func,
10525                  void *notify_baton,
10526                  svn_cancel_func_t cancel_func,
10527                  void *cancel_baton,
10528                  apr_pool_t *pool)
10529{
10530  fs_fs_data_t *ffd = fs->fsap_data;
10531  svn_boolean_t exists;
10532  svn_revnum_t youngest = ffd->youngest_rev_cache; /* cache is current */
10533
10534  if (ffd->format < SVN_FS_FS__MIN_REP_SHARING_FORMAT)
10535    return SVN_NO_ERROR;
10536
10537  /* Input validation. */
10538  if (! SVN_IS_VALID_REVNUM(start))
10539    start = 0;
10540  if (! SVN_IS_VALID_REVNUM(end))
10541    end = youngest;
10542  SVN_ERR(ensure_revision_exists(fs, start, pool));
10543  SVN_ERR(ensure_revision_exists(fs, end, pool));
10544
10545  /* rep-cache verification. */
10546  SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool));
10547  if (exists)
10548    {
10549      /* provide a baton to allow the reuse of open file handles between
10550         iterations (saves 2/3 of OS level file operations). */
10551      verify_walker_baton_t *baton = apr_pcalloc(pool, sizeof(*baton));
10552      baton->rev_hint = SVN_INVALID_REVNUM;
10553      baton->pool = svn_pool_create(pool);
10554      baton->last_notified_revision = SVN_INVALID_REVNUM;
10555      baton->notify_func = notify_func;
10556      baton->notify_baton = notify_baton;
10557
10558      /* tell the user that we are now ready to do *something* */
10559      if (notify_func)
10560        notify_func(SVN_INVALID_REVNUM, notify_baton, baton->pool);
10561
10562      /* Do not attempt to walk the rep-cache database if its file does
10563         not exist,  since doing so would create it --- which may confuse
10564         the administrator.   Don't take any lock. */
10565      SVN_ERR(svn_fs_fs__walk_rep_reference(fs, start, end,
10566                                            verify_walker, baton,
10567                                            cancel_func, cancel_baton,
10568                                            pool));
10569
10570      /* walker resource cleanup */
10571      svn_pool_destroy(baton->pool);
10572    }
10573
10574  return SVN_NO_ERROR;
10575}
10576
10577
10578/** Hotcopy. **/
10579
10580/* Like svn_io_dir_file_copy(), but doesn't copy files that exist at
10581 * the destination and do not differ in terms of kind, size, and mtime. */
10582static svn_error_t *
10583hotcopy_io_dir_file_copy(const char *src_path,
10584                         const char *dst_path,
10585                         const char *file,
10586                         apr_pool_t *scratch_pool)
10587{
10588  const svn_io_dirent2_t *src_dirent;
10589  const svn_io_dirent2_t *dst_dirent;
10590  const char *src_target;
10591  const char *dst_target;
10592
10593  /* Does the destination already exist? If not, we must copy it. */
10594  dst_target = svn_dirent_join(dst_path, file, scratch_pool);
10595  SVN_ERR(svn_io_stat_dirent2(&dst_dirent, dst_target, FALSE, TRUE,
10596                              scratch_pool, scratch_pool));
10597  if (dst_dirent->kind != svn_node_none)
10598    {
10599      /* If the destination's stat information indicates that the file
10600       * is equal to the source, don't bother copying the file again. */
10601      src_target = svn_dirent_join(src_path, file, scratch_pool);
10602      SVN_ERR(svn_io_stat_dirent2(&src_dirent, src_target, FALSE, FALSE,
10603                                  scratch_pool, scratch_pool));
10604      if (src_dirent->kind == dst_dirent->kind &&
10605          src_dirent->special == dst_dirent->special &&
10606          src_dirent->filesize == dst_dirent->filesize &&
10607          src_dirent->mtime <= dst_dirent->mtime)
10608        return SVN_NO_ERROR;
10609    }
10610
10611  return svn_error_trace(svn_io_dir_file_copy(src_path, dst_path, file,
10612                                              scratch_pool));
10613}
10614
10615/* Set *NAME_P to the UTF-8 representation of directory entry NAME.
10616 * NAME is in the internal encoding used by APR; PARENT is in
10617 * UTF-8 and in internal (not local) style.
10618 *
10619 * Use PARENT only for generating an error string if the conversion
10620 * fails because NAME could not be represented in UTF-8.  In that
10621 * case, return a two-level error in which the outer error's message
10622 * mentions PARENT, but the inner error's message does not mention
10623 * NAME (except possibly in hex) since NAME may not be printable.
10624 * Such a compound error at least allows the user to go looking in the
10625 * right directory for the problem.
10626 *
10627 * If there is any other error, just return that error directly.
10628 *
10629 * If there is any error, the effect on *NAME_P is undefined.
10630 *
10631 * *NAME_P and NAME may refer to the same storage.
10632 */
10633static svn_error_t *
10634entry_name_to_utf8(const char **name_p,
10635                   const char *name,
10636                   const char *parent,
10637                   apr_pool_t *pool)
10638{
10639  svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
10640  if (err && err->apr_err == APR_EINVAL)
10641    {
10642      return svn_error_createf(err->apr_err, err,
10643                               _("Error converting entry "
10644                                 "in directory '%s' to UTF-8"),
10645                               svn_dirent_local_style(parent, pool));
10646    }
10647  return err;
10648}
10649
10650/* Like svn_io_copy_dir_recursively() but doesn't copy regular files that
10651 * exist in the destination and do not differ from the source in terms of
10652 * kind, size, and mtime. */
10653static svn_error_t *
10654hotcopy_io_copy_dir_recursively(const char *src,
10655                                const char *dst_parent,
10656                                const char *dst_basename,
10657                                svn_boolean_t copy_perms,
10658                                svn_cancel_func_t cancel_func,
10659                                void *cancel_baton,
10660                                apr_pool_t *pool)
10661{
10662  svn_node_kind_t kind;
10663  apr_status_t status;
10664  const char *dst_path;
10665  apr_dir_t *this_dir;
10666  apr_finfo_t this_entry;
10667  apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
10668
10669  /* Make a subpool for recursion */
10670  apr_pool_t *subpool = svn_pool_create(pool);
10671
10672  /* The 'dst_path' is simply dst_parent/dst_basename */
10673  dst_path = svn_dirent_join(dst_parent, dst_basename, pool);
10674
10675  /* Sanity checks:  SRC and DST_PARENT are directories, and
10676     DST_BASENAME doesn't already exist in DST_PARENT. */
10677  SVN_ERR(svn_io_check_path(src, &kind, subpool));
10678  if (kind != svn_node_dir)
10679    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
10680                             _("Source '%s' is not a directory"),
10681                             svn_dirent_local_style(src, pool));
10682
10683  SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
10684  if (kind != svn_node_dir)
10685    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
10686                             _("Destination '%s' is not a directory"),
10687                             svn_dirent_local_style(dst_parent, pool));
10688
10689  SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
10690
10691  /* Create the new directory. */
10692  /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
10693  SVN_ERR(svn_io_make_dir_recursively(dst_path, pool));
10694
10695  /* Loop over the dirents in SRC.  ('.' and '..' are auto-excluded) */
10696  SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
10697
10698  for (status = apr_dir_read(&this_entry, flags, this_dir);
10699       status == APR_SUCCESS;
10700       status = apr_dir_read(&this_entry, flags, this_dir))
10701    {
10702      if ((this_entry.name[0] == '.')
10703          && ((this_entry.name[1] == '\0')
10704              || ((this_entry.name[1] == '.')
10705                  && (this_entry.name[2] == '\0'))))
10706        {
10707          continue;
10708        }
10709      else
10710        {
10711          const char *entryname_utf8;
10712
10713          if (cancel_func)
10714            SVN_ERR(cancel_func(cancel_baton));
10715
10716          SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
10717                                     src, subpool));
10718          if (this_entry.filetype == APR_REG) /* regular file */
10719            {
10720              SVN_ERR(hotcopy_io_dir_file_copy(src, dst_path, entryname_utf8,
10721                                               subpool));
10722            }
10723          else if (this_entry.filetype == APR_LNK) /* symlink */
10724            {
10725              const char *src_target = svn_dirent_join(src, entryname_utf8,
10726                                                       subpool);
10727              const char *dst_target = svn_dirent_join(dst_path,
10728                                                       entryname_utf8,
10729                                                       subpool);
10730              SVN_ERR(svn_io_copy_link(src_target, dst_target,
10731                                       subpool));
10732            }
10733          else if (this_entry.filetype == APR_DIR) /* recurse */
10734            {
10735              const char *src_target;
10736
10737              /* Prevent infinite recursion by filtering off our
10738                 newly created destination path. */
10739              if (strcmp(src, dst_parent) == 0
10740                  && strcmp(entryname_utf8, dst_basename) == 0)
10741                continue;
10742
10743              src_target = svn_dirent_join(src, entryname_utf8, subpool);
10744              SVN_ERR(hotcopy_io_copy_dir_recursively(src_target,
10745                                                      dst_path,
10746                                                      entryname_utf8,
10747                                                      copy_perms,
10748                                                      cancel_func,
10749                                                      cancel_baton,
10750                                                      subpool));
10751            }
10752          /* ### support other APR node types someday?? */
10753
10754        }
10755    }
10756
10757  if (! (APR_STATUS_IS_ENOENT(status)))
10758    return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
10759                              svn_dirent_local_style(src, pool));
10760
10761  status = apr_dir_close(this_dir);
10762  if (status)
10763    return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
10764                              svn_dirent_local_style(src, pool));
10765
10766  /* Free any memory used by recursion */
10767  svn_pool_destroy(subpool);
10768
10769  return SVN_NO_ERROR;
10770}
10771
10772/* Copy an un-packed revision or revprop file for revision REV from SRC_SUBDIR
10773 * to DST_SUBDIR. Assume a sharding layout based on MAX_FILES_PER_DIR.
10774 * Use SCRATCH_POOL for temporary allocations. */
10775static svn_error_t *
10776hotcopy_copy_shard_file(const char *src_subdir,
10777                        const char *dst_subdir,
10778                        svn_revnum_t rev,
10779                        int max_files_per_dir,
10780                        apr_pool_t *scratch_pool)
10781{
10782  const char *src_subdir_shard = src_subdir,
10783             *dst_subdir_shard = dst_subdir;
10784
10785  if (max_files_per_dir)
10786    {
10787      const char *shard = apr_psprintf(scratch_pool, "%ld",
10788                                       rev / max_files_per_dir);
10789      src_subdir_shard = svn_dirent_join(src_subdir, shard, scratch_pool);
10790      dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10791
10792      if (rev % max_files_per_dir == 0)
10793        {
10794          SVN_ERR(svn_io_make_dir_recursively(dst_subdir_shard, scratch_pool));
10795          SVN_ERR(svn_io_copy_perms(dst_subdir, dst_subdir_shard,
10796                                    scratch_pool));
10797        }
10798    }
10799
10800  SVN_ERR(hotcopy_io_dir_file_copy(src_subdir_shard, dst_subdir_shard,
10801                                   apr_psprintf(scratch_pool, "%ld", rev),
10802                                   scratch_pool));
10803  return SVN_NO_ERROR;
10804}
10805
10806
10807/* Copy a packed shard containing revision REV, and which contains
10808 * MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS.
10809 * Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS.
10810 * Do not re-copy data which already exists in DST_FS.
10811 * Use SCRATCH_POOL for temporary allocations. */
10812static svn_error_t *
10813hotcopy_copy_packed_shard(svn_revnum_t *dst_min_unpacked_rev,
10814                          svn_fs_t *src_fs,
10815                          svn_fs_t *dst_fs,
10816                          svn_revnum_t rev,
10817                          int max_files_per_dir,
10818                          apr_pool_t *scratch_pool)
10819{
10820  const char *src_subdir;
10821  const char *dst_subdir;
10822  const char *packed_shard;
10823  const char *src_subdir_packed_shard;
10824  svn_revnum_t revprop_rev;
10825  apr_pool_t *iterpool;
10826  fs_fs_data_t *src_ffd = src_fs->fsap_data;
10827
10828  /* Copy the packed shard. */
10829  src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
10830  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
10831  packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
10832                              rev / max_files_per_dir);
10833  src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
10834                                            scratch_pool);
10835  SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
10836                                          dst_subdir, packed_shard,
10837                                          TRUE /* copy_perms */,
10838                                          NULL /* cancel_func */, NULL,
10839                                          scratch_pool));
10840
10841  /* Copy revprops belonging to revisions in this pack. */
10842  src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, scratch_pool);
10843  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, scratch_pool);
10844
10845  if (   src_ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
10846      || src_ffd->min_unpacked_rev < rev + max_files_per_dir)
10847    {
10848      /* copy unpacked revprops rev by rev */
10849      iterpool = svn_pool_create(scratch_pool);
10850      for (revprop_rev = rev;
10851           revprop_rev < rev + max_files_per_dir;
10852           revprop_rev++)
10853        {
10854          svn_pool_clear(iterpool);
10855
10856          SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
10857                                          revprop_rev, max_files_per_dir,
10858                                          iterpool));
10859        }
10860      svn_pool_destroy(iterpool);
10861    }
10862  else
10863    {
10864      /* revprop for revision 0 will never be packed */
10865      if (rev == 0)
10866        SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
10867                                        0, max_files_per_dir,
10868                                        scratch_pool));
10869
10870      /* packed revprops folder */
10871      packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
10872                                  rev / max_files_per_dir);
10873      src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
10874                                                scratch_pool);
10875      SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
10876                                              dst_subdir, packed_shard,
10877                                              TRUE /* copy_perms */,
10878                                              NULL /* cancel_func */, NULL,
10879                                              scratch_pool));
10880    }
10881
10882  /* If necessary, update the min-unpacked rev file in the hotcopy. */
10883  if (*dst_min_unpacked_rev < rev + max_files_per_dir)
10884    {
10885      *dst_min_unpacked_rev = rev + max_files_per_dir;
10886      SVN_ERR(write_revnum_file(dst_fs->path, PATH_MIN_UNPACKED_REV,
10887                                *dst_min_unpacked_rev,
10888                                scratch_pool));
10889    }
10890
10891  return SVN_NO_ERROR;
10892}
10893
10894/* If NEW_YOUNGEST is younger than *DST_YOUNGEST, update the 'current'
10895 * file in DST_FS and set *DST_YOUNGEST to NEW_YOUNGEST.
10896 * Use SCRATCH_POOL for temporary allocations. */
10897static svn_error_t *
10898hotcopy_update_current(svn_revnum_t *dst_youngest,
10899                       svn_fs_t *dst_fs,
10900                       svn_revnum_t new_youngest,
10901                       apr_pool_t *scratch_pool)
10902{
10903  char next_node_id[MAX_KEY_SIZE] = "0";
10904  char next_copy_id[MAX_KEY_SIZE] = "0";
10905  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
10906
10907  if (*dst_youngest >= new_youngest)
10908    return SVN_NO_ERROR;
10909
10910  /* If necessary, get new current next_node and next_copy IDs. */
10911  if (dst_ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
10912    {
10913      apr_off_t root_offset;
10914      apr_file_t *rev_file;
10915
10916      if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
10917        SVN_ERR(update_min_unpacked_rev(dst_fs, scratch_pool));
10918
10919      SVN_ERR(open_pack_or_rev_file(&rev_file, dst_fs, new_youngest,
10920                                    scratch_pool));
10921      SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file,
10922                                      dst_fs, new_youngest, scratch_pool));
10923      SVN_ERR(recover_find_max_ids(dst_fs, new_youngest, rev_file,
10924                                   root_offset, next_node_id, next_copy_id,
10925                                   scratch_pool));
10926      SVN_ERR(svn_io_file_close(rev_file, scratch_pool));
10927    }
10928
10929  /* Update 'current'. */
10930  SVN_ERR(write_current(dst_fs, new_youngest, next_node_id, next_copy_id,
10931                        scratch_pool));
10932
10933  *dst_youngest = new_youngest;
10934
10935  return SVN_NO_ERROR;
10936}
10937
10938
10939/* Remove revision or revprop files between START_REV (inclusive) and
10940 * END_REV (non-inclusive) from folder DST_SUBDIR in DST_FS.  Assume
10941 * sharding as per MAX_FILES_PER_DIR.
10942 * Use SCRATCH_POOL for temporary allocations. */
10943static svn_error_t *
10944hotcopy_remove_files(svn_fs_t *dst_fs,
10945                     const char *dst_subdir,
10946                     svn_revnum_t start_rev,
10947                     svn_revnum_t end_rev,
10948                     int max_files_per_dir,
10949                     apr_pool_t *scratch_pool)
10950{
10951  const char *shard;
10952  const char *dst_subdir_shard;
10953  svn_revnum_t rev;
10954  apr_pool_t *iterpool;
10955
10956  /* Pre-compute paths for initial shard. */
10957  shard = apr_psprintf(scratch_pool, "%ld", start_rev / max_files_per_dir);
10958  dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10959
10960  iterpool = svn_pool_create(scratch_pool);
10961  for (rev = start_rev; rev < end_rev; rev++)
10962    {
10963      const char *path;
10964      svn_pool_clear(iterpool);
10965
10966      /* If necessary, update paths for shard. */
10967      if (rev != start_rev && rev % max_files_per_dir == 0)
10968        {
10969          shard = apr_psprintf(iterpool, "%ld", rev / max_files_per_dir);
10970          dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10971        }
10972
10973      /* remove files for REV */
10974      path = svn_dirent_join(dst_subdir_shard,
10975                             apr_psprintf(iterpool, "%ld", rev),
10976                             iterpool);
10977
10978      /* Make the rev file writable and remove it. */
10979      SVN_ERR(svn_io_set_file_read_write(path, TRUE, iterpool));
10980      SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
10981    }
10982
10983  svn_pool_destroy(iterpool);
10984
10985  return SVN_NO_ERROR;
10986}
10987
10988/* Remove revisions between START_REV (inclusive) and END_REV (non-inclusive)
10989 * from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
10990 * Use SCRATCH_POOL for temporary allocations. */
10991static svn_error_t *
10992hotcopy_remove_rev_files(svn_fs_t *dst_fs,
10993                         svn_revnum_t start_rev,
10994                         svn_revnum_t end_rev,
10995                         int max_files_per_dir,
10996                         apr_pool_t *scratch_pool)
10997{
10998  SVN_ERR_ASSERT(start_rev <= end_rev);
10999  SVN_ERR(hotcopy_remove_files(dst_fs,
11000                               svn_dirent_join(dst_fs->path,
11001                                               PATH_REVS_DIR,
11002                                               scratch_pool),
11003                               start_rev, end_rev,
11004                               max_files_per_dir, scratch_pool));
11005
11006  return SVN_NO_ERROR;
11007}
11008
11009/* Remove revision properties between START_REV (inclusive) and END_REV
11010 * (non-inclusive) from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
11011 * Use SCRATCH_POOL for temporary allocations.  Revision 0 revprops will
11012 * not be deleted. */
11013static svn_error_t *
11014hotcopy_remove_revprop_files(svn_fs_t *dst_fs,
11015                             svn_revnum_t start_rev,
11016                             svn_revnum_t end_rev,
11017                             int max_files_per_dir,
11018                             apr_pool_t *scratch_pool)
11019{
11020  SVN_ERR_ASSERT(start_rev <= end_rev);
11021
11022  /* don't delete rev 0 props */
11023  SVN_ERR(hotcopy_remove_files(dst_fs,
11024                               svn_dirent_join(dst_fs->path,
11025                                               PATH_REVPROPS_DIR,
11026                                               scratch_pool),
11027                               start_rev ? start_rev : 1, end_rev,
11028                               max_files_per_dir, scratch_pool));
11029
11030  return SVN_NO_ERROR;
11031}
11032
11033/* Verify that DST_FS is a suitable destination for an incremental
11034 * hotcopy from SRC_FS. */
11035static svn_error_t *
11036hotcopy_incremental_check_preconditions(svn_fs_t *src_fs,
11037                                        svn_fs_t *dst_fs,
11038                                        apr_pool_t *pool)
11039{
11040  fs_fs_data_t *src_ffd = src_fs->fsap_data;
11041  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11042
11043  /* We only support incremental hotcopy between the same format. */
11044  if (src_ffd->format != dst_ffd->format)
11045    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11046      _("The FSFS format (%d) of the hotcopy source does not match the "
11047        "FSFS format (%d) of the hotcopy destination; please upgrade "
11048        "both repositories to the same format"),
11049      src_ffd->format, dst_ffd->format);
11050
11051  /* Make sure the UUID of source and destination match up.
11052   * We don't want to copy over a different repository. */
11053  if (strcmp(src_fs->uuid, dst_fs->uuid) != 0)
11054    return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL,
11055                            _("The UUID of the hotcopy source does "
11056                              "not match the UUID of the hotcopy "
11057                              "destination"));
11058
11059  /* Also require same shard size. */
11060  if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir)
11061    return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11062                            _("The sharding layout configuration "
11063                              "of the hotcopy source does not match "
11064                              "the sharding layout configuration of "
11065                              "the hotcopy destination"));
11066  return SVN_NO_ERROR;
11067}
11068
11069/* Remove folder PATH.  Ignore errors due to the sub-tree not being empty.
11070 * CANCEL_FUNC and CANCEL_BATON do the usual thing.
11071 * Use POOL for temporary allocations.
11072 */
11073static svn_error_t *
11074remove_folder(const char *path,
11075              svn_cancel_func_t cancel_func,
11076              void *cancel_baton,
11077              apr_pool_t *pool)
11078{
11079  svn_error_t *err = svn_io_remove_dir2(path, TRUE,
11080                                        cancel_func, cancel_baton, pool);
11081
11082  if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err))
11083    {
11084      svn_error_clear(err);
11085      err = SVN_NO_ERROR;
11086    }
11087
11088  return svn_error_trace(err);
11089}
11090
11091/* Baton for hotcopy_body(). */
11092struct hotcopy_body_baton {
11093  svn_fs_t *src_fs;
11094  svn_fs_t *dst_fs;
11095  svn_boolean_t incremental;
11096  svn_cancel_func_t cancel_func;
11097  void *cancel_baton;
11098} hotcopy_body_baton;
11099
11100/* Perform a hotcopy, either normal or incremental.
11101 *
11102 * Normal hotcopy assumes that the destination exists as an empty
11103 * directory. It behaves like an incremental hotcopy except that
11104 * none of the copied files already exist in the destination.
11105 *
11106 * An incremental hotcopy copies only changed or new files to the destination,
11107 * and removes files from the destination no longer present in the source.
11108 * While the incremental hotcopy is running, readers should still be able
11109 * to access the destintation repository without error and should not see
11110 * revisions currently in progress of being copied. Readers are able to see
11111 * new fully copied revisions even if the entire incremental hotcopy procedure
11112 * has not yet completed.
11113 *
11114 * Writers are blocked out completely during the entire incremental hotcopy
11115 * process to ensure consistency. This function assumes that the repository
11116 * write-lock is held.
11117 */
11118static svn_error_t *
11119hotcopy_body(void *baton, apr_pool_t *pool)
11120{
11121  struct hotcopy_body_baton *hbb = baton;
11122  svn_fs_t *src_fs = hbb->src_fs;
11123  fs_fs_data_t *src_ffd = src_fs->fsap_data;
11124  svn_fs_t *dst_fs = hbb->dst_fs;
11125  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11126  int max_files_per_dir = src_ffd->max_files_per_dir;
11127  svn_boolean_t incremental = hbb->incremental;
11128  svn_cancel_func_t cancel_func = hbb->cancel_func;
11129  void* cancel_baton = hbb->cancel_baton;
11130  svn_revnum_t src_youngest;
11131  svn_revnum_t dst_youngest;
11132  svn_revnum_t rev;
11133  svn_revnum_t src_min_unpacked_rev;
11134  svn_revnum_t dst_min_unpacked_rev;
11135  const char *src_subdir;
11136  const char *dst_subdir;
11137  const char *revprop_src_subdir;
11138  const char *revprop_dst_subdir;
11139  apr_pool_t *iterpool;
11140  svn_node_kind_t kind;
11141
11142  /* Try to copy the config.
11143   *
11144   * ### We try copying the config file before doing anything else,
11145   * ### because higher layers will abort the hotcopy if we throw
11146   * ### an error from this function, and that renders the hotcopy
11147   * ### unusable anyway. */
11148  if (src_ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
11149    {
11150      svn_error_t *err;
11151
11152      err = svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG,
11153                                 pool);
11154      if (err)
11155        {
11156          if (APR_STATUS_IS_ENOENT(err->apr_err))
11157            {
11158              /* 1.6.0 to 1.6.11 did not copy the configuration file during
11159               * hotcopy. So if we're hotcopying a repository which has been
11160               * created as a hotcopy itself, it's possible that fsfs.conf
11161               * does not exist. Ask the user to re-create it.
11162               *
11163               * ### It would be nice to make this a non-fatal error,
11164               * ### but this function does not get an svn_fs_t object
11165               * ### so we have no way of just printing a warning via
11166               * ### the fs->warning() callback. */
11167
11168              const char *msg;
11169              const char *src_abspath;
11170              const char *dst_abspath;
11171              const char *config_relpath;
11172              svn_error_t *err2;
11173
11174              config_relpath = svn_dirent_join(src_fs->path, PATH_CONFIG, pool);
11175              err2 = svn_dirent_get_absolute(&src_abspath, src_fs->path, pool);
11176              if (err2)
11177                return svn_error_trace(svn_error_compose_create(err, err2));
11178              err2 = svn_dirent_get_absolute(&dst_abspath, dst_fs->path, pool);
11179              if (err2)
11180                return svn_error_trace(svn_error_compose_create(err, err2));
11181
11182              /* ### hack: strip off the 'db/' directory from paths so
11183               * ### they make sense to the user */
11184              src_abspath = svn_dirent_dirname(src_abspath, pool);
11185              dst_abspath = svn_dirent_dirname(dst_abspath, pool);
11186
11187              msg = apr_psprintf(pool,
11188                                 _("Failed to create hotcopy at '%s'. "
11189                                   "The file '%s' is missing from the source "
11190                                   "repository. Please create this file, for "
11191                                   "instance by running 'svnadmin upgrade %s'"),
11192                                 dst_abspath, config_relpath, src_abspath);
11193              return svn_error_quick_wrap(err, msg);
11194            }
11195          else
11196            return svn_error_trace(err);
11197        }
11198    }
11199
11200  if (cancel_func)
11201    SVN_ERR(cancel_func(cancel_baton));
11202
11203  /* Find the youngest revision in the source and destination.
11204   * We only support hotcopies from sources with an equal or greater amount
11205   * of revisions than the destination.
11206   * This also catches the case where users accidentally swap the
11207   * source and destination arguments. */
11208  SVN_ERR(get_youngest(&src_youngest, src_fs->path, pool));
11209  if (incremental)
11210    {
11211      SVN_ERR(get_youngest(&dst_youngest, dst_fs->path, pool));
11212      if (src_youngest < dst_youngest)
11213        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11214                 _("The hotcopy destination already contains more revisions "
11215                   "(%lu) than the hotcopy source contains (%lu); are source "
11216                   "and destination swapped?"),
11217                  dst_youngest, src_youngest);
11218    }
11219  else
11220    dst_youngest = 0;
11221
11222  if (cancel_func)
11223    SVN_ERR(cancel_func(cancel_baton));
11224
11225  /* Copy the min unpacked rev, and read its value. */
11226  if (src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11227    {
11228      const char *min_unpacked_rev_path;
11229
11230      min_unpacked_rev_path = svn_dirent_join(src_fs->path,
11231                                              PATH_MIN_UNPACKED_REV,
11232                                              pool);
11233      SVN_ERR(read_min_unpacked_rev(&src_min_unpacked_rev,
11234                                    min_unpacked_rev_path,
11235                                    pool));
11236
11237      min_unpacked_rev_path = svn_dirent_join(dst_fs->path,
11238                                              PATH_MIN_UNPACKED_REV,
11239                                              pool);
11240      SVN_ERR(read_min_unpacked_rev(&dst_min_unpacked_rev,
11241                                    min_unpacked_rev_path,
11242                                    pool));
11243
11244      /* We only support packs coming from the hotcopy source.
11245       * The destination should not be packed independently from
11246       * the source. This also catches the case where users accidentally
11247       * swap the source and destination arguments. */
11248      if (src_min_unpacked_rev < dst_min_unpacked_rev)
11249        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11250                                 _("The hotcopy destination already contains "
11251                                   "more packed revisions (%lu) than the "
11252                                   "hotcopy source contains (%lu)"),
11253                                   dst_min_unpacked_rev - 1,
11254                                   src_min_unpacked_rev - 1);
11255
11256      SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
11257                                   PATH_MIN_UNPACKED_REV, pool));
11258    }
11259  else
11260    {
11261      src_min_unpacked_rev = 0;
11262      dst_min_unpacked_rev = 0;
11263    }
11264
11265  if (cancel_func)
11266    SVN_ERR(cancel_func(cancel_baton));
11267
11268  /*
11269   * Copy the necessary rev files.
11270   */
11271
11272  src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, pool);
11273  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, pool);
11274  SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
11275
11276  iterpool = svn_pool_create(pool);
11277  /* First, copy packed shards. */
11278  for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir)
11279    {
11280      svn_pool_clear(iterpool);
11281
11282      if (cancel_func)
11283        SVN_ERR(cancel_func(cancel_baton));
11284
11285      /* Copy the packed shard. */
11286      SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
11287                                        src_fs, dst_fs,
11288                                        rev, max_files_per_dir,
11289                                        iterpool));
11290
11291      /* If necessary, update 'current' to the most recent packed rev,
11292       * so readers can see new revisions which arrived in this pack. */
11293      SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs,
11294                                     rev + max_files_per_dir - 1,
11295                                     iterpool));
11296
11297      /* Remove revision files which are now packed. */
11298      if (incremental)
11299        {
11300          SVN_ERR(hotcopy_remove_rev_files(dst_fs, rev,
11301                                           rev + max_files_per_dir,
11302                                           max_files_per_dir, iterpool));
11303          if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
11304            SVN_ERR(hotcopy_remove_revprop_files(dst_fs, rev,
11305                                                 rev + max_files_per_dir,
11306                                                 max_files_per_dir,
11307                                                 iterpool));
11308        }
11309
11310      /* Now that all revisions have moved into the pack, the original
11311       * rev dir can be removed. */
11312      SVN_ERR(remove_folder(path_rev_shard(dst_fs, rev, iterpool),
11313                            cancel_func, cancel_baton, iterpool));
11314      if (rev > 0 && dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
11315        SVN_ERR(remove_folder(path_revprops_shard(dst_fs, rev, iterpool),
11316                              cancel_func, cancel_baton, iterpool));
11317    }
11318
11319  if (cancel_func)
11320    SVN_ERR(cancel_func(cancel_baton));
11321
11322  /* Now, copy pairs of non-packed revisions and revprop files.
11323   * If necessary, update 'current' after copying all files from a shard. */
11324  SVN_ERR_ASSERT(rev == src_min_unpacked_rev);
11325  SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev);
11326  revprop_src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, pool);
11327  revprop_dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, pool);
11328  SVN_ERR(svn_io_make_dir_recursively(revprop_dst_subdir, pool));
11329  for (; rev <= src_youngest; rev++)
11330    {
11331      svn_error_t *err;
11332
11333      svn_pool_clear(iterpool);
11334
11335      if (cancel_func)
11336        SVN_ERR(cancel_func(cancel_baton));
11337
11338      /* Copy the rev file. */
11339      err = hotcopy_copy_shard_file(src_subdir, dst_subdir,
11340                                    rev, max_files_per_dir,
11341                                    iterpool);
11342      if (err)
11343        {
11344          if (APR_STATUS_IS_ENOENT(err->apr_err) &&
11345              src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11346            {
11347              svn_error_clear(err);
11348
11349              /* The source rev file does not exist. This can happen if the
11350               * source repository is being packed concurrently with this
11351               * hotcopy operation.
11352               *
11353               * If the new revision is now packed, and the youngest revision
11354               * we're interested in is not inside this pack, try to copy the
11355               * pack instead.
11356               *
11357               * If the youngest revision ended up being packed, don't try
11358               * to be smart and work around this. Just abort the hotcopy. */
11359              SVN_ERR(update_min_unpacked_rev(src_fs, pool));
11360              if (is_packed_rev(src_fs, rev))
11361                {
11362                  if (is_packed_rev(src_fs, src_youngest))
11363                    return svn_error_createf(
11364                             SVN_ERR_FS_NO_SUCH_REVISION, NULL,
11365                             _("The assumed HEAD revision (%lu) of the "
11366                               "hotcopy source has been packed while the "
11367                               "hotcopy was in progress; please restart "
11368                               "the hotcopy operation"),
11369                             src_youngest);
11370
11371                  SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
11372                                                    src_fs, dst_fs,
11373                                                    rev, max_files_per_dir,
11374                                                    iterpool));
11375                  rev = dst_min_unpacked_rev;
11376                  continue;
11377                }
11378              else
11379                return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
11380                                         _("Revision %lu disappeared from the "
11381                                           "hotcopy source while hotcopy was "
11382                                           "in progress"), rev);
11383            }
11384          else
11385            return svn_error_trace(err);
11386        }
11387
11388      /* Copy the revprop file. */
11389      SVN_ERR(hotcopy_copy_shard_file(revprop_src_subdir,
11390                                      revprop_dst_subdir,
11391                                      rev, max_files_per_dir,
11392                                      iterpool));
11393
11394      /* After completing a full shard, update 'current'. */
11395      if (max_files_per_dir && rev % max_files_per_dir == 0)
11396        SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, rev, iterpool));
11397    }
11398  svn_pool_destroy(iterpool);
11399
11400  if (cancel_func)
11401    SVN_ERR(cancel_func(cancel_baton));
11402
11403  /* We assume that all revisions were copied now, i.e. we didn't exit the
11404   * above loop early. 'rev' was last incremented during exit of the loop. */
11405  SVN_ERR_ASSERT(rev == src_youngest + 1);
11406
11407  /* All revisions were copied. Update 'current'. */
11408  SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, src_youngest, pool));
11409
11410  /* Replace the locks tree.
11411   * This is racy in case readers are currently trying to list locks in
11412   * the destination. However, we need to get rid of stale locks.
11413   * This is the simplest way of doing this, so we accept this small race. */
11414  dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, pool);
11415  SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton,
11416                             pool));
11417  src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, pool);
11418  SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11419  if (kind == svn_node_dir)
11420    SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path,
11421                                        PATH_LOCKS_DIR, TRUE,
11422                                        cancel_func, cancel_baton, pool));
11423
11424  /* Now copy the node-origins cache tree. */
11425  src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR, pool);
11426  SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11427  if (kind == svn_node_dir)
11428    SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir, dst_fs->path,
11429                                            PATH_NODE_ORIGINS_DIR, TRUE,
11430                                            cancel_func, cancel_baton, pool));
11431
11432  /*
11433   * NB: Data copied below is only read by writers, not readers.
11434   *     Writers are still locked out at this point.
11435   */
11436
11437  if (dst_ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
11438    {
11439      /* Copy the rep cache and then remove entries for revisions
11440       * younger than the destination's youngest revision. */
11441      src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, pool);
11442      dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, pool);
11443      SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11444      if (kind == svn_node_file)
11445        {
11446          SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, pool));
11447          SVN_ERR(svn_fs_fs__del_rep_reference(dst_fs, dst_youngest, pool));
11448        }
11449    }
11450
11451  /* Copy the txn-current file. */
11452  if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
11453    SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
11454                                 PATH_TXN_CURRENT, pool));
11455
11456  /* If a revprop generation file exists in the source filesystem,
11457   * reset it to zero (since this is on a different path, it will not
11458   * overlap with data already in cache).  Also, clean up stale files
11459   * used for the named atomics implementation. */
11460  SVN_ERR(svn_io_check_path(path_revprop_generation(src_fs, pool),
11461                            &kind, pool));
11462  if (kind == svn_node_file)
11463    SVN_ERR(write_revprop_generation_file(dst_fs, 0, pool));
11464
11465  SVN_ERR(cleanup_revprop_namespace(dst_fs));
11466
11467  /* Hotcopied FS is complete. Stamp it with a format file. */
11468  SVN_ERR(write_format(svn_dirent_join(dst_fs->path, PATH_FORMAT, pool),
11469                       dst_ffd->format, max_files_per_dir, TRUE, pool));
11470
11471  return SVN_NO_ERROR;
11472}
11473
11474
11475/* Set up shared data between SRC_FS and DST_FS. */
11476static void
11477hotcopy_setup_shared_fs_data(svn_fs_t *src_fs, svn_fs_t *dst_fs)
11478{
11479  fs_fs_data_t *src_ffd = src_fs->fsap_data;
11480  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11481
11482  /* The common pool and mutexes are shared between src and dst filesystems.
11483   * During hotcopy we only grab the mutexes for the destination, so there
11484   * is no risk of dead-lock. We don't write to the src filesystem. Shared
11485   * data for the src_fs has already been initialised in fs_hotcopy(). */
11486  dst_ffd->shared = src_ffd->shared;
11487}
11488
11489/* Create an empty filesystem at DST_FS at DST_PATH with the same
11490 * configuration as SRC_FS (uuid, format, and other parameters).
11491 * After creation DST_FS has no revisions, not even revision zero. */
11492static svn_error_t *
11493hotcopy_create_empty_dest(svn_fs_t *src_fs,
11494                          svn_fs_t *dst_fs,
11495                          const char *dst_path,
11496                          apr_pool_t *pool)
11497{
11498  fs_fs_data_t *src_ffd = src_fs->fsap_data;
11499  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11500
11501  dst_fs->path = apr_pstrdup(pool, dst_path);
11502
11503  dst_ffd->max_files_per_dir = src_ffd->max_files_per_dir;
11504  dst_ffd->config = src_ffd->config;
11505  dst_ffd->format = src_ffd->format;
11506
11507  /* Create the revision data directories. */
11508  if (dst_ffd->max_files_per_dir)
11509    SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(dst_fs, 0, pool),
11510                                        pool));
11511  else
11512    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11513                                                        PATH_REVS_DIR, pool),
11514                                        pool));
11515
11516  /* Create the revprops directory. */
11517  if (src_ffd->max_files_per_dir)
11518    SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(dst_fs, 0, pool),
11519                                        pool));
11520  else
11521    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11522                                                        PATH_REVPROPS_DIR,
11523                                                        pool),
11524                                        pool));
11525
11526  /* Create the transaction directory. */
11527  SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, PATH_TXNS_DIR,
11528                                                      pool),
11529                                      pool));
11530
11531  /* Create the protorevs directory. */
11532  if (dst_ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
11533    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11534                                                        PATH_TXN_PROTOS_DIR,
11535                                                        pool),
11536                                        pool));
11537
11538  /* Create the 'current' file. */
11539  SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(dst_fs, pool),
11540                             (dst_ffd->format >=
11541                                SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
11542                                ? "0\n" : "0 1 1\n"),
11543                             pool));
11544
11545  /* Create lock file and UUID. */
11546  SVN_ERR(svn_io_file_create(path_lock(dst_fs, pool), "", pool));
11547  SVN_ERR(svn_fs_fs__set_uuid(dst_fs, src_fs->uuid, pool));
11548
11549  /* Create the min unpacked rev file. */
11550  if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11551    SVN_ERR(svn_io_file_create(path_min_unpacked_rev(dst_fs, pool),
11552                                                     "0\n", pool));
11553  /* Create the txn-current file if the repository supports
11554     the transaction sequence file. */
11555  if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
11556    {
11557      SVN_ERR(svn_io_file_create(path_txn_current(dst_fs, pool),
11558                                 "0\n", pool));
11559      SVN_ERR(svn_io_file_create(path_txn_current_lock(dst_fs, pool),
11560                                 "", pool));
11561    }
11562
11563  dst_ffd->youngest_rev_cache = 0;
11564
11565  hotcopy_setup_shared_fs_data(src_fs, dst_fs);
11566  SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
11567
11568  return SVN_NO_ERROR;
11569}
11570
11571svn_error_t *
11572svn_fs_fs__hotcopy(svn_fs_t *src_fs,
11573                   svn_fs_t *dst_fs,
11574                   const char *src_path,
11575                   const char *dst_path,
11576                   svn_boolean_t incremental,
11577                   svn_cancel_func_t cancel_func,
11578                   void *cancel_baton,
11579                   apr_pool_t *pool)
11580{
11581  struct hotcopy_body_baton hbb;
11582
11583  if (cancel_func)
11584    SVN_ERR(cancel_func(cancel_baton));
11585
11586  SVN_ERR(svn_fs_fs__open(src_fs, src_path, pool));
11587
11588  if (incremental)
11589    {
11590      const char *dst_format_abspath;
11591      svn_node_kind_t dst_format_kind;
11592
11593      /* Check destination format to be sure we know how to incrementally
11594       * hotcopy to the destination FS. */
11595      dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT, pool);
11596      SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind, pool));
11597      if (dst_format_kind == svn_node_none)
11598        {
11599          /* Destination doesn't exist yet. Perform a normal hotcopy to a
11600           * empty destination using the same configuration as the source. */
11601          SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
11602        }
11603      else
11604        {
11605          /* Check the existing repository. */
11606          SVN_ERR(svn_fs_fs__open(dst_fs, dst_path, pool));
11607          SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs,
11608                                                          pool));
11609          hotcopy_setup_shared_fs_data(src_fs, dst_fs);
11610          SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
11611        }
11612    }
11613  else
11614    {
11615      /* Start out with an empty destination using the same configuration
11616       * as the source. */
11617      SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
11618    }
11619
11620  if (cancel_func)
11621    SVN_ERR(cancel_func(cancel_baton));
11622
11623  hbb.src_fs = src_fs;
11624  hbb.dst_fs = dst_fs;
11625  hbb.incremental = incremental;
11626  hbb.cancel_func = cancel_func;
11627  hbb.cancel_baton = cancel_baton;
11628  SVN_ERR(svn_fs_fs__with_write_lock(dst_fs, hotcopy_body, &hbb, pool));
11629
11630  return SVN_NO_ERROR;
11631}
11632