fs_fs.c revision 289166
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  apr_file_t *file;
3992  svn_stream_t *stream;
3993  *final_path = path_revprops(fs, rev, pool);
3994
3995  /* ### do we have a directory sitting around already? we really shouldn't
3996     ### have to get the dirname here. */
3997  SVN_ERR(svn_io_open_unique_file3(&file, tmp_path,
3998                                   svn_dirent_dirname(*final_path, pool),
3999                                   svn_io_file_del_none, pool, pool));
4000  stream = svn_stream_from_aprfile2(file, TRUE, pool);
4001  SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
4002  SVN_ERR(svn_stream_close(stream));
4003
4004  /* Flush temporary file to disk and close it. */
4005  SVN_ERR(svn_io_file_flush_to_disk(file, pool));
4006  SVN_ERR(svn_io_file_close(file, pool));
4007
4008  return SVN_NO_ERROR;
4009}
4010
4011/* After writing the new revprop file(s), call this function to move the
4012 * file at TMP_PATH to FINAL_PATH and give it the permissions from
4013 * PERMS_REFERENCE.
4014 *
4015 * If indicated in BUMP_GENERATION, increase FS' revprop generation.
4016 * Finally, delete all the temporary files given in FILES_TO_DELETE.
4017 * The latter may be NULL.
4018 *
4019 * Use POOL for temporary allocations.
4020 */
4021static svn_error_t *
4022switch_to_new_revprop(svn_fs_t *fs,
4023                      const char *final_path,
4024                      const char *tmp_path,
4025                      const char *perms_reference,
4026                      apr_array_header_t *files_to_delete,
4027                      svn_boolean_t bump_generation,
4028                      apr_pool_t *pool)
4029{
4030  /* Now, we may actually be replacing revprops. Make sure that all other
4031     threads and processes will know about this. */
4032  if (bump_generation)
4033    SVN_ERR(begin_revprop_change(fs, pool));
4034
4035  SVN_ERR(move_into_place(tmp_path, final_path, perms_reference, pool));
4036
4037  /* Indicate that the update (if relevant) has been completed. */
4038  if (bump_generation)
4039    SVN_ERR(end_revprop_change(fs, pool));
4040
4041  /* Clean up temporary files, if necessary. */
4042  if (files_to_delete)
4043    {
4044      apr_pool_t *iterpool = svn_pool_create(pool);
4045      int i;
4046
4047      for (i = 0; i < files_to_delete->nelts; ++i)
4048        {
4049          const char *path = APR_ARRAY_IDX(files_to_delete, i, const char*);
4050          SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
4051          svn_pool_clear(iterpool);
4052        }
4053
4054      svn_pool_destroy(iterpool);
4055    }
4056  return SVN_NO_ERROR;
4057}
4058
4059/* Write a pack file header to STREAM that starts at revision START_REVISION
4060 * and contains the indexes [START,END) of SIZES.
4061 */
4062static svn_error_t *
4063serialize_revprops_header(svn_stream_t *stream,
4064                          svn_revnum_t start_revision,
4065                          apr_array_header_t *sizes,
4066                          int start,
4067                          int end,
4068                          apr_pool_t *pool)
4069{
4070  apr_pool_t *iterpool = svn_pool_create(pool);
4071  int i;
4072
4073  SVN_ERR_ASSERT(start < end);
4074
4075  /* start revision and entry count */
4076  SVN_ERR(svn_stream_printf(stream, pool, "%ld\n", start_revision));
4077  SVN_ERR(svn_stream_printf(stream, pool, "%d\n", end - start));
4078
4079  /* the sizes array */
4080  for (i = start; i < end; ++i)
4081    {
4082      apr_off_t size = APR_ARRAY_IDX(sizes, i, apr_off_t);
4083      SVN_ERR(svn_stream_printf(stream, iterpool, "%" APR_OFF_T_FMT "\n",
4084                                size));
4085    }
4086
4087  /* the double newline char indicates the end of the header */
4088  SVN_ERR(svn_stream_printf(stream, iterpool, "\n"));
4089
4090  svn_pool_clear(iterpool);
4091  return SVN_NO_ERROR;
4092}
4093
4094/* Writes the a pack file to FILE.  It copies the serialized data
4095 * from REVPROPS for the indexes [START,END) except for index CHANGED_INDEX.
4096 *
4097 * The data for the latter is taken from NEW_SERIALIZED.  Note, that
4098 * CHANGED_INDEX may be outside the [START,END) range, i.e. no new data is
4099 * taken in that case but only a subset of the old data will be copied.
4100 *
4101 * NEW_TOTAL_SIZE is a hint for pre-allocating buffers of appropriate size.
4102 * POOL is used for temporary allocations.
4103 */
4104static svn_error_t *
4105repack_revprops(svn_fs_t *fs,
4106                packed_revprops_t *revprops,
4107                int start,
4108                int end,
4109                int changed_index,
4110                svn_stringbuf_t *new_serialized,
4111                apr_off_t new_total_size,
4112                apr_file_t *file,
4113                apr_pool_t *pool)
4114{
4115  fs_fs_data_t *ffd = fs->fsap_data;
4116  svn_stream_t *stream;
4117  int i;
4118
4119  /* create data empty buffers and the stream object */
4120  svn_stringbuf_t *uncompressed
4121    = svn_stringbuf_create_ensure((apr_size_t)new_total_size, pool);
4122  svn_stringbuf_t *compressed
4123    = svn_stringbuf_create_empty(pool);
4124  stream = svn_stream_from_stringbuf(uncompressed, pool);
4125
4126  /* write the header*/
4127  SVN_ERR(serialize_revprops_header(stream, revprops->start_revision + start,
4128                                    revprops->sizes, start, end, pool));
4129
4130  /* append the serialized revprops */
4131  for (i = start; i < end; ++i)
4132    if (i == changed_index)
4133      {
4134        SVN_ERR(svn_stream_write(stream,
4135                                 new_serialized->data,
4136                                 &new_serialized->len));
4137      }
4138    else
4139      {
4140        apr_size_t size
4141            = (apr_size_t)APR_ARRAY_IDX(revprops->sizes, i, apr_off_t);
4142        apr_size_t offset
4143            = (apr_size_t)APR_ARRAY_IDX(revprops->offsets, i, apr_off_t);
4144
4145        SVN_ERR(svn_stream_write(stream,
4146                                 revprops->packed_revprops->data + offset,
4147                                 &size));
4148      }
4149
4150  /* flush the stream buffer (if any) to our underlying data buffer */
4151  SVN_ERR(svn_stream_close(stream));
4152
4153  /* compress / store the data */
4154  SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed),
4155                        compressed,
4156                        ffd->compress_packed_revprops
4157                          ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
4158                          : SVN_DELTA_COMPRESSION_LEVEL_NONE));
4159
4160  /* finally, write the content to the target file, flush and close it */
4161  SVN_ERR(svn_io_file_write_full(file, compressed->data, compressed->len,
4162                                 NULL, pool));
4163  SVN_ERR(svn_io_file_flush_to_disk(file, pool));
4164  SVN_ERR(svn_io_file_close(file, pool));
4165
4166  return SVN_NO_ERROR;
4167}
4168
4169/* Allocate a new pack file name for revisions
4170 *     [REVPROPS->START_REVISION + START, REVPROPS->START_REVISION + END - 1]
4171 * of REVPROPS->MANIFEST.  Add the name of old file to FILES_TO_DELETE,
4172 * auto-create that array if necessary.  Return an open file *FILE that is
4173 * allocated in POOL.
4174 */
4175static svn_error_t *
4176repack_file_open(apr_file_t **file,
4177                 svn_fs_t *fs,
4178                 packed_revprops_t *revprops,
4179                 int start,
4180                 int end,
4181                 apr_array_header_t **files_to_delete,
4182                 apr_pool_t *pool)
4183{
4184  apr_int64_t tag;
4185  const char *tag_string;
4186  svn_string_t *new_filename;
4187  int i;
4188  int manifest_offset
4189    = (int)(revprops->start_revision - revprops->manifest_start);
4190
4191  /* get the old (= current) file name and enlist it for later deletion */
4192  const char *old_filename = APR_ARRAY_IDX(revprops->manifest,
4193                                           start + manifest_offset,
4194                                           const char*);
4195
4196  if (*files_to_delete == NULL)
4197    *files_to_delete = apr_array_make(pool, 3, sizeof(const char*));
4198
4199  APR_ARRAY_PUSH(*files_to_delete, const char*)
4200    = svn_dirent_join(revprops->folder, old_filename, pool);
4201
4202  /* increase the tag part, i.e. the counter after the dot */
4203  tag_string = strchr(old_filename, '.');
4204  if (tag_string == NULL)
4205    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4206                             _("Packed file '%s' misses a tag"),
4207                             old_filename);
4208
4209  SVN_ERR(svn_cstring_atoi64(&tag, tag_string + 1));
4210  new_filename = svn_string_createf(pool, "%ld.%" APR_INT64_T_FMT,
4211                                    revprops->start_revision + start,
4212                                    ++tag);
4213
4214  /* update the manifest to point to the new file */
4215  for (i = start; i < end; ++i)
4216    APR_ARRAY_IDX(revprops->manifest, i + manifest_offset, const char*)
4217      = new_filename->data;
4218
4219  /* open the file */
4220  SVN_ERR(svn_io_file_open(file, svn_dirent_join(revprops->folder,
4221                                                 new_filename->data,
4222                                                 pool),
4223                           APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
4224
4225  return SVN_NO_ERROR;
4226}
4227
4228/* For revision REV in filesystem FS, set the revision properties to
4229 * PROPLIST.  Return a new file in *TMP_PATH that the caller shall move
4230 * to *FINAL_PATH to make the change visible.  Files to be deleted will
4231 * be listed in *FILES_TO_DELETE which may remain unchanged / unallocated.
4232 * Use POOL for allocations.
4233 */
4234static svn_error_t *
4235write_packed_revprop(const char **final_path,
4236                     const char **tmp_path,
4237                     apr_array_header_t **files_to_delete,
4238                     svn_fs_t *fs,
4239                     svn_revnum_t rev,
4240                     apr_hash_t *proplist,
4241                     apr_pool_t *pool)
4242{
4243  fs_fs_data_t *ffd = fs->fsap_data;
4244  packed_revprops_t *revprops;
4245  apr_int64_t generation = 0;
4246  svn_stream_t *stream;
4247  apr_file_t *file;
4248  svn_stringbuf_t *serialized;
4249  apr_off_t new_total_size;
4250  int changed_index;
4251
4252  /* read the current revprop generation. This value will not change
4253   * while we hold the global write lock to this FS. */
4254  if (has_revprop_cache(fs, pool))
4255    SVN_ERR(read_revprop_generation(&generation, fs, pool));
4256
4257  /* read contents of the current pack file */
4258  SVN_ERR(read_pack_revprop(&revprops, fs, rev, generation, pool));
4259
4260  /* serialize the new revprops */
4261  serialized = svn_stringbuf_create_empty(pool);
4262  stream = svn_stream_from_stringbuf(serialized, pool);
4263  SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
4264  SVN_ERR(svn_stream_close(stream));
4265
4266  /* calculate the size of the new data */
4267  changed_index = (int)(rev - revprops->start_revision);
4268  new_total_size = revprops->total_size - revprops->serialized_size
4269                 + serialized->len
4270                 + (revprops->offsets->nelts + 2) * SVN_INT64_BUFFER_SIZE;
4271
4272  APR_ARRAY_IDX(revprops->sizes, changed_index, apr_off_t) = serialized->len;
4273
4274  /* can we put the new data into the same pack as the before? */
4275  if (   new_total_size < ffd->revprop_pack_size
4276      || revprops->sizes->nelts == 1)
4277    {
4278      /* simply replace the old pack file with new content as we do it
4279       * in the non-packed case */
4280
4281      *final_path = svn_dirent_join(revprops->folder, revprops->filename,
4282                                    pool);
4283      SVN_ERR(svn_io_open_unique_file3(&file, tmp_path, revprops->folder,
4284                                       svn_io_file_del_none, pool, pool));
4285      SVN_ERR(repack_revprops(fs, revprops, 0, revprops->sizes->nelts,
4286                              changed_index, serialized, new_total_size,
4287                              file, pool));
4288    }
4289  else
4290    {
4291      /* split the pack file into two of roughly equal size */
4292      int right_count, left_count, i;
4293
4294      int left = 0;
4295      int right = revprops->sizes->nelts - 1;
4296      apr_off_t left_size = 2 * SVN_INT64_BUFFER_SIZE;
4297      apr_off_t right_size = 2 * SVN_INT64_BUFFER_SIZE;
4298
4299      /* let left and right side grow such that their size difference
4300       * is minimal after each step. */
4301      while (left <= right)
4302        if (  left_size + APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
4303            < right_size + APR_ARRAY_IDX(revprops->sizes, right, apr_off_t))
4304          {
4305            left_size += APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
4306                      + SVN_INT64_BUFFER_SIZE;
4307            ++left;
4308          }
4309        else
4310          {
4311            right_size += APR_ARRAY_IDX(revprops->sizes, right, apr_off_t)
4312                        + SVN_INT64_BUFFER_SIZE;
4313            --right;
4314          }
4315
4316       /* since the items need much less than SVN_INT64_BUFFER_SIZE
4317        * bytes to represent their length, the split may not be optimal */
4318      left_count = left;
4319      right_count = revprops->sizes->nelts - left;
4320
4321      /* if new_size is large, one side may exceed the pack size limit.
4322       * In that case, split before and after the modified revprop.*/
4323      if (   left_size > ffd->revprop_pack_size
4324          || right_size > ffd->revprop_pack_size)
4325        {
4326          left_count = changed_index;
4327          right_count = revprops->sizes->nelts - left_count - 1;
4328        }
4329
4330      /* write the new, split files */
4331      if (left_count)
4332        {
4333          SVN_ERR(repack_file_open(&file, fs, revprops, 0,
4334                                   left_count, files_to_delete, pool));
4335          SVN_ERR(repack_revprops(fs, revprops, 0, left_count,
4336                                  changed_index, serialized, new_total_size,
4337                                  file, pool));
4338        }
4339
4340      if (left_count + right_count < revprops->sizes->nelts)
4341        {
4342          SVN_ERR(repack_file_open(&file, fs, revprops, changed_index,
4343                                   changed_index + 1, files_to_delete,
4344                                   pool));
4345          SVN_ERR(repack_revprops(fs, revprops, changed_index,
4346                                  changed_index + 1,
4347                                  changed_index, serialized, new_total_size,
4348                                  file, pool));
4349        }
4350
4351      if (right_count)
4352        {
4353          SVN_ERR(repack_file_open(&file, fs, revprops,
4354                                   revprops->sizes->nelts - right_count,
4355                                   revprops->sizes->nelts,
4356                                   files_to_delete, pool));
4357          SVN_ERR(repack_revprops(fs, revprops,
4358                                  revprops->sizes->nelts - right_count,
4359                                  revprops->sizes->nelts, changed_index,
4360                                  serialized, new_total_size, file,
4361                                  pool));
4362        }
4363
4364      /* write the new manifest */
4365      *final_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
4366      SVN_ERR(svn_io_open_unique_file3(&file, tmp_path, revprops->folder,
4367                                       svn_io_file_del_none, pool, pool));
4368
4369      for (i = 0; i < revprops->manifest->nelts; ++i)
4370        {
4371          const char *filename = APR_ARRAY_IDX(revprops->manifest, i,
4372                                               const char*);
4373          SVN_ERR(svn_io_file_write_full(file, filename, strlen(filename),
4374                                         NULL, pool));
4375          SVN_ERR(svn_io_file_putc('\n', file, pool));
4376        }
4377
4378      SVN_ERR(svn_io_file_flush_to_disk(file, pool));
4379      SVN_ERR(svn_io_file_close(file, pool));
4380    }
4381
4382  return SVN_NO_ERROR;
4383}
4384
4385/* Set the revision property list of revision REV in filesystem FS to
4386   PROPLIST.  Use POOL for temporary allocations. */
4387static svn_error_t *
4388set_revision_proplist(svn_fs_t *fs,
4389                      svn_revnum_t rev,
4390                      apr_hash_t *proplist,
4391                      apr_pool_t *pool)
4392{
4393  svn_boolean_t is_packed;
4394  svn_boolean_t bump_generation = FALSE;
4395  const char *final_path;
4396  const char *tmp_path;
4397  const char *perms_reference;
4398  apr_array_header_t *files_to_delete = NULL;
4399
4400  SVN_ERR(ensure_revision_exists(fs, rev, pool));
4401
4402  /* this info will not change while we hold the global FS write lock */
4403  is_packed = is_packed_revprop(fs, rev);
4404
4405  /* Test whether revprops already exist for this revision.
4406   * Only then will we need to bump the revprop generation. */
4407  if (has_revprop_cache(fs, pool))
4408    {
4409      if (is_packed)
4410        {
4411          bump_generation = TRUE;
4412        }
4413      else
4414        {
4415          svn_node_kind_t kind;
4416          SVN_ERR(svn_io_check_path(path_revprops(fs, rev, pool), &kind,
4417                                    pool));
4418          bump_generation = kind != svn_node_none;
4419        }
4420    }
4421
4422  /* Serialize the new revprop data */
4423  if (is_packed)
4424    SVN_ERR(write_packed_revprop(&final_path, &tmp_path, &files_to_delete,
4425                                 fs, rev, proplist, pool));
4426  else
4427    SVN_ERR(write_non_packed_revprop(&final_path, &tmp_path,
4428                                     fs, rev, proplist, pool));
4429
4430  /* We use the rev file of this revision as the perms reference,
4431   * because when setting revprops for the first time, the revprop
4432   * file won't exist and therefore can't serve as its own reference.
4433   * (Whereas the rev file should already exist at this point.)
4434   */
4435  SVN_ERR(svn_fs_fs__path_rev_absolute(&perms_reference, fs, rev, pool));
4436
4437  /* Now, switch to the new revprop data. */
4438  SVN_ERR(switch_to_new_revprop(fs, final_path, tmp_path, perms_reference,
4439                                files_to_delete, bump_generation, pool));
4440
4441  return SVN_NO_ERROR;
4442}
4443
4444svn_error_t *
4445svn_fs_fs__revision_proplist(apr_hash_t **proplist_p,
4446                             svn_fs_t *fs,
4447                             svn_revnum_t rev,
4448                             apr_pool_t *pool)
4449{
4450  SVN_ERR(get_revision_proplist(proplist_p, fs, rev, pool));
4451
4452  return SVN_NO_ERROR;
4453}
4454
4455/* Represents where in the current svndiff data block each
4456   representation is. */
4457struct rep_state
4458{
4459  apr_file_t *file;
4460                    /* The txdelta window cache to use or NULL. */
4461  svn_cache__t *window_cache;
4462                    /* Caches un-deltified windows. May be NULL. */
4463  svn_cache__t *combined_cache;
4464  apr_off_t start;  /* The starting offset for the raw
4465                       svndiff/plaintext data minus header. */
4466  apr_off_t off;    /* The current offset into the file. */
4467  apr_off_t end;    /* The end offset of the raw data. */
4468  int ver;          /* If a delta, what svndiff version? */
4469  int chunk_index;
4470};
4471
4472/* See create_rep_state, which wraps this and adds another error. */
4473static svn_error_t *
4474create_rep_state_body(struct rep_state **rep_state,
4475                      struct rep_args **rep_args,
4476                      apr_file_t **file_hint,
4477                      svn_revnum_t *rev_hint,
4478                      representation_t *rep,
4479                      svn_fs_t *fs,
4480                      apr_pool_t *pool)
4481{
4482  fs_fs_data_t *ffd = fs->fsap_data;
4483  struct rep_state *rs = apr_pcalloc(pool, sizeof(*rs));
4484  struct rep_args *ra;
4485  unsigned char buf[4];
4486
4487  /* If the hint is
4488   * - given,
4489   * - refers to a valid revision,
4490   * - refers to a packed revision,
4491   * - as does the rep we want to read, and
4492   * - refers to the same pack file as the rep
4493   * ...
4494   */
4495  if (   file_hint && rev_hint && *file_hint
4496      && SVN_IS_VALID_REVNUM(*rev_hint)
4497      && *rev_hint < ffd->min_unpacked_rev
4498      && rep->revision < ffd->min_unpacked_rev
4499      && (   (*rev_hint / ffd->max_files_per_dir)
4500          == (rep->revision / ffd->max_files_per_dir)))
4501    {
4502      /* ... we can re-use the same, already open file object
4503       */
4504      apr_off_t offset;
4505      SVN_ERR(get_packed_offset(&offset, fs, rep->revision, pool));
4506
4507      offset += rep->offset;
4508      SVN_ERR(svn_io_file_seek(*file_hint, APR_SET, &offset, pool));
4509
4510      rs->file = *file_hint;
4511    }
4512  else
4513    {
4514      /* otherwise, create a new file object
4515       */
4516      SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool));
4517    }
4518
4519  /* remember the current file, if suggested by the caller */
4520  if (file_hint)
4521    *file_hint = rs->file;
4522  if (rev_hint)
4523    *rev_hint = rep->revision;
4524
4525  /* continue constructing RS and RA */
4526  rs->window_cache = ffd->txdelta_window_cache;
4527  rs->combined_cache = ffd->combined_window_cache;
4528
4529  SVN_ERR(read_rep_line(&ra, rs->file, pool));
4530  SVN_ERR(get_file_offset(&rs->start, rs->file, pool));
4531  rs->off = rs->start;
4532  rs->end = rs->start + rep->size;
4533  *rep_state = rs;
4534  *rep_args = ra;
4535
4536  if (!ra->is_delta)
4537    /* This is a plaintext, so just return the current rep_state. */
4538    return SVN_NO_ERROR;
4539
4540  /* We are dealing with a delta, find out what version. */
4541  SVN_ERR(svn_io_file_read_full2(rs->file, buf, sizeof(buf),
4542                                 NULL, NULL, pool));
4543  /* ### Layering violation */
4544  if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N')))
4545    return svn_error_create
4546      (SVN_ERR_FS_CORRUPT, NULL,
4547       _("Malformed svndiff data in representation"));
4548  rs->ver = buf[3];
4549  rs->chunk_index = 0;
4550  rs->off += 4;
4551
4552  return SVN_NO_ERROR;
4553}
4554
4555/* Read the rep args for REP in filesystem FS and create a rep_state
4556   for reading the representation.  Return the rep_state in *REP_STATE
4557   and the rep args in *REP_ARGS, both allocated in POOL.
4558
4559   When reading multiple reps, i.e. a skip delta chain, you may provide
4560   non-NULL FILE_HINT and REV_HINT.  (If FILE_HINT is not NULL, in the first
4561   call it should be a pointer to NULL.)  The function will use these variables
4562   to store the previous call results and tries to re-use them.  This may
4563   result in significant savings in I/O for packed files.
4564 */
4565static svn_error_t *
4566create_rep_state(struct rep_state **rep_state,
4567                 struct rep_args **rep_args,
4568                 apr_file_t **file_hint,
4569                 svn_revnum_t *rev_hint,
4570                 representation_t *rep,
4571                 svn_fs_t *fs,
4572                 apr_pool_t *pool)
4573{
4574  svn_error_t *err = create_rep_state_body(rep_state, rep_args,
4575                                           file_hint, rev_hint,
4576                                           rep, fs, pool);
4577  if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
4578    {
4579      fs_fs_data_t *ffd = fs->fsap_data;
4580
4581      /* ### This always returns "-1" for transaction reps, because
4582         ### this particular bit of code doesn't know if the rep is
4583         ### stored in the protorev or in the mutable area (for props
4584         ### or dir contents).  It is pretty rare for FSFS to *read*
4585         ### from the protorev file, though, so this is probably OK.
4586         ### And anyone going to debug corruption errors is probably
4587         ### going to jump straight to this comment anyway! */
4588      return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
4589                               "Corrupt representation '%s'",
4590                               rep
4591                               ? representation_string(rep, ffd->format, TRUE,
4592                                                       TRUE, pool)
4593                               : "(null)");
4594    }
4595  /* ### Call representation_string() ? */
4596  return svn_error_trace(err);
4597}
4598
4599struct rep_read_baton
4600{
4601  /* The FS from which we're reading. */
4602  svn_fs_t *fs;
4603
4604  /* If not NULL, this is the base for the first delta window in rs_list */
4605  svn_stringbuf_t *base_window;
4606
4607  /* The state of all prior delta representations. */
4608  apr_array_header_t *rs_list;
4609
4610  /* The plaintext state, if there is a plaintext. */
4611  struct rep_state *src_state;
4612
4613  /* The index of the current delta chunk, if we are reading a delta. */
4614  int chunk_index;
4615
4616  /* The buffer where we store undeltified data. */
4617  char *buf;
4618  apr_size_t buf_pos;
4619  apr_size_t buf_len;
4620
4621  /* A checksum context for summing the data read in order to verify it.
4622     Note: we don't need to use the sha1 checksum because we're only doing
4623     data verification, for which md5 is perfectly safe.  */
4624  svn_checksum_ctx_t *md5_checksum_ctx;
4625
4626  svn_boolean_t checksum_finalized;
4627
4628  /* The stored checksum of the representation we are reading, its
4629     length, and the amount we've read so far.  Some of this
4630     information is redundant with rs_list and src_state, but it's
4631     convenient for the checksumming code to have it here. */
4632  svn_checksum_t *md5_checksum;
4633
4634  svn_filesize_t len;
4635  svn_filesize_t off;
4636
4637  /* The key for the fulltext cache for this rep, if there is a
4638     fulltext cache. */
4639  pair_cache_key_t fulltext_cache_key;
4640  /* The text we've been reading, if we're going to cache it. */
4641  svn_stringbuf_t *current_fulltext;
4642
4643  /* Used for temporary allocations during the read. */
4644  apr_pool_t *pool;
4645
4646  /* Pool used to store file handles and other data that is persistant
4647     for the entire stream read. */
4648  apr_pool_t *filehandle_pool;
4649};
4650
4651/* Combine the name of the rev file in RS with the given OFFSET to form
4652 * a cache lookup key.  Allocations will be made from POOL.  May return
4653 * NULL if the key cannot be constructed. */
4654static const char*
4655get_window_key(struct rep_state *rs, apr_off_t offset, apr_pool_t *pool)
4656{
4657  const char *name;
4658  const char *last_part;
4659  const char *name_last;
4660
4661  /* the rev file name containing the txdelta window.
4662   * If this fails we are in serious trouble anyways.
4663   * And if nobody else detects the problems, the file content checksum
4664   * comparison _will_ find them.
4665   */
4666  if (apr_file_name_get(&name, rs->file))
4667    return NULL;
4668
4669  /* Handle packed files as well by scanning backwards until we find the
4670   * revision or pack number. */
4671  name_last = name + strlen(name) - 1;
4672  while (! svn_ctype_isdigit(*name_last))
4673    --name_last;
4674
4675  last_part = name_last;
4676  while (svn_ctype_isdigit(*last_part))
4677    --last_part;
4678
4679  /* We must differentiate between packed files (as of today, the number
4680   * is being followed by a dot) and non-packed files (followed by \0).
4681   * Otherwise, there might be overlaps in the numbering range if the
4682   * repo gets packed after caching the txdeltas of non-packed revs.
4683   * => add the first non-digit char to the packed number. */
4684  if (name_last[1] != '\0')
4685    ++name_last;
4686
4687  /* copy one char MORE than the actual number to mark packed files,
4688   * i.e. packed revision file content uses different key space then
4689   * non-packed ones: keys for packed rev file content ends with a dot
4690   * for non-packed rev files they end with a digit. */
4691  name = apr_pstrndup(pool, last_part + 1, name_last - last_part);
4692  return svn_fs_fs__combine_number_and_string(offset, name, pool);
4693}
4694
4695/* Read the WINDOW_P for the rep state RS from the current FSFS session's
4696 * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
4697 * cache has been given. If a cache is available IS_CACHED will inform
4698 * the caller about the success of the lookup. Allocations (of the window
4699 * in particualar) will be made from POOL.
4700 *
4701 * If the information could be found, put RS and the position within the
4702 * rev file into the same state as if the data had just been read from it.
4703 */
4704static svn_error_t *
4705get_cached_window(svn_txdelta_window_t **window_p,
4706                  struct rep_state *rs,
4707                  svn_boolean_t *is_cached,
4708                  apr_pool_t *pool)
4709{
4710  if (! rs->window_cache)
4711    {
4712      /* txdelta window has not been enabled */
4713      *is_cached = FALSE;
4714    }
4715  else
4716    {
4717      /* ask the cache for the desired txdelta window */
4718      svn_fs_fs__txdelta_cached_window_t *cached_window;
4719      SVN_ERR(svn_cache__get((void **) &cached_window,
4720                             is_cached,
4721                             rs->window_cache,
4722                             get_window_key(rs, rs->off, pool),
4723                             pool));
4724
4725      if (*is_cached)
4726        {
4727          /* found it. Pass it back to the caller. */
4728          *window_p = cached_window->window;
4729
4730          /* manipulate the RS as if we just read the data */
4731          rs->chunk_index++;
4732          rs->off = cached_window->end_offset;
4733
4734          /* manipulate the rev file as if we just read from it */
4735          SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
4736        }
4737    }
4738
4739  return SVN_NO_ERROR;
4740}
4741
4742/* Store the WINDOW read at OFFSET for the rep state RS in the current
4743 * FSFS session's cache. This will be a no-op if no cache has been given.
4744 * Temporary allocations will be made from SCRATCH_POOL. */
4745static svn_error_t *
4746set_cached_window(svn_txdelta_window_t *window,
4747                  struct rep_state *rs,
4748                  apr_off_t offset,
4749                  apr_pool_t *scratch_pool)
4750{
4751  if (rs->window_cache)
4752    {
4753      /* store the window and the first offset _past_ it */
4754      svn_fs_fs__txdelta_cached_window_t cached_window;
4755
4756      cached_window.window = window;
4757      cached_window.end_offset = rs->off;
4758
4759      /* but key it with the start offset because that is the known state
4760       * when we will look it up */
4761      return svn_cache__set(rs->window_cache,
4762                            get_window_key(rs, offset, scratch_pool),
4763                            &cached_window,
4764                            scratch_pool);
4765    }
4766
4767  return SVN_NO_ERROR;
4768}
4769
4770/* Read the WINDOW_P for the rep state RS from the current FSFS session's
4771 * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
4772 * cache has been given. If a cache is available IS_CACHED will inform
4773 * the caller about the success of the lookup. Allocations (of the window
4774 * in particualar) will be made from POOL.
4775 */
4776static svn_error_t *
4777get_cached_combined_window(svn_stringbuf_t **window_p,
4778                           struct rep_state *rs,
4779                           svn_boolean_t *is_cached,
4780                           apr_pool_t *pool)
4781{
4782  if (! rs->combined_cache)
4783    {
4784      /* txdelta window has not been enabled */
4785      *is_cached = FALSE;
4786    }
4787  else
4788    {
4789      /* ask the cache for the desired txdelta window */
4790      return svn_cache__get((void **)window_p,
4791                            is_cached,
4792                            rs->combined_cache,
4793                            get_window_key(rs, rs->start, pool),
4794                            pool);
4795    }
4796
4797  return SVN_NO_ERROR;
4798}
4799
4800/* Store the WINDOW read at OFFSET for the rep state RS in the current
4801 * FSFS session's cache. This will be a no-op if no cache has been given.
4802 * Temporary allocations will be made from SCRATCH_POOL. */
4803static svn_error_t *
4804set_cached_combined_window(svn_stringbuf_t *window,
4805                           struct rep_state *rs,
4806                           apr_off_t offset,
4807                           apr_pool_t *scratch_pool)
4808{
4809  if (rs->combined_cache)
4810    {
4811      /* but key it with the start offset because that is the known state
4812       * when we will look it up */
4813      return svn_cache__set(rs->combined_cache,
4814                            get_window_key(rs, offset, scratch_pool),
4815                            window,
4816                            scratch_pool);
4817    }
4818
4819  return SVN_NO_ERROR;
4820}
4821
4822/* Build an array of rep_state structures in *LIST giving the delta
4823   reps from first_rep to a plain-text or self-compressed rep.  Set
4824   *SRC_STATE to the plain-text rep we find at the end of the chain,
4825   or to NULL if the final delta representation is self-compressed.
4826   The representation to start from is designated by filesystem FS, id
4827   ID, and representation REP.
4828   Also, set *WINDOW_P to the base window content for *LIST, if it
4829   could be found in cache. Otherwise, *LIST will contain the base
4830   representation for the whole delta chain.
4831   Finally, return the expanded size of the representation in
4832   *EXPANDED_SIZE. It will take care of cases where only the on-disk
4833   size is known.  */
4834static svn_error_t *
4835build_rep_list(apr_array_header_t **list,
4836               svn_stringbuf_t **window_p,
4837               struct rep_state **src_state,
4838               svn_filesize_t *expanded_size,
4839               svn_fs_t *fs,
4840               representation_t *first_rep,
4841               apr_pool_t *pool)
4842{
4843  representation_t rep;
4844  struct rep_state *rs = NULL;
4845  struct rep_args *rep_args;
4846  svn_boolean_t is_cached = FALSE;
4847  apr_file_t *last_file = NULL;
4848  svn_revnum_t last_revision;
4849
4850  *list = apr_array_make(pool, 1, sizeof(struct rep_state *));
4851  rep = *first_rep;
4852
4853  /* The value as stored in the data struct.
4854     0 is either for unknown length or actually zero length. */
4855  *expanded_size = first_rep->expanded_size;
4856
4857  /* for the top-level rep, we need the rep_args */
4858  SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
4859                           &last_revision, &rep, fs, pool));
4860
4861  /* Unknown size or empty representation?
4862     That implies the this being the first iteration.
4863     Usually size equals on-disk size, except for empty,
4864     compressed representations (delta, size = 4).
4865     Please note that for all non-empty deltas have
4866     a 4-byte header _plus_ some data. */
4867  if (*expanded_size == 0)
4868    if (! rep_args->is_delta || first_rep->size != 4)
4869      *expanded_size = first_rep->size;
4870
4871  while (1)
4872    {
4873      /* fetch state, if that has not been done already */
4874      if (!rs)
4875        SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
4876                                &last_revision, &rep, fs, pool));
4877
4878      SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool));
4879      if (is_cached)
4880        {
4881          /* We already have a reconstructed window in our cache.
4882             Write a pseudo rep_state with the full length. */
4883          rs->off = rs->start;
4884          rs->end = rs->start + (*window_p)->len;
4885          *src_state = rs;
4886          return SVN_NO_ERROR;
4887        }
4888
4889      if (!rep_args->is_delta)
4890        {
4891          /* This is a plaintext, so just return the current rep_state. */
4892          *src_state = rs;
4893          return SVN_NO_ERROR;
4894        }
4895
4896      /* Push this rep onto the list.  If it's self-compressed, we're done. */
4897      APR_ARRAY_PUSH(*list, struct rep_state *) = rs;
4898      if (rep_args->is_delta_vs_empty)
4899        {
4900          *src_state = NULL;
4901          return SVN_NO_ERROR;
4902        }
4903
4904      rep.revision = rep_args->base_revision;
4905      rep.offset = rep_args->base_offset;
4906      rep.size = rep_args->base_length;
4907      rep.txn_id = NULL;
4908
4909      rs = NULL;
4910    }
4911}
4912
4913
4914/* Create a rep_read_baton structure for node revision NODEREV in
4915   filesystem FS and store it in *RB_P.  If FULLTEXT_CACHE_KEY is not
4916   NULL, it is the rep's key in the fulltext cache, and a stringbuf
4917   must be allocated to store the text.  Perform all allocations in
4918   POOL.  If rep is mutable, it must be for file contents. */
4919static svn_error_t *
4920rep_read_get_baton(struct rep_read_baton **rb_p,
4921                   svn_fs_t *fs,
4922                   representation_t *rep,
4923                   pair_cache_key_t fulltext_cache_key,
4924                   apr_pool_t *pool)
4925{
4926  struct rep_read_baton *b;
4927
4928  b = apr_pcalloc(pool, sizeof(*b));
4929  b->fs = fs;
4930  b->base_window = NULL;
4931  b->chunk_index = 0;
4932  b->buf = NULL;
4933  b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
4934  b->checksum_finalized = FALSE;
4935  b->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
4936  b->len = rep->expanded_size;
4937  b->off = 0;
4938  b->fulltext_cache_key = fulltext_cache_key;
4939  b->pool = svn_pool_create(pool);
4940  b->filehandle_pool = svn_pool_create(pool);
4941
4942  SVN_ERR(build_rep_list(&b->rs_list, &b->base_window,
4943                         &b->src_state, &b->len, fs, rep,
4944                         b->filehandle_pool));
4945
4946  if (SVN_IS_VALID_REVNUM(fulltext_cache_key.revision))
4947    b->current_fulltext = svn_stringbuf_create_ensure
4948                            ((apr_size_t)b->len,
4949                             b->filehandle_pool);
4950  else
4951    b->current_fulltext = NULL;
4952
4953  /* Save our output baton. */
4954  *rb_p = b;
4955
4956  return SVN_NO_ERROR;
4957}
4958
4959/* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
4960   window into *NWIN. */
4961static svn_error_t *
4962read_delta_window(svn_txdelta_window_t **nwin, int this_chunk,
4963                  struct rep_state *rs, apr_pool_t *pool)
4964{
4965  svn_stream_t *stream;
4966  svn_boolean_t is_cached;
4967  apr_off_t old_offset;
4968
4969  SVN_ERR_ASSERT(rs->chunk_index <= this_chunk);
4970
4971  /* RS->FILE may be shared between RS instances -> make sure we point
4972   * to the right data. */
4973  SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
4974
4975  /* Skip windows to reach the current chunk if we aren't there yet. */
4976  while (rs->chunk_index < this_chunk)
4977    {
4978      SVN_ERR(svn_txdelta_skip_svndiff_window(rs->file, rs->ver, pool));
4979      rs->chunk_index++;
4980      SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
4981      if (rs->off >= rs->end)
4982        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4983                                _("Reading one svndiff window read "
4984                                  "beyond the end of the "
4985                                  "representation"));
4986    }
4987
4988  /* Read the next window. But first, try to find it in the cache. */
4989  SVN_ERR(get_cached_window(nwin, rs, &is_cached, pool));
4990  if (is_cached)
4991    return SVN_NO_ERROR;
4992
4993  /* Actually read the next window. */
4994  old_offset = rs->off;
4995  stream = svn_stream_from_aprfile2(rs->file, TRUE, pool);
4996  SVN_ERR(svn_txdelta_read_svndiff_window(nwin, stream, rs->ver, pool));
4997  rs->chunk_index++;
4998  SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
4999
5000  if (rs->off > rs->end)
5001    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5002                            _("Reading one svndiff window read beyond "
5003                              "the end of the representation"));
5004
5005  /* the window has not been cached before, thus cache it now
5006   * (if caching is used for them at all) */
5007  return set_cached_window(*nwin, rs, old_offset, pool);
5008}
5009
5010/* Read SIZE bytes from the representation RS and return it in *NWIN. */
5011static svn_error_t *
5012read_plain_window(svn_stringbuf_t **nwin, struct rep_state *rs,
5013                  apr_size_t size, apr_pool_t *pool)
5014{
5015  /* RS->FILE may be shared between RS instances -> make sure we point
5016   * to the right data. */
5017  SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
5018
5019  /* Read the plain data. */
5020  *nwin = svn_stringbuf_create_ensure(size, pool);
5021  SVN_ERR(svn_io_file_read_full2(rs->file, (*nwin)->data, size, NULL, NULL,
5022                                 pool));
5023  (*nwin)->data[size] = 0;
5024
5025  /* Update RS. */
5026  rs->off += (apr_off_t)size;
5027
5028  return SVN_NO_ERROR;
5029}
5030
5031/* Get the undeltified window that is a result of combining all deltas
5032   from the current desired representation identified in *RB with its
5033   base representation.  Store the window in *RESULT. */
5034static svn_error_t *
5035get_combined_window(svn_stringbuf_t **result,
5036                    struct rep_read_baton *rb)
5037{
5038  apr_pool_t *pool, *new_pool, *window_pool;
5039  int i;
5040  svn_txdelta_window_t *window;
5041  apr_array_header_t *windows;
5042  svn_stringbuf_t *source, *buf = rb->base_window;
5043  struct rep_state *rs;
5044
5045  /* Read all windows that we need to combine. This is fine because
5046     the size of each window is relatively small (100kB) and skip-
5047     delta limits the number of deltas in a chain to well under 100.
5048     Stop early if one of them does not depend on its predecessors. */
5049  window_pool = svn_pool_create(rb->pool);
5050  windows = apr_array_make(window_pool, 0, sizeof(svn_txdelta_window_t *));
5051  for (i = 0; i < rb->rs_list->nelts; ++i)
5052    {
5053      rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
5054      SVN_ERR(read_delta_window(&window, rb->chunk_index, rs, window_pool));
5055
5056      APR_ARRAY_PUSH(windows, svn_txdelta_window_t *) = window;
5057      if (window->src_ops == 0)
5058        {
5059          ++i;
5060          break;
5061        }
5062    }
5063
5064  /* Combine in the windows from the other delta reps. */
5065  pool = svn_pool_create(rb->pool);
5066  for (--i; i >= 0; --i)
5067    {
5068
5069      rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
5070      window = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *);
5071
5072      /* Maybe, we've got a PLAIN start representation.  If we do, read
5073         as much data from it as the needed for the txdelta window's source
5074         view.
5075         Note that BUF / SOURCE may only be NULL in the first iteration.
5076         Also note that we may have short-cut reading the delta chain --
5077         in which case SRC_OPS is 0 and it might not be a PLAIN rep. */
5078      source = buf;
5079      if (source == NULL && rb->src_state != NULL && window->src_ops)
5080        SVN_ERR(read_plain_window(&source, rb->src_state, window->sview_len,
5081                                  pool));
5082
5083      /* Combine this window with the current one. */
5084      new_pool = svn_pool_create(rb->pool);
5085      buf = svn_stringbuf_create_ensure(window->tview_len, new_pool);
5086      buf->len = window->tview_len;
5087
5088      svn_txdelta_apply_instructions(window, source ? source->data : NULL,
5089                                     buf->data, &buf->len);
5090      if (buf->len != window->tview_len)
5091        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5092                                _("svndiff window length is "
5093                                  "corrupt"));
5094
5095      /* Cache windows only if the whole rep content could be read as a
5096         single chunk.  Only then will no other chunk need a deeper RS
5097         list than the cached chunk. */
5098      if ((rb->chunk_index == 0) && (rs->off == rs->end))
5099        SVN_ERR(set_cached_combined_window(buf, rs, rs->start, new_pool));
5100
5101      /* Cycle pools so that we only need to hold three windows at a time. */
5102      svn_pool_destroy(pool);
5103      pool = new_pool;
5104    }
5105
5106  svn_pool_destroy(window_pool);
5107
5108  *result = buf;
5109  return SVN_NO_ERROR;
5110}
5111
5112/* Returns whether or not the expanded fulltext of the file is cachable
5113 * based on its size SIZE.  The decision depends on the cache used by RB.
5114 */
5115static svn_boolean_t
5116fulltext_size_is_cachable(fs_fs_data_t *ffd, svn_filesize_t size)
5117{
5118  return (size < APR_SIZE_MAX)
5119      && svn_cache__is_cachable(ffd->fulltext_cache, (apr_size_t)size);
5120}
5121
5122/* Close method used on streams returned by read_representation().
5123 */
5124static svn_error_t *
5125rep_read_contents_close(void *baton)
5126{
5127  struct rep_read_baton *rb = baton;
5128
5129  svn_pool_destroy(rb->pool);
5130  svn_pool_destroy(rb->filehandle_pool);
5131
5132  return SVN_NO_ERROR;
5133}
5134
5135/* Return the next *LEN bytes of the rep and store them in *BUF. */
5136static svn_error_t *
5137get_contents(struct rep_read_baton *rb,
5138             char *buf,
5139             apr_size_t *len)
5140{
5141  apr_size_t copy_len, remaining = *len;
5142  char *cur = buf;
5143  struct rep_state *rs;
5144
5145  /* Special case for when there are no delta reps, only a plain
5146     text. */
5147  if (rb->rs_list->nelts == 0)
5148    {
5149      copy_len = remaining;
5150      rs = rb->src_state;
5151
5152      if (rb->base_window != NULL)
5153        {
5154          /* We got the desired rep directly from the cache.
5155             This is where we need the pseudo rep_state created
5156             by build_rep_list(). */
5157          apr_size_t offset = (apr_size_t)(rs->off - rs->start);
5158          if (copy_len + offset > rb->base_window->len)
5159            copy_len = offset < rb->base_window->len
5160                     ? rb->base_window->len - offset
5161                     : 0ul;
5162
5163          memcpy (cur, rb->base_window->data + offset, copy_len);
5164        }
5165      else
5166        {
5167          if (((apr_off_t) copy_len) > rs->end - rs->off)
5168            copy_len = (apr_size_t) (rs->end - rs->off);
5169          SVN_ERR(svn_io_file_read_full2(rs->file, cur, copy_len, NULL,
5170                                         NULL, rb->pool));
5171        }
5172
5173      rs->off += copy_len;
5174      *len = copy_len;
5175      return SVN_NO_ERROR;
5176    }
5177
5178  while (remaining > 0)
5179    {
5180      /* If we have buffered data from a previous chunk, use that. */
5181      if (rb->buf)
5182        {
5183          /* Determine how much to copy from the buffer. */
5184          copy_len = rb->buf_len - rb->buf_pos;
5185          if (copy_len > remaining)
5186            copy_len = remaining;
5187
5188          /* Actually copy the data. */
5189          memcpy(cur, rb->buf + rb->buf_pos, copy_len);
5190          rb->buf_pos += copy_len;
5191          cur += copy_len;
5192          remaining -= copy_len;
5193
5194          /* If the buffer is all used up, clear it and empty the
5195             local pool. */
5196          if (rb->buf_pos == rb->buf_len)
5197            {
5198              svn_pool_clear(rb->pool);
5199              rb->buf = NULL;
5200            }
5201        }
5202      else
5203        {
5204          svn_stringbuf_t *sbuf = NULL;
5205
5206          rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *);
5207          if (rs->off == rs->end)
5208            break;
5209
5210          /* Get more buffered data by evaluating a chunk. */
5211          SVN_ERR(get_combined_window(&sbuf, rb));
5212
5213          rb->chunk_index++;
5214          rb->buf_len = sbuf->len;
5215          rb->buf = sbuf->data;
5216          rb->buf_pos = 0;
5217        }
5218    }
5219
5220  *len = cur - buf;
5221
5222  return SVN_NO_ERROR;
5223}
5224
5225/* BATON is of type `rep_read_baton'; read the next *LEN bytes of the
5226   representation and store them in *BUF.  Sum as we read and verify
5227   the MD5 sum at the end. */
5228static svn_error_t *
5229rep_read_contents(void *baton,
5230                  char *buf,
5231                  apr_size_t *len)
5232{
5233  struct rep_read_baton *rb = baton;
5234
5235  /* Get the next block of data. */
5236  SVN_ERR(get_contents(rb, buf, len));
5237
5238  if (rb->current_fulltext)
5239    svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len);
5240
5241  /* Perform checksumming.  We want to check the checksum as soon as
5242     the last byte of data is read, in case the caller never performs
5243     a short read, but we don't want to finalize the MD5 context
5244     twice. */
5245  if (!rb->checksum_finalized)
5246    {
5247      SVN_ERR(svn_checksum_update(rb->md5_checksum_ctx, buf, *len));
5248      rb->off += *len;
5249      if (rb->off == rb->len)
5250        {
5251          svn_checksum_t *md5_checksum;
5252
5253          rb->checksum_finalized = TRUE;
5254          SVN_ERR(svn_checksum_final(&md5_checksum, rb->md5_checksum_ctx,
5255                                     rb->pool));
5256          if (!svn_checksum_match(md5_checksum, rb->md5_checksum))
5257            return svn_error_create(SVN_ERR_FS_CORRUPT,
5258                    svn_checksum_mismatch_err(rb->md5_checksum, md5_checksum,
5259                        rb->pool,
5260                        _("Checksum mismatch while reading representation")),
5261                    NULL);
5262        }
5263    }
5264
5265  if (rb->off == rb->len && rb->current_fulltext)
5266    {
5267      fs_fs_data_t *ffd = rb->fs->fsap_data;
5268      SVN_ERR(svn_cache__set(ffd->fulltext_cache, &rb->fulltext_cache_key,
5269                             rb->current_fulltext, rb->pool));
5270      rb->current_fulltext = NULL;
5271    }
5272
5273  return SVN_NO_ERROR;
5274}
5275
5276
5277/* Return a stream in *CONTENTS_P that will read the contents of a
5278   representation stored at the location given by REP.  Appropriate
5279   for any kind of immutable representation, but only for file
5280   contents (not props or directory contents) in mutable
5281   representations.
5282
5283   If REP is NULL, the representation is assumed to be empty, and the
5284   empty stream is returned.
5285*/
5286static svn_error_t *
5287read_representation(svn_stream_t **contents_p,
5288                    svn_fs_t *fs,
5289                    representation_t *rep,
5290                    apr_pool_t *pool)
5291{
5292  if (! rep)
5293    {
5294      *contents_p = svn_stream_empty(pool);
5295    }
5296  else
5297    {
5298      fs_fs_data_t *ffd = fs->fsap_data;
5299      pair_cache_key_t fulltext_cache_key = { 0 };
5300      svn_filesize_t len = rep->expanded_size ? rep->expanded_size : rep->size;
5301      struct rep_read_baton *rb;
5302
5303      fulltext_cache_key.revision = rep->revision;
5304      fulltext_cache_key.second = rep->offset;
5305      if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
5306          && fulltext_size_is_cachable(ffd, len))
5307        {
5308          svn_stringbuf_t *fulltext;
5309          svn_boolean_t is_cached;
5310          SVN_ERR(svn_cache__get((void **) &fulltext, &is_cached,
5311                                 ffd->fulltext_cache, &fulltext_cache_key,
5312                                 pool));
5313          if (is_cached)
5314            {
5315              *contents_p = svn_stream_from_stringbuf(fulltext, pool);
5316              return SVN_NO_ERROR;
5317            }
5318        }
5319      else
5320        fulltext_cache_key.revision = SVN_INVALID_REVNUM;
5321
5322      SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool));
5323
5324      *contents_p = svn_stream_create(rb, pool);
5325      svn_stream_set_read(*contents_p, rep_read_contents);
5326      svn_stream_set_close(*contents_p, rep_read_contents_close);
5327    }
5328
5329  return SVN_NO_ERROR;
5330}
5331
5332svn_error_t *
5333svn_fs_fs__get_contents(svn_stream_t **contents_p,
5334                        svn_fs_t *fs,
5335                        node_revision_t *noderev,
5336                        apr_pool_t *pool)
5337{
5338  return read_representation(contents_p, fs, noderev->data_rep, pool);
5339}
5340
5341/* Baton used when reading delta windows. */
5342struct delta_read_baton
5343{
5344  struct rep_state *rs;
5345  svn_checksum_t *checksum;
5346};
5347
5348/* This implements the svn_txdelta_next_window_fn_t interface. */
5349static svn_error_t *
5350delta_read_next_window(svn_txdelta_window_t **window, void *baton,
5351                       apr_pool_t *pool)
5352{
5353  struct delta_read_baton *drb = baton;
5354
5355  if (drb->rs->off == drb->rs->end)
5356    {
5357      *window = NULL;
5358      return SVN_NO_ERROR;
5359    }
5360
5361  return read_delta_window(window, drb->rs->chunk_index, drb->rs, pool);
5362}
5363
5364/* This implements the svn_txdelta_md5_digest_fn_t interface. */
5365static const unsigned char *
5366delta_read_md5_digest(void *baton)
5367{
5368  struct delta_read_baton *drb = baton;
5369
5370  if (drb->checksum->kind == svn_checksum_md5)
5371    return drb->checksum->digest;
5372  else
5373    return NULL;
5374}
5375
5376svn_error_t *
5377svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
5378                                 svn_fs_t *fs,
5379                                 node_revision_t *source,
5380                                 node_revision_t *target,
5381                                 apr_pool_t *pool)
5382{
5383  svn_stream_t *source_stream, *target_stream;
5384
5385  /* Try a shortcut: if the target is stored as a delta against the source,
5386     then just use that delta. */
5387  if (source && source->data_rep && target->data_rep)
5388    {
5389      struct rep_state *rep_state;
5390      struct rep_args *rep_args;
5391
5392      /* Read target's base rep if any. */
5393      SVN_ERR(create_rep_state(&rep_state, &rep_args, NULL, NULL,
5394                               target->data_rep, fs, pool));
5395
5396      /* If that matches source, then use this delta as is.
5397         Note that we want an actual delta here.  E.g. a self-delta would
5398         not be good enough. */
5399      if (rep_args->is_delta
5400          && rep_args->base_revision == source->data_rep->revision
5401          && rep_args->base_offset == source->data_rep->offset)
5402        {
5403          /* Create the delta read baton. */
5404          struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb));
5405          drb->rs = rep_state;
5406          drb->checksum = svn_checksum_dup(target->data_rep->md5_checksum,
5407                                           pool);
5408          *stream_p = svn_txdelta_stream_create(drb, delta_read_next_window,
5409                                                delta_read_md5_digest, pool);
5410          return SVN_NO_ERROR;
5411        }
5412      else
5413        SVN_ERR(svn_io_file_close(rep_state->file, pool));
5414    }
5415
5416  /* Read both fulltexts and construct a delta. */
5417  if (source)
5418    SVN_ERR(read_representation(&source_stream, fs, source->data_rep, pool));
5419  else
5420    source_stream = svn_stream_empty(pool);
5421  SVN_ERR(read_representation(&target_stream, fs, target->data_rep, pool));
5422
5423  /* Because source and target stream will already verify their content,
5424   * there is no need to do this once more.  In particular if the stream
5425   * content is being fetched from cache. */
5426  svn_txdelta2(stream_p, source_stream, target_stream, FALSE, pool);
5427
5428  return SVN_NO_ERROR;
5429}
5430
5431/* Baton for cache_access_wrapper. Wraps the original parameters of
5432 * svn_fs_fs__try_process_file_content().
5433 */
5434typedef struct cache_access_wrapper_baton_t
5435{
5436  svn_fs_process_contents_func_t func;
5437  void* baton;
5438} cache_access_wrapper_baton_t;
5439
5440/* Wrapper to translate between svn_fs_process_contents_func_t and
5441 * svn_cache__partial_getter_func_t.
5442 */
5443static svn_error_t *
5444cache_access_wrapper(void **out,
5445                     const void *data,
5446                     apr_size_t data_len,
5447                     void *baton,
5448                     apr_pool_t *pool)
5449{
5450  cache_access_wrapper_baton_t *wrapper_baton = baton;
5451
5452  SVN_ERR(wrapper_baton->func((const unsigned char *)data,
5453                              data_len - 1, /* cache adds terminating 0 */
5454                              wrapper_baton->baton,
5455                              pool));
5456
5457  /* non-NULL value to signal the calling cache that all went well */
5458  *out = baton;
5459
5460  return SVN_NO_ERROR;
5461}
5462
5463svn_error_t *
5464svn_fs_fs__try_process_file_contents(svn_boolean_t *success,
5465                                     svn_fs_t *fs,
5466                                     node_revision_t *noderev,
5467                                     svn_fs_process_contents_func_t processor,
5468                                     void* baton,
5469                                     apr_pool_t *pool)
5470{
5471  representation_t *rep = noderev->data_rep;
5472  if (rep)
5473    {
5474      fs_fs_data_t *ffd = fs->fsap_data;
5475      pair_cache_key_t fulltext_cache_key = { 0 };
5476
5477      fulltext_cache_key.revision = rep->revision;
5478      fulltext_cache_key.second = rep->offset;
5479      if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
5480          && fulltext_size_is_cachable(ffd, rep->expanded_size))
5481        {
5482          cache_access_wrapper_baton_t wrapper_baton;
5483          void *dummy = NULL;
5484
5485          wrapper_baton.func = processor;
5486          wrapper_baton.baton = baton;
5487          return svn_cache__get_partial(&dummy, success,
5488                                        ffd->fulltext_cache,
5489                                        &fulltext_cache_key,
5490                                        cache_access_wrapper,
5491                                        &wrapper_baton,
5492                                        pool);
5493        }
5494    }
5495
5496  *success = FALSE;
5497  return SVN_NO_ERROR;
5498}
5499
5500/* Fetch the contents of a directory into ENTRIES.  Values are stored
5501   as filename to string mappings; further conversion is necessary to
5502   convert them into svn_fs_dirent_t values. */
5503static svn_error_t *
5504get_dir_contents(apr_hash_t *entries,
5505                 svn_fs_t *fs,
5506                 node_revision_t *noderev,
5507                 apr_pool_t *pool)
5508{
5509  svn_stream_t *contents;
5510
5511  if (noderev->data_rep && noderev->data_rep->txn_id)
5512    {
5513      const char *filename = path_txn_node_children(fs, noderev->id, pool);
5514
5515      /* The representation is mutable.  Read the old directory
5516         contents from the mutable children file, followed by the
5517         changes we've made in this transaction. */
5518      SVN_ERR(svn_stream_open_readonly(&contents, filename, pool, pool));
5519      SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
5520      SVN_ERR(svn_hash_read_incremental(entries, contents, NULL, pool));
5521      SVN_ERR(svn_stream_close(contents));
5522    }
5523  else if (noderev->data_rep)
5524    {
5525      /* use a temporary pool for temp objects.
5526       * Also undeltify content before parsing it. Otherwise, we could only
5527       * parse it byte-by-byte.
5528       */
5529      apr_pool_t *text_pool = svn_pool_create(pool);
5530      apr_size_t len = noderev->data_rep->expanded_size
5531                     ? (apr_size_t)noderev->data_rep->expanded_size
5532                     : (apr_size_t)noderev->data_rep->size;
5533      svn_stringbuf_t *text = svn_stringbuf_create_ensure(len, text_pool);
5534      text->len = len;
5535
5536      /* The representation is immutable.  Read it normally. */
5537      SVN_ERR(read_representation(&contents, fs, noderev->data_rep, text_pool));
5538      SVN_ERR(svn_stream_read(contents, text->data, &text->len));
5539      SVN_ERR(svn_stream_close(contents));
5540
5541      /* de-serialize hash */
5542      contents = svn_stream_from_stringbuf(text, text_pool);
5543      SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
5544
5545      svn_pool_destroy(text_pool);
5546    }
5547
5548  return SVN_NO_ERROR;
5549}
5550
5551
5552static const char *
5553unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id,
5554                  apr_pool_t *pool)
5555{
5556  return apr_psprintf(pool, "%s %s",
5557                      (kind == svn_node_file) ? KIND_FILE : KIND_DIR,
5558                      svn_fs_fs__id_unparse(id, pool)->data);
5559}
5560
5561/* Given a hash ENTRIES of dirent structions, return a hash in
5562   *STR_ENTRIES_P, that has svn_string_t as the values in the format
5563   specified by the fs_fs directory contents file.  Perform
5564   allocations in POOL. */
5565static svn_error_t *
5566unparse_dir_entries(apr_hash_t **str_entries_p,
5567                    apr_hash_t *entries,
5568                    apr_pool_t *pool)
5569{
5570  apr_hash_index_t *hi;
5571
5572  /* For now, we use a our own hash function to ensure that we get a
5573   * (largely) stable order when serializing the data.  It also gives
5574   * us some performance improvement.
5575   *
5576   * ### TODO ###
5577   * Use some sorted or other fixed order data container.
5578   */
5579  *str_entries_p = svn_hash__make(pool);
5580
5581  for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
5582    {
5583      const void *key;
5584      apr_ssize_t klen;
5585      svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
5586      const char *new_val;
5587
5588      apr_hash_this(hi, &key, &klen, NULL);
5589      new_val = unparse_dir_entry(dirent->kind, dirent->id, pool);
5590      apr_hash_set(*str_entries_p, key, klen,
5591                   svn_string_create(new_val, pool));
5592    }
5593
5594  return SVN_NO_ERROR;
5595}
5596
5597
5598/* Given a hash STR_ENTRIES with values as svn_string_t as specified
5599   in an FSFS directory contents listing, return a hash of dirents in
5600   *ENTRIES_P.  Perform allocations in POOL. */
5601static svn_error_t *
5602parse_dir_entries(apr_hash_t **entries_p,
5603                  apr_hash_t *str_entries,
5604                  const char *unparsed_id,
5605                  apr_pool_t *pool)
5606{
5607  apr_hash_index_t *hi;
5608
5609  *entries_p = apr_hash_make(pool);
5610
5611  /* Translate the string dir entries into real entries. */
5612  for (hi = apr_hash_first(pool, str_entries); hi; hi = apr_hash_next(hi))
5613    {
5614      const char *name = svn__apr_hash_index_key(hi);
5615      svn_string_t *str_val = svn__apr_hash_index_val(hi);
5616      char *str, *last_str;
5617      svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent));
5618
5619      last_str = apr_pstrdup(pool, str_val->data);
5620      dirent->name = apr_pstrdup(pool, name);
5621
5622      str = svn_cstring_tokenize(" ", &last_str);
5623      if (str == NULL)
5624        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5625                                 _("Directory entry corrupt in '%s'"),
5626                                 unparsed_id);
5627
5628      if (strcmp(str, KIND_FILE) == 0)
5629        {
5630          dirent->kind = svn_node_file;
5631        }
5632      else if (strcmp(str, KIND_DIR) == 0)
5633        {
5634          dirent->kind = svn_node_dir;
5635        }
5636      else
5637        {
5638          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5639                                   _("Directory entry corrupt in '%s'"),
5640                                   unparsed_id);
5641        }
5642
5643      str = svn_cstring_tokenize(" ", &last_str);
5644      if (str == NULL)
5645          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5646                                   _("Directory entry corrupt in '%s'"),
5647                                   unparsed_id);
5648
5649      dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool);
5650
5651      svn_hash_sets(*entries_p, dirent->name, dirent);
5652    }
5653
5654  return SVN_NO_ERROR;
5655}
5656
5657/* Return the cache object in FS responsible to storing the directory
5658 * the NODEREV. If none exists, return NULL. */
5659static svn_cache__t *
5660locate_dir_cache(svn_fs_t *fs,
5661                 node_revision_t *noderev)
5662{
5663  fs_fs_data_t *ffd = fs->fsap_data;
5664  return svn_fs_fs__id_txn_id(noderev->id)
5665      ? ffd->txn_dir_cache
5666      : ffd->dir_cache;
5667}
5668
5669svn_error_t *
5670svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
5671                            svn_fs_t *fs,
5672                            node_revision_t *noderev,
5673                            apr_pool_t *pool)
5674{
5675  const char *unparsed_id = NULL;
5676  apr_hash_t *unparsed_entries, *parsed_entries;
5677
5678  /* find the cache we may use */
5679  svn_cache__t *cache = locate_dir_cache(fs, noderev);
5680  if (cache)
5681    {
5682      svn_boolean_t found;
5683
5684      unparsed_id = svn_fs_fs__id_unparse(noderev->id, pool)->data;
5685      SVN_ERR(svn_cache__get((void **) entries_p, &found, cache,
5686                             unparsed_id, pool));
5687      if (found)
5688        return SVN_NO_ERROR;
5689    }
5690
5691  /* Read in the directory hash. */
5692  unparsed_entries = apr_hash_make(pool);
5693  SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool));
5694  SVN_ERR(parse_dir_entries(&parsed_entries, unparsed_entries,
5695                            unparsed_id, pool));
5696
5697  /* Update the cache, if we are to use one. */
5698  if (cache)
5699    SVN_ERR(svn_cache__set(cache, unparsed_id, parsed_entries, pool));
5700
5701  *entries_p = parsed_entries;
5702  return SVN_NO_ERROR;
5703}
5704
5705svn_error_t *
5706svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
5707                                  svn_fs_t *fs,
5708                                  node_revision_t *noderev,
5709                                  const char *name,
5710                                  apr_pool_t *result_pool,
5711                                  apr_pool_t *scratch_pool)
5712{
5713  svn_boolean_t found = FALSE;
5714
5715  /* find the cache we may use */
5716  svn_cache__t *cache = locate_dir_cache(fs, noderev);
5717  if (cache)
5718    {
5719      const char *unparsed_id =
5720        svn_fs_fs__id_unparse(noderev->id, scratch_pool)->data;
5721
5722      /* Cache lookup. */
5723      SVN_ERR(svn_cache__get_partial((void **)dirent,
5724                                     &found,
5725                                     cache,
5726                                     unparsed_id,
5727                                     svn_fs_fs__extract_dir_entry,
5728                                     (void*)name,
5729                                     result_pool));
5730    }
5731
5732  /* fetch data from disk if we did not find it in the cache */
5733  if (! found)
5734    {
5735      apr_hash_t *entries;
5736      svn_fs_dirent_t *entry;
5737      svn_fs_dirent_t *entry_copy = NULL;
5738
5739      /* read the dir from the file system. It will probably be put it
5740         into the cache for faster lookup in future calls. */
5741      SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev,
5742                                          scratch_pool));
5743
5744      /* find desired entry and return a copy in POOL, if found */
5745      entry = svn_hash_gets(entries, name);
5746      if (entry != NULL)
5747        {
5748          entry_copy = apr_palloc(result_pool, sizeof(*entry_copy));
5749          entry_copy->name = apr_pstrdup(result_pool, entry->name);
5750          entry_copy->id = svn_fs_fs__id_copy(entry->id, result_pool);
5751          entry_copy->kind = entry->kind;
5752        }
5753
5754      *dirent = entry_copy;
5755    }
5756
5757  return SVN_NO_ERROR;
5758}
5759
5760svn_error_t *
5761svn_fs_fs__get_proplist(apr_hash_t **proplist_p,
5762                        svn_fs_t *fs,
5763                        node_revision_t *noderev,
5764                        apr_pool_t *pool)
5765{
5766  apr_hash_t *proplist;
5767  svn_stream_t *stream;
5768
5769  if (noderev->prop_rep && noderev->prop_rep->txn_id)
5770    {
5771      const char *filename = path_txn_node_props(fs, noderev->id, pool);
5772      proplist = apr_hash_make(pool);
5773
5774      SVN_ERR(svn_stream_open_readonly(&stream, filename, pool, pool));
5775      SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
5776      SVN_ERR(svn_stream_close(stream));
5777    }
5778  else if (noderev->prop_rep)
5779    {
5780      fs_fs_data_t *ffd = fs->fsap_data;
5781      representation_t *rep = noderev->prop_rep;
5782      pair_cache_key_t key = { 0 };
5783
5784      key.revision = rep->revision;
5785      key.second = rep->offset;
5786      if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
5787        {
5788          svn_boolean_t is_cached;
5789          SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
5790                                 ffd->properties_cache, &key, pool));
5791          if (is_cached)
5792            return SVN_NO_ERROR;
5793        }
5794
5795      proplist = apr_hash_make(pool);
5796      SVN_ERR(read_representation(&stream, fs, noderev->prop_rep, pool));
5797      SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
5798      SVN_ERR(svn_stream_close(stream));
5799
5800      if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
5801        SVN_ERR(svn_cache__set(ffd->properties_cache, &key, proplist, pool));
5802    }
5803  else
5804    {
5805      /* return an empty prop list if the node doesn't have any props */
5806      proplist = apr_hash_make(pool);
5807    }
5808
5809  *proplist_p = proplist;
5810
5811  return SVN_NO_ERROR;
5812}
5813
5814svn_error_t *
5815svn_fs_fs__file_length(svn_filesize_t *length,
5816                       node_revision_t *noderev,
5817                       apr_pool_t *pool)
5818{
5819  if (noderev->data_rep)
5820    *length = noderev->data_rep->expanded_size;
5821  else
5822    *length = 0;
5823
5824  return SVN_NO_ERROR;
5825}
5826
5827svn_boolean_t
5828svn_fs_fs__noderev_same_rep_key(representation_t *a,
5829                                representation_t *b)
5830{
5831  if (a == b)
5832    return TRUE;
5833
5834  if (a == NULL || b == NULL)
5835    return FALSE;
5836
5837  if (a->offset != b->offset)
5838    return FALSE;
5839
5840  if (a->revision != b->revision)
5841    return FALSE;
5842
5843  if (a->uniquifier == b->uniquifier)
5844    return TRUE;
5845
5846  if (a->uniquifier == NULL || b->uniquifier == NULL)
5847    return FALSE;
5848
5849  return strcmp(a->uniquifier, b->uniquifier) == 0;
5850}
5851
5852svn_error_t *
5853svn_fs_fs__file_checksum(svn_checksum_t **checksum,
5854                         node_revision_t *noderev,
5855                         svn_checksum_kind_t kind,
5856                         apr_pool_t *pool)
5857{
5858  if (noderev->data_rep)
5859    {
5860      switch(kind)
5861        {
5862          case svn_checksum_md5:
5863            *checksum = svn_checksum_dup(noderev->data_rep->md5_checksum,
5864                                         pool);
5865            break;
5866          case svn_checksum_sha1:
5867            *checksum = svn_checksum_dup(noderev->data_rep->sha1_checksum,
5868                                         pool);
5869            break;
5870          default:
5871            *checksum = NULL;
5872        }
5873    }
5874  else
5875    *checksum = NULL;
5876
5877  return SVN_NO_ERROR;
5878}
5879
5880representation_t *
5881svn_fs_fs__rep_copy(representation_t *rep,
5882                    apr_pool_t *pool)
5883{
5884  representation_t *rep_new;
5885
5886  if (rep == NULL)
5887    return NULL;
5888
5889  rep_new = apr_pcalloc(pool, sizeof(*rep_new));
5890
5891  memcpy(rep_new, rep, sizeof(*rep_new));
5892  rep_new->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
5893  rep_new->sha1_checksum = svn_checksum_dup(rep->sha1_checksum, pool);
5894  rep_new->uniquifier = apr_pstrdup(pool, rep->uniquifier);
5895
5896  return rep_new;
5897}
5898
5899/* Merge the internal-use-only CHANGE into a hash of public-FS
5900   svn_fs_path_change2_t CHANGES, collapsing multiple changes into a
5901   single summarical (is that real word?) change per path.  Also keep
5902   the COPYFROM_CACHE up to date with new adds and replaces.  */
5903static svn_error_t *
5904fold_change(apr_hash_t *changes,
5905            const change_t *change,
5906            apr_hash_t *copyfrom_cache)
5907{
5908  apr_pool_t *pool = apr_hash_pool_get(changes);
5909  svn_fs_path_change2_t *old_change, *new_change;
5910  const char *path;
5911  apr_size_t path_len = strlen(change->path);
5912
5913  if ((old_change = apr_hash_get(changes, change->path, path_len)))
5914    {
5915      /* This path already exists in the hash, so we have to merge
5916         this change into the already existing one. */
5917
5918      /* Sanity check:  only allow NULL node revision ID in the
5919         `reset' case. */
5920      if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset))
5921        return svn_error_create
5922          (SVN_ERR_FS_CORRUPT, NULL,
5923           _("Missing required node revision ID"));
5924
5925      /* Sanity check: we should be talking about the same node
5926         revision ID as our last change except where the last change
5927         was a deletion. */
5928      if (change->noderev_id
5929          && (! svn_fs_fs__id_eq(old_change->node_rev_id, change->noderev_id))
5930          && (old_change->change_kind != svn_fs_path_change_delete))
5931        return svn_error_create
5932          (SVN_ERR_FS_CORRUPT, NULL,
5933           _("Invalid change ordering: new node revision ID "
5934             "without delete"));
5935
5936      /* Sanity check: an add, replacement, or reset must be the first
5937         thing to follow a deletion. */
5938      if ((old_change->change_kind == svn_fs_path_change_delete)
5939          && (! ((change->kind == svn_fs_path_change_replace)
5940                 || (change->kind == svn_fs_path_change_reset)
5941                 || (change->kind == svn_fs_path_change_add))))
5942        return svn_error_create
5943          (SVN_ERR_FS_CORRUPT, NULL,
5944           _("Invalid change ordering: non-add change on deleted path"));
5945
5946      /* Sanity check: an add can't follow anything except
5947         a delete or reset.  */
5948      if ((change->kind == svn_fs_path_change_add)
5949          && (old_change->change_kind != svn_fs_path_change_delete)
5950          && (old_change->change_kind != svn_fs_path_change_reset))
5951        return svn_error_create
5952          (SVN_ERR_FS_CORRUPT, NULL,
5953           _("Invalid change ordering: add change on preexisting path"));
5954
5955      /* Now, merge that change in. */
5956      switch (change->kind)
5957        {
5958        case svn_fs_path_change_reset:
5959          /* A reset here will simply remove the path change from the
5960             hash. */
5961          old_change = NULL;
5962          break;
5963
5964        case svn_fs_path_change_delete:
5965          if (old_change->change_kind == svn_fs_path_change_add)
5966            {
5967              /* If the path was introduced in this transaction via an
5968                 add, and we are deleting it, just remove the path
5969                 altogether. */
5970              old_change = NULL;
5971            }
5972          else
5973            {
5974              /* A deletion overrules all previous changes. */
5975              old_change->change_kind = svn_fs_path_change_delete;
5976              old_change->text_mod = change->text_mod;
5977              old_change->prop_mod = change->prop_mod;
5978              old_change->copyfrom_rev = SVN_INVALID_REVNUM;
5979              old_change->copyfrom_path = NULL;
5980            }
5981          break;
5982
5983        case svn_fs_path_change_add:
5984        case svn_fs_path_change_replace:
5985          /* An add at this point must be following a previous delete,
5986             so treat it just like a replace. */
5987          old_change->change_kind = svn_fs_path_change_replace;
5988          old_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id,
5989                                                       pool);
5990          old_change->text_mod = change->text_mod;
5991          old_change->prop_mod = change->prop_mod;
5992          if (change->copyfrom_rev == SVN_INVALID_REVNUM)
5993            {
5994              old_change->copyfrom_rev = SVN_INVALID_REVNUM;
5995              old_change->copyfrom_path = NULL;
5996            }
5997          else
5998            {
5999              old_change->copyfrom_rev = change->copyfrom_rev;
6000              old_change->copyfrom_path = apr_pstrdup(pool,
6001                                                      change->copyfrom_path);
6002            }
6003          break;
6004
6005        case svn_fs_path_change_modify:
6006        default:
6007          if (change->text_mod)
6008            old_change->text_mod = TRUE;
6009          if (change->prop_mod)
6010            old_change->prop_mod = TRUE;
6011          break;
6012        }
6013
6014      /* Point our new_change to our (possibly modified) old_change. */
6015      new_change = old_change;
6016    }
6017  else
6018    {
6019      /* This change is new to the hash, so make a new public change
6020         structure from the internal one (in the hash's pool), and dup
6021         the path into the hash's pool, too. */
6022      new_change = apr_pcalloc(pool, sizeof(*new_change));
6023      new_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id, pool);
6024      new_change->change_kind = change->kind;
6025      new_change->text_mod = change->text_mod;
6026      new_change->prop_mod = change->prop_mod;
6027      /* In FSFS, copyfrom_known is *always* true, since we've always
6028       * stored copyfroms in changed paths lists. */
6029      new_change->copyfrom_known = TRUE;
6030      if (change->copyfrom_rev != SVN_INVALID_REVNUM)
6031        {
6032          new_change->copyfrom_rev = change->copyfrom_rev;
6033          new_change->copyfrom_path = apr_pstrdup(pool, change->copyfrom_path);
6034        }
6035      else
6036        {
6037          new_change->copyfrom_rev = SVN_INVALID_REVNUM;
6038          new_change->copyfrom_path = NULL;
6039        }
6040    }
6041
6042  if (new_change)
6043    new_change->node_kind = change->node_kind;
6044
6045  /* Add (or update) this path.
6046
6047     Note: this key might already be present, and it would be nice to
6048     re-use its value, but there is no way to fetch it. The API makes no
6049     guarantees that this (new) key will not be retained. Thus, we (again)
6050     copy the key into the target pool to ensure a proper lifetime.  */
6051  path = apr_pstrmemdup(pool, change->path, path_len);
6052  apr_hash_set(changes, path, path_len, new_change);
6053
6054  /* Update the copyfrom cache, if any. */
6055  if (copyfrom_cache)
6056    {
6057      apr_pool_t *copyfrom_pool = apr_hash_pool_get(copyfrom_cache);
6058      const char *copyfrom_string = NULL, *copyfrom_key = path;
6059      if (new_change)
6060        {
6061          if (SVN_IS_VALID_REVNUM(new_change->copyfrom_rev))
6062            copyfrom_string = apr_psprintf(copyfrom_pool, "%ld %s",
6063                                           new_change->copyfrom_rev,
6064                                           new_change->copyfrom_path);
6065          else
6066            copyfrom_string = "";
6067        }
6068      /* We need to allocate a copy of the key in the copyfrom_pool if
6069       * we're not doing a deletion and if it isn't already there. */
6070      if (   copyfrom_string
6071          && (   ! apr_hash_count(copyfrom_cache)
6072              || ! apr_hash_get(copyfrom_cache, copyfrom_key, path_len)))
6073        copyfrom_key = apr_pstrmemdup(copyfrom_pool, copyfrom_key, path_len);
6074
6075      apr_hash_set(copyfrom_cache, copyfrom_key, path_len,
6076                   copyfrom_string);
6077    }
6078
6079  return SVN_NO_ERROR;
6080}
6081
6082/* The 256 is an arbitrary size large enough to hold the node id and the
6083 * various flags. */
6084#define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
6085
6086/* Read the next entry in the changes record from file FILE and store
6087   the resulting change in *CHANGE_P.  If there is no next record,
6088   store NULL there.  Perform all allocations from POOL. */
6089static svn_error_t *
6090read_change(change_t **change_p,
6091            apr_file_t *file,
6092            apr_pool_t *pool)
6093{
6094  char buf[MAX_CHANGE_LINE_LEN];
6095  apr_size_t len = sizeof(buf);
6096  change_t *change;
6097  char *str, *last_str = buf, *kind_str;
6098  svn_error_t *err;
6099
6100  /* Default return value. */
6101  *change_p = NULL;
6102
6103  err = svn_io_read_length_line(file, buf, &len, pool);
6104
6105  /* Check for a blank line. */
6106  if (err || (len == 0))
6107    {
6108      if (err && APR_STATUS_IS_EOF(err->apr_err))
6109        {
6110          svn_error_clear(err);
6111          return SVN_NO_ERROR;
6112        }
6113      if ((len == 0) && (! err))
6114        return SVN_NO_ERROR;
6115      return svn_error_trace(err);
6116    }
6117
6118  change = apr_pcalloc(pool, sizeof(*change));
6119
6120  /* Get the node-id of the change. */
6121  str = svn_cstring_tokenize(" ", &last_str);
6122  if (str == NULL)
6123    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6124                            _("Invalid changes line in rev-file"));
6125
6126  change->noderev_id = svn_fs_fs__id_parse(str, strlen(str), pool);
6127  if (change->noderev_id == NULL)
6128    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6129                            _("Invalid changes line in rev-file"));
6130
6131  /* Get the change type. */
6132  str = svn_cstring_tokenize(" ", &last_str);
6133  if (str == NULL)
6134    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6135                            _("Invalid changes line in rev-file"));
6136
6137  /* Don't bother to check the format number before looking for
6138   * node-kinds: just read them if you find them. */
6139  change->node_kind = svn_node_unknown;
6140  kind_str = strchr(str, '-');
6141  if (kind_str)
6142    {
6143      /* Cap off the end of "str" (the action). */
6144      *kind_str = '\0';
6145      kind_str++;
6146      if (strcmp(kind_str, KIND_FILE) == 0)
6147        change->node_kind = svn_node_file;
6148      else if (strcmp(kind_str, KIND_DIR) == 0)
6149        change->node_kind = svn_node_dir;
6150      else
6151        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6152                                _("Invalid changes line in rev-file"));
6153    }
6154
6155  if (strcmp(str, ACTION_MODIFY) == 0)
6156    {
6157      change->kind = svn_fs_path_change_modify;
6158    }
6159  else if (strcmp(str, ACTION_ADD) == 0)
6160    {
6161      change->kind = svn_fs_path_change_add;
6162    }
6163  else if (strcmp(str, ACTION_DELETE) == 0)
6164    {
6165      change->kind = svn_fs_path_change_delete;
6166    }
6167  else if (strcmp(str, ACTION_REPLACE) == 0)
6168    {
6169      change->kind = svn_fs_path_change_replace;
6170    }
6171  else if (strcmp(str, ACTION_RESET) == 0)
6172    {
6173      change->kind = svn_fs_path_change_reset;
6174    }
6175  else
6176    {
6177      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6178                              _("Invalid change kind in rev file"));
6179    }
6180
6181  /* Get the text-mod flag. */
6182  str = svn_cstring_tokenize(" ", &last_str);
6183  if (str == NULL)
6184    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6185                            _("Invalid changes line in rev-file"));
6186
6187  if (strcmp(str, FLAG_TRUE) == 0)
6188    {
6189      change->text_mod = TRUE;
6190    }
6191  else if (strcmp(str, FLAG_FALSE) == 0)
6192    {
6193      change->text_mod = FALSE;
6194    }
6195  else
6196    {
6197      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6198                              _("Invalid text-mod flag in rev-file"));
6199    }
6200
6201  /* Get the prop-mod flag. */
6202  str = svn_cstring_tokenize(" ", &last_str);
6203  if (str == NULL)
6204    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6205                            _("Invalid changes line in rev-file"));
6206
6207  if (strcmp(str, FLAG_TRUE) == 0)
6208    {
6209      change->prop_mod = TRUE;
6210    }
6211  else if (strcmp(str, FLAG_FALSE) == 0)
6212    {
6213      change->prop_mod = FALSE;
6214    }
6215  else
6216    {
6217      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6218                              _("Invalid prop-mod flag in rev-file"));
6219    }
6220
6221  /* Get the changed path. */
6222  change->path = apr_pstrdup(pool, last_str);
6223
6224
6225  /* Read the next line, the copyfrom line. */
6226  len = sizeof(buf);
6227  SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
6228
6229  if (len == 0)
6230    {
6231      change->copyfrom_rev = SVN_INVALID_REVNUM;
6232      change->copyfrom_path = NULL;
6233    }
6234  else
6235    {
6236      last_str = buf;
6237      str = svn_cstring_tokenize(" ", &last_str);
6238      if (! str)
6239        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6240                                _("Invalid changes line in rev-file"));
6241      change->copyfrom_rev = SVN_STR_TO_REV(str);
6242
6243      if (! last_str)
6244        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6245                                _("Invalid changes line in rev-file"));
6246
6247      change->copyfrom_path = apr_pstrdup(pool, last_str);
6248    }
6249
6250  *change_p = change;
6251
6252  return SVN_NO_ERROR;
6253}
6254
6255/* Examine all the changed path entries in CHANGES and store them in
6256   *CHANGED_PATHS.  Folding is done to remove redundant or unnecessary
6257   *data.  Store a hash of paths to copyfrom "REV PATH" strings in
6258   COPYFROM_HASH if it is non-NULL.  If PREFOLDED is true, assume that
6259   the changed-path entries have already been folded (by
6260   write_final_changed_path_info) and may be out of order, so we shouldn't
6261   remove children of replaced or deleted directories.  Do all
6262   allocations in POOL. */
6263static svn_error_t *
6264process_changes(apr_hash_t *changed_paths,
6265                apr_hash_t *copyfrom_cache,
6266                apr_array_header_t *changes,
6267                svn_boolean_t prefolded,
6268                apr_pool_t *pool)
6269{
6270  apr_pool_t *iterpool = svn_pool_create(pool);
6271  int i;
6272
6273  /* Read in the changes one by one, folding them into our local hash
6274     as necessary. */
6275
6276  for (i = 0; i < changes->nelts; ++i)
6277    {
6278      change_t *change = APR_ARRAY_IDX(changes, i, change_t *);
6279
6280      SVN_ERR(fold_change(changed_paths, change, copyfrom_cache));
6281
6282      /* Now, if our change was a deletion or replacement, we have to
6283         blow away any changes thus far on paths that are (or, were)
6284         children of this path.
6285         ### i won't bother with another iteration pool here -- at
6286         most we talking about a few extra dups of paths into what
6287         is already a temporary subpool.
6288      */
6289
6290      if (((change->kind == svn_fs_path_change_delete)
6291           || (change->kind == svn_fs_path_change_replace))
6292          && ! prefolded)
6293        {
6294          apr_hash_index_t *hi;
6295
6296          /* a potential child path must contain at least 2 more chars
6297             (the path separator plus at least one char for the name).
6298             Also, we should not assume that all paths have been normalized
6299             i.e. some might have trailing path separators.
6300          */
6301          apr_ssize_t change_path_len = strlen(change->path);
6302          apr_ssize_t min_child_len = change_path_len == 0
6303                                    ? 1
6304                                    : change->path[change_path_len-1] == '/'
6305                                        ? change_path_len + 1
6306                                        : change_path_len + 2;
6307
6308          /* CAUTION: This is the inner loop of an O(n^2) algorithm.
6309             The number of changes to process may be >> 1000.
6310             Therefore, keep the inner loop as tight as possible.
6311          */
6312          for (hi = apr_hash_first(iterpool, changed_paths);
6313               hi;
6314               hi = apr_hash_next(hi))
6315            {
6316              /* KEY is the path. */
6317              const void *path;
6318              apr_ssize_t klen;
6319              apr_hash_this(hi, &path, &klen, NULL);
6320
6321              /* If we come across a child of our path, remove it.
6322                 Call svn_dirent_is_child only if there is a chance that
6323                 this is actually a sub-path.
6324               */
6325              if (   klen >= min_child_len
6326                  && svn_dirent_is_child(change->path, path, iterpool))
6327                apr_hash_set(changed_paths, path, klen, NULL);
6328            }
6329        }
6330
6331      /* Clear the per-iteration subpool. */
6332      svn_pool_clear(iterpool);
6333    }
6334
6335  /* Destroy the per-iteration subpool. */
6336  svn_pool_destroy(iterpool);
6337
6338  return SVN_NO_ERROR;
6339}
6340
6341/* Fetch all the changes from FILE and store them in *CHANGES.  Do all
6342   allocations in POOL. */
6343static svn_error_t *
6344read_all_changes(apr_array_header_t **changes,
6345                 apr_file_t *file,
6346                 apr_pool_t *pool)
6347{
6348  change_t *change;
6349
6350  /* pre-allocate enough room for most change lists
6351     (will be auto-expanded as necessary) */
6352  *changes = apr_array_make(pool, 30, sizeof(change_t *));
6353
6354  SVN_ERR(read_change(&change, file, pool));
6355  while (change)
6356    {
6357      APR_ARRAY_PUSH(*changes, change_t*) = change;
6358      SVN_ERR(read_change(&change, file, pool));
6359    }
6360
6361  return SVN_NO_ERROR;
6362}
6363
6364svn_error_t *
6365svn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p,
6366                             svn_fs_t *fs,
6367                             const char *txn_id,
6368                             apr_pool_t *pool)
6369{
6370  apr_file_t *file;
6371  apr_hash_t *changed_paths = apr_hash_make(pool);
6372  apr_array_header_t *changes;
6373  apr_pool_t *scratch_pool = svn_pool_create(pool);
6374
6375  SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
6376                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
6377
6378  SVN_ERR(read_all_changes(&changes, file, scratch_pool));
6379  SVN_ERR(process_changes(changed_paths, NULL, changes, FALSE, pool));
6380  svn_pool_destroy(scratch_pool);
6381
6382  SVN_ERR(svn_io_file_close(file, pool));
6383
6384  *changed_paths_p = changed_paths;
6385
6386  return SVN_NO_ERROR;
6387}
6388
6389/* Fetch the list of change in revision REV in FS and return it in *CHANGES.
6390 * Allocate the result in POOL.
6391 */
6392static svn_error_t *
6393get_changes(apr_array_header_t **changes,
6394            svn_fs_t *fs,
6395            svn_revnum_t rev,
6396            apr_pool_t *pool)
6397{
6398  apr_off_t changes_offset;
6399  apr_file_t *revision_file;
6400  svn_boolean_t found;
6401  fs_fs_data_t *ffd = fs->fsap_data;
6402
6403  /* try cache lookup first */
6404
6405  if (ffd->changes_cache)
6406    {
6407      SVN_ERR(svn_cache__get((void **) changes, &found, ffd->changes_cache,
6408                             &rev, pool));
6409      if (found)
6410        return SVN_NO_ERROR;
6411    }
6412
6413  /* read changes from revision file */
6414
6415  SVN_ERR(ensure_revision_exists(fs, rev, pool));
6416
6417  SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool));
6418
6419  SVN_ERR(get_root_changes_offset(NULL, &changes_offset, revision_file, fs,
6420                                  rev, pool));
6421
6422  SVN_ERR(svn_io_file_seek(revision_file, APR_SET, &changes_offset, pool));
6423  SVN_ERR(read_all_changes(changes, revision_file, pool));
6424
6425  SVN_ERR(svn_io_file_close(revision_file, pool));
6426
6427  /* cache for future reference */
6428
6429  if (ffd->changes_cache)
6430    SVN_ERR(svn_cache__set(ffd->changes_cache, &rev, *changes, pool));
6431
6432  return SVN_NO_ERROR;
6433}
6434
6435
6436svn_error_t *
6437svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
6438                         svn_fs_t *fs,
6439                         svn_revnum_t rev,
6440                         apr_hash_t *copyfrom_cache,
6441                         apr_pool_t *pool)
6442{
6443  apr_hash_t *changed_paths;
6444  apr_array_header_t *changes;
6445  apr_pool_t *scratch_pool = svn_pool_create(pool);
6446
6447  SVN_ERR(get_changes(&changes, fs, rev, scratch_pool));
6448
6449  changed_paths = svn_hash__make(pool);
6450
6451  SVN_ERR(process_changes(changed_paths, copyfrom_cache, changes,
6452                          TRUE, pool));
6453  svn_pool_destroy(scratch_pool);
6454
6455  *changed_paths_p = changed_paths;
6456
6457  return SVN_NO_ERROR;
6458}
6459
6460/* Copy a revision node-rev SRC into the current transaction TXN_ID in
6461   the filesystem FS.  This is only used to create the root of a transaction.
6462   Allocations are from POOL.  */
6463static svn_error_t *
6464create_new_txn_noderev_from_rev(svn_fs_t *fs,
6465                                const char *txn_id,
6466                                svn_fs_id_t *src,
6467                                apr_pool_t *pool)
6468{
6469  node_revision_t *noderev;
6470  const char *node_id, *copy_id;
6471
6472  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool));
6473
6474  if (svn_fs_fs__id_txn_id(noderev->id))
6475    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6476                            _("Copying from transactions not allowed"));
6477
6478  noderev->predecessor_id = noderev->id;
6479  noderev->predecessor_count++;
6480  noderev->copyfrom_path = NULL;
6481  noderev->copyfrom_rev = SVN_INVALID_REVNUM;
6482
6483  /* For the transaction root, the copyroot never changes. */
6484
6485  node_id = svn_fs_fs__id_node_id(noderev->id);
6486  copy_id = svn_fs_fs__id_copy_id(noderev->id);
6487  noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
6488
6489  return svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool);
6490}
6491
6492/* A structure used by get_and_increment_txn_key_body(). */
6493struct get_and_increment_txn_key_baton {
6494  svn_fs_t *fs;
6495  char *txn_id;
6496  apr_pool_t *pool;
6497};
6498
6499/* Callback used in the implementation of create_txn_dir().  This gets
6500   the current base 36 value in PATH_TXN_CURRENT and increments it.
6501   It returns the original value by the baton. */
6502static svn_error_t *
6503get_and_increment_txn_key_body(void *baton, apr_pool_t *pool)
6504{
6505  struct get_and_increment_txn_key_baton *cb = baton;
6506  const char *txn_current_filename = path_txn_current(cb->fs, pool);
6507  const char *tmp_filename;
6508  char next_txn_id[MAX_KEY_SIZE+3];
6509  apr_size_t len;
6510
6511  svn_stringbuf_t *buf;
6512  SVN_ERR(read_content(&buf, txn_current_filename, cb->pool));
6513
6514  /* remove trailing newlines */
6515  svn_stringbuf_strip_whitespace(buf);
6516  cb->txn_id = buf->data;
6517  len = buf->len;
6518
6519  /* Increment the key and add a trailing \n to the string so the
6520     txn-current file has a newline in it. */
6521  svn_fs_fs__next_key(cb->txn_id, &len, next_txn_id);
6522  next_txn_id[len] = '\n';
6523  ++len;
6524  next_txn_id[len] = '\0';
6525
6526  SVN_ERR(svn_io_write_unique(&tmp_filename,
6527                              svn_dirent_dirname(txn_current_filename, pool),
6528                              next_txn_id, len, svn_io_file_del_none, pool));
6529  SVN_ERR(move_into_place(tmp_filename, txn_current_filename,
6530                          txn_current_filename, pool));
6531
6532  return SVN_NO_ERROR;
6533}
6534
6535/* Create a unique directory for a transaction in FS based on revision
6536   REV.  Return the ID for this transaction in *ID_P.  Use a sequence
6537   value in the transaction ID to prevent reuse of transaction IDs. */
6538static svn_error_t *
6539create_txn_dir(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
6540               apr_pool_t *pool)
6541{
6542  struct get_and_increment_txn_key_baton cb;
6543  const char *txn_dir;
6544
6545  /* Get the current transaction sequence value, which is a base-36
6546     number, from the txn-current file, and write an
6547     incremented value back out to the file.  Place the revision
6548     number the transaction is based off into the transaction id. */
6549  cb.pool = pool;
6550  cb.fs = fs;
6551  SVN_ERR(with_txn_current_lock(fs,
6552                                get_and_increment_txn_key_body,
6553                                &cb,
6554                                pool));
6555  *id_p = apr_psprintf(pool, "%ld-%s", rev, cb.txn_id);
6556
6557  txn_dir = svn_dirent_join_many(pool,
6558                                 fs->path,
6559                                 PATH_TXNS_DIR,
6560                                 apr_pstrcat(pool, *id_p, PATH_EXT_TXN,
6561                                             (char *)NULL),
6562                                 NULL);
6563
6564  return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool);
6565}
6566
6567/* Create a unique directory for a transaction in FS based on revision
6568   REV.  Return the ID for this transaction in *ID_P.  This
6569   implementation is used in svn 1.4 and earlier repositories and is
6570   kept in 1.5 and greater to support the --pre-1.4-compatible and
6571   --pre-1.5-compatible repository creation options.  Reused
6572   transaction IDs are possible with this implementation. */
6573static svn_error_t *
6574create_txn_dir_pre_1_5(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
6575                       apr_pool_t *pool)
6576{
6577  unsigned int i;
6578  apr_pool_t *subpool;
6579  const char *unique_path, *prefix;
6580
6581  /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
6582  prefix = svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR,
6583                                apr_psprintf(pool, "%ld", rev), NULL);
6584
6585  subpool = svn_pool_create(pool);
6586  for (i = 1; i <= 99999; i++)
6587    {
6588      svn_error_t *err;
6589
6590      svn_pool_clear(subpool);
6591      unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i);
6592      err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool);
6593      if (! err)
6594        {
6595          /* We succeeded.  Return the basename minus the ".txn" extension. */
6596          const char *name = svn_dirent_basename(unique_path, subpool);
6597          *id_p = apr_pstrndup(pool, name,
6598                               strlen(name) - strlen(PATH_EXT_TXN));
6599          svn_pool_destroy(subpool);
6600          return SVN_NO_ERROR;
6601        }
6602      if (! APR_STATUS_IS_EEXIST(err->apr_err))
6603        return svn_error_trace(err);
6604      svn_error_clear(err);
6605    }
6606
6607  return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
6608                           NULL,
6609                           _("Unable to create transaction directory "
6610                             "in '%s' for revision %ld"),
6611                           svn_dirent_local_style(fs->path, pool),
6612                           rev);
6613}
6614
6615svn_error_t *
6616svn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
6617                      svn_fs_t *fs,
6618                      svn_revnum_t rev,
6619                      apr_pool_t *pool)
6620{
6621  fs_fs_data_t *ffd = fs->fsap_data;
6622  svn_fs_txn_t *txn;
6623  svn_fs_id_t *root_id;
6624
6625  txn = apr_pcalloc(pool, sizeof(*txn));
6626
6627  /* Get the txn_id. */
6628  if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
6629    SVN_ERR(create_txn_dir(&txn->id, fs, rev, pool));
6630  else
6631    SVN_ERR(create_txn_dir_pre_1_5(&txn->id, fs, rev, pool));
6632
6633  txn->fs = fs;
6634  txn->base_rev = rev;
6635
6636  txn->vtable = &txn_vtable;
6637  *txn_p = txn;
6638
6639  /* Create a new root node for this transaction. */
6640  SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool));
6641  SVN_ERR(create_new_txn_noderev_from_rev(fs, txn->id, root_id, pool));
6642
6643  /* Create an empty rev file. */
6644  SVN_ERR(svn_io_file_create(path_txn_proto_rev(fs, txn->id, pool), "",
6645                             pool));
6646
6647  /* Create an empty rev-lock file. */
6648  SVN_ERR(svn_io_file_create(path_txn_proto_rev_lock(fs, txn->id, pool), "",
6649                             pool));
6650
6651  /* Create an empty changes file. */
6652  SVN_ERR(svn_io_file_create(path_txn_changes(fs, txn->id, pool), "",
6653                             pool));
6654
6655  /* Create the next-ids file. */
6656  return svn_io_file_create(path_txn_next_ids(fs, txn->id, pool), "0 0\n",
6657                            pool);
6658}
6659
6660/* Store the property list for transaction TXN_ID in PROPLIST.
6661   Perform temporary allocations in POOL. */
6662static svn_error_t *
6663get_txn_proplist(apr_hash_t *proplist,
6664                 svn_fs_t *fs,
6665                 const char *txn_id,
6666                 apr_pool_t *pool)
6667{
6668  svn_stream_t *stream;
6669
6670  /* Check for issue #3696. (When we find and fix the cause, we can change
6671   * this to an assertion.) */
6672  if (txn_id == NULL)
6673    return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
6674                            _("Internal error: a null transaction id was "
6675                              "passed to get_txn_proplist()"));
6676
6677  /* Open the transaction properties file. */
6678  SVN_ERR(svn_stream_open_readonly(&stream, path_txn_props(fs, txn_id, pool),
6679                                   pool, pool));
6680
6681  /* Read in the property list. */
6682  SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
6683
6684  return svn_stream_close(stream);
6685}
6686
6687svn_error_t *
6688svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
6689                           const char *name,
6690                           const svn_string_t *value,
6691                           apr_pool_t *pool)
6692{
6693  apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
6694  svn_prop_t prop;
6695
6696  prop.name = name;
6697  prop.value = value;
6698  APR_ARRAY_PUSH(props, svn_prop_t) = prop;
6699
6700  return svn_fs_fs__change_txn_props(txn, props, pool);
6701}
6702
6703svn_error_t *
6704svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
6705                            const apr_array_header_t *props,
6706                            apr_pool_t *pool)
6707{
6708  const char *txn_prop_filename;
6709  svn_stringbuf_t *buf;
6710  svn_stream_t *stream;
6711  apr_hash_t *txn_prop = apr_hash_make(pool);
6712  int i;
6713  svn_error_t *err;
6714
6715  err = get_txn_proplist(txn_prop, txn->fs, txn->id, pool);
6716  /* Here - and here only - we need to deal with the possibility that the
6717     transaction property file doesn't yet exist.  The rest of the
6718     implementation assumes that the file exists, but we're called to set the
6719     initial transaction properties as the transaction is being created. */
6720  if (err && (APR_STATUS_IS_ENOENT(err->apr_err)))
6721    svn_error_clear(err);
6722  else if (err)
6723    return svn_error_trace(err);
6724
6725  for (i = 0; i < props->nelts; i++)
6726    {
6727      svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
6728
6729      svn_hash_sets(txn_prop, prop->name, prop->value);
6730    }
6731
6732  /* Create a new version of the file and write out the new props. */
6733  /* Open the transaction properties file. */
6734  buf = svn_stringbuf_create_ensure(1024, pool);
6735  stream = svn_stream_from_stringbuf(buf, pool);
6736  SVN_ERR(svn_hash_write2(txn_prop, stream, SVN_HASH_TERMINATOR, pool));
6737  SVN_ERR(svn_stream_close(stream));
6738  SVN_ERR(svn_io_write_unique(&txn_prop_filename,
6739                              path_txn_dir(txn->fs, txn->id, pool),
6740                              buf->data,
6741                              buf->len,
6742                              svn_io_file_del_none,
6743                              pool));
6744  return svn_io_file_rename(txn_prop_filename,
6745                            path_txn_props(txn->fs, txn->id, pool),
6746                            pool);
6747}
6748
6749svn_error_t *
6750svn_fs_fs__get_txn(transaction_t **txn_p,
6751                   svn_fs_t *fs,
6752                   const char *txn_id,
6753                   apr_pool_t *pool)
6754{
6755  transaction_t *txn;
6756  node_revision_t *noderev;
6757  svn_fs_id_t *root_id;
6758
6759  txn = apr_pcalloc(pool, sizeof(*txn));
6760  txn->proplist = apr_hash_make(pool);
6761
6762  SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool));
6763  root_id = svn_fs_fs__id_txn_create("0", "0", txn_id, pool);
6764
6765  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool));
6766
6767  txn->root_id = svn_fs_fs__id_copy(noderev->id, pool);
6768  txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
6769  txn->copies = NULL;
6770
6771  *txn_p = txn;
6772
6773  return SVN_NO_ERROR;
6774}
6775
6776/* Write out the currently available next node_id NODE_ID and copy_id
6777   COPY_ID for transaction TXN_ID in filesystem FS.  The next node-id is
6778   used both for creating new unique nodes for the given transaction, as
6779   well as uniquifying representations.  Perform temporary allocations in
6780   POOL. */
6781static svn_error_t *
6782write_next_ids(svn_fs_t *fs,
6783               const char *txn_id,
6784               const char *node_id,
6785               const char *copy_id,
6786               apr_pool_t *pool)
6787{
6788  apr_file_t *file;
6789  svn_stream_t *out_stream;
6790
6791  SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
6792                           APR_WRITE | APR_TRUNCATE,
6793                           APR_OS_DEFAULT, pool));
6794
6795  out_stream = svn_stream_from_aprfile2(file, TRUE, pool);
6796
6797  SVN_ERR(svn_stream_printf(out_stream, pool, "%s %s\n", node_id, copy_id));
6798
6799  SVN_ERR(svn_stream_close(out_stream));
6800  return svn_io_file_close(file, pool);
6801}
6802
6803/* Find out what the next unique node-id and copy-id are for
6804   transaction TXN_ID in filesystem FS.  Store the results in *NODE_ID
6805   and *COPY_ID.  The next node-id is used both for creating new unique
6806   nodes for the given transaction, as well as uniquifying representations.
6807   Perform all allocations in POOL. */
6808static svn_error_t *
6809read_next_ids(const char **node_id,
6810              const char **copy_id,
6811              svn_fs_t *fs,
6812              const char *txn_id,
6813              apr_pool_t *pool)
6814{
6815  apr_file_t *file;
6816  char buf[MAX_KEY_SIZE*2+3];
6817  apr_size_t limit;
6818  char *str, *last_str = buf;
6819
6820  SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
6821                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
6822
6823  limit = sizeof(buf);
6824  SVN_ERR(svn_io_read_length_line(file, buf, &limit, pool));
6825
6826  SVN_ERR(svn_io_file_close(file, pool));
6827
6828  /* Parse this into two separate strings. */
6829
6830  str = svn_cstring_tokenize(" ", &last_str);
6831  if (! str)
6832    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6833                            _("next-id file corrupt"));
6834
6835  *node_id = apr_pstrdup(pool, str);
6836
6837  str = svn_cstring_tokenize(" ", &last_str);
6838  if (! str)
6839    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6840                            _("next-id file corrupt"));
6841
6842  *copy_id = apr_pstrdup(pool, str);
6843
6844  return SVN_NO_ERROR;
6845}
6846
6847/* Get a new and unique to this transaction node-id for transaction
6848   TXN_ID in filesystem FS.  Store the new node-id in *NODE_ID_P.
6849   Node-ids are guaranteed to be unique to this transction, but may
6850   not necessarily be sequential.  Perform all allocations in POOL. */
6851static svn_error_t *
6852get_new_txn_node_id(const char **node_id_p,
6853                    svn_fs_t *fs,
6854                    const char *txn_id,
6855                    apr_pool_t *pool)
6856{
6857  const char *cur_node_id, *cur_copy_id;
6858  char *node_id;
6859  apr_size_t len;
6860
6861  /* First read in the current next-ids file. */
6862  SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
6863
6864  node_id = apr_pcalloc(pool, strlen(cur_node_id) + 2);
6865
6866  len = strlen(cur_node_id);
6867  svn_fs_fs__next_key(cur_node_id, &len, node_id);
6868
6869  SVN_ERR(write_next_ids(fs, txn_id, node_id, cur_copy_id, pool));
6870
6871  *node_id_p = apr_pstrcat(pool, "_", cur_node_id, (char *)NULL);
6872
6873  return SVN_NO_ERROR;
6874}
6875
6876svn_error_t *
6877svn_fs_fs__create_node(const svn_fs_id_t **id_p,
6878                       svn_fs_t *fs,
6879                       node_revision_t *noderev,
6880                       const char *copy_id,
6881                       const char *txn_id,
6882                       apr_pool_t *pool)
6883{
6884  const char *node_id;
6885  const svn_fs_id_t *id;
6886
6887  /* Get a new node-id for this node. */
6888  SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool));
6889
6890  id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
6891
6892  noderev->id = id;
6893
6894  SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
6895
6896  *id_p = id;
6897
6898  return SVN_NO_ERROR;
6899}
6900
6901svn_error_t *
6902svn_fs_fs__purge_txn(svn_fs_t *fs,
6903                     const char *txn_id,
6904                     apr_pool_t *pool)
6905{
6906  fs_fs_data_t *ffd = fs->fsap_data;
6907
6908  /* Remove the shared transaction object associated with this transaction. */
6909  SVN_ERR(purge_shared_txn(fs, txn_id, pool));
6910  /* Remove the directory associated with this transaction. */
6911  SVN_ERR(svn_io_remove_dir2(path_txn_dir(fs, txn_id, pool), FALSE,
6912                             NULL, NULL, pool));
6913  if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
6914    {
6915      /* Delete protorev and its lock, which aren't in the txn
6916         directory.  It's OK if they don't exist (for example, if this
6917         is post-commit and the proto-rev has been moved into
6918         place). */
6919      SVN_ERR(svn_io_remove_file2(path_txn_proto_rev(fs, txn_id, pool),
6920                                  TRUE, pool));
6921      SVN_ERR(svn_io_remove_file2(path_txn_proto_rev_lock(fs, txn_id, pool),
6922                                  TRUE, pool));
6923    }
6924  return SVN_NO_ERROR;
6925}
6926
6927
6928svn_error_t *
6929svn_fs_fs__abort_txn(svn_fs_txn_t *txn,
6930                     apr_pool_t *pool)
6931{
6932  SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
6933
6934  /* Now, purge the transaction. */
6935  SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool),
6936            apr_psprintf(pool, _("Transaction '%s' cleanup failed"),
6937                         txn->id));
6938
6939  return SVN_NO_ERROR;
6940}
6941
6942
6943svn_error_t *
6944svn_fs_fs__set_entry(svn_fs_t *fs,
6945                     const char *txn_id,
6946                     node_revision_t *parent_noderev,
6947                     const char *name,
6948                     const svn_fs_id_t *id,
6949                     svn_node_kind_t kind,
6950                     apr_pool_t *pool)
6951{
6952  representation_t *rep = parent_noderev->data_rep;
6953  const char *filename = path_txn_node_children(fs, parent_noderev->id, pool);
6954  apr_file_t *file;
6955  svn_stream_t *out;
6956  fs_fs_data_t *ffd = fs->fsap_data;
6957  apr_pool_t *subpool = svn_pool_create(pool);
6958
6959  if (!rep || !rep->txn_id)
6960    {
6961      const char *unique_suffix;
6962      apr_hash_t *entries;
6963
6964      /* Before we can modify the directory, we need to dump its old
6965         contents into a mutable representation file. */
6966      SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
6967                                          subpool));
6968      SVN_ERR(unparse_dir_entries(&entries, entries, subpool));
6969      SVN_ERR(svn_io_file_open(&file, filename,
6970                               APR_WRITE | APR_CREATE | APR_BUFFERED,
6971                               APR_OS_DEFAULT, pool));
6972      out = svn_stream_from_aprfile2(file, TRUE, pool);
6973      SVN_ERR(svn_hash_write2(entries, out, SVN_HASH_TERMINATOR, subpool));
6974
6975      svn_pool_clear(subpool);
6976
6977      /* Mark the node-rev's data rep as mutable. */
6978      rep = apr_pcalloc(pool, sizeof(*rep));
6979      rep->revision = SVN_INVALID_REVNUM;
6980      rep->txn_id = txn_id;
6981
6982      if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
6983        {
6984          SVN_ERR(get_new_txn_node_id(&unique_suffix, fs, txn_id, pool));
6985          rep->uniquifier = apr_psprintf(pool, "%s/%s", txn_id, unique_suffix);
6986        }
6987
6988      parent_noderev->data_rep = rep;
6989      SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id,
6990                                           parent_noderev, FALSE, pool));
6991    }
6992  else
6993    {
6994      /* The directory rep is already mutable, so just open it for append. */
6995      SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
6996                               APR_OS_DEFAULT, pool));
6997      out = svn_stream_from_aprfile2(file, TRUE, pool);
6998    }
6999
7000  /* if we have a directory cache for this transaction, update it */
7001  if (ffd->txn_dir_cache)
7002    {
7003      /* build parameters: (name, new entry) pair */
7004      const char *key =
7005          svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data;
7006      replace_baton_t baton;
7007
7008      baton.name = name;
7009      baton.new_entry = NULL;
7010
7011      if (id)
7012        {
7013          baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry));
7014          baton.new_entry->name = name;
7015          baton.new_entry->kind = kind;
7016          baton.new_entry->id = id;
7017        }
7018
7019      /* actually update the cached directory (if cached) */
7020      SVN_ERR(svn_cache__set_partial(ffd->txn_dir_cache, key,
7021                                     svn_fs_fs__replace_dir_entry, &baton,
7022                                     subpool));
7023    }
7024  svn_pool_clear(subpool);
7025
7026  /* Append an incremental hash entry for the entry change. */
7027  if (id)
7028    {
7029      const char *val = unparse_dir_entry(kind, id, subpool);
7030
7031      SVN_ERR(svn_stream_printf(out, subpool, "K %" APR_SIZE_T_FMT "\n%s\n"
7032                                "V %" APR_SIZE_T_FMT "\n%s\n",
7033                                strlen(name), name,
7034                                strlen(val), val));
7035    }
7036  else
7037    {
7038      SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
7039                                strlen(name), name));
7040    }
7041
7042  SVN_ERR(svn_io_file_close(file, subpool));
7043  svn_pool_destroy(subpool);
7044  return SVN_NO_ERROR;
7045}
7046
7047/* Write a single change entry, path PATH, change CHANGE, and copyfrom
7048   string COPYFROM, into the file specified by FILE.  Only include the
7049   node kind field if INCLUDE_NODE_KIND is true.  All temporary
7050   allocations are in POOL. */
7051static svn_error_t *
7052write_change_entry(apr_file_t *file,
7053                   const char *path,
7054                   svn_fs_path_change2_t *change,
7055                   svn_boolean_t include_node_kind,
7056                   apr_pool_t *pool)
7057{
7058  const char *idstr, *buf;
7059  const char *change_string = NULL;
7060  const char *kind_string = "";
7061
7062  switch (change->change_kind)
7063    {
7064    case svn_fs_path_change_modify:
7065      change_string = ACTION_MODIFY;
7066      break;
7067    case svn_fs_path_change_add:
7068      change_string = ACTION_ADD;
7069      break;
7070    case svn_fs_path_change_delete:
7071      change_string = ACTION_DELETE;
7072      break;
7073    case svn_fs_path_change_replace:
7074      change_string = ACTION_REPLACE;
7075      break;
7076    case svn_fs_path_change_reset:
7077      change_string = ACTION_RESET;
7078      break;
7079    default:
7080      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
7081                               _("Invalid change type %d"),
7082                               change->change_kind);
7083    }
7084
7085  if (change->node_rev_id)
7086    idstr = svn_fs_fs__id_unparse(change->node_rev_id, pool)->data;
7087  else
7088    idstr = ACTION_RESET;
7089
7090  if (include_node_kind)
7091    {
7092      SVN_ERR_ASSERT(change->node_kind == svn_node_dir
7093                     || change->node_kind == svn_node_file);
7094      kind_string = apr_psprintf(pool, "-%s",
7095                                 change->node_kind == svn_node_dir
7096                                 ? KIND_DIR : KIND_FILE);
7097    }
7098  buf = apr_psprintf(pool, "%s %s%s %s %s %s\n",
7099                     idstr, change_string, kind_string,
7100                     change->text_mod ? FLAG_TRUE : FLAG_FALSE,
7101                     change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
7102                     path);
7103
7104  SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
7105
7106  if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
7107    {
7108      buf = apr_psprintf(pool, "%ld %s", change->copyfrom_rev,
7109                         change->copyfrom_path);
7110      SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
7111    }
7112
7113  return svn_io_file_write_full(file, "\n", 1, NULL, pool);
7114}
7115
7116svn_error_t *
7117svn_fs_fs__add_change(svn_fs_t *fs,
7118                      const char *txn_id,
7119                      const char *path,
7120                      const svn_fs_id_t *id,
7121                      svn_fs_path_change_kind_t change_kind,
7122                      svn_boolean_t text_mod,
7123                      svn_boolean_t prop_mod,
7124                      svn_node_kind_t node_kind,
7125                      svn_revnum_t copyfrom_rev,
7126                      const char *copyfrom_path,
7127                      apr_pool_t *pool)
7128{
7129  apr_file_t *file;
7130  svn_fs_path_change2_t *change;
7131
7132  SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
7133                           APR_APPEND | APR_WRITE | APR_CREATE
7134                           | APR_BUFFERED, APR_OS_DEFAULT, pool));
7135
7136  change = svn_fs__path_change_create_internal(id, change_kind, pool);
7137  change->text_mod = text_mod;
7138  change->prop_mod = prop_mod;
7139  change->node_kind = node_kind;
7140  change->copyfrom_rev = copyfrom_rev;
7141  change->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
7142
7143  SVN_ERR(write_change_entry(file, path, change, TRUE, pool));
7144
7145  return svn_io_file_close(file, pool);
7146}
7147
7148/* This baton is used by the representation writing streams.  It keeps
7149   track of the checksum information as well as the total size of the
7150   representation so far. */
7151struct rep_write_baton
7152{
7153  /* The FS we are writing to. */
7154  svn_fs_t *fs;
7155
7156  /* Actual file to which we are writing. */
7157  svn_stream_t *rep_stream;
7158
7159  /* A stream from the delta combiner.  Data written here gets
7160     deltified, then eventually written to rep_stream. */
7161  svn_stream_t *delta_stream;
7162
7163  /* Where is this representation header stored. */
7164  apr_off_t rep_offset;
7165
7166  /* Start of the actual data. */
7167  apr_off_t delta_start;
7168
7169  /* How many bytes have been written to this rep already. */
7170  svn_filesize_t rep_size;
7171
7172  /* The node revision for which we're writing out info. */
7173  node_revision_t *noderev;
7174
7175  /* Actual output file. */
7176  apr_file_t *file;
7177  /* Lock 'cookie' used to unlock the output file once we've finished
7178     writing to it. */
7179  void *lockcookie;
7180
7181  svn_checksum_ctx_t *md5_checksum_ctx;
7182  svn_checksum_ctx_t *sha1_checksum_ctx;
7183
7184  apr_pool_t *pool;
7185
7186  apr_pool_t *parent_pool;
7187};
7188
7189/* Handler for the write method of the representation writable stream.
7190   BATON is a rep_write_baton, DATA is the data to write, and *LEN is
7191   the length of this data. */
7192static svn_error_t *
7193rep_write_contents(void *baton,
7194                   const char *data,
7195                   apr_size_t *len)
7196{
7197  struct rep_write_baton *b = baton;
7198
7199  SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len));
7200  SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len));
7201  b->rep_size += *len;
7202
7203  /* If we are writing a delta, use that stream. */
7204  if (b->delta_stream)
7205    return svn_stream_write(b->delta_stream, data, len);
7206  else
7207    return svn_stream_write(b->rep_stream, data, len);
7208}
7209
7210/* Given a node-revision NODEREV in filesystem FS, return the
7211   representation in *REP to use as the base for a text representation
7212   delta if PROPS is FALSE.  If PROPS has been set, a suitable props
7213   base representation will be returned.  Perform temporary allocations
7214   in *POOL. */
7215static svn_error_t *
7216choose_delta_base(representation_t **rep,
7217                  svn_fs_t *fs,
7218                  node_revision_t *noderev,
7219                  svn_boolean_t props,
7220                  apr_pool_t *pool)
7221{
7222  int count;
7223  int walk;
7224  node_revision_t *base;
7225  fs_fs_data_t *ffd = fs->fsap_data;
7226  svn_boolean_t maybe_shared_rep = FALSE;
7227
7228  /* If we have no predecessors, then use the empty stream as a
7229     base. */
7230  if (! noderev->predecessor_count)
7231    {
7232      *rep = NULL;
7233      return SVN_NO_ERROR;
7234    }
7235
7236  /* Flip the rightmost '1' bit of the predecessor count to determine
7237     which file rev (counting from 0) we want to use.  (To see why
7238     count & (count - 1) unsets the rightmost set bit, think about how
7239     you decrement a binary number.) */
7240  count = noderev->predecessor_count;
7241  count = count & (count - 1);
7242
7243  /* We use skip delta for limiting the number of delta operations
7244     along very long node histories.  Close to HEAD however, we create
7245     a linear history to minimize delta size.  */
7246  walk = noderev->predecessor_count - count;
7247  if (walk < (int)ffd->max_linear_deltification)
7248    count = noderev->predecessor_count - 1;
7249
7250  /* Finding the delta base over a very long distance can become extremely
7251     expensive for very deep histories, possibly causing client timeouts etc.
7252     OTOH, this is a rare operation and its gains are minimal. Lets simply
7253     start deltification anew close every other 1000 changes or so.  */
7254  if (walk > (int)ffd->max_deltification_walk)
7255    {
7256      *rep = NULL;
7257      return SVN_NO_ERROR;
7258    }
7259
7260  /* Walk back a number of predecessors equal to the difference
7261     between count and the original predecessor count.  (For example,
7262     if noderev has ten predecessors and we want the eighth file rev,
7263     walk back two predecessors.) */
7264  base = noderev;
7265  while ((count++) < noderev->predecessor_count)
7266    {
7267      SVN_ERR(svn_fs_fs__get_node_revision(&base, fs,
7268                                           base->predecessor_id, pool));
7269
7270      /* If there is a shared rep along the way, we need to limit the
7271       * length of the deltification chain.
7272       *
7273       * Please note that copied nodes - such as branch directories - will
7274       * look the same (false positive) while reps shared within the same
7275       * revision will not be caught (false negative).
7276       */
7277      if (props)
7278        {
7279          if (   base->prop_rep
7280              && svn_fs_fs__id_rev(base->id) > base->prop_rep->revision)
7281            maybe_shared_rep = TRUE;
7282        }
7283      else
7284        {
7285          if (   base->data_rep
7286              && svn_fs_fs__id_rev(base->id) > base->data_rep->revision)
7287            maybe_shared_rep = TRUE;
7288        }
7289    }
7290
7291  /* return a suitable base representation */
7292  *rep = props ? base->prop_rep : base->data_rep;
7293
7294  /* if we encountered a shared rep, it's parent chain may be different
7295   * from the node-rev parent chain. */
7296  if (*rep && maybe_shared_rep)
7297    {
7298      /* Check whether the length of the deltification chain is acceptable.
7299       * Otherwise, shared reps may form a non-skipping delta chain in
7300       * extreme cases. */
7301      apr_pool_t *sub_pool = svn_pool_create(pool);
7302      representation_t base_rep = **rep;
7303
7304      /* Some reasonable limit, depending on how acceptable longer linear
7305       * chains are in this repo.  Also, allow for some minimal chain. */
7306      int max_chain_length = 2 * (int)ffd->max_linear_deltification + 2;
7307
7308      /* re-use open files between iterations */
7309      svn_revnum_t rev_hint = SVN_INVALID_REVNUM;
7310      apr_file_t *file_hint = NULL;
7311
7312      /* follow the delta chain towards the end but for at most
7313       * MAX_CHAIN_LENGTH steps. */
7314      for (; max_chain_length; --max_chain_length)
7315        {
7316          struct rep_state *rep_state;
7317          struct rep_args *rep_args;
7318
7319          SVN_ERR(create_rep_state_body(&rep_state,
7320                                        &rep_args,
7321                                        &file_hint,
7322                                        &rev_hint,
7323                                        &base_rep,
7324                                        fs,
7325                                        sub_pool));
7326          if (!rep_args->is_delta  || !rep_args->base_revision)
7327            break;
7328
7329          base_rep.revision = rep_args->base_revision;
7330          base_rep.offset = rep_args->base_offset;
7331          base_rep.size = rep_args->base_length;
7332          base_rep.txn_id = NULL;
7333        }
7334
7335      /* start new delta chain if the current one has grown too long */
7336      if (max_chain_length == 0)
7337        *rep = NULL;
7338
7339      svn_pool_destroy(sub_pool);
7340    }
7341
7342  /* verify that the reps don't form a degenerated '*/
7343  return SVN_NO_ERROR;
7344}
7345
7346/* Something went wrong and the pool for the rep write is being
7347   cleared before we've finished writing the rep.  So we need
7348   to remove the rep from the protorevfile and we need to unlock
7349   the protorevfile. */
7350static apr_status_t
7351rep_write_cleanup(void *data)
7352{
7353  struct rep_write_baton *b = data;
7354  const char *txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
7355  svn_error_t *err;
7356
7357  /* Truncate and close the protorevfile. */
7358  err = svn_io_file_trunc(b->file, b->rep_offset, b->pool);
7359  err = svn_error_compose_create(err, svn_io_file_close(b->file, b->pool));
7360
7361  /* Remove our lock regardless of any preceeding errors so that the
7362     being_written flag is always removed and stays consistent with the
7363     file lock which will be removed no matter what since the pool is
7364     going away. */
7365  err = svn_error_compose_create(err, unlock_proto_rev(b->fs, txn_id,
7366                                                       b->lockcookie, b->pool));
7367  if (err)
7368    {
7369      apr_status_t rc = err->apr_err;
7370      svn_error_clear(err);
7371      return rc;
7372    }
7373
7374  return APR_SUCCESS;
7375}
7376
7377
7378/* Get a rep_write_baton and store it in *WB_P for the representation
7379   indicated by NODEREV in filesystem FS.  Perform allocations in
7380   POOL.  Only appropriate for file contents, not for props or
7381   directory contents. */
7382static svn_error_t *
7383rep_write_get_baton(struct rep_write_baton **wb_p,
7384                    svn_fs_t *fs,
7385                    node_revision_t *noderev,
7386                    apr_pool_t *pool)
7387{
7388  struct rep_write_baton *b;
7389  apr_file_t *file;
7390  representation_t *base_rep;
7391  svn_stream_t *source;
7392  const char *header;
7393  svn_txdelta_window_handler_t wh;
7394  void *whb;
7395  fs_fs_data_t *ffd = fs->fsap_data;
7396  int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
7397
7398  b = apr_pcalloc(pool, sizeof(*b));
7399
7400  b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7401  b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7402
7403  b->fs = fs;
7404  b->parent_pool = pool;
7405  b->pool = svn_pool_create(pool);
7406  b->rep_size = 0;
7407  b->noderev = noderev;
7408
7409  /* Open the prototype rev file and seek to its end. */
7410  SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie,
7411                                 fs, svn_fs_fs__id_txn_id(noderev->id),
7412                                 b->pool));
7413
7414  b->file = file;
7415  b->rep_stream = svn_stream_from_aprfile2(file, TRUE, b->pool);
7416
7417  SVN_ERR(get_file_offset(&b->rep_offset, file, b->pool));
7418
7419  /* Get the base for this delta. */
7420  SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->pool));
7421  SVN_ERR(read_representation(&source, fs, base_rep, b->pool));
7422
7423  /* Write out the rep header. */
7424  if (base_rep)
7425    {
7426      header = apr_psprintf(b->pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
7427                            SVN_FILESIZE_T_FMT "\n",
7428                            base_rep->revision, base_rep->offset,
7429                            base_rep->size);
7430    }
7431  else
7432    {
7433      header = REP_DELTA "\n";
7434    }
7435  SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
7436                                 b->pool));
7437
7438  /* Now determine the offset of the actual svndiff data. */
7439  SVN_ERR(get_file_offset(&b->delta_start, file, b->pool));
7440
7441  /* Cleanup in case something goes wrong. */
7442  apr_pool_cleanup_register(b->pool, b, rep_write_cleanup,
7443                            apr_pool_cleanup_null);
7444
7445  /* Prepare to write the svndiff data. */
7446  svn_txdelta_to_svndiff3(&wh,
7447                          &whb,
7448                          b->rep_stream,
7449                          diff_version,
7450                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
7451                          pool);
7452
7453  b->delta_stream = svn_txdelta_target_push(wh, whb, source, b->pool);
7454
7455  *wb_p = b;
7456
7457  return SVN_NO_ERROR;
7458}
7459
7460/* For the hash REP->SHA1, try to find an already existing representation
7461   in FS and return it in *OUT_REP.  If no such representation exists or
7462   if rep sharing has been disabled for FS, NULL will be returned.  Since
7463   there may be new duplicate representations within the same uncommitted
7464   revision, those can be passed in REPS_HASH (maps a sha1 digest onto
7465   representation_t*), otherwise pass in NULL for REPS_HASH.
7466   POOL will be used for allocations. The lifetime of the returned rep is
7467   limited by both, POOL and REP lifetime.
7468 */
7469static svn_error_t *
7470get_shared_rep(representation_t **old_rep,
7471               svn_fs_t *fs,
7472               representation_t *rep,
7473               apr_hash_t *reps_hash,
7474               apr_pool_t *pool)
7475{
7476  svn_error_t *err;
7477  fs_fs_data_t *ffd = fs->fsap_data;
7478
7479  /* Return NULL, if rep sharing has been disabled. */
7480  *old_rep = NULL;
7481  if (!ffd->rep_sharing_allowed)
7482    return SVN_NO_ERROR;
7483
7484  /* Check and see if we already have a representation somewhere that's
7485     identical to the one we just wrote out.  Start with the hash lookup
7486     because it is cheepest. */
7487  if (reps_hash)
7488    *old_rep = apr_hash_get(reps_hash,
7489                            rep->sha1_checksum->digest,
7490                            APR_SHA1_DIGESTSIZE);
7491
7492  /* If we haven't found anything yet, try harder and consult our DB. */
7493  if (*old_rep == NULL)
7494    {
7495      err = svn_fs_fs__get_rep_reference(old_rep, fs, rep->sha1_checksum,
7496                                         pool);
7497      /* ### Other error codes that we shouldn't mask out? */
7498      if (err == SVN_NO_ERROR)
7499        {
7500          if (*old_rep)
7501            SVN_ERR(verify_walker(*old_rep, NULL, fs, pool));
7502        }
7503      else if (err->apr_err == SVN_ERR_FS_CORRUPT
7504               || SVN_ERROR_IN_CATEGORY(err->apr_err,
7505                                        SVN_ERR_MALFUNC_CATEGORY_START))
7506        {
7507          /* Fatal error; don't mask it.
7508
7509             In particular, this block is triggered when the rep-cache refers
7510             to revisions in the future.  We signal that as a corruption situation
7511             since, once those revisions are less than youngest (because of more
7512             commits), the rep-cache would be invalid.
7513           */
7514          SVN_ERR(err);
7515        }
7516      else
7517        {
7518          /* Something's wrong with the rep-sharing index.  We can continue
7519             without rep-sharing, but warn.
7520           */
7521          (fs->warning)(fs->warning_baton, err);
7522          svn_error_clear(err);
7523          *old_rep = NULL;
7524        }
7525    }
7526
7527  /* look for intra-revision matches (usually data reps but not limited
7528     to them in case props happen to look like some data rep)
7529   */
7530  if (*old_rep == NULL && rep->txn_id)
7531    {
7532      svn_node_kind_t kind;
7533      const char *file_name
7534        = path_txn_sha1(fs, rep->txn_id, rep->sha1_checksum, pool);
7535
7536      /* in our txn, is there a rep file named with the wanted SHA1?
7537         If so, read it and use that rep.
7538       */
7539      SVN_ERR(svn_io_check_path(file_name, &kind, pool));
7540      if (kind == svn_node_file)
7541        {
7542          svn_stringbuf_t *rep_string;
7543          SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name, pool));
7544          SVN_ERR(read_rep_offsets_body(old_rep, rep_string->data,
7545                                        rep->txn_id, FALSE, pool));
7546        }
7547    }
7548
7549  /* Add information that is missing in the cached data. */
7550  if (*old_rep)
7551    {
7552      /* Use the old rep for this content. */
7553      (*old_rep)->md5_checksum = rep->md5_checksum;
7554      (*old_rep)->uniquifier = rep->uniquifier;
7555    }
7556
7557  return SVN_NO_ERROR;
7558}
7559
7560/* Close handler for the representation write stream.  BATON is a
7561   rep_write_baton.  Writes out a new node-rev that correctly
7562   references the representation we just finished writing. */
7563static svn_error_t *
7564rep_write_contents_close(void *baton)
7565{
7566  struct rep_write_baton *b = baton;
7567  const char *unique_suffix;
7568  representation_t *rep;
7569  representation_t *old_rep;
7570  apr_off_t offset;
7571  fs_fs_data_t *ffd = b->fs->fsap_data;
7572
7573  rep = apr_pcalloc(b->parent_pool, sizeof(*rep));
7574  rep->offset = b->rep_offset;
7575
7576  /* Close our delta stream so the last bits of svndiff are written
7577     out. */
7578  if (b->delta_stream)
7579    SVN_ERR(svn_stream_close(b->delta_stream));
7580
7581  /* Determine the length of the svndiff data. */
7582  SVN_ERR(get_file_offset(&offset, b->file, b->pool));
7583  rep->size = offset - b->delta_start;
7584
7585  /* Fill in the rest of the representation field. */
7586  rep->expanded_size = b->rep_size;
7587  rep->txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
7588
7589  if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
7590    {
7591      SVN_ERR(get_new_txn_node_id(&unique_suffix, b->fs, rep->txn_id, b->pool));
7592      rep->uniquifier = apr_psprintf(b->parent_pool, "%s/%s", rep->txn_id,
7593                                     unique_suffix);
7594    }
7595  rep->revision = SVN_INVALID_REVNUM;
7596
7597  /* Finalize the checksum. */
7598  SVN_ERR(svn_checksum_final(&rep->md5_checksum, b->md5_checksum_ctx,
7599                              b->parent_pool));
7600  SVN_ERR(svn_checksum_final(&rep->sha1_checksum, b->sha1_checksum_ctx,
7601                              b->parent_pool));
7602
7603  /* Check and see if we already have a representation somewhere that's
7604     identical to the one we just wrote out. */
7605  SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->parent_pool));
7606
7607  if (old_rep)
7608    {
7609      /* We need to erase from the protorev the data we just wrote. */
7610      SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->pool));
7611
7612      /* Use the old rep for this content. */
7613      b->noderev->data_rep = old_rep;
7614    }
7615  else
7616    {
7617      /* Write out our cosmetic end marker. */
7618      SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n"));
7619
7620      b->noderev->data_rep = rep;
7621    }
7622
7623  /* Remove cleanup callback. */
7624  apr_pool_cleanup_kill(b->pool, b, rep_write_cleanup);
7625
7626  /* Write out the new node-rev information. */
7627  SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev, FALSE,
7628                                       b->pool));
7629  if (!old_rep)
7630    SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->pool));
7631
7632  SVN_ERR(svn_io_file_close(b->file, b->pool));
7633  SVN_ERR(unlock_proto_rev(b->fs, rep->txn_id, b->lockcookie, b->pool));
7634  svn_pool_destroy(b->pool);
7635
7636  return SVN_NO_ERROR;
7637}
7638
7639/* Store a writable stream in *CONTENTS_P that will receive all data
7640   written and store it as the file data representation referenced by
7641   NODEREV in filesystem FS.  Perform temporary allocations in
7642   POOL.  Only appropriate for file data, not props or directory
7643   contents. */
7644static svn_error_t *
7645set_representation(svn_stream_t **contents_p,
7646                   svn_fs_t *fs,
7647                   node_revision_t *noderev,
7648                   apr_pool_t *pool)
7649{
7650  struct rep_write_baton *wb;
7651
7652  if (! svn_fs_fs__id_txn_id(noderev->id))
7653    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
7654                             _("Attempted to write to non-transaction '%s'"),
7655                             svn_fs_fs__id_unparse(noderev->id, pool)->data);
7656
7657  SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool));
7658
7659  *contents_p = svn_stream_create(wb, pool);
7660  svn_stream_set_write(*contents_p, rep_write_contents);
7661  svn_stream_set_close(*contents_p, rep_write_contents_close);
7662
7663  return SVN_NO_ERROR;
7664}
7665
7666svn_error_t *
7667svn_fs_fs__set_contents(svn_stream_t **stream,
7668                        svn_fs_t *fs,
7669                        node_revision_t *noderev,
7670                        apr_pool_t *pool)
7671{
7672  if (noderev->kind != svn_node_file)
7673    return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
7674                            _("Can't set text contents of a directory"));
7675
7676  return set_representation(stream, fs, noderev, pool);
7677}
7678
7679svn_error_t *
7680svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
7681                            svn_fs_t *fs,
7682                            const svn_fs_id_t *old_idp,
7683                            node_revision_t *new_noderev,
7684                            const char *copy_id,
7685                            const char *txn_id,
7686                            apr_pool_t *pool)
7687{
7688  const svn_fs_id_t *id;
7689
7690  if (! copy_id)
7691    copy_id = svn_fs_fs__id_copy_id(old_idp);
7692  id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id,
7693                                txn_id, pool);
7694
7695  new_noderev->id = id;
7696
7697  if (! new_noderev->copyroot_path)
7698    {
7699      new_noderev->copyroot_path = apr_pstrdup(pool,
7700                                               new_noderev->created_path);
7701      new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id);
7702    }
7703
7704  SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE,
7705                                       pool));
7706
7707  *new_id_p = id;
7708
7709  return SVN_NO_ERROR;
7710}
7711
7712svn_error_t *
7713svn_fs_fs__set_proplist(svn_fs_t *fs,
7714                        node_revision_t *noderev,
7715                        apr_hash_t *proplist,
7716                        apr_pool_t *pool)
7717{
7718  const char *filename = path_txn_node_props(fs, noderev->id, pool);
7719  apr_file_t *file;
7720  svn_stream_t *out;
7721
7722  /* Dump the property list to the mutable property file. */
7723  SVN_ERR(svn_io_file_open(&file, filename,
7724                           APR_WRITE | APR_CREATE | APR_TRUNCATE
7725                           | APR_BUFFERED, APR_OS_DEFAULT, pool));
7726  out = svn_stream_from_aprfile2(file, TRUE, pool);
7727  SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool));
7728  SVN_ERR(svn_io_file_close(file, pool));
7729
7730  /* Mark the node-rev's prop rep as mutable, if not already done. */
7731  if (!noderev->prop_rep || !noderev->prop_rep->txn_id)
7732    {
7733      noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep));
7734      noderev->prop_rep->txn_id = svn_fs_fs__id_txn_id(noderev->id);
7735      SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
7736    }
7737
7738  return SVN_NO_ERROR;
7739}
7740
7741/* Read the 'current' file for filesystem FS and store the next
7742   available node id in *NODE_ID, and the next available copy id in
7743   *COPY_ID.  Allocations are performed from POOL. */
7744static svn_error_t *
7745get_next_revision_ids(const char **node_id,
7746                      const char **copy_id,
7747                      svn_fs_t *fs,
7748                      apr_pool_t *pool)
7749{
7750  char *buf;
7751  char *str;
7752  svn_stringbuf_t *content;
7753
7754  SVN_ERR(read_content(&content, svn_fs_fs__path_current(fs, pool), pool));
7755  buf = content->data;
7756
7757  str = svn_cstring_tokenize(" ", &buf);
7758  if (! str)
7759    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7760                            _("Corrupt 'current' file"));
7761
7762  str = svn_cstring_tokenize(" ", &buf);
7763  if (! str)
7764    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7765                            _("Corrupt 'current' file"));
7766
7767  *node_id = apr_pstrdup(pool, str);
7768
7769  str = svn_cstring_tokenize(" \n", &buf);
7770  if (! str)
7771    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7772                            _("Corrupt 'current' file"));
7773
7774  *copy_id = apr_pstrdup(pool, str);
7775
7776  return SVN_NO_ERROR;
7777}
7778
7779/* This baton is used by the stream created for write_hash_rep. */
7780struct write_hash_baton
7781{
7782  svn_stream_t *stream;
7783
7784  apr_size_t size;
7785
7786  svn_checksum_ctx_t *md5_ctx;
7787  svn_checksum_ctx_t *sha1_ctx;
7788};
7789
7790/* The handler for the write_hash_rep stream.  BATON is a
7791   write_hash_baton, DATA has the data to write and *LEN is the number
7792   of bytes to write. */
7793static svn_error_t *
7794write_hash_handler(void *baton,
7795                   const char *data,
7796                   apr_size_t *len)
7797{
7798  struct write_hash_baton *whb = baton;
7799
7800  SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
7801  SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
7802
7803  SVN_ERR(svn_stream_write(whb->stream, data, len));
7804  whb->size += *len;
7805
7806  return SVN_NO_ERROR;
7807}
7808
7809/* Write out the hash HASH as a text representation to file FILE.  In
7810   the process, record position, the total size of the dump and MD5 as
7811   well as SHA1 in REP.   If rep sharing has been enabled and REPS_HASH
7812   is not NULL, it will be used in addition to the on-disk cache to find
7813   earlier reps with the same content.  When such existing reps can be
7814   found, we will truncate the one just written from the file and return
7815   the existing rep.  Perform temporary allocations in POOL. */
7816static svn_error_t *
7817write_hash_rep(representation_t *rep,
7818               apr_file_t *file,
7819               apr_hash_t *hash,
7820               svn_fs_t *fs,
7821               apr_hash_t *reps_hash,
7822               apr_pool_t *pool)
7823{
7824  svn_stream_t *stream;
7825  struct write_hash_baton *whb;
7826  representation_t *old_rep;
7827
7828  SVN_ERR(get_file_offset(&rep->offset, file, pool));
7829
7830  whb = apr_pcalloc(pool, sizeof(*whb));
7831
7832  whb->stream = svn_stream_from_aprfile2(file, TRUE, pool);
7833  whb->size = 0;
7834  whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7835  whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7836
7837  stream = svn_stream_create(whb, pool);
7838  svn_stream_set_write(stream, write_hash_handler);
7839
7840  SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n"));
7841
7842  SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
7843
7844  /* Store the results. */
7845  SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
7846  SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
7847
7848  /* Check and see if we already have a representation somewhere that's
7849     identical to the one we just wrote out. */
7850  SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
7851
7852  if (old_rep)
7853    {
7854      /* We need to erase from the protorev the data we just wrote. */
7855      SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
7856
7857      /* Use the old rep for this content. */
7858      memcpy(rep, old_rep, sizeof (*rep));
7859    }
7860  else
7861    {
7862      /* Write out our cosmetic end marker. */
7863      SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n"));
7864
7865      /* update the representation */
7866      rep->size = whb->size;
7867      rep->expanded_size = whb->size;
7868    }
7869
7870  return SVN_NO_ERROR;
7871}
7872
7873/* Write out the hash HASH pertaining to the NODEREV in FS as a deltified
7874   text representation to file FILE.  In the process, record the total size
7875   and the md5 digest in REP.  If rep sharing has been enabled and REPS_HASH
7876   is not NULL, it will be used in addition to the on-disk cache to find
7877   earlier reps with the same content.  When such existing reps can be found,
7878   we will truncate the one just written from the file and return the existing
7879   rep.  If PROPS is set, assume that we want to a props representation as
7880   the base for our delta.  Perform temporary allocations in POOL. */
7881static svn_error_t *
7882write_hash_delta_rep(representation_t *rep,
7883                     apr_file_t *file,
7884                     apr_hash_t *hash,
7885                     svn_fs_t *fs,
7886                     node_revision_t *noderev,
7887                     apr_hash_t *reps_hash,
7888                     svn_boolean_t props,
7889                     apr_pool_t *pool)
7890{
7891  svn_txdelta_window_handler_t diff_wh;
7892  void *diff_whb;
7893
7894  svn_stream_t *file_stream;
7895  svn_stream_t *stream;
7896  representation_t *base_rep;
7897  representation_t *old_rep;
7898  svn_stream_t *source;
7899  const char *header;
7900
7901  apr_off_t rep_end = 0;
7902  apr_off_t delta_start = 0;
7903
7904  struct write_hash_baton *whb;
7905  fs_fs_data_t *ffd = fs->fsap_data;
7906  int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
7907
7908  /* Get the base for this delta. */
7909  SVN_ERR(choose_delta_base(&base_rep, fs, noderev, props, pool));
7910  SVN_ERR(read_representation(&source, fs, base_rep, pool));
7911
7912  SVN_ERR(get_file_offset(&rep->offset, file, pool));
7913
7914  /* Write out the rep header. */
7915  if (base_rep)
7916    {
7917      header = apr_psprintf(pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
7918                            SVN_FILESIZE_T_FMT "\n",
7919                            base_rep->revision, base_rep->offset,
7920                            base_rep->size);
7921    }
7922  else
7923    {
7924      header = REP_DELTA "\n";
7925    }
7926  SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
7927                                 pool));
7928
7929  SVN_ERR(get_file_offset(&delta_start, file, pool));
7930  file_stream = svn_stream_from_aprfile2(file, TRUE, pool);
7931
7932  /* Prepare to write the svndiff data. */
7933  svn_txdelta_to_svndiff3(&diff_wh,
7934                          &diff_whb,
7935                          file_stream,
7936                          diff_version,
7937                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
7938                          pool);
7939
7940  whb = apr_pcalloc(pool, sizeof(*whb));
7941  whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source, pool);
7942  whb->size = 0;
7943  whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7944  whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7945
7946  /* serialize the hash */
7947  stream = svn_stream_create(whb, pool);
7948  svn_stream_set_write(stream, write_hash_handler);
7949
7950  SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
7951  SVN_ERR(svn_stream_close(whb->stream));
7952
7953  /* Store the results. */
7954  SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
7955  SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
7956
7957  /* Check and see if we already have a representation somewhere that's
7958     identical to the one we just wrote out. */
7959  SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
7960
7961  if (old_rep)
7962    {
7963      /* We need to erase from the protorev the data we just wrote. */
7964      SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
7965
7966      /* Use the old rep for this content. */
7967      memcpy(rep, old_rep, sizeof (*rep));
7968    }
7969  else
7970    {
7971      /* Write out our cosmetic end marker. */
7972      SVN_ERR(get_file_offset(&rep_end, file, pool));
7973      SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n"));
7974
7975      /* update the representation */
7976      rep->expanded_size = whb->size;
7977      rep->size = rep_end - delta_start;
7978    }
7979
7980  return SVN_NO_ERROR;
7981}
7982
7983/* Sanity check ROOT_NODEREV, a candidate for being the root node-revision
7984   of (not yet committed) revision REV in FS.  Use POOL for temporary
7985   allocations.
7986
7987   If you change this function, consider updating svn_fs_fs__verify() too.
7988 */
7989static svn_error_t *
7990validate_root_noderev(svn_fs_t *fs,
7991                      node_revision_t *root_noderev,
7992                      svn_revnum_t rev,
7993                      apr_pool_t *pool)
7994{
7995  svn_revnum_t head_revnum = rev-1;
7996  int head_predecessor_count;
7997
7998  SVN_ERR_ASSERT(rev > 0);
7999
8000  /* Compute HEAD_PREDECESSOR_COUNT. */
8001  {
8002    svn_fs_root_t *head_revision;
8003    const svn_fs_id_t *head_root_id;
8004    node_revision_t *head_root_noderev;
8005
8006    /* Get /@HEAD's noderev. */
8007    SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool));
8008    SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool));
8009    SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id,
8010                                         pool));
8011
8012    head_predecessor_count = head_root_noderev->predecessor_count;
8013  }
8014
8015  /* Check that the root noderev's predecessor count equals REV.
8016
8017     This kind of corruption was seen on svn.apache.org (both on
8018     the root noderev and on other fspaths' noderevs); see
8019     issue #4129.
8020
8021     Normally (rev == root_noderev->predecessor_count), but here we
8022     use a more roundabout check that should only trigger on new instances
8023     of the corruption, rather then trigger on each and every new commit
8024     to a repository that has triggered the bug somewhere in its root
8025     noderev's history.
8026   */
8027  if (root_noderev->predecessor_count != -1
8028      && (root_noderev->predecessor_count - head_predecessor_count)
8029         != (rev - head_revnum))
8030    {
8031      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
8032                               _("predecessor count for "
8033                                 "the root node-revision is wrong: "
8034                                 "found (%d+%ld != %d), committing r%ld"),
8035                                 head_predecessor_count,
8036                                 rev - head_revnum, /* This is equal to 1. */
8037                                 root_noderev->predecessor_count,
8038                                 rev);
8039    }
8040
8041  return SVN_NO_ERROR;
8042}
8043
8044/* Copy a node-revision specified by id ID in fileystem FS from a
8045   transaction into the proto-rev-file FILE.  Set *NEW_ID_P to a
8046   pointer to the new node-id which will be allocated in POOL.
8047   If this is a directory, copy all children as well.
8048
8049   START_NODE_ID and START_COPY_ID are
8050   the first available node and copy ids for this filesystem, for older
8051   FS formats.
8052
8053   REV is the revision number that this proto-rev-file will represent.
8054
8055   INITIAL_OFFSET is the offset of the proto-rev-file on entry to
8056   commit_body.
8057
8058   If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
8059   REPS_POOL) of each data rep that is new in this revision.
8060
8061   If REPS_HASH is not NULL, append copies (allocated in REPS_POOL)
8062   of the representations of each property rep that is new in this
8063   revision.
8064
8065   AT_ROOT is true if the node revision being written is the root
8066   node-revision.  It is only controls additional sanity checking
8067   logic.
8068
8069   Temporary allocations are also from POOL. */
8070static svn_error_t *
8071write_final_rev(const svn_fs_id_t **new_id_p,
8072                apr_file_t *file,
8073                svn_revnum_t rev,
8074                svn_fs_t *fs,
8075                const svn_fs_id_t *id,
8076                const char *start_node_id,
8077                const char *start_copy_id,
8078                apr_off_t initial_offset,
8079                apr_array_header_t *reps_to_cache,
8080                apr_hash_t *reps_hash,
8081                apr_pool_t *reps_pool,
8082                svn_boolean_t at_root,
8083                apr_pool_t *pool)
8084{
8085  node_revision_t *noderev;
8086  apr_off_t my_offset;
8087  char my_node_id_buf[MAX_KEY_SIZE + 2];
8088  char my_copy_id_buf[MAX_KEY_SIZE + 2];
8089  const svn_fs_id_t *new_id;
8090  const char *node_id, *copy_id, *my_node_id, *my_copy_id;
8091  fs_fs_data_t *ffd = fs->fsap_data;
8092
8093  *new_id_p = NULL;
8094
8095  /* Check to see if this is a transaction node. */
8096  if (! svn_fs_fs__id_txn_id(id))
8097    return SVN_NO_ERROR;
8098
8099  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
8100
8101  if (noderev->kind == svn_node_dir)
8102    {
8103      apr_pool_t *subpool;
8104      apr_hash_t *entries, *str_entries;
8105      apr_array_header_t *sorted_entries;
8106      int i;
8107
8108      /* This is a directory.  Write out all the children first. */
8109      subpool = svn_pool_create(pool);
8110
8111      SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool));
8112      /* For the sake of the repository administrator sort the entries
8113         so that the final file is deterministic and repeatable,
8114         however the rest of the FSFS code doesn't require any
8115         particular order here. */
8116      sorted_entries = svn_sort__hash(entries, svn_sort_compare_items_lexically,
8117                                      pool);
8118      for (i = 0; i < sorted_entries->nelts; ++i)
8119        {
8120          svn_fs_dirent_t *dirent = APR_ARRAY_IDX(sorted_entries, i,
8121                                                  svn_sort__item_t).value;
8122
8123          svn_pool_clear(subpool);
8124          SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
8125                                  start_node_id, start_copy_id, initial_offset,
8126                                  reps_to_cache, reps_hash, reps_pool, FALSE,
8127                                  subpool));
8128          if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
8129            dirent->id = svn_fs_fs__id_copy(new_id, pool);
8130        }
8131      svn_pool_destroy(subpool);
8132
8133      if (noderev->data_rep && noderev->data_rep->txn_id)
8134        {
8135          /* Write out the contents of this directory as a text rep. */
8136          SVN_ERR(unparse_dir_entries(&str_entries, entries, pool));
8137
8138          noderev->data_rep->txn_id = NULL;
8139          noderev->data_rep->revision = rev;
8140
8141          if (ffd->deltify_directories)
8142            SVN_ERR(write_hash_delta_rep(noderev->data_rep, file,
8143                                         str_entries, fs, noderev, NULL,
8144                                         FALSE, pool));
8145          else
8146            SVN_ERR(write_hash_rep(noderev->data_rep, file, str_entries,
8147                                   fs, NULL, pool));
8148        }
8149    }
8150  else
8151    {
8152      /* This is a file.  We should make sure the data rep, if it
8153         exists in a "this" state, gets rewritten to our new revision
8154         num. */
8155
8156      if (noderev->data_rep && noderev->data_rep->txn_id)
8157        {
8158          noderev->data_rep->txn_id = NULL;
8159          noderev->data_rep->revision = rev;
8160
8161          /* See issue 3845.  Some unknown mechanism caused the
8162             protorev file to get truncated, so check for that
8163             here.  */
8164          if (noderev->data_rep->offset + noderev->data_rep->size
8165              > initial_offset)
8166            return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
8167                                    _("Truncated protorev file detected"));
8168        }
8169    }
8170
8171  /* Fix up the property reps. */
8172  if (noderev->prop_rep && noderev->prop_rep->txn_id)
8173    {
8174      apr_hash_t *proplist;
8175      SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
8176
8177      noderev->prop_rep->txn_id = NULL;
8178      noderev->prop_rep->revision = rev;
8179
8180      if (ffd->deltify_properties)
8181        SVN_ERR(write_hash_delta_rep(noderev->prop_rep, file,
8182                                     proplist, fs, noderev, reps_hash,
8183                                     TRUE, pool));
8184      else
8185        SVN_ERR(write_hash_rep(noderev->prop_rep, file, proplist,
8186                               fs, reps_hash, pool));
8187    }
8188
8189
8190  /* Convert our temporary ID into a permanent revision one. */
8191  SVN_ERR(get_file_offset(&my_offset, file, pool));
8192
8193  node_id = svn_fs_fs__id_node_id(noderev->id);
8194  if (*node_id == '_')
8195    {
8196      if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8197        my_node_id = apr_psprintf(pool, "%s-%ld", node_id + 1, rev);
8198      else
8199        {
8200          svn_fs_fs__add_keys(start_node_id, node_id + 1, my_node_id_buf);
8201          my_node_id = my_node_id_buf;
8202        }
8203    }
8204  else
8205    my_node_id = node_id;
8206
8207  copy_id = svn_fs_fs__id_copy_id(noderev->id);
8208  if (*copy_id == '_')
8209    {
8210      if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8211        my_copy_id = apr_psprintf(pool, "%s-%ld", copy_id + 1, rev);
8212      else
8213        {
8214          svn_fs_fs__add_keys(start_copy_id, copy_id + 1, my_copy_id_buf);
8215          my_copy_id = my_copy_id_buf;
8216        }
8217    }
8218  else
8219    my_copy_id = copy_id;
8220
8221  if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
8222    noderev->copyroot_rev = rev;
8223
8224  new_id = svn_fs_fs__id_rev_create(my_node_id, my_copy_id, rev, my_offset,
8225                                    pool);
8226
8227  noderev->id = new_id;
8228
8229  if (ffd->rep_sharing_allowed)
8230    {
8231      /* Save the data representation's hash in the rep cache. */
8232      if (   noderev->data_rep && noderev->kind == svn_node_file
8233          && noderev->data_rep->revision == rev)
8234        {
8235          SVN_ERR_ASSERT(reps_to_cache && reps_pool);
8236          APR_ARRAY_PUSH(reps_to_cache, representation_t *)
8237            = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool);
8238        }
8239
8240      if (noderev->prop_rep && noderev->prop_rep->revision == rev)
8241        {
8242          /* Add new property reps to hash and on-disk cache. */
8243          representation_t *copy
8244            = svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool);
8245
8246          SVN_ERR_ASSERT(reps_to_cache && reps_pool);
8247          APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy;
8248
8249          apr_hash_set(reps_hash,
8250                        copy->sha1_checksum->digest,
8251                        APR_SHA1_DIGESTSIZE,
8252                        copy);
8253        }
8254    }
8255
8256  /* don't serialize SHA1 for dirs to disk (waste of space) */
8257  if (noderev->data_rep && noderev->kind == svn_node_dir)
8258    noderev->data_rep->sha1_checksum = NULL;
8259
8260  /* don't serialize SHA1 for props to disk (waste of space) */
8261  if (noderev->prop_rep)
8262    noderev->prop_rep->sha1_checksum = NULL;
8263
8264  /* Workaround issue #4031: is-fresh-txn-root in revision files. */
8265  noderev->is_fresh_txn_root = FALSE;
8266
8267  /* Write out our new node-revision. */
8268  if (at_root)
8269    SVN_ERR(validate_root_noderev(fs, noderev, rev, pool));
8270
8271  SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(file, TRUE, pool),
8272                                   noderev, ffd->format,
8273                                   svn_fs_fs__fs_supports_mergeinfo(fs),
8274                                   pool));
8275
8276  /* Return our ID that references the revision file. */
8277  *new_id_p = noderev->id;
8278
8279  return SVN_NO_ERROR;
8280}
8281
8282/* Write the changed path info from transaction TXN_ID in filesystem
8283   FS to the permanent rev-file FILE.  *OFFSET_P is set the to offset
8284   in the file of the beginning of this information.  Perform
8285   temporary allocations in POOL. */
8286static svn_error_t *
8287write_final_changed_path_info(apr_off_t *offset_p,
8288                              apr_file_t *file,
8289                              svn_fs_t *fs,
8290                              const char *txn_id,
8291                              apr_pool_t *pool)
8292{
8293  apr_hash_t *changed_paths;
8294  apr_off_t offset;
8295  apr_pool_t *iterpool = svn_pool_create(pool);
8296  fs_fs_data_t *ffd = fs->fsap_data;
8297  svn_boolean_t include_node_kinds =
8298      ffd->format >= SVN_FS_FS__MIN_KIND_IN_CHANGED_FORMAT;
8299  apr_array_header_t *sorted_changed_paths;
8300  int i;
8301
8302  SVN_ERR(get_file_offset(&offset, file, pool));
8303
8304  SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, fs, txn_id, pool));
8305  /* For the sake of the repository administrator sort the changes so
8306     that the final file is deterministic and repeatable, however the
8307     rest of the FSFS code doesn't require any particular order here. */
8308  sorted_changed_paths = svn_sort__hash(changed_paths,
8309                                        svn_sort_compare_items_lexically, pool);
8310
8311  /* Iterate through the changed paths one at a time, and convert the
8312     temporary node-id into a permanent one for each change entry. */
8313  for (i = 0; i < sorted_changed_paths->nelts; ++i)
8314    {
8315      node_revision_t *noderev;
8316      const svn_fs_id_t *id;
8317      svn_fs_path_change2_t *change;
8318      const char *path;
8319
8320      svn_pool_clear(iterpool);
8321
8322      change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value;
8323      path = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).key;
8324
8325      id = change->node_rev_id;
8326
8327      /* If this was a delete of a mutable node, then it is OK to
8328         leave the change entry pointing to the non-existent temporary
8329         node, since it will never be used. */
8330      if ((change->change_kind != svn_fs_path_change_delete) &&
8331          (! svn_fs_fs__id_txn_id(id)))
8332        {
8333          SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, iterpool));
8334
8335          /* noderev has the permanent node-id at this point, so we just
8336             substitute it for the temporary one. */
8337          change->node_rev_id = noderev->id;
8338        }
8339
8340      /* Write out the new entry into the final rev-file. */
8341      SVN_ERR(write_change_entry(file, path, change, include_node_kinds,
8342                                 iterpool));
8343    }
8344
8345  svn_pool_destroy(iterpool);
8346
8347  *offset_p = offset;
8348
8349  return SVN_NO_ERROR;
8350}
8351
8352/* Atomically update the 'current' file to hold the specifed REV,
8353   NEXT_NODE_ID, and NEXT_COPY_ID.  (The two next-ID parameters are
8354   ignored and may be NULL if the FS format does not use them.)
8355   Perform temporary allocations in POOL. */
8356static svn_error_t *
8357write_current(svn_fs_t *fs, svn_revnum_t rev, const char *next_node_id,
8358              const char *next_copy_id, apr_pool_t *pool)
8359{
8360  char *buf;
8361  const char *tmp_name, *name;
8362  fs_fs_data_t *ffd = fs->fsap_data;
8363
8364  /* Now we can just write out this line. */
8365  if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8366    buf = apr_psprintf(pool, "%ld\n", rev);
8367  else
8368    buf = apr_psprintf(pool, "%ld %s %s\n", rev, next_node_id, next_copy_id);
8369
8370  name = svn_fs_fs__path_current(fs, pool);
8371  SVN_ERR(svn_io_write_unique(&tmp_name,
8372                              svn_dirent_dirname(name, pool),
8373                              buf, strlen(buf),
8374                              svn_io_file_del_none, pool));
8375
8376  return move_into_place(tmp_name, name, name, pool);
8377}
8378
8379/* Open a new svn_fs_t handle to FS, set that handle's concept of "current
8380   youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on
8381   NEW_REV's revision root.
8382
8383   Intended to be called as the very last step in a commit before 'current'
8384   is bumped.  This implies that we are holding the write lock. */
8385static svn_error_t *
8386verify_as_revision_before_current_plus_plus(svn_fs_t *fs,
8387                                            svn_revnum_t new_rev,
8388                                            apr_pool_t *pool)
8389{
8390#ifdef SVN_DEBUG
8391  fs_fs_data_t *ffd = fs->fsap_data;
8392  svn_fs_t *ft; /* fs++ == ft */
8393  svn_fs_root_t *root;
8394  fs_fs_data_t *ft_ffd;
8395  apr_hash_t *fs_config;
8396
8397  SVN_ERR_ASSERT(ffd->svn_fs_open_);
8398
8399  /* make sure FT does not simply return data cached by other instances
8400   * but actually retrieves it from disk at least once.
8401   */
8402  fs_config = apr_hash_make(pool);
8403  svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
8404                           svn_uuid_generate(pool));
8405  SVN_ERR(ffd->svn_fs_open_(&ft, fs->path,
8406                            fs_config,
8407                            pool));
8408  ft_ffd = ft->fsap_data;
8409  /* Don't let FT consult rep-cache.db, either. */
8410  ft_ffd->rep_sharing_allowed = FALSE;
8411
8412  /* Time travel! */
8413  ft_ffd->youngest_rev_cache = new_rev;
8414
8415  SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool));
8416  SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev);
8417  SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev);
8418  SVN_ERR(svn_fs_fs__verify_root(root, pool));
8419#endif /* SVN_DEBUG */
8420
8421  return SVN_NO_ERROR;
8422}
8423
8424/* Update the 'current' file to hold the correct next node and copy_ids
8425   from transaction TXN_ID in filesystem FS.  The current revision is
8426   set to REV.  Perform temporary allocations in POOL. */
8427static svn_error_t *
8428write_final_current(svn_fs_t *fs,
8429                    const char *txn_id,
8430                    svn_revnum_t rev,
8431                    const char *start_node_id,
8432                    const char *start_copy_id,
8433                    apr_pool_t *pool)
8434{
8435  const char *txn_node_id, *txn_copy_id;
8436  char new_node_id[MAX_KEY_SIZE + 2];
8437  char new_copy_id[MAX_KEY_SIZE + 2];
8438  fs_fs_data_t *ffd = fs->fsap_data;
8439
8440  if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8441    return write_current(fs, rev, NULL, NULL, pool);
8442
8443  /* To find the next available ids, we add the id that used to be in
8444     the 'current' file, to the next ids from the transaction file. */
8445  SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool));
8446
8447  svn_fs_fs__add_keys(start_node_id, txn_node_id, new_node_id);
8448  svn_fs_fs__add_keys(start_copy_id, txn_copy_id, new_copy_id);
8449
8450  return write_current(fs, rev, new_node_id, new_copy_id, pool);
8451}
8452
8453/* Verify that the user registed with FS has all the locks necessary to
8454   permit all the changes associate with TXN_NAME.
8455   The FS write lock is assumed to be held by the caller. */
8456static svn_error_t *
8457verify_locks(svn_fs_t *fs,
8458             const char *txn_name,
8459             apr_pool_t *pool)
8460{
8461  apr_pool_t *subpool = svn_pool_create(pool);
8462  apr_hash_t *changes;
8463  apr_hash_index_t *hi;
8464  apr_array_header_t *changed_paths;
8465  svn_stringbuf_t *last_recursed = NULL;
8466  int i;
8467
8468  /* Fetch the changes for this transaction. */
8469  SVN_ERR(svn_fs_fs__txn_changes_fetch(&changes, fs, txn_name, pool));
8470
8471  /* Make an array of the changed paths, and sort them depth-first-ily.  */
8472  changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1,
8473                                 sizeof(const char *));
8474  for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
8475    APR_ARRAY_PUSH(changed_paths, const char *) = svn__apr_hash_index_key(hi);
8476  qsort(changed_paths->elts, changed_paths->nelts,
8477        changed_paths->elt_size, svn_sort_compare_paths);
8478
8479  /* Now, traverse the array of changed paths, verify locks.  Note
8480     that if we need to do a recursive verification a path, we'll skip
8481     over children of that path when we get to them. */
8482  for (i = 0; i < changed_paths->nelts; i++)
8483    {
8484      const char *path;
8485      svn_fs_path_change2_t *change;
8486      svn_boolean_t recurse = TRUE;
8487
8488      svn_pool_clear(subpool);
8489      path = APR_ARRAY_IDX(changed_paths, i, const char *);
8490
8491      /* If this path has already been verified as part of a recursive
8492         check of one of its parents, no need to do it again.  */
8493      if (last_recursed
8494          && svn_dirent_is_child(last_recursed->data, path, subpool))
8495        continue;
8496
8497      /* Fetch the change associated with our path.  */
8498      change = svn_hash_gets(changes, path);
8499
8500      /* What does it mean to succeed at lock verification for a given
8501         path?  For an existing file or directory getting modified
8502         (text, props), it means we hold the lock on the file or
8503         directory.  For paths being added or removed, we need to hold
8504         the locks for that path and any children of that path.
8505
8506         WHEW!  We have no reliable way to determine the node kind
8507         of deleted items, but fortunately we are going to do a
8508         recursive check on deleted paths regardless of their kind.  */
8509      if (change->change_kind == svn_fs_path_change_modify)
8510        recurse = FALSE;
8511      SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE,
8512                                                subpool));
8513
8514      /* If we just did a recursive check, remember the path we
8515         checked (so children can be skipped).  */
8516      if (recurse)
8517        {
8518          if (! last_recursed)
8519            last_recursed = svn_stringbuf_create(path, pool);
8520          else
8521            svn_stringbuf_set(last_recursed, path);
8522        }
8523    }
8524  svn_pool_destroy(subpool);
8525  return SVN_NO_ERROR;
8526}
8527
8528/* Baton used for commit_body below. */
8529struct commit_baton {
8530  svn_revnum_t *new_rev_p;
8531  svn_fs_t *fs;
8532  svn_fs_txn_t *txn;
8533  apr_array_header_t *reps_to_cache;
8534  apr_hash_t *reps_hash;
8535  apr_pool_t *reps_pool;
8536};
8537
8538/* The work-horse for svn_fs_fs__commit, called with the FS write lock.
8539   This implements the svn_fs_fs__with_write_lock() 'body' callback
8540   type.  BATON is a 'struct commit_baton *'. */
8541static svn_error_t *
8542commit_body(void *baton, apr_pool_t *pool)
8543{
8544  struct commit_baton *cb = baton;
8545  fs_fs_data_t *ffd = cb->fs->fsap_data;
8546  const char *old_rev_filename, *rev_filename, *proto_filename;
8547  const char *revprop_filename, *final_revprop;
8548  const svn_fs_id_t *root_id, *new_root_id;
8549  const char *start_node_id = NULL, *start_copy_id = NULL;
8550  svn_revnum_t old_rev, new_rev;
8551  apr_file_t *proto_file;
8552  void *proto_file_lockcookie;
8553  apr_off_t initial_offset, changed_path_offset;
8554  char *buf;
8555  apr_hash_t *txnprops;
8556  apr_array_header_t *txnprop_list;
8557  svn_prop_t prop;
8558  svn_string_t date;
8559
8560  /* Get the current youngest revision. */
8561  SVN_ERR(svn_fs_fs__youngest_rev(&old_rev, cb->fs, pool));
8562
8563  /* Check to make sure this transaction is based off the most recent
8564     revision. */
8565  if (cb->txn->base_rev != old_rev)
8566    return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
8567                            _("Transaction out of date"));
8568
8569  /* Locks may have been added (or stolen) between the calling of
8570     previous svn_fs.h functions and svn_fs_commit_txn(), so we need
8571     to re-examine every changed-path in the txn and re-verify all
8572     discovered locks. */
8573  SVN_ERR(verify_locks(cb->fs, cb->txn->id, pool));
8574
8575  /* Get the next node_id and copy_id to use. */
8576  if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8577    SVN_ERR(get_next_revision_ids(&start_node_id, &start_copy_id, cb->fs,
8578                                  pool));
8579
8580  /* We are going to be one better than this puny old revision. */
8581  new_rev = old_rev + 1;
8582
8583  /* Get a write handle on the proto revision file. */
8584  SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
8585                                 cb->fs, cb->txn->id, pool));
8586  SVN_ERR(get_file_offset(&initial_offset, proto_file, pool));
8587
8588  /* Write out all the node-revisions and directory contents. */
8589  root_id = svn_fs_fs__id_txn_create("0", "0", cb->txn->id, pool);
8590  SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
8591                          start_node_id, start_copy_id, initial_offset,
8592                          cb->reps_to_cache, cb->reps_hash, cb->reps_pool,
8593                          TRUE, pool));
8594
8595  /* Write the changed-path information. */
8596  SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
8597                                        cb->fs, cb->txn->id, pool));
8598
8599  /* Write the final line. */
8600  buf = apr_psprintf(pool, "\n%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
8601                     svn_fs_fs__id_offset(new_root_id),
8602                     changed_path_offset);
8603  SVN_ERR(svn_io_file_write_full(proto_file, buf, strlen(buf), NULL,
8604                                 pool));
8605  SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
8606  SVN_ERR(svn_io_file_close(proto_file, pool));
8607
8608  /* We don't unlock the prototype revision file immediately to avoid a
8609     race with another caller writing to the prototype revision file
8610     before we commit it. */
8611
8612  /* Remove any temporary txn props representing 'flags'. */
8613  SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, cb->txn, pool));
8614  txnprop_list = apr_array_make(pool, 3, sizeof(svn_prop_t));
8615  prop.value = NULL;
8616
8617  if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
8618    {
8619      prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
8620      APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop;
8621    }
8622
8623  if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
8624    {
8625      prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
8626      APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop;
8627    }
8628
8629  if (! apr_is_empty_array(txnprop_list))
8630    SVN_ERR(svn_fs_fs__change_txn_props(cb->txn, txnprop_list, pool));
8631
8632  /* Create the shard for the rev and revprop file, if we're sharding and
8633     this is the first revision of a new shard.  We don't care if this
8634     fails because the shard already existed for some reason. */
8635  if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0)
8636    {
8637      /* Create the revs shard. */
8638        {
8639          const char *new_dir = path_rev_shard(cb->fs, new_rev, pool);
8640          svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
8641          if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
8642            return svn_error_trace(err);
8643          svn_error_clear(err);
8644          SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
8645                                                    PATH_REVS_DIR,
8646                                                    pool),
8647                                    new_dir, pool));
8648        }
8649
8650      /* Create the revprops shard. */
8651      SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev));
8652        {
8653          const char *new_dir = path_revprops_shard(cb->fs, new_rev, pool);
8654          svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
8655          if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
8656            return svn_error_trace(err);
8657          svn_error_clear(err);
8658          SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
8659                                                    PATH_REVPROPS_DIR,
8660                                                    pool),
8661                                    new_dir, pool));
8662        }
8663    }
8664
8665  /* Move the finished rev file into place. */
8666  SVN_ERR(svn_fs_fs__path_rev_absolute(&old_rev_filename,
8667                                       cb->fs, old_rev, pool));
8668  rev_filename = path_rev(cb->fs, new_rev, pool);
8669  proto_filename = path_txn_proto_rev(cb->fs, cb->txn->id, pool);
8670  SVN_ERR(move_into_place(proto_filename, rev_filename, old_rev_filename,
8671                          pool));
8672
8673  /* Now that we've moved the prototype revision file out of the way,
8674     we can unlock it (since further attempts to write to the file
8675     will fail as it no longer exists).  We must do this so that we can
8676     remove the transaction directory later. */
8677  SVN_ERR(unlock_proto_rev(cb->fs, cb->txn->id, proto_file_lockcookie, pool));
8678
8679  /* Update commit time to ensure that svn:date revprops remain ordered. */
8680  date.data = svn_time_to_cstring(apr_time_now(), pool);
8681  date.len = strlen(date.data);
8682
8683  SVN_ERR(svn_fs_fs__change_txn_prop(cb->txn, SVN_PROP_REVISION_DATE,
8684                                     &date, pool));
8685
8686  /* Move the revprops file into place. */
8687  SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev));
8688  revprop_filename = path_txn_props(cb->fs, cb->txn->id, pool);
8689  final_revprop = path_revprops(cb->fs, new_rev, pool);
8690  SVN_ERR(move_into_place(revprop_filename, final_revprop,
8691                          old_rev_filename, pool));
8692
8693  /* Update the 'current' file. */
8694  SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool));
8695  SVN_ERR(write_final_current(cb->fs, cb->txn->id, new_rev, start_node_id,
8696                              start_copy_id, pool));
8697
8698  /* At this point the new revision is committed and globally visible
8699     so let the caller know it succeeded by giving it the new revision
8700     number, which fulfills svn_fs_commit_txn() contract.  Any errors
8701     after this point do not change the fact that a new revision was
8702     created. */
8703  *cb->new_rev_p = new_rev;
8704
8705  ffd->youngest_rev_cache = new_rev;
8706
8707  /* Remove this transaction directory. */
8708  SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool));
8709
8710  return SVN_NO_ERROR;
8711}
8712
8713/* Add the representations in REPS_TO_CACHE (an array of representation_t *)
8714 * to the rep-cache database of FS. */
8715static svn_error_t *
8716write_reps_to_cache(svn_fs_t *fs,
8717                    const apr_array_header_t *reps_to_cache,
8718                    apr_pool_t *scratch_pool)
8719{
8720  int i;
8721
8722  for (i = 0; i < reps_to_cache->nelts; i++)
8723    {
8724      representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *);
8725
8726      /* FALSE because we don't care if another parallel commit happened to
8727       * collide with us.  (Non-parallel collisions will not be detected.) */
8728      SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, FALSE, scratch_pool));
8729    }
8730
8731  return SVN_NO_ERROR;
8732}
8733
8734svn_error_t *
8735svn_fs_fs__commit(svn_revnum_t *new_rev_p,
8736                  svn_fs_t *fs,
8737                  svn_fs_txn_t *txn,
8738                  apr_pool_t *pool)
8739{
8740  struct commit_baton cb;
8741  fs_fs_data_t *ffd = fs->fsap_data;
8742
8743  cb.new_rev_p = new_rev_p;
8744  cb.fs = fs;
8745  cb.txn = txn;
8746
8747  if (ffd->rep_sharing_allowed)
8748    {
8749      cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *));
8750      cb.reps_hash = apr_hash_make(pool);
8751      cb.reps_pool = pool;
8752    }
8753  else
8754    {
8755      cb.reps_to_cache = NULL;
8756      cb.reps_hash = NULL;
8757      cb.reps_pool = NULL;
8758    }
8759
8760  SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool));
8761
8762  /* At this point, *NEW_REV_P has been set, so errors below won't affect
8763     the success of the commit.  (See svn_fs_commit_txn().)  */
8764
8765  if (ffd->rep_sharing_allowed)
8766    {
8767      SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
8768
8769      /* Write new entries to the rep-sharing database.
8770       *
8771       * We use an sqlite transaction to speed things up;
8772       * see <http://www.sqlite.org/faq.html#q19>.
8773       */
8774      SVN_SQLITE__WITH_TXN(
8775        write_reps_to_cache(fs, cb.reps_to_cache, pool),
8776        ffd->rep_cache_db);
8777    }
8778
8779  return SVN_NO_ERROR;
8780}
8781
8782
8783svn_error_t *
8784svn_fs_fs__reserve_copy_id(const char **copy_id_p,
8785                           svn_fs_t *fs,
8786                           const char *txn_id,
8787                           apr_pool_t *pool)
8788{
8789  const char *cur_node_id, *cur_copy_id;
8790  char *copy_id;
8791  apr_size_t len;
8792
8793  /* First read in the current next-ids file. */
8794  SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
8795
8796  copy_id = apr_pcalloc(pool, strlen(cur_copy_id) + 2);
8797
8798  len = strlen(cur_copy_id);
8799  svn_fs_fs__next_key(cur_copy_id, &len, copy_id);
8800
8801  SVN_ERR(write_next_ids(fs, txn_id, cur_node_id, copy_id, pool));
8802
8803  *copy_id_p = apr_pstrcat(pool, "_", cur_copy_id, (char *)NULL);
8804
8805  return SVN_NO_ERROR;
8806}
8807
8808/* Write out the zeroth revision for filesystem FS. */
8809static svn_error_t *
8810write_revision_zero(svn_fs_t *fs)
8811{
8812  const char *path_revision_zero = path_rev(fs, 0, fs->pool);
8813  apr_hash_t *proplist;
8814  svn_string_t date;
8815
8816  /* Write out a rev file for revision 0. */
8817  SVN_ERR(svn_io_file_create(path_revision_zero,
8818                             "PLAIN\nEND\nENDREP\n"
8819                             "id: 0.0.r0/17\n"
8820                             "type: dir\n"
8821                             "count: 0\n"
8822                             "text: 0 0 4 4 "
8823                             "2d2977d1c96f487abe4a1e202dd03b4e\n"
8824                             "cpath: /\n"
8825                             "\n\n17 107\n", fs->pool));
8826  SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, fs->pool));
8827
8828  /* Set a date on revision 0. */
8829  date.data = svn_time_to_cstring(apr_time_now(), fs->pool);
8830  date.len = strlen(date.data);
8831  proplist = apr_hash_make(fs->pool);
8832  svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date);
8833  return set_revision_proplist(fs, 0, proplist, fs->pool);
8834}
8835
8836svn_error_t *
8837svn_fs_fs__create(svn_fs_t *fs,
8838                  const char *path,
8839                  apr_pool_t *pool)
8840{
8841  int format = SVN_FS_FS__FORMAT_NUMBER;
8842  fs_fs_data_t *ffd = fs->fsap_data;
8843
8844  fs->path = apr_pstrdup(pool, path);
8845  /* See if compatibility with older versions was explicitly requested. */
8846  if (fs->config)
8847    {
8848      if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE))
8849        format = 1;
8850      else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE))
8851        format = 2;
8852      else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE))
8853        format = 3;
8854      else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE))
8855        format = 4;
8856    }
8857  ffd->format = format;
8858
8859  /* Override the default linear layout if this is a new-enough format. */
8860  if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
8861    ffd->max_files_per_dir = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
8862
8863  /* Create the revision data directories. */
8864  if (ffd->max_files_per_dir)
8865    SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(fs, 0, pool), pool));
8866  else
8867    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_REVS_DIR,
8868                                                        pool),
8869                                        pool));
8870
8871  /* Create the revprops directory. */
8872  if (ffd->max_files_per_dir)
8873    SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(fs, 0, pool),
8874                                        pool));
8875  else
8876    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path,
8877                                                        PATH_REVPROPS_DIR,
8878                                                        pool),
8879                                        pool));
8880
8881  /* Create the transaction directory. */
8882  SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXNS_DIR,
8883                                                      pool),
8884                                      pool));
8885
8886  /* Create the protorevs directory. */
8887  if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
8888    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXN_PROTOS_DIR,
8889                                                      pool),
8890                                        pool));
8891
8892  /* Create the 'current' file. */
8893  SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(fs, pool),
8894                             (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
8895                              ? "0\n" : "0 1 1\n"),
8896                             pool));
8897  SVN_ERR(svn_io_file_create(path_lock(fs, pool), "", pool));
8898  SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, pool));
8899
8900  SVN_ERR(write_revision_zero(fs));
8901
8902  /* Create the fsfs.conf file if supported.  Older server versions would
8903     simply ignore the file but that might result in a different behavior
8904     than with the later releases.  Also, hotcopy would ignore, i.e. not
8905     copy, a fsfs.conf with old formats. */
8906  if (ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
8907    SVN_ERR(write_config(fs, pool));
8908
8909  SVN_ERR(read_config(ffd, fs->path, pool));
8910
8911  /* Create the min unpacked rev file. */
8912  if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
8913    SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool));
8914
8915  /* Create the txn-current file if the repository supports
8916     the transaction sequence file. */
8917  if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
8918    {
8919      SVN_ERR(svn_io_file_create(path_txn_current(fs, pool),
8920                                 "0\n", pool));
8921      SVN_ERR(svn_io_file_create(path_txn_current_lock(fs, pool),
8922                                 "", pool));
8923    }
8924
8925  /* This filesystem is ready.  Stamp it with a format number. */
8926  SVN_ERR(write_format(path_format(fs, pool),
8927                       ffd->format, ffd->max_files_per_dir, FALSE, pool));
8928
8929  ffd->youngest_rev_cache = 0;
8930  return SVN_NO_ERROR;
8931}
8932
8933/* Part of the recovery procedure.  Return the largest revision *REV in
8934   filesystem FS.  Use POOL for temporary allocation. */
8935static svn_error_t *
8936recover_get_largest_revision(svn_fs_t *fs, svn_revnum_t *rev, apr_pool_t *pool)
8937{
8938  /* Discovering the largest revision in the filesystem would be an
8939     expensive operation if we did a readdir() or searched linearly,
8940     so we'll do a form of binary search.  left is a revision that we
8941     know exists, right a revision that we know does not exist. */
8942  apr_pool_t *iterpool;
8943  svn_revnum_t left, right = 1;
8944
8945  iterpool = svn_pool_create(pool);
8946  /* Keep doubling right, until we find a revision that doesn't exist. */
8947  while (1)
8948    {
8949      svn_error_t *err;
8950      apr_file_t *file;
8951
8952      err = open_pack_or_rev_file(&file, fs, right, iterpool);
8953      svn_pool_clear(iterpool);
8954
8955      if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
8956        {
8957          svn_error_clear(err);
8958          break;
8959        }
8960      else
8961        SVN_ERR(err);
8962
8963      right <<= 1;
8964    }
8965
8966  left = right >> 1;
8967
8968  /* We know that left exists and right doesn't.  Do a normal bsearch to find
8969     the last revision. */
8970  while (left + 1 < right)
8971    {
8972      svn_revnum_t probe = left + ((right - left) / 2);
8973      svn_error_t *err;
8974      apr_file_t *file;
8975
8976      err = open_pack_or_rev_file(&file, fs, probe, iterpool);
8977      svn_pool_clear(iterpool);
8978
8979      if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
8980        {
8981          svn_error_clear(err);
8982          right = probe;
8983        }
8984      else
8985        {
8986          SVN_ERR(err);
8987          left = probe;
8988        }
8989    }
8990
8991  svn_pool_destroy(iterpool);
8992
8993  /* left is now the largest revision that exists. */
8994  *rev = left;
8995  return SVN_NO_ERROR;
8996}
8997
8998/* A baton for reading a fixed amount from an open file.  For
8999   recover_find_max_ids() below. */
9000struct recover_read_from_file_baton
9001{
9002  apr_file_t *file;
9003  apr_pool_t *pool;
9004  apr_off_t remaining;
9005};
9006
9007/* A stream read handler used by recover_find_max_ids() below.
9008   Read and return at most BATON->REMAINING bytes from the stream,
9009   returning nothing after that to indicate EOF. */
9010static svn_error_t *
9011read_handler_recover(void *baton, char *buffer, apr_size_t *len)
9012{
9013  struct recover_read_from_file_baton *b = baton;
9014  svn_filesize_t bytes_to_read = *len;
9015
9016  if (b->remaining == 0)
9017    {
9018      /* Return a successful read of zero bytes to signal EOF. */
9019      *len = 0;
9020      return SVN_NO_ERROR;
9021    }
9022
9023  if (bytes_to_read > b->remaining)
9024    bytes_to_read = b->remaining;
9025  b->remaining -= bytes_to_read;
9026
9027  return svn_io_file_read_full2(b->file, buffer, (apr_size_t) bytes_to_read,
9028                                len, NULL, b->pool);
9029}
9030
9031/* Part of the recovery procedure.  Read the directory noderev at offset
9032   OFFSET of file REV_FILE (the revision file of revision REV of
9033   filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id
9034   and copy-id of that node, if greater than the current value stored
9035   in either.  Recurse into any child directories that were modified in
9036   this revision.
9037
9038   MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE.
9039
9040   Perform temporary allocation in POOL. */
9041static svn_error_t *
9042recover_find_max_ids(svn_fs_t *fs, svn_revnum_t rev,
9043                     apr_file_t *rev_file, apr_off_t offset,
9044                     char *max_node_id, char *max_copy_id,
9045                     apr_pool_t *pool)
9046{
9047  apr_hash_t *headers;
9048  char *value;
9049  representation_t *data_rep;
9050  struct rep_args *ra;
9051  struct recover_read_from_file_baton baton;
9052  svn_stream_t *stream;
9053  apr_hash_t *entries;
9054  apr_hash_index_t *hi;
9055  apr_pool_t *iterpool;
9056
9057  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
9058  SVN_ERR(read_header_block(&headers, svn_stream_from_aprfile2(rev_file, TRUE,
9059                                                               pool),
9060                            pool));
9061
9062  /* Check that this is a directory.  It should be. */
9063  value = svn_hash_gets(headers, HEADER_TYPE);
9064  if (value == NULL || strcmp(value, KIND_DIR) != 0)
9065    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9066                            _("Recovery encountered a non-directory node"));
9067
9068  /* Get the data location.  No data location indicates an empty directory. */
9069  value = svn_hash_gets(headers, HEADER_TEXT);
9070  if (!value)
9071    return SVN_NO_ERROR;
9072  SVN_ERR(read_rep_offsets(&data_rep, value, NULL, FALSE, pool));
9073
9074  /* If the directory's data representation wasn't changed in this revision,
9075     we've already scanned the directory's contents for noderevs, so we don't
9076     need to again.  This will occur if a property is changed on a directory
9077     without changing the directory's contents. */
9078  if (data_rep->revision != rev)
9079    return SVN_NO_ERROR;
9080
9081  /* We could use get_dir_contents(), but this is much cheaper.  It does
9082     rely on directory entries being stored as PLAIN reps, though. */
9083  offset = data_rep->offset;
9084  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
9085  SVN_ERR(read_rep_line(&ra, rev_file, pool));
9086  if (ra->is_delta)
9087    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9088                            _("Recovery encountered a deltified directory "
9089                              "representation"));
9090
9091  /* Now create a stream that's allowed to read only as much data as is
9092     stored in the representation. */
9093  baton.file = rev_file;
9094  baton.pool = pool;
9095  baton.remaining = data_rep->expanded_size
9096                  ? data_rep->expanded_size
9097                  : data_rep->size;
9098  stream = svn_stream_create(&baton, pool);
9099  svn_stream_set_read(stream, read_handler_recover);
9100
9101  /* Now read the entries from that stream. */
9102  entries = apr_hash_make(pool);
9103  SVN_ERR(svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool));
9104  SVN_ERR(svn_stream_close(stream));
9105
9106  /* Now check each of the entries in our directory to find new node and
9107     copy ids, and recurse into new subdirectories. */
9108  iterpool = svn_pool_create(pool);
9109  for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
9110    {
9111      char *str_val;
9112      char *str;
9113      svn_node_kind_t kind;
9114      svn_fs_id_t *id;
9115      const char *node_id, *copy_id;
9116      apr_off_t child_dir_offset;
9117      const svn_string_t *path = svn__apr_hash_index_val(hi);
9118
9119      svn_pool_clear(iterpool);
9120
9121      str_val = apr_pstrdup(iterpool, path->data);
9122
9123      str = svn_cstring_tokenize(" ", &str_val);
9124      if (str == NULL)
9125        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9126                                _("Directory entry corrupt"));
9127
9128      if (strcmp(str, KIND_FILE) == 0)
9129        kind = svn_node_file;
9130      else if (strcmp(str, KIND_DIR) == 0)
9131        kind = svn_node_dir;
9132      else
9133        {
9134          return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9135                                  _("Directory entry corrupt"));
9136        }
9137
9138      str = svn_cstring_tokenize(" ", &str_val);
9139      if (str == NULL)
9140        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9141                                _("Directory entry corrupt"));
9142
9143      id = svn_fs_fs__id_parse(str, strlen(str), iterpool);
9144
9145      if (svn_fs_fs__id_rev(id) != rev)
9146        {
9147          /* If the node wasn't modified in this revision, we've already
9148             checked the node and copy id. */
9149          continue;
9150        }
9151
9152      node_id = svn_fs_fs__id_node_id(id);
9153      copy_id = svn_fs_fs__id_copy_id(id);
9154
9155      if (svn_fs_fs__key_compare(node_id, max_node_id) > 0)
9156        {
9157          SVN_ERR_ASSERT(strlen(node_id) < MAX_KEY_SIZE);
9158          apr_cpystrn(max_node_id, node_id, MAX_KEY_SIZE);
9159        }
9160      if (svn_fs_fs__key_compare(copy_id, max_copy_id) > 0)
9161        {
9162          SVN_ERR_ASSERT(strlen(copy_id) < MAX_KEY_SIZE);
9163          apr_cpystrn(max_copy_id, copy_id, MAX_KEY_SIZE);
9164        }
9165
9166      if (kind == svn_node_file)
9167        continue;
9168
9169      child_dir_offset = svn_fs_fs__id_offset(id);
9170      SVN_ERR(recover_find_max_ids(fs, rev, rev_file, child_dir_offset,
9171                                   max_node_id, max_copy_id, iterpool));
9172    }
9173  svn_pool_destroy(iterpool);
9174
9175  return SVN_NO_ERROR;
9176}
9177
9178/* Return TRUE, if for REVISION in FS, we can find the revprop pack file.
9179 * Use POOL for temporary allocations.
9180 * Set *MISSING, if the reason is a missing manifest or pack file.
9181 */
9182static svn_boolean_t
9183packed_revprop_available(svn_boolean_t *missing,
9184                         svn_fs_t *fs,
9185                         svn_revnum_t revision,
9186                         apr_pool_t *pool)
9187{
9188  fs_fs_data_t *ffd = fs->fsap_data;
9189  svn_stringbuf_t *content = NULL;
9190
9191  /* try to read the manifest file */
9192  const char *folder = path_revprops_pack_shard(fs, revision, pool);
9193  const char *manifest_path = svn_dirent_join(folder, PATH_MANIFEST, pool);
9194
9195  svn_error_t *err = try_stringbuf_from_file(&content,
9196                                             missing,
9197                                             manifest_path,
9198                                             FALSE,
9199                                             pool);
9200
9201  /* if the manifest cannot be read, consider the pack files inaccessible
9202   * even if the file itself exists. */
9203  if (err)
9204    {
9205      svn_error_clear(err);
9206      return FALSE;
9207    }
9208
9209  if (*missing)
9210    return FALSE;
9211
9212  /* parse manifest content until we find the entry for REVISION.
9213   * Revision 0 is never packed. */
9214  revision = revision < ffd->max_files_per_dir
9215           ? revision - 1
9216           : revision % ffd->max_files_per_dir;
9217  while (content->data)
9218    {
9219      char *next = strchr(content->data, '\n');
9220      if (next)
9221        {
9222          *next = 0;
9223          ++next;
9224        }
9225
9226      if (revision-- == 0)
9227        {
9228          /* the respective pack file must exist (and be a file) */
9229          svn_node_kind_t kind;
9230          err = svn_io_check_path(svn_dirent_join(folder, content->data,
9231                                                  pool),
9232                                  &kind, pool);
9233          if (err)
9234            {
9235              svn_error_clear(err);
9236              return FALSE;
9237            }
9238
9239          *missing = kind == svn_node_none;
9240          return kind == svn_node_file;
9241        }
9242
9243      content->data = next;
9244    }
9245
9246  return FALSE;
9247}
9248
9249/* Baton used for recover_body below. */
9250struct recover_baton {
9251  svn_fs_t *fs;
9252  svn_cancel_func_t cancel_func;
9253  void *cancel_baton;
9254};
9255
9256/* The work-horse for svn_fs_fs__recover, called with the FS
9257   write lock.  This implements the svn_fs_fs__with_write_lock()
9258   'body' callback type.  BATON is a 'struct recover_baton *'. */
9259static svn_error_t *
9260recover_body(void *baton, apr_pool_t *pool)
9261{
9262  struct recover_baton *b = baton;
9263  svn_fs_t *fs = b->fs;
9264  fs_fs_data_t *ffd = fs->fsap_data;
9265  svn_revnum_t max_rev;
9266  char next_node_id_buf[MAX_KEY_SIZE], next_copy_id_buf[MAX_KEY_SIZE];
9267  char *next_node_id = NULL, *next_copy_id = NULL;
9268  svn_revnum_t youngest_rev;
9269  svn_node_kind_t youngest_revprops_kind;
9270
9271  /* Lose potentially corrupted data in temp files */
9272  SVN_ERR(cleanup_revprop_namespace(fs));
9273
9274  /* We need to know the largest revision in the filesystem. */
9275  SVN_ERR(recover_get_largest_revision(fs, &max_rev, pool));
9276
9277  /* Get the expected youngest revision */
9278  SVN_ERR(get_youngest(&youngest_rev, fs->path, pool));
9279
9280  /* Policy note:
9281
9282     Since the revprops file is written after the revs file, the true
9283     maximum available revision is the youngest one for which both are
9284     present.  That's probably the same as the max_rev we just found,
9285     but if it's not, we could, in theory, repeatedly decrement
9286     max_rev until we find a revision that has both a revs and
9287     revprops file, then write db/current with that.
9288
9289     But we choose not to.  If a repository is so corrupt that it's
9290     missing at least one revprops file, we shouldn't assume that the
9291     youngest revision for which both the revs and revprops files are
9292     present is healthy.  In other words, we're willing to recover
9293     from a missing or out-of-date db/current file, because db/current
9294     is truly redundant -- it's basically a cache so we don't have to
9295     find max_rev each time, albeit a cache with unusual semantics,
9296     since it also officially defines when a revision goes live.  But
9297     if we're missing more than the cache, it's time to back out and
9298     let the admin reconstruct things by hand: correctness at that
9299     point may depend on external things like checking a commit email
9300     list, looking in particular working copies, etc.
9301
9302     This policy matches well with a typical naive backup scenario.
9303     Say you're rsyncing your FSFS repository nightly to the same
9304     location.  Once revs and revprops are written, you've got the
9305     maximum rev; if the backup should bomb before db/current is
9306     written, then db/current could stay arbitrarily out-of-date, but
9307     we can still recover.  It's a small window, but we might as well
9308     do what we can. */
9309
9310  /* Even if db/current were missing, it would be created with 0 by
9311     get_youngest(), so this conditional remains valid. */
9312  if (youngest_rev > max_rev)
9313    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9314                             _("Expected current rev to be <= %ld "
9315                               "but found %ld"), max_rev, youngest_rev);
9316
9317  /* We only need to search for maximum IDs for old FS formats which
9318     se global ID counters. */
9319  if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
9320    {
9321      /* Next we need to find the maximum node id and copy id in use across the
9322         filesystem.  Unfortunately, the only way we can get this information
9323         is to scan all the noderevs of all the revisions and keep track as
9324         we go along. */
9325      svn_revnum_t rev;
9326      apr_pool_t *iterpool = svn_pool_create(pool);
9327      char max_node_id[MAX_KEY_SIZE] = "0", max_copy_id[MAX_KEY_SIZE] = "0";
9328      apr_size_t len;
9329
9330      for (rev = 0; rev <= max_rev; rev++)
9331        {
9332          apr_file_t *rev_file;
9333          apr_off_t root_offset;
9334
9335          svn_pool_clear(iterpool);
9336
9337          if (b->cancel_func)
9338            SVN_ERR(b->cancel_func(b->cancel_baton));
9339
9340          SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, iterpool));
9341          SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file, fs, rev,
9342                                          iterpool));
9343          SVN_ERR(recover_find_max_ids(fs, rev, rev_file, root_offset,
9344                                       max_node_id, max_copy_id, iterpool));
9345          SVN_ERR(svn_io_file_close(rev_file, iterpool));
9346        }
9347      svn_pool_destroy(iterpool);
9348
9349      /* Now that we finally have the maximum revision, node-id and copy-id, we
9350         can bump the two ids to get the next of each. */
9351      len = strlen(max_node_id);
9352      svn_fs_fs__next_key(max_node_id, &len, next_node_id_buf);
9353      next_node_id = next_node_id_buf;
9354      len = strlen(max_copy_id);
9355      svn_fs_fs__next_key(max_copy_id, &len, next_copy_id_buf);
9356      next_copy_id = next_copy_id_buf;
9357    }
9358
9359  /* Before setting current, verify that there is a revprops file
9360     for the youngest revision.  (Issue #2992) */
9361  SVN_ERR(svn_io_check_path(path_revprops(fs, max_rev, pool),
9362                            &youngest_revprops_kind, pool));
9363  if (youngest_revprops_kind == svn_node_none)
9364    {
9365      svn_boolean_t missing = TRUE;
9366      if (!packed_revprop_available(&missing, fs, max_rev, pool))
9367        {
9368          if (missing)
9369            {
9370              return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9371                                      _("Revision %ld has a revs file but no "
9372                                        "revprops file"),
9373                                      max_rev);
9374            }
9375          else
9376            {
9377              return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9378                                      _("Revision %ld has a revs file but the "
9379                                        "revprops file is inaccessible"),
9380                                      max_rev);
9381            }
9382          }
9383    }
9384  else if (youngest_revprops_kind != svn_node_file)
9385    {
9386      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9387                               _("Revision %ld has a non-file where its "
9388                                 "revprops file should be"),
9389                               max_rev);
9390    }
9391
9392  /* Prune younger-than-(newfound-youngest) revisions from the rep
9393     cache if sharing is enabled taking care not to create the cache
9394     if it does not exist. */
9395  if (ffd->rep_sharing_allowed)
9396    {
9397      svn_boolean_t rep_cache_exists;
9398
9399      SVN_ERR(svn_fs_fs__exists_rep_cache(&rep_cache_exists, fs, pool));
9400      if (rep_cache_exists)
9401        SVN_ERR(svn_fs_fs__del_rep_reference(fs, max_rev, pool));
9402    }
9403
9404  /* Now store the discovered youngest revision, and the next IDs if
9405     relevant, in a new 'current' file. */
9406  return write_current(fs, max_rev, next_node_id, next_copy_id, pool);
9407}
9408
9409/* This implements the fs_library_vtable_t.recover() API. */
9410svn_error_t *
9411svn_fs_fs__recover(svn_fs_t *fs,
9412                   svn_cancel_func_t cancel_func, void *cancel_baton,
9413                   apr_pool_t *pool)
9414{
9415  struct recover_baton b;
9416
9417  /* We have no way to take out an exclusive lock in FSFS, so we're
9418     restricted as to the types of recovery we can do.  Luckily,
9419     we just want to recreate the 'current' file, and we can do that just
9420     by blocking other writers. */
9421  b.fs = fs;
9422  b.cancel_func = cancel_func;
9423  b.cancel_baton = cancel_baton;
9424  return svn_fs_fs__with_write_lock(fs, recover_body, &b, pool);
9425}
9426
9427svn_error_t *
9428svn_fs_fs__set_uuid(svn_fs_t *fs,
9429                    const char *uuid,
9430                    apr_pool_t *pool)
9431{
9432  char *my_uuid;
9433  apr_size_t my_uuid_len;
9434  const char *tmp_path;
9435  const char *uuid_path = path_uuid(fs, pool);
9436
9437  if (! uuid)
9438    uuid = svn_uuid_generate(pool);
9439
9440  /* Make sure we have a copy in FS->POOL, and append a newline. */
9441  my_uuid = apr_pstrcat(fs->pool, uuid, "\n", (char *)NULL);
9442  my_uuid_len = strlen(my_uuid);
9443
9444  SVN_ERR(svn_io_write_unique(&tmp_path,
9445                              svn_dirent_dirname(uuid_path, pool),
9446                              my_uuid, my_uuid_len,
9447                              svn_io_file_del_none, pool));
9448
9449  /* We use the permissions of the 'current' file, because the 'uuid'
9450     file does not exist during repository creation. */
9451  SVN_ERR(move_into_place(tmp_path, uuid_path,
9452                          svn_fs_fs__path_current(fs, pool), pool));
9453
9454  /* Remove the newline we added, and stash the UUID. */
9455  my_uuid[my_uuid_len - 1] = '\0';
9456  fs->uuid = my_uuid;
9457
9458  return SVN_NO_ERROR;
9459}
9460
9461/** Node origin lazy cache. */
9462
9463/* If directory PATH does not exist, create it and give it the same
9464   permissions as FS_path.*/
9465svn_error_t *
9466svn_fs_fs__ensure_dir_exists(const char *path,
9467                             const char *fs_path,
9468                             apr_pool_t *pool)
9469{
9470  svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
9471  if (err && APR_STATUS_IS_EEXIST(err->apr_err))
9472    {
9473      svn_error_clear(err);
9474      return SVN_NO_ERROR;
9475    }
9476  SVN_ERR(err);
9477
9478  /* We successfully created a new directory.  Dup the permissions
9479     from FS->path. */
9480  return svn_io_copy_perms(fs_path, path, pool);
9481}
9482
9483/* Set *NODE_ORIGINS to a hash mapping 'const char *' node IDs to
9484   'svn_string_t *' node revision IDs.  Use POOL for allocations. */
9485static svn_error_t *
9486get_node_origins_from_file(svn_fs_t *fs,
9487                           apr_hash_t **node_origins,
9488                           const char *node_origins_file,
9489                           apr_pool_t *pool)
9490{
9491  apr_file_t *fd;
9492  svn_error_t *err;
9493  svn_stream_t *stream;
9494
9495  *node_origins = NULL;
9496  err = svn_io_file_open(&fd, node_origins_file,
9497                         APR_READ, APR_OS_DEFAULT, pool);
9498  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
9499    {
9500      svn_error_clear(err);
9501      return SVN_NO_ERROR;
9502    }
9503  SVN_ERR(err);
9504
9505  stream = svn_stream_from_aprfile2(fd, FALSE, pool);
9506  *node_origins = apr_hash_make(pool);
9507  SVN_ERR(svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool));
9508  return svn_stream_close(stream);
9509}
9510
9511svn_error_t *
9512svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
9513                           svn_fs_t *fs,
9514                           const char *node_id,
9515                           apr_pool_t *pool)
9516{
9517  apr_hash_t *node_origins;
9518
9519  *origin_id = NULL;
9520  SVN_ERR(get_node_origins_from_file(fs, &node_origins,
9521                                     path_node_origin(fs, node_id, pool),
9522                                     pool));
9523  if (node_origins)
9524    {
9525      svn_string_t *origin_id_str =
9526        svn_hash_gets(node_origins, node_id);
9527      if (origin_id_str)
9528        *origin_id = svn_fs_fs__id_parse(origin_id_str->data,
9529                                         origin_id_str->len, pool);
9530    }
9531  return SVN_NO_ERROR;
9532}
9533
9534
9535/* Helper for svn_fs_fs__set_node_origin.  Takes a NODE_ID/NODE_REV_ID
9536   pair and adds it to the NODE_ORIGINS_PATH file.  */
9537static svn_error_t *
9538set_node_origins_for_file(svn_fs_t *fs,
9539                          const char *node_origins_path,
9540                          const char *node_id,
9541                          svn_string_t *node_rev_id,
9542                          apr_pool_t *pool)
9543{
9544  const char *path_tmp;
9545  svn_stream_t *stream;
9546  apr_hash_t *origins_hash;
9547  svn_string_t *old_node_rev_id;
9548
9549  SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs->path,
9550                                                       PATH_NODE_ORIGINS_DIR,
9551                                                       pool),
9552                                       fs->path, pool));
9553
9554  /* Read the previously existing origins (if any), and merge our
9555     update with it. */
9556  SVN_ERR(get_node_origins_from_file(fs, &origins_hash,
9557                                     node_origins_path, pool));
9558  if (! origins_hash)
9559    origins_hash = apr_hash_make(pool);
9560
9561  old_node_rev_id = svn_hash_gets(origins_hash, node_id);
9562
9563  if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id))
9564    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9565                             _("Node origin for '%s' exists with a different "
9566                               "value (%s) than what we were about to store "
9567                               "(%s)"),
9568                             node_id, old_node_rev_id->data, node_rev_id->data);
9569
9570  svn_hash_sets(origins_hash, node_id, node_rev_id);
9571
9572  /* Sure, there's a race condition here.  Two processes could be
9573     trying to add different cache elements to the same file at the
9574     same time, and the entries added by the first one to write will
9575     be lost.  But this is just a cache of reconstructible data, so
9576     we'll accept this problem in return for not having to deal with
9577     locking overhead. */
9578
9579  /* Create a temporary file, write out our hash, and close the file. */
9580  SVN_ERR(svn_stream_open_unique(&stream, &path_tmp,
9581                                 svn_dirent_dirname(node_origins_path, pool),
9582                                 svn_io_file_del_none, pool, pool));
9583  SVN_ERR(svn_hash_write2(origins_hash, stream, SVN_HASH_TERMINATOR, pool));
9584  SVN_ERR(svn_stream_close(stream));
9585
9586  /* Rename the temp file as the real destination */
9587  return svn_io_file_rename(path_tmp, node_origins_path, pool);
9588}
9589
9590
9591svn_error_t *
9592svn_fs_fs__set_node_origin(svn_fs_t *fs,
9593                           const char *node_id,
9594                           const svn_fs_id_t *node_rev_id,
9595                           apr_pool_t *pool)
9596{
9597  svn_error_t *err;
9598  const char *filename = path_node_origin(fs, node_id, pool);
9599
9600  err = set_node_origins_for_file(fs, filename,
9601                                  node_id,
9602                                  svn_fs_fs__id_unparse(node_rev_id, pool),
9603                                  pool);
9604  if (err && APR_STATUS_IS_EACCES(err->apr_err))
9605    {
9606      /* It's just a cache; stop trying if I can't write. */
9607      svn_error_clear(err);
9608      err = NULL;
9609    }
9610  return svn_error_trace(err);
9611}
9612
9613
9614svn_error_t *
9615svn_fs_fs__list_transactions(apr_array_header_t **names_p,
9616                             svn_fs_t *fs,
9617                             apr_pool_t *pool)
9618{
9619  const char *txn_dir;
9620  apr_hash_t *dirents;
9621  apr_hash_index_t *hi;
9622  apr_array_header_t *names;
9623  apr_size_t ext_len = strlen(PATH_EXT_TXN);
9624
9625  names = apr_array_make(pool, 1, sizeof(const char *));
9626
9627  /* Get the transactions directory. */
9628  txn_dir = svn_dirent_join(fs->path, PATH_TXNS_DIR, pool);
9629
9630  /* Now find a listing of this directory. */
9631  SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool));
9632
9633  /* Loop through all the entries and return anything that ends with '.txn'. */
9634  for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
9635    {
9636      const char *name = svn__apr_hash_index_key(hi);
9637      apr_ssize_t klen = svn__apr_hash_index_klen(hi);
9638      const char *id;
9639
9640      /* The name must end with ".txn" to be considered a transaction. */
9641      if ((apr_size_t) klen <= ext_len
9642          || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0)
9643        continue;
9644
9645      /* Truncate the ".txn" extension and store the ID. */
9646      id = apr_pstrndup(pool, name, strlen(name) - ext_len);
9647      APR_ARRAY_PUSH(names, const char *) = id;
9648    }
9649
9650  *names_p = names;
9651
9652  return SVN_NO_ERROR;
9653}
9654
9655svn_error_t *
9656svn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
9657                    svn_fs_t *fs,
9658                    const char *name,
9659                    apr_pool_t *pool)
9660{
9661  svn_fs_txn_t *txn;
9662  svn_node_kind_t kind;
9663  transaction_t *local_txn;
9664
9665  /* First check to see if the directory exists. */
9666  SVN_ERR(svn_io_check_path(path_txn_dir(fs, name, pool), &kind, pool));
9667
9668  /* Did we find it? */
9669  if (kind != svn_node_dir)
9670    return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL,
9671                             _("No such transaction '%s'"),
9672                             name);
9673
9674  txn = apr_pcalloc(pool, sizeof(*txn));
9675
9676  /* Read in the root node of this transaction. */
9677  txn->id = apr_pstrdup(pool, name);
9678  txn->fs = fs;
9679
9680  SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, name, pool));
9681
9682  txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id);
9683
9684  txn->vtable = &txn_vtable;
9685  *txn_p = txn;
9686
9687  return SVN_NO_ERROR;
9688}
9689
9690svn_error_t *
9691svn_fs_fs__txn_proplist(apr_hash_t **table_p,
9692                        svn_fs_txn_t *txn,
9693                        apr_pool_t *pool)
9694{
9695  apr_hash_t *proplist = apr_hash_make(pool);
9696  SVN_ERR(get_txn_proplist(proplist, txn->fs, txn->id, pool));
9697  *table_p = proplist;
9698
9699  return SVN_NO_ERROR;
9700}
9701
9702svn_error_t *
9703svn_fs_fs__delete_node_revision(svn_fs_t *fs,
9704                                const svn_fs_id_t *id,
9705                                apr_pool_t *pool)
9706{
9707  node_revision_t *noderev;
9708
9709  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
9710
9711  /* Delete any mutable property representation. */
9712  if (noderev->prop_rep && noderev->prop_rep->txn_id)
9713    SVN_ERR(svn_io_remove_file2(path_txn_node_props(fs, id, pool), FALSE,
9714                                pool));
9715
9716  /* Delete any mutable data representation. */
9717  if (noderev->data_rep && noderev->data_rep->txn_id
9718      && noderev->kind == svn_node_dir)
9719    {
9720      fs_fs_data_t *ffd = fs->fsap_data;
9721      SVN_ERR(svn_io_remove_file2(path_txn_node_children(fs, id, pool), FALSE,
9722                                  pool));
9723
9724      /* remove the corresponding entry from the cache, if such exists */
9725      if (ffd->txn_dir_cache)
9726        {
9727          const char *key = svn_fs_fs__id_unparse(id, pool)->data;
9728          SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL, pool));
9729        }
9730    }
9731
9732  return svn_io_remove_file2(path_txn_node_rev(fs, id, pool), FALSE, pool);
9733}
9734
9735
9736
9737/*** Revisions ***/
9738
9739svn_error_t *
9740svn_fs_fs__revision_prop(svn_string_t **value_p,
9741                         svn_fs_t *fs,
9742                         svn_revnum_t rev,
9743                         const char *propname,
9744                         apr_pool_t *pool)
9745{
9746  apr_hash_t *table;
9747
9748  SVN_ERR(svn_fs__check_fs(fs, TRUE));
9749  SVN_ERR(svn_fs_fs__revision_proplist(&table, fs, rev, pool));
9750
9751  *value_p = svn_hash_gets(table, propname);
9752
9753  return SVN_NO_ERROR;
9754}
9755
9756
9757/* Baton used for change_rev_prop_body below. */
9758struct change_rev_prop_baton {
9759  svn_fs_t *fs;
9760  svn_revnum_t rev;
9761  const char *name;
9762  const svn_string_t *const *old_value_p;
9763  const svn_string_t *value;
9764};
9765
9766/* The work-horse for svn_fs_fs__change_rev_prop, called with the FS
9767   write lock.  This implements the svn_fs_fs__with_write_lock()
9768   'body' callback type.  BATON is a 'struct change_rev_prop_baton *'. */
9769static svn_error_t *
9770change_rev_prop_body(void *baton, apr_pool_t *pool)
9771{
9772  struct change_rev_prop_baton *cb = baton;
9773  apr_hash_t *table;
9774
9775  SVN_ERR(svn_fs_fs__revision_proplist(&table, cb->fs, cb->rev, pool));
9776
9777  if (cb->old_value_p)
9778    {
9779      const svn_string_t *wanted_value = *cb->old_value_p;
9780      const svn_string_t *present_value = svn_hash_gets(table, cb->name);
9781      if ((!wanted_value != !present_value)
9782          || (wanted_value && present_value
9783              && !svn_string_compare(wanted_value, present_value)))
9784        {
9785          /* What we expected isn't what we found. */
9786          return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL,
9787                                   _("revprop '%s' has unexpected value in "
9788                                     "filesystem"),
9789                                   cb->name);
9790        }
9791      /* Fall through. */
9792    }
9793  svn_hash_sets(table, cb->name, cb->value);
9794
9795  return set_revision_proplist(cb->fs, cb->rev, table, pool);
9796}
9797
9798svn_error_t *
9799svn_fs_fs__change_rev_prop(svn_fs_t *fs,
9800                           svn_revnum_t rev,
9801                           const char *name,
9802                           const svn_string_t *const *old_value_p,
9803                           const svn_string_t *value,
9804                           apr_pool_t *pool)
9805{
9806  struct change_rev_prop_baton cb;
9807
9808  SVN_ERR(svn_fs__check_fs(fs, TRUE));
9809
9810  cb.fs = fs;
9811  cb.rev = rev;
9812  cb.name = name;
9813  cb.old_value_p = old_value_p;
9814  cb.value = value;
9815
9816  return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool);
9817}
9818
9819
9820
9821/*** Transactions ***/
9822
9823svn_error_t *
9824svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
9825                       const svn_fs_id_t **base_root_id_p,
9826                       svn_fs_t *fs,
9827                       const char *txn_name,
9828                       apr_pool_t *pool)
9829{
9830  transaction_t *txn;
9831  SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_name, pool));
9832  *root_id_p = txn->root_id;
9833  *base_root_id_p = txn->base_id;
9834  return SVN_NO_ERROR;
9835}
9836
9837
9838/* Generic transaction operations.  */
9839
9840svn_error_t *
9841svn_fs_fs__txn_prop(svn_string_t **value_p,
9842                    svn_fs_txn_t *txn,
9843                    const char *propname,
9844                    apr_pool_t *pool)
9845{
9846  apr_hash_t *table;
9847  svn_fs_t *fs = txn->fs;
9848
9849  SVN_ERR(svn_fs__check_fs(fs, TRUE));
9850  SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool));
9851
9852  *value_p = svn_hash_gets(table, propname);
9853
9854  return SVN_NO_ERROR;
9855}
9856
9857svn_error_t *
9858svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
9859                     svn_fs_t *fs,
9860                     svn_revnum_t rev,
9861                     apr_uint32_t flags,
9862                     apr_pool_t *pool)
9863{
9864  svn_string_t date;
9865  svn_prop_t prop;
9866  apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t));
9867
9868  SVN_ERR(svn_fs__check_fs(fs, TRUE));
9869
9870  SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool));
9871
9872  /* Put a datestamp on the newly created txn, so we always know
9873     exactly how old it is.  (This will help sysadmins identify
9874     long-abandoned txns that may need to be manually removed.)  When
9875     a txn is promoted to a revision, this property will be
9876     automatically overwritten with a revision datestamp. */
9877  date.data = svn_time_to_cstring(apr_time_now(), pool);
9878  date.len = strlen(date.data);
9879
9880  prop.name = SVN_PROP_REVISION_DATE;
9881  prop.value = &date;
9882  APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9883
9884  /* Set temporary txn props that represent the requested 'flags'
9885     behaviors. */
9886  if (flags & SVN_FS_TXN_CHECK_OOD)
9887    {
9888      prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
9889      prop.value = svn_string_create("true", pool);
9890      APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9891    }
9892
9893  if (flags & SVN_FS_TXN_CHECK_LOCKS)
9894    {
9895      prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
9896      prop.value = svn_string_create("true", pool);
9897      APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9898    }
9899
9900  return svn_fs_fs__change_txn_props(*txn_p, props, pool);
9901}
9902
9903
9904/****** Packing FSFS shards *********/
9905
9906/* Write a file FILENAME in directory FS_PATH, containing a single line
9907 * with the number REVNUM in ASCII decimal.  Move the file into place
9908 * atomically, overwriting any existing file.
9909 *
9910 * Similar to write_current(). */
9911static svn_error_t *
9912write_revnum_file(const char *fs_path,
9913                  const char *filename,
9914                  svn_revnum_t revnum,
9915                  apr_pool_t *scratch_pool)
9916{
9917  const char *final_path, *tmp_path;
9918  svn_stream_t *tmp_stream;
9919
9920  final_path = svn_dirent_join(fs_path, filename, scratch_pool);
9921  SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_path, fs_path,
9922                                   svn_io_file_del_none,
9923                                   scratch_pool, scratch_pool));
9924  SVN_ERR(svn_stream_printf(tmp_stream, scratch_pool, "%ld\n", revnum));
9925  SVN_ERR(svn_stream_close(tmp_stream));
9926  SVN_ERR(move_into_place(tmp_path, final_path, final_path, scratch_pool));
9927  return SVN_NO_ERROR;
9928}
9929
9930/* Pack the revision SHARD containing exactly MAX_FILES_PER_DIR revisions
9931 * from SHARD_PATH into the PACK_FILE_DIR, using POOL for allocations.
9932 * CANCEL_FUNC and CANCEL_BATON are what you think they are.
9933 *
9934 * If for some reason we detect a partial packing already performed, we
9935 * remove the pack file and start again.
9936 */
9937static svn_error_t *
9938pack_rev_shard(const char *pack_file_dir,
9939               const char *shard_path,
9940               apr_int64_t shard,
9941               int max_files_per_dir,
9942               svn_cancel_func_t cancel_func,
9943               void *cancel_baton,
9944               apr_pool_t *pool)
9945{
9946  const char *pack_file_path, *manifest_file_path;
9947  svn_stream_t *pack_stream, *manifest_stream;
9948  svn_revnum_t start_rev, end_rev, rev;
9949  apr_off_t next_offset;
9950  apr_pool_t *iterpool;
9951
9952  /* Some useful paths. */
9953  pack_file_path = svn_dirent_join(pack_file_dir, PATH_PACKED, pool);
9954  manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST, pool);
9955
9956  /* Remove any existing pack file for this shard, since it is incomplete. */
9957  SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
9958                             pool));
9959
9960  /* Create the new directory and pack and manifest files. */
9961  SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, pool));
9962  SVN_ERR(svn_stream_open_writable(&pack_stream, pack_file_path, pool,
9963                                    pool));
9964  SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
9965                                   pool, pool));
9966
9967  start_rev = (svn_revnum_t) (shard * max_files_per_dir);
9968  end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
9969  next_offset = 0;
9970  iterpool = svn_pool_create(pool);
9971
9972  /* Iterate over the revisions in this shard, squashing them together. */
9973  for (rev = start_rev; rev <= end_rev; rev++)
9974    {
9975      svn_stream_t *rev_stream;
9976      apr_finfo_t finfo;
9977      const char *path;
9978
9979      svn_pool_clear(iterpool);
9980
9981      /* Get the size of the file. */
9982      path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
9983                             iterpool);
9984      SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
9985
9986      /* Update the manifest. */
9987      SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%" APR_OFF_T_FMT
9988                                "\n", next_offset));
9989      next_offset += finfo.size;
9990
9991      /* Copy all the bits from the rev file to the end of the pack file. */
9992      SVN_ERR(svn_stream_open_readonly(&rev_stream, path, iterpool, iterpool));
9993      SVN_ERR(svn_stream_copy3(rev_stream, svn_stream_disown(pack_stream,
9994                                                             iterpool),
9995                          cancel_func, cancel_baton, iterpool));
9996    }
9997
9998  SVN_ERR(svn_stream_close(manifest_stream));
9999  SVN_ERR(svn_stream_close(pack_stream));
10000  SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
10001  SVN_ERR(svn_io_set_file_read_only(pack_file_path, FALSE, iterpool));
10002  SVN_ERR(svn_io_set_file_read_only(manifest_file_path, FALSE, iterpool));
10003
10004  svn_pool_destroy(iterpool);
10005
10006  return SVN_NO_ERROR;
10007}
10008
10009/* Copy revprop files for revisions [START_REV, END_REV) from SHARD_PATH
10010 * to the pack file at PACK_FILE_NAME in PACK_FILE_DIR.
10011 *
10012 * The file sizes have already been determined and written to SIZES.
10013 * Please note that this function will be executed while the filesystem
10014 * has been locked and that revprops files will therefore not be modified
10015 * while the pack is in progress.
10016 *
10017 * COMPRESSION_LEVEL defines how well the resulting pack file shall be
10018 * compressed or whether is shall be compressed at all.  TOTAL_SIZE is
10019 * a hint on which initial buffer size we should use to hold the pack file
10020 * content.
10021 *
10022 * CANCEL_FUNC and CANCEL_BATON are used as usual. Temporary allocations
10023 * are done in SCRATCH_POOL.
10024 */
10025static svn_error_t *
10026copy_revprops(const char *pack_file_dir,
10027              const char *pack_filename,
10028              const char *shard_path,
10029              svn_revnum_t start_rev,
10030              svn_revnum_t end_rev,
10031              apr_array_header_t *sizes,
10032              apr_size_t total_size,
10033              int compression_level,
10034              svn_cancel_func_t cancel_func,
10035              void *cancel_baton,
10036              apr_pool_t *scratch_pool)
10037{
10038  svn_stream_t *pack_stream;
10039  apr_file_t *pack_file;
10040  svn_revnum_t rev;
10041  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10042  svn_stream_t *stream;
10043
10044  /* create empty data buffer and a write stream on top of it */
10045  svn_stringbuf_t *uncompressed
10046    = svn_stringbuf_create_ensure(total_size, scratch_pool);
10047  svn_stringbuf_t *compressed
10048    = svn_stringbuf_create_empty(scratch_pool);
10049  pack_stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
10050
10051  /* write the pack file header */
10052  SVN_ERR(serialize_revprops_header(pack_stream, start_rev, sizes, 0,
10053                                    sizes->nelts, iterpool));
10054
10055  /* Some useful paths. */
10056  SVN_ERR(svn_io_file_open(&pack_file, svn_dirent_join(pack_file_dir,
10057                                                       pack_filename,
10058                                                       scratch_pool),
10059                           APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
10060                           scratch_pool));
10061
10062  /* Iterate over the revisions in this shard, squashing them together. */
10063  for (rev = start_rev; rev <= end_rev; rev++)
10064    {
10065      const char *path;
10066
10067      svn_pool_clear(iterpool);
10068
10069      /* Construct the file name. */
10070      path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
10071                             iterpool);
10072
10073      /* Copy all the bits from the non-packed revprop file to the end of
10074       * the pack file. */
10075      SVN_ERR(svn_stream_open_readonly(&stream, path, iterpool, iterpool));
10076      SVN_ERR(svn_stream_copy3(stream, pack_stream,
10077                               cancel_func, cancel_baton, iterpool));
10078    }
10079
10080  /* flush stream buffers to content buffer */
10081  SVN_ERR(svn_stream_close(pack_stream));
10082
10083  /* compress the content (or just store it for COMPRESSION_LEVEL 0) */
10084  SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed),
10085                        compressed, compression_level));
10086
10087  /* write the pack file content to disk */
10088  stream = svn_stream_from_aprfile2(pack_file, FALSE, scratch_pool);
10089  SVN_ERR(svn_stream_write(stream, compressed->data, &compressed->len));
10090  SVN_ERR(svn_stream_close(stream));
10091
10092  svn_pool_destroy(iterpool);
10093
10094  return SVN_NO_ERROR;
10095}
10096
10097/* For the revprop SHARD at SHARD_PATH with exactly MAX_FILES_PER_DIR
10098 * revprop files in it, create a packed shared at PACK_FILE_DIR.
10099 *
10100 * COMPRESSION_LEVEL defines how well the resulting pack file shall be
10101 * compressed or whether is shall be compressed at all.  Individual pack
10102 * file containing more than one revision will be limited to a size of
10103 * MAX_PACK_SIZE bytes before compression.
10104 *
10105 * CANCEL_FUNC and CANCEL_BATON are used in the usual way.  Temporary
10106 * allocations are done in SCRATCH_POOL.
10107 */
10108static svn_error_t *
10109pack_revprops_shard(const char *pack_file_dir,
10110                    const char *shard_path,
10111                    apr_int64_t shard,
10112                    int max_files_per_dir,
10113                    apr_off_t max_pack_size,
10114                    int compression_level,
10115                    svn_cancel_func_t cancel_func,
10116                    void *cancel_baton,
10117                    apr_pool_t *scratch_pool)
10118{
10119  const char *manifest_file_path, *pack_filename = NULL;
10120  svn_stream_t *manifest_stream;
10121  svn_revnum_t start_rev, end_rev, rev;
10122  apr_off_t total_size;
10123  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10124  apr_array_header_t *sizes;
10125
10126  /* Some useful paths. */
10127  manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST,
10128                                       scratch_pool);
10129
10130  /* Remove any existing pack file for this shard, since it is incomplete. */
10131  SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
10132                             scratch_pool));
10133
10134  /* Create the new directory and manifest file stream. */
10135  SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, scratch_pool));
10136  SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
10137                                   scratch_pool, scratch_pool));
10138
10139  /* revisions to handle. Special case: revision 0 */
10140  start_rev = (svn_revnum_t) (shard * max_files_per_dir);
10141  end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
10142  if (start_rev == 0)
10143    ++start_rev;
10144
10145  /* initialize the revprop size info */
10146  sizes = apr_array_make(scratch_pool, max_files_per_dir, sizeof(apr_off_t));
10147  total_size = 2 * SVN_INT64_BUFFER_SIZE;
10148
10149  /* Iterate over the revisions in this shard, determine their size and
10150   * squashing them together into pack files. */
10151  for (rev = start_rev; rev <= end_rev; rev++)
10152    {
10153      apr_finfo_t finfo;
10154      const char *path;
10155
10156      svn_pool_clear(iterpool);
10157
10158      /* Get the size of the file. */
10159      path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
10160                             iterpool);
10161      SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
10162
10163      /* if we already have started a pack file and this revprop cannot be
10164       * appended to it, write the previous pack file. */
10165      if (sizes->nelts != 0 &&
10166          total_size + SVN_INT64_BUFFER_SIZE + finfo.size > max_pack_size)
10167        {
10168          SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
10169                                start_rev, rev-1, sizes, (apr_size_t)total_size,
10170                                compression_level, cancel_func, cancel_baton,
10171                                iterpool));
10172
10173          /* next pack file starts empty again */
10174          apr_array_clear(sizes);
10175          total_size = 2 * SVN_INT64_BUFFER_SIZE;
10176          start_rev = rev;
10177        }
10178
10179      /* Update the manifest. Allocate a file name for the current pack
10180       * file if it is a new one */
10181      if (sizes->nelts == 0)
10182        pack_filename = apr_psprintf(scratch_pool, "%ld.0", rev);
10183
10184      SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%s\n",
10185                                pack_filename));
10186
10187      /* add to list of files to put into the current pack file */
10188      APR_ARRAY_PUSH(sizes, apr_off_t) = finfo.size;
10189      total_size += SVN_INT64_BUFFER_SIZE + finfo.size;
10190    }
10191
10192  /* write the last pack file */
10193  if (sizes->nelts != 0)
10194    SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
10195                          start_rev, rev-1, sizes, (apr_size_t)total_size,
10196                          compression_level, cancel_func, cancel_baton,
10197                          iterpool));
10198
10199  /* flush the manifest file and update permissions */
10200  SVN_ERR(svn_stream_close(manifest_stream));
10201  SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
10202
10203  svn_pool_destroy(iterpool);
10204
10205  return SVN_NO_ERROR;
10206}
10207
10208/* Delete the non-packed revprop SHARD at SHARD_PATH with exactly
10209 * MAX_FILES_PER_DIR revprop files in it.  If this is shard 0, keep the
10210 * revprop file for revision 0.
10211 *
10212 * CANCEL_FUNC and CANCEL_BATON are used in the usual way.  Temporary
10213 * allocations are done in SCRATCH_POOL.
10214 */
10215static svn_error_t *
10216delete_revprops_shard(const char *shard_path,
10217                      apr_int64_t shard,
10218                      int max_files_per_dir,
10219                      svn_cancel_func_t cancel_func,
10220                      void *cancel_baton,
10221                      apr_pool_t *scratch_pool)
10222{
10223  if (shard == 0)
10224    {
10225      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10226      int i;
10227
10228      /* delete all files except the one for revision 0 */
10229      for (i = 1; i < max_files_per_dir; ++i)
10230        {
10231          const char *path = svn_dirent_join(shard_path,
10232                                       apr_psprintf(iterpool, "%d", i),
10233                                       iterpool);
10234          if (cancel_func)
10235            SVN_ERR((*cancel_func)(cancel_baton));
10236
10237          SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
10238          svn_pool_clear(iterpool);
10239        }
10240
10241      svn_pool_destroy(iterpool);
10242    }
10243  else
10244    SVN_ERR(svn_io_remove_dir2(shard_path, TRUE,
10245                               cancel_func, cancel_baton, scratch_pool));
10246
10247  return SVN_NO_ERROR;
10248}
10249
10250/* In the file system at FS_PATH, pack the SHARD in REVS_DIR and
10251 * REVPROPS_DIR containing exactly MAX_FILES_PER_DIR revisions, using POOL
10252 * for allocations.  REVPROPS_DIR will be NULL if revprop packing is not
10253 * supported.  COMPRESSION_LEVEL and MAX_PACK_SIZE will be ignored in that
10254 * case.
10255 *
10256 * CANCEL_FUNC and CANCEL_BATON are what you think they are; similarly
10257 * NOTIFY_FUNC and NOTIFY_BATON.
10258 *
10259 * If for some reason we detect a partial packing already performed, we
10260 * remove the pack file and start again.
10261 */
10262static svn_error_t *
10263pack_shard(const char *revs_dir,
10264           const char *revsprops_dir,
10265           const char *fs_path,
10266           apr_int64_t shard,
10267           int max_files_per_dir,
10268           apr_off_t max_pack_size,
10269           int compression_level,
10270           svn_fs_pack_notify_t notify_func,
10271           void *notify_baton,
10272           svn_cancel_func_t cancel_func,
10273           void *cancel_baton,
10274           apr_pool_t *pool)
10275{
10276  const char *rev_shard_path, *rev_pack_file_dir;
10277  const char *revprops_shard_path, *revprops_pack_file_dir;
10278
10279  /* Notify caller we're starting to pack this shard. */
10280  if (notify_func)
10281    SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_start,
10282                        pool));
10283
10284  /* Some useful paths. */
10285  rev_pack_file_dir = svn_dirent_join(revs_dir,
10286                  apr_psprintf(pool,
10287                               "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
10288                               shard),
10289                  pool);
10290  rev_shard_path = svn_dirent_join(revs_dir,
10291                           apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
10292                           pool);
10293
10294  /* pack the revision content */
10295  SVN_ERR(pack_rev_shard(rev_pack_file_dir, rev_shard_path,
10296                         shard, max_files_per_dir,
10297                         cancel_func, cancel_baton, pool));
10298
10299  /* if enabled, pack the revprops in an equivalent way */
10300  if (revsprops_dir)
10301    {
10302      revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
10303                   apr_psprintf(pool,
10304                                "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
10305                                shard),
10306                   pool);
10307      revprops_shard_path = svn_dirent_join(revsprops_dir,
10308                           apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
10309                           pool);
10310
10311      SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path,
10312                                  shard, max_files_per_dir,
10313                                  (int)(0.9 * max_pack_size),
10314                                  compression_level,
10315                                  cancel_func, cancel_baton, pool));
10316    }
10317
10318  /* Update the min-unpacked-rev file to reflect our newly packed shard.
10319   * (This doesn't update ffd->min_unpacked_rev.  That will be updated by
10320   * update_min_unpacked_rev() when necessary.) */
10321  SVN_ERR(write_revnum_file(fs_path, PATH_MIN_UNPACKED_REV,
10322                            (svn_revnum_t)((shard + 1) * max_files_per_dir),
10323                            pool));
10324
10325  /* Finally, remove the existing shard directories. */
10326  SVN_ERR(svn_io_remove_dir2(rev_shard_path, TRUE,
10327                             cancel_func, cancel_baton, pool));
10328  if (revsprops_dir)
10329    SVN_ERR(delete_revprops_shard(revprops_shard_path,
10330                                  shard, max_files_per_dir,
10331                                  cancel_func, cancel_baton, pool));
10332
10333  /* Notify caller we're starting to pack this shard. */
10334  if (notify_func)
10335    SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_end,
10336                        pool));
10337
10338  return SVN_NO_ERROR;
10339}
10340
10341struct pack_baton
10342{
10343  svn_fs_t *fs;
10344  svn_fs_pack_notify_t notify_func;
10345  void *notify_baton;
10346  svn_cancel_func_t cancel_func;
10347  void *cancel_baton;
10348};
10349
10350
10351/* The work-horse for svn_fs_fs__pack, called with the FS write lock.
10352   This implements the svn_fs_fs__with_write_lock() 'body' callback
10353   type.  BATON is a 'struct pack_baton *'.
10354
10355   WARNING: if you add a call to this function, please note:
10356     The code currently assumes that any piece of code running with
10357     the write-lock set can rely on the ffd->min_unpacked_rev and
10358     ffd->min_unpacked_revprop caches to be up-to-date (and, by
10359     extension, on not having to use a retry when calling
10360     svn_fs_fs__path_rev_absolute() and friends).  If you add a call
10361     to this function, consider whether you have to call
10362     update_min_unpacked_rev().
10363     See this thread: http://thread.gmane.org/1291206765.3782.3309.camel@edith
10364 */
10365static svn_error_t *
10366pack_body(void *baton,
10367          apr_pool_t *pool)
10368{
10369  struct pack_baton *pb = baton;
10370  fs_fs_data_t ffd = {0};
10371  apr_int64_t completed_shards;
10372  apr_int64_t i;
10373  svn_revnum_t youngest;
10374  apr_pool_t *iterpool;
10375  const char *rev_data_path;
10376  const char *revprops_data_path = NULL;
10377
10378  /* read repository settings */
10379  SVN_ERR(read_format(&ffd.format, &ffd.max_files_per_dir,
10380                      path_format(pb->fs, pool), pool));
10381  SVN_ERR(check_format(ffd.format));
10382  SVN_ERR(read_config(&ffd, pb->fs->path, pool));
10383
10384  /* If the repository isn't a new enough format, we don't support packing.
10385     Return a friendly error to that effect. */
10386  if (ffd.format < SVN_FS_FS__MIN_PACKED_FORMAT)
10387    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
10388      _("FSFS format (%d) too old to pack; please upgrade the filesystem."),
10389      ffd.format);
10390
10391  /* If we aren't using sharding, we can't do any packing, so quit. */
10392  if (!ffd.max_files_per_dir)
10393    return SVN_NO_ERROR;
10394
10395  SVN_ERR(read_min_unpacked_rev(&ffd.min_unpacked_rev,
10396                                path_min_unpacked_rev(pb->fs, pool),
10397                                pool));
10398
10399  SVN_ERR(get_youngest(&youngest, pb->fs->path, pool));
10400  completed_shards = (youngest + 1) / ffd.max_files_per_dir;
10401
10402  /* See if we've already completed all possible shards thus far. */
10403  if (ffd.min_unpacked_rev == (completed_shards * ffd.max_files_per_dir))
10404    return SVN_NO_ERROR;
10405
10406  rev_data_path = svn_dirent_join(pb->fs->path, PATH_REVS_DIR, pool);
10407  if (ffd.format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
10408    revprops_data_path = svn_dirent_join(pb->fs->path, PATH_REVPROPS_DIR,
10409                                         pool);
10410
10411  iterpool = svn_pool_create(pool);
10412  for (i = ffd.min_unpacked_rev / ffd.max_files_per_dir;
10413       i < completed_shards;
10414       i++)
10415    {
10416      svn_pool_clear(iterpool);
10417
10418      if (pb->cancel_func)
10419        SVN_ERR(pb->cancel_func(pb->cancel_baton));
10420
10421      SVN_ERR(pack_shard(rev_data_path, revprops_data_path,
10422                         pb->fs->path, i, ffd.max_files_per_dir,
10423                         ffd.revprop_pack_size,
10424                         ffd.compress_packed_revprops
10425                           ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
10426                           : SVN_DELTA_COMPRESSION_LEVEL_NONE,
10427                         pb->notify_func, pb->notify_baton,
10428                         pb->cancel_func, pb->cancel_baton, iterpool));
10429    }
10430
10431  svn_pool_destroy(iterpool);
10432  return SVN_NO_ERROR;
10433}
10434
10435svn_error_t *
10436svn_fs_fs__pack(svn_fs_t *fs,
10437                svn_fs_pack_notify_t notify_func,
10438                void *notify_baton,
10439                svn_cancel_func_t cancel_func,
10440                void *cancel_baton,
10441                apr_pool_t *pool)
10442{
10443  struct pack_baton pb = { 0 };
10444  pb.fs = fs;
10445  pb.notify_func = notify_func;
10446  pb.notify_baton = notify_baton;
10447  pb.cancel_func = cancel_func;
10448  pb.cancel_baton = cancel_baton;
10449  return svn_fs_fs__with_write_lock(fs, pack_body, &pb, pool);
10450}
10451
10452
10453/** Verifying. **/
10454
10455/* Baton type expected by verify_walker().  The purpose is to reuse open
10456 * rev / pack file handles between calls.  Its contents need to be cleaned
10457 * periodically to limit resource usage.
10458 */
10459typedef struct verify_walker_baton_t
10460{
10461  /* number of calls to verify_walker() since the last clean */
10462  int iteration_count;
10463
10464  /* number of files opened since the last clean */
10465  int file_count;
10466
10467  /* progress notification callback to invoke periodically (may be NULL) */
10468  svn_fs_progress_notify_func_t notify_func;
10469
10470  /* baton to use with NOTIFY_FUNC */
10471  void *notify_baton;
10472
10473  /* remember the last revision for which we called notify_func */
10474  svn_revnum_t last_notified_revision;
10475
10476  /* current file handle (or NULL) */
10477  apr_file_t *file_hint;
10478
10479  /* corresponding revision (or SVN_INVALID_REVNUM) */
10480  svn_revnum_t rev_hint;
10481
10482  /* pool to use for the file handles etc. */
10483  apr_pool_t *pool;
10484} verify_walker_baton_t;
10485
10486/* Used by svn_fs_fs__verify().
10487   Implements svn_fs_fs__walk_rep_reference().walker.  */
10488static svn_error_t *
10489verify_walker(representation_t *rep,
10490              void *baton,
10491              svn_fs_t *fs,
10492              apr_pool_t *scratch_pool)
10493{
10494  struct rep_state *rs;
10495  struct rep_args *rep_args;
10496
10497  if (baton)
10498    {
10499      verify_walker_baton_t *walker_baton = baton;
10500      apr_file_t * previous_file;
10501
10502      /* notify and free resources periodically */
10503      if (   walker_baton->iteration_count > 1000
10504          || walker_baton->file_count > 16)
10505        {
10506          if (   walker_baton->notify_func
10507              && rep->revision != walker_baton->last_notified_revision)
10508            {
10509              walker_baton->notify_func(rep->revision,
10510                                        walker_baton->notify_baton,
10511                                        scratch_pool);
10512              walker_baton->last_notified_revision = rep->revision;
10513            }
10514
10515          svn_pool_clear(walker_baton->pool);
10516
10517          walker_baton->iteration_count = 0;
10518          walker_baton->file_count = 0;
10519          walker_baton->file_hint = NULL;
10520          walker_baton->rev_hint = SVN_INVALID_REVNUM;
10521        }
10522
10523      /* access the repo data */
10524      previous_file = walker_baton->file_hint;
10525      SVN_ERR(create_rep_state(&rs, &rep_args, &walker_baton->file_hint,
10526                               &walker_baton->rev_hint, rep, fs,
10527                               walker_baton->pool));
10528
10529      /* update resource usage counters */
10530      walker_baton->iteration_count++;
10531      if (previous_file != walker_baton->file_hint)
10532        walker_baton->file_count++;
10533    }
10534  else
10535    {
10536      /* ### Should this be using read_rep_line() directly? */
10537      SVN_ERR(create_rep_state(&rs, &rep_args, NULL, NULL, rep, fs,
10538                               scratch_pool));
10539    }
10540
10541  return SVN_NO_ERROR;
10542}
10543
10544svn_error_t *
10545svn_fs_fs__verify(svn_fs_t *fs,
10546                  svn_revnum_t start,
10547                  svn_revnum_t end,
10548                  svn_fs_progress_notify_func_t notify_func,
10549                  void *notify_baton,
10550                  svn_cancel_func_t cancel_func,
10551                  void *cancel_baton,
10552                  apr_pool_t *pool)
10553{
10554  fs_fs_data_t *ffd = fs->fsap_data;
10555  svn_boolean_t exists;
10556  svn_revnum_t youngest = ffd->youngest_rev_cache; /* cache is current */
10557
10558  if (ffd->format < SVN_FS_FS__MIN_REP_SHARING_FORMAT)
10559    return SVN_NO_ERROR;
10560
10561  /* Input validation. */
10562  if (! SVN_IS_VALID_REVNUM(start))
10563    start = 0;
10564  if (! SVN_IS_VALID_REVNUM(end))
10565    end = youngest;
10566  SVN_ERR(ensure_revision_exists(fs, start, pool));
10567  SVN_ERR(ensure_revision_exists(fs, end, pool));
10568
10569  /* rep-cache verification. */
10570  SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool));
10571  if (exists)
10572    {
10573      /* provide a baton to allow the reuse of open file handles between
10574         iterations (saves 2/3 of OS level file operations). */
10575      verify_walker_baton_t *baton = apr_pcalloc(pool, sizeof(*baton));
10576      baton->rev_hint = SVN_INVALID_REVNUM;
10577      baton->pool = svn_pool_create(pool);
10578      baton->last_notified_revision = SVN_INVALID_REVNUM;
10579      baton->notify_func = notify_func;
10580      baton->notify_baton = notify_baton;
10581
10582      /* tell the user that we are now ready to do *something* */
10583      if (notify_func)
10584        notify_func(SVN_INVALID_REVNUM, notify_baton, baton->pool);
10585
10586      /* Do not attempt to walk the rep-cache database if its file does
10587         not exist,  since doing so would create it --- which may confuse
10588         the administrator.   Don't take any lock. */
10589      SVN_ERR(svn_fs_fs__walk_rep_reference(fs, start, end,
10590                                            verify_walker, baton,
10591                                            cancel_func, cancel_baton,
10592                                            pool));
10593
10594      /* walker resource cleanup */
10595      svn_pool_destroy(baton->pool);
10596    }
10597
10598  return SVN_NO_ERROR;
10599}
10600
10601
10602/** Hotcopy. **/
10603
10604/* Like svn_io_dir_file_copy(), but doesn't copy files that exist at
10605 * the destination and do not differ in terms of kind, size, and mtime. */
10606static svn_error_t *
10607hotcopy_io_dir_file_copy(const char *src_path,
10608                         const char *dst_path,
10609                         const char *file,
10610                         apr_pool_t *scratch_pool)
10611{
10612  const svn_io_dirent2_t *src_dirent;
10613  const svn_io_dirent2_t *dst_dirent;
10614  const char *src_target;
10615  const char *dst_target;
10616
10617  /* Does the destination already exist? If not, we must copy it. */
10618  dst_target = svn_dirent_join(dst_path, file, scratch_pool);
10619  SVN_ERR(svn_io_stat_dirent2(&dst_dirent, dst_target, FALSE, TRUE,
10620                              scratch_pool, scratch_pool));
10621  if (dst_dirent->kind != svn_node_none)
10622    {
10623      /* If the destination's stat information indicates that the file
10624       * is equal to the source, don't bother copying the file again. */
10625      src_target = svn_dirent_join(src_path, file, scratch_pool);
10626      SVN_ERR(svn_io_stat_dirent2(&src_dirent, src_target, FALSE, FALSE,
10627                                  scratch_pool, scratch_pool));
10628      if (src_dirent->kind == dst_dirent->kind &&
10629          src_dirent->special == dst_dirent->special &&
10630          src_dirent->filesize == dst_dirent->filesize &&
10631          src_dirent->mtime <= dst_dirent->mtime)
10632        return SVN_NO_ERROR;
10633    }
10634
10635  return svn_error_trace(svn_io_dir_file_copy(src_path, dst_path, file,
10636                                              scratch_pool));
10637}
10638
10639/* Set *NAME_P to the UTF-8 representation of directory entry NAME.
10640 * NAME is in the internal encoding used by APR; PARENT is in
10641 * UTF-8 and in internal (not local) style.
10642 *
10643 * Use PARENT only for generating an error string if the conversion
10644 * fails because NAME could not be represented in UTF-8.  In that
10645 * case, return a two-level error in which the outer error's message
10646 * mentions PARENT, but the inner error's message does not mention
10647 * NAME (except possibly in hex) since NAME may not be printable.
10648 * Such a compound error at least allows the user to go looking in the
10649 * right directory for the problem.
10650 *
10651 * If there is any other error, just return that error directly.
10652 *
10653 * If there is any error, the effect on *NAME_P is undefined.
10654 *
10655 * *NAME_P and NAME may refer to the same storage.
10656 */
10657static svn_error_t *
10658entry_name_to_utf8(const char **name_p,
10659                   const char *name,
10660                   const char *parent,
10661                   apr_pool_t *pool)
10662{
10663  svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
10664  if (err && err->apr_err == APR_EINVAL)
10665    {
10666      return svn_error_createf(err->apr_err, err,
10667                               _("Error converting entry "
10668                                 "in directory '%s' to UTF-8"),
10669                               svn_dirent_local_style(parent, pool));
10670    }
10671  return err;
10672}
10673
10674/* Like svn_io_copy_dir_recursively() but doesn't copy regular files that
10675 * exist in the destination and do not differ from the source in terms of
10676 * kind, size, and mtime. */
10677static svn_error_t *
10678hotcopy_io_copy_dir_recursively(const char *src,
10679                                const char *dst_parent,
10680                                const char *dst_basename,
10681                                svn_boolean_t copy_perms,
10682                                svn_cancel_func_t cancel_func,
10683                                void *cancel_baton,
10684                                apr_pool_t *pool)
10685{
10686  svn_node_kind_t kind;
10687  apr_status_t status;
10688  const char *dst_path;
10689  apr_dir_t *this_dir;
10690  apr_finfo_t this_entry;
10691  apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
10692
10693  /* Make a subpool for recursion */
10694  apr_pool_t *subpool = svn_pool_create(pool);
10695
10696  /* The 'dst_path' is simply dst_parent/dst_basename */
10697  dst_path = svn_dirent_join(dst_parent, dst_basename, pool);
10698
10699  /* Sanity checks:  SRC and DST_PARENT are directories, and
10700     DST_BASENAME doesn't already exist in DST_PARENT. */
10701  SVN_ERR(svn_io_check_path(src, &kind, subpool));
10702  if (kind != svn_node_dir)
10703    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
10704                             _("Source '%s' is not a directory"),
10705                             svn_dirent_local_style(src, pool));
10706
10707  SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
10708  if (kind != svn_node_dir)
10709    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
10710                             _("Destination '%s' is not a directory"),
10711                             svn_dirent_local_style(dst_parent, pool));
10712
10713  SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
10714
10715  /* Create the new directory. */
10716  /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
10717  SVN_ERR(svn_io_make_dir_recursively(dst_path, pool));
10718
10719  /* Loop over the dirents in SRC.  ('.' and '..' are auto-excluded) */
10720  SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
10721
10722  for (status = apr_dir_read(&this_entry, flags, this_dir);
10723       status == APR_SUCCESS;
10724       status = apr_dir_read(&this_entry, flags, this_dir))
10725    {
10726      if ((this_entry.name[0] == '.')
10727          && ((this_entry.name[1] == '\0')
10728              || ((this_entry.name[1] == '.')
10729                  && (this_entry.name[2] == '\0'))))
10730        {
10731          continue;
10732        }
10733      else
10734        {
10735          const char *entryname_utf8;
10736
10737          if (cancel_func)
10738            SVN_ERR(cancel_func(cancel_baton));
10739
10740          SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
10741                                     src, subpool));
10742          if (this_entry.filetype == APR_REG) /* regular file */
10743            {
10744              SVN_ERR(hotcopy_io_dir_file_copy(src, dst_path, entryname_utf8,
10745                                               subpool));
10746            }
10747          else if (this_entry.filetype == APR_LNK) /* symlink */
10748            {
10749              const char *src_target = svn_dirent_join(src, entryname_utf8,
10750                                                       subpool);
10751              const char *dst_target = svn_dirent_join(dst_path,
10752                                                       entryname_utf8,
10753                                                       subpool);
10754              SVN_ERR(svn_io_copy_link(src_target, dst_target,
10755                                       subpool));
10756            }
10757          else if (this_entry.filetype == APR_DIR) /* recurse */
10758            {
10759              const char *src_target;
10760
10761              /* Prevent infinite recursion by filtering off our
10762                 newly created destination path. */
10763              if (strcmp(src, dst_parent) == 0
10764                  && strcmp(entryname_utf8, dst_basename) == 0)
10765                continue;
10766
10767              src_target = svn_dirent_join(src, entryname_utf8, subpool);
10768              SVN_ERR(hotcopy_io_copy_dir_recursively(src_target,
10769                                                      dst_path,
10770                                                      entryname_utf8,
10771                                                      copy_perms,
10772                                                      cancel_func,
10773                                                      cancel_baton,
10774                                                      subpool));
10775            }
10776          /* ### support other APR node types someday?? */
10777
10778        }
10779    }
10780
10781  if (! (APR_STATUS_IS_ENOENT(status)))
10782    return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
10783                              svn_dirent_local_style(src, pool));
10784
10785  status = apr_dir_close(this_dir);
10786  if (status)
10787    return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
10788                              svn_dirent_local_style(src, pool));
10789
10790  /* Free any memory used by recursion */
10791  svn_pool_destroy(subpool);
10792
10793  return SVN_NO_ERROR;
10794}
10795
10796/* Copy an un-packed revision or revprop file for revision REV from SRC_SUBDIR
10797 * to DST_SUBDIR. Assume a sharding layout based on MAX_FILES_PER_DIR.
10798 * Use SCRATCH_POOL for temporary allocations. */
10799static svn_error_t *
10800hotcopy_copy_shard_file(const char *src_subdir,
10801                        const char *dst_subdir,
10802                        svn_revnum_t rev,
10803                        int max_files_per_dir,
10804                        apr_pool_t *scratch_pool)
10805{
10806  const char *src_subdir_shard = src_subdir,
10807             *dst_subdir_shard = dst_subdir;
10808
10809  if (max_files_per_dir)
10810    {
10811      const char *shard = apr_psprintf(scratch_pool, "%ld",
10812                                       rev / max_files_per_dir);
10813      src_subdir_shard = svn_dirent_join(src_subdir, shard, scratch_pool);
10814      dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10815
10816      if (rev % max_files_per_dir == 0)
10817        {
10818          SVN_ERR(svn_io_make_dir_recursively(dst_subdir_shard, scratch_pool));
10819          SVN_ERR(svn_io_copy_perms(dst_subdir, dst_subdir_shard,
10820                                    scratch_pool));
10821        }
10822    }
10823
10824  SVN_ERR(hotcopy_io_dir_file_copy(src_subdir_shard, dst_subdir_shard,
10825                                   apr_psprintf(scratch_pool, "%ld", rev),
10826                                   scratch_pool));
10827  return SVN_NO_ERROR;
10828}
10829
10830
10831/* Copy a packed shard containing revision REV, and which contains
10832 * MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS.
10833 * Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS.
10834 * Do not re-copy data which already exists in DST_FS.
10835 * Use SCRATCH_POOL for temporary allocations. */
10836static svn_error_t *
10837hotcopy_copy_packed_shard(svn_revnum_t *dst_min_unpacked_rev,
10838                          svn_fs_t *src_fs,
10839                          svn_fs_t *dst_fs,
10840                          svn_revnum_t rev,
10841                          int max_files_per_dir,
10842                          apr_pool_t *scratch_pool)
10843{
10844  const char *src_subdir;
10845  const char *dst_subdir;
10846  const char *packed_shard;
10847  const char *src_subdir_packed_shard;
10848  svn_revnum_t revprop_rev;
10849  apr_pool_t *iterpool;
10850  fs_fs_data_t *src_ffd = src_fs->fsap_data;
10851
10852  /* Copy the packed shard. */
10853  src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
10854  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
10855  packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
10856                              rev / max_files_per_dir);
10857  src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
10858                                            scratch_pool);
10859  SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
10860                                          dst_subdir, packed_shard,
10861                                          TRUE /* copy_perms */,
10862                                          NULL /* cancel_func */, NULL,
10863                                          scratch_pool));
10864
10865  /* Copy revprops belonging to revisions in this pack. */
10866  src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, scratch_pool);
10867  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, scratch_pool);
10868
10869  if (   src_ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
10870      || src_ffd->min_unpacked_rev < rev + max_files_per_dir)
10871    {
10872      /* copy unpacked revprops rev by rev */
10873      iterpool = svn_pool_create(scratch_pool);
10874      for (revprop_rev = rev;
10875           revprop_rev < rev + max_files_per_dir;
10876           revprop_rev++)
10877        {
10878          svn_pool_clear(iterpool);
10879
10880          SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
10881                                          revprop_rev, max_files_per_dir,
10882                                          iterpool));
10883        }
10884      svn_pool_destroy(iterpool);
10885    }
10886  else
10887    {
10888      /* revprop for revision 0 will never be packed */
10889      if (rev == 0)
10890        SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
10891                                        0, max_files_per_dir,
10892                                        scratch_pool));
10893
10894      /* packed revprops folder */
10895      packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
10896                                  rev / max_files_per_dir);
10897      src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
10898                                                scratch_pool);
10899      SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
10900                                              dst_subdir, packed_shard,
10901                                              TRUE /* copy_perms */,
10902                                              NULL /* cancel_func */, NULL,
10903                                              scratch_pool));
10904    }
10905
10906  /* If necessary, update the min-unpacked rev file in the hotcopy. */
10907  if (*dst_min_unpacked_rev < rev + max_files_per_dir)
10908    {
10909      *dst_min_unpacked_rev = rev + max_files_per_dir;
10910      SVN_ERR(write_revnum_file(dst_fs->path, PATH_MIN_UNPACKED_REV,
10911                                *dst_min_unpacked_rev,
10912                                scratch_pool));
10913    }
10914
10915  return SVN_NO_ERROR;
10916}
10917
10918/* If NEW_YOUNGEST is younger than *DST_YOUNGEST, update the 'current'
10919 * file in DST_FS and set *DST_YOUNGEST to NEW_YOUNGEST.
10920 * Use SCRATCH_POOL for temporary allocations. */
10921static svn_error_t *
10922hotcopy_update_current(svn_revnum_t *dst_youngest,
10923                       svn_fs_t *dst_fs,
10924                       svn_revnum_t new_youngest,
10925                       apr_pool_t *scratch_pool)
10926{
10927  char next_node_id[MAX_KEY_SIZE] = "0";
10928  char next_copy_id[MAX_KEY_SIZE] = "0";
10929  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
10930
10931  if (*dst_youngest >= new_youngest)
10932    return SVN_NO_ERROR;
10933
10934  /* If necessary, get new current next_node and next_copy IDs. */
10935  if (dst_ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
10936    {
10937      apr_off_t root_offset;
10938      apr_file_t *rev_file;
10939      char max_node_id[MAX_KEY_SIZE] = "0";
10940      char max_copy_id[MAX_KEY_SIZE] = "0";
10941      apr_size_t len;
10942
10943      if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
10944        SVN_ERR(update_min_unpacked_rev(dst_fs, scratch_pool));
10945
10946      SVN_ERR(open_pack_or_rev_file(&rev_file, dst_fs, new_youngest,
10947                                    scratch_pool));
10948      SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file,
10949                                      dst_fs, new_youngest, scratch_pool));
10950      SVN_ERR(recover_find_max_ids(dst_fs, new_youngest, rev_file,
10951                                   root_offset, max_node_id, max_copy_id,
10952                                   scratch_pool));
10953      SVN_ERR(svn_io_file_close(rev_file, scratch_pool));
10954
10955      /* We store the _next_ ids. */
10956      len = strlen(max_node_id);
10957      svn_fs_fs__next_key(max_node_id, &len, next_node_id);
10958      len = strlen(max_copy_id);
10959      svn_fs_fs__next_key(max_copy_id, &len, next_copy_id);
10960    }
10961
10962  /* Update 'current'. */
10963  SVN_ERR(write_current(dst_fs, new_youngest, next_node_id, next_copy_id,
10964                        scratch_pool));
10965
10966  *dst_youngest = new_youngest;
10967
10968  return SVN_NO_ERROR;
10969}
10970
10971
10972/* Remove revision or revprop files between START_REV (inclusive) and
10973 * END_REV (non-inclusive) from folder DST_SUBDIR in DST_FS.  Assume
10974 * sharding as per MAX_FILES_PER_DIR.
10975 * Use SCRATCH_POOL for temporary allocations. */
10976static svn_error_t *
10977hotcopy_remove_files(svn_fs_t *dst_fs,
10978                     const char *dst_subdir,
10979                     svn_revnum_t start_rev,
10980                     svn_revnum_t end_rev,
10981                     int max_files_per_dir,
10982                     apr_pool_t *scratch_pool)
10983{
10984  const char *shard;
10985  const char *dst_subdir_shard;
10986  svn_revnum_t rev;
10987  apr_pool_t *iterpool;
10988
10989  /* Pre-compute paths for initial shard. */
10990  shard = apr_psprintf(scratch_pool, "%ld", start_rev / max_files_per_dir);
10991  dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10992
10993  iterpool = svn_pool_create(scratch_pool);
10994  for (rev = start_rev; rev < end_rev; rev++)
10995    {
10996      const char *path;
10997      svn_pool_clear(iterpool);
10998
10999      /* If necessary, update paths for shard. */
11000      if (rev != start_rev && rev % max_files_per_dir == 0)
11001        {
11002          shard = apr_psprintf(iterpool, "%ld", rev / max_files_per_dir);
11003          dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
11004        }
11005
11006      /* remove files for REV */
11007      path = svn_dirent_join(dst_subdir_shard,
11008                             apr_psprintf(iterpool, "%ld", rev),
11009                             iterpool);
11010
11011      /* Make the rev file writable and remove it. */
11012      SVN_ERR(svn_io_set_file_read_write(path, TRUE, iterpool));
11013      SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
11014    }
11015
11016  svn_pool_destroy(iterpool);
11017
11018  return SVN_NO_ERROR;
11019}
11020
11021/* Remove revisions between START_REV (inclusive) and END_REV (non-inclusive)
11022 * from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
11023 * Use SCRATCH_POOL for temporary allocations. */
11024static svn_error_t *
11025hotcopy_remove_rev_files(svn_fs_t *dst_fs,
11026                         svn_revnum_t start_rev,
11027                         svn_revnum_t end_rev,
11028                         int max_files_per_dir,
11029                         apr_pool_t *scratch_pool)
11030{
11031  SVN_ERR_ASSERT(start_rev <= end_rev);
11032  SVN_ERR(hotcopy_remove_files(dst_fs,
11033                               svn_dirent_join(dst_fs->path,
11034                                               PATH_REVS_DIR,
11035                                               scratch_pool),
11036                               start_rev, end_rev,
11037                               max_files_per_dir, scratch_pool));
11038
11039  return SVN_NO_ERROR;
11040}
11041
11042/* Remove revision properties between START_REV (inclusive) and END_REV
11043 * (non-inclusive) from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
11044 * Use SCRATCH_POOL for temporary allocations.  Revision 0 revprops will
11045 * not be deleted. */
11046static svn_error_t *
11047hotcopy_remove_revprop_files(svn_fs_t *dst_fs,
11048                             svn_revnum_t start_rev,
11049                             svn_revnum_t end_rev,
11050                             int max_files_per_dir,
11051                             apr_pool_t *scratch_pool)
11052{
11053  SVN_ERR_ASSERT(start_rev <= end_rev);
11054
11055  /* don't delete rev 0 props */
11056  SVN_ERR(hotcopy_remove_files(dst_fs,
11057                               svn_dirent_join(dst_fs->path,
11058                                               PATH_REVPROPS_DIR,
11059                                               scratch_pool),
11060                               start_rev ? start_rev : 1, end_rev,
11061                               max_files_per_dir, scratch_pool));
11062
11063  return SVN_NO_ERROR;
11064}
11065
11066/* Verify that DST_FS is a suitable destination for an incremental
11067 * hotcopy from SRC_FS. */
11068static svn_error_t *
11069hotcopy_incremental_check_preconditions(svn_fs_t *src_fs,
11070                                        svn_fs_t *dst_fs,
11071                                        apr_pool_t *pool)
11072{
11073  fs_fs_data_t *src_ffd = src_fs->fsap_data;
11074  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11075
11076  /* We only support incremental hotcopy between the same format. */
11077  if (src_ffd->format != dst_ffd->format)
11078    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11079      _("The FSFS format (%d) of the hotcopy source does not match the "
11080        "FSFS format (%d) of the hotcopy destination; please upgrade "
11081        "both repositories to the same format"),
11082      src_ffd->format, dst_ffd->format);
11083
11084  /* Make sure the UUID of source and destination match up.
11085   * We don't want to copy over a different repository. */
11086  if (strcmp(src_fs->uuid, dst_fs->uuid) != 0)
11087    return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL,
11088                            _("The UUID of the hotcopy source does "
11089                              "not match the UUID of the hotcopy "
11090                              "destination"));
11091
11092  /* Also require same shard size. */
11093  if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir)
11094    return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11095                            _("The sharding layout configuration "
11096                              "of the hotcopy source does not match "
11097                              "the sharding layout configuration of "
11098                              "the hotcopy destination"));
11099  return SVN_NO_ERROR;
11100}
11101
11102/* Remove folder PATH.  Ignore errors due to the sub-tree not being empty.
11103 * CANCEL_FUNC and CANCEL_BATON do the usual thing.
11104 * Use POOL for temporary allocations.
11105 */
11106static svn_error_t *
11107remove_folder(const char *path,
11108              svn_cancel_func_t cancel_func,
11109              void *cancel_baton,
11110              apr_pool_t *pool)
11111{
11112  svn_error_t *err = svn_io_remove_dir2(path, TRUE,
11113                                        cancel_func, cancel_baton, pool);
11114
11115  if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err))
11116    {
11117      svn_error_clear(err);
11118      err = SVN_NO_ERROR;
11119    }
11120
11121  return svn_error_trace(err);
11122}
11123
11124/* Baton for hotcopy_body(). */
11125struct hotcopy_body_baton {
11126  svn_fs_t *src_fs;
11127  svn_fs_t *dst_fs;
11128  svn_boolean_t incremental;
11129  svn_cancel_func_t cancel_func;
11130  void *cancel_baton;
11131} hotcopy_body_baton;
11132
11133/* Perform a hotcopy, either normal or incremental.
11134 *
11135 * Normal hotcopy assumes that the destination exists as an empty
11136 * directory. It behaves like an incremental hotcopy except that
11137 * none of the copied files already exist in the destination.
11138 *
11139 * An incremental hotcopy copies only changed or new files to the destination,
11140 * and removes files from the destination no longer present in the source.
11141 * While the incremental hotcopy is running, readers should still be able
11142 * to access the destintation repository without error and should not see
11143 * revisions currently in progress of being copied. Readers are able to see
11144 * new fully copied revisions even if the entire incremental hotcopy procedure
11145 * has not yet completed.
11146 *
11147 * Writers are blocked out completely during the entire incremental hotcopy
11148 * process to ensure consistency. This function assumes that the repository
11149 * write-lock is held.
11150 */
11151static svn_error_t *
11152hotcopy_body(void *baton, apr_pool_t *pool)
11153{
11154  struct hotcopy_body_baton *hbb = baton;
11155  svn_fs_t *src_fs = hbb->src_fs;
11156  fs_fs_data_t *src_ffd = src_fs->fsap_data;
11157  svn_fs_t *dst_fs = hbb->dst_fs;
11158  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11159  int max_files_per_dir = src_ffd->max_files_per_dir;
11160  svn_boolean_t incremental = hbb->incremental;
11161  svn_cancel_func_t cancel_func = hbb->cancel_func;
11162  void* cancel_baton = hbb->cancel_baton;
11163  svn_revnum_t src_youngest;
11164  svn_revnum_t dst_youngest;
11165  svn_revnum_t rev;
11166  svn_revnum_t src_min_unpacked_rev;
11167  svn_revnum_t dst_min_unpacked_rev;
11168  const char *src_subdir;
11169  const char *dst_subdir;
11170  const char *revprop_src_subdir;
11171  const char *revprop_dst_subdir;
11172  apr_pool_t *iterpool;
11173  svn_node_kind_t kind;
11174
11175  /* Try to copy the config.
11176   *
11177   * ### We try copying the config file before doing anything else,
11178   * ### because higher layers will abort the hotcopy if we throw
11179   * ### an error from this function, and that renders the hotcopy
11180   * ### unusable anyway. */
11181  if (src_ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
11182    {
11183      svn_error_t *err;
11184
11185      err = svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG,
11186                                 pool);
11187      if (err)
11188        {
11189          if (APR_STATUS_IS_ENOENT(err->apr_err))
11190            {
11191              /* 1.6.0 to 1.6.11 did not copy the configuration file during
11192               * hotcopy. So if we're hotcopying a repository which has been
11193               * created as a hotcopy itself, it's possible that fsfs.conf
11194               * does not exist. Ask the user to re-create it.
11195               *
11196               * ### It would be nice to make this a non-fatal error,
11197               * ### but this function does not get an svn_fs_t object
11198               * ### so we have no way of just printing a warning via
11199               * ### the fs->warning() callback. */
11200
11201              const char *msg;
11202              const char *src_abspath;
11203              const char *dst_abspath;
11204              const char *config_relpath;
11205              svn_error_t *err2;
11206
11207              config_relpath = svn_dirent_join(src_fs->path, PATH_CONFIG, pool);
11208              err2 = svn_dirent_get_absolute(&src_abspath, src_fs->path, pool);
11209              if (err2)
11210                return svn_error_trace(svn_error_compose_create(err, err2));
11211              err2 = svn_dirent_get_absolute(&dst_abspath, dst_fs->path, pool);
11212              if (err2)
11213                return svn_error_trace(svn_error_compose_create(err, err2));
11214
11215              /* ### hack: strip off the 'db/' directory from paths so
11216               * ### they make sense to the user */
11217              src_abspath = svn_dirent_dirname(src_abspath, pool);
11218              dst_abspath = svn_dirent_dirname(dst_abspath, pool);
11219
11220              msg = apr_psprintf(pool,
11221                                 _("Failed to create hotcopy at '%s'. "
11222                                   "The file '%s' is missing from the source "
11223                                   "repository. Please create this file, for "
11224                                   "instance by running 'svnadmin upgrade %s'"),
11225                                 dst_abspath, config_relpath, src_abspath);
11226              return svn_error_quick_wrap(err, msg);
11227            }
11228          else
11229            return svn_error_trace(err);
11230        }
11231    }
11232
11233  if (cancel_func)
11234    SVN_ERR(cancel_func(cancel_baton));
11235
11236  /* Find the youngest revision in the source and destination.
11237   * We only support hotcopies from sources with an equal or greater amount
11238   * of revisions than the destination.
11239   * This also catches the case where users accidentally swap the
11240   * source and destination arguments. */
11241  SVN_ERR(get_youngest(&src_youngest, src_fs->path, pool));
11242  if (incremental)
11243    {
11244      SVN_ERR(get_youngest(&dst_youngest, dst_fs->path, pool));
11245      if (src_youngest < dst_youngest)
11246        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11247                 _("The hotcopy destination already contains more revisions "
11248                   "(%lu) than the hotcopy source contains (%lu); are source "
11249                   "and destination swapped?"),
11250                  dst_youngest, src_youngest);
11251    }
11252  else
11253    dst_youngest = 0;
11254
11255  if (cancel_func)
11256    SVN_ERR(cancel_func(cancel_baton));
11257
11258  /* Copy the min unpacked rev, and read its value. */
11259  if (src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11260    {
11261      const char *min_unpacked_rev_path;
11262
11263      min_unpacked_rev_path = svn_dirent_join(src_fs->path,
11264                                              PATH_MIN_UNPACKED_REV,
11265                                              pool);
11266      SVN_ERR(read_min_unpacked_rev(&src_min_unpacked_rev,
11267                                    min_unpacked_rev_path,
11268                                    pool));
11269
11270      min_unpacked_rev_path = svn_dirent_join(dst_fs->path,
11271                                              PATH_MIN_UNPACKED_REV,
11272                                              pool);
11273      SVN_ERR(read_min_unpacked_rev(&dst_min_unpacked_rev,
11274                                    min_unpacked_rev_path,
11275                                    pool));
11276
11277      /* We only support packs coming from the hotcopy source.
11278       * The destination should not be packed independently from
11279       * the source. This also catches the case where users accidentally
11280       * swap the source and destination arguments. */
11281      if (src_min_unpacked_rev < dst_min_unpacked_rev)
11282        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11283                                 _("The hotcopy destination already contains "
11284                                   "more packed revisions (%lu) than the "
11285                                   "hotcopy source contains (%lu)"),
11286                                   dst_min_unpacked_rev - 1,
11287                                   src_min_unpacked_rev - 1);
11288
11289      SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
11290                                   PATH_MIN_UNPACKED_REV, pool));
11291    }
11292  else
11293    {
11294      src_min_unpacked_rev = 0;
11295      dst_min_unpacked_rev = 0;
11296    }
11297
11298  if (cancel_func)
11299    SVN_ERR(cancel_func(cancel_baton));
11300
11301  /*
11302   * Copy the necessary rev files.
11303   */
11304
11305  src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, pool);
11306  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, pool);
11307  SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
11308
11309  iterpool = svn_pool_create(pool);
11310  /* First, copy packed shards. */
11311  for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir)
11312    {
11313      svn_pool_clear(iterpool);
11314
11315      if (cancel_func)
11316        SVN_ERR(cancel_func(cancel_baton));
11317
11318      /* Copy the packed shard. */
11319      SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
11320                                        src_fs, dst_fs,
11321                                        rev, max_files_per_dir,
11322                                        iterpool));
11323
11324      /* If necessary, update 'current' to the most recent packed rev,
11325       * so readers can see new revisions which arrived in this pack. */
11326      SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs,
11327                                     rev + max_files_per_dir - 1,
11328                                     iterpool));
11329
11330      /* Remove revision files which are now packed. */
11331      if (incremental)
11332        {
11333          SVN_ERR(hotcopy_remove_rev_files(dst_fs, rev,
11334                                           rev + max_files_per_dir,
11335                                           max_files_per_dir, iterpool));
11336          if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
11337            SVN_ERR(hotcopy_remove_revprop_files(dst_fs, rev,
11338                                                 rev + max_files_per_dir,
11339                                                 max_files_per_dir,
11340                                                 iterpool));
11341        }
11342
11343      /* Now that all revisions have moved into the pack, the original
11344       * rev dir can be removed. */
11345      SVN_ERR(remove_folder(path_rev_shard(dst_fs, rev, iterpool),
11346                            cancel_func, cancel_baton, iterpool));
11347      if (rev > 0 && dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
11348        SVN_ERR(remove_folder(path_revprops_shard(dst_fs, rev, iterpool),
11349                              cancel_func, cancel_baton, iterpool));
11350    }
11351
11352  if (cancel_func)
11353    SVN_ERR(cancel_func(cancel_baton));
11354
11355  /* Now, copy pairs of non-packed revisions and revprop files.
11356   * If necessary, update 'current' after copying all files from a shard. */
11357  SVN_ERR_ASSERT(rev == src_min_unpacked_rev);
11358  SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev);
11359  revprop_src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, pool);
11360  revprop_dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, pool);
11361  SVN_ERR(svn_io_make_dir_recursively(revprop_dst_subdir, pool));
11362  for (; rev <= src_youngest; rev++)
11363    {
11364      svn_error_t *err;
11365
11366      svn_pool_clear(iterpool);
11367
11368      if (cancel_func)
11369        SVN_ERR(cancel_func(cancel_baton));
11370
11371      /* Copy the rev file. */
11372      err = hotcopy_copy_shard_file(src_subdir, dst_subdir,
11373                                    rev, max_files_per_dir,
11374                                    iterpool);
11375      if (err)
11376        {
11377          if (APR_STATUS_IS_ENOENT(err->apr_err) &&
11378              src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11379            {
11380              svn_error_clear(err);
11381
11382              /* The source rev file does not exist. This can happen if the
11383               * source repository is being packed concurrently with this
11384               * hotcopy operation.
11385               *
11386               * If the new revision is now packed, and the youngest revision
11387               * we're interested in is not inside this pack, try to copy the
11388               * pack instead.
11389               *
11390               * If the youngest revision ended up being packed, don't try
11391               * to be smart and work around this. Just abort the hotcopy. */
11392              SVN_ERR(update_min_unpacked_rev(src_fs, pool));
11393              if (is_packed_rev(src_fs, rev))
11394                {
11395                  if (is_packed_rev(src_fs, src_youngest))
11396                    return svn_error_createf(
11397                             SVN_ERR_FS_NO_SUCH_REVISION, NULL,
11398                             _("The assumed HEAD revision (%lu) of the "
11399                               "hotcopy source has been packed while the "
11400                               "hotcopy was in progress; please restart "
11401                               "the hotcopy operation"),
11402                             src_youngest);
11403
11404                  SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
11405                                                    src_fs, dst_fs,
11406                                                    rev, max_files_per_dir,
11407                                                    iterpool));
11408                  rev = dst_min_unpacked_rev;
11409                  continue;
11410                }
11411              else
11412                return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
11413                                         _("Revision %lu disappeared from the "
11414                                           "hotcopy source while hotcopy was "
11415                                           "in progress"), rev);
11416            }
11417          else
11418            return svn_error_trace(err);
11419        }
11420
11421      /* Copy the revprop file. */
11422      SVN_ERR(hotcopy_copy_shard_file(revprop_src_subdir,
11423                                      revprop_dst_subdir,
11424                                      rev, max_files_per_dir,
11425                                      iterpool));
11426
11427      /* After completing a full shard, update 'current'. */
11428      if (max_files_per_dir && rev % max_files_per_dir == 0)
11429        SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, rev, iterpool));
11430    }
11431  svn_pool_destroy(iterpool);
11432
11433  if (cancel_func)
11434    SVN_ERR(cancel_func(cancel_baton));
11435
11436  /* We assume that all revisions were copied now, i.e. we didn't exit the
11437   * above loop early. 'rev' was last incremented during exit of the loop. */
11438  SVN_ERR_ASSERT(rev == src_youngest + 1);
11439
11440  /* All revisions were copied. Update 'current'. */
11441  SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, src_youngest, pool));
11442
11443  /* Replace the locks tree.
11444   * This is racy in case readers are currently trying to list locks in
11445   * the destination. However, we need to get rid of stale locks.
11446   * This is the simplest way of doing this, so we accept this small race. */
11447  dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, pool);
11448  SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton,
11449                             pool));
11450  src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, pool);
11451  SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11452  if (kind == svn_node_dir)
11453    SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path,
11454                                        PATH_LOCKS_DIR, TRUE,
11455                                        cancel_func, cancel_baton, pool));
11456
11457  /* Now copy the node-origins cache tree. */
11458  src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR, pool);
11459  SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11460  if (kind == svn_node_dir)
11461    SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir, dst_fs->path,
11462                                            PATH_NODE_ORIGINS_DIR, TRUE,
11463                                            cancel_func, cancel_baton, pool));
11464
11465  /*
11466   * NB: Data copied below is only read by writers, not readers.
11467   *     Writers are still locked out at this point.
11468   */
11469
11470  if (dst_ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
11471    {
11472      /* Copy the rep cache and then remove entries for revisions
11473       * younger than the destination's youngest revision. */
11474      src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, pool);
11475      dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, pool);
11476      SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11477      if (kind == svn_node_file)
11478        {
11479          SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, pool));
11480          SVN_ERR(svn_fs_fs__del_rep_reference(dst_fs, dst_youngest, pool));
11481        }
11482    }
11483
11484  /* Copy the txn-current file. */
11485  if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
11486    SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
11487                                 PATH_TXN_CURRENT, pool));
11488
11489  /* If a revprop generation file exists in the source filesystem,
11490   * reset it to zero (since this is on a different path, it will not
11491   * overlap with data already in cache).  Also, clean up stale files
11492   * used for the named atomics implementation. */
11493  SVN_ERR(svn_io_check_path(path_revprop_generation(src_fs, pool),
11494                            &kind, pool));
11495  if (kind == svn_node_file)
11496    SVN_ERR(write_revprop_generation_file(dst_fs, 0, pool));
11497
11498  SVN_ERR(cleanup_revprop_namespace(dst_fs));
11499
11500  /* Hotcopied FS is complete. Stamp it with a format file. */
11501  SVN_ERR(write_format(svn_dirent_join(dst_fs->path, PATH_FORMAT, pool),
11502                       dst_ffd->format, max_files_per_dir, TRUE, pool));
11503
11504  return SVN_NO_ERROR;
11505}
11506
11507
11508/* Set up shared data between SRC_FS and DST_FS. */
11509static void
11510hotcopy_setup_shared_fs_data(svn_fs_t *src_fs, svn_fs_t *dst_fs)
11511{
11512  fs_fs_data_t *src_ffd = src_fs->fsap_data;
11513  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11514
11515  /* The common pool and mutexes are shared between src and dst filesystems.
11516   * During hotcopy we only grab the mutexes for the destination, so there
11517   * is no risk of dead-lock. We don't write to the src filesystem. Shared
11518   * data for the src_fs has already been initialised in fs_hotcopy(). */
11519  dst_ffd->shared = src_ffd->shared;
11520}
11521
11522/* Create an empty filesystem at DST_FS at DST_PATH with the same
11523 * configuration as SRC_FS (uuid, format, and other parameters).
11524 * After creation DST_FS has no revisions, not even revision zero. */
11525static svn_error_t *
11526hotcopy_create_empty_dest(svn_fs_t *src_fs,
11527                          svn_fs_t *dst_fs,
11528                          const char *dst_path,
11529                          apr_pool_t *pool)
11530{
11531  fs_fs_data_t *src_ffd = src_fs->fsap_data;
11532  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11533
11534  dst_fs->path = apr_pstrdup(pool, dst_path);
11535
11536  dst_ffd->max_files_per_dir = src_ffd->max_files_per_dir;
11537  dst_ffd->config = src_ffd->config;
11538  dst_ffd->format = src_ffd->format;
11539
11540  /* Create the revision data directories. */
11541  if (dst_ffd->max_files_per_dir)
11542    SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(dst_fs, 0, pool),
11543                                        pool));
11544  else
11545    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11546                                                        PATH_REVS_DIR, pool),
11547                                        pool));
11548
11549  /* Create the revprops directory. */
11550  if (src_ffd->max_files_per_dir)
11551    SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(dst_fs, 0, pool),
11552                                        pool));
11553  else
11554    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11555                                                        PATH_REVPROPS_DIR,
11556                                                        pool),
11557                                        pool));
11558
11559  /* Create the transaction directory. */
11560  SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, PATH_TXNS_DIR,
11561                                                      pool),
11562                                      pool));
11563
11564  /* Create the protorevs directory. */
11565  if (dst_ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
11566    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11567                                                        PATH_TXN_PROTOS_DIR,
11568                                                        pool),
11569                                        pool));
11570
11571  /* Create the 'current' file. */
11572  SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(dst_fs, pool),
11573                             (dst_ffd->format >=
11574                                SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
11575                                ? "0\n" : "0 1 1\n"),
11576                             pool));
11577
11578  /* Create lock file and UUID. */
11579  SVN_ERR(svn_io_file_create(path_lock(dst_fs, pool), "", pool));
11580  SVN_ERR(svn_fs_fs__set_uuid(dst_fs, src_fs->uuid, pool));
11581
11582  /* Create the min unpacked rev file. */
11583  if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11584    SVN_ERR(svn_io_file_create(path_min_unpacked_rev(dst_fs, pool),
11585                                                     "0\n", pool));
11586  /* Create the txn-current file if the repository supports
11587     the transaction sequence file. */
11588  if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
11589    {
11590      SVN_ERR(svn_io_file_create(path_txn_current(dst_fs, pool),
11591                                 "0\n", pool));
11592      SVN_ERR(svn_io_file_create(path_txn_current_lock(dst_fs, pool),
11593                                 "", pool));
11594    }
11595
11596  dst_ffd->youngest_rev_cache = 0;
11597
11598  hotcopy_setup_shared_fs_data(src_fs, dst_fs);
11599  SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
11600
11601  return SVN_NO_ERROR;
11602}
11603
11604svn_error_t *
11605svn_fs_fs__hotcopy(svn_fs_t *src_fs,
11606                   svn_fs_t *dst_fs,
11607                   const char *src_path,
11608                   const char *dst_path,
11609                   svn_boolean_t incremental,
11610                   svn_cancel_func_t cancel_func,
11611                   void *cancel_baton,
11612                   apr_pool_t *pool)
11613{
11614  struct hotcopy_body_baton hbb;
11615
11616  if (cancel_func)
11617    SVN_ERR(cancel_func(cancel_baton));
11618
11619  SVN_ERR(svn_fs_fs__open(src_fs, src_path, pool));
11620
11621  if (incremental)
11622    {
11623      const char *dst_format_abspath;
11624      svn_node_kind_t dst_format_kind;
11625
11626      /* Check destination format to be sure we know how to incrementally
11627       * hotcopy to the destination FS. */
11628      dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT, pool);
11629      SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind, pool));
11630      if (dst_format_kind == svn_node_none)
11631        {
11632          /* Destination doesn't exist yet. Perform a normal hotcopy to a
11633           * empty destination using the same configuration as the source. */
11634          SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
11635        }
11636      else
11637        {
11638          /* Check the existing repository. */
11639          SVN_ERR(svn_fs_fs__open(dst_fs, dst_path, pool));
11640          SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs,
11641                                                          pool));
11642          hotcopy_setup_shared_fs_data(src_fs, dst_fs);
11643          SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
11644        }
11645    }
11646  else
11647    {
11648      /* Start out with an empty destination using the same configuration
11649       * as the source. */
11650      SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
11651    }
11652
11653  if (cancel_func)
11654    SVN_ERR(cancel_func(cancel_baton));
11655
11656  hbb.src_fs = src_fs;
11657  hbb.dst_fs = dst_fs;
11658  hbb.incremental = incremental;
11659  hbb.cancel_func = cancel_func;
11660  hbb.cancel_baton = cancel_baton;
11661  SVN_ERR(svn_fs_fs__with_write_lock(dst_fs, hotcopy_body, &hbb, pool));
11662
11663  return SVN_NO_ERROR;
11664}
11665