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