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"
29 #include "svn_props.h"
30 #include "svn_sorts.h"
32 #include "svn_dirent_uri.h"
38 #include "low_level.h"
39 #include "temp_serializer.h"
40 #include "cached_data.h"
42 #include "rep-cache.h"
45 #include "private/svn_fs_util.h"
46 #include "private/svn_fspath.h"
47 #include "private/svn_sorts_private.h"
48 #include "private/svn_string_private.h"
49 #include "private/svn_subr_private.h"
50 #include "private/svn_io_private.h"
51 #include "../libsvn_fs/fs-loader.h"
53 #include "svn_private_config.h"
55 /* The vtable associated with an open transaction object. */
56 static txn_vtable_t txn_vtable = {
60 svn_fs_x__txn_proplist,
61 svn_fs_x__change_txn_prop,
63 svn_fs_x__change_txn_props
66 /* FSX-specific data being attached to svn_fs_txn_t.
68 typedef struct fs_txn_data_t
70 /* Strongly typed representation of the TXN's ID member. */
71 svn_fs_x__txn_id_t txn_id;
75 svn_fs_x__txn_get_id(svn_fs_txn_t *txn)
77 fs_txn_data_t *ftd = txn->fsap_data;
81 /* Functions for working with shared transaction data. */
83 /* Return the transaction object for transaction TXN_ID from the
84 transaction list of filesystem FS (which must already be locked via the
85 txn_list_lock mutex). If the transaction does not exist in the list,
86 then create a new transaction object and return it (if CREATE_NEW is
87 true) or return NULL (otherwise). */
88 static svn_fs_x__shared_txn_data_t *
89 get_shared_txn(svn_fs_t *fs,
90 svn_fs_x__txn_id_t txn_id,
91 svn_boolean_t create_new)
93 svn_fs_x__data_t *ffd = fs->fsap_data;
94 svn_fs_x__shared_data_t *ffsd = ffd->shared;
95 svn_fs_x__shared_txn_data_t *txn;
97 for (txn = ffsd->txns; txn; txn = txn->next)
98 if (txn->txn_id == txn_id)
101 if (txn || !create_new)
104 /* Use the transaction object from the (single-object) freelist,
105 if one is available, or otherwise create a new object. */
108 txn = ffsd->free_txn;
109 ffsd->free_txn = NULL;
113 apr_pool_t *subpool = svn_pool_create(ffsd->common_pool);
114 txn = apr_palloc(subpool, sizeof(*txn));
118 txn->txn_id = txn_id;
119 txn->being_written = FALSE;
121 /* Link this transaction into the head of the list. We will typically
122 be dealing with only one active transaction at a time, so it makes
123 sense for searches through the transaction list to look at the
124 newest transactions first. */
125 txn->next = ffsd->txns;
131 /* Free the transaction object for transaction TXN_ID, and remove it
132 from the transaction list of filesystem FS (which must already be
133 locked via the txn_list_lock mutex). Do nothing if the transaction
136 free_shared_txn(svn_fs_t *fs, svn_fs_x__txn_id_t txn_id)
138 svn_fs_x__data_t *ffd = fs->fsap_data;
139 svn_fs_x__shared_data_t *ffsd = ffd->shared;
140 svn_fs_x__shared_txn_data_t *txn, *prev = NULL;
142 for (txn = ffsd->txns; txn; prev = txn, txn = txn->next)
143 if (txn->txn_id == txn_id)
150 prev->next = txn->next;
152 ffsd->txns = txn->next;
154 /* As we typically will be dealing with one transaction after another,
155 we will maintain a single-object free list so that we can hopefully
156 keep reusing the same transaction object. */
158 ffsd->free_txn = txn;
160 svn_pool_destroy(txn->pool);
164 /* Obtain a lock on the transaction list of filesystem FS, call BODY
165 with FS, BATON, and POOL, and then unlock the transaction list.
166 Return what BODY returned. */
168 with_txnlist_lock(svn_fs_t *fs,
169 svn_error_t *(*body)(svn_fs_t *fs,
175 svn_fs_x__data_t *ffd = fs->fsap_data;
176 svn_fs_x__shared_data_t *ffsd = ffd->shared;
178 SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock,
179 body(fs, baton, pool));
185 /* Get a lock on empty file LOCK_FILENAME, creating it in RESULT_POOL. */
187 get_lock_on_filesystem(const char *lock_filename,
188 apr_pool_t *result_pool)
190 return svn_error_trace(svn_io__file_lock_autocreate(lock_filename,
194 /* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID.
195 When registered with the pool holding the lock on the lock file,
196 this makes sure the flag gets reset just before we release the lock. */
198 reset_lock_flag(void *baton_void)
200 svn_fs_x__data_t *ffd = baton_void;
201 ffd->has_write_lock = FALSE;
205 /* Structure defining a file system lock to be acquired and the function
206 to be executed while the lock is held.
208 Instances of this structure may be nested to allow for multiple locks to
209 be taken out before executing the user-provided body. In that case, BODY
210 and BATON of the outer instances will be with_lock and a with_lock_baton_t
211 instance (transparently, no special treatment is required.). It is
212 illegal to attempt to acquire the same lock twice within the same lock
213 chain or via nesting calls using separate lock chains.
215 All instances along the chain share the same LOCK_POOL such that only one
216 pool needs to be created and cleared for all locks. We also allocate as
217 much data from that lock pool as possible to minimize memory usage in
219 typedef struct with_lock_baton_t
221 /* The filesystem we operate on. Same for all instances along the chain. */
224 /* Mutex to complement the lock file in an APR threaded process.
225 No-op object for non-threaded processes but never NULL. */
228 /* Path to the file to lock. */
229 const char *lock_path;
231 /* If true, set FS->HAS_WRITE_LOCK after we acquired the lock. */
232 svn_boolean_t is_global_lock;
234 /* Function body to execute after we acquired the lock.
235 This may be user-provided or a nested call to with_lock(). */
236 svn_error_t *(*body)(void *baton,
237 apr_pool_t *scratch_pool);
239 /* Baton to pass to BODY; possibly NULL.
240 This may be user-provided or a nested lock baton instance. */
243 /* Pool for all allocations along the lock chain and BODY. Will hold the
244 file locks and gets destroyed after the outermost BODY returned,
245 releasing all file locks.
246 Same for all instances along the chain. */
247 apr_pool_t *lock_pool;
249 /* TRUE, iff BODY is the user-provided body. */
250 svn_boolean_t is_inner_most_lock;
252 /* TRUE, iff this is not a nested lock.
253 Then responsible for destroying LOCK_POOL. */
254 svn_boolean_t is_outer_most_lock;
257 /* Obtain a write lock on the file BATON->LOCK_PATH and call BATON->BODY
258 with BATON->BATON. If this is the outermost lock call, release all file
259 locks after the body returned. If BATON->IS_GLOBAL_LOCK is set, set the
260 HAS_WRITE_LOCK flag while we keep the write lock. */
262 with_some_lock_file(with_lock_baton_t *baton)
264 apr_pool_t *pool = baton->lock_pool;
265 svn_error_t *err = get_lock_on_filesystem(baton->lock_path, pool);
269 svn_fs_t *fs = baton->fs;
270 svn_fs_x__data_t *ffd = fs->fsap_data;
272 if (baton->is_global_lock)
274 /* set the "got the lock" flag and register reset function */
275 apr_pool_cleanup_register(pool,
278 apr_pool_cleanup_null);
279 ffd->has_write_lock = TRUE;
282 /* nobody else will modify the repo state
283 => read HEAD & pack info once */
284 if (baton->is_inner_most_lock)
286 err = svn_fs_x__update_min_unpacked_rev(fs, pool);
288 err = svn_fs_x__youngest_rev(&ffd->youngest_rev_cache, fs, pool);
292 err = baton->body(baton->baton, pool);
295 if (baton->is_outer_most_lock)
296 svn_pool_destroy(pool);
298 return svn_error_trace(err);
301 /* Wraps with_some_lock_file, protecting it with BATON->MUTEX.
303 SCRATCH_POOL is unused here and only provided for signature compatibility
304 with WITH_LOCK_BATON_T.BODY. */
306 with_lock(void *baton,
307 apr_pool_t *scratch_pool)
309 with_lock_baton_t *lock_baton = baton;
310 SVN_MUTEX__WITH_LOCK(lock_baton->mutex, with_some_lock_file(lock_baton));
315 /* Enum identifying a filesystem lock. */
316 typedef enum lock_id_t
323 /* Initialize BATON->MUTEX, BATON->LOCK_PATH and BATON->IS_GLOBAL_LOCK
324 according to the LOCK_ID. All other members of BATON must already be
327 init_lock_baton(with_lock_baton_t *baton,
330 svn_fs_x__data_t *ffd = baton->fs->fsap_data;
331 svn_fs_x__shared_data_t *ffsd = ffd->shared;
336 baton->mutex = ffsd->fs_write_lock;
337 baton->lock_path = svn_fs_x__path_lock(baton->fs, baton->lock_pool);
338 baton->is_global_lock = TRUE;
342 baton->mutex = ffsd->txn_current_lock;
343 baton->lock_path = svn_fs_x__path_txn_current_lock(baton->fs,
345 baton->is_global_lock = FALSE;
349 baton->mutex = ffsd->fs_pack_lock;
350 baton->lock_path = svn_fs_x__path_pack_lock(baton->fs,
352 baton->is_global_lock = FALSE;
357 /* Return the baton for the innermost lock of a (potential) lock chain.
358 The baton shall take out LOCK_ID from FS and execute BODY with BATON
359 while the lock is being held. Allocate the result in a sub-pool of
362 static with_lock_baton_t *
363 create_lock_baton(svn_fs_t *fs,
365 svn_error_t *(*body)(void *baton,
366 apr_pool_t *scratch_pool),
368 apr_pool_t *result_pool)
370 /* Allocate everything along the lock chain into a single sub-pool.
371 This minimizes memory usage and cleanup overhead. */
372 apr_pool_t *lock_pool = svn_pool_create(result_pool);
373 with_lock_baton_t *result = apr_pcalloc(lock_pool, sizeof(*result));
375 /* Store parameters. */
378 result->baton = baton;
380 /* File locks etc. will use this pool as well for easy cleanup. */
381 result->lock_pool = lock_pool;
383 /* Right now, we are the first, (only, ) and last struct in the chain. */
384 result->is_inner_most_lock = TRUE;
385 result->is_outer_most_lock = TRUE;
387 /* Select mutex and lock file path depending on LOCK_ID.
388 Also, initialize dependent members (IS_GLOBAL_LOCK only, ATM). */
389 init_lock_baton(result, lock_id);
394 /* Return a baton that wraps NESTED and requests LOCK_ID as additional lock.
396 * That means, when you create a lock chain, start with the last / innermost
397 * lock to take out and add the first / outermost lock last.
399 static with_lock_baton_t *
400 chain_lock_baton(lock_id_t lock_id,
401 with_lock_baton_t *nested)
403 /* Use the same pool for batons along the lock chain. */
404 apr_pool_t *lock_pool = nested->lock_pool;
405 with_lock_baton_t *result = apr_pcalloc(lock_pool, sizeof(*result));
407 /* All locks along the chain operate on the same FS. */
408 result->fs = nested->fs;
410 /* Execution of this baton means acquiring the nested lock and its
412 result->body = with_lock;
413 result->baton = nested;
415 /* Shared among all locks along the chain. */
416 result->lock_pool = lock_pool;
418 /* We are the new outermost lock but surely not the innermost lock. */
419 result->is_inner_most_lock = FALSE;
420 result->is_outer_most_lock = TRUE;
421 nested->is_outer_most_lock = FALSE;
423 /* Select mutex and lock file path depending on LOCK_ID.
424 Also, initialize dependent members (IS_GLOBAL_LOCK only, ATM). */
425 init_lock_baton(result, lock_id);
431 svn_fs_x__with_write_lock(svn_fs_t *fs,
432 svn_error_t *(*body)(void *baton,
433 apr_pool_t *scratch_pool),
435 apr_pool_t *scratch_pool)
437 return svn_error_trace(
438 with_lock(create_lock_baton(fs, write_lock, body, baton,
444 svn_fs_x__with_pack_lock(svn_fs_t *fs,
445 svn_error_t *(*body)(void *baton,
446 apr_pool_t *scratch_pool),
448 apr_pool_t *scratch_pool)
450 return svn_error_trace(
451 with_lock(create_lock_baton(fs, pack_lock, body, baton,
457 svn_fs_x__with_txn_current_lock(svn_fs_t *fs,
458 svn_error_t *(*body)(void *baton,
459 apr_pool_t *scratch_pool),
461 apr_pool_t *scratch_pool)
463 return svn_error_trace(
464 with_lock(create_lock_baton(fs, txn_lock, body, baton,
470 svn_fs_x__with_all_locks(svn_fs_t *fs,
471 svn_error_t *(*body)(void *baton,
472 apr_pool_t *scratch_pool),
474 apr_pool_t *scratch_pool)
476 /* Be sure to use the correct lock ordering as documented in
477 fs_fs_shared_data_t. The lock chain is being created in
478 innermost (last to acquire) -> outermost (first to acquire) order. */
479 with_lock_baton_t *lock_baton
480 = create_lock_baton(fs, write_lock, body, baton, scratch_pool);
482 lock_baton = chain_lock_baton(pack_lock, lock_baton);
483 lock_baton = chain_lock_baton(txn_lock, lock_baton);
485 return svn_error_trace(with_lock(lock_baton, scratch_pool));
489 /* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
491 typedef struct unlock_proto_rev_baton_t
493 svn_fs_x__txn_id_t txn_id;
495 } unlock_proto_rev_baton_t;
497 /* Callback used in the implementation of unlock_proto_rev(). */
499 unlock_proto_rev_body(svn_fs_t *fs,
501 apr_pool_t *scratch_pool)
503 const unlock_proto_rev_baton_t *b = baton;
504 apr_file_t *lockfile = b->lockcookie;
505 svn_fs_x__shared_txn_data_t *txn = get_shared_txn(fs, b->txn_id, FALSE);
506 apr_status_t apr_err;
509 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
510 _("Can't unlock unknown transaction '%s'"),
511 svn_fs_x__txn_name(b->txn_id, scratch_pool));
512 if (!txn->being_written)
513 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
514 _("Can't unlock nonlocked transaction '%s'"),
515 svn_fs_x__txn_name(b->txn_id, scratch_pool));
517 apr_err = apr_file_unlock(lockfile);
519 return svn_error_wrap_apr
521 _("Can't unlock prototype revision lockfile for transaction '%s'"),
522 svn_fs_x__txn_name(b->txn_id, scratch_pool));
523 apr_err = apr_file_close(lockfile);
525 return svn_error_wrap_apr
527 _("Can't close prototype revision lockfile for transaction '%s'"),
528 svn_fs_x__txn_name(b->txn_id, scratch_pool));
530 txn->being_written = FALSE;
535 /* Unlock the prototype revision file for transaction TXN_ID in filesystem
536 FS using cookie LOCKCOOKIE. The original prototype revision file must
537 have been closed _before_ calling this function.
539 Perform temporary allocations in SCRATCH_POOL. */
541 unlock_proto_rev(svn_fs_t *fs,
542 svn_fs_x__txn_id_t txn_id,
544 apr_pool_t *scratch_pool)
546 unlock_proto_rev_baton_t b;
549 b.lockcookie = lockcookie;
550 return with_txnlist_lock(fs, unlock_proto_rev_body, &b, scratch_pool);
553 /* A structure used by get_writable_proto_rev() and
554 get_writable_proto_rev_body(), which see. */
555 typedef struct get_writable_proto_rev_baton_t
558 svn_fs_x__txn_id_t txn_id;
559 } get_writable_proto_rev_baton_t;
561 /* Callback used in the implementation of get_writable_proto_rev(). */
563 get_writable_proto_rev_body(svn_fs_t *fs,
565 apr_pool_t *scratch_pool)
567 const get_writable_proto_rev_baton_t *b = baton;
568 void **lockcookie = b->lockcookie;
569 svn_fs_x__shared_txn_data_t *txn = get_shared_txn(fs, b->txn_id, TRUE);
571 /* First, ensure that no thread in this process (including this one)
572 is currently writing to this transaction's proto-rev file. */
573 if (txn->being_written)
574 return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
575 _("Cannot write to the prototype revision file "
576 "of transaction '%s' because a previous "
577 "representation is currently being written by "
579 svn_fs_x__txn_name(b->txn_id, scratch_pool));
582 /* We know that no thread in this process is writing to the proto-rev
583 file, and by extension, that no thread in this process is holding a
584 lock on the prototype revision lock file. It is therefore safe
585 for us to attempt to lock this file, to see if any other process
586 is holding a lock. */
589 apr_file_t *lockfile;
590 apr_status_t apr_err;
591 const char *lockfile_path
592 = svn_fs_x__path_txn_proto_rev_lock(fs, b->txn_id, scratch_pool);
594 /* Open the proto-rev lockfile, creating it if necessary, as it may
595 not exist if the transaction dates from before the lockfiles were
598 ### We'd also like to use something like svn_io_file_lock2(), but
599 that forces us to create a subpool just to be able to unlock
600 the file, which seems a waste. */
601 SVN_ERR(svn_io_file_open(&lockfile, lockfile_path,
602 APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
605 apr_err = apr_file_lock(lockfile,
606 APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK);
609 svn_error_clear(svn_io_file_close(lockfile, scratch_pool));
611 if (APR_STATUS_IS_EAGAIN(apr_err))
612 return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
613 _("Cannot write to the prototype revision "
614 "file of transaction '%s' because a "
615 "previous representation is currently "
616 "being written by another process"),
617 svn_fs_x__txn_name(b->txn_id,
620 return svn_error_wrap_apr(apr_err,
621 _("Can't get exclusive lock on file '%s'"),
622 svn_dirent_local_style(lockfile_path,
626 *lockcookie = lockfile;
629 /* We've successfully locked the transaction; mark it as such. */
630 txn->being_written = TRUE;
635 /* Make sure the length ACTUAL_LENGTH of the proto-revision file PROTO_REV
636 of transaction TXN_ID in filesystem FS matches the proto-index file.
637 Trim any crash / failure related extra data from the proto-rev file.
639 If the prototype revision file is too short, we can't do much but bail out.
641 Perform all allocations in SCRATCH_POOL. */
643 auto_truncate_proto_rev(svn_fs_t *fs,
644 apr_file_t *proto_rev,
645 apr_off_t actual_length,
646 svn_fs_x__txn_id_t txn_id,
647 apr_pool_t *scratch_pool)
649 /* Determine file range covered by the proto-index so far. Note that
650 we always append to both file, i.e. the last index entry also
651 corresponds to the last addition in the rev file. */
652 const char *path = svn_fs_x__path_p2l_proto_index(fs, txn_id, scratch_pool);
654 apr_off_t indexed_length;
656 SVN_ERR(svn_fs_x__p2l_proto_index_open(&file, path, scratch_pool));
657 SVN_ERR(svn_fs_x__p2l_proto_index_next_offset(&indexed_length, file,
659 SVN_ERR(svn_io_file_close(file, scratch_pool));
661 /* Handle mismatches. */
662 if (indexed_length < actual_length)
663 SVN_ERR(svn_io_file_trunc(proto_rev, indexed_length, scratch_pool));
664 else if (indexed_length > actual_length)
665 return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
667 _("p2l proto index offset %s beyond proto"
668 "rev file size %s for TXN %s"),
669 apr_off_t_toa(scratch_pool, indexed_length),
670 apr_off_t_toa(scratch_pool, actual_length),
671 svn_fs_x__txn_name(txn_id, scratch_pool));
676 /* Get a handle to the prototype revision file for transaction TXN_ID in
677 filesystem FS, and lock it for writing. Return FILE, a file handle
678 positioned at the end of the file, and LOCKCOOKIE, a cookie that
679 should be passed to unlock_proto_rev() to unlock the file once FILE
682 If the prototype revision file is already locked, return error
683 SVN_ERR_FS_REP_BEING_WRITTEN.
685 Perform all allocations in POOL. */
687 get_writable_proto_rev(apr_file_t **file,
690 svn_fs_x__txn_id_t txn_id,
693 get_writable_proto_rev_baton_t b;
695 apr_off_t end_offset = 0;
697 b.lockcookie = lockcookie;
700 SVN_ERR(with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool));
702 /* Now open the prototype revision file and seek to the end. */
703 err = svn_io_file_open(file,
704 svn_fs_x__path_txn_proto_rev(fs, txn_id, pool),
705 APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT, pool);
707 /* You might expect that we could dispense with the following seek
708 and achieve the same thing by opening the file using APR_APPEND.
709 Unfortunately, APR's buffered file implementation unconditionally
710 places its initial file pointer at the start of the file (even for
711 files opened with APR_APPEND), so we need this seek to reconcile
712 the APR file pointer to the OS file pointer (since we need to be
713 able to read the current file position later). */
715 err = svn_io_file_seek(*file, APR_END, &end_offset, pool);
717 /* We don't want unused sections (such as leftovers from failed delta
718 stream) in our file. If we use log addressing, we would need an
719 index entry for the unused section and that section would need to
720 be all NUL by convention. So, detect and fix those cases by truncating
721 the protorev file. */
723 err = auto_truncate_proto_rev(fs, *file, end_offset, txn_id, pool);
727 err = svn_error_compose_create(
729 unlock_proto_rev(fs, txn_id, *lockcookie, pool));
734 return svn_error_trace(err);
737 /* Callback used in the implementation of purge_shared_txn(). */
739 purge_shared_txn_body(svn_fs_t *fs,
741 apr_pool_t *scratch_pool)
743 svn_fs_x__txn_id_t txn_id = *(const svn_fs_x__txn_id_t *)baton;
745 free_shared_txn(fs, txn_id);
750 /* Purge the shared data for transaction TXN_ID in filesystem FS.
751 Perform all temporary allocations in SCRATCH_POOL. */
753 purge_shared_txn(svn_fs_t *fs,
754 svn_fs_x__txn_id_t txn_id,
755 apr_pool_t *scratch_pool)
757 return with_txnlist_lock(fs, purge_shared_txn_body, &txn_id, scratch_pool);
762 svn_fs_x__is_fresh_txn_root(svn_fs_x__noderev_t *noderev)
764 /* Is it a root node? */
765 if (noderev->noderev_id.number != SVN_FS_X__ITEM_INDEX_ROOT_NODE)
768 /* ... in a transaction? */
769 if (!svn_fs_x__is_txn(noderev->noderev_id.change_set))
772 /* ... with no prop change in that txn?
773 (Once we set a property, the prop rep will never become NULL again.) */
774 if (noderev->prop_rep && svn_fs_x__is_txn(noderev->prop_rep->id.change_set))
777 /* ... and no sub-tree change?
778 (Once we set a text, the data rep will never become NULL again.) */
779 if (noderev->data_rep && svn_fs_x__is_txn(noderev->data_rep->id.change_set))
782 /* Root node of a txn with no changes. */
787 svn_fs_x__put_node_revision(svn_fs_t *fs,
788 svn_fs_x__noderev_t *noderev,
789 apr_pool_t *scratch_pool)
791 apr_file_t *noderev_file;
792 const svn_fs_x__id_t *id = &noderev->noderev_id;
794 if (! svn_fs_x__is_txn(id->change_set))
795 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
796 _("Attempted to write to non-transaction '%s'"),
797 svn_fs_x__id_unparse(id, scratch_pool)->data);
799 SVN_ERR(svn_io_file_open(&noderev_file,
800 svn_fs_x__path_txn_node_rev(fs, id, scratch_pool,
802 APR_WRITE | APR_CREATE | APR_TRUNCATE
803 | APR_BUFFERED, APR_OS_DEFAULT, scratch_pool));
805 SVN_ERR(svn_fs_x__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE,
807 noderev, scratch_pool));
809 SVN_ERR(svn_io_file_close(noderev_file, scratch_pool));
814 /* For the in-transaction NODEREV within FS, write the sha1->rep mapping
815 * file in the respective transaction, if rep sharing has been enabled etc.
816 * Use SCATCH_POOL for temporary allocations.
819 store_sha1_rep_mapping(svn_fs_t *fs,
820 svn_fs_x__noderev_t *noderev,
821 apr_pool_t *scratch_pool)
823 svn_fs_x__data_t *ffd = fs->fsap_data;
825 /* if rep sharing has been enabled and the noderev has a data rep and
826 * its SHA-1 is known, store the rep struct under its SHA1. */
827 if ( ffd->rep_sharing_allowed
829 && noderev->data_rep->has_sha1)
831 apr_file_t *rep_file;
833 = svn_fs_x__get_txn_id(noderev->data_rep->id.change_set);
834 const char *file_name
835 = svn_fs_x__path_txn_sha1(fs, txn_id,
836 noderev->data_rep->sha1_digest,
838 svn_stringbuf_t *rep_string
839 = svn_fs_x__unparse_representation(noderev->data_rep,
840 (noderev->kind == svn_node_dir),
841 scratch_pool, scratch_pool);
843 SVN_ERR(svn_io_file_open(&rep_file, file_name,
844 APR_WRITE | APR_CREATE | APR_TRUNCATE
845 | APR_BUFFERED, APR_OS_DEFAULT, scratch_pool));
847 SVN_ERR(svn_io_file_write_full(rep_file, rep_string->data,
848 rep_string->len, NULL, scratch_pool));
850 SVN_ERR(svn_io_file_close(rep_file, scratch_pool));
857 unparse_dir_entry(svn_fs_x__dirent_t *dirent,
858 svn_stream_t *stream,
859 apr_pool_t *scratch_pool)
862 = apr_psprintf(scratch_pool, "%s %s",
863 (dirent->kind == svn_node_file) ? SVN_FS_X__KIND_FILE
864 : SVN_FS_X__KIND_DIR,
865 svn_fs_x__id_unparse(&dirent->id, scratch_pool)->data);
867 SVN_ERR(svn_stream_printf(stream, scratch_pool, "K %" APR_SIZE_T_FMT
868 "\n%s\nV %" APR_SIZE_T_FMT "\n%s\n",
869 strlen(dirent->name), dirent->name,
874 /* Write the directory given as array of dirent structs in ENTRIES to STREAM.
875 Perform temporary allocations in SCRATCH_POOL. */
877 unparse_dir_entries(apr_array_header_t *entries,
878 svn_stream_t *stream,
879 apr_pool_t *scratch_pool)
881 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
883 for (i = 0; i < entries->nelts; ++i)
885 svn_fs_x__dirent_t *dirent;
887 svn_pool_clear(iterpool);
888 dirent = APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *);
889 SVN_ERR(unparse_dir_entry(dirent, stream, iterpool));
892 SVN_ERR(svn_stream_printf(stream, scratch_pool, "%s\n",
893 SVN_HASH_TERMINATOR));
895 svn_pool_destroy(iterpool);
899 /* Return a deep copy of SOURCE and allocate it in RESULT_POOL.
901 static svn_fs_x__change_t *
902 path_change_dup(const svn_fs_x__change_t *source,
903 apr_pool_t *result_pool)
905 svn_fs_x__change_t *result
906 = apr_pmemdup(result_pool, source, sizeof(*source));
908 = apr_pstrmemdup(result_pool, source->path.data, source->path.len);
910 if (source->copyfrom_path)
911 result->copyfrom_path = apr_pstrdup(result_pool, source->copyfrom_path);
916 /* Merge the internal-use-only CHANGE into a hash of public-FS
917 svn_fs_x__change_t CHANGED_PATHS, collapsing multiple changes into a
918 single summarical (is that real word?) change per path. DELETIONS is
919 also a path->svn_fs_x__change_t hash and contains all the deletions
920 that got turned into a replacement. */
922 fold_change(apr_hash_t *changed_paths,
923 apr_hash_t *deletions,
924 const svn_fs_x__change_t *change)
926 apr_pool_t *pool = apr_hash_pool_get(changed_paths);
927 svn_fs_x__change_t *old_change, *new_change;
928 const svn_string_t *path = &change->path;
930 if ((old_change = apr_hash_get(changed_paths, path->data, path->len)))
932 /* This path already exists in the hash, so we have to merge
933 this change into the already existing one. */
935 /* Sanity check: only allow unused node revision IDs in the
937 if ((! svn_fs_x__id_used(&change->noderev_id))
938 && (change->change_kind != svn_fs_path_change_reset))
939 return svn_error_create
940 (SVN_ERR_FS_CORRUPT, NULL,
941 _("Missing required node revision ID"));
943 /* Sanity check: we should be talking about the same node
944 revision ID as our last change except where the last change
946 if (svn_fs_x__id_used(&change->noderev_id)
947 && (!svn_fs_x__id_eq(&old_change->noderev_id, &change->noderev_id))
948 && (old_change->change_kind != svn_fs_path_change_delete))
949 return svn_error_create
950 (SVN_ERR_FS_CORRUPT, NULL,
951 _("Invalid change ordering: new node revision ID "
954 /* Sanity check: an add, replacement, or reset must be the first
955 thing to follow a deletion. */
956 if ((old_change->change_kind == svn_fs_path_change_delete)
957 && (! ((change->change_kind == svn_fs_path_change_replace)
958 || (change->change_kind == svn_fs_path_change_reset)
959 || (change->change_kind == svn_fs_path_change_add))))
960 return svn_error_create
961 (SVN_ERR_FS_CORRUPT, NULL,
962 _("Invalid change ordering: non-add change on deleted path"));
964 /* Sanity check: an add can't follow anything except
965 a delete or reset. */
966 if ((change->change_kind == svn_fs_path_change_add)
967 && (old_change->change_kind != svn_fs_path_change_delete)
968 && (old_change->change_kind != svn_fs_path_change_reset))
969 return svn_error_create
970 (SVN_ERR_FS_CORRUPT, NULL,
971 _("Invalid change ordering: add change on preexisting path"));
973 /* Now, merge that change in. */
974 switch (change->change_kind)
976 case svn_fs_path_change_reset:
977 /* A reset here will simply remove the path change from the
979 apr_hash_set(changed_paths, path->data, path->len, NULL);
982 case svn_fs_path_change_delete:
983 if (old_change->change_kind == svn_fs_path_change_add)
985 /* If the path was introduced in this transaction via an
986 add, and we are deleting it, just remove the path
987 altogether. (The caller will delete any child paths.) */
988 apr_hash_set(changed_paths, path->data, path->len, NULL);
990 else if (old_change->change_kind == svn_fs_path_change_replace)
992 /* A deleting a 'replace' restore the original deletion. */
993 new_change = apr_hash_get(deletions, path->data, path->len);
994 SVN_ERR_ASSERT(new_change);
995 apr_hash_set(changed_paths, path->data, path->len, new_change);
999 /* A deletion overrules a previous change (modify). */
1000 new_change = path_change_dup(change, pool);
1001 apr_hash_set(changed_paths, path->data, path->len, new_change);
1005 case svn_fs_path_change_add:
1006 case svn_fs_path_change_replace:
1007 /* An add at this point must be following a previous delete,
1008 so treat it just like a replace. Remember the original
1009 deletion such that we are able to delete this path again
1010 (the replacement may have changed node kind and id). */
1011 new_change = path_change_dup(change, pool);
1012 new_change->change_kind = svn_fs_path_change_replace;
1014 apr_hash_set(changed_paths, path->data, path->len, new_change);
1016 /* Remember the original change.
1017 * Make sure to allocate the hash key in a durable pool. */
1018 apr_hash_set(deletions,
1019 apr_pstrmemdup(apr_hash_pool_get(deletions),
1020 path->data, path->len),
1021 path->len, old_change);
1024 case svn_fs_path_change_modify:
1026 /* If the new change modifies some attribute of the node, set
1027 the corresponding flag, whether it already was set or not.
1028 Note: We do not reset a flag to FALSE if a change is undone. */
1029 if (change->text_mod)
1030 old_change->text_mod = TRUE;
1031 if (change->prop_mod)
1032 old_change->prop_mod = TRUE;
1033 if (change->mergeinfo_mod == svn_tristate_true)
1034 old_change->mergeinfo_mod = svn_tristate_true;
1040 /* Add this path. The API makes no guarantees that this (new) key
1041 will not be retained. Thus, we copy the key into the target pool
1042 to ensure a proper lifetime. */
1043 new_change = path_change_dup(change, pool);
1044 apr_hash_set(changed_paths, new_change->path.data,
1045 new_change->path.len, new_change);
1048 return SVN_NO_ERROR;
1051 /* Baton type to be used with process_changes(). */
1052 typedef struct process_changes_baton_t
1054 /* Folded list of path changes. */
1055 apr_hash_t *changed_paths;
1057 /* Path changes that are deletions and have been turned into
1058 replacements. If those replacements get deleted again, this
1059 container contains the record that we have to revert to. */
1060 apr_hash_t *deletions;
1061 } process_changes_baton_t;
1063 /* An implementation of svn_fs_x__change_receiver_t.
1064 Examine all the changed path entries in CHANGES and store them in
1065 *CHANGED_PATHS. Folding is done to remove redundant or unnecessary
1066 data. Use SCRATCH_POOL for temporary allocations. */
1067 static svn_error_t *
1068 process_changes(void *baton_p,
1069 svn_fs_x__change_t *change,
1070 apr_pool_t *scratch_pool)
1072 process_changes_baton_t *baton = baton_p;
1074 SVN_ERR(fold_change(baton->changed_paths, baton->deletions, change));
1076 /* Now, if our change was a deletion or replacement, we have to
1077 blow away any changes thus far on paths that are (or, were)
1078 children of this path.
1079 ### i won't bother with another iteration pool here -- at
1080 most we talking about a few extra dups of paths into what
1081 is already a temporary subpool.
1084 if ((change->change_kind == svn_fs_path_change_delete)
1085 || (change->change_kind == svn_fs_path_change_replace))
1087 apr_hash_index_t *hi;
1089 /* a potential child path must contain at least 2 more chars
1090 (the path separator plus at least one char for the name).
1091 Also, we should not assume that all paths have been normalized
1092 i.e. some might have trailing path separators.
1094 apr_ssize_t path_len = change->path.len;
1095 apr_ssize_t min_child_len = path_len == 0
1097 : change->path.data[path_len-1] == '/'
1101 /* CAUTION: This is the inner loop of an O(n^2) algorithm.
1102 The number of changes to process may be >> 1000.
1103 Therefore, keep the inner loop as tight as possible.
1105 for (hi = apr_hash_first(scratch_pool, baton->changed_paths);
1107 hi = apr_hash_next(hi))
1109 /* KEY is the path. */
1112 apr_hash_this(hi, &path, &klen, NULL);
1114 /* If we come across a child of our path, remove it.
1115 Call svn_fspath__skip_ancestor only if there is a chance that
1116 this is actually a sub-path.
1118 if (klen >= min_child_len)
1122 child = svn_fspath__skip_ancestor(change->path.data, path);
1123 if (child && child[0] != '\0')
1124 apr_hash_set(baton->changed_paths, path, klen, NULL);
1129 return SVN_NO_ERROR;
1133 svn_fs_x__txn_changes_fetch(apr_hash_t **changed_paths_p,
1135 svn_fs_x__txn_id_t txn_id,
1139 apr_hash_t *changed_paths = apr_hash_make(pool);
1140 apr_pool_t *scratch_pool = svn_pool_create(pool);
1141 process_changes_baton_t baton;
1143 baton.changed_paths = changed_paths;
1144 baton.deletions = apr_hash_make(scratch_pool);
1146 SVN_ERR(svn_io_file_open(&file,
1147 svn_fs_x__path_txn_changes(fs, txn_id, scratch_pool),
1148 APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
1151 SVN_ERR(svn_fs_x__read_changes_incrementally(
1152 svn_stream_from_aprfile2(file, TRUE,
1154 process_changes, &baton,
1156 svn_pool_destroy(scratch_pool);
1158 *changed_paths_p = changed_paths;
1160 return SVN_NO_ERROR;
1163 /* Copy a revision node-rev SRC into the current transaction TXN_ID in
1164 the filesystem FS. This is only used to create the root of a transaction.
1165 Temporary allocations are from SCRATCH_POOL. */
1166 static svn_error_t *
1167 create_new_txn_noderev_from_rev(svn_fs_t *fs,
1168 svn_fs_x__txn_id_t txn_id,
1169 svn_fs_x__id_t *src,
1170 apr_pool_t *scratch_pool)
1172 svn_fs_x__noderev_t *noderev;
1173 SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, src, scratch_pool,
1176 /* This must be a root node. */
1177 SVN_ERR_ASSERT( noderev->node_id.number == 0
1178 && noderev->copy_id.number == 0);
1180 if (svn_fs_x__is_txn(noderev->noderev_id.change_set))
1181 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1182 _("Copying from transactions not allowed"));
1184 noderev->predecessor_id = noderev->noderev_id;
1185 noderev->predecessor_count++;
1186 noderev->copyfrom_path = NULL;
1187 noderev->copyfrom_rev = SVN_INVALID_REVNUM;
1189 /* For the transaction root, the copyroot never changes. */
1190 svn_fs_x__init_txn_root(&noderev->noderev_id, txn_id);
1192 return svn_fs_x__put_node_revision(fs, noderev, scratch_pool);
1195 /* A structure used by get_and_increment_txn_key_body(). */
1196 typedef struct get_and_increment_txn_key_baton_t
1199 apr_uint64_t txn_number;
1200 } get_and_increment_txn_key_baton_t;
1202 /* Callback used in the implementation of create_txn_dir(). This gets
1203 the current base 36 value in PATH_TXN_CURRENT and increments it.
1204 It returns the original value by the baton. */
1205 static svn_error_t *
1206 get_and_increment_txn_key_body(void *baton,
1207 apr_pool_t *scratch_pool)
1209 get_and_increment_txn_key_baton_t *cb = baton;
1210 const char *txn_current_filename = svn_fs_x__path_txn_current(cb->fs,
1212 const char *tmp_filename;
1213 char new_id_str[SVN_INT64_BUFFER_SIZE];
1215 svn_stringbuf_t *buf;
1216 SVN_ERR(svn_fs_x__read_content(&buf, txn_current_filename, scratch_pool));
1218 /* remove trailing newlines */
1219 cb->txn_number = svn__base36toui64(NULL, buf->data);
1221 /* Increment the key and add a trailing \n to the string so the
1222 txn-current file has a newline in it. */
1223 SVN_ERR(svn_io_write_unique(&tmp_filename,
1224 svn_dirent_dirname(txn_current_filename,
1227 svn__ui64tobase36(new_id_str, cb->txn_number+1),
1228 svn_io_file_del_none, scratch_pool));
1229 SVN_ERR(svn_fs_x__move_into_place(tmp_filename, txn_current_filename,
1230 txn_current_filename, scratch_pool));
1232 return SVN_NO_ERROR;
1235 /* Create a unique directory for a transaction in FS based on revision REV.
1236 Return the ID for this transaction in *ID_P and *TXN_ID. Use a sequence
1237 value in the transaction ID to prevent reuse of transaction IDs. */
1238 static svn_error_t *
1239 create_txn_dir(const char **id_p,
1240 svn_fs_x__txn_id_t *txn_id,
1242 apr_pool_t *result_pool,
1243 apr_pool_t *scratch_pool)
1245 get_and_increment_txn_key_baton_t cb;
1246 const char *txn_dir;
1248 /* Get the current transaction sequence value, which is a base-36
1249 number, from the txn-current file, and write an
1250 incremented value back out to the file. Place the revision
1251 number the transaction is based off into the transaction id. */
1253 SVN_ERR(svn_fs_x__with_txn_current_lock(fs,
1254 get_and_increment_txn_key_body,
1257 *txn_id = cb.txn_number;
1259 *id_p = svn_fs_x__txn_name(*txn_id, result_pool);
1260 txn_dir = svn_fs_x__path_txn_dir(fs, *txn_id, scratch_pool);
1262 return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, scratch_pool);
1265 /* Create a new transaction in filesystem FS, based on revision REV,
1266 and store it in *TXN_P, allocated in RESULT_POOL. Allocate necessary
1267 temporaries from SCRATCH_POOL. */
1268 static svn_error_t *
1269 create_txn(svn_fs_txn_t **txn_p,
1272 apr_pool_t *result_pool,
1273 apr_pool_t *scratch_pool)
1277 svn_fs_x__id_t root_id;
1279 txn = apr_pcalloc(result_pool, sizeof(*txn));
1280 ftd = apr_pcalloc(result_pool, sizeof(*ftd));
1282 /* Valid revision number? */
1283 SVN_ERR(svn_fs_x__ensure_revision_exists(rev, fs, scratch_pool));
1285 /* Get the txn_id. */
1286 SVN_ERR(create_txn_dir(&txn->id, &ftd->txn_id, fs, result_pool,
1290 txn->base_rev = rev;
1292 txn->vtable = &txn_vtable;
1293 txn->fsap_data = ftd;
1296 /* Create a new root node for this transaction. */
1297 svn_fs_x__init_rev_root(&root_id, rev);
1298 SVN_ERR(create_new_txn_noderev_from_rev(fs, ftd->txn_id, &root_id,
1301 /* Create an empty rev file. */
1302 SVN_ERR(svn_io_file_create_empty(
1303 svn_fs_x__path_txn_proto_rev(fs, ftd->txn_id, scratch_pool),
1306 /* Create an empty rev-lock file. */
1307 SVN_ERR(svn_io_file_create_empty(
1308 svn_fs_x__path_txn_proto_rev_lock(fs, ftd->txn_id, scratch_pool),
1311 /* Create an empty changes file. */
1312 SVN_ERR(svn_io_file_create_empty(
1313 svn_fs_x__path_txn_changes(fs, ftd->txn_id, scratch_pool),
1316 /* Create the next-ids file. */
1317 SVN_ERR(svn_io_file_create(
1318 svn_fs_x__path_txn_next_ids(fs, ftd->txn_id, scratch_pool),
1319 "0 0\n", scratch_pool));
1321 return SVN_NO_ERROR;
1324 /* Store the property list for transaction TXN_ID in PROPLIST.
1325 Perform temporary allocations in POOL. */
1326 static svn_error_t *
1327 get_txn_proplist(apr_hash_t *proplist,
1329 svn_fs_x__txn_id_t txn_id,
1332 svn_stream_t *stream;
1334 /* Check for issue #3696. (When we find and fix the cause, we can change
1335 * this to an assertion.) */
1336 if (txn_id == SVN_FS_X__INVALID_TXN_ID)
1337 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1338 _("Internal error: a null transaction id was "
1339 "passed to get_txn_proplist()"));
1341 /* Open the transaction properties file. */
1342 SVN_ERR(svn_stream_open_readonly(&stream,
1343 svn_fs_x__path_txn_props(fs, txn_id, pool),
1346 /* Read in the property list. */
1347 SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
1349 return svn_stream_close(stream);
1352 /* Save the property list PROPS as the revprops for transaction TXN_ID
1353 in FS. Perform temporary allocations in SCRATCH_POOL. */
1354 static svn_error_t *
1355 set_txn_proplist(svn_fs_t *fs,
1356 svn_fs_x__txn_id_t txn_id,
1358 svn_boolean_t final,
1359 apr_pool_t *scratch_pool)
1361 svn_stringbuf_t *buf;
1362 svn_stream_t *stream;
1364 /* Write out the new file contents to BUF. */
1365 buf = svn_stringbuf_create_ensure(1024, scratch_pool);
1366 stream = svn_stream_from_stringbuf(buf, scratch_pool);
1367 SVN_ERR(svn_hash_write2(props, stream, SVN_HASH_TERMINATOR, scratch_pool));
1368 SVN_ERR(svn_stream_close(stream));
1370 /* Open the transaction properties file and write new contents to it. */
1371 SVN_ERR(svn_io_write_atomic((final
1372 ? svn_fs_x__path_txn_props_final(fs, txn_id,
1374 : svn_fs_x__path_txn_props(fs, txn_id,
1376 buf->data, buf->len,
1377 NULL /* copy_perms_path */, scratch_pool));
1378 return SVN_NO_ERROR;
1383 svn_fs_x__change_txn_prop(svn_fs_txn_t *txn,
1385 const svn_string_t *value,
1386 apr_pool_t *scratch_pool)
1388 apr_array_header_t *props = apr_array_make(scratch_pool, 1,
1389 sizeof(svn_prop_t));
1394 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
1396 return svn_fs_x__change_txn_props(txn, props, scratch_pool);
1400 svn_fs_x__change_txn_props(svn_fs_txn_t *txn,
1401 const apr_array_header_t *props,
1402 apr_pool_t *scratch_pool)
1404 fs_txn_data_t *ftd = txn->fsap_data;
1405 apr_hash_t *txn_prop = apr_hash_make(scratch_pool);
1409 err = get_txn_proplist(txn_prop, txn->fs, ftd->txn_id, scratch_pool);
1410 /* Here - and here only - we need to deal with the possibility that the
1411 transaction property file doesn't yet exist. The rest of the
1412 implementation assumes that the file exists, but we're called to set the
1413 initial transaction properties as the transaction is being created. */
1414 if (err && (APR_STATUS_IS_ENOENT(err->apr_err)))
1415 svn_error_clear(err);
1417 return svn_error_trace(err);
1419 for (i = 0; i < props->nelts; i++)
1421 svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
1423 if (svn_hash_gets(txn_prop, SVN_FS__PROP_TXN_CLIENT_DATE)
1424 && !strcmp(prop->name, SVN_PROP_REVISION_DATE))
1425 svn_hash_sets(txn_prop, SVN_FS__PROP_TXN_CLIENT_DATE,
1426 svn_string_create("1", scratch_pool));
1428 svn_hash_sets(txn_prop, prop->name, prop->value);
1431 /* Create a new version of the file and write out the new props. */
1432 /* Open the transaction properties file. */
1433 SVN_ERR(set_txn_proplist(txn->fs, ftd->txn_id, txn_prop, FALSE,
1436 return SVN_NO_ERROR;
1440 svn_fs_x__get_txn(svn_fs_x__transaction_t **txn_p,
1442 svn_fs_x__txn_id_t txn_id,
1445 svn_fs_x__transaction_t *txn;
1446 svn_fs_x__noderev_t *noderev;
1447 svn_fs_x__id_t root_id;
1449 txn = apr_pcalloc(pool, sizeof(*txn));
1450 txn->proplist = apr_hash_make(pool);
1452 SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool));
1453 svn_fs_x__init_txn_root(&root_id, txn_id);
1455 SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, &root_id, pool, pool));
1457 txn->base_rev = svn_fs_x__get_revnum(noderev->predecessor_id.change_set);
1462 return SVN_NO_ERROR;
1465 /* If it is supported by the format of file system FS, store the (ITEM_INDEX,
1466 * OFFSET) pair in the log-to-phys proto index file of transaction TXN_ID.
1467 * Use SCRATCH_POOL for temporary allocations.
1469 static svn_error_t *
1470 store_l2p_index_entry(svn_fs_t *fs,
1471 svn_fs_x__txn_id_t txn_id,
1473 apr_uint64_t item_index,
1474 apr_pool_t *scratch_pool)
1476 const char *path = svn_fs_x__path_l2p_proto_index(fs, txn_id, scratch_pool);
1478 SVN_ERR(svn_fs_x__l2p_proto_index_open(&file, path, scratch_pool));
1479 SVN_ERR(svn_fs_x__l2p_proto_index_add_entry(file, offset, 0,
1480 item_index, scratch_pool));
1481 SVN_ERR(svn_io_file_close(file, scratch_pool));
1483 return SVN_NO_ERROR;
1486 /* If it is supported by the format of file system FS, store ENTRY in the
1487 * phys-to-log proto index file of transaction TXN_ID.
1488 * Use SCRATCH_POOL for temporary allocations.
1490 static svn_error_t *
1491 store_p2l_index_entry(svn_fs_t *fs,
1492 svn_fs_x__txn_id_t txn_id,
1493 svn_fs_x__p2l_entry_t *entry,
1494 apr_pool_t *scratch_pool)
1496 const char *path = svn_fs_x__path_p2l_proto_index(fs, txn_id, scratch_pool);
1498 SVN_ERR(svn_fs_x__p2l_proto_index_open(&file, path, scratch_pool));
1499 SVN_ERR(svn_fs_x__p2l_proto_index_add_entry(file, entry, scratch_pool));
1500 SVN_ERR(svn_io_file_close(file, scratch_pool));
1502 return SVN_NO_ERROR;
1505 /* Allocate an item index in the transaction TXN_ID of file system FS and
1506 * return it in *ITEM_INDEX. Use SCRATCH_POOL for temporary allocations.
1508 static svn_error_t *
1509 allocate_item_index(apr_uint64_t *item_index,
1511 svn_fs_x__txn_id_t txn_id,
1512 apr_pool_t *scratch_pool)
1515 char buffer[SVN_INT64_BUFFER_SIZE] = { 0 };
1516 svn_boolean_t eof = FALSE;
1517 apr_size_t to_write;
1519 apr_off_t offset = 0;
1522 SVN_ERR(svn_io_file_open(&file,
1523 svn_fs_x__path_txn_item_index(fs, txn_id,
1525 APR_READ | APR_WRITE
1526 | APR_CREATE | APR_BUFFERED,
1527 APR_OS_DEFAULT, scratch_pool));
1528 SVN_ERR(svn_io_file_read_full2(file, buffer, sizeof(buffer)-1,
1529 &read, &eof, scratch_pool));
1531 SVN_ERR(svn_cstring_atoui64(item_index, buffer));
1533 *item_index = SVN_FS_X__ITEM_INDEX_FIRST_USER;
1536 to_write = svn__ui64toa(buffer, *item_index + 1);
1538 /* write it back to disk */
1539 SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool));
1540 SVN_ERR(svn_io_file_write_full(file, buffer, to_write, NULL, scratch_pool));
1541 SVN_ERR(svn_io_file_close(file, scratch_pool));
1543 return SVN_NO_ERROR;
1546 /* Write out the currently available next node_id NODE_ID and copy_id
1547 COPY_ID for transaction TXN_ID in filesystem FS. The next node-id is
1548 used both for creating new unique nodes for the given transaction, as
1549 well as uniquifying representations. Perform temporary allocations in
1551 static svn_error_t *
1552 write_next_ids(svn_fs_t *fs,
1553 svn_fs_x__txn_id_t txn_id,
1554 apr_uint64_t node_id,
1555 apr_uint64_t copy_id,
1556 apr_pool_t *scratch_pool)
1559 char buffer[2 * SVN_INT64_BUFFER_SIZE + 2];
1562 p += svn__ui64tobase36(p, node_id);
1564 p += svn__ui64tobase36(p, copy_id);
1568 SVN_ERR(svn_io_file_open(&file,
1569 svn_fs_x__path_txn_next_ids(fs, txn_id,
1571 APR_WRITE | APR_TRUNCATE,
1572 APR_OS_DEFAULT, scratch_pool));
1573 SVN_ERR(svn_io_file_write_full(file, buffer, p - buffer, NULL,
1575 return svn_io_file_close(file, scratch_pool);
1578 /* Find out what the next unique node-id and copy-id are for
1579 transaction TXN_ID in filesystem FS. Store the results in *NODE_ID
1580 and *COPY_ID. The next node-id is used both for creating new unique
1581 nodes for the given transaction, as well as uniquifying representations.
1582 Perform temporary allocations in SCRATCH_POOL. */
1583 static svn_error_t *
1584 read_next_ids(apr_uint64_t *node_id,
1585 apr_uint64_t *copy_id,
1587 svn_fs_x__txn_id_t txn_id,
1588 apr_pool_t *scratch_pool)
1590 svn_stringbuf_t *buf;
1592 SVN_ERR(svn_fs_x__read_content(&buf,
1593 svn_fs_x__path_txn_next_ids(fs, txn_id,
1597 /* Parse this into two separate strings. */
1600 *node_id = svn__base36toui64(&str, str);
1602 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1603 _("next-id file corrupt"));
1606 *copy_id = svn__base36toui64(&str, str);
1608 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1609 _("next-id file corrupt"));
1611 return SVN_NO_ERROR;
1614 /* Get a new and unique to this transaction node-id for transaction
1615 TXN_ID in filesystem FS. Store the new node-id in *NODE_ID_P.
1616 Node-ids are guaranteed to be unique to this transction, but may
1617 not necessarily be sequential.
1618 Perform temporary allocations in SCRATCH_POOL. */
1619 static svn_error_t *
1620 get_new_txn_node_id(svn_fs_x__id_t *node_id_p,
1622 svn_fs_x__txn_id_t txn_id,
1623 apr_pool_t *scratch_pool)
1625 apr_uint64_t node_id, copy_id;
1627 /* First read in the current next-ids file. */
1628 SVN_ERR(read_next_ids(&node_id, ©_id, fs, txn_id, scratch_pool));
1630 node_id_p->change_set = svn_fs_x__change_set_by_txn(txn_id);
1631 node_id_p->number = node_id;
1633 SVN_ERR(write_next_ids(fs, txn_id, ++node_id, copy_id, scratch_pool));
1635 return SVN_NO_ERROR;
1639 svn_fs_x__reserve_copy_id(svn_fs_x__id_t *copy_id_p,
1641 svn_fs_x__txn_id_t txn_id,
1642 apr_pool_t *scratch_pool)
1644 apr_uint64_t node_id, copy_id;
1646 /* First read in the current next-ids file. */
1647 SVN_ERR(read_next_ids(&node_id, ©_id, fs, txn_id, scratch_pool));
1649 copy_id_p->change_set = svn_fs_x__change_set_by_txn(txn_id);
1650 copy_id_p->number = copy_id;
1652 SVN_ERR(write_next_ids(fs, txn_id, node_id, ++copy_id, scratch_pool));
1654 return SVN_NO_ERROR;
1658 svn_fs_x__create_node(svn_fs_t *fs,
1659 svn_fs_x__noderev_t *noderev,
1660 const svn_fs_x__id_t *copy_id,
1661 svn_fs_x__txn_id_t txn_id,
1662 apr_pool_t *scratch_pool)
1664 /* Get a new node-id for this node. */
1665 SVN_ERR(get_new_txn_node_id(&noderev->node_id, fs, txn_id, scratch_pool));
1667 /* Assign copy-id. */
1668 noderev->copy_id = *copy_id;
1670 /* Noderev-id = Change set and item number within this change set. */
1671 noderev->noderev_id.change_set = svn_fs_x__change_set_by_txn(txn_id);
1672 SVN_ERR(allocate_item_index(&noderev->noderev_id.number, fs, txn_id,
1675 SVN_ERR(svn_fs_x__put_node_revision(fs, noderev, scratch_pool));
1677 return SVN_NO_ERROR;
1681 svn_fs_x__purge_txn(svn_fs_t *fs,
1682 const char *txn_id_str,
1683 apr_pool_t *scratch_pool)
1685 svn_fs_x__txn_id_t txn_id;
1686 SVN_ERR(svn_fs_x__txn_by_name(&txn_id, txn_id_str));
1688 /* Remove the shared transaction object associated with this transaction. */
1689 SVN_ERR(purge_shared_txn(fs, txn_id, scratch_pool));
1690 /* Remove the directory associated with this transaction. */
1691 SVN_ERR(svn_io_remove_dir2(svn_fs_x__path_txn_dir(fs, txn_id, scratch_pool),
1692 FALSE, NULL, NULL, scratch_pool));
1694 /* Delete protorev and its lock, which aren't in the txn
1695 directory. It's OK if they don't exist (for example, if this
1696 is post-commit and the proto-rev has been moved into
1698 SVN_ERR(svn_io_remove_file2(
1699 svn_fs_x__path_txn_proto_rev(fs, txn_id, scratch_pool),
1700 TRUE, scratch_pool));
1701 SVN_ERR(svn_io_remove_file2(
1702 svn_fs_x__path_txn_proto_rev_lock(fs, txn_id, scratch_pool),
1703 TRUE, scratch_pool));
1705 return SVN_NO_ERROR;
1710 svn_fs_x__abort_txn(svn_fs_txn_t *txn,
1711 apr_pool_t *scratch_pool)
1713 SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
1715 /* Now, purge the transaction. */
1716 SVN_ERR_W(svn_fs_x__purge_txn(txn->fs, txn->id, scratch_pool),
1717 apr_psprintf(scratch_pool, _("Transaction '%s' cleanup failed"),
1720 return SVN_NO_ERROR;
1724 svn_fs_x__set_entry(svn_fs_t *fs,
1725 svn_fs_x__txn_id_t txn_id,
1726 svn_fs_x__noderev_t *parent_noderev,
1728 const svn_fs_x__id_t *id,
1729 svn_node_kind_t kind,
1730 apr_pool_t *result_pool,
1731 apr_pool_t *scratch_pool)
1733 svn_fs_x__representation_t *rep = parent_noderev->data_rep;
1734 const char *filename
1735 = svn_fs_x__path_txn_node_children(fs, &parent_noderev->noderev_id,
1736 scratch_pool, scratch_pool);
1739 svn_fs_x__data_t *ffd = fs->fsap_data;
1740 apr_pool_t *subpool = svn_pool_create(scratch_pool);
1742 if (!rep || !svn_fs_x__is_txn(rep->id.change_set))
1744 apr_array_header_t *entries;
1746 /* Before we can modify the directory, we need to dump its old
1747 contents into a mutable representation file. */
1748 SVN_ERR(svn_fs_x__rep_contents_dir(&entries, fs, parent_noderev,
1750 SVN_ERR(svn_io_file_open(&file, filename,
1751 APR_WRITE | APR_CREATE | APR_BUFFERED,
1752 APR_OS_DEFAULT, scratch_pool));
1753 out = svn_stream_from_aprfile2(file, TRUE, scratch_pool);
1754 SVN_ERR(unparse_dir_entries(entries, out, subpool));
1756 svn_pool_clear(subpool);
1758 /* Provide the parent with a data rep if it had none before
1759 (directories so far empty). */
1762 rep = apr_pcalloc(result_pool, sizeof(*rep));
1763 parent_noderev->data_rep = rep;
1766 /* Mark the node-rev's data rep as mutable. */
1767 rep->id.change_set = svn_fs_x__change_set_by_txn(txn_id);
1768 rep->id.number = SVN_FS_X__ITEM_INDEX_UNUSED;
1770 /* Save noderev to disk. */
1771 SVN_ERR(svn_fs_x__put_node_revision(fs, parent_noderev, subpool));
1775 /* The directory rep is already mutable, so just open it for append. */
1776 SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
1777 APR_OS_DEFAULT, scratch_pool));
1778 out = svn_stream_from_aprfile2(file, TRUE, scratch_pool);
1781 /* update directory cache */
1783 /* build parameters: (name, new entry) pair */
1784 const svn_fs_x__id_t *key = &(parent_noderev->noderev_id);
1785 replace_baton_t baton;
1788 baton.new_entry = NULL;
1792 baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry));
1793 baton.new_entry->name = name;
1794 baton.new_entry->kind = kind;
1795 baton.new_entry->id = *id;
1798 /* actually update the cached directory (if cached) */
1799 SVN_ERR(svn_cache__set_partial(ffd->dir_cache, key,
1800 svn_fs_x__replace_dir_entry, &baton,
1803 svn_pool_clear(subpool);
1805 /* Append an incremental hash entry for the entry change. */
1808 svn_fs_x__dirent_t entry;
1813 SVN_ERR(unparse_dir_entry(&entry, out, subpool));
1817 SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
1818 strlen(name), name));
1821 SVN_ERR(svn_io_file_close(file, subpool));
1822 svn_pool_destroy(subpool);
1823 return SVN_NO_ERROR;
1827 svn_fs_x__add_change(svn_fs_t *fs,
1828 svn_fs_x__txn_id_t txn_id,
1830 const svn_fs_x__id_t *id,
1831 svn_fs_path_change_kind_t change_kind,
1832 svn_boolean_t text_mod,
1833 svn_boolean_t prop_mod,
1834 svn_boolean_t mergeinfo_mod,
1835 svn_node_kind_t node_kind,
1836 svn_revnum_t copyfrom_rev,
1837 const char *copyfrom_path,
1838 apr_pool_t *scratch_pool)
1841 svn_fs_x__change_t change;
1842 apr_hash_t *changes = apr_hash_make(scratch_pool);
1844 /* Not using APR_BUFFERED to append change in one atomic write operation. */
1845 SVN_ERR(svn_io_file_open(&file,
1846 svn_fs_x__path_txn_changes(fs, txn_id,
1848 APR_APPEND | APR_WRITE | APR_CREATE,
1849 APR_OS_DEFAULT, scratch_pool));
1851 change.path.data = path;
1852 change.path.len = strlen(path);
1853 change.noderev_id = *id;
1854 change.change_kind = change_kind;
1855 change.text_mod = text_mod;
1856 change.prop_mod = prop_mod;
1857 change.mergeinfo_mod = mergeinfo_mod ? svn_tristate_true
1858 : svn_tristate_false;
1859 change.node_kind = node_kind;
1860 change.copyfrom_known = TRUE;
1861 change.copyfrom_rev = copyfrom_rev;
1863 change.copyfrom_path = apr_pstrdup(scratch_pool, copyfrom_path);
1865 svn_hash_sets(changes, path, &change);
1866 SVN_ERR(svn_fs_x__write_changes(svn_stream_from_aprfile2(file, TRUE,
1868 fs, changes, FALSE, scratch_pool));
1870 return svn_io_file_close(file, scratch_pool);
1873 /* This baton is used by the representation writing streams. It keeps
1874 track of the checksum information as well as the total size of the
1875 representation so far. */
1876 typedef struct rep_write_baton_t
1878 /* The FS we are writing to. */
1881 /* Actual file to which we are writing. */
1882 svn_stream_t *rep_stream;
1884 /* A stream from the delta combiner. Data written here gets
1885 deltified, then eventually written to rep_stream. */
1886 svn_stream_t *delta_stream;
1888 /* Where is this representation header stored. */
1889 apr_off_t rep_offset;
1891 /* Start of the actual data. */
1892 apr_off_t delta_start;
1894 /* How many bytes have been written to this rep already. */
1895 svn_filesize_t rep_size;
1897 /* The node revision for which we're writing out info. */
1898 svn_fs_x__noderev_t *noderev;
1900 /* Actual output file. */
1902 /* Lock 'cookie' used to unlock the output file once we've finished
1906 svn_checksum_ctx_t *md5_checksum_ctx;
1907 svn_checksum_ctx_t *sha1_checksum_ctx;
1909 /* Receives the low-level checksum when closing REP_STREAM. */
1910 apr_uint32_t fnv1a_checksum;
1912 /* Local pool, available for allocations that must remain valid as long
1913 as this baton is used but may be cleaned up immediately afterwards. */
1914 apr_pool_t *local_pool;
1916 /* Outer / result pool. */
1917 apr_pool_t *result_pool;
1918 } rep_write_baton_t;
1920 /* Handler for the write method of the representation writable stream.
1921 BATON is a rep_write_baton_t, DATA is the data to write, and *LEN is
1922 the length of this data. */
1923 static svn_error_t *
1924 rep_write_contents(void *baton,
1928 rep_write_baton_t *b = baton;
1930 SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len));
1931 SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len));
1932 b->rep_size += *len;
1934 return svn_stream_write(b->delta_stream, data, len);
1937 /* Set *SPANNED to the number of shards touched when walking WALK steps on
1938 * NODEREV's predecessor chain in FS.
1939 * Use SCRATCH_POOL for temporary allocations.
1941 static svn_error_t *
1942 shards_spanned(int *spanned,
1944 svn_fs_x__noderev_t *noderev,
1946 apr_pool_t *scratch_pool)
1948 svn_fs_x__data_t *ffd = fs->fsap_data;
1949 int shard_size = ffd->max_files_per_dir;
1950 apr_pool_t *iterpool;
1952 int count = walk ? 1 : 0; /* The start of a walk already touches a shard. */
1953 svn_revnum_t shard, last_shard = ffd->youngest_rev_cache / shard_size;
1954 iterpool = svn_pool_create(scratch_pool);
1955 while (walk-- && noderev->predecessor_count)
1957 svn_fs_x__id_t id = noderev->predecessor_id;
1959 svn_pool_clear(iterpool);
1960 SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, &id, scratch_pool,
1962 shard = svn_fs_x__get_revnum(id.change_set) / shard_size;
1963 if (shard != last_shard)
1969 svn_pool_destroy(iterpool);
1972 return SVN_NO_ERROR;
1975 /* Given a node-revision NODEREV in filesystem FS, return the
1976 representation in *REP to use as the base for a text representation
1977 delta if PROPS is FALSE. If PROPS has been set, a suitable props
1978 base representation will be returned. Perform temporary allocations
1980 static svn_error_t *
1981 choose_delta_base(svn_fs_x__representation_t **rep,
1983 svn_fs_x__noderev_t *noderev,
1984 svn_boolean_t props,
1987 /* The zero-based index (counting from the "oldest" end), along NODEREVs line
1988 * predecessors, of the node-rev we will use as delta base. */
1990 /* The length of the linear part of a delta chain. (Delta chains use
1991 * skip-delta bits for the high-order bits and are linear in the low-order
1994 svn_fs_x__noderev_t *base;
1995 svn_fs_x__data_t *ffd = fs->fsap_data;
1996 apr_pool_t *iterpool;
1998 /* If we have no predecessors, or that one is empty, then use the empty
1999 * stream as a base. */
2000 if (! noderev->predecessor_count)
2003 return SVN_NO_ERROR;
2006 /* Flip the rightmost '1' bit of the predecessor count to determine
2007 which file rev (counting from 0) we want to use. (To see why
2008 count & (count - 1) unsets the rightmost set bit, think about how
2009 you decrement a binary number.) */
2010 count = noderev->predecessor_count;
2011 count = count & (count - 1);
2013 /* Finding the delta base over a very long distance can become extremely
2014 expensive for very deep histories, possibly causing client timeouts etc.
2015 OTOH, this is a rare operation and its gains are minimal. Lets simply
2016 start deltification anew close every other 1000 changes or so. */
2017 walk = noderev->predecessor_count - count;
2018 if (walk > (int)ffd->max_deltification_walk)
2021 return SVN_NO_ERROR;
2024 /* We use skip delta for limiting the number of delta operations
2025 along very long node histories. Close to HEAD however, we create
2026 a linear history to minimize delta size. */
2027 if (walk < (int)ffd->max_linear_deltification)
2030 SVN_ERR(shards_spanned(&shards, fs, noderev, walk, pool));
2032 /* We also don't want the linear deltification to span more shards
2033 than if deltas we used in a simple skip-delta scheme. */
2034 if ((1 << (--shards)) <= walk)
2035 count = noderev->predecessor_count - 1;
2038 /* Walk back a number of predecessors equal to the difference
2039 between count and the original predecessor count. (For example,
2040 if noderev has ten predecessors and we want the eighth file rev,
2041 walk back two predecessors.) */
2043 iterpool = svn_pool_create(pool);
2044 while ((count++) < noderev->predecessor_count)
2046 svn_fs_x__id_t id = noderev->predecessor_id;
2047 svn_pool_clear(iterpool);
2048 SVN_ERR(svn_fs_x__get_node_revision(&base, fs, &id, pool, iterpool));
2050 svn_pool_destroy(iterpool);
2052 /* return a suitable base representation */
2053 *rep = props ? base->prop_rep : base->data_rep;
2055 /* if we encountered a shared rep, its parent chain may be different
2056 * from the node-rev parent chain. */
2059 int chain_length = 0;
2060 int shard_count = 0;
2062 /* Very short rep bases are simply not worth it as we are unlikely
2063 * to re-coup the deltification space overhead of 20+ bytes. */
2064 svn_filesize_t rep_size = (*rep)->expanded_size
2065 ? (*rep)->expanded_size
2070 return SVN_NO_ERROR;
2073 /* Check whether the length of the deltification chain is acceptable.
2074 * Otherwise, shared reps may form a non-skipping delta chain in
2076 SVN_ERR(svn_fs_x__rep_chain_length(&chain_length, &shard_count,
2079 /* Some reasonable limit, depending on how acceptable longer linear
2080 * chains are in this repo. Also, allow for some minimal chain. */
2081 if (chain_length >= 2 * (int)ffd->max_linear_deltification + 2)
2084 /* To make it worth opening additional shards / pack files, we
2085 * require that the reps have a certain minimal size. To deltify
2086 * against a rep in different shard, the lower limit is 512 bytes
2087 * and doubles with every extra shard to visit along the delta
2089 if ( shard_count > 1
2090 && ((svn_filesize_t)128 << shard_count) >= rep_size)
2094 return SVN_NO_ERROR;
2097 /* Something went wrong and the pool for the rep write is being
2098 cleared before we've finished writing the rep. So we need
2099 to remove the rep from the protorevfile and we need to unlock
2100 the protorevfile. */
2102 rep_write_cleanup(void *data)
2105 rep_write_baton_t *b = data;
2106 svn_fs_x__txn_id_t txn_id
2107 = svn_fs_x__get_txn_id(b->noderev->noderev_id.change_set);
2109 /* Truncate and close the protorevfile. */
2110 err = svn_io_file_trunc(b->file, b->rep_offset, b->local_pool);
2111 err = svn_error_compose_create(err, svn_io_file_close(b->file,
2114 /* Remove our lock regardless of any preceding errors so that the
2115 being_written flag is always removed and stays consistent with the
2116 file lock which will be removed no matter what since the pool is
2118 err = svn_error_compose_create(err,
2119 unlock_proto_rev(b->fs, txn_id,
2124 apr_status_t rc = err->apr_err;
2125 svn_error_clear(err);
2132 /* Get a rep_write_baton_t, allocated from RESULT_POOL, and store it in
2133 WB_P for the representation indicated by NODEREV in filesystem FS.
2134 Only appropriate for file contents, not for props or directory contents.
2136 static svn_error_t *
2137 rep_write_get_baton(rep_write_baton_t **wb_p,
2139 svn_fs_x__noderev_t *noderev,
2140 apr_pool_t *result_pool)
2142 svn_fs_x__data_t *ffd = fs->fsap_data;
2143 rep_write_baton_t *b;
2145 svn_fs_x__representation_t *base_rep;
2146 svn_stream_t *source;
2147 svn_txdelta_window_handler_t wh;
2149 int diff_version = 1;
2150 svn_fs_x__rep_header_t header = { 0 };
2151 svn_fs_x__txn_id_t txn_id
2152 = svn_fs_x__get_txn_id(noderev->noderev_id.change_set);
2154 b = apr_pcalloc(result_pool, sizeof(*b));
2156 b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1,
2158 b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5,
2162 b->result_pool = result_pool;
2163 b->local_pool = svn_pool_create(result_pool);
2165 b->noderev = noderev;
2167 /* Open the prototype rev file and seek to its end. */
2168 SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie, fs, txn_id,
2172 b->rep_stream = svn_checksum__wrap_write_stream_fnv1a_32x4(
2174 svn_stream_from_aprfile2(file, TRUE,
2178 SVN_ERR(svn_fs_x__get_file_offset(&b->rep_offset, file, b->local_pool));
2180 /* Get the base for this delta. */
2181 SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->local_pool));
2182 SVN_ERR(svn_fs_x__get_contents(&source, fs, base_rep, TRUE,
2185 /* Write out the rep header. */
2188 header.base_revision = svn_fs_x__get_revnum(base_rep->id.change_set);
2189 header.base_item_index = base_rep->id.number;
2190 header.base_length = base_rep->size;
2191 header.type = svn_fs_x__rep_delta;
2195 header.type = svn_fs_x__rep_self_delta;
2197 SVN_ERR(svn_fs_x__write_rep_header(&header, b->rep_stream,
2200 /* Now determine the offset of the actual svndiff data. */
2201 SVN_ERR(svn_fs_x__get_file_offset(&b->delta_start, file,
2204 /* Cleanup in case something goes wrong. */
2205 apr_pool_cleanup_register(b->local_pool, b, rep_write_cleanup,
2206 apr_pool_cleanup_null);
2208 /* Prepare to write the svndiff data. */
2209 svn_txdelta_to_svndiff3(&wh,
2211 svn_stream_disown(b->rep_stream, b->result_pool),
2213 ffd->delta_compression_level,
2216 b->delta_stream = svn_txdelta_target_push(wh, whb, source,
2221 return SVN_NO_ERROR;
2224 /* For REP->SHA1_CHECKSUM, try to find an already existing representation
2225 in FS and return it in *OUT_REP. If no such representation exists or
2226 if rep sharing has been disabled for FS, NULL will be returned. Since
2227 there may be new duplicate representations within the same uncommitted
2228 revision, those can be passed in REPS_HASH (maps a sha1 digest onto
2229 svn_fs_x__representation_t*), otherwise pass in NULL for REPS_HASH.
2230 Use RESULT_POOL for *OLD_REP allocations and SCRATCH_POOL for temporaries.
2231 The lifetime of *OLD_REP is limited by both, RESULT_POOL and REP lifetime.
2233 static svn_error_t *
2234 get_shared_rep(svn_fs_x__representation_t **old_rep,
2236 svn_fs_x__representation_t *rep,
2237 apr_hash_t *reps_hash,
2238 apr_pool_t *result_pool,
2239 apr_pool_t *scratch_pool)
2242 svn_fs_x__data_t *ffd = fs->fsap_data;
2244 /* Return NULL, if rep sharing has been disabled. */
2246 if (!ffd->rep_sharing_allowed)
2247 return SVN_NO_ERROR;
2249 /* Check and see if we already have a representation somewhere that's
2250 identical to the one we just wrote out. Start with the hash lookup
2251 because it is cheepest. */
2253 *old_rep = apr_hash_get(reps_hash,
2255 APR_SHA1_DIGESTSIZE);
2257 /* If we haven't found anything yet, try harder and consult our DB. */
2258 if (*old_rep == NULL)
2260 svn_checksum_t checksum;
2261 checksum.digest = rep->sha1_digest;
2262 checksum.kind = svn_checksum_sha1;
2263 err = svn_fs_x__get_rep_reference(old_rep, fs, &checksum, result_pool,
2266 /* ### Other error codes that we shouldn't mask out? */
2267 if (err == SVN_NO_ERROR)
2270 SVN_ERR(svn_fs_x__check_rep(*old_rep, fs, scratch_pool));
2272 else if (err->apr_err == SVN_ERR_FS_CORRUPT
2273 || SVN_ERROR_IN_CATEGORY(err->apr_err,
2274 SVN_ERR_MALFUNC_CATEGORY_START))
2276 /* Fatal error; don't mask it.
2278 In particular, this block is triggered when the rep-cache refers
2279 to revisions in the future. We signal that as a corruption situation
2280 since, once those revisions are less than youngest (because of more
2281 commits), the rep-cache would be invalid.
2287 /* Something's wrong with the rep-sharing index. We can continue
2288 without rep-sharing, but warn.
2290 (fs->warning)(fs->warning_baton, err);
2291 svn_error_clear(err);
2296 /* look for intra-revision matches (usually data reps but not limited
2297 to them in case props happen to look like some data rep)
2299 if (*old_rep == NULL && svn_fs_x__is_txn(rep->id.change_set))
2301 svn_node_kind_t kind;
2302 const char *file_name
2303 = svn_fs_x__path_txn_sha1(fs,
2304 svn_fs_x__get_txn_id(rep->id.change_set),
2305 rep->sha1_digest, scratch_pool);
2307 /* in our txn, is there a rep file named with the wanted SHA1?
2308 If so, read it and use that rep.
2310 SVN_ERR(svn_io_check_path(file_name, &kind, scratch_pool));
2311 if (kind == svn_node_file)
2313 svn_stringbuf_t *rep_string;
2314 SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name,
2316 SVN_ERR(svn_fs_x__parse_representation(old_rep, rep_string,
2317 result_pool, scratch_pool));
2321 /* Add information that is missing in the cached data. */
2324 /* Use the old rep for this content. */
2325 memcpy((*old_rep)->md5_digest, rep->md5_digest, sizeof(rep->md5_digest));
2328 return SVN_NO_ERROR;
2331 /* Copy the hash sum calculation results from MD5_CTX, SHA1_CTX into REP.
2332 * Use SCRATCH_POOL for temporary allocations.
2334 static svn_error_t *
2335 digests_final(svn_fs_x__representation_t *rep,
2336 const svn_checksum_ctx_t *md5_ctx,
2337 const svn_checksum_ctx_t *sha1_ctx,
2338 apr_pool_t *scratch_pool)
2340 svn_checksum_t *checksum;
2342 SVN_ERR(svn_checksum_final(&checksum, md5_ctx, scratch_pool));
2343 memcpy(rep->md5_digest, checksum->digest, svn_checksum_size(checksum));
2344 SVN_ERR(svn_checksum_final(&checksum, sha1_ctx, scratch_pool));
2345 rep->has_sha1 = checksum != NULL;
2347 memcpy(rep->sha1_digest, checksum->digest, svn_checksum_size(checksum));
2349 return SVN_NO_ERROR;
2352 /* Close handler for the representation write stream. BATON is a
2353 rep_write_baton_t. Writes out a new node-rev that correctly
2354 references the representation we just finished writing. */
2355 static svn_error_t *
2356 rep_write_contents_close(void *baton)
2358 rep_write_baton_t *b = baton;
2359 svn_fs_x__representation_t *rep;
2360 svn_fs_x__representation_t *old_rep;
2364 rep = apr_pcalloc(b->result_pool, sizeof(*rep));
2366 /* Close our delta stream so the last bits of svndiff are written
2368 SVN_ERR(svn_stream_close(b->delta_stream));
2370 /* Determine the length of the svndiff data. */
2371 SVN_ERR(svn_fs_x__get_file_offset(&offset, b->file, b->local_pool));
2372 rep->size = offset - b->delta_start;
2374 /* Fill in the rest of the representation field. */
2375 rep->expanded_size = b->rep_size;
2376 txn_id = svn_fs_x__get_txn_id(b->noderev->noderev_id.change_set);
2377 rep->id.change_set = svn_fs_x__change_set_by_txn(txn_id);
2379 /* Finalize the checksum. */
2380 SVN_ERR(digests_final(rep, b->md5_checksum_ctx, b->sha1_checksum_ctx,
2383 /* Check and see if we already have a representation somewhere that's
2384 identical to the one we just wrote out. */
2385 SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->result_pool,
2390 /* We need to erase from the protorev the data we just wrote. */
2391 SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->local_pool));
2393 /* Use the old rep for this content. */
2394 b->noderev->data_rep = old_rep;
2398 /* Write out our cosmetic end marker. */
2399 SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n"));
2400 SVN_ERR(allocate_item_index(&rep->id.number, b->fs, txn_id,
2402 SVN_ERR(store_l2p_index_entry(b->fs, txn_id, b->rep_offset,
2403 rep->id.number, b->local_pool));
2405 b->noderev->data_rep = rep;
2408 SVN_ERR(svn_stream_close(b->rep_stream));
2410 /* Remove cleanup callback. */
2411 apr_pool_cleanup_kill(b->local_pool, b, rep_write_cleanup);
2413 /* Write out the new node-rev information. */
2414 SVN_ERR(svn_fs_x__put_node_revision(b->fs, b->noderev, b->local_pool));
2417 svn_fs_x__p2l_entry_t entry;
2418 svn_fs_x__id_t noderev_id;
2419 noderev_id.change_set = SVN_FS_X__INVALID_CHANGE_SET;
2420 noderev_id.number = rep->id.number;
2422 entry.offset = b->rep_offset;
2423 SVN_ERR(svn_fs_x__get_file_offset(&offset, b->file, b->local_pool));
2424 entry.size = offset - b->rep_offset;
2425 entry.type = SVN_FS_X__ITEM_TYPE_FILE_REP;
2426 entry.item_count = 1;
2427 entry.items = &noderev_id;
2428 entry.fnv1_checksum = b->fnv1a_checksum;
2430 SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->local_pool));
2431 SVN_ERR(store_p2l_index_entry(b->fs, txn_id, &entry, b->local_pool));
2434 SVN_ERR(svn_io_file_close(b->file, b->local_pool));
2435 SVN_ERR(unlock_proto_rev(b->fs, txn_id, b->lockcookie, b->local_pool));
2436 svn_pool_destroy(b->local_pool);
2438 return SVN_NO_ERROR;
2441 /* Store a writable stream in *CONTENTS_P, allocated in RESULT_POOL, that
2442 will receive all data written and store it as the file data representation
2443 referenced by NODEREV in filesystem FS. Only appropriate for file data,
2444 not props or directory contents. */
2445 static svn_error_t *
2446 set_representation(svn_stream_t **contents_p,
2448 svn_fs_x__noderev_t *noderev,
2449 apr_pool_t *result_pool)
2451 rep_write_baton_t *wb;
2453 if (! svn_fs_x__is_txn(noderev->noderev_id.change_set))
2454 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2455 _("Attempted to write to non-transaction '%s'"),
2456 svn_fs_x__id_unparse(&noderev->noderev_id,
2457 result_pool)->data);
2459 SVN_ERR(rep_write_get_baton(&wb, fs, noderev, result_pool));
2461 *contents_p = svn_stream_create(wb, result_pool);
2462 svn_stream_set_write(*contents_p, rep_write_contents);
2463 svn_stream_set_close(*contents_p, rep_write_contents_close);
2465 return SVN_NO_ERROR;
2469 svn_fs_x__set_contents(svn_stream_t **stream,
2471 svn_fs_x__noderev_t *noderev,
2472 apr_pool_t *result_pool)
2474 if (noderev->kind != svn_node_file)
2475 return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
2476 _("Can't set text contents of a directory"));
2478 return set_representation(stream, fs, noderev, result_pool);
2482 svn_fs_x__create_successor(svn_fs_t *fs,
2483 svn_fs_x__noderev_t *new_noderev,
2484 const svn_fs_x__id_t *copy_id,
2485 svn_fs_x__txn_id_t txn_id,
2486 apr_pool_t *scratch_pool)
2488 new_noderev->copy_id = *copy_id;
2489 new_noderev->noderev_id.change_set = svn_fs_x__change_set_by_txn(txn_id);
2490 SVN_ERR(allocate_item_index(&new_noderev->noderev_id.number, fs, txn_id,
2493 if (! new_noderev->copyroot_path)
2495 new_noderev->copyroot_path
2496 = apr_pstrdup(scratch_pool, new_noderev->created_path);
2497 new_noderev->copyroot_rev
2498 = svn_fs_x__get_revnum(new_noderev->noderev_id.change_set);
2501 SVN_ERR(svn_fs_x__put_node_revision(fs, new_noderev, scratch_pool));
2503 return SVN_NO_ERROR;
2507 svn_fs_x__set_proplist(svn_fs_t *fs,
2508 svn_fs_x__noderev_t *noderev,
2509 apr_hash_t *proplist,
2510 apr_pool_t *scratch_pool)
2512 const svn_fs_x__id_t *id = &noderev->noderev_id;
2513 const char *filename = svn_fs_x__path_txn_node_props(fs, id, scratch_pool,
2518 /* Dump the property list to the mutable property file. */
2519 SVN_ERR(svn_io_file_open(&file, filename,
2520 APR_WRITE | APR_CREATE | APR_TRUNCATE
2521 | APR_BUFFERED, APR_OS_DEFAULT, scratch_pool));
2522 out = svn_stream_from_aprfile2(file, TRUE, scratch_pool);
2523 SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, scratch_pool));
2524 SVN_ERR(svn_io_file_close(file, scratch_pool));
2526 /* Mark the node-rev's prop rep as mutable, if not already done. */
2527 if (!noderev->prop_rep
2528 || svn_fs_x__is_revision(noderev->prop_rep->id.change_set))
2530 svn_fs_x__txn_id_t txn_id
2531 = svn_fs_x__get_txn_id(noderev->noderev_id.change_set);
2532 noderev->prop_rep = apr_pcalloc(scratch_pool, sizeof(*noderev->prop_rep));
2533 noderev->prop_rep->id.change_set = id->change_set;
2534 SVN_ERR(allocate_item_index(&noderev->prop_rep->id.number, fs,
2535 txn_id, scratch_pool));
2536 SVN_ERR(svn_fs_x__put_node_revision(fs, noderev, scratch_pool));
2539 return SVN_NO_ERROR;
2542 /* This baton is used by the stream created for write_container_rep. */
2543 typedef struct write_container_baton_t
2545 svn_stream_t *stream;
2549 svn_checksum_ctx_t *md5_ctx;
2550 svn_checksum_ctx_t *sha1_ctx;
2551 } write_container_baton_t;
2553 /* The handler for the write_container_rep stream. BATON is a
2554 write_container_baton_t, DATA has the data to write and *LEN is the number
2555 of bytes to write. */
2556 static svn_error_t *
2557 write_container_handler(void *baton,
2561 write_container_baton_t *whb = baton;
2563 SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
2564 SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
2566 SVN_ERR(svn_stream_write(whb->stream, data, len));
2569 return SVN_NO_ERROR;
2572 /* Callback function type. Write the data provided by BATON into STREAM. */
2573 typedef svn_error_t *
2574 (* collection_writer_t)(svn_stream_t *stream,
2576 apr_pool_t *scratch_pool);
2578 /* Implement collection_writer_t writing the C string->svn_string_t hash
2580 static svn_error_t *
2581 write_hash_to_stream(svn_stream_t *stream,
2583 apr_pool_t *scratch_pool)
2585 apr_hash_t *hash = baton;
2586 SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, scratch_pool));
2588 return SVN_NO_ERROR;
2591 /* Implement collection_writer_t writing the svn_fs_x__dirent_t* array given
2593 static svn_error_t *
2594 write_directory_to_stream(svn_stream_t *stream,
2596 apr_pool_t *scratch_pool)
2598 apr_array_header_t *dir = baton;
2599 SVN_ERR(unparse_dir_entries(dir, stream, scratch_pool));
2601 return SVN_NO_ERROR;
2605 /* Write out the COLLECTION pertaining to the NODEREV in FS as a deltified
2606 text representation to file FILE using WRITER. In the process, record the
2607 total size and the md5 digest in REP and add the representation of type
2608 ITEM_TYPE to the indexes if necessary. If rep sharing has been enabled and
2609 REPS_HASH is not NULL, it will be used in addition to the on-disk cache to
2610 find earlier reps with the same content. When such existing reps can be
2611 found, we will truncate the one just written from the file and return the
2614 If ITEM_TYPE is IS_PROPS equals SVN_FS_FS__ITEM_TYPE_*_PROPS, assume
2615 that we want to a props representation as the base for our delta.
2616 If FINAL_REVISION is not SVN_INVALID_REVNUM, use it to determine whether
2617 to write to the proto-index files.
2618 Perform temporary allocations in SCRATCH_POOL.
2620 static svn_error_t *
2621 write_container_delta_rep(svn_fs_x__representation_t *rep,
2624 collection_writer_t writer,
2626 svn_fs_x__txn_id_t txn_id,
2627 svn_fs_x__noderev_t *noderev,
2628 apr_hash_t *reps_hash,
2629 apr_uint32_t item_type,
2630 svn_revnum_t final_revision,
2631 apr_pool_t *scratch_pool)
2633 svn_fs_x__data_t *ffd = fs->fsap_data;
2634 svn_txdelta_window_handler_t diff_wh;
2637 svn_stream_t *file_stream;
2638 svn_stream_t *stream;
2639 svn_fs_x__representation_t *base_rep;
2640 svn_fs_x__representation_t *old_rep;
2641 svn_fs_x__p2l_entry_t entry;
2642 svn_stream_t *source;
2643 svn_fs_x__rep_header_t header = { 0 };
2645 apr_off_t rep_end = 0;
2646 apr_off_t delta_start = 0;
2647 apr_off_t offset = 0;
2649 write_container_baton_t *whb;
2650 int diff_version = 1;
2651 svn_boolean_t is_props = (item_type == SVN_FS_X__ITEM_TYPE_FILE_PROPS)
2652 || (item_type == SVN_FS_X__ITEM_TYPE_DIR_PROPS);
2654 /* Get the base for this delta. */
2655 SVN_ERR(choose_delta_base(&base_rep, fs, noderev, is_props, scratch_pool));
2656 SVN_ERR(svn_fs_x__get_contents(&source, fs, base_rep, FALSE, scratch_pool));
2658 SVN_ERR(svn_fs_x__get_file_offset(&offset, file, scratch_pool));
2660 /* Write out the rep header. */
2663 header.base_revision = svn_fs_x__get_revnum(base_rep->id.change_set);
2664 header.base_item_index = base_rep->id.number;
2665 header.base_length = base_rep->size;
2666 header.type = svn_fs_x__rep_delta;
2670 header.type = svn_fs_x__rep_self_delta;
2673 file_stream = svn_checksum__wrap_write_stream_fnv1a_32x4(
2674 &entry.fnv1_checksum,
2675 svn_stream_from_aprfile2(file, TRUE,
2678 SVN_ERR(svn_fs_x__write_rep_header(&header, file_stream, scratch_pool));
2679 SVN_ERR(svn_fs_x__get_file_offset(&delta_start, file, scratch_pool));
2681 /* Prepare to write the svndiff data. */
2682 svn_txdelta_to_svndiff3(&diff_wh,
2684 svn_stream_disown(file_stream, scratch_pool),
2686 ffd->delta_compression_level,
2689 whb = apr_pcalloc(scratch_pool, sizeof(*whb));
2690 whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source,
2693 whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool);
2694 whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool);
2696 /* serialize the hash */
2697 stream = svn_stream_create(whb, scratch_pool);
2698 svn_stream_set_write(stream, write_container_handler);
2700 SVN_ERR(writer(stream, collection, scratch_pool));
2701 SVN_ERR(svn_stream_close(whb->stream));
2703 /* Store the results. */
2704 SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool));
2706 /* Check and see if we already have a representation somewhere that's
2707 identical to the one we just wrote out. */
2708 SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, scratch_pool,
2713 SVN_ERR(svn_stream_close(file_stream));
2715 /* We need to erase from the protorev the data we just wrote. */
2716 SVN_ERR(svn_io_file_trunc(file, offset, scratch_pool));
2718 /* Use the old rep for this content. */
2719 memcpy(rep, old_rep, sizeof (*rep));
2723 svn_fs_x__id_t noderev_id;
2725 /* Write out our cosmetic end marker. */
2726 SVN_ERR(svn_fs_x__get_file_offset(&rep_end, file, scratch_pool));
2727 SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n"));
2728 SVN_ERR(svn_stream_close(file_stream));
2730 SVN_ERR(allocate_item_index(&rep->id.number, fs, txn_id,
2732 SVN_ERR(store_l2p_index_entry(fs, txn_id, offset, rep->id.number,
2735 noderev_id.change_set = SVN_FS_X__INVALID_CHANGE_SET;
2736 noderev_id.number = rep->id.number;
2738 entry.offset = offset;
2739 SVN_ERR(svn_fs_x__get_file_offset(&offset, file, scratch_pool));
2740 entry.size = offset - entry.offset;
2741 entry.type = item_type;
2742 entry.item_count = 1;
2743 entry.items = &noderev_id;
2745 SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, scratch_pool));
2747 /* update the representation */
2748 rep->expanded_size = whb->size;
2749 rep->size = rep_end - delta_start;
2752 return SVN_NO_ERROR;
2755 /* Sanity check ROOT_NODEREV, a candidate for being the root node-revision
2756 of (not yet committed) revision REV in FS. Use SCRATCH_POOL for temporary
2759 If you change this function, consider updating svn_fs_x__verify() too.
2761 static svn_error_t *
2762 validate_root_noderev(svn_fs_t *fs,
2763 svn_fs_x__noderev_t *root_noderev,
2765 apr_pool_t *scratch_pool)
2767 svn_revnum_t head_revnum = rev-1;
2768 int head_predecessor_count;
2770 SVN_ERR_ASSERT(rev > 0);
2772 /* Compute HEAD_PREDECESSOR_COUNT. */
2774 svn_fs_x__id_t head_root_id;
2775 svn_fs_x__noderev_t *head_root_noderev;
2777 /* Get /@HEAD's noderev. */
2778 svn_fs_x__init_rev_root(&head_root_id, head_revnum);
2779 SVN_ERR(svn_fs_x__get_node_revision(&head_root_noderev, fs,
2780 &head_root_id, scratch_pool,
2783 head_predecessor_count = head_root_noderev->predecessor_count;
2786 /* Check that the root noderev's predecessor count equals REV.
2788 This kind of corruption was seen on svn.apache.org (both on
2789 the root noderev and on other fspaths' noderevs); see
2792 Normally (rev == root_noderev->predecessor_count), but here we
2793 use a more roundabout check that should only trigger on new instances
2794 of the corruption, rather then trigger on each and every new commit
2795 to a repository that has triggered the bug somewhere in its root
2798 if ((root_noderev->predecessor_count - head_predecessor_count)
2799 != (rev - head_revnum))
2801 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2802 _("predecessor count for "
2803 "the root node-revision is wrong: "
2804 "found (%d+%ld != %d), committing r%ld"),
2805 head_predecessor_count,
2806 rev - head_revnum, /* This is equal to 1. */
2807 root_noderev->predecessor_count,
2811 return SVN_NO_ERROR;
2814 /* Given the potentially txn-local id PART, update that to a permanent ID
2815 * based on the REVISION.
2818 get_final_id(svn_fs_x__id_t *part,
2819 svn_revnum_t revision)
2821 if (!svn_fs_x__is_revision(part->change_set))
2822 part->change_set = svn_fs_x__change_set_by_rev(revision);
2825 /* Copy a node-revision specified by id ID in fileystem FS from a
2826 transaction into the proto-rev-file FILE. Set *NEW_ID_P to a
2827 pointer to the new noderev-id. If this is a directory, copy all
2830 START_NODE_ID and START_COPY_ID are
2831 the first available node and copy ids for this filesystem, for older
2834 REV is the revision number that this proto-rev-file will represent.
2836 INITIAL_OFFSET is the offset of the proto-rev-file on entry to
2839 If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
2840 REPS_POOL) of each data rep that is new in this revision.
2842 If REPS_HASH is not NULL, append copies (allocated in REPS_POOL)
2843 of the representations of each property rep that is new in this
2846 AT_ROOT is true if the node revision being written is the root
2847 node-revision. It is only controls additional sanity checking
2850 Temporary allocations are also from SCRATCH_POOL. */
2851 static svn_error_t *
2852 write_final_rev(svn_fs_x__id_t *new_id_p,
2856 const svn_fs_x__id_t *id,
2857 apr_off_t initial_offset,
2858 apr_array_header_t *reps_to_cache,
2859 apr_hash_t *reps_hash,
2860 apr_pool_t *reps_pool,
2861 svn_boolean_t at_root,
2862 apr_pool_t *scratch_pool)
2864 svn_fs_x__noderev_t *noderev;
2865 apr_off_t my_offset;
2866 svn_fs_x__id_t new_id;
2867 svn_fs_x__id_t noderev_id;
2868 svn_fs_x__data_t *ffd = fs->fsap_data;
2869 svn_fs_x__txn_id_t txn_id = svn_fs_x__get_txn_id(id->change_set);
2870 svn_fs_x__p2l_entry_t entry;
2871 svn_fs_x__change_set_t change_set = svn_fs_x__change_set_by_rev(rev);
2872 svn_stream_t *file_stream;
2873 apr_pool_t *subpool;
2875 /* Check to see if this is a transaction node. */
2876 if (txn_id == SVN_FS_X__INVALID_TXN_ID)
2878 svn_fs_x__id_reset(new_id_p);
2879 return SVN_NO_ERROR;
2882 subpool = svn_pool_create(scratch_pool);
2883 SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, id, scratch_pool,
2886 if (noderev->kind == svn_node_dir)
2888 apr_array_header_t *entries;
2891 /* This is a directory. Write out all the children first. */
2893 SVN_ERR(svn_fs_x__rep_contents_dir(&entries, fs, noderev, scratch_pool,
2895 for (i = 0; i < entries->nelts; ++i)
2897 svn_fs_x__dirent_t *dirent = APR_ARRAY_IDX(entries, i,
2898 svn_fs_x__dirent_t *);
2900 svn_pool_clear(subpool);
2901 SVN_ERR(write_final_rev(&new_id, file, rev, fs, &dirent->id,
2902 initial_offset, reps_to_cache, reps_hash,
2903 reps_pool, FALSE, subpool));
2904 if ( svn_fs_x__id_used(&new_id)
2905 && (svn_fs_x__get_revnum(new_id.change_set) == rev))
2906 dirent->id = new_id;
2909 if (noderev->data_rep
2910 && ! svn_fs_x__is_revision(noderev->data_rep->id.change_set))
2912 /* Write out the contents of this directory as a text rep. */
2913 noderev->data_rep->id.change_set = change_set;
2914 SVN_ERR(write_container_delta_rep(noderev->data_rep, file,
2916 write_directory_to_stream,
2917 fs, txn_id, noderev, NULL,
2918 SVN_FS_X__ITEM_TYPE_DIR_REP,
2919 rev, scratch_pool));
2924 /* This is a file. We should make sure the data rep, if it
2925 exists in a "this" state, gets rewritten to our new revision
2928 if (noderev->data_rep
2929 && svn_fs_x__is_txn(noderev->data_rep->id.change_set))
2931 noderev->data_rep->id.change_set = change_set;
2935 svn_pool_destroy(subpool);
2937 /* Fix up the property reps. */
2938 if (noderev->prop_rep
2939 && svn_fs_x__is_txn(noderev->prop_rep->id.change_set))
2941 apr_hash_t *proplist;
2942 apr_uint32_t item_type = noderev->kind == svn_node_dir
2943 ? SVN_FS_X__ITEM_TYPE_DIR_PROPS
2944 : SVN_FS_X__ITEM_TYPE_FILE_PROPS;
2945 SVN_ERR(svn_fs_x__get_proplist(&proplist, fs, noderev, scratch_pool,
2948 noderev->prop_rep->id.change_set = change_set;
2950 SVN_ERR(write_container_delta_rep(noderev->prop_rep, file, proplist,
2951 write_hash_to_stream, fs, txn_id,
2952 noderev, reps_hash, item_type, rev,
2956 /* Convert our temporary ID into a permanent revision one. */
2957 get_final_id(&noderev->node_id, rev);
2958 get_final_id(&noderev->copy_id, rev);
2959 get_final_id(&noderev->noderev_id, rev);
2961 if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
2962 noderev->copyroot_rev = rev;
2964 SVN_ERR(svn_fs_x__get_file_offset(&my_offset, file, scratch_pool));
2966 SVN_ERR(store_l2p_index_entry(fs, txn_id, my_offset,
2967 noderev->noderev_id.number, scratch_pool));
2968 new_id = noderev->noderev_id;
2970 if (ffd->rep_sharing_allowed)
2972 /* Save the data representation's hash in the rep cache. */
2973 if ( noderev->data_rep && noderev->kind == svn_node_file
2974 && svn_fs_x__get_revnum(noderev->data_rep->id.change_set) == rev)
2976 SVN_ERR_ASSERT(reps_to_cache && reps_pool);
2977 APR_ARRAY_PUSH(reps_to_cache, svn_fs_x__representation_t *)
2978 = svn_fs_x__rep_copy(noderev->data_rep, reps_pool);
2981 if ( noderev->prop_rep
2982 && svn_fs_x__get_revnum(noderev->prop_rep->id.change_set) == rev)
2984 /* Add new property reps to hash and on-disk cache. */
2985 svn_fs_x__representation_t *copy
2986 = svn_fs_x__rep_copy(noderev->prop_rep, reps_pool);
2988 SVN_ERR_ASSERT(reps_to_cache && reps_pool);
2989 APR_ARRAY_PUSH(reps_to_cache, svn_fs_x__representation_t *) = copy;
2991 apr_hash_set(reps_hash,
2993 APR_SHA1_DIGESTSIZE,
2998 /* don't serialize SHA1 for dirs to disk (waste of space) */
2999 if (noderev->data_rep && noderev->kind == svn_node_dir)
3000 noderev->data_rep->has_sha1 = FALSE;
3002 /* don't serialize SHA1 for props to disk (waste of space) */
3003 if (noderev->prop_rep)
3004 noderev->prop_rep->has_sha1 = FALSE;
3006 /* Write out our new node-revision. */
3008 SVN_ERR(validate_root_noderev(fs, noderev, rev, scratch_pool));
3010 file_stream = svn_checksum__wrap_write_stream_fnv1a_32x4(
3011 &entry.fnv1_checksum,
3012 svn_stream_from_aprfile2(file, TRUE,
3015 SVN_ERR(svn_fs_x__write_noderev(file_stream, noderev, scratch_pool));
3016 SVN_ERR(svn_stream_close(file_stream));
3018 /* reference the root noderev from the log-to-phys index */
3019 noderev_id = noderev->noderev_id;
3020 noderev_id.change_set = SVN_FS_X__INVALID_CHANGE_SET;
3022 entry.offset = my_offset;
3023 SVN_ERR(svn_fs_x__get_file_offset(&my_offset, file, scratch_pool));
3024 entry.size = my_offset - entry.offset;
3025 entry.type = SVN_FS_X__ITEM_TYPE_NODEREV;
3026 entry.item_count = 1;
3027 entry.items = &noderev_id;
3029 SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, scratch_pool));
3031 /* Return our ID that references the revision file. */
3034 return SVN_NO_ERROR;
3037 /* Write the changed path info CHANGED_PATHS from transaction TXN_ID to the
3038 permanent rev-file FILE representing NEW_REV in filesystem FS. *OFFSET_P
3039 is set the to offset in the file of the beginning of this information.
3040 NEW_REV is the revision currently being committed.
3041 Perform temporary allocations in SCRATCH_POOL. */
3042 static svn_error_t *
3043 write_final_changed_path_info(apr_off_t *offset_p,
3046 svn_fs_x__txn_id_t txn_id,
3047 apr_hash_t *changed_paths,
3048 svn_revnum_t new_rev,
3049 apr_pool_t *scratch_pool)
3052 svn_stream_t *stream;
3053 svn_fs_x__p2l_entry_t entry;
3054 svn_fs_x__id_t rev_item
3055 = {SVN_INVALID_REVNUM, SVN_FS_X__ITEM_INDEX_CHANGES};
3057 SVN_ERR(svn_fs_x__get_file_offset(&offset, file, scratch_pool));
3059 /* write to target file & calculate checksum */
3060 stream = svn_checksum__wrap_write_stream_fnv1a_32x4(&entry.fnv1_checksum,
3061 svn_stream_from_aprfile2(file, TRUE, scratch_pool),
3063 SVN_ERR(svn_fs_x__write_changes(stream, fs, changed_paths, TRUE,
3065 SVN_ERR(svn_stream_close(stream));
3069 /* reference changes from the indexes */
3070 entry.offset = offset;
3071 SVN_ERR(svn_fs_x__get_file_offset(&offset, file, scratch_pool));
3072 entry.size = offset - entry.offset;
3073 entry.type = SVN_FS_X__ITEM_TYPE_CHANGES;
3074 entry.item_count = 1;
3075 entry.items = &rev_item;
3077 SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, scratch_pool));
3078 SVN_ERR(store_l2p_index_entry(fs, txn_id, entry.offset,
3079 SVN_FS_X__ITEM_INDEX_CHANGES, scratch_pool));
3081 return SVN_NO_ERROR;
3084 /* Open a new svn_fs_t handle to FS, set that handle's concept of "current
3085 youngest revision" to NEW_REV, and call svn_fs_x__verify_root() on
3086 NEW_REV's revision root.
3088 Intended to be called as the very last step in a commit before 'current'
3089 is bumped. This implies that we are holding the write lock. */
3090 static svn_error_t *
3091 verify_as_revision_before_current_plus_plus(svn_fs_t *fs,
3092 svn_revnum_t new_rev,
3093 apr_pool_t *scratch_pool)
3096 svn_fs_x__data_t *ffd = fs->fsap_data;
3097 svn_fs_t *ft; /* fs++ == ft */
3098 svn_fs_root_t *root;
3099 svn_fs_x__data_t *ft_ffd;
3100 apr_hash_t *fs_config;
3102 SVN_ERR_ASSERT(ffd->svn_fs_open_);
3104 /* make sure FT does not simply return data cached by other instances
3105 * but actually retrieves it from disk at least once.
3107 fs_config = apr_hash_make(scratch_pool);
3108 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
3109 svn_uuid_generate(scratch_pool));
3110 SVN_ERR(ffd->svn_fs_open_(&ft, fs->path,
3114 ft_ffd = ft->fsap_data;
3115 /* Don't let FT consult rep-cache.db, either. */
3116 ft_ffd->rep_sharing_allowed = FALSE;
3119 ft_ffd->youngest_rev_cache = new_rev;
3121 SVN_ERR(svn_fs_x__revision_root(&root, ft, new_rev, scratch_pool));
3122 SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev);
3123 SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev);
3124 SVN_ERR(svn_fs_x__verify_root(root, scratch_pool));
3125 #endif /* SVN_DEBUG */
3127 return SVN_NO_ERROR;
3130 /* Verify that the user registered with FS has all the locks necessary to
3131 permit all the changes associated with TXN_NAME.
3132 The FS write lock is assumed to be held by the caller. */
3133 static svn_error_t *
3134 verify_locks(svn_fs_t *fs,
3135 svn_fs_x__txn_id_t txn_id,
3136 apr_hash_t *changed_paths,
3137 apr_pool_t *scratch_pool)
3139 apr_pool_t *iterpool;
3140 apr_array_header_t *changed_paths_sorted;
3141 svn_stringbuf_t *last_recursed = NULL;
3144 /* Make an array of the changed paths, and sort them depth-first-ily. */
3145 changed_paths_sorted = svn_sort__hash(changed_paths,
3146 svn_sort_compare_items_as_paths,
3149 /* Now, traverse the array of changed paths, verify locks. Note
3150 that if we need to do a recursive verification a path, we'll skip
3151 over children of that path when we get to them. */
3152 iterpool = svn_pool_create(scratch_pool);
3153 for (i = 0; i < changed_paths_sorted->nelts; i++)
3155 const svn_sort__item_t *item;
3157 svn_fs_x__change_t *change;
3158 svn_boolean_t recurse = TRUE;
3160 svn_pool_clear(iterpool);
3162 item = &APR_ARRAY_IDX(changed_paths_sorted, i, svn_sort__item_t);
3164 /* Fetch the change associated with our path. */
3166 change = item->value;
3168 /* If this path has already been verified as part of a recursive
3169 check of one of its parents, no need to do it again. */
3171 && svn_fspath__skip_ancestor(last_recursed->data, path))
3174 /* What does it mean to succeed at lock verification for a given
3175 path? For an existing file or directory getting modified
3176 (text, props), it means we hold the lock on the file or
3177 directory. For paths being added or removed, we need to hold
3178 the locks for that path and any children of that path.
3180 WHEW! We have no reliable way to determine the node kind
3181 of deleted items, but fortunately we are going to do a
3182 recursive check on deleted paths regardless of their kind. */
3183 if (change->change_kind == svn_fs_path_change_modify)
3185 SVN_ERR(svn_fs_x__allow_locked_operation(path, fs, recurse, TRUE,
3188 /* If we just did a recursive check, remember the path we
3189 checked (so children can be skipped). */
3192 if (! last_recursed)
3193 last_recursed = svn_stringbuf_create(path, scratch_pool);
3195 svn_stringbuf_set(last_recursed, path);
3198 svn_pool_destroy(iterpool);
3199 return SVN_NO_ERROR;
3202 /* Return in *PATH the path to a file containing the properties that
3203 make up the final revision properties file. This involves setting
3204 svn:date and removing any temporary properties associated with the
3206 static svn_error_t *
3207 write_final_revprop(const char **path,
3209 svn_fs_x__txn_id_t txn_id,
3212 apr_hash_t *txnprops;
3213 svn_boolean_t final_mods = FALSE;
3215 svn_string_t *client_date;
3217 SVN_ERR(svn_fs_x__txn_proplist(&txnprops, txn, pool));
3219 /* Remove any temporary txn props representing 'flags'. */
3220 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
3222 svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD, NULL);
3226 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
3228 svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL);
3232 client_date = svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE);
3235 svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE, NULL);
3239 /* Update commit time to ensure that svn:date revprops remain ordered if
3241 if (!client_date || strcmp(client_date->data, "1"))
3243 date.data = svn_time_to_cstring(apr_time_now(), pool);
3244 date.len = strlen(date.data);
3245 svn_hash_sets(txnprops, SVN_PROP_REVISION_DATE, &date);
3251 SVN_ERR(set_txn_proplist(txn->fs, txn_id, txnprops, TRUE, pool));
3252 *path = svn_fs_x__path_txn_props_final(txn->fs, txn_id, pool);
3256 *path = svn_fs_x__path_txn_props(txn->fs, txn_id, pool);
3259 return SVN_NO_ERROR;
3263 svn_fs_x__add_index_data(svn_fs_t *fs,
3265 const char *l2p_proto_index,
3266 const char *p2l_proto_index,
3267 svn_revnum_t revision,
3268 apr_pool_t *scratch_pool)
3270 apr_off_t l2p_offset;
3271 apr_off_t p2l_offset;
3272 svn_stringbuf_t *footer;
3273 unsigned char footer_length;
3274 svn_checksum_t *l2p_checksum;
3275 svn_checksum_t *p2l_checksum;
3277 /* Append the actual index data to the pack file. */
3279 SVN_ERR(svn_io_file_seek(file, APR_END, &l2p_offset, scratch_pool));
3280 SVN_ERR(svn_fs_x__l2p_index_append(&l2p_checksum, fs, file,
3281 l2p_proto_index, revision,
3282 scratch_pool, scratch_pool));
3285 SVN_ERR(svn_io_file_seek(file, APR_END, &p2l_offset, scratch_pool));
3286 SVN_ERR(svn_fs_x__p2l_index_append(&p2l_checksum, fs, file,
3287 p2l_proto_index, revision,
3288 scratch_pool, scratch_pool));
3290 /* Append footer. */
3291 footer = svn_fs_x__unparse_footer(l2p_offset, l2p_checksum,
3292 p2l_offset, p2l_checksum, scratch_pool,
3294 SVN_ERR(svn_io_file_write_full(file, footer->data, footer->len, NULL,
3297 footer_length = footer->len;
3298 SVN_ERR_ASSERT(footer_length == footer->len);
3299 SVN_ERR(svn_io_file_write_full(file, &footer_length, 1, NULL,
3302 return SVN_NO_ERROR;
3305 /* Baton used for commit_body below. */
3306 typedef struct commit_baton_t {
3307 svn_revnum_t *new_rev_p;
3310 apr_array_header_t *reps_to_cache;
3311 apr_hash_t *reps_hash;
3312 apr_pool_t *reps_pool;
3315 /* The work-horse for svn_fs_x__commit, called with the FS write lock.
3316 This implements the svn_fs_x__with_write_lock() 'body' callback
3317 type. BATON is a 'commit_baton_t *'. */
3318 static svn_error_t *
3319 commit_body(void *baton,
3320 apr_pool_t *scratch_pool)
3322 commit_baton_t *cb = baton;
3323 svn_fs_x__data_t *ffd = cb->fs->fsap_data;
3324 const char *old_rev_filename, *rev_filename, *proto_filename;
3325 const char *revprop_filename, *final_revprop;
3326 svn_fs_x__id_t root_id, new_root_id;
3327 svn_revnum_t old_rev, new_rev;
3328 apr_file_t *proto_file;
3329 void *proto_file_lockcookie;
3330 apr_off_t initial_offset, changed_path_offset;
3331 svn_fs_x__txn_id_t txn_id = svn_fs_x__txn_get_id(cb->txn);
3332 apr_hash_t *changed_paths;
3334 /* Re-Read the current repository format. All our repo upgrade and
3335 config evaluation strategies are such that existing information in
3336 FS and FFD remains valid.
3338 Although we don't recommend upgrading hot repositories, people may
3339 still do it and we must make sure to either handle them gracefully
3342 Committing pre-format 3 txns will fail after upgrade to format 3+
3343 because the proto-rev cannot be found; no further action needed.
3344 Upgrades from pre-f7 to f7+ means a potential change in addressing
3345 mode for the final rev. We must be sure to detect that cause because
3346 the failure would only manifest once the new revision got committed.
3348 SVN_ERR(svn_fs_x__read_format_file(cb->fs, scratch_pool));
3350 /* Get the current youngest revision. */
3351 SVN_ERR(svn_fs_x__youngest_rev(&old_rev, cb->fs, scratch_pool));
3353 /* Check to make sure this transaction is based off the most recent
3355 if (cb->txn->base_rev != old_rev)
3356 return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
3357 _("Transaction out of date"));
3359 /* We need the changes list for verification as well as for writing it
3360 to the final rev file. */
3361 SVN_ERR(svn_fs_x__txn_changes_fetch(&changed_paths, cb->fs, txn_id,
3364 /* Locks may have been added (or stolen) between the calling of
3365 previous svn_fs.h functions and svn_fs_commit_txn(), so we need
3366 to re-examine every changed-path in the txn and re-verify all
3367 discovered locks. */
3368 SVN_ERR(verify_locks(cb->fs, txn_id, changed_paths, scratch_pool));
3370 /* We are going to be one better than this puny old revision. */
3371 new_rev = old_rev + 1;
3373 /* Get a write handle on the proto revision file. */
3374 SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
3375 cb->fs, txn_id, scratch_pool));
3376 SVN_ERR(svn_fs_x__get_file_offset(&initial_offset, proto_file,
3379 /* Write out all the node-revisions and directory contents. */
3380 svn_fs_x__init_txn_root(&root_id, txn_id);
3381 SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, &root_id,
3382 initial_offset, cb->reps_to_cache, cb->reps_hash,
3383 cb->reps_pool, TRUE, scratch_pool));
3385 /* Write the changed-path information. */
3386 SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
3387 cb->fs, txn_id, changed_paths,
3388 new_rev, scratch_pool));
3390 /* Append the index data to the rev file. */
3391 SVN_ERR(svn_fs_x__add_index_data(cb->fs, proto_file,
3392 svn_fs_x__path_l2p_proto_index(cb->fs, txn_id, scratch_pool),
3393 svn_fs_x__path_p2l_proto_index(cb->fs, txn_id, scratch_pool),
3394 new_rev, scratch_pool));
3396 SVN_ERR(svn_io_file_flush_to_disk(proto_file, scratch_pool));
3397 SVN_ERR(svn_io_file_close(proto_file, scratch_pool));
3399 /* We don't unlock the prototype revision file immediately to avoid a
3400 race with another caller writing to the prototype revision file
3401 before we commit it. */
3403 /* Create the shard for the rev and revprop file, if we're sharding and
3404 this is the first revision of a new shard. We don't care if this
3405 fails because the shard already existed for some reason. */
3406 if (new_rev % ffd->max_files_per_dir == 0)
3408 /* Create the revs shard. */
3411 = svn_fs_x__path_rev_shard(cb->fs, new_rev, scratch_pool);
3412 svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT,
3414 if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
3415 return svn_error_trace(err);
3416 svn_error_clear(err);
3417 SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
3420 new_dir, scratch_pool));
3423 /* Create the revprops shard. */
3424 SVN_ERR_ASSERT(! svn_fs_x__is_packed_revprop(cb->fs, new_rev));
3427 = svn_fs_x__path_revprops_shard(cb->fs, new_rev, scratch_pool);
3428 svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT,
3430 if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
3431 return svn_error_trace(err);
3432 svn_error_clear(err);
3433 SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
3436 new_dir, scratch_pool));
3440 /* Move the finished rev file into place.
3442 ### This "breaks" the transaction by removing the protorev file
3443 ### but the revision is not yet complete. If this commit does
3444 ### not complete for any reason the transaction will be lost. */
3445 old_rev_filename = svn_fs_x__path_rev_absolute(cb->fs, old_rev,
3448 rev_filename = svn_fs_x__path_rev(cb->fs, new_rev, scratch_pool);
3449 proto_filename = svn_fs_x__path_txn_proto_rev(cb->fs, txn_id,
3451 SVN_ERR(svn_fs_x__move_into_place(proto_filename, rev_filename,
3452 old_rev_filename, scratch_pool));
3454 /* Now that we've moved the prototype revision file out of the way,
3455 we can unlock it (since further attempts to write to the file
3456 will fail as it no longer exists). We must do this so that we can
3457 remove the transaction directory later. */
3458 SVN_ERR(unlock_proto_rev(cb->fs, txn_id, proto_file_lockcookie,
3461 /* Move the revprops file into place. */
3462 SVN_ERR_ASSERT(! svn_fs_x__is_packed_revprop(cb->fs, new_rev));
3463 SVN_ERR(write_final_revprop(&revprop_filename, cb->txn, txn_id,
3465 final_revprop = svn_fs_x__path_revprops(cb->fs, new_rev, scratch_pool);
3466 SVN_ERR(svn_fs_x__move_into_place(revprop_filename, final_revprop,
3467 old_rev_filename, scratch_pool));
3469 /* Update the 'current' file. */
3470 SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev,
3472 SVN_ERR(svn_fs_x__write_current(cb->fs, new_rev, scratch_pool));
3474 /* At this point the new revision is committed and globally visible
3475 so let the caller know it succeeded by giving it the new revision
3476 number, which fulfills svn_fs_commit_txn() contract. Any errors
3477 after this point do not change the fact that a new revision was
3479 *cb->new_rev_p = new_rev;
3481 ffd->youngest_rev_cache = new_rev;
3483 /* Remove this transaction directory. */
3484 SVN_ERR(svn_fs_x__purge_txn(cb->fs, cb->txn->id, scratch_pool));
3486 return SVN_NO_ERROR;
3489 /* Add the representations in REPS_TO_CACHE (an array of
3490 * svn_fs_x__representation_t *) to the rep-cache database of FS. */
3491 static svn_error_t *
3492 write_reps_to_cache(svn_fs_t *fs,
3493 const apr_array_header_t *reps_to_cache,
3494 apr_pool_t *scratch_pool)
3498 for (i = 0; i < reps_to_cache->nelts; i++)
3500 svn_fs_x__representation_t *rep
3501 = APR_ARRAY_IDX(reps_to_cache, i, svn_fs_x__representation_t *);
3503 /* FALSE because we don't care if another parallel commit happened to
3504 * collide with us. (Non-parallel collisions will not be detected.) */
3505 SVN_ERR(svn_fs_x__set_rep_reference(fs, rep, scratch_pool));
3508 return SVN_NO_ERROR;
3512 svn_fs_x__commit(svn_revnum_t *new_rev_p,
3515 apr_pool_t *scratch_pool)
3518 svn_fs_x__data_t *ffd = fs->fsap_data;
3520 cb.new_rev_p = new_rev_p;
3524 if (ffd->rep_sharing_allowed)
3526 cb.reps_to_cache = apr_array_make(scratch_pool, 5,
3527 sizeof(svn_fs_x__representation_t *));
3528 cb.reps_hash = apr_hash_make(scratch_pool);
3529 cb.reps_pool = scratch_pool;
3533 cb.reps_to_cache = NULL;
3534 cb.reps_hash = NULL;
3535 cb.reps_pool = NULL;
3538 SVN_ERR(svn_fs_x__with_write_lock(fs, commit_body, &cb, scratch_pool));
3540 /* At this point, *NEW_REV_P has been set, so errors below won't affect
3541 the success of the commit. (See svn_fs_commit_txn().) */
3543 if (ffd->rep_sharing_allowed)
3545 SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool));
3547 /* Write new entries to the rep-sharing database.
3549 * We use an sqlite transaction to speed things up;
3550 * see <http://www.sqlite.org/faq.html#q19>.
3552 /* ### A commit that touches thousands of files will starve other
3553 (reader/writer) commits for the duration of the below call.
3554 Maybe write in batches? */
3555 SVN_SQLITE__WITH_TXN(
3556 write_reps_to_cache(fs, cb.reps_to_cache, scratch_pool),
3560 return SVN_NO_ERROR;
3565 svn_fs_x__list_transactions(apr_array_header_t **names_p,
3569 const char *txn_dir;
3570 apr_hash_t *dirents;
3571 apr_hash_index_t *hi;
3572 apr_array_header_t *names;
3573 apr_size_t ext_len = strlen(PATH_EXT_TXN);
3575 names = apr_array_make(pool, 1, sizeof(const char *));
3577 /* Get the transactions directory. */
3578 txn_dir = svn_fs_x__path_txns_dir(fs, pool);
3580 /* Now find a listing of this directory. */
3581 SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool));
3583 /* Loop through all the entries and return anything that ends with '.txn'. */
3584 for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
3586 const char *name = apr_hash_this_key(hi);
3587 apr_ssize_t klen = apr_hash_this_key_len(hi);
3590 /* The name must end with ".txn" to be considered a transaction. */
3591 if ((apr_size_t) klen <= ext_len
3592 || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0)
3595 /* Truncate the ".txn" extension and store the ID. */
3596 id = apr_pstrndup(pool, name, strlen(name) - ext_len);
3597 APR_ARRAY_PUSH(names, const char *) = id;
3602 return SVN_NO_ERROR;
3606 svn_fs_x__open_txn(svn_fs_txn_t **txn_p,
3613 svn_node_kind_t kind;
3614 svn_fs_x__transaction_t *local_txn;
3615 svn_fs_x__txn_id_t txn_id;
3617 SVN_ERR(svn_fs_x__txn_by_name(&txn_id, name));
3619 /* First check to see if the directory exists. */
3620 SVN_ERR(svn_io_check_path(svn_fs_x__path_txn_dir(fs, txn_id, pool),
3623 /* Did we find it? */
3624 if (kind != svn_node_dir)
3625 return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL,
3626 _("No such transaction '%s'"),
3629 txn = apr_pcalloc(pool, sizeof(*txn));
3630 ftd = apr_pcalloc(pool, sizeof(*ftd));
3631 ftd->txn_id = txn_id;
3633 /* Read in the root node of this transaction. */
3634 txn->id = apr_pstrdup(pool, name);
3637 SVN_ERR(svn_fs_x__get_txn(&local_txn, fs, txn_id, pool));
3639 txn->base_rev = local_txn->base_rev;
3641 txn->vtable = &txn_vtable;
3642 txn->fsap_data = ftd;
3645 return SVN_NO_ERROR;
3649 svn_fs_x__txn_proplist(apr_hash_t **table_p,
3653 apr_hash_t *proplist = apr_hash_make(pool);
3654 SVN_ERR(get_txn_proplist(proplist, txn->fs, svn_fs_x__txn_get_id(txn),
3656 *table_p = proplist;
3658 return SVN_NO_ERROR;
3662 svn_fs_x__delete_node_revision(svn_fs_t *fs,
3663 const svn_fs_x__id_t *id,
3664 apr_pool_t *scratch_pool)
3666 svn_fs_x__noderev_t *noderev;
3667 SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, id, scratch_pool,
3670 /* Delete any mutable property representation. */
3671 if (noderev->prop_rep
3672 && svn_fs_x__is_txn(noderev->prop_rep->id.change_set))
3673 SVN_ERR(svn_io_remove_file2(svn_fs_x__path_txn_node_props(fs, id,
3676 FALSE, scratch_pool));
3678 /* Delete any mutable data representation. */
3679 if (noderev->data_rep
3680 && svn_fs_x__is_txn(noderev->data_rep->id.change_set)
3681 && noderev->kind == svn_node_dir)
3683 svn_fs_x__data_t *ffd = fs->fsap_data;
3684 const svn_fs_x__id_t *key = id;
3686 SVN_ERR(svn_io_remove_file2(
3687 svn_fs_x__path_txn_node_children(fs, id, scratch_pool,
3689 FALSE, scratch_pool));
3691 /* remove the corresponding entry from the cache, if such exists */
3692 SVN_ERR(svn_cache__set(ffd->dir_cache, key, NULL, scratch_pool));
3695 return svn_io_remove_file2(svn_fs_x__path_txn_node_rev(fs, id,
3698 FALSE, scratch_pool);
3703 /*** Transactions ***/
3706 svn_fs_x__get_base_rev(svn_revnum_t *revnum,
3708 svn_fs_x__txn_id_t txn_id,
3709 apr_pool_t *scratch_pool)
3711 svn_fs_x__transaction_t *txn;
3712 SVN_ERR(svn_fs_x__get_txn(&txn, fs, txn_id, scratch_pool));
3713 *revnum = txn->base_rev;
3715 return SVN_NO_ERROR;
3719 /* Generic transaction operations. */
3722 svn_fs_x__txn_prop(svn_string_t **value_p,
3724 const char *propname,
3728 svn_fs_t *fs = txn->fs;
3730 SVN_ERR(svn_fs__check_fs(fs, TRUE));
3731 SVN_ERR(svn_fs_x__txn_proplist(&table, txn, pool));
3733 *value_p = svn_hash_gets(table, propname);
3735 return SVN_NO_ERROR;
3739 svn_fs_x__begin_txn(svn_fs_txn_t **txn_p,
3743 apr_pool_t *result_pool,
3744 apr_pool_t *scratch_pool)
3748 apr_hash_t *props = apr_hash_make(scratch_pool);
3750 SVN_ERR(svn_fs__check_fs(fs, TRUE));
3752 SVN_ERR(create_txn(txn_p, fs, rev, result_pool, scratch_pool));
3754 /* Put a datestamp on the newly created txn, so we always know
3755 exactly how old it is. (This will help sysadmins identify
3756 long-abandoned txns that may need to be manually removed.) When
3757 a txn is promoted to a revision, this property will be
3758 automatically overwritten with a revision datestamp. */
3759 date.data = svn_time_to_cstring(apr_time_now(), scratch_pool);
3760 date.len = strlen(date.data);
3762 svn_hash_sets(props, SVN_PROP_REVISION_DATE, &date);
3764 /* Set temporary txn props that represent the requested 'flags'
3766 if (flags & SVN_FS_TXN_CHECK_OOD)
3767 svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_OOD,
3768 svn_string_create("true", scratch_pool));
3770 if (flags & SVN_FS_TXN_CHECK_LOCKS)
3771 svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_LOCKS,
3772 svn_string_create("true", scratch_pool));
3774 if (flags & SVN_FS_TXN_CLIENT_DATE)
3775 svn_hash_sets(props, SVN_FS__PROP_TXN_CLIENT_DATE,
3776 svn_string_create("0", scratch_pool));
3778 ftd = (*txn_p)->fsap_data;
3779 SVN_ERR(set_txn_proplist(fs, ftd->txn_id, props, FALSE, scratch_pool));
3781 return SVN_NO_ERROR;