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