1 /* transaction.c --- transaction-related functions of FSX
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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
20 * ====================================================================
23 #include "transaction.h"
28 #include "svn_error_codes.h"
30 #include "svn_props.h"
31 #include "svn_sorts.h"
33 #include "svn_dirent_uri.h"
39 #include "low_level.h"
40 #include "temp_serializer.h"
41 #include "cached_data.h"
43 #include "rep-cache.h"
45 #include "batch_fsync.h"
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"
56 #include "svn_private_config.h"
58 /* The vtable associated with an open transaction object. */
59 static txn_vtable_t txn_vtable = {
63 svn_fs_x__txn_proplist,
64 svn_fs_x__change_txn_prop,
66 svn_fs_x__change_txn_props
69 /* FSX-specific data being attached to svn_fs_txn_t.
71 typedef struct fs_txn_data_t
73 /* Strongly typed representation of the TXN's ID member. */
74 svn_fs_x__txn_id_t txn_id;
78 svn_fs_x__txn_get_id(svn_fs_txn_t *txn)
80 fs_txn_data_t *ftd = txn->fsap_data;
84 /* Functions for working with shared transaction data. */
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)
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;
100 for (txn = ffsd->txns; txn; txn = txn->next)
101 if (txn->txn_id == txn_id)
104 if (txn || !create_new)
107 /* Use the transaction object from the (single-object) freelist,
108 if one is available, or otherwise create a new object. */
111 txn = ffsd->free_txn;
112 ffsd->free_txn = NULL;
116 apr_pool_t *subpool = svn_pool_create(ffsd->common_pool);
117 txn = apr_palloc(subpool, sizeof(*txn));
121 txn->txn_id = txn_id;
122 txn->being_written = FALSE;
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;
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
139 free_shared_txn(svn_fs_t *fs, svn_fs_x__txn_id_t txn_id)
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;
145 for (txn = ffsd->txns; txn; prev = txn, txn = txn->next)
146 if (txn->txn_id == txn_id)
153 prev->next = txn->next;
155 ffsd->txns = txn->next;
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. */
161 ffsd->free_txn = txn;
163 svn_pool_destroy(txn->pool);
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. */
171 with_txnlist_lock(svn_fs_t *fs,
172 svn_error_t *(*body)(svn_fs_t *fs,
178 svn_fs_x__data_t *ffd = fs->fsap_data;
179 svn_fs_x__shared_data_t *ffsd = ffd->shared;
181 SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock,
182 body(fs, baton, pool));
188 /* Get a lock on empty file LOCK_FILENAME, creating it in RESULT_POOL. */
190 get_lock_on_filesystem(const char *lock_filename,
191 apr_pool_t *result_pool)
193 return svn_error_trace(svn_io__file_lock_autocreate(lock_filename,
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. */
201 reset_lock_flag(void *baton_void)
203 svn_fs_x__data_t *ffd = baton_void;
204 ffd->has_write_lock = FALSE;
208 /* Structure defining a file system lock to be acquired and the function
209 to be executed while the lock is held.
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.
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
222 typedef struct with_lock_baton_t
224 /* The filesystem we operate on. Same for all instances along the chain. */
227 /* Mutex to complement the lock file in an APR threaded process.
228 No-op object for non-threaded processes but never NULL. */
231 /* Path to the file to lock. */
232 const char *lock_path;
234 /* If true, set FS->HAS_WRITE_LOCK after we acquired the lock. */
235 svn_boolean_t is_global_lock;
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);
242 /* Baton to pass to BODY; possibly NULL.
243 This may be user-provided or a nested lock baton instance. */
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;
252 /* TRUE, iff BODY is the user-provided body. */
253 svn_boolean_t is_inner_most_lock;
255 /* TRUE, iff this is not a nested lock.
256 Then responsible for destroying LOCK_POOL. */
257 svn_boolean_t is_outer_most_lock;
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. */
265 with_some_lock_file(with_lock_baton_t *baton)
267 apr_pool_t *pool = baton->lock_pool;
268 svn_error_t *err = get_lock_on_filesystem(baton->lock_path, pool);
272 svn_fs_t *fs = baton->fs;
273 svn_fs_x__data_t *ffd = fs->fsap_data;
275 if (baton->is_global_lock)
277 /* set the "got the lock" flag and register reset function */
278 apr_pool_cleanup_register(pool,
281 apr_pool_cleanup_null);
282 ffd->has_write_lock = TRUE;
285 if (baton->is_inner_most_lock)
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.
290 apr_pool_t *subpool = svn_pool_create(pool);
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);
296 err = svn_fs_x__youngest_rev(&ffd->youngest_rev_cache, fs,
299 /* We performed a few file operations. Clean the pool. */
300 svn_pool_clear(subpool);
303 err = baton->body(baton->baton, subpool);
305 svn_pool_destroy(subpool);
309 /* Nested lock level */
310 err = baton->body(baton->baton, pool);
314 if (baton->is_outer_most_lock)
315 svn_pool_destroy(pool);
317 return svn_error_trace(err);
320 /* Wraps with_some_lock_file, protecting it with BATON->MUTEX.
322 SCRATCH_POOL is unused here and only provided for signature compatibility
323 with WITH_LOCK_BATON_T.BODY. */
325 with_lock(void *baton,
326 apr_pool_t *scratch_pool)
328 with_lock_baton_t *lock_baton = baton;
329 SVN_MUTEX__WITH_LOCK(lock_baton->mutex, with_some_lock_file(lock_baton));
334 /* Enum identifying a filesystem lock. */
335 typedef enum lock_id_t
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
346 init_lock_baton(with_lock_baton_t *baton,
349 svn_fs_x__data_t *ffd = baton->fs->fsap_data;
350 svn_fs_x__shared_data_t *ffsd = ffd->shared;
355 baton->mutex = ffsd->txn_current_lock;
356 baton->lock_path = svn_fs_x__path_txn_current_lock(baton->fs,
358 baton->is_global_lock = FALSE;
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;
368 baton->mutex = ffsd->fs_pack_lock;
369 baton->lock_path = svn_fs_x__path_pack_lock(baton->fs,
371 baton->is_global_lock = FALSE;
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
381 static with_lock_baton_t *
382 create_lock_baton(svn_fs_t *fs,
384 svn_error_t *(*body)(void *baton,
385 apr_pool_t *scratch_pool),
387 apr_pool_t *result_pool)
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));
394 /* Store parameters. */
397 result->baton = baton;
399 /* File locks etc. will use this pool as well for easy cleanup. */
400 result->lock_pool = lock_pool;
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;
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);
413 /* Return a baton that wraps NESTED and requests LOCK_ID as additional lock.
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.
418 static with_lock_baton_t *
419 chain_lock_baton(lock_id_t lock_id,
420 with_lock_baton_t *nested)
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));
426 /* All locks along the chain operate on the same FS. */
427 result->fs = nested->fs;
429 /* Execution of this baton means acquiring the nested lock and its
431 result->body = with_lock;
432 result->baton = nested;
434 /* Shared among all locks along the chain. */
435 result->lock_pool = lock_pool;
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;
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);
450 svn_fs_x__with_write_lock(svn_fs_t *fs,
451 svn_error_t *(*body)(void *baton,
452 apr_pool_t *scratch_pool),
454 apr_pool_t *scratch_pool)
456 return svn_error_trace(
457 with_lock(create_lock_baton(fs, write_lock, body, baton,
463 svn_fs_x__with_pack_lock(svn_fs_t *fs,
464 svn_error_t *(*body)(void *baton,
465 apr_pool_t *scratch_pool),
467 apr_pool_t *scratch_pool)
469 return svn_error_trace(
470 with_lock(create_lock_baton(fs, pack_lock, body, baton,
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),
480 apr_pool_t *scratch_pool)
482 return svn_error_trace(
483 with_lock(create_lock_baton(fs, txn_lock, body, baton,
489 svn_fs_x__with_all_locks(svn_fs_t *fs,
490 svn_error_t *(*body)(void *baton,
491 apr_pool_t *scratch_pool),
493 apr_pool_t *scratch_pool)
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);
501 lock_baton = chain_lock_baton(write_lock, lock_baton);
502 lock_baton = chain_lock_baton(pack_lock, lock_baton);
504 return svn_error_trace(with_lock(lock_baton, scratch_pool));
508 /* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
510 typedef struct unlock_proto_rev_baton_t
512 svn_fs_x__txn_id_t txn_id;
514 } unlock_proto_rev_baton_t;
516 /* Callback used in the implementation of unlock_proto_rev(). */
518 unlock_proto_rev_body(svn_fs_t *fs,
520 apr_pool_t *scratch_pool)
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;
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));
536 apr_err = apr_file_unlock(lockfile);
538 return svn_error_wrap_apr
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);
544 return svn_error_wrap_apr
546 _("Can't close prototype revision lockfile for transaction '%s'"),
547 svn_fs_x__txn_name(b->txn_id, scratch_pool));
549 txn->being_written = FALSE;
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.
558 Perform temporary allocations in SCRATCH_POOL. */
560 unlock_proto_rev(svn_fs_t *fs,
561 svn_fs_x__txn_id_t txn_id,
563 apr_pool_t *scratch_pool)
565 unlock_proto_rev_baton_t b;
568 b.lockcookie = lockcookie;
569 return with_txnlist_lock(fs, unlock_proto_rev_body, &b, scratch_pool);
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
577 svn_fs_x__txn_id_t txn_id;
578 } get_writable_proto_rev_baton_t;
580 /* Callback used in the implementation of get_writable_proto_rev(). */
582 get_writable_proto_rev_body(svn_fs_t *fs,
584 apr_pool_t *scratch_pool)
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);
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 "
598 svn_fs_x__txn_name(b->txn_id, scratch_pool));
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. */
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);
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
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,
624 apr_err = apr_file_lock(lockfile,
625 APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK);
628 svn_error_clear(svn_io_file_close(lockfile, scratch_pool));
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,
639 return svn_error_wrap_apr(apr_err,
640 _("Can't get exclusive lock on file '%s'"),
641 svn_dirent_local_style(lockfile_path,
645 *lockcookie = lockfile;
648 /* We've successfully locked the transaction; mark it as such. */
649 txn->being_written = TRUE;
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.
658 If the prototype revision file is too short, we can't do much but bail out.
660 Perform all allocations in SCRATCH_POOL. */
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)
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);
673 apr_off_t indexed_length;
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,
678 SVN_ERR(svn_io_file_close(file, scratch_pool));
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,
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));
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
701 If the prototype revision file is already locked, return error
702 SVN_ERR_FS_REP_BEING_WRITTEN.
704 Perform all allocations in POOL. */
706 get_writable_proto_rev(apr_file_t **file,
709 svn_fs_x__txn_id_t txn_id,
712 get_writable_proto_rev_baton_t b;
714 apr_off_t end_offset = 0;
716 b.lockcookie = lockcookie;
719 SVN_ERR(with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool));
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,
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). */
735 err = svn_io_file_seek(*file, APR_END, &end_offset, pool);
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. */
743 err = auto_truncate_proto_rev(fs, *file, end_offset, txn_id, pool);
747 err = svn_error_compose_create(
749 unlock_proto_rev(fs, txn_id, *lockcookie, pool));
754 return svn_error_trace(err);
757 /* Callback used in the implementation of purge_shared_txn(). */
759 purge_shared_txn_body(svn_fs_t *fs,
761 apr_pool_t *scratch_pool)
763 svn_fs_x__txn_id_t txn_id = *(const svn_fs_x__txn_id_t *)baton;
765 free_shared_txn(fs, txn_id);
770 /* Purge the shared data for transaction TXN_ID in filesystem FS.
771 Perform all temporary allocations in SCRATCH_POOL. */
773 purge_shared_txn(svn_fs_t *fs,
774 svn_fs_x__txn_id_t txn_id,
775 apr_pool_t *scratch_pool)
777 return with_txnlist_lock(fs, purge_shared_txn_body, &txn_id, scratch_pool);
782 svn_fs_x__is_fresh_txn_root(svn_fs_x__noderev_t *noderev)
784 /* Is it a root node? */
785 if (noderev->noderev_id.number != SVN_FS_X__ITEM_INDEX_ROOT_NODE)
788 /* ... in a transaction? */
789 if (!svn_fs_x__is_txn(noderev->noderev_id.change_set))
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))
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))
802 /* Root node of a txn with no changes. */
807 svn_fs_x__put_node_revision(svn_fs_t *fs,
808 svn_fs_x__noderev_t *noderev,
809 apr_pool_t *scratch_pool)
811 apr_file_t *noderev_file;
812 const svn_fs_x__id_t *id = &noderev->noderev_id;
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);
819 SVN_ERR(svn_io_file_open(&noderev_file,
820 svn_fs_x__path_txn_node_rev(fs, id, scratch_pool,
822 APR_WRITE | APR_CREATE | APR_TRUNCATE
823 | APR_BUFFERED, APR_OS_DEFAULT, scratch_pool));
825 SVN_ERR(svn_fs_x__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE,
827 noderev, scratch_pool));
829 SVN_ERR(svn_io_file_close(noderev_file, scratch_pool));
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.
839 store_sha1_rep_mapping(svn_fs_t *fs,
840 svn_fs_x__noderev_t *noderev,
841 apr_pool_t *scratch_pool)
843 svn_fs_x__data_t *ffd = fs->fsap_data;
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
849 && noderev->data_rep->has_sha1)
851 apr_file_t *rep_file;
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,
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);
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));
867 SVN_ERR(svn_io_file_write_full(rep_file, rep_string->data,
868 rep_string->len, NULL, scratch_pool));
870 SVN_ERR(svn_io_file_close(rep_file, scratch_pool));
877 unparse_dir_entry(svn_fs_x__dirent_t *dirent,
878 svn_stream_t *stream,
879 apr_pool_t *scratch_pool)
882 apr_size_t name_len = strlen(dirent->name);
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
889 apr_byte_t *buffer = apr_palloc(scratch_pool,
890 name_len + 2 + 2 * SVN__MAX_ENCODED_UINT_LEN);
892 /* Now construct the value. */
893 apr_byte_t *p = buffer;
895 /* The entry name, terminated by NUL. */
896 memcpy(p, dirent->name, name_len + 1);
899 /* The entry type. */
900 p = svn__encode_uint(p, dirent->kind);
903 p = svn__encode_int(p, dirent->id.change_set);
904 p = svn__encode_uint(p, dirent->id.number);
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));
913 /* Write the directory given as array of dirent structs in ENTRIES to STREAM.
914 Perform temporary allocations in SCRATCH_POOL. */
916 unparse_dir_entries(apr_array_header_t *entries,
917 svn_stream_t *stream,
918 apr_pool_t *scratch_pool)
920 apr_byte_t buffer[SVN__MAX_ENCODED_UINT_LEN];
921 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
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));
928 /* Write all entries */
929 for (i = 0; i < entries->nelts; ++i)
931 svn_fs_x__dirent_t *dirent;
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));
938 svn_pool_destroy(iterpool);
942 /* Return a deep copy of SOURCE and allocate it in RESULT_POOL.
944 static svn_fs_x__change_t *
945 path_change_dup(const svn_fs_x__change_t *source,
946 apr_pool_t *result_pool)
948 svn_fs_x__change_t *result
949 = apr_pmemdup(result_pool, source, sizeof(*source));
951 = apr_pstrmemdup(result_pool, source->path.data, source->path.len);
953 if (source->copyfrom_path)
954 result->copyfrom_path = apr_pstrdup(result_pool, source->copyfrom_path);
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. */
965 fold_change(apr_hash_t *changed_paths,
966 apr_hash_t *deletions,
967 const svn_fs_x__change_t *change)
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;
973 if ((old_change = apr_hash_get(changed_paths, path->data, path->len)))
975 /* This path already exists in the hash, so we have to merge
976 this change into the already existing one. */
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"));
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"));
995 /* Now, merge that change in. */
996 switch (change->change_kind)
998 case svn_fs_path_change_delete:
999 if (old_change->change_kind == svn_fs_path_change_add)
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);
1006 else if (old_change->change_kind == svn_fs_path_change_replace)
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);
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);
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;
1030 apr_hash_set(changed_paths, path->data, path->len, new_change);
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);
1040 case svn_fs_path_change_modify:
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;
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);
1064 return SVN_NO_ERROR;
1067 /* Baton type to be used with process_changes(). */
1068 typedef struct process_changes_baton_t
1070 /* Folded list of path changes. */
1071 apr_hash_t *changed_paths;
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;
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)
1088 process_changes_baton_t *baton = baton_p;
1090 SVN_ERR(fold_change(baton->changed_paths, baton->deletions, change));
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.
1100 if ((change->change_kind == svn_fs_path_change_delete)
1101 || (change->change_kind == svn_fs_path_change_replace))
1103 apr_hash_index_t *hi;
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.
1110 apr_ssize_t path_len = change->path.len;
1111 apr_ssize_t min_child_len = path_len == 0
1113 : change->path.data[path_len-1] == '/'
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.
1121 for (hi = apr_hash_first(scratch_pool, baton->changed_paths);
1123 hi = apr_hash_next(hi))
1125 /* KEY is the path. */
1128 apr_hash_this(hi, &path, &klen, NULL);
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.
1134 if (klen >= min_child_len)
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);
1145 return SVN_NO_ERROR;
1149 svn_fs_x__txn_changes_fetch(apr_hash_t **changed_paths_p,
1151 svn_fs_x__txn_id_t txn_id,
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;
1159 baton.changed_paths = changed_paths;
1160 baton.deletions = apr_hash_make(scratch_pool);
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,
1167 SVN_ERR(svn_fs_x__read_changes_incrementally(
1168 svn_stream_from_aprfile2(file, TRUE,
1170 process_changes, &baton,
1172 svn_pool_destroy(scratch_pool);
1174 *changed_paths_p = changed_paths;
1176 return SVN_NO_ERROR;
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)
1188 svn_fs_x__noderev_t *noderev;
1189 SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, src, scratch_pool,
1192 /* This must be a root node. */
1193 SVN_ERR_ASSERT( noderev->node_id.number == 0
1194 && noderev->copy_id.number == 0);
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"));
1200 noderev->predecessor_id = noderev->noderev_id;
1201 noderev->predecessor_count++;
1202 noderev->copyfrom_path = NULL;
1203 noderev->copyfrom_rev = SVN_INVALID_REVNUM;
1205 /* For the transaction root, the copyroot never changes. */
1206 svn_fs_x__init_txn_root(&noderev->noderev_id, txn_id);
1208 return svn_fs_x__put_node_revision(fs, noderev, scratch_pool);
1211 /* A structure used by get_and_increment_txn_key_body(). */
1212 typedef struct get_and_increment_txn_key_baton_t
1215 apr_uint64_t txn_number;
1216 } get_and_increment_txn_key_baton_t;
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)
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];
1231 svn_stringbuf_t *buf;
1232 SVN_ERR(svn_fs_x__read_content(&buf, txn_current_path, scratch_pool));
1234 /* Parse the txn number, stopping at the next non-digit.
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.
1240 cb->txn_number = svn__base36toui64(NULL, buf->data);
1241 if (cb->txn_number == 0)
1244 /* Check for conflicts. Those might happen if the server crashed and we
1245 * had 'svnadmin recover' reset the txn counter.
1247 * Once we found an unused txn id, claim it by creating the respective
1250 * Note that this is not racy because we hold the txn-current-lock.
1254 const char *txn_dir;
1255 svn_node_kind_t kind;
1256 svn_pool_clear(iterpool);
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)
1262 SVN_ERR(svn_io_dir_make(txn_dir, APR_OS_DEFAULT, iterpool));
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));
1276 svn_pool_destroy(iterpool);
1278 return SVN_NO_ERROR;
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,
1289 apr_pool_t *result_pool,
1290 apr_pool_t *scratch_pool)
1292 get_and_increment_txn_key_baton_t cb;
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. */
1299 SVN_ERR(svn_fs_x__with_txn_current_lock(fs,
1300 get_and_increment_txn_key_body,
1303 *txn_id = cb.txn_number;
1304 *id_p = svn_fs_x__txn_name(*txn_id, result_pool);
1306 return SVN_NO_ERROR;
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,
1316 apr_pool_t *result_pool,
1317 apr_pool_t *scratch_pool)
1321 svn_fs_x__id_t root_id;
1323 txn = apr_pcalloc(result_pool, sizeof(*txn));
1324 ftd = apr_pcalloc(result_pool, sizeof(*ftd));
1326 /* Valid revision number? */
1327 SVN_ERR(svn_fs_x__ensure_revision_exists(rev, fs, scratch_pool));
1329 /* Get the txn_id. */
1330 SVN_ERR(create_txn_dir(&txn->id, &ftd->txn_id, fs, result_pool,
1334 txn->base_rev = rev;
1336 txn->vtable = &txn_vtable;
1337 txn->fsap_data = ftd;
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,
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),
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),
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),
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));
1365 return SVN_NO_ERROR;
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,
1373 svn_fs_x__txn_id_t txn_id,
1374 apr_pool_t *result_pool,
1375 apr_pool_t *scratch_pool)
1377 svn_stringbuf_t *content;
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()"));
1386 /* Open the transaction properties file. */
1387 SVN_ERR(svn_stringbuf_from_file2(&content,
1388 svn_fs_x__path_txn_props(fs, txn_id,
1392 /* Read in the property list. */
1393 SVN_ERR_W(svn_fs_x__parse_properties(proplist,
1394 svn_stringbuf__morph_into_string(content),
1396 apr_psprintf(scratch_pool,
1397 _("malformed property list in transaction '%s'"),
1398 svn_fs_x__path_txn_props(fs, txn_id, scratch_pool)));
1400 return SVN_NO_ERROR;
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,
1409 apr_pool_t *scratch_pool)
1411 svn_stream_t *stream;
1412 const char *temp_path;
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,
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));
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,
1430 return SVN_NO_ERROR;
1435 svn_fs_x__change_txn_prop(svn_fs_txn_t *txn,
1437 const svn_string_t *value,
1438 apr_pool_t *scratch_pool)
1440 apr_array_header_t *props = apr_array_make(scratch_pool, 1,
1441 sizeof(svn_prop_t));
1446 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
1448 return svn_fs_x__change_txn_props(txn, props, scratch_pool);
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)
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;
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);
1470 return svn_error_trace(err);
1472 for (i = 0; i < props->nelts; i++)
1474 svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
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));
1481 svn_hash_sets(txn_prop, prop->name, prop->value);
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));
1488 svn_pool_destroy(subpool);
1489 return SVN_NO_ERROR;
1493 svn_fs_x__get_txn(svn_fs_x__transaction_t **txn_p,
1495 svn_fs_x__txn_id_t txn_id,
1498 svn_fs_x__transaction_t *txn;
1499 svn_fs_x__noderev_t *noderev;
1500 svn_fs_x__id_t root_id;
1502 txn = apr_pcalloc(pool, sizeof(*txn));
1503 svn_fs_x__init_txn_root(&root_id, txn_id);
1505 SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, &root_id, pool, pool));
1507 txn->base_rev = svn_fs_x__get_revnum(noderev->predecessor_id.change_set);
1512 return SVN_NO_ERROR;
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.
1519 static svn_error_t *
1520 store_l2p_index_entry(svn_fs_t *fs,
1521 svn_fs_x__txn_id_t txn_id,
1523 apr_uint64_t item_index,
1524 apr_pool_t *scratch_pool)
1526 const char *path = svn_fs_x__path_l2p_proto_index(fs, txn_id, scratch_pool);
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));
1533 return SVN_NO_ERROR;
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.
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)
1545 const char *path = svn_fs_x__path_p2l_proto_index(fs, txn_id, scratch_pool);
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));
1551 return SVN_NO_ERROR;
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.
1557 static svn_error_t *
1558 allocate_item_index(apr_uint64_t *item_index,
1560 svn_fs_x__txn_id_t txn_id,
1561 apr_pool_t *scratch_pool)
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;
1571 SVN_ERR(svn_io_file_open(&file,
1572 svn_fs_x__path_txn_item_index(fs, txn_id,
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));
1579 /* Item index file should be shorter than SVN_INT64_BUFFER_SIZE,
1580 otherwise we truncate data. */
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));
1587 *item_index = SVN_FS_X__ITEM_INDEX_FIRST_USER;
1590 to_write = svn__ui64toa(buffer, *item_index + 1);
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));
1597 return SVN_NO_ERROR;
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
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)
1613 char buffer[2 * SVN_INT64_BUFFER_SIZE + 2];
1616 p += svn__ui64tobase36(p, node_id);
1618 p += svn__ui64tobase36(p, copy_id);
1622 SVN_ERR(svn_io_file_open(&file,
1623 svn_fs_x__path_txn_next_ids(fs, txn_id,
1625 APR_WRITE | APR_TRUNCATE,
1626 APR_OS_DEFAULT, scratch_pool));
1627 SVN_ERR(svn_io_file_write_full(file, buffer, p - buffer, NULL,
1629 return svn_io_file_close(file, scratch_pool);
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,
1641 svn_fs_x__txn_id_t txn_id,
1642 apr_pool_t *scratch_pool)
1644 svn_stringbuf_t *buf;
1646 SVN_ERR(svn_fs_x__read_content(&buf,
1647 svn_fs_x__path_txn_next_ids(fs, txn_id,
1651 /* Parse this into two separate strings. */
1654 *node_id = svn__base36toui64(&str, str);
1656 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1657 _("next-id file corrupt"));
1660 *copy_id = svn__base36toui64(&str, str);
1662 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1663 _("next-id file corrupt"));
1665 return SVN_NO_ERROR;
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,
1676 svn_fs_x__txn_id_t txn_id,
1677 apr_pool_t *scratch_pool)
1679 apr_uint64_t node_id, copy_id;
1681 /* First read in the current next-ids file. */
1682 SVN_ERR(read_next_ids(&node_id, ©_id, fs, txn_id, scratch_pool));
1684 node_id_p->change_set = svn_fs_x__change_set_by_txn(txn_id);
1685 node_id_p->number = node_id;
1687 SVN_ERR(write_next_ids(fs, txn_id, ++node_id, copy_id, scratch_pool));
1689 return SVN_NO_ERROR;
1693 svn_fs_x__reserve_copy_id(svn_fs_x__id_t *copy_id_p,
1695 svn_fs_x__txn_id_t txn_id,
1696 apr_pool_t *scratch_pool)
1698 apr_uint64_t node_id, copy_id;
1700 /* First read in the current next-ids file. */
1701 SVN_ERR(read_next_ids(&node_id, ©_id, fs, txn_id, scratch_pool));
1703 copy_id_p->change_set = svn_fs_x__change_set_by_txn(txn_id);
1704 copy_id_p->number = copy_id;
1706 SVN_ERR(write_next_ids(fs, txn_id, node_id, ++copy_id, scratch_pool));
1708 return SVN_NO_ERROR;
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)
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));
1721 /* Assign copy-id. */
1722 noderev->copy_id = *copy_id;
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,
1729 SVN_ERR(svn_fs_x__put_node_revision(fs, noderev, scratch_pool));
1731 return SVN_NO_ERROR;
1735 svn_fs_x__purge_txn(svn_fs_t *fs,
1736 const char *txn_id_str,
1737 apr_pool_t *scratch_pool)
1739 svn_fs_x__txn_id_t txn_id;
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));
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));
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),
1759 SVN_ERR(svn_io_remove_file2(
1760 svn_fs_x__path_txn_proto_rev_lock(fs, txn_id, subpool),
1763 svn_pool_destroy(subpool);
1764 return SVN_NO_ERROR;
1769 svn_fs_x__abort_txn(svn_fs_txn_t *txn,
1770 apr_pool_t *scratch_pool)
1772 SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
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"),
1779 return SVN_NO_ERROR;
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,
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)
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);
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;
1804 if (!rep || !svn_fs_x__is_txn(rep->id.change_set))
1806 apr_array_header_t *entries;
1807 svn_fs_x__dir_data_t dir_data;
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,
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));
1819 /* Provide the parent with a data rep if it had none before
1820 (directories so far empty). */
1823 rep = apr_pcalloc(result_pool, sizeof(*rep));
1824 parent_noderev->data_rep = rep;
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;
1831 /* Save noderev to disk. */
1832 SVN_ERR(svn_fs_x__put_node_revision(fs, parent_noderev, subpool));
1834 /* Immediately populate the txn dir cache to avoid re-reading
1835 * the file we just wrote. */
1837 /* Flush APR buffers. */
1838 SVN_ERR(svn_io_file_flush(file, subpool));
1840 /* Obtain final file size to update txn_dir_cache. */
1841 SVN_ERR(svn_io_file_size_get(&filesize, file, subpool));
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));
1848 svn_pool_clear(subpool);
1852 svn_boolean_t found;
1853 svn_filesize_t cached_filesize;
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);
1860 /* If the cache contents is stale, drop it.
1862 * Note that the directory file is append-only, i.e. if the size
1863 * did not change, the contents didn't either. */
1865 /* Get the file size that corresponds to the cached contents
1867 SVN_ERR(svn_cache__get_partial((void **)&cached_filesize, &found,
1868 ffd->dir_cache, key,
1869 svn_fs_x__extract_dir_filesize,
1872 /* File size info still matches?
1873 * If not, we need to drop the cache entry. */
1876 SVN_ERR(svn_io_file_size_get(&filesize, file, subpool));
1878 if (cached_filesize != filesize)
1879 SVN_ERR(svn_cache__set(ffd->dir_cache, key, NULL, subpool));
1883 /* Append an incremental hash entry for the entry change.
1884 A deletion is represented by an "unused" noderev-id. */
1888 svn_fs_x__id_reset(&entry.id);
1893 SVN_ERR(unparse_dir_entry(&entry, out, subpool));
1895 /* Flush APR buffers. */
1896 SVN_ERR(svn_io_file_flush(file, subpool));
1898 /* Obtain final file size to update txn_dir_cache. */
1899 SVN_ERR(svn_io_file_size_get(&filesize, file, subpool));
1902 SVN_ERR(svn_io_file_close(file, subpool));
1903 svn_pool_clear(subpool);
1905 /* update directory cache */
1907 /* build parameters: name, new entry, new file size */
1908 replace_baton_t baton;
1911 baton.new_entry = NULL;
1912 baton.txn_filesize = filesize;
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;
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,
1928 svn_pool_destroy(subpool);
1929 return SVN_NO_ERROR;
1933 svn_fs_x__add_change(svn_fs_t *fs,
1934 svn_fs_x__txn_id_t txn_id,
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)
1946 svn_fs_x__change_t change;
1947 apr_hash_t *changes = apr_hash_make(scratch_pool);
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,
1953 APR_APPEND | APR_WRITE | APR_CREATE,
1954 APR_OS_DEFAULT, scratch_pool));
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;
1967 change.copyfrom_path = apr_pstrdup(scratch_pool, copyfrom_path);
1969 svn_hash_sets(changes, path, &change);
1970 SVN_ERR(svn_fs_x__write_changes(svn_stream_from_aprfile2(file, TRUE,
1972 fs, changes, FALSE, scratch_pool));
1974 return svn_io_file_close(file, scratch_pool);
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
1982 /* The FS we are writing to. */
1985 /* Actual file to which we are writing. */
1986 svn_stream_t *rep_stream;
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;
1992 /* Where is this representation header stored. */
1993 apr_off_t rep_offset;
1995 /* Start of the actual data. */
1996 apr_off_t delta_start;
1998 /* How many bytes have been written to this rep already. */
1999 svn_filesize_t rep_size;
2001 /* The node revision for which we're writing out info. */
2002 svn_fs_x__noderev_t *noderev;
2004 /* Actual output file. */
2006 /* Lock 'cookie' used to unlock the output file once we've finished
2010 svn_checksum_ctx_t *md5_checksum_ctx;
2011 svn_checksum_ctx_t *sha1_checksum_ctx;
2013 /* Receives the low-level checksum when closing REP_STREAM. */
2014 apr_uint32_t fnv1a_checksum;
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;
2020 /* Outer / result pool. */
2021 apr_pool_t *result_pool;
2022 } rep_write_baton_t;
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,
2032 rep_write_baton_t *b = baton;
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;
2038 return svn_stream_write(b->delta_stream, data, len);
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.
2045 static svn_error_t *
2046 shards_spanned(int *spanned,
2048 svn_fs_x__noderev_t *noderev,
2050 apr_pool_t *scratch_pool)
2052 svn_fs_x__data_t *ffd = fs->fsap_data;
2053 int shard_size = ffd->max_files_per_dir;
2054 apr_pool_t *iterpool;
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)
2061 svn_fs_x__id_t id = noderev->predecessor_id;
2063 svn_pool_clear(iterpool);
2064 SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, &id, scratch_pool,
2066 shard = svn_fs_x__get_revnum(id.change_set) / shard_size;
2067 if (shard != last_shard)
2073 svn_pool_destroy(iterpool);
2076 return SVN_NO_ERROR;
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,
2086 svn_fs_x__noderev_t *noderev,
2087 svn_boolean_t props,
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. */
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
2098 svn_fs_x__noderev_t *base;
2099 svn_fs_x__data_t *ffd = fs->fsap_data;
2100 apr_pool_t *iterpool;
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)
2107 return SVN_NO_ERROR;
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);
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)
2125 return SVN_NO_ERROR;
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)
2134 SVN_ERR(shards_spanned(&shards, fs, noderev, walk, pool));
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;
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.) */
2147 iterpool = svn_pool_create(pool);
2148 while ((count++) < noderev->predecessor_count)
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));
2154 svn_pool_destroy(iterpool);
2156 /* return a suitable base representation */
2157 *rep = props ? base->prop_rep : base->data_rep;
2159 /* if we encountered a shared rep, its parent chain may be different
2160 * from the node-rev parent chain. */
2163 int chain_length = 0;
2164 int shard_count = 0;
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
2174 return SVN_NO_ERROR;
2177 /* Check whether the length of the deltification chain is acceptable.
2178 * Otherwise, shared reps may form a non-skipping delta chain in
2180 SVN_ERR(svn_fs_x__rep_chain_length(&chain_length, &shard_count,
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)
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
2193 if ( shard_count > 1
2194 && ((svn_filesize_t)128 << shard_count) >= rep_size)
2198 return SVN_NO_ERROR;
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. */
2206 rep_write_cleanup(void *data)
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);
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,
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
2222 err = svn_error_compose_create(err,
2223 unlock_proto_rev(b->fs, txn_id,
2228 apr_status_t rc = err->apr_err;
2229 svn_error_clear(err);
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.
2240 static svn_error_t *
2241 rep_write_get_baton(rep_write_baton_t **wb_p,
2243 svn_fs_x__noderev_t *noderev,
2244 apr_pool_t *result_pool)
2246 svn_fs_x__data_t *ffd = fs->fsap_data;
2247 rep_write_baton_t *b;
2249 svn_fs_x__representation_t *base_rep;
2250 svn_stream_t *source;
2251 svn_txdelta_window_handler_t wh;
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);
2258 b = apr_pcalloc(result_pool, sizeof(*b));
2260 b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1,
2262 b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5,
2266 b->result_pool = result_pool;
2267 b->local_pool = svn_pool_create(result_pool);
2269 b->noderev = noderev;
2271 /* Open the prototype rev file and seek to its end. */
2272 SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie, fs, txn_id,
2276 b->rep_stream = svn_checksum__wrap_write_stream_fnv1a_32x4(
2278 svn_stream_from_aprfile2(file, TRUE,
2282 SVN_ERR(svn_io_file_get_offset(&b->rep_offset, file, b->local_pool));
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,
2289 /* Write out the rep header. */
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;
2299 header.type = svn_fs_x__rep_self_delta;
2301 SVN_ERR(svn_fs_x__write_rep_header(&header, b->rep_stream,
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));
2307 /* Cleanup in case something goes wrong. */
2308 apr_pool_cleanup_register(b->local_pool, b, rep_write_cleanup,
2309 apr_pool_cleanup_null);
2311 /* Prepare to write the svndiff data. */
2312 svn_txdelta_to_svndiff3(&wh,
2314 svn_stream_disown(b->rep_stream, b->result_pool),
2316 ffd->delta_compression_level,
2319 b->delta_stream = svn_txdelta_target_push(wh, whb, source,
2324 return SVN_NO_ERROR;
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.
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
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.
2341 static svn_error_t *
2342 get_shared_rep(svn_fs_x__representation_t **old_rep,
2344 svn_fs_x__txn_id_t txn_id,
2345 svn_fs_x__representation_t *rep,
2348 apr_hash_t *reps_hash,
2349 apr_pool_t *result_pool,
2350 apr_pool_t *scratch_pool)
2353 svn_fs_x__data_t *ffd = fs->fsap_data;
2355 svn_checksum_t checksum;
2356 checksum.digest = rep->sha1_digest;
2357 checksum.kind = svn_checksum_sha1;
2359 /* Return NULL, if rep sharing has been disabled. */
2361 if (!ffd->rep_sharing_allowed)
2362 return SVN_NO_ERROR;
2364 /* Can't look up if we don't know the key (happens for directories). */
2366 return SVN_NO_ERROR;
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. */
2372 *old_rep = apr_hash_get(reps_hash,
2374 APR_SHA1_DIGESTSIZE);
2376 /* If we haven't found anything yet, try harder and consult our DB. */
2377 if (*old_rep == NULL)
2379 err = svn_fs_x__get_rep_reference(old_rep, fs, &checksum, result_pool,
2382 /* ### Other error codes that we shouldn't mask out? */
2383 if (err == SVN_NO_ERROR)
2386 SVN_ERR(svn_fs_x__check_rep(*old_rep, fs, scratch_pool));
2388 else if (err->apr_err == SVN_ERR_FS_CORRUPT
2389 || SVN_ERROR_IN_CATEGORY(err->apr_err,
2390 SVN_ERR_MALFUNC_CATEGORY_START))
2392 /* Fatal error; don't mask it.
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.
2403 /* Something's wrong with the rep-sharing index. We can continue
2404 without rep-sharing, but warn.
2406 (fs->warning)(fs->warning_baton, err);
2407 svn_error_clear(err);
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)
2415 if (*old_rep == NULL && svn_fs_x__is_txn(rep->id.change_set))
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);
2423 /* in our txn, is there a rep file named with the wanted SHA1?
2424 If so, read it and use that rep.
2426 SVN_ERR(svn_io_check_path(file_name, &kind, scratch_pool));
2427 if (kind == svn_node_file)
2429 svn_stringbuf_t *rep_string;
2430 SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name,
2432 SVN_ERR(svn_fs_x__parse_representation(old_rep, rep_string,
2433 result_pool, scratch_pool));
2438 return SVN_NO_ERROR;
2440 /* A simple guard against general rep-cache induced corruption. */
2441 if ((*old_rep)->expanded_size != rep->expanded_size)
2443 /* Make the problem show up in the server log.
2445 Because not sharing reps is always a safe option,
2446 terminating the request would be inappropriate.
2448 err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2449 "Rep size %s mismatches rep-cache.db value %s "
2451 "You should delete the rep-cache.db and "
2452 "verify the repository. The cached rep will "
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,
2463 (fs->warning)(fs->warning_baton, err);
2464 svn_error_clear(err);
2466 /* Ignore the shared rep. */
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));
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. */
2481 apr_off_t old_position;
2482 svn_stream_t *contents;
2483 svn_stream_t *old_contents;
2486 /* Make sure we can later restore FILE's current position. */
2487 SVN_ERR(svn_io_file_get_offset(&old_position, file, scratch_pool));
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)
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;
2504 id.change_set = svn_fs_x__change_set_by_txn(txn_id);
2505 id.number = (*old_rep)->id.number;
2507 SVN_ERR(svn_fs_x__rev_file_wrap_temp(&rev_file, fs, file,
2509 SVN_ERR(svn_fs_x__item_offset(&offset, &sub_item, fs, rev_file,
2510 &id, scratch_pool));
2512 SVN_ERR(svn_fs_x__get_contents_from_file(&old_contents, fs,
2514 offset, scratch_pool));
2518 SVN_ERR(svn_fs_x__get_contents(&old_contents, fs, *old_rep,
2519 FALSE, scratch_pool));
2521 err = svn_stream_contents_same2(&same, contents, old_contents,
2524 /* A mismatch should be extremely rare.
2525 * If it does happen, reject the commit. */
2528 /* SHA1 collision or worse. */
2529 svn_stringbuf_t *old_rep_str
2530 = svn_fs_x__unparse_representation(*old_rep, FALSE,
2533 svn_stringbuf_t *rep_str
2534 = svn_fs_x__unparse_representation(rep, FALSE,
2537 const char *checksum__str
2538 = svn_checksum_to_cstring_display(&checksum, scratch_pool);
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,
2547 /* Restore FILE's read / write position. */
2548 SVN_ERR(svn_io_file_seek(file, APR_SET, &old_position, scratch_pool));
2551 return SVN_NO_ERROR;
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.
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)
2564 svn_checksum_t *checksum;
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;
2571 SVN_ERR(svn_checksum_final(&checksum, sha1_ctx, scratch_pool));
2572 memcpy(rep->sha1_digest, checksum->digest, svn_checksum_size(checksum));
2575 return SVN_NO_ERROR;
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)
2584 rep_write_baton_t *b = baton;
2585 svn_fs_x__representation_t *rep;
2586 svn_fs_x__representation_t *old_rep;
2590 rep = apr_pcalloc(b->result_pool, sizeof(*rep));
2592 /* Close our delta stream so the last bits of svndiff are written
2594 SVN_ERR(svn_stream_close(b->delta_stream));
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;
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);
2605 /* Finalize the checksum. */
2606 SVN_ERR(digests_final(rep, b->md5_checksum_ctx, b->sha1_checksum_ctx,
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));
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));
2619 /* Use the old rep for this content. */
2620 b->noderev->data_rep = old_rep;
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,
2628 SVN_ERR(store_l2p_index_entry(b->fs, txn_id, b->rep_offset,
2629 rep->id.number, b->local_pool));
2631 b->noderev->data_rep = rep;
2634 SVN_ERR(svn_stream_close(b->rep_stream));
2636 /* Remove cleanup callback. */
2637 apr_pool_cleanup_kill(b->local_pool, b, rep_write_cleanup);
2639 /* Write out the new node-rev information. */
2640 SVN_ERR(svn_fs_x__put_node_revision(b->fs, b->noderev, b->local_pool));
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;
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;
2656 SVN_ERR(store_p2l_index_entry(b->fs, txn_id, &entry, b->local_pool));
2659 SVN_ERR(svn_io_file_close(b->file, b->local_pool));
2661 /* Write the sha1->rep mapping *after* we successfully written node
2662 * revision to disk. */
2664 SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->local_pool));
2666 SVN_ERR(unlock_proto_rev(b->fs, txn_id, b->lockcookie, b->local_pool));
2667 svn_pool_destroy(b->local_pool);
2669 return SVN_NO_ERROR;
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,
2679 svn_fs_x__noderev_t *noderev,
2680 apr_pool_t *result_pool)
2682 rep_write_baton_t *wb;
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);
2690 SVN_ERR(rep_write_get_baton(&wb, fs, noderev, result_pool));
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);
2696 return SVN_NO_ERROR;
2700 svn_fs_x__set_contents(svn_stream_t **stream,
2702 svn_fs_x__noderev_t *noderev,
2703 apr_pool_t *result_pool)
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"));
2709 return set_representation(stream, fs, noderev, result_pool);
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)
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,
2724 if (! new_noderev->copyroot_path)
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);
2732 SVN_ERR(svn_fs_x__put_node_revision(fs, new_noderev, scratch_pool));
2734 return SVN_NO_ERROR;
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)
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,
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));
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))
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));
2771 return SVN_NO_ERROR;
2774 /* This baton is used by the stream created for write_container_rep. */
2775 typedef struct write_container_baton_t
2777 svn_stream_t *stream;
2781 svn_checksum_ctx_t *md5_ctx;
2783 /* SHA1 calculation is optional. If not needed, this will be NULL. */
2784 svn_checksum_ctx_t *sha1_ctx;
2785 } write_container_baton_t;
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,
2795 write_container_baton_t *whb = baton;
2797 SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
2799 SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
2801 SVN_ERR(svn_stream_write(whb->stream, data, len));
2804 return SVN_NO_ERROR;
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,
2811 apr_pool_t *scratch_pool);
2813 /* Implement collection_writer_t writing the C string->svn_string_t hash
2815 static svn_error_t *
2816 write_hash_to_stream(svn_stream_t *stream,
2818 apr_pool_t *scratch_pool)
2820 apr_hash_t *hash = baton;
2821 SVN_ERR(svn_fs_x__write_properties(stream, hash, scratch_pool));
2823 return SVN_NO_ERROR;
2826 /* Implement collection_writer_t writing the svn_fs_x__dirent_t* array given
2828 static svn_error_t *
2829 write_directory_to_stream(svn_stream_t *stream,
2831 apr_pool_t *scratch_pool)
2833 apr_array_header_t *dir = baton;
2834 SVN_ERR(unparse_dir_entries(dir, stream, scratch_pool));
2836 return SVN_NO_ERROR;
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.
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.
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.
2858 static svn_error_t *
2859 write_container_delta_rep(svn_fs_x__representation_t *rep,
2862 collection_writer_t writer,
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)
2872 svn_fs_x__data_t *ffd = fs->fsap_data;
2873 svn_txdelta_window_handler_t diff_wh;
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 };
2884 apr_off_t rep_end = 0;
2885 apr_off_t delta_start = 0;
2886 apr_off_t offset = 0;
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);
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));
2897 SVN_ERR(svn_io_file_get_offset(&offset, file, scratch_pool));
2899 /* Write out the rep header. */
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;
2909 header.type = svn_fs_x__rep_self_delta;
2912 file_stream = svn_checksum__wrap_write_stream_fnv1a_32x4(
2913 &entry.fnv1_checksum,
2914 svn_stream_from_aprfile2(file, TRUE,
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));
2920 /* Prepare to write the svndiff data. */
2921 svn_txdelta_to_svndiff3(&diff_wh,
2923 svn_stream_disown(file_stream, scratch_pool),
2925 ffd->delta_compression_level,
2928 whb = apr_pcalloc(scratch_pool, sizeof(*whb));
2929 whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source,
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);
2936 /* serialize the hash */
2937 stream = svn_stream_create(whb, scratch_pool);
2938 svn_stream_set_write(stream, write_container_handler);
2940 SVN_ERR(writer(stream, collection, scratch_pool));
2941 SVN_ERR(svn_stream_close(whb->stream));
2943 /* Store the results. */
2944 SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool));
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;
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));
2959 SVN_ERR(svn_stream_close(file_stream));
2961 /* We need to erase from the protorev the data we just wrote. */
2962 SVN_ERR(svn_io_file_trunc(file, offset, scratch_pool));
2964 /* Use the old rep for this content. */
2965 memcpy(rep, old_rep, sizeof (*rep));
2969 svn_fs_x__id_t noderev_id;
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));
2975 SVN_ERR(allocate_item_index(&rep->id.number, fs, txn_id,
2977 SVN_ERR(store_l2p_index_entry(fs, txn_id, offset, rep->id.number,
2980 noderev_id.change_set = SVN_FS_X__INVALID_CHANGE_SET;
2981 noderev_id.number = rep->id.number;
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;
2990 SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, scratch_pool));
2992 /* update the representation */
2993 rep->size = rep_end - delta_start;
2996 return SVN_NO_ERROR;
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
3003 If you change this function, consider updating svn_fs_x__verify() too.
3005 static svn_error_t *
3006 validate_root_noderev(svn_fs_t *fs,
3007 svn_fs_x__noderev_t *root_noderev,
3009 apr_pool_t *scratch_pool)
3011 svn_revnum_t head_revnum = rev-1;
3012 int head_predecessor_count;
3014 SVN_ERR_ASSERT(rev > 0);
3016 /* Compute HEAD_PREDECESSOR_COUNT. */
3018 svn_fs_x__id_t head_root_id;
3019 svn_fs_x__noderev_t *head_root_noderev;
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,
3027 head_predecessor_count = head_root_noderev->predecessor_count;
3030 /* Check that the root noderev's predecessor count equals REV.
3032 This kind of corruption was seen on svn.apache.org (both on
3033 the root noderev and on other fspaths' noderevs); see
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
3042 if ((root_noderev->predecessor_count - head_predecessor_count)
3043 != (rev - head_revnum))
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,
3055 return SVN_NO_ERROR;
3058 /* Given the potentially txn-local id PART, update that to a permanent ID
3059 * based on the REVISION.
3062 get_final_id(svn_fs_x__id_t *part,
3063 svn_revnum_t revision)
3065 if (!svn_fs_x__is_revision(part->change_set))
3066 part->change_set = svn_fs_x__change_set_by_rev(revision);
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
3074 START_NODE_ID and START_COPY_ID are
3075 the first available node and copy ids for this filesystem, for older
3078 REV is the revision number that this proto-rev-file will represent.
3080 INITIAL_OFFSET is the offset of the proto-rev-file on entry to
3083 Collect the pair_cache_key_t of all directories written to the
3084 committed cache in DIRECTORY_IDS.
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.
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
3093 AT_ROOT is true if the node revision being written is the root
3094 node-revision. It is only controls additional sanity checking
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.
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,
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)
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;
3128 /* Check to see if this is a transaction node. */
3129 if (txn_id == SVN_FS_X__INVALID_TXN_ID)
3131 svn_fs_x__id_reset(new_id_p);
3132 return SVN_NO_ERROR;
3135 subpool = svn_pool_create(scratch_pool);
3136 SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, id, scratch_pool,
3139 if (noderev->kind == svn_node_dir)
3141 apr_array_header_t *entries;
3144 /* This is a directory. Write out all the children first. */
3146 SVN_ERR(svn_fs_x__rep_contents_dir(&entries, fs, noderev, scratch_pool,
3148 for (i = 0; i < entries->nelts; ++i)
3150 svn_fs_x__dirent_t *dirent = APR_ARRAY_IDX(entries, i,
3151 svn_fs_x__dirent_t *);
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;
3162 if (noderev->data_rep
3163 && ! svn_fs_x__is_revision(noderev->data_rep->id.change_set))
3165 svn_fs_x__pair_cache_key_t *key;
3166 svn_fs_x__dir_data_t dir_data;
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,
3172 write_directory_to_stream,
3173 fs, txn_id, noderev, NULL, FALSE,
3174 SVN_FS_X__ITEM_TYPE_DIR_REP,
3175 rev, scratch_pool));
3177 /* Cache the new directory contents. Otherwise, subsequent reads
3178 * or commits will likely have to reconstruct, verify and parse
3180 key = apr_array_push(directory_ids);
3181 key->revision = noderev->data_rep->id.change_set;
3182 key->second = noderev->data_rep->id.number;
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.
3189 dir_data.entries = entries;
3190 dir_data.txn_filesize = 0;
3192 SVN_ERR(svn_cache__set(ffd->dir_cache, key, &dir_data, subpool));
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
3201 if (noderev->data_rep
3202 && svn_fs_x__is_txn(noderev->data_rep->id.change_set))
3204 noderev->data_rep->id.change_set = change_set;
3208 svn_pool_destroy(subpool);
3210 /* Fix up the property reps. */
3211 if (noderev->prop_rep
3212 && svn_fs_x__is_txn(noderev->prop_rep->id.change_set))
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,
3221 noderev->prop_rep->id.change_set = change_set;
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));
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);
3234 if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
3235 noderev->copyroot_rev = rev;
3237 SVN_ERR(svn_io_file_get_offset(&my_offset, file, scratch_pool));
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;
3243 if (ffd->rep_sharing_allowed)
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)
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);
3254 if ( noderev->prop_rep
3255 && svn_fs_x__get_revnum(noderev->prop_rep->id.change_set) == rev)
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);
3261 SVN_ERR_ASSERT(reps_to_cache && reps_pool);
3262 APR_ARRAY_PUSH(reps_to_cache, svn_fs_x__representation_t *) = copy;
3264 apr_hash_set(reps_hash,
3266 APR_SHA1_DIGESTSIZE,
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;
3275 /* don't serialize SHA1 for props to disk (waste of space) */
3276 if (noderev->prop_rep)
3277 noderev->prop_rep->has_sha1 = FALSE;
3279 /* Write out our new node-revision. */
3281 SVN_ERR(validate_root_noderev(fs, noderev, rev, scratch_pool));
3283 file_stream = svn_checksum__wrap_write_stream_fnv1a_32x4(
3284 &entry.fnv1_checksum,
3285 svn_stream_from_aprfile2(file, TRUE,
3288 SVN_ERR(svn_fs_x__write_noderev(file_stream, noderev, scratch_pool));
3289 SVN_ERR(svn_stream_close(file_stream));
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;
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;
3302 SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, scratch_pool));
3304 /* Return our ID that references the revision file. */
3307 return SVN_NO_ERROR;
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,
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)
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};
3330 SVN_ERR(svn_io_file_get_offset(&offset, file, scratch_pool));
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),
3336 SVN_ERR(svn_fs_x__write_changes(stream, fs, changed_paths, TRUE,
3338 SVN_ERR(svn_stream_close(stream));
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;
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));
3354 return SVN_NO_ERROR;
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.
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)
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;
3375 SVN_ERR_ASSERT(ffd->svn_fs_open_);
3377 /* make sure FT does not simply return data cached by other instances
3378 * but actually retrieves it from disk at least once.
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,
3387 ft_ffd = ft->fsap_data;
3388 /* Don't let FT consult rep-cache.db, either. */
3389 ft_ffd->rep_sharing_allowed = FALSE;
3392 ft_ffd->youngest_rev_cache = new_rev;
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 */
3400 return SVN_NO_ERROR;
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)
3412 apr_pool_t *iterpool;
3413 apr_array_header_t *changed_paths_sorted;
3414 svn_stringbuf_t *last_recursed = NULL;
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,
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++)
3428 const svn_sort__item_t *item;
3430 svn_fs_x__change_t *change;
3431 svn_boolean_t recurse = TRUE;
3433 svn_pool_clear(iterpool);
3435 item = &APR_ARRAY_IDX(changed_paths_sorted, i, svn_sort__item_t);
3437 /* Fetch the change associated with our path. */
3439 change = item->value;
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. */
3444 && svn_fspath__skip_ancestor(last_recursed->data, path))
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.
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)
3458 SVN_ERR(svn_fs_x__allow_locked_operation(path, fs, recurse, TRUE,
3461 /* If we just did a recursive check, remember the path we
3462 checked (so children can be skipped). */
3465 if (! last_recursed)
3466 last_recursed = svn_stringbuf_create(path, scratch_pool);
3468 svn_stringbuf_set(last_recursed, path);
3471 svn_pool_destroy(iterpool);
3472 return SVN_NO_ERROR;
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,
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)
3490 svn_string_t *client_date;
3493 SVN_ERR(svn_fs_x__txn_proplist(&props, txn, scratch_pool));
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);
3499 if (svn_hash_gets(props, SVN_FS__PROP_TXN_CHECK_LOCKS))
3500 svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL);
3502 client_date = svn_hash_gets(props, SVN_FS__PROP_TXN_CLIENT_DATE);
3504 svn_hash_sets(props, SVN_FS__PROP_TXN_CLIENT_DATE, NULL);
3506 /* Update commit time to ensure that svn:date revprops remain ordered if
3508 if (!client_date || strcmp(client_date->data, "1"))
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);
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));
3519 /* Write the new contents to the final revprops file. */
3520 SVN_ERR(svn_fs_x__write_non_packed_revprops(file, props, scratch_pool));
3522 return SVN_NO_ERROR;
3526 svn_fs_x__add_index_data(svn_fs_t *fs,
3528 const char *l2p_proto_index,
3529 const char *p2l_proto_index,
3530 svn_revnum_t revision,
3531 apr_pool_t *scratch_pool)
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;
3540 /* Append the actual index data to the pack file. */
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));
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));
3553 /* Append footer. */
3554 footer = svn_fs_x__unparse_footer(l2p_offset, l2p_checksum,
3555 p2l_offset, p2l_checksum, scratch_pool,
3557 SVN_ERR(svn_io_file_write_full(file, footer->data, footer->len, NULL,
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,
3565 return SVN_NO_ERROR;
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)
3577 svn_fs_x__data_t *ffd = fs->fsap_data;
3578 if (revision % ffd->max_files_per_dir == 0)
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,
3584 if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
3585 return svn_error_trace(err);
3586 svn_error_clear(err);
3588 SVN_ERR(svn_io_copy_perms(svn_dirent_join(fs->path, PATH_REVS_DIR,
3590 new_dir, scratch_pool));
3591 SVN_ERR(svn_fs_x__batch_fsync_new_path(batch, new_dir, scratch_pool));
3594 return SVN_NO_ERROR;
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.
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.
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.
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,
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)
3621 get_writable_proto_rev_baton_t baton;
3622 apr_off_t end_offset = 0;
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);
3630 /* Acquire exclusive access to the proto-rev file. */
3631 baton.lockcookie = &lockcookie;
3632 baton.txn_id = txn_id;
3634 SVN_ERR(with_txnlist_lock(fs, get_writable_proto_rev_body, &baton,
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,
3643 unlock_proto_rev(fs, txn_id, lockcookie,
3645 SVN_ERR(svn_fs_x__batch_fsync_new_path(batch, final_rev_filename,
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,
3652 SVN_ERR(svn_io_file_seek(*file, APR_END, &end_offset, scratch_pool));
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
3657 SVN_ERR(auto_truncate_proto_rev(fs, *file, end_offset, txn_id,
3660 return SVN_NO_ERROR;
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)
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);
3676 /* Create / open the 'next' file. */
3677 SVN_ERR(svn_fs_x__batch_fsync_open_file(&file, batch, path, scratch_pool));
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));
3683 /* Adjust permissions. */
3684 SVN_ERR(svn_io_copy_perms(perms_path, path, scratch_pool));
3686 return SVN_NO_ERROR;
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)
3697 const char *current_filename;
3699 /* Write the 'next' file. */
3700 SVN_ERR(write_next_file(fs, new_rev, batch, scratch_pool));
3702 /* Commit all changes to disk. */
3703 SVN_ERR(svn_fs_x__batch_fsync_run(batch, scratch_pool));
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));
3711 /* Make the new revision permanently visible. */
3712 SVN_ERR(svn_fs_x__batch_fsync_run(batch, scratch_pool));
3714 return SVN_NO_ERROR;
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)
3724 svn_fs_x__data_t *ffd = fs->fsap_data;
3725 apr_pool_t *iterpool;
3728 if (!ffd->dir_cache)
3729 return SVN_NO_ERROR;
3731 iterpool = svn_pool_create(scratch_pool);
3732 for (i = 0; i < directory_ids->nelts; ++i)
3734 const svn_fs_x__pair_cache_key_t *key
3735 = &APR_ARRAY_IDX(directory_ids, i, svn_fs_x__pair_cache_key_t);
3737 svn_pool_clear(iterpool);
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-
3742 SVN_ERR(svn_cache__set_partial(ffd->dir_cache, key,
3743 svn_fs_x__reset_txn_filesize, NULL,
3747 svn_pool_destroy(iterpool);
3749 return SVN_NO_ERROR;
3752 /* Baton used for commit_body below. */
3753 typedef struct commit_baton_t {
3754 svn_revnum_t *new_rev_p;
3757 apr_array_header_t *reps_to_cache;
3758 apr_hash_t *reps_hash;
3759 apr_pool_t *reps_pool;
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)
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));
3783 /* We perform a sequence of (potentially) large allocations.
3784 Keep the peak memory usage low by using a SUBPOOL and cleaning it
3786 apr_pool_t *subpool = svn_pool_create(scratch_pool);
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.
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
3796 SVN_ERR(svn_fs_x__read_format_file(cb->fs, subpool));
3798 /* Get the current youngest revision. */
3799 SVN_ERR(svn_fs_x__youngest_rev(&old_rev, cb->fs, subpool));
3800 svn_pool_clear(subpool);
3802 /* Check to make sure this transaction is based off the most recent
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"));
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,
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);
3820 /* We are going to be one better than this puny old revision. */
3821 new_rev = old_rev + 1;
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,
3828 /* Set up the target directory. */
3829 SVN_ERR(auto_create_shard(cb->fs, new_rev, batch, subpool));
3831 /* Get a write handle on the proto revision file.
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,
3838 SVN_ERR(svn_io_file_get_offset(&initial_offset, proto_file, subpool));
3839 svn_pool_clear(subpool);
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,
3847 svn_pool_clear(subpool);
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,
3853 svn_pool_clear(subpool);
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),
3860 svn_pool_clear(subpool);
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));
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,
3871 SVN_ERR(svn_io_copy_perms(revprop_filename, old_rev_filename, subpool));
3872 svn_pool_clear(subpool);
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,
3879 /* Bump 'current'. */
3880 SVN_ERR(bump_current(cb->fs, new_rev, batch, subpool));
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
3887 *cb->new_rev_p = new_rev;
3889 ffd->youngest_rev_cache = new_rev;
3891 /* Make the directory contents already cached for the new revision
3893 SVN_ERR(promote_cached_directories(cb->fs, directory_ids, subpool));
3895 /* Remove this transaction directory. */
3896 SVN_ERR(svn_fs_x__purge_txn(cb->fs, cb->txn->id, subpool));
3898 svn_pool_destroy(subpool);
3899 return SVN_NO_ERROR;
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)
3911 for (i = 0; i < reps_to_cache->nelts; i++)
3913 svn_fs_x__representation_t *rep
3914 = APR_ARRAY_IDX(reps_to_cache, i, svn_fs_x__representation_t *);
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));
3921 return SVN_NO_ERROR;
3925 svn_fs_x__commit(svn_revnum_t *new_rev_p,
3928 apr_pool_t *scratch_pool)
3931 svn_fs_x__data_t *ffd = fs->fsap_data;
3933 cb.new_rev_p = new_rev_p;
3937 if (ffd->rep_sharing_allowed)
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;
3946 cb.reps_to_cache = NULL;
3947 cb.reps_hash = NULL;
3948 cb.reps_pool = NULL;
3951 SVN_ERR(svn_fs_x__with_write_lock(fs, commit_body, &cb, scratch_pool));
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().) */
3956 if (ffd->rep_sharing_allowed)
3960 SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool));
3962 /* Write new entries to the rep-sharing database.
3964 * We use an sqlite transaction to speed things up;
3965 * see <http://www.sqlite.org/faq.html#q19>.
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);
3974 if (svn_error_find_cause(err, SVN_ERR_SQLITE_ROLLBACK_FAILED))
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)));
3984 return svn_error_trace(err);
3987 return SVN_NO_ERROR;
3992 svn_fs_x__list_transactions(apr_array_header_t **names_p,
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);
4002 names = apr_array_make(pool, 1, sizeof(const char *));
4004 /* Get the transactions directory. */
4005 txn_dir = svn_fs_x__path_txns_dir(fs, pool);
4007 /* Now find a listing of this directory. */
4008 SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool));
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))
4013 const char *name = apr_hash_this_key(hi);
4014 apr_ssize_t klen = apr_hash_this_key_len(hi);
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)
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;
4029 return SVN_NO_ERROR;
4033 svn_fs_x__open_txn(svn_fs_txn_t **txn_p,
4040 svn_node_kind_t kind;
4041 svn_fs_x__transaction_t *local_txn;
4042 svn_fs_x__txn_id_t txn_id;
4044 SVN_ERR(svn_fs_x__txn_by_name(&txn_id, name));
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),
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'"),
4056 txn = apr_pcalloc(pool, sizeof(*txn));
4057 ftd = apr_pcalloc(pool, sizeof(*ftd));
4058 ftd->txn_id = txn_id;
4060 /* Read in the root node of this transaction. */
4061 txn->id = apr_pstrdup(pool, name);
4064 SVN_ERR(svn_fs_x__get_txn(&local_txn, fs, txn_id, pool));
4066 txn->base_rev = local_txn->base_rev;
4068 txn->vtable = &txn_vtable;
4069 txn->fsap_data = ftd;
4072 return SVN_NO_ERROR;
4076 svn_fs_x__txn_proplist(apr_hash_t **table_p,
4080 SVN_ERR(get_txn_proplist(table_p, txn->fs, svn_fs_x__txn_get_id(txn),
4083 return SVN_NO_ERROR;
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)
4091 svn_fs_x__noderev_t *noderev;
4092 SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, id, scratch_pool,
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,
4101 FALSE, scratch_pool));
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)
4108 svn_fs_x__data_t *ffd = fs->fsap_data;
4109 const svn_fs_x__id_t *key = id;
4111 SVN_ERR(svn_io_remove_file2(
4112 svn_fs_x__path_txn_node_children(fs, id, scratch_pool,
4114 FALSE, scratch_pool));
4116 /* remove the corresponding entry from the cache, if such exists */
4117 SVN_ERR(svn_cache__set(ffd->dir_cache, key, NULL, scratch_pool));
4120 return svn_io_remove_file2(svn_fs_x__path_txn_node_rev(fs, id,
4123 FALSE, scratch_pool);
4128 /*** Transactions ***/
4131 svn_fs_x__get_base_rev(svn_revnum_t *revnum,
4133 svn_fs_x__txn_id_t txn_id,
4134 apr_pool_t *scratch_pool)
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;
4140 return SVN_NO_ERROR;
4144 /* Generic transaction operations. */
4147 svn_fs_x__txn_prop(svn_string_t **value_p,
4149 const char *propname,
4153 svn_fs_t *fs = txn->fs;
4155 SVN_ERR(svn_fs__check_fs(fs, TRUE));
4156 SVN_ERR(svn_fs_x__txn_proplist(&table, txn, pool));
4158 *value_p = svn_hash_gets(table, propname);
4160 return SVN_NO_ERROR;
4164 svn_fs_x__begin_txn(svn_fs_txn_t **txn_p,
4168 apr_pool_t *result_pool,
4169 apr_pool_t *scratch_pool)
4173 apr_hash_t *props = apr_hash_make(scratch_pool);
4175 SVN_ERR(svn_fs__check_fs(fs, TRUE));
4177 SVN_ERR(create_txn(txn_p, fs, rev, result_pool, scratch_pool));
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);
4187 svn_hash_sets(props, SVN_PROP_REVISION_DATE, &date);
4189 /* Set temporary txn props that represent the requested 'flags'
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));
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));
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));
4203 ftd = (*txn_p)->fsap_data;
4204 SVN_ERR(set_txn_proplist(fs, ftd->txn_id, props, scratch_pool));
4206 return SVN_NO_ERROR;