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