1/* transaction.c --- transaction-related functions of FSX
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 "transaction.h"
24
25#include <assert.h>
26#include <apr_sha1.h>
27
28#include "svn_hash.h"
29#include "svn_props.h"
30#include "svn_sorts.h"
31#include "svn_time.h"
32#include "svn_dirent_uri.h"
33
34#include "fs_x.h"
35#include "tree.h"
36#include "util.h"
37#include "id.h"
38#include "low_level.h"
39#include "temp_serializer.h"
40#include "cached_data.h"
41#include "lock.h"
42#include "rep-cache.h"
43#include "index.h"
44
45#include "private/svn_fs_util.h"
46#include "private/svn_fspath.h"
47#include "private/svn_sorts_private.h"
48#include "private/svn_string_private.h"
49#include "private/svn_subr_private.h"
50#include "private/svn_io_private.h"
51#include "../libsvn_fs/fs-loader.h"
52
53#include "svn_private_config.h"
54
55/* The vtable associated with an open transaction object. */
56static txn_vtable_t txn_vtable = {
57  svn_fs_x__commit_txn,
58  svn_fs_x__abort_txn,
59  svn_fs_x__txn_prop,
60  svn_fs_x__txn_proplist,
61  svn_fs_x__change_txn_prop,
62  svn_fs_x__txn_root,
63  svn_fs_x__change_txn_props
64};
65
66/* FSX-specific data being attached to svn_fs_txn_t.
67 */
68typedef struct fs_txn_data_t
69{
70  /* Strongly typed representation of the TXN's ID member. */
71  svn_fs_x__txn_id_t txn_id;
72} fs_txn_data_t;
73
74svn_fs_x__txn_id_t
75svn_fs_x__txn_get_id(svn_fs_txn_t *txn)
76{
77  fs_txn_data_t *ftd = txn->fsap_data;
78  return ftd->txn_id;
79}
80
81/* Functions for working with shared transaction data. */
82
83/* Return the transaction object for transaction TXN_ID from the
84   transaction list of filesystem FS (which must already be locked via the
85   txn_list_lock mutex).  If the transaction does not exist in the list,
86   then create a new transaction object and return it (if CREATE_NEW is
87   true) or return NULL (otherwise). */
88static svn_fs_x__shared_txn_data_t *
89get_shared_txn(svn_fs_t *fs,
90               svn_fs_x__txn_id_t txn_id,
91               svn_boolean_t create_new)
92{
93  svn_fs_x__data_t *ffd = fs->fsap_data;
94  svn_fs_x__shared_data_t *ffsd = ffd->shared;
95  svn_fs_x__shared_txn_data_t *txn;
96
97  for (txn = ffsd->txns; txn; txn = txn->next)
98    if (txn->txn_id == txn_id)
99      break;
100
101  if (txn || !create_new)
102    return txn;
103
104  /* Use the transaction object from the (single-object) freelist,
105     if one is available, or otherwise create a new object. */
106  if (ffsd->free_txn)
107    {
108      txn = ffsd->free_txn;
109      ffsd->free_txn = NULL;
110    }
111  else
112    {
113      apr_pool_t *subpool = svn_pool_create(ffsd->common_pool);
114      txn = apr_palloc(subpool, sizeof(*txn));
115      txn->pool = subpool;
116    }
117
118  txn->txn_id = txn_id;
119  txn->being_written = FALSE;
120
121  /* Link this transaction into the head of the list.  We will typically
122     be dealing with only one active transaction at a time, so it makes
123     sense for searches through the transaction list to look at the
124     newest transactions first.  */
125  txn->next = ffsd->txns;
126  ffsd->txns = txn;
127
128  return txn;
129}
130
131/* Free the transaction object for transaction TXN_ID, and remove it
132   from the transaction list of filesystem FS (which must already be
133   locked via the txn_list_lock mutex).  Do nothing if the transaction
134   does not exist. */
135static void
136free_shared_txn(svn_fs_t *fs, svn_fs_x__txn_id_t txn_id)
137{
138  svn_fs_x__data_t *ffd = fs->fsap_data;
139  svn_fs_x__shared_data_t *ffsd = ffd->shared;
140  svn_fs_x__shared_txn_data_t *txn, *prev = NULL;
141
142  for (txn = ffsd->txns; txn; prev = txn, txn = txn->next)
143    if (txn->txn_id == txn_id)
144      break;
145
146  if (!txn)
147    return;
148
149  if (prev)
150    prev->next = txn->next;
151  else
152    ffsd->txns = txn->next;
153
154  /* As we typically will be dealing with one transaction after another,
155     we will maintain a single-object free list so that we can hopefully
156     keep reusing the same transaction object. */
157  if (!ffsd->free_txn)
158    ffsd->free_txn = txn;
159  else
160    svn_pool_destroy(txn->pool);
161}
162
163
164/* Obtain a lock on the transaction list of filesystem FS, call BODY
165   with FS, BATON, and POOL, and then unlock the transaction list.
166   Return what BODY returned. */
167static svn_error_t *
168with_txnlist_lock(svn_fs_t *fs,
169                  svn_error_t *(*body)(svn_fs_t *fs,
170                                       const void *baton,
171                                       apr_pool_t *pool),
172                  const void *baton,
173                  apr_pool_t *pool)
174{
175  svn_fs_x__data_t *ffd = fs->fsap_data;
176  svn_fs_x__shared_data_t *ffsd = ffd->shared;
177
178  SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock,
179                       body(fs, baton, pool));
180
181  return SVN_NO_ERROR;
182}
183
184
185/* Get a lock on empty file LOCK_FILENAME, creating it in RESULT_POOL. */
186static svn_error_t *
187get_lock_on_filesystem(const char *lock_filename,
188                       apr_pool_t *result_pool)
189{
190  return svn_error_trace(svn_io__file_lock_autocreate(lock_filename,
191                                                      result_pool));
192}
193
194/* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID.
195   When registered with the pool holding the lock on the lock file,
196   this makes sure the flag gets reset just before we release the lock. */
197static apr_status_t
198reset_lock_flag(void *baton_void)
199{
200  svn_fs_x__data_t *ffd = baton_void;
201  ffd->has_write_lock = FALSE;
202  return APR_SUCCESS;
203}
204
205/* Structure defining a file system lock to be acquired and the function
206   to be executed while the lock is held.
207
208   Instances of this structure may be nested to allow for multiple locks to
209   be taken out before executing the user-provided body.  In that case, BODY
210   and BATON of the outer instances will be with_lock and a with_lock_baton_t
211   instance (transparently, no special treatment is required.).  It is
212   illegal to attempt to acquire the same lock twice within the same lock
213   chain or via nesting calls using separate lock chains.
214
215   All instances along the chain share the same LOCK_POOL such that only one
216   pool needs to be created and cleared for all locks.  We also allocate as
217   much data from that lock pool as possible to minimize memory usage in
218   caller pools. */
219typedef struct with_lock_baton_t
220{
221  /* The filesystem we operate on.  Same for all instances along the chain. */
222  svn_fs_t *fs;
223
224  /* Mutex to complement the lock file in an APR threaded process.
225     No-op object for non-threaded processes but never NULL. */
226  svn_mutex__t *mutex;
227
228  /* Path to the file to lock. */
229  const char *lock_path;
230
231  /* If true, set FS->HAS_WRITE_LOCK after we acquired the lock. */
232  svn_boolean_t is_global_lock;
233
234  /* Function body to execute after we acquired the lock.
235     This may be user-provided or a nested call to with_lock(). */
236  svn_error_t *(*body)(void *baton,
237                       apr_pool_t *scratch_pool);
238
239  /* Baton to pass to BODY; possibly NULL.
240     This may be user-provided or a nested lock baton instance. */
241  void *baton;
242
243  /* Pool for all allocations along the lock chain and BODY.  Will hold the
244     file locks and gets destroyed after the outermost BODY returned,
245     releasing all file locks.
246     Same for all instances along the chain. */
247  apr_pool_t *lock_pool;
248
249  /* TRUE, iff BODY is the user-provided body. */
250  svn_boolean_t is_inner_most_lock;
251
252  /* TRUE, iff this is not a nested lock.
253     Then responsible for destroying LOCK_POOL. */
254  svn_boolean_t is_outer_most_lock;
255} with_lock_baton_t;
256
257/* Obtain a write lock on the file BATON->LOCK_PATH and call BATON->BODY
258   with BATON->BATON.  If this is the outermost lock call, release all file
259   locks after the body returned.  If BATON->IS_GLOBAL_LOCK is set, set the
260   HAS_WRITE_LOCK flag while we keep the write lock. */
261static svn_error_t *
262with_some_lock_file(with_lock_baton_t *baton)
263{
264  apr_pool_t *pool = baton->lock_pool;
265  svn_error_t *err = get_lock_on_filesystem(baton->lock_path, pool);
266
267  if (!err)
268    {
269      svn_fs_t *fs = baton->fs;
270      svn_fs_x__data_t *ffd = fs->fsap_data;
271
272      if (baton->is_global_lock)
273        {
274          /* set the "got the lock" flag and register reset function */
275          apr_pool_cleanup_register(pool,
276                                    ffd,
277                                    reset_lock_flag,
278                                    apr_pool_cleanup_null);
279          ffd->has_write_lock = TRUE;
280        }
281
282      /* nobody else will modify the repo state
283         => read HEAD & pack info once */
284      if (baton->is_inner_most_lock)
285        {
286          err = svn_fs_x__update_min_unpacked_rev(fs, pool);
287          if (!err)
288            err = svn_fs_x__youngest_rev(&ffd->youngest_rev_cache, fs, pool);
289        }
290
291      if (!err)
292        err = baton->body(baton->baton, pool);
293    }
294
295  if (baton->is_outer_most_lock)
296    svn_pool_destroy(pool);
297
298  return svn_error_trace(err);
299}
300
301/* Wraps with_some_lock_file, protecting it with BATON->MUTEX.
302
303   SCRATCH_POOL is unused here and only provided for signature compatibility
304   with WITH_LOCK_BATON_T.BODY. */
305static svn_error_t *
306with_lock(void *baton,
307          apr_pool_t *scratch_pool)
308{
309  with_lock_baton_t *lock_baton = baton;
310  SVN_MUTEX__WITH_LOCK(lock_baton->mutex, with_some_lock_file(lock_baton));
311
312  return SVN_NO_ERROR;
313}
314
315/* Enum identifying a filesystem lock. */
316typedef enum lock_id_t
317{
318  write_lock,
319  txn_lock,
320  pack_lock
321} lock_id_t;
322
323/* Initialize BATON->MUTEX, BATON->LOCK_PATH and BATON->IS_GLOBAL_LOCK
324   according to the LOCK_ID.  All other members of BATON must already be
325   valid. */
326static void
327init_lock_baton(with_lock_baton_t *baton,
328                lock_id_t lock_id)
329{
330  svn_fs_x__data_t *ffd = baton->fs->fsap_data;
331  svn_fs_x__shared_data_t *ffsd = ffd->shared;
332
333  switch (lock_id)
334    {
335    case write_lock:
336      baton->mutex = ffsd->fs_write_lock;
337      baton->lock_path = svn_fs_x__path_lock(baton->fs, baton->lock_pool);
338      baton->is_global_lock = TRUE;
339      break;
340
341    case txn_lock:
342      baton->mutex = ffsd->txn_current_lock;
343      baton->lock_path = svn_fs_x__path_txn_current_lock(baton->fs,
344                                                         baton->lock_pool);
345      baton->is_global_lock = FALSE;
346      break;
347
348    case pack_lock:
349      baton->mutex = ffsd->fs_pack_lock;
350      baton->lock_path = svn_fs_x__path_pack_lock(baton->fs,
351                                                  baton->lock_pool);
352      baton->is_global_lock = FALSE;
353      break;
354    }
355}
356
357/* Return the  baton for the innermost lock of a (potential) lock chain.
358   The baton shall take out LOCK_ID from FS and execute BODY with BATON
359   while the lock is being held.  Allocate the result in a sub-pool of
360   RESULT_POOL.
361 */
362static with_lock_baton_t *
363create_lock_baton(svn_fs_t *fs,
364                  lock_id_t lock_id,
365                  svn_error_t *(*body)(void *baton,
366                                       apr_pool_t *scratch_pool),
367                  void *baton,
368                  apr_pool_t *result_pool)
369{
370  /* Allocate everything along the lock chain into a single sub-pool.
371     This minimizes memory usage and cleanup overhead. */
372  apr_pool_t *lock_pool = svn_pool_create(result_pool);
373  with_lock_baton_t *result = apr_pcalloc(lock_pool, sizeof(*result));
374
375  /* Store parameters. */
376  result->fs = fs;
377  result->body = body;
378  result->baton = baton;
379
380  /* File locks etc. will use this pool as well for easy cleanup. */
381  result->lock_pool = lock_pool;
382
383  /* Right now, we are the first, (only, ) and last struct in the chain. */
384  result->is_inner_most_lock = TRUE;
385  result->is_outer_most_lock = TRUE;
386
387  /* Select mutex and lock file path depending on LOCK_ID.
388     Also, initialize dependent members (IS_GLOBAL_LOCK only, ATM). */
389  init_lock_baton(result, lock_id);
390
391  return result;
392}
393
394/* Return a baton that wraps NESTED and requests LOCK_ID as additional lock.
395 *
396 * That means, when you create a lock chain, start with the last / innermost
397 * lock to take out and add the first / outermost lock last.
398 */
399static with_lock_baton_t *
400chain_lock_baton(lock_id_t lock_id,
401                 with_lock_baton_t *nested)
402{
403  /* Use the same pool for batons along the lock chain. */
404  apr_pool_t *lock_pool = nested->lock_pool;
405  with_lock_baton_t *result = apr_pcalloc(lock_pool, sizeof(*result));
406
407  /* All locks along the chain operate on the same FS. */
408  result->fs = nested->fs;
409
410  /* Execution of this baton means acquiring the nested lock and its
411     execution. */
412  result->body = with_lock;
413  result->baton = nested;
414
415  /* Shared among all locks along the chain. */
416  result->lock_pool = lock_pool;
417
418  /* We are the new outermost lock but surely not the innermost lock. */
419  result->is_inner_most_lock = FALSE;
420  result->is_outer_most_lock = TRUE;
421  nested->is_outer_most_lock = FALSE;
422
423  /* Select mutex and lock file path depending on LOCK_ID.
424     Also, initialize dependent members (IS_GLOBAL_LOCK only, ATM). */
425  init_lock_baton(result, lock_id);
426
427  return result;
428}
429
430svn_error_t *
431svn_fs_x__with_write_lock(svn_fs_t *fs,
432                          svn_error_t *(*body)(void *baton,
433                                               apr_pool_t *scratch_pool),
434                          void *baton,
435                          apr_pool_t *scratch_pool)
436{
437  return svn_error_trace(
438           with_lock(create_lock_baton(fs, write_lock, body, baton,
439                                       scratch_pool),
440                     scratch_pool));
441}
442
443svn_error_t *
444svn_fs_x__with_pack_lock(svn_fs_t *fs,
445                         svn_error_t *(*body)(void *baton,
446                                              apr_pool_t *scratch_pool),
447                         void *baton,
448                         apr_pool_t *scratch_pool)
449{
450  return svn_error_trace(
451           with_lock(create_lock_baton(fs, pack_lock, body, baton,
452                                       scratch_pool),
453                     scratch_pool));
454}
455
456svn_error_t *
457svn_fs_x__with_txn_current_lock(svn_fs_t *fs,
458                                svn_error_t *(*body)(void *baton,
459                                                     apr_pool_t *scratch_pool),
460                                void *baton,
461                                apr_pool_t *scratch_pool)
462{
463  return svn_error_trace(
464           with_lock(create_lock_baton(fs, txn_lock, body, baton,
465                                       scratch_pool),
466                     scratch_pool));
467}
468
469svn_error_t *
470svn_fs_x__with_all_locks(svn_fs_t *fs,
471                         svn_error_t *(*body)(void *baton,
472                                              apr_pool_t *scratch_pool),
473                         void *baton,
474                         apr_pool_t *scratch_pool)
475{
476  /* Be sure to use the correct lock ordering as documented in
477     fs_fs_shared_data_t.  The lock chain is being created in
478     innermost (last to acquire) -> outermost (first to acquire) order. */
479  with_lock_baton_t *lock_baton
480    = create_lock_baton(fs, write_lock, body, baton, scratch_pool);
481
482  lock_baton = chain_lock_baton(pack_lock, lock_baton);
483  lock_baton = chain_lock_baton(txn_lock, lock_baton);
484
485  return svn_error_trace(with_lock(lock_baton, scratch_pool));
486}
487
488
489/* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
490   which see. */
491typedef struct unlock_proto_rev_baton_t
492{
493  svn_fs_x__txn_id_t txn_id;
494  void *lockcookie;
495} unlock_proto_rev_baton_t;
496
497/* Callback used in the implementation of unlock_proto_rev(). */
498static svn_error_t *
499unlock_proto_rev_body(svn_fs_t *fs,
500                      const void *baton,
501                      apr_pool_t *scratch_pool)
502{
503  const unlock_proto_rev_baton_t *b = baton;
504  apr_file_t *lockfile = b->lockcookie;
505  svn_fs_x__shared_txn_data_t *txn = get_shared_txn(fs, b->txn_id, FALSE);
506  apr_status_t apr_err;
507
508  if (!txn)
509    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
510                             _("Can't unlock unknown transaction '%s'"),
511                             svn_fs_x__txn_name(b->txn_id, scratch_pool));
512  if (!txn->being_written)
513    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
514                             _("Can't unlock nonlocked transaction '%s'"),
515                             svn_fs_x__txn_name(b->txn_id, scratch_pool));
516
517  apr_err = apr_file_unlock(lockfile);
518  if (apr_err)
519    return svn_error_wrap_apr
520      (apr_err,
521       _("Can't unlock prototype revision lockfile for transaction '%s'"),
522       svn_fs_x__txn_name(b->txn_id, scratch_pool));
523  apr_err = apr_file_close(lockfile);
524  if (apr_err)
525    return svn_error_wrap_apr
526      (apr_err,
527       _("Can't close prototype revision lockfile for transaction '%s'"),
528       svn_fs_x__txn_name(b->txn_id, scratch_pool));
529
530  txn->being_written = FALSE;
531
532  return SVN_NO_ERROR;
533}
534
535/* Unlock the prototype revision file for transaction TXN_ID in filesystem
536   FS using cookie LOCKCOOKIE.  The original prototype revision file must
537   have been closed _before_ calling this function.
538
539   Perform temporary allocations in SCRATCH_POOL. */
540static svn_error_t *
541unlock_proto_rev(svn_fs_t *fs,
542                 svn_fs_x__txn_id_t txn_id,
543                 void *lockcookie,
544                 apr_pool_t *scratch_pool)
545{
546  unlock_proto_rev_baton_t b;
547
548  b.txn_id = txn_id;
549  b.lockcookie = lockcookie;
550  return with_txnlist_lock(fs, unlock_proto_rev_body, &b, scratch_pool);
551}
552
553/* A structure used by get_writable_proto_rev() and
554   get_writable_proto_rev_body(), which see. */
555typedef struct get_writable_proto_rev_baton_t
556{
557  void **lockcookie;
558  svn_fs_x__txn_id_t txn_id;
559} get_writable_proto_rev_baton_t;
560
561/* Callback used in the implementation of get_writable_proto_rev(). */
562static svn_error_t *
563get_writable_proto_rev_body(svn_fs_t *fs,
564                            const void *baton,
565                            apr_pool_t *scratch_pool)
566{
567  const get_writable_proto_rev_baton_t *b = baton;
568  void **lockcookie = b->lockcookie;
569  svn_fs_x__shared_txn_data_t *txn = get_shared_txn(fs, b->txn_id, TRUE);
570
571  /* First, ensure that no thread in this process (including this one)
572     is currently writing to this transaction's proto-rev file. */
573  if (txn->being_written)
574    return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
575                             _("Cannot write to the prototype revision file "
576                               "of transaction '%s' because a previous "
577                               "representation is currently being written by "
578                               "this process"),
579                             svn_fs_x__txn_name(b->txn_id, scratch_pool));
580
581
582  /* We know that no thread in this process is writing to the proto-rev
583     file, and by extension, that no thread in this process is holding a
584     lock on the prototype revision lock file.  It is therefore safe
585     for us to attempt to lock this file, to see if any other process
586     is holding a lock. */
587
588  {
589    apr_file_t *lockfile;
590    apr_status_t apr_err;
591    const char *lockfile_path
592      = svn_fs_x__path_txn_proto_rev_lock(fs, b->txn_id, scratch_pool);
593
594    /* Open the proto-rev lockfile, creating it if necessary, as it may
595       not exist if the transaction dates from before the lockfiles were
596       introduced.
597
598       ### We'd also like to use something like svn_io_file_lock2(), but
599           that forces us to create a subpool just to be able to unlock
600           the file, which seems a waste. */
601    SVN_ERR(svn_io_file_open(&lockfile, lockfile_path,
602                             APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
603                             scratch_pool));
604
605    apr_err = apr_file_lock(lockfile,
606                            APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK);
607    if (apr_err)
608      {
609        svn_error_clear(svn_io_file_close(lockfile, scratch_pool));
610
611        if (APR_STATUS_IS_EAGAIN(apr_err))
612          return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
613                                   _("Cannot write to the prototype revision "
614                                     "file of transaction '%s' because a "
615                                     "previous representation is currently "
616                                     "being written by another process"),
617                                   svn_fs_x__txn_name(b->txn_id,
618                                                      scratch_pool));
619
620        return svn_error_wrap_apr(apr_err,
621                                  _("Can't get exclusive lock on file '%s'"),
622                                  svn_dirent_local_style(lockfile_path,
623                                                         scratch_pool));
624      }
625
626    *lockcookie = lockfile;
627  }
628
629  /* We've successfully locked the transaction; mark it as such. */
630  txn->being_written = TRUE;
631
632  return SVN_NO_ERROR;
633}
634
635/* Make sure the length ACTUAL_LENGTH of the proto-revision file PROTO_REV
636   of transaction TXN_ID in filesystem FS matches the proto-index file.
637   Trim any crash / failure related extra data from the proto-rev file.
638
639   If the prototype revision file is too short, we can't do much but bail out.
640
641   Perform all allocations in SCRATCH_POOL. */
642static svn_error_t *
643auto_truncate_proto_rev(svn_fs_t *fs,
644                        apr_file_t *proto_rev,
645                        apr_off_t actual_length,
646                        svn_fs_x__txn_id_t txn_id,
647                        apr_pool_t *scratch_pool)
648{
649  /* Determine file range covered by the proto-index so far.  Note that
650     we always append to both file, i.e. the last index entry also
651     corresponds to the last addition in the rev file. */
652  const char *path = svn_fs_x__path_p2l_proto_index(fs, txn_id, scratch_pool);
653  apr_file_t *file;
654  apr_off_t indexed_length;
655
656  SVN_ERR(svn_fs_x__p2l_proto_index_open(&file, path, scratch_pool));
657  SVN_ERR(svn_fs_x__p2l_proto_index_next_offset(&indexed_length, file,
658                                                scratch_pool));
659  SVN_ERR(svn_io_file_close(file, scratch_pool));
660
661  /* Handle mismatches. */
662  if (indexed_length < actual_length)
663    SVN_ERR(svn_io_file_trunc(proto_rev, indexed_length, scratch_pool));
664  else if (indexed_length > actual_length)
665    return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
666                             NULL,
667                             _("p2l proto index offset %s beyond proto"
668                               "rev file size %s for TXN %s"),
669                             apr_off_t_toa(scratch_pool, indexed_length),
670                             apr_off_t_toa(scratch_pool, actual_length),
671                             svn_fs_x__txn_name(txn_id, scratch_pool));
672
673  return SVN_NO_ERROR;
674}
675
676/* Get a handle to the prototype revision file for transaction TXN_ID in
677   filesystem FS, and lock it for writing.  Return FILE, a file handle
678   positioned at the end of the file, and LOCKCOOKIE, a cookie that
679   should be passed to unlock_proto_rev() to unlock the file once FILE
680   has been closed.
681
682   If the prototype revision file is already locked, return error
683   SVN_ERR_FS_REP_BEING_WRITTEN.
684
685   Perform all allocations in POOL. */
686static svn_error_t *
687get_writable_proto_rev(apr_file_t **file,
688                       void **lockcookie,
689                       svn_fs_t *fs,
690                       svn_fs_x__txn_id_t txn_id,
691                       apr_pool_t *pool)
692{
693  get_writable_proto_rev_baton_t b;
694  svn_error_t *err;
695  apr_off_t end_offset = 0;
696
697  b.lockcookie = lockcookie;
698  b.txn_id = txn_id;
699
700  SVN_ERR(with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool));
701
702  /* Now open the prototype revision file and seek to the end. */
703  err = svn_io_file_open(file,
704                         svn_fs_x__path_txn_proto_rev(fs, txn_id, pool),
705                         APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT, pool);
706
707  /* You might expect that we could dispense with the following seek
708     and achieve the same thing by opening the file using APR_APPEND.
709     Unfortunately, APR's buffered file implementation unconditionally
710     places its initial file pointer at the start of the file (even for
711     files opened with APR_APPEND), so we need this seek to reconcile
712     the APR file pointer to the OS file pointer (since we need to be
713     able to read the current file position later). */
714  if (!err)
715    err = svn_io_file_seek(*file, APR_END, &end_offset, pool);
716
717  /* We don't want unused sections (such as leftovers from failed delta
718     stream) in our file.  If we use log addressing, we would need an
719     index entry for the unused section and that section would need to
720     be all NUL by convention.  So, detect and fix those cases by truncating
721     the protorev file. */
722  if (!err)
723    err = auto_truncate_proto_rev(fs, *file, end_offset, txn_id, pool);
724
725  if (err)
726    {
727      err = svn_error_compose_create(
728              err,
729              unlock_proto_rev(fs, txn_id, *lockcookie, pool));
730
731      *lockcookie = NULL;
732    }
733
734  return svn_error_trace(err);
735}
736
737/* Callback used in the implementation of purge_shared_txn(). */
738static svn_error_t *
739purge_shared_txn_body(svn_fs_t *fs,
740                      const void *baton,
741                      apr_pool_t *scratch_pool)
742{
743  svn_fs_x__txn_id_t txn_id = *(const svn_fs_x__txn_id_t *)baton;
744
745  free_shared_txn(fs, txn_id);
746
747  return SVN_NO_ERROR;
748}
749
750/* Purge the shared data for transaction TXN_ID in filesystem FS.
751   Perform all temporary allocations in SCRATCH_POOL. */
752static svn_error_t *
753purge_shared_txn(svn_fs_t *fs,
754                 svn_fs_x__txn_id_t txn_id,
755                 apr_pool_t *scratch_pool)
756{
757  return with_txnlist_lock(fs, purge_shared_txn_body, &txn_id, scratch_pool);
758}
759
760
761svn_boolean_t
762svn_fs_x__is_fresh_txn_root(svn_fs_x__noderev_t *noderev)
763{
764  /* Is it a root node? */
765  if (noderev->noderev_id.number != SVN_FS_X__ITEM_INDEX_ROOT_NODE)
766    return FALSE;
767
768  /* ... in a transaction? */
769  if (!svn_fs_x__is_txn(noderev->noderev_id.change_set))
770    return FALSE;
771
772  /* ... with no prop change in that txn?
773     (Once we set a property, the prop rep will never become NULL again.) */
774  if (noderev->prop_rep && svn_fs_x__is_txn(noderev->prop_rep->id.change_set))
775    return FALSE;
776
777  /* ... and no sub-tree change?
778     (Once we set a text, the data rep will never become NULL again.) */
779  if (noderev->data_rep && svn_fs_x__is_txn(noderev->data_rep->id.change_set))
780    return FALSE;
781
782  /* Root node of a txn with no changes. */
783  return TRUE;
784}
785
786svn_error_t *
787svn_fs_x__put_node_revision(svn_fs_t *fs,
788                            svn_fs_x__noderev_t *noderev,
789                            apr_pool_t *scratch_pool)
790{
791  apr_file_t *noderev_file;
792  const svn_fs_x__id_t *id = &noderev->noderev_id;
793
794  if (! svn_fs_x__is_txn(id->change_set))
795    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
796                             _("Attempted to write to non-transaction '%s'"),
797                             svn_fs_x__id_unparse(id, scratch_pool)->data);
798
799  SVN_ERR(svn_io_file_open(&noderev_file,
800                           svn_fs_x__path_txn_node_rev(fs, id, scratch_pool,
801                                                       scratch_pool),
802                           APR_WRITE | APR_CREATE | APR_TRUNCATE
803                           | APR_BUFFERED, APR_OS_DEFAULT, scratch_pool));
804
805  SVN_ERR(svn_fs_x__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE,
806                                                           scratch_pool),
807                                  noderev, scratch_pool));
808
809  SVN_ERR(svn_io_file_close(noderev_file, scratch_pool));
810
811  return SVN_NO_ERROR;
812}
813
814/* For the in-transaction NODEREV within FS, write the sha1->rep mapping
815 * file in the respective transaction, if rep sharing has been enabled etc.
816 * Use SCATCH_POOL for temporary allocations.
817 */
818static svn_error_t *
819store_sha1_rep_mapping(svn_fs_t *fs,
820                       svn_fs_x__noderev_t *noderev,
821                       apr_pool_t *scratch_pool)
822{
823  svn_fs_x__data_t *ffd = fs->fsap_data;
824
825  /* if rep sharing has been enabled and the noderev has a data rep and
826   * its SHA-1 is known, store the rep struct under its SHA1. */
827  if (   ffd->rep_sharing_allowed
828      && noderev->data_rep
829      && noderev->data_rep->has_sha1)
830    {
831      apr_file_t *rep_file;
832      apr_int64_t txn_id
833        = svn_fs_x__get_txn_id(noderev->data_rep->id.change_set);
834      const char *file_name
835        = svn_fs_x__path_txn_sha1(fs, txn_id,
836                                  noderev->data_rep->sha1_digest,
837                                  scratch_pool);
838      svn_stringbuf_t *rep_string
839        = svn_fs_x__unparse_representation(noderev->data_rep,
840                                           (noderev->kind == svn_node_dir),
841                                           scratch_pool, scratch_pool);
842
843      SVN_ERR(svn_io_file_open(&rep_file, file_name,
844                               APR_WRITE | APR_CREATE | APR_TRUNCATE
845                               | APR_BUFFERED, APR_OS_DEFAULT, scratch_pool));
846
847      SVN_ERR(svn_io_file_write_full(rep_file, rep_string->data,
848                                     rep_string->len, NULL, scratch_pool));
849
850      SVN_ERR(svn_io_file_close(rep_file, scratch_pool));
851    }
852
853  return SVN_NO_ERROR;
854}
855
856static svn_error_t *
857unparse_dir_entry(svn_fs_x__dirent_t *dirent,
858                  svn_stream_t *stream,
859                  apr_pool_t *scratch_pool)
860{
861  const char *val
862    = apr_psprintf(scratch_pool, "%s %s",
863                   (dirent->kind == svn_node_file) ? SVN_FS_X__KIND_FILE
864                                                   : SVN_FS_X__KIND_DIR,
865                   svn_fs_x__id_unparse(&dirent->id, scratch_pool)->data);
866
867  SVN_ERR(svn_stream_printf(stream, scratch_pool, "K %" APR_SIZE_T_FMT
868                            "\n%s\nV %" APR_SIZE_T_FMT "\n%s\n",
869                            strlen(dirent->name), dirent->name,
870                            strlen(val), val));
871  return SVN_NO_ERROR;
872}
873
874/* Write the directory given as array of dirent structs in ENTRIES to STREAM.
875   Perform temporary allocations in SCRATCH_POOL. */
876static svn_error_t *
877unparse_dir_entries(apr_array_header_t *entries,
878                    svn_stream_t *stream,
879                    apr_pool_t *scratch_pool)
880{
881  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
882  int i;
883  for (i = 0; i < entries->nelts; ++i)
884    {
885      svn_fs_x__dirent_t *dirent;
886
887      svn_pool_clear(iterpool);
888      dirent = APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *);
889      SVN_ERR(unparse_dir_entry(dirent, stream, iterpool));
890    }
891
892  SVN_ERR(svn_stream_printf(stream, scratch_pool, "%s\n",
893                            SVN_HASH_TERMINATOR));
894
895  svn_pool_destroy(iterpool);
896  return SVN_NO_ERROR;
897}
898
899/* Return a deep copy of SOURCE and allocate it in RESULT_POOL.
900 */
901static svn_fs_x__change_t *
902path_change_dup(const svn_fs_x__change_t *source,
903                apr_pool_t *result_pool)
904{
905  svn_fs_x__change_t *result
906    = apr_pmemdup(result_pool, source, sizeof(*source));
907  result->path.data
908    = apr_pstrmemdup(result_pool, source->path.data, source->path.len);
909
910  if (source->copyfrom_path)
911    result->copyfrom_path = apr_pstrdup(result_pool, source->copyfrom_path);
912
913  return result;
914}
915
916/* Merge the internal-use-only CHANGE into a hash of public-FS
917   svn_fs_x__change_t CHANGED_PATHS, collapsing multiple changes into a
918   single summarical (is that real word?) change per path.  DELETIONS is
919   also a path->svn_fs_x__change_t hash and contains all the deletions
920   that got turned into a replacement. */
921static svn_error_t *
922fold_change(apr_hash_t *changed_paths,
923            apr_hash_t *deletions,
924            const svn_fs_x__change_t *change)
925{
926  apr_pool_t *pool = apr_hash_pool_get(changed_paths);
927  svn_fs_x__change_t *old_change, *new_change;
928  const svn_string_t *path = &change->path;
929
930  if ((old_change = apr_hash_get(changed_paths, path->data, path->len)))
931    {
932      /* This path already exists in the hash, so we have to merge
933         this change into the already existing one. */
934
935      /* Sanity check:  only allow unused node revision IDs in the
936         `reset' case. */
937      if ((! svn_fs_x__id_used(&change->noderev_id))
938           && (change->change_kind != svn_fs_path_change_reset))
939        return svn_error_create
940          (SVN_ERR_FS_CORRUPT, NULL,
941           _("Missing required node revision ID"));
942
943      /* Sanity check: we should be talking about the same node
944         revision ID as our last change except where the last change
945         was a deletion. */
946      if (svn_fs_x__id_used(&change->noderev_id)
947          && (!svn_fs_x__id_eq(&old_change->noderev_id, &change->noderev_id))
948          && (old_change->change_kind != svn_fs_path_change_delete))
949        return svn_error_create
950          (SVN_ERR_FS_CORRUPT, NULL,
951           _("Invalid change ordering: new node revision ID "
952             "without delete"));
953
954      /* Sanity check: an add, replacement, or reset must be the first
955         thing to follow a deletion. */
956      if ((old_change->change_kind == svn_fs_path_change_delete)
957          && (! ((change->change_kind == svn_fs_path_change_replace)
958                 || (change->change_kind == svn_fs_path_change_reset)
959                 || (change->change_kind == svn_fs_path_change_add))))
960        return svn_error_create
961          (SVN_ERR_FS_CORRUPT, NULL,
962           _("Invalid change ordering: non-add change on deleted path"));
963
964      /* Sanity check: an add can't follow anything except
965         a delete or reset.  */
966      if ((change->change_kind == svn_fs_path_change_add)
967          && (old_change->change_kind != svn_fs_path_change_delete)
968          && (old_change->change_kind != svn_fs_path_change_reset))
969        return svn_error_create
970          (SVN_ERR_FS_CORRUPT, NULL,
971           _("Invalid change ordering: add change on preexisting path"));
972
973      /* Now, merge that change in. */
974      switch (change->change_kind)
975        {
976        case svn_fs_path_change_reset:
977          /* A reset here will simply remove the path change from the
978             hash. */
979          apr_hash_set(changed_paths, path->data, path->len, NULL);
980          break;
981
982        case svn_fs_path_change_delete:
983          if (old_change->change_kind == svn_fs_path_change_add)
984            {
985              /* If the path was introduced in this transaction via an
986                 add, and we are deleting it, just remove the path
987                 altogether.  (The caller will delete any child paths.) */
988              apr_hash_set(changed_paths, path->data, path->len, NULL);
989            }
990          else if (old_change->change_kind == svn_fs_path_change_replace)
991            {
992              /* A deleting a 'replace' restore the original deletion. */
993              new_change = apr_hash_get(deletions, path->data, path->len);
994              SVN_ERR_ASSERT(new_change);
995              apr_hash_set(changed_paths, path->data, path->len, new_change);
996            }
997          else
998            {
999              /* A deletion overrules a previous change (modify). */
1000              new_change = path_change_dup(change, pool);
1001              apr_hash_set(changed_paths, path->data, path->len, new_change);
1002            }
1003          break;
1004
1005        case svn_fs_path_change_add:
1006        case svn_fs_path_change_replace:
1007          /* An add at this point must be following a previous delete,
1008             so treat it just like a replace.  Remember the original
1009             deletion such that we are able to delete this path again
1010             (the replacement may have changed node kind and id). */
1011          new_change = path_change_dup(change, pool);
1012          new_change->change_kind = svn_fs_path_change_replace;
1013
1014          apr_hash_set(changed_paths, path->data, path->len, new_change);
1015
1016          /* Remember the original change.
1017           * Make sure to allocate the hash key in a durable pool. */
1018          apr_hash_set(deletions,
1019                       apr_pstrmemdup(apr_hash_pool_get(deletions),
1020                                      path->data, path->len),
1021                       path->len, old_change);
1022          break;
1023
1024        case svn_fs_path_change_modify:
1025        default:
1026          /* If the new change modifies some attribute of the node, set
1027             the corresponding flag, whether it already was set or not.
1028             Note: We do not reset a flag to FALSE if a change is undone. */
1029          if (change->text_mod)
1030            old_change->text_mod = TRUE;
1031          if (change->prop_mod)
1032            old_change->prop_mod = TRUE;
1033          if (change->mergeinfo_mod == svn_tristate_true)
1034            old_change->mergeinfo_mod = svn_tristate_true;
1035          break;
1036        }
1037    }
1038  else
1039    {
1040      /* Add this path.  The API makes no guarantees that this (new) key
1041         will not be retained.  Thus, we copy the key into the target pool
1042         to ensure a proper lifetime.  */
1043      new_change = path_change_dup(change, pool);
1044      apr_hash_set(changed_paths, new_change->path.data,
1045                   new_change->path.len, new_change);
1046    }
1047
1048  return SVN_NO_ERROR;
1049}
1050
1051/* Baton type to be used with process_changes(). */
1052typedef struct process_changes_baton_t
1053{
1054  /* Folded list of path changes. */
1055  apr_hash_t *changed_paths;
1056
1057  /* Path changes that are deletions and have been turned into
1058     replacements.  If those replacements get deleted again, this
1059     container contains the record that we have to revert to. */
1060  apr_hash_t *deletions;
1061} process_changes_baton_t;
1062
1063/* An implementation of svn_fs_x__change_receiver_t.
1064   Examine all the changed path entries in CHANGES and store them in
1065   *CHANGED_PATHS.  Folding is done to remove redundant or unnecessary
1066   data. Use SCRATCH_POOL for temporary allocations. */
1067static svn_error_t *
1068process_changes(void *baton_p,
1069                svn_fs_x__change_t *change,
1070                apr_pool_t *scratch_pool)
1071{
1072  process_changes_baton_t *baton = baton_p;
1073
1074  SVN_ERR(fold_change(baton->changed_paths, baton->deletions, change));
1075
1076  /* Now, if our change was a deletion or replacement, we have to
1077     blow away any changes thus far on paths that are (or, were)
1078     children of this path.
1079     ### i won't bother with another iteration pool here -- at
1080     most we talking about a few extra dups of paths into what
1081     is already a temporary subpool.
1082  */
1083
1084  if ((change->change_kind == svn_fs_path_change_delete)
1085       || (change->change_kind == svn_fs_path_change_replace))
1086    {
1087      apr_hash_index_t *hi;
1088
1089      /* a potential child path must contain at least 2 more chars
1090         (the path separator plus at least one char for the name).
1091         Also, we should not assume that all paths have been normalized
1092         i.e. some might have trailing path separators.
1093      */
1094      apr_ssize_t path_len = change->path.len;
1095      apr_ssize_t min_child_len = path_len == 0
1096                                ? 1
1097                                : change->path.data[path_len-1] == '/'
1098                                    ? path_len + 1
1099                                    : path_len + 2;
1100
1101      /* CAUTION: This is the inner loop of an O(n^2) algorithm.
1102         The number of changes to process may be >> 1000.
1103         Therefore, keep the inner loop as tight as possible.
1104      */
1105      for (hi = apr_hash_first(scratch_pool, baton->changed_paths);
1106           hi;
1107           hi = apr_hash_next(hi))
1108        {
1109          /* KEY is the path. */
1110          const void *path;
1111          apr_ssize_t klen;
1112          apr_hash_this(hi, &path, &klen, NULL);
1113
1114          /* If we come across a child of our path, remove it.
1115             Call svn_fspath__skip_ancestor only if there is a chance that
1116             this is actually a sub-path.
1117           */
1118          if (klen >= min_child_len)
1119            {
1120              const char *child;
1121
1122              child = svn_fspath__skip_ancestor(change->path.data, path);
1123              if (child && child[0] != '\0')
1124                apr_hash_set(baton->changed_paths, path, klen, NULL);
1125            }
1126        }
1127    }
1128
1129  return SVN_NO_ERROR;
1130}
1131
1132svn_error_t *
1133svn_fs_x__txn_changes_fetch(apr_hash_t **changed_paths_p,
1134                            svn_fs_t *fs,
1135                            svn_fs_x__txn_id_t txn_id,
1136                            apr_pool_t *pool)
1137{
1138  apr_file_t *file;
1139  apr_hash_t *changed_paths = apr_hash_make(pool);
1140  apr_pool_t *scratch_pool = svn_pool_create(pool);
1141  process_changes_baton_t baton;
1142
1143  baton.changed_paths = changed_paths;
1144  baton.deletions = apr_hash_make(scratch_pool);
1145
1146  SVN_ERR(svn_io_file_open(&file,
1147                           svn_fs_x__path_txn_changes(fs, txn_id, scratch_pool),
1148                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
1149                           scratch_pool));
1150
1151  SVN_ERR(svn_fs_x__read_changes_incrementally(
1152                                  svn_stream_from_aprfile2(file, TRUE,
1153                                                           scratch_pool),
1154                                  process_changes, &baton,
1155                                  scratch_pool));
1156  svn_pool_destroy(scratch_pool);
1157
1158  *changed_paths_p = changed_paths;
1159
1160  return SVN_NO_ERROR;
1161}
1162
1163/* Copy a revision node-rev SRC into the current transaction TXN_ID in
1164   the filesystem FS.  This is only used to create the root of a transaction.
1165   Temporary allocations are from SCRATCH_POOL.  */
1166static svn_error_t *
1167create_new_txn_noderev_from_rev(svn_fs_t *fs,
1168                                svn_fs_x__txn_id_t txn_id,
1169                                svn_fs_x__id_t *src,
1170                                apr_pool_t *scratch_pool)
1171{
1172  svn_fs_x__noderev_t *noderev;
1173  SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, src, scratch_pool,
1174                                      scratch_pool));
1175
1176  /* This must be a root node. */
1177  SVN_ERR_ASSERT(   noderev->node_id.number == 0
1178                 && noderev->copy_id.number == 0);
1179
1180  if (svn_fs_x__is_txn(noderev->noderev_id.change_set))
1181    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1182                            _("Copying from transactions not allowed"));
1183
1184  noderev->predecessor_id = noderev->noderev_id;
1185  noderev->predecessor_count++;
1186  noderev->copyfrom_path = NULL;
1187  noderev->copyfrom_rev = SVN_INVALID_REVNUM;
1188
1189  /* For the transaction root, the copyroot never changes. */
1190  svn_fs_x__init_txn_root(&noderev->noderev_id, txn_id);
1191
1192  return svn_fs_x__put_node_revision(fs, noderev, scratch_pool);
1193}
1194
1195/* A structure used by get_and_increment_txn_key_body(). */
1196typedef struct get_and_increment_txn_key_baton_t
1197{
1198  svn_fs_t *fs;
1199  apr_uint64_t txn_number;
1200} get_and_increment_txn_key_baton_t;
1201
1202/* Callback used in the implementation of create_txn_dir().  This gets
1203   the current base 36 value in PATH_TXN_CURRENT and increments it.
1204   It returns the original value by the baton. */
1205static svn_error_t *
1206get_and_increment_txn_key_body(void *baton,
1207                               apr_pool_t *scratch_pool)
1208{
1209  get_and_increment_txn_key_baton_t *cb = baton;
1210  const char *txn_current_filename = svn_fs_x__path_txn_current(cb->fs,
1211                                                                scratch_pool);
1212  const char *tmp_filename;
1213  char new_id_str[SVN_INT64_BUFFER_SIZE];
1214
1215  svn_stringbuf_t *buf;
1216  SVN_ERR(svn_fs_x__read_content(&buf, txn_current_filename, scratch_pool));
1217
1218  /* remove trailing newlines */
1219  cb->txn_number = svn__base36toui64(NULL, buf->data);
1220
1221  /* Increment the key and add a trailing \n to the string so the
1222     txn-current file has a newline in it. */
1223  SVN_ERR(svn_io_write_unique(&tmp_filename,
1224                              svn_dirent_dirname(txn_current_filename,
1225                                                 scratch_pool),
1226                              new_id_str,
1227                              svn__ui64tobase36(new_id_str, cb->txn_number+1),
1228                              svn_io_file_del_none, scratch_pool));
1229  SVN_ERR(svn_fs_x__move_into_place(tmp_filename, txn_current_filename,
1230                                    txn_current_filename, scratch_pool));
1231
1232  return SVN_NO_ERROR;
1233}
1234
1235/* Create a unique directory for a transaction in FS based on revision REV.
1236   Return the ID for this transaction in *ID_P and *TXN_ID.  Use a sequence
1237   value in the transaction ID to prevent reuse of transaction IDs. */
1238static svn_error_t *
1239create_txn_dir(const char **id_p,
1240               svn_fs_x__txn_id_t *txn_id,
1241               svn_fs_t *fs,
1242               apr_pool_t *result_pool,
1243               apr_pool_t *scratch_pool)
1244{
1245  get_and_increment_txn_key_baton_t cb;
1246  const char *txn_dir;
1247
1248  /* Get the current transaction sequence value, which is a base-36
1249     number, from the txn-current file, and write an
1250     incremented value back out to the file.  Place the revision
1251     number the transaction is based off into the transaction id. */
1252  cb.fs = fs;
1253  SVN_ERR(svn_fs_x__with_txn_current_lock(fs,
1254                                          get_and_increment_txn_key_body,
1255                                          &cb,
1256                                          scratch_pool));
1257  *txn_id = cb.txn_number;
1258
1259  *id_p = svn_fs_x__txn_name(*txn_id, result_pool);
1260  txn_dir = svn_fs_x__path_txn_dir(fs, *txn_id, scratch_pool);
1261
1262  return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, scratch_pool);
1263}
1264
1265/* Create a new transaction in filesystem FS, based on revision REV,
1266   and store it in *TXN_P, allocated in RESULT_POOL.  Allocate necessary
1267   temporaries from SCRATCH_POOL. */
1268static svn_error_t *
1269create_txn(svn_fs_txn_t **txn_p,
1270           svn_fs_t *fs,
1271           svn_revnum_t rev,
1272           apr_pool_t *result_pool,
1273           apr_pool_t *scratch_pool)
1274{
1275  svn_fs_txn_t *txn;
1276  fs_txn_data_t *ftd;
1277  svn_fs_x__id_t root_id;
1278
1279  txn = apr_pcalloc(result_pool, sizeof(*txn));
1280  ftd = apr_pcalloc(result_pool, sizeof(*ftd));
1281
1282  /* Valid revision number? */
1283  SVN_ERR(svn_fs_x__ensure_revision_exists(rev, fs, scratch_pool));
1284
1285  /* Get the txn_id. */
1286  SVN_ERR(create_txn_dir(&txn->id, &ftd->txn_id, fs, result_pool,
1287                         scratch_pool));
1288
1289  txn->fs = fs;
1290  txn->base_rev = rev;
1291
1292  txn->vtable = &txn_vtable;
1293  txn->fsap_data = ftd;
1294  *txn_p = txn;
1295
1296  /* Create a new root node for this transaction. */
1297  svn_fs_x__init_rev_root(&root_id, rev);
1298  SVN_ERR(create_new_txn_noderev_from_rev(fs, ftd->txn_id, &root_id,
1299                                          scratch_pool));
1300
1301  /* Create an empty rev file. */
1302  SVN_ERR(svn_io_file_create_empty(
1303              svn_fs_x__path_txn_proto_rev(fs, ftd->txn_id, scratch_pool),
1304              scratch_pool));
1305
1306  /* Create an empty rev-lock file. */
1307  SVN_ERR(svn_io_file_create_empty(
1308              svn_fs_x__path_txn_proto_rev_lock(fs, ftd->txn_id, scratch_pool),
1309              scratch_pool));
1310
1311  /* Create an empty changes file. */
1312  SVN_ERR(svn_io_file_create_empty(
1313              svn_fs_x__path_txn_changes(fs, ftd->txn_id, scratch_pool),
1314              scratch_pool));
1315
1316  /* Create the next-ids file. */
1317  SVN_ERR(svn_io_file_create(
1318              svn_fs_x__path_txn_next_ids(fs, ftd->txn_id, scratch_pool),
1319              "0 0\n", scratch_pool));
1320
1321  return SVN_NO_ERROR;
1322}
1323
1324/* Store the property list for transaction TXN_ID in PROPLIST.
1325   Perform temporary allocations in POOL. */
1326static svn_error_t *
1327get_txn_proplist(apr_hash_t *proplist,
1328                 svn_fs_t *fs,
1329                 svn_fs_x__txn_id_t txn_id,
1330                 apr_pool_t *pool)
1331{
1332  svn_stream_t *stream;
1333
1334  /* Check for issue #3696. (When we find and fix the cause, we can change
1335   * this to an assertion.) */
1336  if (txn_id == SVN_FS_X__INVALID_TXN_ID)
1337    return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1338                            _("Internal error: a null transaction id was "
1339                              "passed to get_txn_proplist()"));
1340
1341  /* Open the transaction properties file. */
1342  SVN_ERR(svn_stream_open_readonly(&stream,
1343                                   svn_fs_x__path_txn_props(fs, txn_id, pool),
1344                                   pool, pool));
1345
1346  /* Read in the property list. */
1347  SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
1348
1349  return svn_stream_close(stream);
1350}
1351
1352/* Save the property list PROPS as the revprops for transaction TXN_ID
1353   in FS.  Perform temporary allocations in SCRATCH_POOL. */
1354static svn_error_t *
1355set_txn_proplist(svn_fs_t *fs,
1356                 svn_fs_x__txn_id_t txn_id,
1357                 apr_hash_t *props,
1358                 svn_boolean_t final,
1359                 apr_pool_t *scratch_pool)
1360{
1361  svn_stringbuf_t *buf;
1362  svn_stream_t *stream;
1363
1364  /* Write out the new file contents to BUF. */
1365  buf = svn_stringbuf_create_ensure(1024, scratch_pool);
1366  stream = svn_stream_from_stringbuf(buf, scratch_pool);
1367  SVN_ERR(svn_hash_write2(props, stream, SVN_HASH_TERMINATOR, scratch_pool));
1368  SVN_ERR(svn_stream_close(stream));
1369
1370  /* Open the transaction properties file and write new contents to it. */
1371  SVN_ERR(svn_io_write_atomic((final
1372                               ? svn_fs_x__path_txn_props_final(fs, txn_id,
1373                                                                scratch_pool)
1374                               : svn_fs_x__path_txn_props(fs, txn_id,
1375                                                          scratch_pool)),
1376                              buf->data, buf->len,
1377                              NULL /* copy_perms_path */, scratch_pool));
1378  return SVN_NO_ERROR;
1379}
1380
1381
1382svn_error_t *
1383svn_fs_x__change_txn_prop(svn_fs_txn_t *txn,
1384                          const char *name,
1385                          const svn_string_t *value,
1386                          apr_pool_t *scratch_pool)
1387{
1388  apr_array_header_t *props = apr_array_make(scratch_pool, 1,
1389                                             sizeof(svn_prop_t));
1390  svn_prop_t prop;
1391
1392  prop.name = name;
1393  prop.value = value;
1394  APR_ARRAY_PUSH(props, svn_prop_t) = prop;
1395
1396  return svn_fs_x__change_txn_props(txn, props, scratch_pool);
1397}
1398
1399svn_error_t *
1400svn_fs_x__change_txn_props(svn_fs_txn_t *txn,
1401                           const apr_array_header_t *props,
1402                           apr_pool_t *scratch_pool)
1403{
1404  fs_txn_data_t *ftd = txn->fsap_data;
1405  apr_hash_t *txn_prop = apr_hash_make(scratch_pool);
1406  int i;
1407  svn_error_t *err;
1408
1409  err = get_txn_proplist(txn_prop, txn->fs, ftd->txn_id, scratch_pool);
1410  /* Here - and here only - we need to deal with the possibility that the
1411     transaction property file doesn't yet exist.  The rest of the
1412     implementation assumes that the file exists, but we're called to set the
1413     initial transaction properties as the transaction is being created. */
1414  if (err && (APR_STATUS_IS_ENOENT(err->apr_err)))
1415    svn_error_clear(err);
1416  else if (err)
1417    return svn_error_trace(err);
1418
1419  for (i = 0; i < props->nelts; i++)
1420    {
1421      svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
1422
1423      if (svn_hash_gets(txn_prop, SVN_FS__PROP_TXN_CLIENT_DATE)
1424          && !strcmp(prop->name, SVN_PROP_REVISION_DATE))
1425        svn_hash_sets(txn_prop, SVN_FS__PROP_TXN_CLIENT_DATE,
1426                      svn_string_create("1", scratch_pool));
1427
1428      svn_hash_sets(txn_prop, prop->name, prop->value);
1429    }
1430
1431  /* Create a new version of the file and write out the new props. */
1432  /* Open the transaction properties file. */
1433  SVN_ERR(set_txn_proplist(txn->fs, ftd->txn_id, txn_prop, FALSE,
1434                           scratch_pool));
1435
1436  return SVN_NO_ERROR;
1437}
1438
1439svn_error_t *
1440svn_fs_x__get_txn(svn_fs_x__transaction_t **txn_p,
1441                  svn_fs_t *fs,
1442                  svn_fs_x__txn_id_t txn_id,
1443                  apr_pool_t *pool)
1444{
1445  svn_fs_x__transaction_t *txn;
1446  svn_fs_x__noderev_t *noderev;
1447  svn_fs_x__id_t root_id;
1448
1449  txn = apr_pcalloc(pool, sizeof(*txn));
1450  txn->proplist = apr_hash_make(pool);
1451
1452  SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool));
1453  svn_fs_x__init_txn_root(&root_id, txn_id);
1454
1455  SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, &root_id, pool, pool));
1456
1457  txn->base_rev = svn_fs_x__get_revnum(noderev->predecessor_id.change_set);
1458  txn->copies = NULL;
1459
1460  *txn_p = txn;
1461
1462  return SVN_NO_ERROR;
1463}
1464
1465/* If it is supported by the format of file system FS, store the (ITEM_INDEX,
1466 * OFFSET) pair in the log-to-phys proto index file of transaction TXN_ID.
1467 * Use SCRATCH_POOL for temporary allocations.
1468 */
1469static svn_error_t *
1470store_l2p_index_entry(svn_fs_t *fs,
1471                      svn_fs_x__txn_id_t txn_id,
1472                      apr_off_t offset,
1473                      apr_uint64_t item_index,
1474                      apr_pool_t *scratch_pool)
1475{
1476  const char *path = svn_fs_x__path_l2p_proto_index(fs, txn_id, scratch_pool);
1477  apr_file_t *file;
1478  SVN_ERR(svn_fs_x__l2p_proto_index_open(&file, path, scratch_pool));
1479  SVN_ERR(svn_fs_x__l2p_proto_index_add_entry(file, offset, 0,
1480                                              item_index, scratch_pool));
1481  SVN_ERR(svn_io_file_close(file, scratch_pool));
1482
1483  return SVN_NO_ERROR;
1484}
1485
1486/* If it is supported by the format of file system FS, store ENTRY in the
1487 * phys-to-log proto index file of transaction TXN_ID.
1488 * Use SCRATCH_POOL for temporary allocations.
1489 */
1490static svn_error_t *
1491store_p2l_index_entry(svn_fs_t *fs,
1492                      svn_fs_x__txn_id_t txn_id,
1493                      svn_fs_x__p2l_entry_t *entry,
1494                      apr_pool_t *scratch_pool)
1495{
1496  const char *path = svn_fs_x__path_p2l_proto_index(fs, txn_id, scratch_pool);
1497  apr_file_t *file;
1498  SVN_ERR(svn_fs_x__p2l_proto_index_open(&file, path, scratch_pool));
1499  SVN_ERR(svn_fs_x__p2l_proto_index_add_entry(file, entry, scratch_pool));
1500  SVN_ERR(svn_io_file_close(file, scratch_pool));
1501
1502  return SVN_NO_ERROR;
1503}
1504
1505/* Allocate an item index in the transaction TXN_ID of file system FS and
1506 * return it in *ITEM_INDEX.  Use SCRATCH_POOL for temporary allocations.
1507 */
1508static svn_error_t *
1509allocate_item_index(apr_uint64_t *item_index,
1510                    svn_fs_t *fs,
1511                    svn_fs_x__txn_id_t txn_id,
1512                    apr_pool_t *scratch_pool)
1513{
1514  apr_file_t *file;
1515  char buffer[SVN_INT64_BUFFER_SIZE] = { 0 };
1516  svn_boolean_t eof = FALSE;
1517  apr_size_t to_write;
1518  apr_size_t read;
1519  apr_off_t offset = 0;
1520
1521  /* read number */
1522  SVN_ERR(svn_io_file_open(&file,
1523                            svn_fs_x__path_txn_item_index(fs, txn_id,
1524                                                          scratch_pool),
1525                            APR_READ | APR_WRITE
1526                            | APR_CREATE | APR_BUFFERED,
1527                            APR_OS_DEFAULT, scratch_pool));
1528  SVN_ERR(svn_io_file_read_full2(file, buffer, sizeof(buffer)-1,
1529                                  &read, &eof, scratch_pool));
1530  if (read)
1531    SVN_ERR(svn_cstring_atoui64(item_index, buffer));
1532  else
1533    *item_index = SVN_FS_X__ITEM_INDEX_FIRST_USER;
1534
1535  /* increment it */
1536  to_write = svn__ui64toa(buffer, *item_index + 1);
1537
1538  /* write it back to disk */
1539  SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool));
1540  SVN_ERR(svn_io_file_write_full(file, buffer, to_write, NULL, scratch_pool));
1541  SVN_ERR(svn_io_file_close(file, scratch_pool));
1542
1543  return SVN_NO_ERROR;
1544}
1545
1546/* Write out the currently available next node_id NODE_ID and copy_id
1547   COPY_ID for transaction TXN_ID in filesystem FS.  The next node-id is
1548   used both for creating new unique nodes for the given transaction, as
1549   well as uniquifying representations.  Perform temporary allocations in
1550   SCRATCH_POOL. */
1551static svn_error_t *
1552write_next_ids(svn_fs_t *fs,
1553               svn_fs_x__txn_id_t txn_id,
1554               apr_uint64_t node_id,
1555               apr_uint64_t copy_id,
1556               apr_pool_t *scratch_pool)
1557{
1558  apr_file_t *file;
1559  char buffer[2 * SVN_INT64_BUFFER_SIZE + 2];
1560  char *p = buffer;
1561
1562  p += svn__ui64tobase36(p, node_id);
1563  *(p++) = ' ';
1564  p += svn__ui64tobase36(p, copy_id);
1565  *(p++) = '\n';
1566  *(p++) = '\0';
1567
1568  SVN_ERR(svn_io_file_open(&file,
1569                           svn_fs_x__path_txn_next_ids(fs, txn_id,
1570                                                       scratch_pool),
1571                           APR_WRITE | APR_TRUNCATE,
1572                           APR_OS_DEFAULT, scratch_pool));
1573  SVN_ERR(svn_io_file_write_full(file, buffer, p - buffer, NULL,
1574                                 scratch_pool));
1575  return svn_io_file_close(file, scratch_pool);
1576}
1577
1578/* Find out what the next unique node-id and copy-id are for
1579   transaction TXN_ID in filesystem FS.  Store the results in *NODE_ID
1580   and *COPY_ID.  The next node-id is used both for creating new unique
1581   nodes for the given transaction, as well as uniquifying representations.
1582   Perform temporary allocations in SCRATCH_POOL. */
1583static svn_error_t *
1584read_next_ids(apr_uint64_t *node_id,
1585              apr_uint64_t *copy_id,
1586              svn_fs_t *fs,
1587              svn_fs_x__txn_id_t txn_id,
1588              apr_pool_t *scratch_pool)
1589{
1590  svn_stringbuf_t *buf;
1591  const char *str;
1592  SVN_ERR(svn_fs_x__read_content(&buf,
1593                                 svn_fs_x__path_txn_next_ids(fs, txn_id,
1594                                                             scratch_pool),
1595                                 scratch_pool));
1596
1597  /* Parse this into two separate strings. */
1598
1599  str = buf->data;
1600  *node_id = svn__base36toui64(&str, str);
1601  if (*str != ' ')
1602    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1603                            _("next-id file corrupt"));
1604
1605  ++str;
1606  *copy_id = svn__base36toui64(&str, str);
1607  if (*str != '\n')
1608    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1609                            _("next-id file corrupt"));
1610
1611  return SVN_NO_ERROR;
1612}
1613
1614/* Get a new and unique to this transaction node-id for transaction
1615   TXN_ID in filesystem FS.  Store the new node-id in *NODE_ID_P.
1616   Node-ids are guaranteed to be unique to this transction, but may
1617   not necessarily be sequential.
1618   Perform temporary allocations in SCRATCH_POOL. */
1619static svn_error_t *
1620get_new_txn_node_id(svn_fs_x__id_t *node_id_p,
1621                    svn_fs_t *fs,
1622                    svn_fs_x__txn_id_t txn_id,
1623                    apr_pool_t *scratch_pool)
1624{
1625  apr_uint64_t node_id, copy_id;
1626
1627  /* First read in the current next-ids file. */
1628  SVN_ERR(read_next_ids(&node_id, &copy_id, fs, txn_id, scratch_pool));
1629
1630  node_id_p->change_set = svn_fs_x__change_set_by_txn(txn_id);
1631  node_id_p->number = node_id;
1632
1633  SVN_ERR(write_next_ids(fs, txn_id, ++node_id, copy_id, scratch_pool));
1634
1635  return SVN_NO_ERROR;
1636}
1637
1638svn_error_t *
1639svn_fs_x__reserve_copy_id(svn_fs_x__id_t *copy_id_p,
1640                          svn_fs_t *fs,
1641                          svn_fs_x__txn_id_t txn_id,
1642                          apr_pool_t *scratch_pool)
1643{
1644  apr_uint64_t node_id, copy_id;
1645
1646  /* First read in the current next-ids file. */
1647  SVN_ERR(read_next_ids(&node_id, &copy_id, fs, txn_id, scratch_pool));
1648
1649  copy_id_p->change_set = svn_fs_x__change_set_by_txn(txn_id);
1650  copy_id_p->number = copy_id;
1651
1652  SVN_ERR(write_next_ids(fs, txn_id, node_id, ++copy_id, scratch_pool));
1653
1654  return SVN_NO_ERROR;
1655}
1656
1657svn_error_t *
1658svn_fs_x__create_node(svn_fs_t *fs,
1659                      svn_fs_x__noderev_t *noderev,
1660                      const svn_fs_x__id_t *copy_id,
1661                      svn_fs_x__txn_id_t txn_id,
1662                      apr_pool_t *scratch_pool)
1663{
1664  /* Get a new node-id for this node. */
1665  SVN_ERR(get_new_txn_node_id(&noderev->node_id, fs, txn_id, scratch_pool));
1666
1667  /* Assign copy-id. */
1668  noderev->copy_id = *copy_id;
1669
1670  /* Noderev-id = Change set and item number within this change set. */
1671  noderev->noderev_id.change_set = svn_fs_x__change_set_by_txn(txn_id);
1672  SVN_ERR(allocate_item_index(&noderev->noderev_id.number, fs, txn_id,
1673                              scratch_pool));
1674
1675  SVN_ERR(svn_fs_x__put_node_revision(fs, noderev, scratch_pool));
1676
1677  return SVN_NO_ERROR;
1678}
1679
1680svn_error_t *
1681svn_fs_x__purge_txn(svn_fs_t *fs,
1682                    const char *txn_id_str,
1683                    apr_pool_t *scratch_pool)
1684{
1685  svn_fs_x__txn_id_t txn_id;
1686  SVN_ERR(svn_fs_x__txn_by_name(&txn_id, txn_id_str));
1687
1688  /* Remove the shared transaction object associated with this transaction. */
1689  SVN_ERR(purge_shared_txn(fs, txn_id, scratch_pool));
1690  /* Remove the directory associated with this transaction. */
1691  SVN_ERR(svn_io_remove_dir2(svn_fs_x__path_txn_dir(fs, txn_id, scratch_pool),
1692                             FALSE, NULL, NULL, scratch_pool));
1693
1694  /* Delete protorev and its lock, which aren't in the txn
1695      directory.  It's OK if they don't exist (for example, if this
1696      is post-commit and the proto-rev has been moved into
1697      place). */
1698  SVN_ERR(svn_io_remove_file2(
1699                  svn_fs_x__path_txn_proto_rev(fs, txn_id, scratch_pool),
1700                  TRUE, scratch_pool));
1701  SVN_ERR(svn_io_remove_file2(
1702                  svn_fs_x__path_txn_proto_rev_lock(fs, txn_id, scratch_pool),
1703                  TRUE, scratch_pool));
1704
1705  return SVN_NO_ERROR;
1706}
1707
1708
1709svn_error_t *
1710svn_fs_x__abort_txn(svn_fs_txn_t *txn,
1711                    apr_pool_t *scratch_pool)
1712{
1713  SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
1714
1715  /* Now, purge the transaction. */
1716  SVN_ERR_W(svn_fs_x__purge_txn(txn->fs, txn->id, scratch_pool),
1717            apr_psprintf(scratch_pool, _("Transaction '%s' cleanup failed"),
1718                         txn->id));
1719
1720  return SVN_NO_ERROR;
1721}
1722
1723svn_error_t *
1724svn_fs_x__set_entry(svn_fs_t *fs,
1725                    svn_fs_x__txn_id_t txn_id,
1726                    svn_fs_x__noderev_t *parent_noderev,
1727                    const char *name,
1728                    const svn_fs_x__id_t *id,
1729                    svn_node_kind_t kind,
1730                    apr_pool_t *result_pool,
1731                    apr_pool_t *scratch_pool)
1732{
1733  svn_fs_x__representation_t *rep = parent_noderev->data_rep;
1734  const char *filename
1735    = svn_fs_x__path_txn_node_children(fs, &parent_noderev->noderev_id,
1736                                       scratch_pool, scratch_pool);
1737  apr_file_t *file;
1738  svn_stream_t *out;
1739  svn_fs_x__data_t *ffd = fs->fsap_data;
1740  apr_pool_t *subpool = svn_pool_create(scratch_pool);
1741
1742  if (!rep || !svn_fs_x__is_txn(rep->id.change_set))
1743    {
1744      apr_array_header_t *entries;
1745
1746      /* Before we can modify the directory, we need to dump its old
1747         contents into a mutable representation file. */
1748      SVN_ERR(svn_fs_x__rep_contents_dir(&entries, fs, parent_noderev,
1749                                         subpool, subpool));
1750      SVN_ERR(svn_io_file_open(&file, filename,
1751                               APR_WRITE | APR_CREATE | APR_BUFFERED,
1752                               APR_OS_DEFAULT, scratch_pool));
1753      out = svn_stream_from_aprfile2(file, TRUE, scratch_pool);
1754      SVN_ERR(unparse_dir_entries(entries, out, subpool));
1755
1756      svn_pool_clear(subpool);
1757
1758      /* Provide the parent with a data rep if it had none before
1759         (directories so far empty). */
1760      if (!rep)
1761        {
1762          rep = apr_pcalloc(result_pool, sizeof(*rep));
1763          parent_noderev->data_rep = rep;
1764        }
1765
1766      /* Mark the node-rev's data rep as mutable. */
1767      rep->id.change_set = svn_fs_x__change_set_by_txn(txn_id);
1768      rep->id.number = SVN_FS_X__ITEM_INDEX_UNUSED;
1769
1770      /* Save noderev to disk. */
1771      SVN_ERR(svn_fs_x__put_node_revision(fs, parent_noderev, subpool));
1772    }
1773  else
1774    {
1775      /* The directory rep is already mutable, so just open it for append. */
1776      SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
1777                               APR_OS_DEFAULT, scratch_pool));
1778      out = svn_stream_from_aprfile2(file, TRUE, scratch_pool);
1779    }
1780
1781  /* update directory cache */
1782    {
1783      /* build parameters: (name, new entry) pair */
1784      const svn_fs_x__id_t *key = &(parent_noderev->noderev_id);
1785      replace_baton_t baton;
1786
1787      baton.name = name;
1788      baton.new_entry = NULL;
1789
1790      if (id)
1791        {
1792          baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry));
1793          baton.new_entry->name = name;
1794          baton.new_entry->kind = kind;
1795          baton.new_entry->id = *id;
1796        }
1797
1798      /* actually update the cached directory (if cached) */
1799      SVN_ERR(svn_cache__set_partial(ffd->dir_cache, key,
1800                                     svn_fs_x__replace_dir_entry, &baton,
1801                                     subpool));
1802    }
1803  svn_pool_clear(subpool);
1804
1805  /* Append an incremental hash entry for the entry change. */
1806  if (id)
1807    {
1808      svn_fs_x__dirent_t entry;
1809      entry.name = name;
1810      entry.id = *id;
1811      entry.kind = kind;
1812
1813      SVN_ERR(unparse_dir_entry(&entry, out, subpool));
1814    }
1815  else
1816    {
1817      SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
1818                                strlen(name), name));
1819    }
1820
1821  SVN_ERR(svn_io_file_close(file, subpool));
1822  svn_pool_destroy(subpool);
1823  return SVN_NO_ERROR;
1824}
1825
1826svn_error_t *
1827svn_fs_x__add_change(svn_fs_t *fs,
1828                     svn_fs_x__txn_id_t txn_id,
1829                     const char *path,
1830                     const svn_fs_x__id_t *id,
1831                     svn_fs_path_change_kind_t change_kind,
1832                     svn_boolean_t text_mod,
1833                     svn_boolean_t prop_mod,
1834                     svn_boolean_t mergeinfo_mod,
1835                     svn_node_kind_t node_kind,
1836                     svn_revnum_t copyfrom_rev,
1837                     const char *copyfrom_path,
1838                     apr_pool_t *scratch_pool)
1839{
1840  apr_file_t *file;
1841  svn_fs_x__change_t change;
1842  apr_hash_t *changes = apr_hash_make(scratch_pool);
1843
1844  /* Not using APR_BUFFERED to append change in one atomic write operation. */
1845  SVN_ERR(svn_io_file_open(&file,
1846                           svn_fs_x__path_txn_changes(fs, txn_id,
1847                                                      scratch_pool),
1848                           APR_APPEND | APR_WRITE | APR_CREATE,
1849                           APR_OS_DEFAULT, scratch_pool));
1850
1851  change.path.data = path;
1852  change.path.len = strlen(path);
1853  change.noderev_id = *id;
1854  change.change_kind = change_kind;
1855  change.text_mod = text_mod;
1856  change.prop_mod = prop_mod;
1857  change.mergeinfo_mod = mergeinfo_mod ? svn_tristate_true
1858                                       : svn_tristate_false;
1859  change.node_kind = node_kind;
1860  change.copyfrom_known = TRUE;
1861  change.copyfrom_rev = copyfrom_rev;
1862  if (copyfrom_path)
1863    change.copyfrom_path = apr_pstrdup(scratch_pool, copyfrom_path);
1864
1865  svn_hash_sets(changes, path, &change);
1866  SVN_ERR(svn_fs_x__write_changes(svn_stream_from_aprfile2(file, TRUE,
1867                                                           scratch_pool),
1868                                  fs, changes, FALSE, scratch_pool));
1869
1870  return svn_io_file_close(file, scratch_pool);
1871}
1872
1873/* This baton is used by the representation writing streams.  It keeps
1874   track of the checksum information as well as the total size of the
1875   representation so far. */
1876typedef struct rep_write_baton_t
1877{
1878  /* The FS we are writing to. */
1879  svn_fs_t *fs;
1880
1881  /* Actual file to which we are writing. */
1882  svn_stream_t *rep_stream;
1883
1884  /* A stream from the delta combiner.  Data written here gets
1885     deltified, then eventually written to rep_stream. */
1886  svn_stream_t *delta_stream;
1887
1888  /* Where is this representation header stored. */
1889  apr_off_t rep_offset;
1890
1891  /* Start of the actual data. */
1892  apr_off_t delta_start;
1893
1894  /* How many bytes have been written to this rep already. */
1895  svn_filesize_t rep_size;
1896
1897  /* The node revision for which we're writing out info. */
1898  svn_fs_x__noderev_t *noderev;
1899
1900  /* Actual output file. */
1901  apr_file_t *file;
1902  /* Lock 'cookie' used to unlock the output file once we've finished
1903     writing to it. */
1904  void *lockcookie;
1905
1906  svn_checksum_ctx_t *md5_checksum_ctx;
1907  svn_checksum_ctx_t *sha1_checksum_ctx;
1908
1909  /* Receives the low-level checksum when closing REP_STREAM. */
1910  apr_uint32_t fnv1a_checksum;
1911
1912  /* Local pool, available for allocations that must remain valid as long
1913     as this baton is used but may be cleaned up immediately afterwards. */
1914  apr_pool_t *local_pool;
1915
1916  /* Outer / result pool. */
1917  apr_pool_t *result_pool;
1918} rep_write_baton_t;
1919
1920/* Handler for the write method of the representation writable stream.
1921   BATON is a rep_write_baton_t, DATA is the data to write, and *LEN is
1922   the length of this data. */
1923static svn_error_t *
1924rep_write_contents(void *baton,
1925                   const char *data,
1926                   apr_size_t *len)
1927{
1928  rep_write_baton_t *b = baton;
1929
1930  SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len));
1931  SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len));
1932  b->rep_size += *len;
1933
1934  return svn_stream_write(b->delta_stream, data, len);
1935}
1936
1937/* Set *SPANNED to the number of shards touched when walking WALK steps on
1938 * NODEREV's predecessor chain in FS.
1939 * Use SCRATCH_POOL for temporary allocations.
1940 */
1941static svn_error_t *
1942shards_spanned(int *spanned,
1943               svn_fs_t *fs,
1944               svn_fs_x__noderev_t *noderev,
1945               int walk,
1946               apr_pool_t *scratch_pool)
1947{
1948  svn_fs_x__data_t *ffd = fs->fsap_data;
1949  int shard_size = ffd->max_files_per_dir;
1950  apr_pool_t *iterpool;
1951
1952  int count = walk ? 1 : 0; /* The start of a walk already touches a shard. */
1953  svn_revnum_t shard, last_shard = ffd->youngest_rev_cache / shard_size;
1954  iterpool = svn_pool_create(scratch_pool);
1955  while (walk-- && noderev->predecessor_count)
1956    {
1957      svn_fs_x__id_t id = noderev->predecessor_id;
1958
1959      svn_pool_clear(iterpool);
1960      SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, &id, scratch_pool,
1961                                          iterpool));
1962      shard = svn_fs_x__get_revnum(id.change_set) / shard_size;
1963      if (shard != last_shard)
1964        {
1965          ++count;
1966          last_shard = shard;
1967        }
1968    }
1969  svn_pool_destroy(iterpool);
1970
1971  *spanned = count;
1972  return SVN_NO_ERROR;
1973}
1974
1975/* Given a node-revision NODEREV in filesystem FS, return the
1976   representation in *REP to use as the base for a text representation
1977   delta if PROPS is FALSE.  If PROPS has been set, a suitable props
1978   base representation will be returned.  Perform temporary allocations
1979   in *POOL. */
1980static svn_error_t *
1981choose_delta_base(svn_fs_x__representation_t **rep,
1982                  svn_fs_t *fs,
1983                  svn_fs_x__noderev_t *noderev,
1984                  svn_boolean_t props,
1985                  apr_pool_t *pool)
1986{
1987  /* The zero-based index (counting from the "oldest" end), along NODEREVs line
1988   * predecessors, of the node-rev we will use as delta base. */
1989  int count;
1990  /* The length of the linear part of a delta chain.  (Delta chains use
1991   * skip-delta bits for the high-order bits and are linear in the low-order
1992   * bits.) */
1993  int walk;
1994  svn_fs_x__noderev_t *base;
1995  svn_fs_x__data_t *ffd = fs->fsap_data;
1996  apr_pool_t *iterpool;
1997
1998  /* If we have no predecessors, or that one is empty, then use the empty
1999   * stream as a base. */
2000  if (! noderev->predecessor_count)
2001    {
2002      *rep = NULL;
2003      return SVN_NO_ERROR;
2004    }
2005
2006  /* Flip the rightmost '1' bit of the predecessor count to determine
2007     which file rev (counting from 0) we want to use.  (To see why
2008     count & (count - 1) unsets the rightmost set bit, think about how
2009     you decrement a binary number.) */
2010  count = noderev->predecessor_count;
2011  count = count & (count - 1);
2012
2013  /* Finding the delta base over a very long distance can become extremely
2014     expensive for very deep histories, possibly causing client timeouts etc.
2015     OTOH, this is a rare operation and its gains are minimal. Lets simply
2016     start deltification anew close every other 1000 changes or so.  */
2017  walk = noderev->predecessor_count - count;
2018  if (walk > (int)ffd->max_deltification_walk)
2019    {
2020      *rep = NULL;
2021      return SVN_NO_ERROR;
2022    }
2023
2024  /* We use skip delta for limiting the number of delta operations
2025     along very long node histories.  Close to HEAD however, we create
2026     a linear history to minimize delta size.  */
2027  if (walk < (int)ffd->max_linear_deltification)
2028    {
2029      int shards;
2030      SVN_ERR(shards_spanned(&shards, fs, noderev, walk, pool));
2031
2032      /* We also don't want the linear deltification to span more shards
2033         than if deltas we used in a simple skip-delta scheme. */
2034      if ((1 << (--shards)) <= walk)
2035        count = noderev->predecessor_count - 1;
2036    }
2037
2038  /* Walk back a number of predecessors equal to the difference
2039     between count and the original predecessor count.  (For example,
2040     if noderev has ten predecessors and we want the eighth file rev,
2041     walk back two predecessors.) */
2042  base = noderev;
2043  iterpool = svn_pool_create(pool);
2044  while ((count++) < noderev->predecessor_count)
2045    {
2046      svn_fs_x__id_t id = noderev->predecessor_id;
2047      svn_pool_clear(iterpool);
2048      SVN_ERR(svn_fs_x__get_node_revision(&base, fs, &id, pool, iterpool));
2049    }
2050  svn_pool_destroy(iterpool);
2051
2052  /* return a suitable base representation */
2053  *rep = props ? base->prop_rep : base->data_rep;
2054
2055  /* if we encountered a shared rep, its parent chain may be different
2056   * from the node-rev parent chain. */
2057  if (*rep)
2058    {
2059      int chain_length = 0;
2060      int shard_count = 0;
2061
2062      /* Very short rep bases are simply not worth it as we are unlikely
2063       * to re-coup the deltification space overhead of 20+ bytes. */
2064      svn_filesize_t rep_size = (*rep)->expanded_size
2065                              ? (*rep)->expanded_size
2066                              : (*rep)->size;
2067      if (rep_size < 64)
2068        {
2069          *rep = NULL;
2070          return SVN_NO_ERROR;
2071        }
2072
2073      /* Check whether the length of the deltification chain is acceptable.
2074       * Otherwise, shared reps may form a non-skipping delta chain in
2075       * extreme cases. */
2076      SVN_ERR(svn_fs_x__rep_chain_length(&chain_length, &shard_count,
2077                                          *rep, fs, pool));
2078
2079      /* Some reasonable limit, depending on how acceptable longer linear
2080       * chains are in this repo.  Also, allow for some minimal chain. */
2081      if (chain_length >= 2 * (int)ffd->max_linear_deltification + 2)
2082        *rep = NULL;
2083      else
2084        /* To make it worth opening additional shards / pack files, we
2085         * require that the reps have a certain minimal size.  To deltify
2086         * against a rep in different shard, the lower limit is 512 bytes
2087         * and doubles with every extra shard to visit along the delta
2088         * chain. */
2089        if (   shard_count > 1
2090            && ((svn_filesize_t)128 << shard_count) >= rep_size)
2091          *rep = NULL;
2092    }
2093
2094  return SVN_NO_ERROR;
2095}
2096
2097/* Something went wrong and the pool for the rep write is being
2098   cleared before we've finished writing the rep.  So we need
2099   to remove the rep from the protorevfile and we need to unlock
2100   the protorevfile. */
2101static apr_status_t
2102rep_write_cleanup(void *data)
2103{
2104  svn_error_t *err;
2105  rep_write_baton_t *b = data;
2106  svn_fs_x__txn_id_t txn_id
2107    = svn_fs_x__get_txn_id(b->noderev->noderev_id.change_set);
2108
2109  /* Truncate and close the protorevfile. */
2110  err = svn_io_file_trunc(b->file, b->rep_offset, b->local_pool);
2111  err = svn_error_compose_create(err, svn_io_file_close(b->file,
2112                                                        b->local_pool));
2113
2114  /* Remove our lock regardless of any preceding errors so that the
2115     being_written flag is always removed and stays consistent with the
2116     file lock which will be removed no matter what since the pool is
2117     going away. */
2118  err = svn_error_compose_create(err,
2119                                 unlock_proto_rev(b->fs, txn_id,
2120                                                  b->lockcookie,
2121                                                  b->local_pool));
2122  if (err)
2123    {
2124      apr_status_t rc = err->apr_err;
2125      svn_error_clear(err);
2126      return rc;
2127    }
2128
2129  return APR_SUCCESS;
2130}
2131
2132/* Get a rep_write_baton_t, allocated from RESULT_POOL, and store it in
2133   WB_P for the representation indicated by NODEREV in filesystem FS.
2134   Only appropriate for file contents, not for props or directory contents.
2135 */
2136static svn_error_t *
2137rep_write_get_baton(rep_write_baton_t **wb_p,
2138                    svn_fs_t *fs,
2139                    svn_fs_x__noderev_t *noderev,
2140                    apr_pool_t *result_pool)
2141{
2142  svn_fs_x__data_t *ffd = fs->fsap_data;
2143  rep_write_baton_t *b;
2144  apr_file_t *file;
2145  svn_fs_x__representation_t *base_rep;
2146  svn_stream_t *source;
2147  svn_txdelta_window_handler_t wh;
2148  void *whb;
2149  int diff_version = 1;
2150  svn_fs_x__rep_header_t header = { 0 };
2151  svn_fs_x__txn_id_t txn_id
2152    = svn_fs_x__get_txn_id(noderev->noderev_id.change_set);
2153
2154  b = apr_pcalloc(result_pool, sizeof(*b));
2155
2156  b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1,
2157                                                 result_pool);
2158  b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5,
2159                                                result_pool);
2160
2161  b->fs = fs;
2162  b->result_pool = result_pool;
2163  b->local_pool = svn_pool_create(result_pool);
2164  b->rep_size = 0;
2165  b->noderev = noderev;
2166
2167  /* Open the prototype rev file and seek to its end. */
2168  SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie, fs, txn_id,
2169                                 b->local_pool));
2170
2171  b->file = file;
2172  b->rep_stream = svn_checksum__wrap_write_stream_fnv1a_32x4(
2173                              &b->fnv1a_checksum,
2174                              svn_stream_from_aprfile2(file, TRUE,
2175                                                       b->local_pool),
2176                              b->local_pool);
2177
2178  SVN_ERR(svn_fs_x__get_file_offset(&b->rep_offset, file, b->local_pool));
2179
2180  /* Get the base for this delta. */
2181  SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->local_pool));
2182  SVN_ERR(svn_fs_x__get_contents(&source, fs, base_rep, TRUE,
2183                                 b->local_pool));
2184
2185  /* Write out the rep header. */
2186  if (base_rep)
2187    {
2188      header.base_revision = svn_fs_x__get_revnum(base_rep->id.change_set);
2189      header.base_item_index = base_rep->id.number;
2190      header.base_length = base_rep->size;
2191      header.type = svn_fs_x__rep_delta;
2192    }
2193  else
2194    {
2195      header.type = svn_fs_x__rep_self_delta;
2196    }
2197  SVN_ERR(svn_fs_x__write_rep_header(&header, b->rep_stream,
2198                                      b->local_pool));
2199
2200  /* Now determine the offset of the actual svndiff data. */
2201  SVN_ERR(svn_fs_x__get_file_offset(&b->delta_start, file,
2202                                    b->local_pool));
2203
2204  /* Cleanup in case something goes wrong. */
2205  apr_pool_cleanup_register(b->local_pool, b, rep_write_cleanup,
2206                            apr_pool_cleanup_null);
2207
2208  /* Prepare to write the svndiff data. */
2209  svn_txdelta_to_svndiff3(&wh,
2210                          &whb,
2211                          svn_stream_disown(b->rep_stream, b->result_pool),
2212                          diff_version,
2213                          ffd->delta_compression_level,
2214                          result_pool);
2215
2216  b->delta_stream = svn_txdelta_target_push(wh, whb, source,
2217                                            b->result_pool);
2218
2219  *wb_p = b;
2220
2221  return SVN_NO_ERROR;
2222}
2223
2224/* For REP->SHA1_CHECKSUM, try to find an already existing representation
2225   in FS and return it in *OUT_REP.  If no such representation exists or
2226   if rep sharing has been disabled for FS, NULL will be returned.  Since
2227   there may be new duplicate representations within the same uncommitted
2228   revision, those can be passed in REPS_HASH (maps a sha1 digest onto
2229   svn_fs_x__representation_t*), otherwise pass in NULL for REPS_HASH.
2230   Use RESULT_POOL for *OLD_REP  allocations and SCRATCH_POOL for temporaries.
2231   The lifetime of *OLD_REP is limited by both, RESULT_POOL and REP lifetime.
2232 */
2233static svn_error_t *
2234get_shared_rep(svn_fs_x__representation_t **old_rep,
2235               svn_fs_t *fs,
2236               svn_fs_x__representation_t *rep,
2237               apr_hash_t *reps_hash,
2238               apr_pool_t *result_pool,
2239               apr_pool_t *scratch_pool)
2240{
2241  svn_error_t *err;
2242  svn_fs_x__data_t *ffd = fs->fsap_data;
2243
2244  /* Return NULL, if rep sharing has been disabled. */
2245  *old_rep = NULL;
2246  if (!ffd->rep_sharing_allowed)
2247    return SVN_NO_ERROR;
2248
2249  /* Check and see if we already have a representation somewhere that's
2250     identical to the one we just wrote out.  Start with the hash lookup
2251     because it is cheepest. */
2252  if (reps_hash)
2253    *old_rep = apr_hash_get(reps_hash,
2254                            rep->sha1_digest,
2255                            APR_SHA1_DIGESTSIZE);
2256
2257  /* If we haven't found anything yet, try harder and consult our DB. */
2258  if (*old_rep == NULL)
2259    {
2260      svn_checksum_t checksum;
2261      checksum.digest = rep->sha1_digest;
2262      checksum.kind = svn_checksum_sha1;
2263      err = svn_fs_x__get_rep_reference(old_rep, fs, &checksum, result_pool,
2264                                        scratch_pool);
2265
2266      /* ### Other error codes that we shouldn't mask out? */
2267      if (err == SVN_NO_ERROR)
2268        {
2269          if (*old_rep)
2270            SVN_ERR(svn_fs_x__check_rep(*old_rep, fs, scratch_pool));
2271        }
2272      else if (err->apr_err == SVN_ERR_FS_CORRUPT
2273               || SVN_ERROR_IN_CATEGORY(err->apr_err,
2274                                        SVN_ERR_MALFUNC_CATEGORY_START))
2275        {
2276          /* Fatal error; don't mask it.
2277
2278             In particular, this block is triggered when the rep-cache refers
2279             to revisions in the future.  We signal that as a corruption situation
2280             since, once those revisions are less than youngest (because of more
2281             commits), the rep-cache would be invalid.
2282           */
2283          SVN_ERR(err);
2284        }
2285      else
2286        {
2287          /* Something's wrong with the rep-sharing index.  We can continue
2288             without rep-sharing, but warn.
2289           */
2290          (fs->warning)(fs->warning_baton, err);
2291          svn_error_clear(err);
2292          *old_rep = NULL;
2293        }
2294    }
2295
2296  /* look for intra-revision matches (usually data reps but not limited
2297     to them in case props happen to look like some data rep)
2298   */
2299  if (*old_rep == NULL && svn_fs_x__is_txn(rep->id.change_set))
2300    {
2301      svn_node_kind_t kind;
2302      const char *file_name
2303        = svn_fs_x__path_txn_sha1(fs,
2304                                  svn_fs_x__get_txn_id(rep->id.change_set),
2305                                  rep->sha1_digest, scratch_pool);
2306
2307      /* in our txn, is there a rep file named with the wanted SHA1?
2308         If so, read it and use that rep.
2309       */
2310      SVN_ERR(svn_io_check_path(file_name, &kind, scratch_pool));
2311      if (kind == svn_node_file)
2312        {
2313          svn_stringbuf_t *rep_string;
2314          SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name,
2315                                           scratch_pool));
2316          SVN_ERR(svn_fs_x__parse_representation(old_rep, rep_string,
2317                                                 result_pool, scratch_pool));
2318        }
2319    }
2320
2321  /* Add information that is missing in the cached data. */
2322  if (*old_rep)
2323    {
2324      /* Use the old rep for this content. */
2325      memcpy((*old_rep)->md5_digest, rep->md5_digest, sizeof(rep->md5_digest));
2326    }
2327
2328  return SVN_NO_ERROR;
2329}
2330
2331/* Copy the hash sum calculation results from MD5_CTX, SHA1_CTX into REP.
2332 * Use SCRATCH_POOL for temporary allocations.
2333 */
2334static svn_error_t *
2335digests_final(svn_fs_x__representation_t *rep,
2336              const svn_checksum_ctx_t *md5_ctx,
2337              const svn_checksum_ctx_t *sha1_ctx,
2338              apr_pool_t *scratch_pool)
2339{
2340  svn_checksum_t *checksum;
2341
2342  SVN_ERR(svn_checksum_final(&checksum, md5_ctx, scratch_pool));
2343  memcpy(rep->md5_digest, checksum->digest, svn_checksum_size(checksum));
2344  SVN_ERR(svn_checksum_final(&checksum, sha1_ctx, scratch_pool));
2345  rep->has_sha1 = checksum != NULL;
2346  if (rep->has_sha1)
2347    memcpy(rep->sha1_digest, checksum->digest, svn_checksum_size(checksum));
2348
2349  return SVN_NO_ERROR;
2350}
2351
2352/* Close handler for the representation write stream.  BATON is a
2353   rep_write_baton_t.  Writes out a new node-rev that correctly
2354   references the representation we just finished writing. */
2355static svn_error_t *
2356rep_write_contents_close(void *baton)
2357{
2358  rep_write_baton_t *b = baton;
2359  svn_fs_x__representation_t *rep;
2360  svn_fs_x__representation_t *old_rep;
2361  apr_off_t offset;
2362  apr_int64_t txn_id;
2363
2364  rep = apr_pcalloc(b->result_pool, sizeof(*rep));
2365
2366  /* Close our delta stream so the last bits of svndiff are written
2367     out. */
2368  SVN_ERR(svn_stream_close(b->delta_stream));
2369
2370  /* Determine the length of the svndiff data. */
2371  SVN_ERR(svn_fs_x__get_file_offset(&offset, b->file, b->local_pool));
2372  rep->size = offset - b->delta_start;
2373
2374  /* Fill in the rest of the representation field. */
2375  rep->expanded_size = b->rep_size;
2376  txn_id = svn_fs_x__get_txn_id(b->noderev->noderev_id.change_set);
2377  rep->id.change_set = svn_fs_x__change_set_by_txn(txn_id);
2378
2379  /* Finalize the checksum. */
2380  SVN_ERR(digests_final(rep, b->md5_checksum_ctx, b->sha1_checksum_ctx,
2381                        b->result_pool));
2382
2383  /* Check and see if we already have a representation somewhere that's
2384     identical to the one we just wrote out. */
2385  SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->result_pool,
2386                         b->local_pool));
2387
2388  if (old_rep)
2389    {
2390      /* We need to erase from the protorev the data we just wrote. */
2391      SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->local_pool));
2392
2393      /* Use the old rep for this content. */
2394      b->noderev->data_rep = old_rep;
2395    }
2396  else
2397    {
2398      /* Write out our cosmetic end marker. */
2399      SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n"));
2400      SVN_ERR(allocate_item_index(&rep->id.number, b->fs, txn_id,
2401                                  b->local_pool));
2402      SVN_ERR(store_l2p_index_entry(b->fs, txn_id, b->rep_offset,
2403                                    rep->id.number, b->local_pool));
2404
2405      b->noderev->data_rep = rep;
2406    }
2407
2408  SVN_ERR(svn_stream_close(b->rep_stream));
2409
2410  /* Remove cleanup callback. */
2411  apr_pool_cleanup_kill(b->local_pool, b, rep_write_cleanup);
2412
2413  /* Write out the new node-rev information. */
2414  SVN_ERR(svn_fs_x__put_node_revision(b->fs, b->noderev, b->local_pool));
2415  if (!old_rep)
2416    {
2417      svn_fs_x__p2l_entry_t entry;
2418      svn_fs_x__id_t noderev_id;
2419      noderev_id.change_set = SVN_FS_X__INVALID_CHANGE_SET;
2420      noderev_id.number = rep->id.number;
2421
2422      entry.offset = b->rep_offset;
2423      SVN_ERR(svn_fs_x__get_file_offset(&offset, b->file, b->local_pool));
2424      entry.size = offset - b->rep_offset;
2425      entry.type = SVN_FS_X__ITEM_TYPE_FILE_REP;
2426      entry.item_count = 1;
2427      entry.items = &noderev_id;
2428      entry.fnv1_checksum = b->fnv1a_checksum;
2429
2430      SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->local_pool));
2431      SVN_ERR(store_p2l_index_entry(b->fs, txn_id, &entry, b->local_pool));
2432    }
2433
2434  SVN_ERR(svn_io_file_close(b->file, b->local_pool));
2435  SVN_ERR(unlock_proto_rev(b->fs, txn_id, b->lockcookie, b->local_pool));
2436  svn_pool_destroy(b->local_pool);
2437
2438  return SVN_NO_ERROR;
2439}
2440
2441/* Store a writable stream in *CONTENTS_P, allocated in RESULT_POOL, that
2442   will receive all data written and store it as the file data representation
2443   referenced by NODEREV in filesystem FS.  Only appropriate for file data,
2444   not props or directory contents. */
2445static svn_error_t *
2446set_representation(svn_stream_t **contents_p,
2447                   svn_fs_t *fs,
2448                   svn_fs_x__noderev_t *noderev,
2449                   apr_pool_t *result_pool)
2450{
2451  rep_write_baton_t *wb;
2452
2453  if (! svn_fs_x__is_txn(noderev->noderev_id.change_set))
2454    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2455                             _("Attempted to write to non-transaction '%s'"),
2456                             svn_fs_x__id_unparse(&noderev->noderev_id,
2457                                                  result_pool)->data);
2458
2459  SVN_ERR(rep_write_get_baton(&wb, fs, noderev, result_pool));
2460
2461  *contents_p = svn_stream_create(wb, result_pool);
2462  svn_stream_set_write(*contents_p, rep_write_contents);
2463  svn_stream_set_close(*contents_p, rep_write_contents_close);
2464
2465  return SVN_NO_ERROR;
2466}
2467
2468svn_error_t *
2469svn_fs_x__set_contents(svn_stream_t **stream,
2470                       svn_fs_t *fs,
2471                       svn_fs_x__noderev_t *noderev,
2472                       apr_pool_t *result_pool)
2473{
2474  if (noderev->kind != svn_node_file)
2475    return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
2476                            _("Can't set text contents of a directory"));
2477
2478  return set_representation(stream, fs, noderev, result_pool);
2479}
2480
2481svn_error_t *
2482svn_fs_x__create_successor(svn_fs_t *fs,
2483                           svn_fs_x__noderev_t *new_noderev,
2484                           const svn_fs_x__id_t *copy_id,
2485                           svn_fs_x__txn_id_t txn_id,
2486                           apr_pool_t *scratch_pool)
2487{
2488  new_noderev->copy_id = *copy_id;
2489  new_noderev->noderev_id.change_set = svn_fs_x__change_set_by_txn(txn_id);
2490  SVN_ERR(allocate_item_index(&new_noderev->noderev_id.number, fs, txn_id,
2491                              scratch_pool));
2492
2493  if (! new_noderev->copyroot_path)
2494    {
2495      new_noderev->copyroot_path
2496        = apr_pstrdup(scratch_pool, new_noderev->created_path);
2497      new_noderev->copyroot_rev
2498        = svn_fs_x__get_revnum(new_noderev->noderev_id.change_set);
2499    }
2500
2501  SVN_ERR(svn_fs_x__put_node_revision(fs, new_noderev, scratch_pool));
2502
2503  return SVN_NO_ERROR;
2504}
2505
2506svn_error_t *
2507svn_fs_x__set_proplist(svn_fs_t *fs,
2508                       svn_fs_x__noderev_t *noderev,
2509                       apr_hash_t *proplist,
2510                       apr_pool_t *scratch_pool)
2511{
2512  const svn_fs_x__id_t *id = &noderev->noderev_id;
2513  const char *filename = svn_fs_x__path_txn_node_props(fs, id, scratch_pool,
2514                                                       scratch_pool);
2515  apr_file_t *file;
2516  svn_stream_t *out;
2517
2518  /* Dump the property list to the mutable property file. */
2519  SVN_ERR(svn_io_file_open(&file, filename,
2520                           APR_WRITE | APR_CREATE | APR_TRUNCATE
2521                           | APR_BUFFERED, APR_OS_DEFAULT, scratch_pool));
2522  out = svn_stream_from_aprfile2(file, TRUE, scratch_pool);
2523  SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, scratch_pool));
2524  SVN_ERR(svn_io_file_close(file, scratch_pool));
2525
2526  /* Mark the node-rev's prop rep as mutable, if not already done. */
2527  if (!noderev->prop_rep
2528      || svn_fs_x__is_revision(noderev->prop_rep->id.change_set))
2529    {
2530      svn_fs_x__txn_id_t txn_id
2531        = svn_fs_x__get_txn_id(noderev->noderev_id.change_set);
2532      noderev->prop_rep = apr_pcalloc(scratch_pool, sizeof(*noderev->prop_rep));
2533      noderev->prop_rep->id.change_set = id->change_set;
2534      SVN_ERR(allocate_item_index(&noderev->prop_rep->id.number, fs,
2535                                  txn_id, scratch_pool));
2536      SVN_ERR(svn_fs_x__put_node_revision(fs, noderev, scratch_pool));
2537    }
2538
2539  return SVN_NO_ERROR;
2540}
2541
2542/* This baton is used by the stream created for write_container_rep. */
2543typedef struct write_container_baton_t
2544{
2545  svn_stream_t *stream;
2546
2547  apr_size_t size;
2548
2549  svn_checksum_ctx_t *md5_ctx;
2550  svn_checksum_ctx_t *sha1_ctx;
2551} write_container_baton_t;
2552
2553/* The handler for the write_container_rep stream.  BATON is a
2554   write_container_baton_t, DATA has the data to write and *LEN is the number
2555   of bytes to write. */
2556static svn_error_t *
2557write_container_handler(void *baton,
2558                        const char *data,
2559                        apr_size_t *len)
2560{
2561  write_container_baton_t *whb = baton;
2562
2563  SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
2564  SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
2565
2566  SVN_ERR(svn_stream_write(whb->stream, data, len));
2567  whb->size += *len;
2568
2569  return SVN_NO_ERROR;
2570}
2571
2572/* Callback function type.  Write the data provided by BATON into STREAM. */
2573typedef svn_error_t *
2574(* collection_writer_t)(svn_stream_t *stream,
2575                        void *baton,
2576                        apr_pool_t *scratch_pool);
2577
2578/* Implement collection_writer_t writing the C string->svn_string_t hash
2579   given as BATON. */
2580static svn_error_t *
2581write_hash_to_stream(svn_stream_t *stream,
2582                     void *baton,
2583                     apr_pool_t *scratch_pool)
2584{
2585  apr_hash_t *hash = baton;
2586  SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, scratch_pool));
2587
2588  return SVN_NO_ERROR;
2589}
2590
2591/* Implement collection_writer_t writing the svn_fs_x__dirent_t* array given
2592   as BATON. */
2593static svn_error_t *
2594write_directory_to_stream(svn_stream_t *stream,
2595                          void *baton,
2596                          apr_pool_t *scratch_pool)
2597{
2598  apr_array_header_t *dir = baton;
2599  SVN_ERR(unparse_dir_entries(dir, stream, scratch_pool));
2600
2601  return SVN_NO_ERROR;
2602}
2603
2604
2605/* Write out the COLLECTION pertaining to the NODEREV in FS as a deltified
2606   text representation to file FILE using WRITER.  In the process, record the
2607   total size and the md5 digest in REP and add the representation of type
2608   ITEM_TYPE to the indexes if necessary.  If rep sharing has been enabled and
2609   REPS_HASH is not NULL, it will be used in addition to the on-disk cache to
2610   find earlier reps with the same content.  When such existing reps can be
2611   found, we will truncate the one just written from the file and return the
2612   existing rep.
2613
2614   If ITEM_TYPE is IS_PROPS equals SVN_FS_FS__ITEM_TYPE_*_PROPS, assume
2615   that we want to a props representation as the base for our delta.
2616   If FINAL_REVISION is not SVN_INVALID_REVNUM, use it to determine whether
2617   to write to the proto-index files.
2618   Perform temporary allocations in SCRATCH_POOL.
2619 */
2620static svn_error_t *
2621write_container_delta_rep(svn_fs_x__representation_t *rep,
2622                          apr_file_t *file,
2623                          void *collection,
2624                          collection_writer_t writer,
2625                          svn_fs_t *fs,
2626                          svn_fs_x__txn_id_t txn_id,
2627                          svn_fs_x__noderev_t *noderev,
2628                          apr_hash_t *reps_hash,
2629                          apr_uint32_t item_type,
2630                          svn_revnum_t final_revision,
2631                          apr_pool_t *scratch_pool)
2632{
2633  svn_fs_x__data_t *ffd = fs->fsap_data;
2634  svn_txdelta_window_handler_t diff_wh;
2635  void *diff_whb;
2636
2637  svn_stream_t *file_stream;
2638  svn_stream_t *stream;
2639  svn_fs_x__representation_t *base_rep;
2640  svn_fs_x__representation_t *old_rep;
2641  svn_fs_x__p2l_entry_t entry;
2642  svn_stream_t *source;
2643  svn_fs_x__rep_header_t header = { 0 };
2644
2645  apr_off_t rep_end = 0;
2646  apr_off_t delta_start = 0;
2647  apr_off_t offset = 0;
2648
2649  write_container_baton_t *whb;
2650  int diff_version = 1;
2651  svn_boolean_t is_props = (item_type == SVN_FS_X__ITEM_TYPE_FILE_PROPS)
2652                        || (item_type == SVN_FS_X__ITEM_TYPE_DIR_PROPS);
2653
2654  /* Get the base for this delta. */
2655  SVN_ERR(choose_delta_base(&base_rep, fs, noderev, is_props, scratch_pool));
2656  SVN_ERR(svn_fs_x__get_contents(&source, fs, base_rep, FALSE, scratch_pool));
2657
2658  SVN_ERR(svn_fs_x__get_file_offset(&offset, file, scratch_pool));
2659
2660  /* Write out the rep header. */
2661  if (base_rep)
2662    {
2663      header.base_revision = svn_fs_x__get_revnum(base_rep->id.change_set);
2664      header.base_item_index = base_rep->id.number;
2665      header.base_length = base_rep->size;
2666      header.type = svn_fs_x__rep_delta;
2667    }
2668  else
2669    {
2670      header.type = svn_fs_x__rep_self_delta;
2671    }
2672
2673  file_stream = svn_checksum__wrap_write_stream_fnv1a_32x4(
2674                                  &entry.fnv1_checksum,
2675                                  svn_stream_from_aprfile2(file, TRUE,
2676                                                           scratch_pool),
2677                                  scratch_pool);
2678  SVN_ERR(svn_fs_x__write_rep_header(&header, file_stream, scratch_pool));
2679  SVN_ERR(svn_fs_x__get_file_offset(&delta_start, file, scratch_pool));
2680
2681  /* Prepare to write the svndiff data. */
2682  svn_txdelta_to_svndiff3(&diff_wh,
2683                          &diff_whb,
2684                          svn_stream_disown(file_stream, scratch_pool),
2685                          diff_version,
2686                          ffd->delta_compression_level,
2687                          scratch_pool);
2688
2689  whb = apr_pcalloc(scratch_pool, sizeof(*whb));
2690  whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source,
2691                                        scratch_pool);
2692  whb->size = 0;
2693  whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool);
2694  whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool);
2695
2696  /* serialize the hash */
2697  stream = svn_stream_create(whb, scratch_pool);
2698  svn_stream_set_write(stream, write_container_handler);
2699
2700  SVN_ERR(writer(stream, collection, scratch_pool));
2701  SVN_ERR(svn_stream_close(whb->stream));
2702
2703  /* Store the results. */
2704  SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool));
2705
2706  /* Check and see if we already have a representation somewhere that's
2707     identical to the one we just wrote out. */
2708  SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, scratch_pool,
2709                         scratch_pool));
2710
2711  if (old_rep)
2712    {
2713      SVN_ERR(svn_stream_close(file_stream));
2714
2715      /* We need to erase from the protorev the data we just wrote. */
2716      SVN_ERR(svn_io_file_trunc(file, offset, scratch_pool));
2717
2718      /* Use the old rep for this content. */
2719      memcpy(rep, old_rep, sizeof (*rep));
2720    }
2721  else
2722    {
2723      svn_fs_x__id_t noderev_id;
2724
2725      /* Write out our cosmetic end marker. */
2726      SVN_ERR(svn_fs_x__get_file_offset(&rep_end, file, scratch_pool));
2727      SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n"));
2728      SVN_ERR(svn_stream_close(file_stream));
2729
2730      SVN_ERR(allocate_item_index(&rep->id.number, fs, txn_id,
2731                                  scratch_pool));
2732      SVN_ERR(store_l2p_index_entry(fs, txn_id, offset, rep->id.number,
2733                                    scratch_pool));
2734
2735      noderev_id.change_set = SVN_FS_X__INVALID_CHANGE_SET;
2736      noderev_id.number = rep->id.number;
2737
2738      entry.offset = offset;
2739      SVN_ERR(svn_fs_x__get_file_offset(&offset, file, scratch_pool));
2740      entry.size = offset - entry.offset;
2741      entry.type = item_type;
2742      entry.item_count = 1;
2743      entry.items = &noderev_id;
2744
2745      SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, scratch_pool));
2746
2747      /* update the representation */
2748      rep->expanded_size = whb->size;
2749      rep->size = rep_end - delta_start;
2750    }
2751
2752  return SVN_NO_ERROR;
2753}
2754
2755/* Sanity check ROOT_NODEREV, a candidate for being the root node-revision
2756   of (not yet committed) revision REV in FS.  Use SCRATCH_POOL for temporary
2757   allocations.
2758
2759   If you change this function, consider updating svn_fs_x__verify() too.
2760 */
2761static svn_error_t *
2762validate_root_noderev(svn_fs_t *fs,
2763                      svn_fs_x__noderev_t *root_noderev,
2764                      svn_revnum_t rev,
2765                      apr_pool_t *scratch_pool)
2766{
2767  svn_revnum_t head_revnum = rev-1;
2768  int head_predecessor_count;
2769
2770  SVN_ERR_ASSERT(rev > 0);
2771
2772  /* Compute HEAD_PREDECESSOR_COUNT. */
2773  {
2774    svn_fs_x__id_t head_root_id;
2775    svn_fs_x__noderev_t *head_root_noderev;
2776
2777    /* Get /@HEAD's noderev. */
2778    svn_fs_x__init_rev_root(&head_root_id, head_revnum);
2779    SVN_ERR(svn_fs_x__get_node_revision(&head_root_noderev, fs,
2780                                        &head_root_id, scratch_pool,
2781                                        scratch_pool));
2782
2783    head_predecessor_count = head_root_noderev->predecessor_count;
2784  }
2785
2786  /* Check that the root noderev's predecessor count equals REV.
2787
2788     This kind of corruption was seen on svn.apache.org (both on
2789     the root noderev and on other fspaths' noderevs); see
2790     issue #4129.
2791
2792     Normally (rev == root_noderev->predecessor_count), but here we
2793     use a more roundabout check that should only trigger on new instances
2794     of the corruption, rather then trigger on each and every new commit
2795     to a repository that has triggered the bug somewhere in its root
2796     noderev's history.
2797   */
2798  if ((root_noderev->predecessor_count - head_predecessor_count)
2799      != (rev - head_revnum))
2800    {
2801      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2802                               _("predecessor count for "
2803                                 "the root node-revision is wrong: "
2804                                 "found (%d+%ld != %d), committing r%ld"),
2805                                 head_predecessor_count,
2806                                 rev - head_revnum, /* This is equal to 1. */
2807                                 root_noderev->predecessor_count,
2808                                 rev);
2809    }
2810
2811  return SVN_NO_ERROR;
2812}
2813
2814/* Given the potentially txn-local id PART, update that to a permanent ID
2815 * based on the REVISION.
2816 */
2817static void
2818get_final_id(svn_fs_x__id_t *part,
2819             svn_revnum_t revision)
2820{
2821  if (!svn_fs_x__is_revision(part->change_set))
2822    part->change_set = svn_fs_x__change_set_by_rev(revision);
2823}
2824
2825/* Copy a node-revision specified by id ID in fileystem FS from a
2826   transaction into the proto-rev-file FILE.  Set *NEW_ID_P to a
2827   pointer to the new noderev-id.  If this is a directory, copy all
2828   children as well.
2829
2830   START_NODE_ID and START_COPY_ID are
2831   the first available node and copy ids for this filesystem, for older
2832   FS formats.
2833
2834   REV is the revision number that this proto-rev-file will represent.
2835
2836   INITIAL_OFFSET is the offset of the proto-rev-file on entry to
2837   commit_body.
2838
2839   If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
2840   REPS_POOL) of each data rep that is new in this revision.
2841
2842   If REPS_HASH is not NULL, append copies (allocated in REPS_POOL)
2843   of the representations of each property rep that is new in this
2844   revision.
2845
2846   AT_ROOT is true if the node revision being written is the root
2847   node-revision.  It is only controls additional sanity checking
2848   logic.
2849
2850   Temporary allocations are also from SCRATCH_POOL. */
2851static svn_error_t *
2852write_final_rev(svn_fs_x__id_t *new_id_p,
2853                apr_file_t *file,
2854                svn_revnum_t rev,
2855                svn_fs_t *fs,
2856                const svn_fs_x__id_t *id,
2857                apr_off_t initial_offset,
2858                apr_array_header_t *reps_to_cache,
2859                apr_hash_t *reps_hash,
2860                apr_pool_t *reps_pool,
2861                svn_boolean_t at_root,
2862                apr_pool_t *scratch_pool)
2863{
2864  svn_fs_x__noderev_t *noderev;
2865  apr_off_t my_offset;
2866  svn_fs_x__id_t new_id;
2867  svn_fs_x__id_t noderev_id;
2868  svn_fs_x__data_t *ffd = fs->fsap_data;
2869  svn_fs_x__txn_id_t txn_id = svn_fs_x__get_txn_id(id->change_set);
2870  svn_fs_x__p2l_entry_t entry;
2871  svn_fs_x__change_set_t change_set = svn_fs_x__change_set_by_rev(rev);
2872  svn_stream_t *file_stream;
2873  apr_pool_t *subpool;
2874
2875  /* Check to see if this is a transaction node. */
2876  if (txn_id == SVN_FS_X__INVALID_TXN_ID)
2877    {
2878      svn_fs_x__id_reset(new_id_p);
2879      return SVN_NO_ERROR;
2880    }
2881
2882  subpool = svn_pool_create(scratch_pool);
2883  SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, id, scratch_pool,
2884                                      subpool));
2885
2886  if (noderev->kind == svn_node_dir)
2887    {
2888      apr_array_header_t *entries;
2889      int i;
2890
2891      /* This is a directory.  Write out all the children first. */
2892
2893      SVN_ERR(svn_fs_x__rep_contents_dir(&entries, fs, noderev, scratch_pool,
2894                                         subpool));
2895      for (i = 0; i < entries->nelts; ++i)
2896        {
2897          svn_fs_x__dirent_t *dirent = APR_ARRAY_IDX(entries, i,
2898                                                     svn_fs_x__dirent_t *);
2899
2900          svn_pool_clear(subpool);
2901          SVN_ERR(write_final_rev(&new_id, file, rev, fs, &dirent->id,
2902                                  initial_offset, reps_to_cache, reps_hash,
2903                                  reps_pool, FALSE, subpool));
2904          if (   svn_fs_x__id_used(&new_id)
2905              && (svn_fs_x__get_revnum(new_id.change_set) == rev))
2906            dirent->id = new_id;
2907        }
2908
2909      if (noderev->data_rep
2910          && ! svn_fs_x__is_revision(noderev->data_rep->id.change_set))
2911        {
2912          /* Write out the contents of this directory as a text rep. */
2913          noderev->data_rep->id.change_set = change_set;
2914          SVN_ERR(write_container_delta_rep(noderev->data_rep, file,
2915                                            entries,
2916                                            write_directory_to_stream,
2917                                            fs, txn_id, noderev, NULL,
2918                                            SVN_FS_X__ITEM_TYPE_DIR_REP,
2919                                            rev, scratch_pool));
2920        }
2921    }
2922  else
2923    {
2924      /* This is a file.  We should make sure the data rep, if it
2925         exists in a "this" state, gets rewritten to our new revision
2926         num. */
2927
2928      if (noderev->data_rep
2929          && svn_fs_x__is_txn(noderev->data_rep->id.change_set))
2930        {
2931          noderev->data_rep->id.change_set = change_set;
2932        }
2933    }
2934
2935  svn_pool_destroy(subpool);
2936
2937  /* Fix up the property reps. */
2938  if (noderev->prop_rep
2939      && svn_fs_x__is_txn(noderev->prop_rep->id.change_set))
2940    {
2941      apr_hash_t *proplist;
2942      apr_uint32_t item_type = noderev->kind == svn_node_dir
2943                             ? SVN_FS_X__ITEM_TYPE_DIR_PROPS
2944                             : SVN_FS_X__ITEM_TYPE_FILE_PROPS;
2945      SVN_ERR(svn_fs_x__get_proplist(&proplist, fs, noderev, scratch_pool,
2946                                     scratch_pool));
2947
2948      noderev->prop_rep->id.change_set = change_set;
2949
2950      SVN_ERR(write_container_delta_rep(noderev->prop_rep, file, proplist,
2951                                        write_hash_to_stream, fs, txn_id,
2952                                        noderev, reps_hash, item_type, rev,
2953                                        scratch_pool));
2954    }
2955
2956  /* Convert our temporary ID into a permanent revision one. */
2957  get_final_id(&noderev->node_id, rev);
2958  get_final_id(&noderev->copy_id, rev);
2959  get_final_id(&noderev->noderev_id, rev);
2960
2961  if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
2962    noderev->copyroot_rev = rev;
2963
2964  SVN_ERR(svn_fs_x__get_file_offset(&my_offset, file, scratch_pool));
2965
2966  SVN_ERR(store_l2p_index_entry(fs, txn_id, my_offset,
2967                                noderev->noderev_id.number, scratch_pool));
2968  new_id = noderev->noderev_id;
2969
2970  if (ffd->rep_sharing_allowed)
2971    {
2972      /* Save the data representation's hash in the rep cache. */
2973      if (   noderev->data_rep && noderev->kind == svn_node_file
2974          && svn_fs_x__get_revnum(noderev->data_rep->id.change_set) == rev)
2975        {
2976          SVN_ERR_ASSERT(reps_to_cache && reps_pool);
2977          APR_ARRAY_PUSH(reps_to_cache, svn_fs_x__representation_t *)
2978            = svn_fs_x__rep_copy(noderev->data_rep, reps_pool);
2979        }
2980
2981      if (   noderev->prop_rep
2982          && svn_fs_x__get_revnum(noderev->prop_rep->id.change_set) == rev)
2983        {
2984          /* Add new property reps to hash and on-disk cache. */
2985          svn_fs_x__representation_t *copy
2986            = svn_fs_x__rep_copy(noderev->prop_rep, reps_pool);
2987
2988          SVN_ERR_ASSERT(reps_to_cache && reps_pool);
2989          APR_ARRAY_PUSH(reps_to_cache, svn_fs_x__representation_t *) = copy;
2990
2991          apr_hash_set(reps_hash,
2992                        copy->sha1_digest,
2993                        APR_SHA1_DIGESTSIZE,
2994                        copy);
2995        }
2996    }
2997
2998  /* don't serialize SHA1 for dirs to disk (waste of space) */
2999  if (noderev->data_rep && noderev->kind == svn_node_dir)
3000    noderev->data_rep->has_sha1 = FALSE;
3001
3002  /* don't serialize SHA1 for props to disk (waste of space) */
3003  if (noderev->prop_rep)
3004    noderev->prop_rep->has_sha1 = FALSE;
3005
3006  /* Write out our new node-revision. */
3007  if (at_root)
3008    SVN_ERR(validate_root_noderev(fs, noderev, rev, scratch_pool));
3009
3010  file_stream = svn_checksum__wrap_write_stream_fnv1a_32x4(
3011                                  &entry.fnv1_checksum,
3012                                  svn_stream_from_aprfile2(file, TRUE,
3013                                                           scratch_pool),
3014                                  scratch_pool);
3015  SVN_ERR(svn_fs_x__write_noderev(file_stream, noderev, scratch_pool));
3016  SVN_ERR(svn_stream_close(file_stream));
3017
3018  /* reference the root noderev from the log-to-phys index */
3019  noderev_id = noderev->noderev_id;
3020  noderev_id.change_set = SVN_FS_X__INVALID_CHANGE_SET;
3021
3022  entry.offset = my_offset;
3023  SVN_ERR(svn_fs_x__get_file_offset(&my_offset, file, scratch_pool));
3024  entry.size = my_offset - entry.offset;
3025  entry.type = SVN_FS_X__ITEM_TYPE_NODEREV;
3026  entry.item_count = 1;
3027  entry.items = &noderev_id;
3028
3029  SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, scratch_pool));
3030
3031  /* Return our ID that references the revision file. */
3032  *new_id_p = new_id;
3033
3034  return SVN_NO_ERROR;
3035}
3036
3037/* Write the changed path info CHANGED_PATHS from transaction TXN_ID to the
3038   permanent rev-file FILE representing NEW_REV in filesystem FS.  *OFFSET_P
3039   is set the to offset in the file of the beginning of this information.
3040   NEW_REV is the revision currently being committed.
3041   Perform temporary allocations in SCRATCH_POOL. */
3042static svn_error_t *
3043write_final_changed_path_info(apr_off_t *offset_p,
3044                              apr_file_t *file,
3045                              svn_fs_t *fs,
3046                              svn_fs_x__txn_id_t txn_id,
3047                              apr_hash_t *changed_paths,
3048                              svn_revnum_t new_rev,
3049                              apr_pool_t *scratch_pool)
3050{
3051  apr_off_t offset;
3052  svn_stream_t *stream;
3053  svn_fs_x__p2l_entry_t entry;
3054  svn_fs_x__id_t rev_item
3055    = {SVN_INVALID_REVNUM, SVN_FS_X__ITEM_INDEX_CHANGES};
3056
3057  SVN_ERR(svn_fs_x__get_file_offset(&offset, file, scratch_pool));
3058
3059  /* write to target file & calculate checksum */
3060  stream = svn_checksum__wrap_write_stream_fnv1a_32x4(&entry.fnv1_checksum,
3061                         svn_stream_from_aprfile2(file, TRUE, scratch_pool),
3062                         scratch_pool);
3063  SVN_ERR(svn_fs_x__write_changes(stream, fs, changed_paths, TRUE,
3064                                  scratch_pool));
3065  SVN_ERR(svn_stream_close(stream));
3066
3067  *offset_p = offset;
3068
3069  /* reference changes from the indexes */
3070  entry.offset = offset;
3071  SVN_ERR(svn_fs_x__get_file_offset(&offset, file, scratch_pool));
3072  entry.size = offset - entry.offset;
3073  entry.type = SVN_FS_X__ITEM_TYPE_CHANGES;
3074  entry.item_count = 1;
3075  entry.items = &rev_item;
3076
3077  SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, scratch_pool));
3078  SVN_ERR(store_l2p_index_entry(fs, txn_id, entry.offset,
3079                                SVN_FS_X__ITEM_INDEX_CHANGES, scratch_pool));
3080
3081  return SVN_NO_ERROR;
3082}
3083
3084/* Open a new svn_fs_t handle to FS, set that handle's concept of "current
3085   youngest revision" to NEW_REV, and call svn_fs_x__verify_root() on
3086   NEW_REV's revision root.
3087
3088   Intended to be called as the very last step in a commit before 'current'
3089   is bumped.  This implies that we are holding the write lock. */
3090static svn_error_t *
3091verify_as_revision_before_current_plus_plus(svn_fs_t *fs,
3092                                            svn_revnum_t new_rev,
3093                                            apr_pool_t *scratch_pool)
3094{
3095#ifdef SVN_DEBUG
3096  svn_fs_x__data_t *ffd = fs->fsap_data;
3097  svn_fs_t *ft; /* fs++ == ft */
3098  svn_fs_root_t *root;
3099  svn_fs_x__data_t *ft_ffd;
3100  apr_hash_t *fs_config;
3101
3102  SVN_ERR_ASSERT(ffd->svn_fs_open_);
3103
3104  /* make sure FT does not simply return data cached by other instances
3105   * but actually retrieves it from disk at least once.
3106   */
3107  fs_config = apr_hash_make(scratch_pool);
3108  svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
3109                           svn_uuid_generate(scratch_pool));
3110  SVN_ERR(ffd->svn_fs_open_(&ft, fs->path,
3111                            fs_config,
3112                            scratch_pool,
3113                            scratch_pool));
3114  ft_ffd = ft->fsap_data;
3115  /* Don't let FT consult rep-cache.db, either. */
3116  ft_ffd->rep_sharing_allowed = FALSE;
3117
3118  /* Time travel! */
3119  ft_ffd->youngest_rev_cache = new_rev;
3120
3121  SVN_ERR(svn_fs_x__revision_root(&root, ft, new_rev, scratch_pool));
3122  SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev);
3123  SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev);
3124  SVN_ERR(svn_fs_x__verify_root(root, scratch_pool));
3125#endif /* SVN_DEBUG */
3126
3127  return SVN_NO_ERROR;
3128}
3129
3130/* Verify that the user registered with FS has all the locks necessary to
3131   permit all the changes associated with TXN_NAME.
3132   The FS write lock is assumed to be held by the caller. */
3133static svn_error_t *
3134verify_locks(svn_fs_t *fs,
3135             svn_fs_x__txn_id_t txn_id,
3136             apr_hash_t *changed_paths,
3137             apr_pool_t *scratch_pool)
3138{
3139  apr_pool_t *iterpool;
3140  apr_array_header_t *changed_paths_sorted;
3141  svn_stringbuf_t *last_recursed = NULL;
3142  int i;
3143
3144  /* Make an array of the changed paths, and sort them depth-first-ily.  */
3145  changed_paths_sorted = svn_sort__hash(changed_paths,
3146                                        svn_sort_compare_items_as_paths,
3147                                        scratch_pool);
3148
3149  /* Now, traverse the array of changed paths, verify locks.  Note
3150     that if we need to do a recursive verification a path, we'll skip
3151     over children of that path when we get to them. */
3152  iterpool = svn_pool_create(scratch_pool);
3153  for (i = 0; i < changed_paths_sorted->nelts; i++)
3154    {
3155      const svn_sort__item_t *item;
3156      const char *path;
3157      svn_fs_x__change_t *change;
3158      svn_boolean_t recurse = TRUE;
3159
3160      svn_pool_clear(iterpool);
3161
3162      item = &APR_ARRAY_IDX(changed_paths_sorted, i, svn_sort__item_t);
3163
3164      /* Fetch the change associated with our path.  */
3165      path = item->key;
3166      change = item->value;
3167
3168      /* If this path has already been verified as part of a recursive
3169         check of one of its parents, no need to do it again.  */
3170      if (last_recursed
3171          && svn_fspath__skip_ancestor(last_recursed->data, path))
3172        continue;
3173
3174      /* What does it mean to succeed at lock verification for a given
3175         path?  For an existing file or directory getting modified
3176         (text, props), it means we hold the lock on the file or
3177         directory.  For paths being added or removed, we need to hold
3178         the locks for that path and any children of that path.
3179
3180         WHEW!  We have no reliable way to determine the node kind
3181         of deleted items, but fortunately we are going to do a
3182         recursive check on deleted paths regardless of their kind.  */
3183      if (change->change_kind == svn_fs_path_change_modify)
3184        recurse = FALSE;
3185      SVN_ERR(svn_fs_x__allow_locked_operation(path, fs, recurse, TRUE,
3186                                               iterpool));
3187
3188      /* If we just did a recursive check, remember the path we
3189         checked (so children can be skipped).  */
3190      if (recurse)
3191        {
3192          if (! last_recursed)
3193            last_recursed = svn_stringbuf_create(path, scratch_pool);
3194          else
3195            svn_stringbuf_set(last_recursed, path);
3196        }
3197    }
3198  svn_pool_destroy(iterpool);
3199  return SVN_NO_ERROR;
3200}
3201
3202/* Return in *PATH the path to a file containing the properties that
3203   make up the final revision properties file.  This involves setting
3204   svn:date and removing any temporary properties associated with the
3205   commit flags. */
3206static svn_error_t *
3207write_final_revprop(const char **path,
3208                    svn_fs_txn_t *txn,
3209                    svn_fs_x__txn_id_t txn_id,
3210                    apr_pool_t *pool)
3211{
3212  apr_hash_t *txnprops;
3213  svn_boolean_t final_mods = FALSE;
3214  svn_string_t date;
3215  svn_string_t *client_date;
3216
3217  SVN_ERR(svn_fs_x__txn_proplist(&txnprops, txn, pool));
3218
3219  /* Remove any temporary txn props representing 'flags'. */
3220  if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
3221    {
3222      svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD, NULL);
3223      final_mods = TRUE;
3224    }
3225
3226  if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
3227    {
3228      svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL);
3229      final_mods = TRUE;
3230    }
3231
3232  client_date = svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE);
3233  if (client_date)
3234    {
3235      svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE, NULL);
3236      final_mods = TRUE;
3237    }
3238
3239  /* Update commit time to ensure that svn:date revprops remain ordered if
3240     requested. */
3241  if (!client_date || strcmp(client_date->data, "1"))
3242    {
3243      date.data = svn_time_to_cstring(apr_time_now(), pool);
3244      date.len = strlen(date.data);
3245      svn_hash_sets(txnprops, SVN_PROP_REVISION_DATE, &date);
3246      final_mods = TRUE;
3247    }
3248
3249  if (final_mods)
3250    {
3251      SVN_ERR(set_txn_proplist(txn->fs, txn_id, txnprops, TRUE, pool));
3252      *path = svn_fs_x__path_txn_props_final(txn->fs, txn_id, pool);
3253    }
3254  else
3255    {
3256      *path = svn_fs_x__path_txn_props(txn->fs, txn_id, pool);
3257    }
3258
3259  return SVN_NO_ERROR;
3260}
3261
3262svn_error_t *
3263svn_fs_x__add_index_data(svn_fs_t *fs,
3264                         apr_file_t *file,
3265                         const char *l2p_proto_index,
3266                         const char *p2l_proto_index,
3267                         svn_revnum_t revision,
3268                         apr_pool_t *scratch_pool)
3269{
3270  apr_off_t l2p_offset;
3271  apr_off_t p2l_offset;
3272  svn_stringbuf_t *footer;
3273  unsigned char footer_length;
3274  svn_checksum_t *l2p_checksum;
3275  svn_checksum_t *p2l_checksum;
3276
3277  /* Append the actual index data to the pack file. */
3278  l2p_offset = 0;
3279  SVN_ERR(svn_io_file_seek(file, APR_END, &l2p_offset, scratch_pool));
3280  SVN_ERR(svn_fs_x__l2p_index_append(&l2p_checksum, fs, file,
3281                                     l2p_proto_index, revision,
3282                                     scratch_pool, scratch_pool));
3283
3284  p2l_offset = 0;
3285  SVN_ERR(svn_io_file_seek(file, APR_END, &p2l_offset, scratch_pool));
3286  SVN_ERR(svn_fs_x__p2l_index_append(&p2l_checksum, fs, file,
3287                                     p2l_proto_index, revision,
3288                                     scratch_pool, scratch_pool));
3289
3290  /* Append footer. */
3291  footer = svn_fs_x__unparse_footer(l2p_offset, l2p_checksum,
3292                                    p2l_offset, p2l_checksum, scratch_pool,
3293                                    scratch_pool);
3294  SVN_ERR(svn_io_file_write_full(file, footer->data, footer->len, NULL,
3295                                 scratch_pool));
3296
3297  footer_length = footer->len;
3298  SVN_ERR_ASSERT(footer_length == footer->len);
3299  SVN_ERR(svn_io_file_write_full(file, &footer_length, 1, NULL,
3300                                 scratch_pool));
3301
3302  return SVN_NO_ERROR;
3303}
3304
3305/* Baton used for commit_body below. */
3306typedef struct commit_baton_t {
3307  svn_revnum_t *new_rev_p;
3308  svn_fs_t *fs;
3309  svn_fs_txn_t *txn;
3310  apr_array_header_t *reps_to_cache;
3311  apr_hash_t *reps_hash;
3312  apr_pool_t *reps_pool;
3313} commit_baton_t;
3314
3315/* The work-horse for svn_fs_x__commit, called with the FS write lock.
3316   This implements the svn_fs_x__with_write_lock() 'body' callback
3317   type.  BATON is a 'commit_baton_t *'. */
3318static svn_error_t *
3319commit_body(void *baton,
3320            apr_pool_t *scratch_pool)
3321{
3322  commit_baton_t *cb = baton;
3323  svn_fs_x__data_t *ffd = cb->fs->fsap_data;
3324  const char *old_rev_filename, *rev_filename, *proto_filename;
3325  const char *revprop_filename, *final_revprop;
3326  svn_fs_x__id_t root_id, new_root_id;
3327  svn_revnum_t old_rev, new_rev;
3328  apr_file_t *proto_file;
3329  void *proto_file_lockcookie;
3330  apr_off_t initial_offset, changed_path_offset;
3331  svn_fs_x__txn_id_t txn_id = svn_fs_x__txn_get_id(cb->txn);
3332  apr_hash_t *changed_paths;
3333
3334  /* Re-Read the current repository format.  All our repo upgrade and
3335     config evaluation strategies are such that existing information in
3336     FS and FFD remains valid.
3337
3338     Although we don't recommend upgrading hot repositories, people may
3339     still do it and we must make sure to either handle them gracefully
3340     or to error out.
3341
3342     Committing pre-format 3 txns will fail after upgrade to format 3+
3343     because the proto-rev cannot be found; no further action needed.
3344     Upgrades from pre-f7 to f7+ means a potential change in addressing
3345     mode for the final rev.  We must be sure to detect that cause because
3346     the failure would only manifest once the new revision got committed.
3347   */
3348  SVN_ERR(svn_fs_x__read_format_file(cb->fs, scratch_pool));
3349
3350  /* Get the current youngest revision. */
3351  SVN_ERR(svn_fs_x__youngest_rev(&old_rev, cb->fs, scratch_pool));
3352
3353  /* Check to make sure this transaction is based off the most recent
3354     revision. */
3355  if (cb->txn->base_rev != old_rev)
3356    return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
3357                            _("Transaction out of date"));
3358
3359  /* We need the changes list for verification as well as for writing it
3360     to the final rev file. */
3361  SVN_ERR(svn_fs_x__txn_changes_fetch(&changed_paths, cb->fs, txn_id,
3362                                      scratch_pool));
3363
3364  /* Locks may have been added (or stolen) between the calling of
3365     previous svn_fs.h functions and svn_fs_commit_txn(), so we need
3366     to re-examine every changed-path in the txn and re-verify all
3367     discovered locks. */
3368  SVN_ERR(verify_locks(cb->fs, txn_id, changed_paths, scratch_pool));
3369
3370  /* We are going to be one better than this puny old revision. */
3371  new_rev = old_rev + 1;
3372
3373  /* Get a write handle on the proto revision file. */
3374  SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
3375                                 cb->fs, txn_id, scratch_pool));
3376  SVN_ERR(svn_fs_x__get_file_offset(&initial_offset, proto_file,
3377                                    scratch_pool));
3378
3379  /* Write out all the node-revisions and directory contents. */
3380  svn_fs_x__init_txn_root(&root_id, txn_id);
3381  SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, &root_id,
3382                          initial_offset, cb->reps_to_cache, cb->reps_hash,
3383                          cb->reps_pool, TRUE, scratch_pool));
3384
3385  /* Write the changed-path information. */
3386  SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
3387                                        cb->fs, txn_id, changed_paths,
3388                                        new_rev, scratch_pool));
3389
3390  /* Append the index data to the rev file. */
3391  SVN_ERR(svn_fs_x__add_index_data(cb->fs, proto_file,
3392                svn_fs_x__path_l2p_proto_index(cb->fs, txn_id, scratch_pool),
3393                svn_fs_x__path_p2l_proto_index(cb->fs, txn_id, scratch_pool),
3394                new_rev, scratch_pool));
3395
3396  SVN_ERR(svn_io_file_flush_to_disk(proto_file, scratch_pool));
3397  SVN_ERR(svn_io_file_close(proto_file, scratch_pool));
3398
3399  /* We don't unlock the prototype revision file immediately to avoid a
3400     race with another caller writing to the prototype revision file
3401     before we commit it. */
3402
3403  /* Create the shard for the rev and revprop file, if we're sharding and
3404     this is the first revision of a new shard.  We don't care if this
3405     fails because the shard already existed for some reason. */
3406  if (new_rev % ffd->max_files_per_dir == 0)
3407    {
3408      /* Create the revs shard. */
3409        {
3410          const char *new_dir
3411            = svn_fs_x__path_rev_shard(cb->fs, new_rev, scratch_pool);
3412          svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT,
3413                                             scratch_pool);
3414          if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
3415            return svn_error_trace(err);
3416          svn_error_clear(err);
3417          SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
3418                                                    PATH_REVS_DIR,
3419                                                    scratch_pool),
3420                                    new_dir, scratch_pool));
3421        }
3422
3423      /* Create the revprops shard. */
3424      SVN_ERR_ASSERT(! svn_fs_x__is_packed_revprop(cb->fs, new_rev));
3425        {
3426          const char *new_dir
3427            = svn_fs_x__path_revprops_shard(cb->fs, new_rev, scratch_pool);
3428          svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT,
3429                                             scratch_pool);
3430          if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
3431            return svn_error_trace(err);
3432          svn_error_clear(err);
3433          SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
3434                                                    PATH_REVPROPS_DIR,
3435                                                    scratch_pool),
3436                                    new_dir, scratch_pool));
3437        }
3438    }
3439
3440  /* Move the finished rev file into place.
3441
3442     ### This "breaks" the transaction by removing the protorev file
3443     ### but the revision is not yet complete.  If this commit does
3444     ### not complete for any reason the transaction will be lost. */
3445  old_rev_filename = svn_fs_x__path_rev_absolute(cb->fs, old_rev,
3446                                                 scratch_pool);
3447
3448  rev_filename = svn_fs_x__path_rev(cb->fs, new_rev, scratch_pool);
3449  proto_filename = svn_fs_x__path_txn_proto_rev(cb->fs, txn_id,
3450                                                scratch_pool);
3451  SVN_ERR(svn_fs_x__move_into_place(proto_filename, rev_filename,
3452                                    old_rev_filename, scratch_pool));
3453
3454  /* Now that we've moved the prototype revision file out of the way,
3455     we can unlock it (since further attempts to write to the file
3456     will fail as it no longer exists).  We must do this so that we can
3457     remove the transaction directory later. */
3458  SVN_ERR(unlock_proto_rev(cb->fs, txn_id, proto_file_lockcookie,
3459                           scratch_pool));
3460
3461  /* Move the revprops file into place. */
3462  SVN_ERR_ASSERT(! svn_fs_x__is_packed_revprop(cb->fs, new_rev));
3463  SVN_ERR(write_final_revprop(&revprop_filename, cb->txn, txn_id,
3464                              scratch_pool));
3465  final_revprop = svn_fs_x__path_revprops(cb->fs, new_rev, scratch_pool);
3466  SVN_ERR(svn_fs_x__move_into_place(revprop_filename, final_revprop,
3467                                    old_rev_filename, scratch_pool));
3468
3469  /* Update the 'current' file. */
3470  SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev,
3471                                                      scratch_pool));
3472  SVN_ERR(svn_fs_x__write_current(cb->fs, new_rev, scratch_pool));
3473
3474  /* At this point the new revision is committed and globally visible
3475     so let the caller know it succeeded by giving it the new revision
3476     number, which fulfills svn_fs_commit_txn() contract.  Any errors
3477     after this point do not change the fact that a new revision was
3478     created. */
3479  *cb->new_rev_p = new_rev;
3480
3481  ffd->youngest_rev_cache = new_rev;
3482
3483  /* Remove this transaction directory. */
3484  SVN_ERR(svn_fs_x__purge_txn(cb->fs, cb->txn->id, scratch_pool));
3485
3486  return SVN_NO_ERROR;
3487}
3488
3489/* Add the representations in REPS_TO_CACHE (an array of
3490 * svn_fs_x__representation_t *) to the rep-cache database of FS. */
3491static svn_error_t *
3492write_reps_to_cache(svn_fs_t *fs,
3493                    const apr_array_header_t *reps_to_cache,
3494                    apr_pool_t *scratch_pool)
3495{
3496  int i;
3497
3498  for (i = 0; i < reps_to_cache->nelts; i++)
3499    {
3500      svn_fs_x__representation_t *rep
3501        = APR_ARRAY_IDX(reps_to_cache, i, svn_fs_x__representation_t *);
3502
3503      /* FALSE because we don't care if another parallel commit happened to
3504       * collide with us.  (Non-parallel collisions will not be detected.) */
3505      SVN_ERR(svn_fs_x__set_rep_reference(fs, rep, scratch_pool));
3506    }
3507
3508  return SVN_NO_ERROR;
3509}
3510
3511svn_error_t *
3512svn_fs_x__commit(svn_revnum_t *new_rev_p,
3513                 svn_fs_t *fs,
3514                 svn_fs_txn_t *txn,
3515                 apr_pool_t *scratch_pool)
3516{
3517  commit_baton_t cb;
3518  svn_fs_x__data_t *ffd = fs->fsap_data;
3519
3520  cb.new_rev_p = new_rev_p;
3521  cb.fs = fs;
3522  cb.txn = txn;
3523
3524  if (ffd->rep_sharing_allowed)
3525    {
3526      cb.reps_to_cache = apr_array_make(scratch_pool, 5,
3527                                        sizeof(svn_fs_x__representation_t *));
3528      cb.reps_hash = apr_hash_make(scratch_pool);
3529      cb.reps_pool = scratch_pool;
3530    }
3531  else
3532    {
3533      cb.reps_to_cache = NULL;
3534      cb.reps_hash = NULL;
3535      cb.reps_pool = NULL;
3536    }
3537
3538  SVN_ERR(svn_fs_x__with_write_lock(fs, commit_body, &cb, scratch_pool));
3539
3540  /* At this point, *NEW_REV_P has been set, so errors below won't affect
3541     the success of the commit.  (See svn_fs_commit_txn().)  */
3542
3543  if (ffd->rep_sharing_allowed)
3544    {
3545      SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool));
3546
3547      /* Write new entries to the rep-sharing database.
3548       *
3549       * We use an sqlite transaction to speed things up;
3550       * see <http://www.sqlite.org/faq.html#q19>.
3551       */
3552      /* ### A commit that touches thousands of files will starve other
3553             (reader/writer) commits for the duration of the below call.
3554             Maybe write in batches? */
3555      SVN_SQLITE__WITH_TXN(
3556        write_reps_to_cache(fs, cb.reps_to_cache, scratch_pool),
3557        ffd->rep_cache_db);
3558    }
3559
3560  return SVN_NO_ERROR;
3561}
3562
3563
3564svn_error_t *
3565svn_fs_x__list_transactions(apr_array_header_t **names_p,
3566                            svn_fs_t *fs,
3567                            apr_pool_t *pool)
3568{
3569  const char *txn_dir;
3570  apr_hash_t *dirents;
3571  apr_hash_index_t *hi;
3572  apr_array_header_t *names;
3573  apr_size_t ext_len = strlen(PATH_EXT_TXN);
3574
3575  names = apr_array_make(pool, 1, sizeof(const char *));
3576
3577  /* Get the transactions directory. */
3578  txn_dir = svn_fs_x__path_txns_dir(fs, pool);
3579
3580  /* Now find a listing of this directory. */
3581  SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool));
3582
3583  /* Loop through all the entries and return anything that ends with '.txn'. */
3584  for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
3585    {
3586      const char *name = apr_hash_this_key(hi);
3587      apr_ssize_t klen = apr_hash_this_key_len(hi);
3588      const char *id;
3589
3590      /* The name must end with ".txn" to be considered a transaction. */
3591      if ((apr_size_t) klen <= ext_len
3592          || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0)
3593        continue;
3594
3595      /* Truncate the ".txn" extension and store the ID. */
3596      id = apr_pstrndup(pool, name, strlen(name) - ext_len);
3597      APR_ARRAY_PUSH(names, const char *) = id;
3598    }
3599
3600  *names_p = names;
3601
3602  return SVN_NO_ERROR;
3603}
3604
3605svn_error_t *
3606svn_fs_x__open_txn(svn_fs_txn_t **txn_p,
3607                   svn_fs_t *fs,
3608                   const char *name,
3609                   apr_pool_t *pool)
3610{
3611  svn_fs_txn_t *txn;
3612  fs_txn_data_t *ftd;
3613  svn_node_kind_t kind;
3614  svn_fs_x__transaction_t *local_txn;
3615  svn_fs_x__txn_id_t txn_id;
3616
3617  SVN_ERR(svn_fs_x__txn_by_name(&txn_id, name));
3618
3619  /* First check to see if the directory exists. */
3620  SVN_ERR(svn_io_check_path(svn_fs_x__path_txn_dir(fs, txn_id, pool),
3621                            &kind, pool));
3622
3623  /* Did we find it? */
3624  if (kind != svn_node_dir)
3625    return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL,
3626                             _("No such transaction '%s'"),
3627                             name);
3628
3629  txn = apr_pcalloc(pool, sizeof(*txn));
3630  ftd = apr_pcalloc(pool, sizeof(*ftd));
3631  ftd->txn_id = txn_id;
3632
3633  /* Read in the root node of this transaction. */
3634  txn->id = apr_pstrdup(pool, name);
3635  txn->fs = fs;
3636
3637  SVN_ERR(svn_fs_x__get_txn(&local_txn, fs, txn_id, pool));
3638
3639  txn->base_rev = local_txn->base_rev;
3640
3641  txn->vtable = &txn_vtable;
3642  txn->fsap_data = ftd;
3643  *txn_p = txn;
3644
3645  return SVN_NO_ERROR;
3646}
3647
3648svn_error_t *
3649svn_fs_x__txn_proplist(apr_hash_t **table_p,
3650                       svn_fs_txn_t *txn,
3651                       apr_pool_t *pool)
3652{
3653  apr_hash_t *proplist = apr_hash_make(pool);
3654  SVN_ERR(get_txn_proplist(proplist, txn->fs, svn_fs_x__txn_get_id(txn),
3655                           pool));
3656  *table_p = proplist;
3657
3658  return SVN_NO_ERROR;
3659}
3660
3661svn_error_t *
3662svn_fs_x__delete_node_revision(svn_fs_t *fs,
3663                               const svn_fs_x__id_t *id,
3664                               apr_pool_t *scratch_pool)
3665{
3666  svn_fs_x__noderev_t *noderev;
3667  SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, id, scratch_pool,
3668                                      scratch_pool));
3669
3670  /* Delete any mutable property representation. */
3671  if (noderev->prop_rep
3672      && svn_fs_x__is_txn(noderev->prop_rep->id.change_set))
3673    SVN_ERR(svn_io_remove_file2(svn_fs_x__path_txn_node_props(fs, id,
3674                                                              scratch_pool,
3675                                                              scratch_pool),
3676                                FALSE, scratch_pool));
3677
3678  /* Delete any mutable data representation. */
3679  if (noderev->data_rep
3680      && svn_fs_x__is_txn(noderev->data_rep->id.change_set)
3681      && noderev->kind == svn_node_dir)
3682    {
3683      svn_fs_x__data_t *ffd = fs->fsap_data;
3684      const svn_fs_x__id_t *key = id;
3685
3686      SVN_ERR(svn_io_remove_file2(
3687                  svn_fs_x__path_txn_node_children(fs, id, scratch_pool,
3688                                                   scratch_pool),
3689                  FALSE, scratch_pool));
3690
3691      /* remove the corresponding entry from the cache, if such exists */
3692      SVN_ERR(svn_cache__set(ffd->dir_cache, key, NULL, scratch_pool));
3693    }
3694
3695  return svn_io_remove_file2(svn_fs_x__path_txn_node_rev(fs, id,
3696                                                         scratch_pool,
3697                                                         scratch_pool),
3698                             FALSE, scratch_pool);
3699}
3700
3701
3702
3703/*** Transactions ***/
3704
3705svn_error_t *
3706svn_fs_x__get_base_rev(svn_revnum_t *revnum,
3707                       svn_fs_t *fs,
3708                       svn_fs_x__txn_id_t txn_id,
3709                       apr_pool_t *scratch_pool)
3710{
3711  svn_fs_x__transaction_t *txn;
3712  SVN_ERR(svn_fs_x__get_txn(&txn, fs, txn_id, scratch_pool));
3713  *revnum = txn->base_rev;
3714
3715  return SVN_NO_ERROR;
3716}
3717
3718
3719/* Generic transaction operations.  */
3720
3721svn_error_t *
3722svn_fs_x__txn_prop(svn_string_t **value_p,
3723                   svn_fs_txn_t *txn,
3724                   const char *propname,
3725                   apr_pool_t *pool)
3726{
3727  apr_hash_t *table;
3728  svn_fs_t *fs = txn->fs;
3729
3730  SVN_ERR(svn_fs__check_fs(fs, TRUE));
3731  SVN_ERR(svn_fs_x__txn_proplist(&table, txn, pool));
3732
3733  *value_p = svn_hash_gets(table, propname);
3734
3735  return SVN_NO_ERROR;
3736}
3737
3738svn_error_t *
3739svn_fs_x__begin_txn(svn_fs_txn_t **txn_p,
3740                    svn_fs_t *fs,
3741                    svn_revnum_t rev,
3742                    apr_uint32_t flags,
3743                    apr_pool_t *result_pool,
3744                    apr_pool_t *scratch_pool)
3745{
3746  svn_string_t date;
3747  fs_txn_data_t *ftd;
3748  apr_hash_t *props = apr_hash_make(scratch_pool);
3749
3750  SVN_ERR(svn_fs__check_fs(fs, TRUE));
3751
3752  SVN_ERR(create_txn(txn_p, fs, rev, result_pool, scratch_pool));
3753
3754  /* Put a datestamp on the newly created txn, so we always know
3755     exactly how old it is.  (This will help sysadmins identify
3756     long-abandoned txns that may need to be manually removed.)  When
3757     a txn is promoted to a revision, this property will be
3758     automatically overwritten with a revision datestamp. */
3759  date.data = svn_time_to_cstring(apr_time_now(), scratch_pool);
3760  date.len = strlen(date.data);
3761
3762  svn_hash_sets(props, SVN_PROP_REVISION_DATE, &date);
3763
3764  /* Set temporary txn props that represent the requested 'flags'
3765     behaviors. */
3766  if (flags & SVN_FS_TXN_CHECK_OOD)
3767    svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_OOD,
3768                  svn_string_create("true", scratch_pool));
3769
3770  if (flags & SVN_FS_TXN_CHECK_LOCKS)
3771    svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_LOCKS,
3772                  svn_string_create("true", scratch_pool));
3773
3774  if (flags & SVN_FS_TXN_CLIENT_DATE)
3775    svn_hash_sets(props, SVN_FS__PROP_TXN_CLIENT_DATE,
3776                  svn_string_create("0", scratch_pool));
3777
3778  ftd = (*txn_p)->fsap_data;
3779  SVN_ERR(set_txn_proplist(fs, ftd->txn_id, props, FALSE, scratch_pool));
3780
3781  return SVN_NO_ERROR;
3782}
3783