1 /* transaction.c --- transaction-related functions of FSFS
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"
39 #include "low_level.h"
40 #include "temp_serializer.h"
41 #include "cached_data.h"
43 #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_subr_private.h"
49 #include "private/svn_string_private.h"
50 #include "../libsvn_fs/fs-loader.h"
52 #include "svn_private_config.h"
54 /* Return the name of the sha1->rep mapping file in transaction TXN_ID
55 * within FS for the given SHA1 checksum. Use POOL for allocations.
57 static APR_INLINE const char *
58 path_txn_sha1(svn_fs_t *fs,
59 const svn_fs_fs__id_part_t *txn_id,
60 const unsigned char *sha1,
63 svn_checksum_t checksum;
64 checksum.digest = sha1;
65 checksum.kind = svn_checksum_sha1;
67 return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
68 svn_checksum_to_cstring(&checksum, pool),
72 static APR_INLINE const char *
73 path_txn_changes(svn_fs_t *fs,
74 const svn_fs_fs__id_part_t *txn_id,
77 return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
81 static APR_INLINE const char *
82 path_txn_props(svn_fs_t *fs,
83 const svn_fs_fs__id_part_t *txn_id,
86 return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
87 PATH_TXN_PROPS, pool);
90 static APR_INLINE const char *
91 path_txn_props_final(svn_fs_t *fs,
92 const svn_fs_fs__id_part_t *txn_id,
95 return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
96 PATH_TXN_PROPS_FINAL, pool);
99 static APR_INLINE const char *
100 path_txn_next_ids(svn_fs_t *fs,
101 const svn_fs_fs__id_part_t *txn_id,
104 return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
105 PATH_NEXT_IDS, pool);
109 /* The vtable associated with an open transaction object. */
110 static txn_vtable_t txn_vtable = {
111 svn_fs_fs__commit_txn,
112 svn_fs_fs__abort_txn,
114 svn_fs_fs__txn_proplist,
115 svn_fs_fs__change_txn_prop,
117 svn_fs_fs__change_txn_props
120 /* FSFS-specific data being attached to svn_fs_txn_t.
122 typedef struct fs_txn_data_t
124 /* Strongly typed representation of the TXN's ID member. */
125 svn_fs_fs__id_part_t txn_id;
128 const svn_fs_fs__id_part_t *
129 svn_fs_fs__txn_get_id(svn_fs_txn_t *txn)
131 fs_txn_data_t *ftd = txn->fsap_data;
135 /* Functions for working with shared transaction data. */
137 /* Return the transaction object for transaction TXN_ID from the
138 transaction list of filesystem FS (which must already be locked via the
139 txn_list_lock mutex). If the transaction does not exist in the list,
140 then create a new transaction object and return it (if CREATE_NEW is
141 true) or return NULL (otherwise). */
142 static fs_fs_shared_txn_data_t *
143 get_shared_txn(svn_fs_t *fs,
144 const svn_fs_fs__id_part_t *txn_id,
145 svn_boolean_t create_new)
147 fs_fs_data_t *ffd = fs->fsap_data;
148 fs_fs_shared_data_t *ffsd = ffd->shared;
149 fs_fs_shared_txn_data_t *txn;
151 for (txn = ffsd->txns; txn; txn = txn->next)
152 if (svn_fs_fs__id_part_eq(&txn->txn_id, txn_id))
155 if (txn || !create_new)
158 /* Use the transaction object from the (single-object) freelist,
159 if one is available, or otherwise create a new object. */
162 txn = ffsd->free_txn;
163 ffsd->free_txn = NULL;
167 apr_pool_t *subpool = svn_pool_create(ffsd->common_pool);
168 txn = apr_palloc(subpool, sizeof(*txn));
172 txn->txn_id = *txn_id;
173 txn->being_written = FALSE;
175 /* Link this transaction into the head of the list. We will typically
176 be dealing with only one active transaction at a time, so it makes
177 sense for searches through the transaction list to look at the
178 newest transactions first. */
179 txn->next = ffsd->txns;
185 /* Free the transaction object for transaction TXN_ID, and remove it
186 from the transaction list of filesystem FS (which must already be
187 locked via the txn_list_lock mutex). Do nothing if the transaction
190 free_shared_txn(svn_fs_t *fs, const svn_fs_fs__id_part_t *txn_id)
192 fs_fs_data_t *ffd = fs->fsap_data;
193 fs_fs_shared_data_t *ffsd = ffd->shared;
194 fs_fs_shared_txn_data_t *txn, *prev = NULL;
196 for (txn = ffsd->txns; txn; prev = txn, txn = txn->next)
197 if (svn_fs_fs__id_part_eq(&txn->txn_id, txn_id))
204 prev->next = txn->next;
206 ffsd->txns = txn->next;
208 /* As we typically will be dealing with one transaction after another,
209 we will maintain a single-object free list so that we can hopefully
210 keep reusing the same transaction object. */
212 ffsd->free_txn = txn;
214 svn_pool_destroy(txn->pool);
218 /* Obtain a lock on the transaction list of filesystem FS, call BODY
219 with FS, BATON, and POOL, and then unlock the transaction list.
220 Return what BODY returned. */
222 with_txnlist_lock(svn_fs_t *fs,
223 svn_error_t *(*body)(svn_fs_t *fs,
229 fs_fs_data_t *ffd = fs->fsap_data;
230 fs_fs_shared_data_t *ffsd = ffd->shared;
232 SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock,
233 body(fs, baton, pool));
239 /* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
241 struct unlock_proto_rev_baton
243 svn_fs_fs__id_part_t txn_id;
247 /* Callback used in the implementation of unlock_proto_rev(). */
249 unlock_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
251 const struct unlock_proto_rev_baton *b = baton;
252 apr_file_t *lockfile = b->lockcookie;
253 fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, &b->txn_id, FALSE);
254 apr_status_t apr_err;
257 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
258 _("Can't unlock unknown transaction '%s'"),
259 svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
260 if (!txn->being_written)
261 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
262 _("Can't unlock nonlocked transaction '%s'"),
263 svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
265 apr_err = apr_file_unlock(lockfile);
267 return svn_error_wrap_apr
269 _("Can't unlock prototype revision lockfile for transaction '%s'"),
270 svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
271 apr_err = apr_file_close(lockfile);
273 return svn_error_wrap_apr
275 _("Can't close prototype revision lockfile for transaction '%s'"),
276 svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
278 txn->being_written = FALSE;
283 /* Unlock the prototype revision file for transaction TXN_ID in filesystem
284 FS using cookie LOCKCOOKIE. The original prototype revision file must
285 have been closed _before_ calling this function.
287 Perform temporary allocations in POOL. */
289 unlock_proto_rev(svn_fs_t *fs,
290 const svn_fs_fs__id_part_t *txn_id,
294 struct unlock_proto_rev_baton b;
297 b.lockcookie = lockcookie;
298 return with_txnlist_lock(fs, unlock_proto_rev_body, &b, pool);
301 /* A structure used by get_writable_proto_rev() and
302 get_writable_proto_rev_body(), which see. */
303 struct get_writable_proto_rev_baton
306 svn_fs_fs__id_part_t txn_id;
309 /* Callback used in the implementation of get_writable_proto_rev(). */
311 get_writable_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
313 const struct get_writable_proto_rev_baton *b = baton;
314 void **lockcookie = b->lockcookie;
315 fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, &b->txn_id, TRUE);
317 /* First, ensure that no thread in this process (including this one)
318 is currently writing to this transaction's proto-rev file. */
319 if (txn->being_written)
320 return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
321 _("Cannot write to the prototype revision file "
322 "of transaction '%s' because a previous "
323 "representation is currently being written by "
325 svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
328 /* We know that no thread in this process is writing to the proto-rev
329 file, and by extension, that no thread in this process is holding a
330 lock on the prototype revision lock file. It is therefore safe
331 for us to attempt to lock this file, to see if any other process
332 is holding a lock. */
335 apr_file_t *lockfile;
336 apr_status_t apr_err;
337 const char *lockfile_path
338 = svn_fs_fs__path_txn_proto_rev_lock(fs, &b->txn_id, pool);
340 /* Open the proto-rev lockfile, creating it if necessary, as it may
341 not exist if the transaction dates from before the lockfiles were
344 ### We'd also like to use something like svn_io_file_lock2(), but
345 that forces us to create a subpool just to be able to unlock
346 the file, which seems a waste. */
347 SVN_ERR(svn_io_file_open(&lockfile, lockfile_path,
348 APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
350 apr_err = apr_file_lock(lockfile,
351 APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK);
354 svn_error_clear(svn_io_file_close(lockfile, pool));
356 if (APR_STATUS_IS_EAGAIN(apr_err))
357 return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
358 _("Cannot write to the prototype revision "
359 "file of transaction '%s' because a "
360 "previous representation is currently "
361 "being written by another process"),
362 svn_fs_fs__id_txn_unparse(&b->txn_id,
365 return svn_error_wrap_apr(apr_err,
366 _("Can't get exclusive lock on file '%s'"),
367 svn_dirent_local_style(lockfile_path, pool));
370 *lockcookie = lockfile;
373 /* We've successfully locked the transaction; mark it as such. */
374 txn->being_written = TRUE;
379 /* Make sure the length ACTUAL_LENGTH of the proto-revision file PROTO_REV
380 of transaction TXN_ID in filesystem FS matches the proto-index file.
381 Trim any crash / failure related extra data from the proto-rev file.
383 If the prototype revision file is too short, we can't do much but bail out.
385 Perform all allocations in POOL. */
387 auto_truncate_proto_rev(svn_fs_t *fs,
388 apr_file_t *proto_rev,
389 apr_off_t actual_length,
390 const svn_fs_fs__id_part_t *txn_id,
393 /* Only relevant for newer FSFS formats. */
394 if (svn_fs_fs__use_log_addressing(fs))
396 /* Determine file range covered by the proto-index so far. Note that
397 we always append to both file, i.e. the last index entry also
398 corresponds to the last addition in the rev file. */
399 const char *path = svn_fs_fs__path_p2l_proto_index(fs, txn_id, pool);
401 apr_off_t indexed_length;
403 SVN_ERR(svn_fs_fs__p2l_proto_index_open(&file, path, pool));
404 SVN_ERR(svn_fs_fs__p2l_proto_index_next_offset(&indexed_length, file,
406 SVN_ERR(svn_io_file_close(file, pool));
408 /* Handle mismatches. */
409 if (indexed_length < actual_length)
410 SVN_ERR(svn_io_file_trunc(proto_rev, indexed_length, pool));
411 else if (indexed_length > actual_length)
412 return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
414 _("p2l proto index offset %s beyond proto"
415 "rev file size %s for TXN %s"),
416 apr_off_t_toa(pool, indexed_length),
417 apr_off_t_toa(pool, actual_length),
418 svn_fs_fs__id_txn_unparse(txn_id, pool));
424 /* Get a handle to the prototype revision file for transaction TXN_ID in
425 filesystem FS, and lock it for writing. Return FILE, a file handle
426 positioned at the end of the file, and LOCKCOOKIE, a cookie that
427 should be passed to unlock_proto_rev() to unlock the file once FILE
430 If the prototype revision file is already locked, return error
431 SVN_ERR_FS_REP_BEING_WRITTEN.
433 Perform all allocations in POOL. */
435 get_writable_proto_rev(apr_file_t **file,
438 const svn_fs_fs__id_part_t *txn_id,
441 struct get_writable_proto_rev_baton b;
443 apr_off_t end_offset = 0;
445 b.lockcookie = lockcookie;
448 SVN_ERR(with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool));
450 /* Now open the prototype revision file and seek to the end. */
451 err = svn_io_file_open(file,
452 svn_fs_fs__path_txn_proto_rev(fs, txn_id, pool),
453 APR_READ | APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT,
456 /* You might expect that we could dispense with the following seek
457 and achieve the same thing by opening the file using APR_APPEND.
458 Unfortunately, APR's buffered file implementation unconditionally
459 places its initial file pointer at the start of the file (even for
460 files opened with APR_APPEND), so we need this seek to reconcile
461 the APR file pointer to the OS file pointer (since we need to be
462 able to read the current file position later). */
464 err = svn_io_file_seek(*file, APR_END, &end_offset, pool);
466 /* We don't want unused sections (such as leftovers from failed delta
467 stream) in our file. If we use log addressing, we would need an
468 index entry for the unused section and that section would need to
469 be all NUL by convention. So, detect and fix those cases by truncating
470 the protorev file. */
472 err = auto_truncate_proto_rev(fs, *file, end_offset, txn_id, pool);
476 err = svn_error_compose_create(
478 unlock_proto_rev(fs, txn_id, *lockcookie, pool));
483 return svn_error_trace(err);
486 /* Callback used in the implementation of purge_shared_txn(). */
488 purge_shared_txn_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
490 const svn_fs_fs__id_part_t *txn_id = baton;
492 free_shared_txn(fs, txn_id);
493 svn_fs_fs__reset_txn_caches(fs);
498 /* Purge the shared data for transaction TXN_ID in filesystem FS.
499 Perform all allocations in POOL. */
501 purge_shared_txn(svn_fs_t *fs,
502 const svn_fs_fs__id_part_t *txn_id,
505 return with_txnlist_lock(fs, purge_shared_txn_body, txn_id, pool);
510 svn_fs_fs__put_node_revision(svn_fs_t *fs,
511 const svn_fs_id_t *id,
512 node_revision_t *noderev,
513 svn_boolean_t fresh_txn_root,
516 fs_fs_data_t *ffd = fs->fsap_data;
517 apr_file_t *noderev_file;
519 noderev->is_fresh_txn_root = fresh_txn_root;
521 if (! svn_fs_fs__id_is_txn(id))
522 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
523 _("Attempted to write to non-transaction '%s'"),
524 svn_fs_fs__id_unparse(id, pool)->data);
526 SVN_ERR(svn_io_file_open(&noderev_file,
527 svn_fs_fs__path_txn_node_rev(fs, id, pool),
528 APR_WRITE | APR_CREATE | APR_TRUNCATE
529 | APR_BUFFERED, APR_OS_DEFAULT, pool));
531 SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE,
533 noderev, ffd->format,
534 svn_fs_fs__fs_supports_mergeinfo(fs),
537 SVN_ERR(svn_io_file_close(noderev_file, pool));
542 /* For the in-transaction NODEREV within FS, write the sha1->rep mapping
543 * file in the respective transaction, if rep sharing has been enabled etc.
544 * Use SCATCH_POOL for temporary allocations.
547 store_sha1_rep_mapping(svn_fs_t *fs,
548 node_revision_t *noderev,
549 apr_pool_t *scratch_pool)
551 fs_fs_data_t *ffd = fs->fsap_data;
553 /* if rep sharing has been enabled and the noderev has a data rep and
554 * its SHA-1 is known, store the rep struct under its SHA1. */
555 if ( ffd->rep_sharing_allowed
557 && noderev->data_rep->has_sha1)
559 apr_file_t *rep_file;
560 const char *file_name = path_txn_sha1(fs,
561 &noderev->data_rep->txn_id,
562 noderev->data_rep->sha1_digest,
564 svn_stringbuf_t *rep_string
565 = svn_fs_fs__unparse_representation(noderev->data_rep,
567 (noderev->kind == svn_node_dir),
568 scratch_pool, scratch_pool);
569 SVN_ERR(svn_io_file_open(&rep_file, file_name,
570 APR_WRITE | APR_CREATE | APR_TRUNCATE
571 | APR_BUFFERED, APR_OS_DEFAULT, scratch_pool));
573 SVN_ERR(svn_io_file_write_full(rep_file, rep_string->data,
574 rep_string->len, NULL, scratch_pool));
576 SVN_ERR(svn_io_file_close(rep_file, scratch_pool));
583 unparse_dir_entry(svn_fs_dirent_t *dirent,
584 svn_stream_t *stream,
588 = apr_psprintf(pool, "%s %s",
589 (dirent->kind == svn_node_file) ? SVN_FS_FS__KIND_FILE
590 : SVN_FS_FS__KIND_DIR,
591 svn_fs_fs__id_unparse(dirent->id, pool)->data);
593 SVN_ERR(svn_stream_printf(stream, pool, "K %" APR_SIZE_T_FMT "\n%s\n"
594 "V %" APR_SIZE_T_FMT "\n%s\n",
595 strlen(dirent->name), dirent->name,
600 /* Write the directory given as array of dirent structs in ENTRIES to STREAM.
601 Perform temporary allocations in POOL. */
603 unparse_dir_entries(apr_array_header_t *entries,
604 svn_stream_t *stream,
607 apr_pool_t *iterpool = svn_pool_create(pool);
609 for (i = 0; i < entries->nelts; ++i)
611 svn_fs_dirent_t *dirent;
613 svn_pool_clear(iterpool);
614 dirent = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
615 SVN_ERR(unparse_dir_entry(dirent, stream, iterpool));
618 SVN_ERR(svn_stream_printf(stream, pool, "%s\n", SVN_HASH_TERMINATOR));
620 svn_pool_destroy(iterpool);
624 /* Return a deep copy of SOURCE and allocate it in RESULT_POOL.
626 static svn_fs_path_change2_t *
627 path_change_dup(const svn_fs_path_change2_t *source,
628 apr_pool_t *result_pool)
630 svn_fs_path_change2_t *result = apr_pmemdup(result_pool, source,
632 result->node_rev_id = svn_fs_fs__id_copy(source->node_rev_id, result_pool);
633 if (source->copyfrom_path)
634 result->copyfrom_path = apr_pstrdup(result_pool, source->copyfrom_path);
639 /* Merge the internal-use-only CHANGE into a hash of public-FS
640 svn_fs_path_change2_t CHANGED_PATHS, collapsing multiple changes into a
641 single summarical (is that real word?) change per path. DELETIONS is
642 also a path->svn_fs_path_change2_t hash and contains all the deletions
643 that got turned into a replacement. */
645 fold_change(apr_hash_t *changed_paths,
646 apr_hash_t *deletions,
647 const change_t *change)
649 apr_pool_t *pool = apr_hash_pool_get(changed_paths);
650 svn_fs_path_change2_t *old_change, *new_change;
651 const svn_string_t *path = &change->path;
652 const svn_fs_path_change2_t *info = &change->info;
654 if ((old_change = apr_hash_get(changed_paths, path->data, path->len)))
656 /* This path already exists in the hash, so we have to merge
657 this change into the already existing one. */
659 /* Sanity check: only allow NULL node revision ID in the
661 if ((! info->node_rev_id)
662 && (info->change_kind != svn_fs_path_change_reset))
663 return svn_error_create
664 (SVN_ERR_FS_CORRUPT, NULL,
665 _("Missing required node revision ID"));
667 /* Sanity check: we should be talking about the same node
668 revision ID as our last change except where the last change
670 if (info->node_rev_id
671 && (! svn_fs_fs__id_eq(old_change->node_rev_id, info->node_rev_id))
672 && (old_change->change_kind != svn_fs_path_change_delete))
673 return svn_error_create
674 (SVN_ERR_FS_CORRUPT, NULL,
675 _("Invalid change ordering: new node revision ID "
678 /* Sanity check: an add, replacement, or reset must be the first
679 thing to follow a deletion. */
680 if ((old_change->change_kind == svn_fs_path_change_delete)
681 && (! ((info->change_kind == svn_fs_path_change_replace)
682 || (info->change_kind == svn_fs_path_change_reset)
683 || (info->change_kind == svn_fs_path_change_add))))
684 return svn_error_create
685 (SVN_ERR_FS_CORRUPT, NULL,
686 _("Invalid change ordering: non-add change on deleted path"));
688 /* Sanity check: an add can't follow anything except
689 a delete or reset. */
690 if ((info->change_kind == svn_fs_path_change_add)
691 && (old_change->change_kind != svn_fs_path_change_delete)
692 && (old_change->change_kind != svn_fs_path_change_reset))
693 return svn_error_create
694 (SVN_ERR_FS_CORRUPT, NULL,
695 _("Invalid change ordering: add change on preexisting path"));
697 /* Now, merge that change in. */
698 switch (info->change_kind)
700 case svn_fs_path_change_reset:
701 /* A reset here will simply remove the path change from the
703 apr_hash_set(changed_paths, path->data, path->len, NULL);
706 case svn_fs_path_change_delete:
707 if (old_change->change_kind == svn_fs_path_change_add)
709 /* If the path was introduced in this transaction via an
710 add, and we are deleting it, just remove the path
711 altogether. (The caller will delete any child paths.) */
712 apr_hash_set(changed_paths, path->data, path->len, NULL);
714 else if (old_change->change_kind == svn_fs_path_change_replace)
716 /* A deleting a 'replace' restore the original deletion. */
717 new_change = apr_hash_get(deletions, path->data, path->len);
718 SVN_ERR_ASSERT(new_change);
719 apr_hash_set(changed_paths, path->data, path->len, new_change);
723 /* A deletion overrules a previous change (modify). */
724 new_change = path_change_dup(info, pool);
725 apr_hash_set(changed_paths, path->data, path->len, new_change);
729 case svn_fs_path_change_add:
730 case svn_fs_path_change_replace:
731 /* An add at this point must be following a previous delete,
732 so treat it just like a replace. Remember the original
733 deletion such that we are able to delete this path again
734 (the replacement may have changed node kind and id). */
735 new_change = path_change_dup(info, pool);
736 new_change->change_kind = svn_fs_path_change_replace;
738 apr_hash_set(changed_paths, path->data, path->len, new_change);
740 /* Remember the original change.
741 * Make sure to allocate the hash key in a durable pool. */
742 apr_hash_set(deletions,
743 apr_pstrmemdup(apr_hash_pool_get(deletions),
744 path->data, path->len),
745 path->len, old_change);
748 case svn_fs_path_change_modify:
750 /* If the new change modifies some attribute of the node, set
751 the corresponding flag, whether it already was set or not.
752 Note: We do not reset a flag to FALSE if a change is undone. */
754 old_change->text_mod = TRUE;
756 old_change->prop_mod = TRUE;
757 if (info->mergeinfo_mod == svn_tristate_true)
758 old_change->mergeinfo_mod = svn_tristate_true;
764 /* Add this path. The API makes no guarantees that this (new) key
765 will not be retained. Thus, we copy the key into the target pool
766 to ensure a proper lifetime. */
767 apr_hash_set(changed_paths,
768 apr_pstrmemdup(pool, path->data, path->len), path->len,
769 path_change_dup(info, pool));
775 /* Baton type to be used with process_changes(). */
776 typedef struct process_changes_baton_t
778 /* Folded list of path changes. */
779 apr_hash_t *changed_paths;
781 /* Path changes that are deletions and have been turned into
782 replacements. If those replacements get deleted again, this
783 container contains the record that we have to revert to. */
784 apr_hash_t *deletions;
785 } process_changes_baton_t;
787 /* An implementation of svn_fs_fs__change_receiver_t.
788 Examine all the changed path entries in CHANGES and store them in
789 *CHANGED_PATHS. Folding is done to remove redundant or unnecessary
790 data. Do all allocations in POOL. */
792 process_changes(void *baton_p,
794 apr_pool_t *scratch_pool)
796 process_changes_baton_t *baton = baton_p;
798 SVN_ERR(fold_change(baton->changed_paths, baton->deletions, change));
800 /* Now, if our change was a deletion or replacement, we have to
801 blow away any changes thus far on paths that are (or, were)
802 children of this path.
803 ### i won't bother with another iteration pool here -- at
804 most we talking about a few extra dups of paths into what
805 is already a temporary subpool.
808 if ((change->info.change_kind == svn_fs_path_change_delete)
809 || (change->info.change_kind == svn_fs_path_change_replace))
811 apr_hash_index_t *hi;
813 /* a potential child path must contain at least 2 more chars
814 (the path separator plus at least one char for the name).
815 Also, we should not assume that all paths have been normalized
816 i.e. some might have trailing path separators.
818 apr_ssize_t path_len = change->path.len;
819 apr_ssize_t min_child_len = path_len == 0
821 : change->path.data[path_len-1] == '/'
825 /* CAUTION: This is the inner loop of an O(n^2) algorithm.
826 The number of changes to process may be >> 1000.
827 Therefore, keep the inner loop as tight as possible.
829 for (hi = apr_hash_first(scratch_pool, baton->changed_paths);
831 hi = apr_hash_next(hi))
833 /* KEY is the path. */
836 svn_fs_path_change2_t *old_change;
837 apr_hash_this(hi, &path, &klen, (void**)&old_change);
839 /* If we come across a child of our path, remove it.
840 Call svn_fspath__skip_ancestor only if there is a chance that
841 this is actually a sub-path.
843 if (klen >= min_child_len)
847 child = svn_fspath__skip_ancestor(change->path.data, path);
848 if (child && child[0] != '\0')
850 apr_hash_set(baton->changed_paths, path, klen, NULL);
860 svn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p,
862 const svn_fs_fs__id_part_t *txn_id,
866 apr_hash_t *changed_paths = apr_hash_make(pool);
867 apr_pool_t *scratch_pool = svn_pool_create(pool);
868 process_changes_baton_t baton;
870 baton.changed_paths = changed_paths;
871 baton.deletions = apr_hash_make(scratch_pool);
873 SVN_ERR(svn_io_file_open(&file,
874 path_txn_changes(fs, txn_id, scratch_pool),
875 APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
878 SVN_ERR(svn_fs_fs__read_changes_incrementally(
879 svn_stream_from_aprfile2(file, TRUE,
881 process_changes, &baton,
883 svn_pool_destroy(scratch_pool);
885 *changed_paths_p = changed_paths;
892 svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
897 apr_hash_t *changed_paths;
898 apr_array_header_t *changes;
901 SVN_ERR(svn_fs_fs__get_changes(&changes, fs, rev, pool));
903 changed_paths = svn_hash__make(pool);
904 for (i = 0; i < changes->nelts; ++i)
906 change_t *change = APR_ARRAY_IDX(changes, i, change_t *);
907 apr_hash_set(changed_paths, change->path.data, change->path.len,
911 *changed_paths_p = changed_paths;
916 /* Copy a revision node-rev SRC into the current transaction TXN_ID in
917 the filesystem FS. This is only used to create the root of a transaction.
918 Allocations are from POOL. */
920 create_new_txn_noderev_from_rev(svn_fs_t *fs,
921 const svn_fs_fs__id_part_t *txn_id,
925 node_revision_t *noderev;
926 const svn_fs_fs__id_part_t *node_id, *copy_id;
928 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool, pool));
930 if (svn_fs_fs__id_is_txn(noderev->id))
931 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
932 _("Copying from transactions not allowed"));
934 noderev->predecessor_id = noderev->id;
935 noderev->predecessor_count++;
936 noderev->copyfrom_path = NULL;
937 noderev->copyfrom_rev = SVN_INVALID_REVNUM;
939 /* For the transaction root, the copyroot never changes. */
941 node_id = svn_fs_fs__id_node_id(noderev->id);
942 copy_id = svn_fs_fs__id_copy_id(noderev->id);
943 noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
945 return svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool);
948 /* A structure used by get_and_increment_txn_key_body(). */
949 struct get_and_increment_txn_key_baton {
951 apr_uint64_t txn_number;
955 /* Callback used in the implementation of create_txn_dir(). This gets
956 the current base 36 value in PATH_TXN_CURRENT and increments it.
957 It returns the original value by the baton. */
959 get_and_increment_txn_key_body(void *baton, apr_pool_t *pool)
961 struct get_and_increment_txn_key_baton *cb = baton;
962 const char *txn_current_filename
963 = svn_fs_fs__path_txn_current(cb->fs, pool);
964 char new_id_str[SVN_INT64_BUFFER_SIZE + 1]; /* add space for a newline */
965 apr_size_t line_length;
967 svn_stringbuf_t *buf;
968 SVN_ERR(svn_fs_fs__read_content(&buf, txn_current_filename, cb->pool));
970 /* assign the current txn counter value to our result */
971 cb->txn_number = svn__base36toui64(NULL, buf->data);
973 /* remove trailing newlines */
974 line_length = svn__ui64tobase36(new_id_str, cb->txn_number+1);
975 new_id_str[line_length] = '\n';
977 /* Increment the key and add a trailing \n to the string so the
978 txn-current file has a newline in it. */
979 SVN_ERR(svn_io_write_atomic(txn_current_filename, new_id_str,
981 txn_current_filename /* copy_perms path */,
987 /* Create a unique directory for a transaction in FS based on revision REV.
988 Return the ID for this transaction in *ID_P and *TXN_ID. Use a sequence
989 value in the transaction ID to prevent reuse of transaction IDs. */
991 create_txn_dir(const char **id_p,
992 svn_fs_fs__id_part_t *txn_id,
997 struct get_and_increment_txn_key_baton cb;
1000 /* Get the current transaction sequence value, which is a base-36
1001 number, from the txn-current file, and write an
1002 incremented value back out to the file. Place the revision
1003 number the transaction is based off into the transaction id. */
1006 SVN_ERR(svn_fs_fs__with_txn_current_lock(fs,
1007 get_and_increment_txn_key_body,
1010 txn_id->revision = rev;
1011 txn_id->number = cb.txn_number;
1013 *id_p = svn_fs_fs__id_txn_unparse(txn_id, pool);
1014 txn_dir = svn_fs_fs__path_txn_dir(fs, txn_id, pool);
1016 return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool);
1019 /* Create a unique directory for a transaction in FS based on revision
1020 REV. Return the ID for this transaction in *ID_P and *TXN_ID. This
1021 implementation is used in svn 1.4 and earlier repositories and is
1022 kept in 1.5 and greater to support the --pre-1.4-compatible and
1023 --pre-1.5-compatible repository creation options. Reused
1024 transaction IDs are possible with this implementation. */
1025 static svn_error_t *
1026 create_txn_dir_pre_1_5(const char **id_p,
1027 svn_fs_fs__id_part_t *txn_id,
1033 apr_pool_t *subpool;
1034 const char *unique_path, *prefix;
1036 /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
1037 prefix = svn_dirent_join(svn_fs_fs__path_txns_dir(fs, pool),
1038 apr_psprintf(pool, "%ld", rev), pool);
1040 subpool = svn_pool_create(pool);
1041 for (i = 1; i <= 99999; i++)
1045 svn_pool_clear(subpool);
1046 unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i);
1047 err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool);
1050 /* We succeeded. Return the basename minus the ".txn" extension. */
1051 const char *name = svn_dirent_basename(unique_path, subpool);
1052 *id_p = apr_pstrndup(pool, name,
1053 strlen(name) - strlen(PATH_EXT_TXN));
1054 SVN_ERR(svn_fs_fs__id_txn_parse(txn_id, *id_p));
1055 svn_pool_destroy(subpool);
1056 return SVN_NO_ERROR;
1058 if (! APR_STATUS_IS_EEXIST(err->apr_err))
1059 return svn_error_trace(err);
1060 svn_error_clear(err);
1063 return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
1065 _("Unable to create transaction directory "
1066 "in '%s' for revision %ld"),
1067 svn_dirent_local_style(fs->path, pool),
1072 svn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
1077 fs_fs_data_t *ffd = fs->fsap_data;
1080 svn_fs_id_t *root_id;
1082 txn = apr_pcalloc(pool, sizeof(*txn));
1083 ftd = apr_pcalloc(pool, sizeof(*ftd));
1085 /* Get the txn_id. */
1086 if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1087 SVN_ERR(create_txn_dir(&txn->id, &ftd->txn_id, fs, rev, pool));
1089 SVN_ERR(create_txn_dir_pre_1_5(&txn->id, &ftd->txn_id, fs, rev, pool));
1092 txn->base_rev = rev;
1094 txn->vtable = &txn_vtable;
1095 txn->fsap_data = ftd;
1098 /* Create a new root node for this transaction. */
1099 SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool, pool));
1100 SVN_ERR(create_new_txn_noderev_from_rev(fs, &ftd->txn_id, root_id, pool));
1102 /* Create an empty rev file. */
1103 SVN_ERR(svn_io_file_create_empty(
1104 svn_fs_fs__path_txn_proto_rev(fs, &ftd->txn_id, pool),
1107 /* Create an empty rev-lock file. */
1108 SVN_ERR(svn_io_file_create_empty(
1109 svn_fs_fs__path_txn_proto_rev_lock(fs, &ftd->txn_id, pool),
1112 /* Create an empty changes file. */
1113 SVN_ERR(svn_io_file_create_empty(path_txn_changes(fs, &ftd->txn_id, pool),
1116 /* Create the next-ids file. */
1117 return svn_io_file_create(path_txn_next_ids(fs, &ftd->txn_id, pool),
1121 /* Store the property list for transaction TXN_ID in PROPLIST.
1122 Perform temporary allocations in POOL. */
1123 static svn_error_t *
1124 get_txn_proplist(apr_hash_t *proplist,
1126 const svn_fs_fs__id_part_t *txn_id,
1129 svn_stream_t *stream;
1132 /* Check for issue #3696. (When we find and fix the cause, we can change
1133 * this to an assertion.) */
1134 if (!txn_id || !svn_fs_fs__id_txn_used(txn_id))
1135 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1136 _("Internal error: a null transaction id was "
1137 "passed to get_txn_proplist()"));
1139 /* Open the transaction properties file. */
1140 SVN_ERR(svn_stream_open_readonly(&stream, path_txn_props(fs, txn_id, pool),
1143 /* Read in the property list. */
1144 err = svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool);
1147 svn_error_clear(svn_stream_close(stream));
1148 return svn_error_quick_wrapf(err,
1149 _("malformed property list in transaction '%s'"),
1150 path_txn_props(fs, txn_id, pool));
1153 return svn_stream_close(stream);
1156 /* Save the property list PROPS as the revprops for transaction TXN_ID
1157 in FS. Perform temporary allocations in POOL. */
1158 static svn_error_t *
1159 set_txn_proplist(svn_fs_t *fs,
1160 const svn_fs_fs__id_part_t *txn_id,
1162 svn_boolean_t final,
1165 svn_stringbuf_t *buf;
1166 svn_stream_t *stream;
1168 /* Write out the new file contents to BUF. */
1169 buf = svn_stringbuf_create_ensure(1024, pool);
1170 stream = svn_stream_from_stringbuf(buf, pool);
1171 SVN_ERR(svn_hash_write2(props, stream, SVN_HASH_TERMINATOR, pool));
1172 SVN_ERR(svn_stream_close(stream));
1174 /* Open the transaction properties file and write new contents to it. */
1175 SVN_ERR(svn_io_write_atomic((final
1176 ? path_txn_props_final(fs, txn_id, pool)
1177 : path_txn_props(fs, txn_id, pool)),
1178 buf->data, buf->len,
1179 NULL /* copy_perms_path */, pool));
1180 return SVN_NO_ERROR;
1185 svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
1187 const svn_string_t *value,
1190 apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
1195 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
1197 return svn_fs_fs__change_txn_props(txn, props, pool);
1201 svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
1202 const apr_array_header_t *props,
1205 fs_txn_data_t *ftd = txn->fsap_data;
1206 apr_hash_t *txn_prop = apr_hash_make(pool);
1210 err = get_txn_proplist(txn_prop, txn->fs, &ftd->txn_id, pool);
1211 /* Here - and here only - we need to deal with the possibility that the
1212 transaction property file doesn't yet exist. The rest of the
1213 implementation assumes that the file exists, but we're called to set the
1214 initial transaction properties as the transaction is being created. */
1215 if (err && (APR_STATUS_IS_ENOENT(err->apr_err)))
1216 svn_error_clear(err);
1218 return svn_error_trace(err);
1220 for (i = 0; i < props->nelts; i++)
1222 svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
1224 if (svn_hash_gets(txn_prop, SVN_FS__PROP_TXN_CLIENT_DATE)
1225 && !strcmp(prop->name, SVN_PROP_REVISION_DATE))
1226 svn_hash_sets(txn_prop, SVN_FS__PROP_TXN_CLIENT_DATE,
1227 svn_string_create("1", pool));
1229 svn_hash_sets(txn_prop, prop->name, prop->value);
1232 /* Create a new version of the file and write out the new props. */
1233 /* Open the transaction properties file. */
1234 SVN_ERR(set_txn_proplist(txn->fs, &ftd->txn_id, txn_prop, FALSE, pool));
1236 return SVN_NO_ERROR;
1240 svn_fs_fs__get_txn(transaction_t **txn_p,
1242 const svn_fs_fs__id_part_t *txn_id,
1246 node_revision_t *noderev;
1247 svn_fs_id_t *root_id;
1249 txn = apr_pcalloc(pool, sizeof(*txn));
1250 root_id = svn_fs_fs__id_txn_create_root(txn_id, pool);
1252 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool, pool));
1254 txn->root_id = svn_fs_fs__id_copy(noderev->id, pool);
1255 txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
1260 return SVN_NO_ERROR;
1263 /* Write out the currently available next node_id NODE_ID and copy_id
1264 COPY_ID for transaction TXN_ID in filesystem FS. The next node-id is
1265 used both for creating new unique nodes for the given transaction, as
1266 well as uniquifying representations. Perform temporary allocations in
1268 static svn_error_t *
1269 write_next_ids(svn_fs_t *fs,
1270 const svn_fs_fs__id_part_t *txn_id,
1271 apr_uint64_t node_id,
1272 apr_uint64_t copy_id,
1276 char buffer[2 * SVN_INT64_BUFFER_SIZE + 2];
1279 p += svn__ui64tobase36(p, node_id);
1281 p += svn__ui64tobase36(p, copy_id);
1285 SVN_ERR(svn_io_file_open(&file,
1286 path_txn_next_ids(fs, txn_id, pool),
1287 APR_WRITE | APR_TRUNCATE,
1288 APR_OS_DEFAULT, pool));
1289 SVN_ERR(svn_io_file_write_full(file, buffer, p - buffer, NULL, pool));
1290 return svn_io_file_close(file, pool);
1293 /* Find out what the next unique node-id and copy-id are for
1294 transaction TXN_ID in filesystem FS. Store the results in *NODE_ID
1295 and *COPY_ID. The next node-id is used both for creating new unique
1296 nodes for the given transaction, as well as uniquifying representations.
1297 Perform all allocations in POOL. */
1298 static svn_error_t *
1299 read_next_ids(apr_uint64_t *node_id,
1300 apr_uint64_t *copy_id,
1302 const svn_fs_fs__id_part_t *txn_id,
1305 svn_stringbuf_t *buf;
1307 SVN_ERR(svn_fs_fs__read_content(&buf,
1308 path_txn_next_ids(fs, txn_id, pool),
1311 /* Parse this into two separate strings. */
1314 *node_id = svn__base36toui64(&str, str);
1316 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1317 _("next-id file corrupt"));
1320 *copy_id = svn__base36toui64(&str, str);
1322 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1323 _("next-id file corrupt"));
1325 return SVN_NO_ERROR;
1328 /* Get a new and unique to this transaction node-id for transaction
1329 TXN_ID in filesystem FS. Store the new node-id in *NODE_ID_P.
1330 Node-ids are guaranteed to be unique to this transction, but may
1331 not necessarily be sequential. Perform all allocations in POOL. */
1332 static svn_error_t *
1333 get_new_txn_node_id(svn_fs_fs__id_part_t *node_id_p,
1335 const svn_fs_fs__id_part_t *txn_id,
1338 apr_uint64_t node_id, copy_id;
1340 /* First read in the current next-ids file. */
1341 SVN_ERR(read_next_ids(&node_id, ©_id, fs, txn_id, pool));
1343 node_id_p->revision = SVN_INVALID_REVNUM;
1344 node_id_p->number = node_id;
1346 SVN_ERR(write_next_ids(fs, txn_id, ++node_id, copy_id, pool));
1348 return SVN_NO_ERROR;
1352 svn_fs_fs__reserve_copy_id(svn_fs_fs__id_part_t *copy_id_p,
1354 const svn_fs_fs__id_part_t *txn_id,
1357 apr_uint64_t node_id, copy_id;
1359 /* First read in the current next-ids file. */
1360 SVN_ERR(read_next_ids(&node_id, ©_id, fs, txn_id, pool));
1362 /* this is an in-txn ID now */
1363 copy_id_p->revision = SVN_INVALID_REVNUM;
1364 copy_id_p->number = copy_id;
1366 /* Update the ID counter file */
1367 SVN_ERR(write_next_ids(fs, txn_id, node_id, ++copy_id, pool));
1369 return SVN_NO_ERROR;
1373 svn_fs_fs__create_node(const svn_fs_id_t **id_p,
1375 node_revision_t *noderev,
1376 const svn_fs_fs__id_part_t *copy_id,
1377 const svn_fs_fs__id_part_t *txn_id,
1380 svn_fs_fs__id_part_t node_id;
1381 const svn_fs_id_t *id;
1383 /* Get a new node-id for this node. */
1384 SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool));
1386 id = svn_fs_fs__id_txn_create(&node_id, copy_id, txn_id, pool);
1390 SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
1394 return SVN_NO_ERROR;
1398 svn_fs_fs__purge_txn(svn_fs_t *fs,
1399 const char *txn_id_str,
1402 fs_fs_data_t *ffd = fs->fsap_data;
1403 svn_fs_fs__id_part_t txn_id;
1404 SVN_ERR(svn_fs_fs__id_txn_parse(&txn_id, txn_id_str));
1406 /* Remove the shared transaction object associated with this transaction. */
1407 SVN_ERR(purge_shared_txn(fs, &txn_id, pool));
1408 /* Remove the directory associated with this transaction. */
1409 SVN_ERR(svn_io_remove_dir2(svn_fs_fs__path_txn_dir(fs, &txn_id, pool),
1410 FALSE, NULL, NULL, pool));
1411 if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1413 /* Delete protorev and its lock, which aren't in the txn
1414 directory. It's OK if they don't exist (for example, if this
1415 is post-commit and the proto-rev has been moved into
1417 SVN_ERR(svn_io_remove_file2(
1418 svn_fs_fs__path_txn_proto_rev(fs, &txn_id, pool),
1420 SVN_ERR(svn_io_remove_file2(
1421 svn_fs_fs__path_txn_proto_rev_lock(fs, &txn_id, pool),
1424 return SVN_NO_ERROR;
1429 svn_fs_fs__abort_txn(svn_fs_txn_t *txn,
1432 SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
1434 /* Now, purge the transaction. */
1435 SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool),
1436 apr_psprintf(pool, _("Transaction '%s' cleanup failed"),
1439 return SVN_NO_ERROR;
1442 /* Assign the UNIQUIFIER member of REP based on the current state of TXN_ID
1443 * in FS. Allocate the uniquifier in POOL.
1445 static svn_error_t *
1446 set_uniquifier(svn_fs_t *fs,
1447 representation_t *rep,
1450 svn_fs_fs__id_part_t temp;
1451 fs_fs_data_t *ffd = fs->fsap_data;
1453 if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
1455 SVN_ERR(get_new_txn_node_id(&temp, fs, &rep->txn_id, pool));
1456 rep->uniquifier.noderev_txn_id = rep->txn_id;
1457 rep->uniquifier.number = temp.number;
1460 return SVN_NO_ERROR;
1463 /* Return TRUE if the TXN_ID member of REP is in use.
1465 static svn_boolean_t
1466 is_txn_rep(const representation_t *rep)
1468 return svn_fs_fs__id_txn_used(&rep->txn_id);
1471 /* Mark the TXN_ID member of REP as "unused".
1474 reset_txn_in_rep(representation_t *rep)
1476 svn_fs_fs__id_txn_reset(&rep->txn_id);
1480 svn_fs_fs__set_entry(svn_fs_t *fs,
1481 const svn_fs_fs__id_part_t *txn_id,
1482 node_revision_t *parent_noderev,
1484 const svn_fs_id_t *id,
1485 svn_node_kind_t kind,
1488 representation_t *rep = parent_noderev->data_rep;
1489 const char *filename
1490 = svn_fs_fs__path_txn_node_children(fs, parent_noderev->id, pool);
1493 fs_fs_data_t *ffd = fs->fsap_data;
1494 apr_pool_t *subpool = svn_pool_create(pool);
1496 if (!rep || !is_txn_rep(rep))
1498 apr_array_header_t *entries;
1500 /* Before we can modify the directory, we need to dump its old
1501 contents into a mutable representation file. */
1502 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
1504 SVN_ERR(svn_io_file_open(&file, filename,
1505 APR_WRITE | APR_CREATE | APR_BUFFERED,
1506 APR_OS_DEFAULT, pool));
1507 out = svn_stream_from_aprfile2(file, TRUE, pool);
1508 SVN_ERR(unparse_dir_entries(entries, out, subpool));
1510 svn_pool_clear(subpool);
1512 /* Mark the node-rev's data rep as mutable. */
1513 rep = apr_pcalloc(pool, sizeof(*rep));
1514 rep->revision = SVN_INVALID_REVNUM;
1515 rep->txn_id = *txn_id;
1516 SVN_ERR(set_uniquifier(fs, rep, pool));
1517 parent_noderev->data_rep = rep;
1518 SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id,
1519 parent_noderev, FALSE, pool));
1523 /* The directory rep is already mutable, so just open it for append. */
1524 SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
1525 APR_OS_DEFAULT, pool));
1526 out = svn_stream_from_aprfile2(file, TRUE, pool);
1529 /* if we have a directory cache for this transaction, update it */
1530 if (ffd->txn_dir_cache)
1532 /* build parameters: (name, new entry) pair */
1534 svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data;
1535 replace_baton_t baton;
1538 baton.new_entry = NULL;
1542 baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry));
1543 baton.new_entry->name = name;
1544 baton.new_entry->kind = kind;
1545 baton.new_entry->id = id;
1548 /* actually update the cached directory (if cached) */
1549 SVN_ERR(svn_cache__set_partial(ffd->txn_dir_cache, key,
1550 svn_fs_fs__replace_dir_entry, &baton,
1553 svn_pool_clear(subpool);
1555 /* Append an incremental hash entry for the entry change. */
1558 svn_fs_dirent_t entry;
1563 SVN_ERR(unparse_dir_entry(&entry, out, subpool));
1567 SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
1568 strlen(name), name));
1571 SVN_ERR(svn_io_file_close(file, subpool));
1572 svn_pool_destroy(subpool);
1573 return SVN_NO_ERROR;
1577 svn_fs_fs__add_change(svn_fs_t *fs,
1578 const svn_fs_fs__id_part_t *txn_id,
1580 const svn_fs_id_t *id,
1581 svn_fs_path_change_kind_t change_kind,
1582 svn_boolean_t text_mod,
1583 svn_boolean_t prop_mod,
1584 svn_boolean_t mergeinfo_mod,
1585 svn_node_kind_t node_kind,
1586 svn_revnum_t copyfrom_rev,
1587 const char *copyfrom_path,
1591 svn_fs_path_change2_t *change;
1592 apr_hash_t *changes = apr_hash_make(pool);
1594 /* Not using APR_BUFFERED to append change in one atomic write operation. */
1595 SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
1596 APR_APPEND | APR_WRITE | APR_CREATE,
1597 APR_OS_DEFAULT, pool));
1599 change = svn_fs__path_change_create_internal(id, change_kind, pool);
1600 change->text_mod = text_mod;
1601 change->prop_mod = prop_mod;
1602 change->mergeinfo_mod = mergeinfo_mod
1604 : svn_tristate_false;
1605 change->node_kind = node_kind;
1606 change->copyfrom_known = TRUE;
1607 change->copyfrom_rev = copyfrom_rev;
1609 change->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
1611 svn_hash_sets(changes, path, change);
1612 SVN_ERR(svn_fs_fs__write_changes(svn_stream_from_aprfile2(file, TRUE, pool),
1613 fs, changes, FALSE, pool));
1615 return svn_io_file_close(file, pool);
1618 /* If the transaction TXN_ID in FS uses logical addressing, store the
1619 * (ITEM_INDEX, OFFSET) pair in the txn's log-to-phys proto index file.
1620 * Use POOL for allocations.
1622 static svn_error_t *
1623 store_l2p_index_entry(svn_fs_t *fs,
1624 const svn_fs_fs__id_part_t *txn_id,
1626 apr_uint64_t item_index,
1629 if (svn_fs_fs__use_log_addressing(fs))
1631 const char *path = svn_fs_fs__path_l2p_proto_index(fs, txn_id, pool);
1633 SVN_ERR(svn_fs_fs__l2p_proto_index_open(&file, path, pool));
1634 SVN_ERR(svn_fs_fs__l2p_proto_index_add_entry(file, offset,
1636 SVN_ERR(svn_io_file_close(file, pool));
1639 return SVN_NO_ERROR;
1642 /* If the transaction TXN_ID in FS uses logical addressing, store ENTRY
1643 * in the phys-to-log proto index file of transaction TXN_ID.
1644 * Use POOL for allocations.
1646 static svn_error_t *
1647 store_p2l_index_entry(svn_fs_t *fs,
1648 const svn_fs_fs__id_part_t *txn_id,
1649 svn_fs_fs__p2l_entry_t *entry,
1652 if (svn_fs_fs__use_log_addressing(fs))
1654 const char *path = svn_fs_fs__path_p2l_proto_index(fs, txn_id, pool);
1656 SVN_ERR(svn_fs_fs__p2l_proto_index_open(&file, path, pool));
1657 SVN_ERR(svn_fs_fs__p2l_proto_index_add_entry(file, entry, pool));
1658 SVN_ERR(svn_io_file_close(file, pool));
1661 return SVN_NO_ERROR;
1664 /* Allocate an item index for the given MY_OFFSET in the transaction TXN_ID
1665 * of file system FS and return it in *ITEM_INDEX. For old formats, it
1666 * will simply return the offset as item index; in new formats, it will
1667 * increment the txn's item index counter file and store the mapping in
1668 * the proto index file. Use POOL for allocations.
1670 static svn_error_t *
1671 allocate_item_index(apr_uint64_t *item_index,
1673 const svn_fs_fs__id_part_t *txn_id,
1674 apr_off_t my_offset,
1677 if (svn_fs_fs__use_log_addressing(fs))
1680 char buffer[SVN_INT64_BUFFER_SIZE] = { 0 };
1681 svn_boolean_t eof = FALSE;
1682 apr_size_t to_write;
1684 apr_off_t offset = 0;
1686 /* read number, increment it and write it back to disk */
1687 SVN_ERR(svn_io_file_open(&file,
1688 svn_fs_fs__path_txn_item_index(fs, txn_id, pool),
1689 APR_READ | APR_WRITE | APR_CREATE | APR_BUFFERED,
1690 APR_OS_DEFAULT, pool));
1691 SVN_ERR(svn_io_file_read_full2(file, buffer, sizeof(buffer)-1,
1692 &read, &eof, pool));
1694 SVN_ERR(svn_cstring_atoui64(item_index, buffer));
1696 *item_index = SVN_FS_FS__ITEM_INDEX_FIRST_USER;
1698 to_write = svn__ui64toa(buffer, *item_index + 1);
1699 SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, pool));
1700 SVN_ERR(svn_io_file_write_full(file, buffer, to_write, NULL, pool));
1701 SVN_ERR(svn_io_file_close(file, pool));
1703 /* write log-to-phys index */
1704 SVN_ERR(store_l2p_index_entry(fs, txn_id, my_offset, *item_index, pool));
1708 *item_index = (apr_uint64_t)my_offset;
1711 return SVN_NO_ERROR;
1714 /* Baton used by fnv1a_write_handler to calculate the FNV checksum
1715 * before passing the data on to the INNER_STREAM.
1717 typedef struct fnv1a_stream_baton_t
1719 svn_stream_t *inner_stream;
1720 svn_checksum_ctx_t *context;
1721 } fnv1a_stream_baton_t;
1723 /* Implement svn_write_fn_t.
1724 * Update checksum and pass data on to inner stream.
1726 static svn_error_t *
1727 fnv1a_write_handler(void *baton,
1731 fnv1a_stream_baton_t *b = baton;
1733 SVN_ERR(svn_checksum_update(b->context, data, *len));
1734 SVN_ERR(svn_stream_write(b->inner_stream, data, len));
1736 return SVN_NO_ERROR;
1739 /* Return a stream that calculates a FNV checksum in *CONTEXT
1740 * over all data written to the stream and passes that data on
1741 * to INNER_STREAM. Allocate objects in POOL.
1743 static svn_stream_t *
1744 fnv1a_wrap_stream(svn_checksum_ctx_t **context,
1745 svn_stream_t *inner_stream,
1748 svn_stream_t *outer_stream;
1750 fnv1a_stream_baton_t *baton = apr_pcalloc(pool, sizeof(*baton));
1751 baton->inner_stream = inner_stream;
1752 baton->context = svn_checksum_ctx_create(svn_checksum_fnv1a_32x4, pool);
1753 *context = baton->context;
1755 outer_stream = svn_stream_create(baton, pool);
1756 svn_stream_set_write(outer_stream, fnv1a_write_handler);
1758 return outer_stream;
1761 /* Set *DIGEST to the FNV checksum calculated in CONTEXT.
1762 * Use SCRATCH_POOL for temporary allocations.
1764 static svn_error_t *
1765 fnv1a_checksum_finalize(apr_uint32_t *digest,
1766 svn_checksum_ctx_t *context,
1767 apr_pool_t *scratch_pool)
1769 svn_checksum_t *checksum;
1771 SVN_ERR(svn_checksum_final(&checksum, context, scratch_pool));
1772 SVN_ERR_ASSERT(checksum->kind == svn_checksum_fnv1a_32x4);
1773 *digest = ntohl(*(const apr_uint32_t *)(checksum->digest));
1775 return SVN_NO_ERROR;
1778 /* This baton is used by the representation writing streams. It keeps
1779 track of the checksum information as well as the total size of the
1780 representation so far. */
1781 struct rep_write_baton
1783 /* The FS we are writing to. */
1786 /* Actual file to which we are writing. */
1787 svn_stream_t *rep_stream;
1789 /* A stream from the delta combiner. Data written here gets
1790 deltified, then eventually written to rep_stream. */
1791 svn_stream_t *delta_stream;
1793 /* Where is this representation header stored. */
1794 apr_off_t rep_offset;
1796 /* Start of the actual data. */
1797 apr_off_t delta_start;
1799 /* How many bytes have been written to this rep already. */
1800 svn_filesize_t rep_size;
1802 /* The node revision for which we're writing out info. */
1803 node_revision_t *noderev;
1805 /* Actual output file. */
1807 /* Lock 'cookie' used to unlock the output file once we've finished
1811 svn_checksum_ctx_t *md5_checksum_ctx;
1812 svn_checksum_ctx_t *sha1_checksum_ctx;
1814 /* calculate a modified FNV-1a checksum of the on-disk representation */
1815 svn_checksum_ctx_t *fnv1a_checksum_ctx;
1817 /* Local / scratch pool, available for temporary allocations. */
1818 apr_pool_t *scratch_pool;
1820 /* Outer / result pool. */
1821 apr_pool_t *result_pool;
1824 /* Handler for the write method of the representation writable stream.
1825 BATON is a rep_write_baton, DATA is the data to write, and *LEN is
1826 the length of this data. */
1827 static svn_error_t *
1828 rep_write_contents(void *baton,
1832 struct rep_write_baton *b = baton;
1834 SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len));
1835 SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len));
1836 b->rep_size += *len;
1838 /* If we are writing a delta, use that stream. */
1839 if (b->delta_stream)
1840 return svn_stream_write(b->delta_stream, data, len);
1842 return svn_stream_write(b->rep_stream, data, len);
1845 /* Set *SPANNED to the number of shards touched when walking WALK steps on
1846 * NODEREV's predecessor chain in FS. Use POOL for temporary allocations.
1848 static svn_error_t *
1849 shards_spanned(int *spanned,
1851 node_revision_t *noderev,
1855 fs_fs_data_t *ffd = fs->fsap_data;
1856 int shard_size = ffd->max_files_per_dir ? ffd->max_files_per_dir : 1;
1857 apr_pool_t *iterpool;
1859 int count = walk ? 1 : 0; /* The start of a walk already touches a shard. */
1860 svn_revnum_t shard, last_shard = ffd->youngest_rev_cache / shard_size;
1861 iterpool = svn_pool_create(pool);
1862 while (walk-- && noderev->predecessor_count)
1864 svn_pool_clear(iterpool);
1865 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs,
1866 noderev->predecessor_id, pool,
1868 shard = svn_fs_fs__id_rev(noderev->id) / shard_size;
1869 if (shard != last_shard)
1875 svn_pool_destroy(iterpool);
1878 return SVN_NO_ERROR;
1881 /* Given a node-revision NODEREV in filesystem FS, return the
1882 representation in *REP to use as the base for a text representation
1883 delta if PROPS is FALSE. If PROPS has been set, a suitable props
1884 base representation will be returned. Perform temporary allocations
1886 static svn_error_t *
1887 choose_delta_base(representation_t **rep,
1889 node_revision_t *noderev,
1890 svn_boolean_t props,
1893 /* The zero-based index (counting from the "oldest" end), along NODEREVs line
1894 * predecessors, of the node-rev we will use as delta base. */
1896 /* The length of the linear part of a delta chain. (Delta chains use
1897 * skip-delta bits for the high-order bits and are linear in the low-order
1900 node_revision_t *base;
1901 fs_fs_data_t *ffd = fs->fsap_data;
1902 apr_pool_t *iterpool;
1904 /* If we have no predecessors, or that one is empty, then use the empty
1905 * stream as a base. */
1906 if (! noderev->predecessor_count)
1909 return SVN_NO_ERROR;
1912 /* Flip the rightmost '1' bit of the predecessor count to determine
1913 which file rev (counting from 0) we want to use. (To see why
1914 count & (count - 1) unsets the rightmost set bit, think about how
1915 you decrement a binary number.) */
1916 count = noderev->predecessor_count;
1917 count = count & (count - 1);
1919 /* Finding the delta base over a very long distance can become extremely
1920 expensive for very deep histories, possibly causing client timeouts etc.
1921 OTOH, this is a rare operation and its gains are minimal. Lets simply
1922 start deltification anew close every other 1000 changes or so. */
1923 walk = noderev->predecessor_count - count;
1924 if (walk > (int)ffd->max_deltification_walk)
1927 return SVN_NO_ERROR;
1930 /* We use skip delta for limiting the number of delta operations
1931 along very long node histories. Close to HEAD however, we create
1932 a linear history to minimize delta size. */
1933 if (walk < (int)ffd->max_linear_deltification)
1936 SVN_ERR(shards_spanned(&shards, fs, noderev, walk, pool));
1938 /* We also don't want the linear deltification to span more shards
1939 than if deltas we used in a simple skip-delta scheme. */
1940 if ((1 << (--shards)) <= walk)
1941 count = noderev->predecessor_count - 1;
1944 /* Walk back a number of predecessors equal to the difference
1945 between count and the original predecessor count. (For example,
1946 if noderev has ten predecessors and we want the eighth file rev,
1947 walk back two predecessors.) */
1949 iterpool = svn_pool_create(pool);
1950 while ((count++) < noderev->predecessor_count)
1952 svn_pool_clear(iterpool);
1953 SVN_ERR(svn_fs_fs__get_node_revision(&base, fs,
1954 base->predecessor_id, pool,
1957 svn_pool_destroy(iterpool);
1959 /* return a suitable base representation */
1960 *rep = props ? base->prop_rep : base->data_rep;
1962 /* if we encountered a shared rep, its parent chain may be different
1963 * from the node-rev parent chain. */
1966 int chain_length = 0;
1967 int shard_count = 0;
1969 /* Very short rep bases are simply not worth it as we are unlikely
1970 * to re-coup the deltification space overhead of 20+ bytes. */
1971 svn_filesize_t rep_size = (*rep)->expanded_size
1972 ? (*rep)->expanded_size
1977 return SVN_NO_ERROR;
1980 /* Check whether the length of the deltification chain is acceptable.
1981 * Otherwise, shared reps may form a non-skipping delta chain in
1983 SVN_ERR(svn_fs_fs__rep_chain_length(&chain_length, &shard_count,
1986 /* Some reasonable limit, depending on how acceptable longer linear
1987 * chains are in this repo. Also, allow for some minimal chain. */
1988 if (chain_length >= 2 * (int)ffd->max_linear_deltification + 2)
1991 /* To make it worth opening additional shards / pack files, we
1992 * require that the reps have a certain minimal size. To deltify
1993 * against a rep in different shard, the lower limit is 512 bytes
1994 * and doubles with every extra shard to visit along the delta
1996 if ( shard_count > 1
1997 && ((svn_filesize_t)128 << shard_count) >= rep_size)
2001 return SVN_NO_ERROR;
2004 /* Something went wrong and the pool for the rep write is being
2005 cleared before we've finished writing the rep. So we need
2006 to remove the rep from the protorevfile and we need to unlock
2007 the protorevfile. */
2009 rep_write_cleanup(void *data)
2011 struct rep_write_baton *b = data;
2014 /* Truncate and close the protorevfile. */
2015 err = svn_io_file_trunc(b->file, b->rep_offset, b->scratch_pool);
2016 err = svn_error_compose_create(err, svn_io_file_close(b->file,
2019 /* Remove our lock regardless of any preceding errors so that the
2020 being_written flag is always removed and stays consistent with the
2021 file lock which will be removed no matter what since the pool is
2023 err = svn_error_compose_create(err,
2024 unlock_proto_rev(b->fs,
2025 svn_fs_fs__id_txn_id(b->noderev->id),
2026 b->lockcookie, b->scratch_pool));
2029 apr_status_t rc = err->apr_err;
2030 svn_error_clear(err);
2037 /* Get a rep_write_baton and store it in *WB_P for the representation
2038 indicated by NODEREV in filesystem FS. Perform allocations in
2039 POOL. Only appropriate for file contents, not for props or
2040 directory contents. */
2041 static svn_error_t *
2042 rep_write_get_baton(struct rep_write_baton **wb_p,
2044 node_revision_t *noderev,
2047 struct rep_write_baton *b;
2049 representation_t *base_rep;
2050 svn_stream_t *source;
2051 svn_txdelta_window_handler_t wh;
2053 fs_fs_data_t *ffd = fs->fsap_data;
2054 int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
2055 svn_fs_fs__rep_header_t header = { 0 };
2057 b = apr_pcalloc(pool, sizeof(*b));
2059 b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
2060 b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
2063 b->result_pool = pool;
2064 b->scratch_pool = svn_pool_create(pool);
2066 b->noderev = noderev;
2068 /* Open the prototype rev file and seek to its end. */
2069 SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie,
2070 fs, svn_fs_fs__id_txn_id(noderev->id),
2074 b->rep_stream = fnv1a_wrap_stream(&b->fnv1a_checksum_ctx,
2075 svn_stream_from_aprfile2(file, TRUE,
2079 SVN_ERR(svn_fs_fs__get_file_offset(&b->rep_offset, file, b->scratch_pool));
2081 /* Get the base for this delta. */
2082 SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->scratch_pool));
2083 SVN_ERR(svn_fs_fs__get_contents(&source, fs, base_rep, TRUE,
2086 /* Write out the rep header. */
2089 header.base_revision = base_rep->revision;
2090 header.base_item_index = base_rep->item_index;
2091 header.base_length = base_rep->size;
2092 header.type = svn_fs_fs__rep_delta;
2096 header.type = svn_fs_fs__rep_self_delta;
2098 SVN_ERR(svn_fs_fs__write_rep_header(&header, b->rep_stream,
2101 /* Now determine the offset of the actual svndiff data. */
2102 SVN_ERR(svn_fs_fs__get_file_offset(&b->delta_start, file,
2105 /* Cleanup in case something goes wrong. */
2106 apr_pool_cleanup_register(b->scratch_pool, b, rep_write_cleanup,
2107 apr_pool_cleanup_null);
2109 /* Prepare to write the svndiff data. */
2110 svn_txdelta_to_svndiff3(&wh,
2114 ffd->delta_compression_level,
2117 b->delta_stream = svn_txdelta_target_push(wh, whb, source,
2122 return SVN_NO_ERROR;
2125 /* For REP->SHA1_CHECKSUM, try to find an already existing representation
2126 in FS and return it in *OUT_REP. If no such representation exists or
2127 if rep sharing has been disabled for FS, NULL will be returned. Since
2128 there may be new duplicate representations within the same uncommitted
2129 revision, those can be passed in REPS_HASH (maps a sha1 digest onto
2130 representation_t*), otherwise pass in NULL for REPS_HASH.
2131 Use RESULT_POOL for *OLD_REP allocations and SCRATCH_POOL for temporaries.
2132 The lifetime of *OLD_REP is limited by both, RESULT_POOL and REP lifetime.
2134 static svn_error_t *
2135 get_shared_rep(representation_t **old_rep,
2137 representation_t *rep,
2138 apr_hash_t *reps_hash,
2139 apr_pool_t *result_pool,
2140 apr_pool_t *scratch_pool)
2143 fs_fs_data_t *ffd = fs->fsap_data;
2145 /* Return NULL, if rep sharing has been disabled. */
2147 if (!ffd->rep_sharing_allowed)
2148 return SVN_NO_ERROR;
2150 /* Check and see if we already have a representation somewhere that's
2151 identical to the one we just wrote out. Start with the hash lookup
2152 because it is cheepest. */
2154 *old_rep = apr_hash_get(reps_hash,
2156 APR_SHA1_DIGESTSIZE);
2158 /* If we haven't found anything yet, try harder and consult our DB. */
2159 if (*old_rep == NULL)
2161 svn_checksum_t checksum;
2162 checksum.digest = rep->sha1_digest;
2163 checksum.kind = svn_checksum_sha1;
2164 err = svn_fs_fs__get_rep_reference(old_rep, fs, &checksum, result_pool);
2165 /* ### Other error codes that we shouldn't mask out? */
2166 if (err == SVN_NO_ERROR)
2169 SVN_ERR(svn_fs_fs__check_rep(*old_rep, fs, NULL, scratch_pool));
2171 else if (err->apr_err == SVN_ERR_FS_CORRUPT
2172 || SVN_ERROR_IN_CATEGORY(err->apr_err,
2173 SVN_ERR_MALFUNC_CATEGORY_START))
2175 /* Fatal error; don't mask it.
2177 In particular, this block is triggered when the rep-cache refers
2178 to revisions in the future. We signal that as a corruption situation
2179 since, once those revisions are less than youngest (because of more
2180 commits), the rep-cache would be invalid.
2186 /* Something's wrong with the rep-sharing index. We can continue
2187 without rep-sharing, but warn.
2189 (fs->warning)(fs->warning_baton, err);
2190 svn_error_clear(err);
2195 /* look for intra-revision matches (usually data reps but not limited
2196 to them in case props happen to look like some data rep)
2198 if (*old_rep == NULL && is_txn_rep(rep))
2200 svn_node_kind_t kind;
2201 const char *file_name
2202 = path_txn_sha1(fs, &rep->txn_id, rep->sha1_digest, scratch_pool);
2204 /* in our txn, is there a rep file named with the wanted SHA1?
2205 If so, read it and use that rep.
2207 SVN_ERR(svn_io_check_path(file_name, &kind, scratch_pool));
2208 if (kind == svn_node_file)
2210 svn_stringbuf_t *rep_string;
2211 SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name,
2213 SVN_ERR(svn_fs_fs__parse_representation(old_rep, rep_string,
2214 result_pool, scratch_pool));
2219 return SVN_NO_ERROR;
2221 /* We don't want 0-length PLAIN representations to replace non-0-length
2222 ones (see issue #4554). Take into account that EXPANDED_SIZE may be
2223 0 in which case we have to check the on-disk SIZE. Also, this doubles
2224 as a simple guard against general rep-cache induced corruption. */
2225 if ( ((*old_rep)->expanded_size != rep->expanded_size)
2226 || ((rep->expanded_size == 0) && ((*old_rep)->size != rep->size)))
2232 /* Add information that is missing in the cached data.
2233 Use the old rep for this content. */
2234 memcpy((*old_rep)->md5_digest, rep->md5_digest, sizeof(rep->md5_digest));
2235 (*old_rep)->uniquifier = rep->uniquifier;
2238 return SVN_NO_ERROR;
2241 /* Copy the hash sum calculation results from MD5_CTX, SHA1_CTX into REP.
2242 * Use POOL for allocations.
2244 static svn_error_t *
2245 digests_final(representation_t *rep,
2246 const svn_checksum_ctx_t *md5_ctx,
2247 const svn_checksum_ctx_t *sha1_ctx,
2250 svn_checksum_t *checksum;
2252 SVN_ERR(svn_checksum_final(&checksum, md5_ctx, pool));
2253 memcpy(rep->md5_digest, checksum->digest, svn_checksum_size(checksum));
2254 SVN_ERR(svn_checksum_final(&checksum, sha1_ctx, pool));
2255 rep->has_sha1 = checksum != NULL;
2257 memcpy(rep->sha1_digest, checksum->digest, svn_checksum_size(checksum));
2259 return SVN_NO_ERROR;
2262 /* Close handler for the representation write stream. BATON is a
2263 rep_write_baton. Writes out a new node-rev that correctly
2264 references the representation we just finished writing. */
2265 static svn_error_t *
2266 rep_write_contents_close(void *baton)
2268 struct rep_write_baton *b = baton;
2269 representation_t *rep;
2270 representation_t *old_rep;
2273 rep = apr_pcalloc(b->result_pool, sizeof(*rep));
2275 /* Close our delta stream so the last bits of svndiff are written
2277 if (b->delta_stream)
2278 SVN_ERR(svn_stream_close(b->delta_stream));
2280 /* Determine the length of the svndiff data. */
2281 SVN_ERR(svn_fs_fs__get_file_offset(&offset, b->file, b->scratch_pool));
2282 rep->size = offset - b->delta_start;
2284 /* Fill in the rest of the representation field. */
2285 rep->expanded_size = b->rep_size;
2286 rep->txn_id = *svn_fs_fs__id_txn_id(b->noderev->id);
2287 SVN_ERR(set_uniquifier(b->fs, rep, b->scratch_pool));
2288 rep->revision = SVN_INVALID_REVNUM;
2290 /* Finalize the checksum. */
2291 SVN_ERR(digests_final(rep, b->md5_checksum_ctx, b->sha1_checksum_ctx,
2294 /* Check and see if we already have a representation somewhere that's
2295 identical to the one we just wrote out. */
2296 SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->result_pool,
2301 /* We need to erase from the protorev the data we just wrote. */
2302 SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->scratch_pool));
2304 /* Use the old rep for this content. */
2305 b->noderev->data_rep = old_rep;
2309 /* Write out our cosmetic end marker. */
2310 SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n"));
2311 SVN_ERR(allocate_item_index(&rep->item_index, b->fs, &rep->txn_id,
2312 b->rep_offset, b->scratch_pool));
2314 b->noderev->data_rep = rep;
2317 /* Remove cleanup callback. */
2318 apr_pool_cleanup_kill(b->scratch_pool, b, rep_write_cleanup);
2320 /* Write out the new node-rev information. */
2321 SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev,
2322 FALSE, b->scratch_pool));
2325 svn_fs_fs__p2l_entry_t entry;
2327 entry.offset = b->rep_offset;
2328 SVN_ERR(svn_fs_fs__get_file_offset(&offset, b->file, b->scratch_pool));
2329 entry.size = offset - b->rep_offset;
2330 entry.type = SVN_FS_FS__ITEM_TYPE_FILE_REP;
2331 entry.item.revision = SVN_INVALID_REVNUM;
2332 entry.item.number = rep->item_index;
2333 SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
2334 b->fnv1a_checksum_ctx,
2337 SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->scratch_pool));
2338 SVN_ERR(store_p2l_index_entry(b->fs, &rep->txn_id, &entry,
2342 SVN_ERR(svn_io_file_close(b->file, b->scratch_pool));
2343 SVN_ERR(unlock_proto_rev(b->fs, &rep->txn_id, b->lockcookie,
2345 svn_pool_destroy(b->scratch_pool);
2347 return SVN_NO_ERROR;
2350 /* Store a writable stream in *CONTENTS_P that will receive all data
2351 written and store it as the file data representation referenced by
2352 NODEREV in filesystem FS. Perform temporary allocations in
2353 POOL. Only appropriate for file data, not props or directory
2355 static svn_error_t *
2356 set_representation(svn_stream_t **contents_p,
2358 node_revision_t *noderev,
2361 struct rep_write_baton *wb;
2363 if (! svn_fs_fs__id_is_txn(noderev->id))
2364 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2365 _("Attempted to write to non-transaction '%s'"),
2366 svn_fs_fs__id_unparse(noderev->id, pool)->data);
2368 SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool));
2370 *contents_p = svn_stream_create(wb, pool);
2371 svn_stream_set_write(*contents_p, rep_write_contents);
2372 svn_stream_set_close(*contents_p, rep_write_contents_close);
2374 return SVN_NO_ERROR;
2378 svn_fs_fs__set_contents(svn_stream_t **stream,
2380 node_revision_t *noderev,
2383 if (noderev->kind != svn_node_file)
2384 return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
2385 _("Can't set text contents of a directory"));
2387 return set_representation(stream, fs, noderev, pool);
2391 svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
2393 const svn_fs_id_t *old_idp,
2394 node_revision_t *new_noderev,
2395 const svn_fs_fs__id_part_t *copy_id,
2396 const svn_fs_fs__id_part_t *txn_id,
2399 const svn_fs_id_t *id;
2402 copy_id = svn_fs_fs__id_copy_id(old_idp);
2403 id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id,
2406 new_noderev->id = id;
2408 if (! new_noderev->copyroot_path)
2410 new_noderev->copyroot_path = apr_pstrdup(pool,
2411 new_noderev->created_path);
2412 new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id);
2415 SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE,
2420 return SVN_NO_ERROR;
2424 svn_fs_fs__set_proplist(svn_fs_t *fs,
2425 node_revision_t *noderev,
2426 apr_hash_t *proplist,
2429 const char *filename
2430 = svn_fs_fs__path_txn_node_props(fs, noderev->id, pool);
2434 /* Dump the property list to the mutable property file. */
2435 SVN_ERR(svn_io_file_open(&file, filename,
2436 APR_WRITE | APR_CREATE | APR_TRUNCATE
2437 | APR_BUFFERED, APR_OS_DEFAULT, pool));
2438 out = svn_stream_from_aprfile2(file, TRUE, pool);
2439 SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool));
2440 SVN_ERR(svn_io_file_close(file, pool));
2442 /* Mark the node-rev's prop rep as mutable, if not already done. */
2443 if (!noderev->prop_rep || !is_txn_rep(noderev->prop_rep))
2445 noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep));
2446 noderev->prop_rep->txn_id = *svn_fs_fs__id_txn_id(noderev->id);
2447 SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE,
2451 return SVN_NO_ERROR;
2454 /* This baton is used by the stream created for write_container_rep. */
2455 struct write_container_baton
2457 svn_stream_t *stream;
2461 svn_checksum_ctx_t *md5_ctx;
2462 svn_checksum_ctx_t *sha1_ctx;
2465 /* The handler for the write_container_rep stream. BATON is a
2466 write_container_baton, DATA has the data to write and *LEN is the number
2467 of bytes to write. */
2468 static svn_error_t *
2469 write_container_handler(void *baton,
2473 struct write_container_baton *whb = baton;
2475 SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
2476 SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
2478 SVN_ERR(svn_stream_write(whb->stream, data, len));
2481 return SVN_NO_ERROR;
2484 /* Callback function type. Write the data provided by BATON into STREAM. */
2485 typedef svn_error_t *
2486 (* collection_writer_t)(svn_stream_t *stream, void *baton, apr_pool_t *pool);
2488 /* Implement collection_writer_t writing the C string->svn_string_t hash
2490 static svn_error_t *
2491 write_hash_to_stream(svn_stream_t *stream,
2495 apr_hash_t *hash = baton;
2496 SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
2498 return SVN_NO_ERROR;
2501 /* Implement collection_writer_t writing the svn_fs_dirent_t* array given
2503 static svn_error_t *
2504 write_directory_to_stream(svn_stream_t *stream,
2508 apr_array_header_t *dir = baton;
2509 SVN_ERR(unparse_dir_entries(dir, stream, pool));
2511 return SVN_NO_ERROR;
2514 /* Write out the COLLECTION as a text representation to file FILE using
2515 WRITER. In the process, record position, the total size of the dump and
2516 MD5 as well as SHA1 in REP. Add the representation of type ITEM_TYPE to
2517 the indexes if necessary. If rep sharing has been enabled and REPS_HASH
2518 is not NULL, it will be used in addition to the on-disk cache to find
2519 earlier reps with the same content. When such existing reps can be
2520 found, we will truncate the one just written from the file and return
2521 the existing rep. Perform temporary allocations in SCRATCH_POOL. */
2522 static svn_error_t *
2523 write_container_rep(representation_t *rep,
2526 collection_writer_t writer,
2528 apr_hash_t *reps_hash,
2529 apr_uint32_t item_type,
2530 apr_pool_t *scratch_pool)
2532 svn_stream_t *stream;
2533 struct write_container_baton *whb;
2534 svn_checksum_ctx_t *fnv1a_checksum_ctx;
2535 representation_t *old_rep;
2536 apr_off_t offset = 0;
2538 SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
2540 whb = apr_pcalloc(scratch_pool, sizeof(*whb));
2542 whb->stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
2543 svn_stream_from_aprfile2(file, TRUE,
2547 whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool);
2548 whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool);
2550 stream = svn_stream_create(whb, scratch_pool);
2551 svn_stream_set_write(stream, write_container_handler);
2553 SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n"));
2555 SVN_ERR(writer(stream, collection, scratch_pool));
2557 /* Store the results. */
2558 SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool));
2560 /* Check and see if we already have a representation somewhere that's
2561 identical to the one we just wrote out. */
2562 SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, scratch_pool,
2567 /* We need to erase from the protorev the data we just wrote. */
2568 SVN_ERR(svn_io_file_trunc(file, offset, scratch_pool));
2570 /* Use the old rep for this content. */
2571 memcpy(rep, old_rep, sizeof (*rep));
2575 svn_fs_fs__p2l_entry_t entry;
2577 /* Write out our cosmetic end marker. */
2578 SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n"));
2580 SVN_ERR(allocate_item_index(&rep->item_index, fs, &rep->txn_id,
2581 offset, scratch_pool));
2583 entry.offset = offset;
2584 SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
2585 entry.size = offset - entry.offset;
2586 entry.type = item_type;
2587 entry.item.revision = SVN_INVALID_REVNUM;
2588 entry.item.number = rep->item_index;
2589 SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
2593 SVN_ERR(store_p2l_index_entry(fs, &rep->txn_id, &entry, scratch_pool));
2595 /* update the representation */
2596 rep->size = whb->size;
2597 rep->expanded_size = whb->size;
2600 return SVN_NO_ERROR;
2603 /* Write out the COLLECTION pertaining to the NODEREV in FS as a deltified
2604 text representation to file FILE using WRITER. In the process, record the
2605 total size and the md5 digest in REP and add the representation of type
2606 ITEM_TYPE to the indexes if necessary. If rep sharing has been enabled and
2607 REPS_HASH is not NULL, it will be used in addition to the on-disk cache to
2608 find earlier reps with the same content. When such existing reps can be
2609 found, we will truncate the one just written from the file and return the
2612 If ITEM_TYPE is IS_PROPS equals SVN_FS_FS__ITEM_TYPE_*_PROPS, assume
2613 that we want to a props representation as the base for our delta.
2614 Perform temporary allocations in SCRATCH_POOL.
2616 static svn_error_t *
2617 write_container_delta_rep(representation_t *rep,
2620 collection_writer_t writer,
2622 node_revision_t *noderev,
2623 apr_hash_t *reps_hash,
2624 apr_uint32_t item_type,
2625 apr_pool_t *scratch_pool)
2627 svn_txdelta_window_handler_t diff_wh;
2630 svn_stream_t *file_stream;
2631 svn_stream_t *stream;
2632 representation_t *base_rep;
2633 representation_t *old_rep;
2634 svn_checksum_ctx_t *fnv1a_checksum_ctx;
2635 svn_stream_t *source;
2636 svn_fs_fs__rep_header_t header = { 0 };
2638 apr_off_t rep_end = 0;
2639 apr_off_t delta_start = 0;
2640 apr_off_t offset = 0;
2642 struct write_container_baton *whb;
2643 fs_fs_data_t *ffd = fs->fsap_data;
2644 int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
2645 svn_boolean_t is_props = (item_type == SVN_FS_FS__ITEM_TYPE_FILE_PROPS)
2646 || (item_type == SVN_FS_FS__ITEM_TYPE_DIR_PROPS);
2648 /* Get the base for this delta. */
2649 SVN_ERR(choose_delta_base(&base_rep, fs, noderev, is_props, scratch_pool));
2650 SVN_ERR(svn_fs_fs__get_contents(&source, fs, base_rep, FALSE, scratch_pool));
2652 SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
2654 /* Write out the rep header. */
2657 header.base_revision = base_rep->revision;
2658 header.base_item_index = base_rep->item_index;
2659 header.base_length = base_rep->size;
2660 header.type = svn_fs_fs__rep_delta;
2664 header.type = svn_fs_fs__rep_self_delta;
2667 file_stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
2668 svn_stream_from_aprfile2(file, TRUE,
2671 SVN_ERR(svn_fs_fs__write_rep_header(&header, file_stream, scratch_pool));
2672 SVN_ERR(svn_fs_fs__get_file_offset(&delta_start, file, scratch_pool));
2674 /* Prepare to write the svndiff data. */
2675 svn_txdelta_to_svndiff3(&diff_wh,
2679 ffd->delta_compression_level,
2682 whb = apr_pcalloc(scratch_pool, sizeof(*whb));
2683 whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source,
2686 whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool);
2687 whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool);
2689 /* serialize the hash */
2690 stream = svn_stream_create(whb, scratch_pool);
2691 svn_stream_set_write(stream, write_container_handler);
2693 SVN_ERR(writer(stream, collection, scratch_pool));
2694 SVN_ERR(svn_stream_close(whb->stream));
2696 /* Store the results. */
2697 SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool));
2699 /* Check and see if we already have a representation somewhere that's
2700 identical to the one we just wrote out. */
2701 SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, scratch_pool,
2706 /* We need to erase from the protorev the data we just wrote. */
2707 SVN_ERR(svn_io_file_trunc(file, offset, scratch_pool));
2709 /* Use the old rep for this content. */
2710 memcpy(rep, old_rep, sizeof (*rep));
2714 svn_fs_fs__p2l_entry_t entry;
2716 /* Write out our cosmetic end marker. */
2717 SVN_ERR(svn_fs_fs__get_file_offset(&rep_end, file, scratch_pool));
2718 SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n"));
2720 SVN_ERR(allocate_item_index(&rep->item_index, fs, &rep->txn_id,
2721 offset, scratch_pool));
2723 entry.offset = offset;
2724 SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
2725 entry.size = offset - entry.offset;
2726 entry.type = item_type;
2727 entry.item.revision = SVN_INVALID_REVNUM;
2728 entry.item.number = rep->item_index;
2729 SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
2733 SVN_ERR(store_p2l_index_entry(fs, &rep->txn_id, &entry, scratch_pool));
2735 /* update the representation */
2736 rep->expanded_size = whb->size;
2737 rep->size = rep_end - delta_start;
2740 return SVN_NO_ERROR;
2743 /* Sanity check ROOT_NODEREV, a candidate for being the root node-revision
2744 of (not yet committed) revision REV in FS. Use POOL for temporary
2747 If you change this function, consider updating svn_fs_fs__verify() too.
2749 static svn_error_t *
2750 validate_root_noderev(svn_fs_t *fs,
2751 node_revision_t *root_noderev,
2755 svn_revnum_t head_revnum = rev-1;
2756 int head_predecessor_count;
2758 SVN_ERR_ASSERT(rev > 0);
2760 /* Compute HEAD_PREDECESSOR_COUNT. */
2762 svn_fs_root_t *head_revision;
2763 const svn_fs_id_t *head_root_id;
2764 node_revision_t *head_root_noderev;
2766 /* Get /@HEAD's noderev. */
2767 SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool));
2768 SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool));
2769 SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id,
2771 head_predecessor_count = head_root_noderev->predecessor_count;
2774 /* Check that the root noderev's predecessor count equals REV.
2776 This kind of corruption was seen on svn.apache.org (both on
2777 the root noderev and on other fspaths' noderevs); see
2780 Normally (rev == root_noderev->predecessor_count), but here we
2781 use a more roundabout check that should only trigger on new instances
2782 of the corruption, rather then trigger on each and every new commit
2783 to a repository that has triggered the bug somewhere in its root
2786 if (root_noderev->predecessor_count != -1
2787 && (root_noderev->predecessor_count - head_predecessor_count)
2788 != (rev - head_revnum))
2790 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2791 _("predecessor count for "
2792 "the root node-revision is wrong: "
2793 "found (%d+%ld != %d), committing r%ld"),
2794 head_predecessor_count,
2795 rev - head_revnum, /* This is equal to 1. */
2796 root_noderev->predecessor_count,
2800 return SVN_NO_ERROR;
2803 /* Given the potentially txn-local id PART, update that to a permanent ID
2804 * based on the REVISION currently being written and the START_ID for that
2805 * revision. Use the repo FORMAT to decide which implementation to use.
2808 get_final_id(svn_fs_fs__id_part_t *part,
2809 svn_revnum_t revision,
2810 apr_uint64_t start_id,
2813 if (part->revision == SVN_INVALID_REVNUM)
2815 if (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
2817 part->revision = revision;
2822 part->number += start_id;
2827 /* Copy a node-revision specified by id ID in fileystem FS from a
2828 transaction into the proto-rev-file FILE. Set *NEW_ID_P to a
2829 pointer to the new node-id which will be allocated in POOL.
2830 If this is a directory, copy all children as well.
2832 START_NODE_ID and START_COPY_ID are
2833 the first available node and copy ids for this filesystem, for older
2836 REV is the revision number that this proto-rev-file will represent.
2838 INITIAL_OFFSET is the offset of the proto-rev-file on entry to
2841 If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
2842 REPS_POOL) of each data rep that is new in this revision.
2844 If REPS_HASH is not NULL, append copies (allocated in REPS_POOL)
2845 of the representations of each property rep that is new in this
2848 AT_ROOT is true if the node revision being written is the root
2849 node-revision. It is only controls additional sanity checking
2852 Temporary allocations are also from POOL. */
2853 static svn_error_t *
2854 write_final_rev(const svn_fs_id_t **new_id_p,
2858 const svn_fs_id_t *id,
2859 apr_uint64_t start_node_id,
2860 apr_uint64_t start_copy_id,
2861 apr_off_t initial_offset,
2862 apr_array_header_t *reps_to_cache,
2863 apr_hash_t *reps_hash,
2864 apr_pool_t *reps_pool,
2865 svn_boolean_t at_root,
2868 node_revision_t *noderev;
2869 apr_off_t my_offset;
2870 const svn_fs_id_t *new_id;
2871 svn_fs_fs__id_part_t node_id, copy_id, rev_item;
2872 fs_fs_data_t *ffd = fs->fsap_data;
2873 const svn_fs_fs__id_part_t *txn_id = svn_fs_fs__id_txn_id(id);
2874 svn_stream_t *file_stream;
2875 svn_checksum_ctx_t *fnv1a_checksum_ctx;
2876 apr_pool_t *subpool;
2880 /* Check to see if this is a transaction node. */
2881 if (! svn_fs_fs__id_is_txn(id))
2882 return SVN_NO_ERROR;
2884 subpool = svn_pool_create(pool);
2885 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool, subpool));
2887 if (noderev->kind == svn_node_dir)
2889 apr_array_header_t *entries;
2892 /* This is a directory. Write out all the children first. */
2894 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool,
2896 for (i = 0; i < entries->nelts; ++i)
2898 svn_fs_dirent_t *dirent
2899 = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
2901 svn_pool_clear(subpool);
2902 SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
2903 start_node_id, start_copy_id, initial_offset,
2904 reps_to_cache, reps_hash, reps_pool, FALSE,
2906 if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
2907 dirent->id = svn_fs_fs__id_copy(new_id, pool);
2910 if (noderev->data_rep && is_txn_rep(noderev->data_rep))
2912 /* Write out the contents of this directory as a text rep. */
2913 noderev->data_rep->revision = rev;
2914 if (ffd->deltify_directories)
2915 SVN_ERR(write_container_delta_rep(noderev->data_rep, file,
2917 write_directory_to_stream,
2919 SVN_FS_FS__ITEM_TYPE_DIR_REP,
2922 SVN_ERR(write_container_rep(noderev->data_rep, file, entries,
2923 write_directory_to_stream, fs, NULL,
2924 SVN_FS_FS__ITEM_TYPE_DIR_REP, pool));
2926 reset_txn_in_rep(noderev->data_rep);
2931 /* This is a file. We should make sure the data rep, if it
2932 exists in a "this" state, gets rewritten to our new revision
2935 if (noderev->data_rep && is_txn_rep(noderev->data_rep))
2937 reset_txn_in_rep(noderev->data_rep);
2938 noderev->data_rep->revision = rev;
2940 if (!svn_fs_fs__use_log_addressing(fs))
2942 /* See issue 3845. Some unknown mechanism caused the
2943 protorev file to get truncated, so check for that
2945 if (noderev->data_rep->item_index + noderev->data_rep->size
2947 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2948 _("Truncated protorev file detected"));
2953 svn_pool_destroy(subpool);
2955 /* Fix up the property reps. */
2956 if (noderev->prop_rep && is_txn_rep(noderev->prop_rep))
2958 apr_hash_t *proplist;
2959 apr_uint32_t item_type = noderev->kind == svn_node_dir
2960 ? SVN_FS_FS__ITEM_TYPE_DIR_PROPS
2961 : SVN_FS_FS__ITEM_TYPE_FILE_PROPS;
2962 SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
2964 noderev->prop_rep->revision = rev;
2966 if (ffd->deltify_properties)
2967 SVN_ERR(write_container_delta_rep(noderev->prop_rep, file, proplist,
2968 write_hash_to_stream, fs, noderev,
2969 reps_hash, item_type, pool));
2971 SVN_ERR(write_container_rep(noderev->prop_rep, file, proplist,
2972 write_hash_to_stream, fs, reps_hash,
2975 reset_txn_in_rep(noderev->prop_rep);
2978 /* Convert our temporary ID into a permanent revision one. */
2979 node_id = *svn_fs_fs__id_node_id(noderev->id);
2980 get_final_id(&node_id, rev, start_node_id, ffd->format);
2981 copy_id = *svn_fs_fs__id_copy_id(noderev->id);
2982 get_final_id(©_id, rev, start_copy_id, ffd->format);
2984 if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
2985 noderev->copyroot_rev = rev;
2987 /* root nodes have a fixed ID in log addressing mode */
2988 SVN_ERR(svn_fs_fs__get_file_offset(&my_offset, file, pool));
2989 if (svn_fs_fs__use_log_addressing(fs) && at_root)
2991 /* reference the root noderev from the log-to-phys index */
2992 rev_item.number = SVN_FS_FS__ITEM_INDEX_ROOT_NODE;
2993 SVN_ERR(store_l2p_index_entry(fs, txn_id, my_offset,
2994 rev_item.number, pool));
2997 SVN_ERR(allocate_item_index(&rev_item.number, fs, txn_id,
3000 rev_item.revision = rev;
3001 new_id = svn_fs_fs__id_rev_create(&node_id, ©_id, &rev_item, pool);
3003 noderev->id = new_id;
3005 if (ffd->rep_sharing_allowed)
3007 /* Save the data representation's hash in the rep cache. */
3008 if ( noderev->data_rep && noderev->kind == svn_node_file
3009 && noderev->data_rep->revision == rev)
3011 SVN_ERR_ASSERT(reps_to_cache && reps_pool);
3012 APR_ARRAY_PUSH(reps_to_cache, representation_t *)
3013 = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool);
3016 if (noderev->prop_rep && noderev->prop_rep->revision == rev)
3018 /* Add new property reps to hash and on-disk cache. */
3019 representation_t *copy
3020 = svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool);
3022 SVN_ERR_ASSERT(reps_to_cache && reps_pool);
3023 APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy;
3025 apr_hash_set(reps_hash,
3027 APR_SHA1_DIGESTSIZE,
3032 /* don't serialize SHA1 for dirs to disk (waste of space) */
3033 if (noderev->data_rep && noderev->kind == svn_node_dir)
3034 noderev->data_rep->has_sha1 = FALSE;
3036 /* don't serialize SHA1 for props to disk (waste of space) */
3037 if (noderev->prop_rep)
3038 noderev->prop_rep->has_sha1 = FALSE;
3040 /* Workaround issue #4031: is-fresh-txn-root in revision files. */
3041 noderev->is_fresh_txn_root = FALSE;
3043 /* Write out our new node-revision. */
3045 SVN_ERR(validate_root_noderev(fs, noderev, rev, pool));
3047 file_stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
3048 svn_stream_from_aprfile2(file, TRUE, pool),
3050 SVN_ERR(svn_fs_fs__write_noderev(file_stream, noderev, ffd->format,
3051 svn_fs_fs__fs_supports_mergeinfo(fs),
3054 /* reference the root noderev from the log-to-phys index */
3055 if (svn_fs_fs__use_log_addressing(fs))
3057 svn_fs_fs__p2l_entry_t entry;
3058 rev_item.revision = SVN_INVALID_REVNUM;
3060 entry.offset = my_offset;
3061 SVN_ERR(svn_fs_fs__get_file_offset(&my_offset, file, pool));
3062 entry.size = my_offset - entry.offset;
3063 entry.type = SVN_FS_FS__ITEM_TYPE_NODEREV;
3064 entry.item = rev_item;
3065 SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
3069 SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, pool));
3072 /* Return our ID that references the revision file. */
3073 *new_id_p = noderev->id;
3075 return SVN_NO_ERROR;
3078 /* Write the changed path info CHANGED_PATHS from transaction TXN_ID to the
3079 permanent rev-file FILE in filesystem FS. *OFFSET_P is set the to offset
3080 in the file of the beginning of this information. Perform temporary
3081 allocations in POOL. */
3082 static svn_error_t *
3083 write_final_changed_path_info(apr_off_t *offset_p,
3086 const svn_fs_fs__id_part_t *txn_id,
3087 apr_hash_t *changed_paths,
3091 svn_stream_t *stream;
3092 svn_checksum_ctx_t *fnv1a_checksum_ctx;
3094 SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool));
3096 /* write to target file & calculate checksum */
3097 stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
3098 svn_stream_from_aprfile2(file, TRUE, pool),
3100 SVN_ERR(svn_fs_fs__write_changes(stream, fs, changed_paths, TRUE, pool));
3104 /* reference changes from the indexes */
3105 if (svn_fs_fs__use_log_addressing(fs))
3107 svn_fs_fs__p2l_entry_t entry;
3109 entry.offset = offset;
3110 SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool));
3111 entry.size = offset - entry.offset;
3112 entry.type = SVN_FS_FS__ITEM_TYPE_CHANGES;
3113 entry.item.revision = SVN_INVALID_REVNUM;
3114 entry.item.number = SVN_FS_FS__ITEM_INDEX_CHANGES;
3115 SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
3119 SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, pool));
3120 SVN_ERR(store_l2p_index_entry(fs, txn_id, entry.offset,
3121 SVN_FS_FS__ITEM_INDEX_CHANGES, pool));
3124 return SVN_NO_ERROR;
3127 /* Open a new svn_fs_t handle to FS, set that handle's concept of "current
3128 youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on
3129 NEW_REV's revision root.
3131 Intended to be called as the very last step in a commit before 'current'
3132 is bumped. This implies that we are holding the write lock. */
3133 static svn_error_t *
3134 verify_as_revision_before_current_plus_plus(svn_fs_t *fs,
3135 svn_revnum_t new_rev,
3139 fs_fs_data_t *ffd = fs->fsap_data;
3140 svn_fs_t *ft; /* fs++ == ft */
3141 svn_fs_root_t *root;
3142 fs_fs_data_t *ft_ffd;
3143 apr_hash_t *fs_config;
3145 SVN_ERR_ASSERT(ffd->svn_fs_open_);
3147 /* make sure FT does not simply return data cached by other instances
3148 * but actually retrieves it from disk at least once.
3150 fs_config = apr_hash_make(pool);
3151 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
3152 svn_uuid_generate(pool));
3153 SVN_ERR(ffd->svn_fs_open_(&ft, fs->path,
3157 ft_ffd = ft->fsap_data;
3158 /* Don't let FT consult rep-cache.db, either. */
3159 ft_ffd->rep_sharing_allowed = FALSE;
3162 ft_ffd->youngest_rev_cache = new_rev;
3164 SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool));
3165 SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev);
3166 SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev);
3167 SVN_ERR(svn_fs_fs__verify_root(root, pool));
3168 #endif /* SVN_DEBUG */
3170 return SVN_NO_ERROR;
3173 /* Update the 'current' file to hold the correct next node and copy_ids
3174 from transaction TXN_ID in filesystem FS. The current revision is
3175 set to REV. Perform temporary allocations in POOL. */
3176 static svn_error_t *
3177 write_final_current(svn_fs_t *fs,
3178 const svn_fs_fs__id_part_t *txn_id,
3180 apr_uint64_t start_node_id,
3181 apr_uint64_t start_copy_id,
3184 apr_uint64_t txn_node_id;
3185 apr_uint64_t txn_copy_id;
3186 fs_fs_data_t *ffd = fs->fsap_data;
3188 if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
3189 return svn_fs_fs__write_current(fs, rev, 0, 0, pool);
3191 /* To find the next available ids, we add the id that used to be in
3192 the 'current' file, to the next ids from the transaction file. */
3193 SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool));
3195 start_node_id += txn_node_id;
3196 start_copy_id += txn_copy_id;
3198 return svn_fs_fs__write_current(fs, rev, start_node_id, start_copy_id,
3202 /* Verify that the user registered with FS has all the locks necessary to
3203 permit all the changes associated with TXN_NAME.
3204 The FS write lock is assumed to be held by the caller. */
3205 static svn_error_t *
3206 verify_locks(svn_fs_t *fs,
3207 const svn_fs_fs__id_part_t *txn_id,
3208 apr_hash_t *changed_paths,
3211 apr_pool_t *iterpool;
3212 apr_array_header_t *changed_paths_sorted;
3213 svn_stringbuf_t *last_recursed = NULL;
3216 /* Make an array of the changed paths, and sort them depth-first-ily. */
3217 changed_paths_sorted = svn_sort__hash(changed_paths,
3218 svn_sort_compare_items_as_paths,
3221 /* Now, traverse the array of changed paths, verify locks. Note
3222 that if we need to do a recursive verification a path, we'll skip
3223 over children of that path when we get to them. */
3224 iterpool = svn_pool_create(pool);
3225 for (i = 0; i < changed_paths_sorted->nelts; i++)
3227 const svn_sort__item_t *item;
3229 svn_fs_path_change2_t *change;
3230 svn_boolean_t recurse = TRUE;
3232 svn_pool_clear(iterpool);
3234 item = &APR_ARRAY_IDX(changed_paths_sorted, i, svn_sort__item_t);
3236 /* Fetch the change associated with our path. */
3238 change = item->value;
3240 /* If this path has already been verified as part of a recursive
3241 check of one of its parents, no need to do it again. */
3243 && svn_fspath__skip_ancestor(last_recursed->data, path))
3246 /* What does it mean to succeed at lock verification for a given
3247 path? For an existing file or directory getting modified
3248 (text, props), it means we hold the lock on the file or
3249 directory. For paths being added or removed, we need to hold
3250 the locks for that path and any children of that path.
3252 WHEW! We have no reliable way to determine the node kind
3253 of deleted items, but fortunately we are going to do a
3254 recursive check on deleted paths regardless of their kind. */
3255 if (change->change_kind == svn_fs_path_change_modify)
3257 SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE,
3260 /* If we just did a recursive check, remember the path we
3261 checked (so children can be skipped). */
3264 if (! last_recursed)
3265 last_recursed = svn_stringbuf_create(path, pool);
3267 svn_stringbuf_set(last_recursed, path);
3270 svn_pool_destroy(iterpool);
3271 return SVN_NO_ERROR;
3274 /* Return in *PATH the path to a file containing the properties that
3275 make up the final revision properties file. This involves setting
3276 svn:date and removing any temporary properties associated with the
3278 static svn_error_t *
3279 write_final_revprop(const char **path,
3281 const svn_fs_fs__id_part_t *txn_id,
3284 apr_hash_t *txnprops;
3285 svn_boolean_t final_mods = FALSE;
3287 svn_string_t *client_date;
3289 SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, txn, pool));
3291 /* Remove any temporary txn props representing 'flags'. */
3292 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
3294 svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD, NULL);
3298 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
3300 svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL);
3304 client_date = svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE);
3307 svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE, NULL);
3311 /* Update commit time to ensure that svn:date revprops remain ordered if
3313 if (!client_date || strcmp(client_date->data, "1"))
3315 date.data = svn_time_to_cstring(apr_time_now(), pool);
3316 date.len = strlen(date.data);
3317 svn_hash_sets(txnprops, SVN_PROP_REVISION_DATE, &date);
3323 SVN_ERR(set_txn_proplist(txn->fs, txn_id, txnprops, TRUE, pool));
3324 *path = path_txn_props_final(txn->fs, txn_id, pool);
3328 *path = path_txn_props(txn->fs, txn_id, pool);
3331 return SVN_NO_ERROR;
3335 svn_fs_fs__add_index_data(svn_fs_t *fs,
3337 const char *l2p_proto_index,
3338 const char *p2l_proto_index,
3339 svn_revnum_t revision,
3342 apr_off_t l2p_offset;
3343 apr_off_t p2l_offset;
3344 svn_stringbuf_t *footer;
3345 unsigned char footer_length;
3346 svn_checksum_t *l2p_checksum;
3347 svn_checksum_t *p2l_checksum;
3349 /* Append the actual index data to the pack file. */
3351 SVN_ERR(svn_io_file_seek(file, APR_END, &l2p_offset, pool));
3352 SVN_ERR(svn_fs_fs__l2p_index_append(&l2p_checksum, fs, file,
3353 l2p_proto_index, revision,
3357 SVN_ERR(svn_io_file_seek(file, APR_END, &p2l_offset, pool));
3358 SVN_ERR(svn_fs_fs__p2l_index_append(&p2l_checksum, fs, file,
3359 p2l_proto_index, revision,
3362 /* Append footer. */
3363 footer = svn_fs_fs__unparse_footer(l2p_offset, l2p_checksum,
3364 p2l_offset, p2l_checksum, pool, pool);
3365 SVN_ERR(svn_io_file_write_full(file, footer->data, footer->len, NULL,
3368 footer_length = footer->len;
3369 SVN_ERR_ASSERT(footer_length == footer->len);
3370 SVN_ERR(svn_io_file_write_full(file, &footer_length, 1, NULL, pool));
3372 return SVN_NO_ERROR;
3375 /* Baton used for commit_body below. */
3376 struct commit_baton {
3377 svn_revnum_t *new_rev_p;
3380 apr_array_header_t *reps_to_cache;
3381 apr_hash_t *reps_hash;
3382 apr_pool_t *reps_pool;
3385 /* The work-horse for svn_fs_fs__commit, called with the FS write lock.
3386 This implements the svn_fs_fs__with_write_lock() 'body' callback
3387 type. BATON is a 'struct commit_baton *'. */
3388 static svn_error_t *
3389 commit_body(void *baton, apr_pool_t *pool)
3391 struct commit_baton *cb = baton;
3392 fs_fs_data_t *ffd = cb->fs->fsap_data;
3393 const char *old_rev_filename, *rev_filename, *proto_filename;
3394 const char *revprop_filename, *final_revprop;
3395 const svn_fs_id_t *root_id, *new_root_id;
3396 apr_uint64_t start_node_id;
3397 apr_uint64_t start_copy_id;
3398 svn_revnum_t old_rev, new_rev;
3399 apr_file_t *proto_file;
3400 void *proto_file_lockcookie;
3401 apr_off_t initial_offset, changed_path_offset;
3402 const svn_fs_fs__id_part_t *txn_id = svn_fs_fs__txn_get_id(cb->txn);
3403 apr_hash_t *changed_paths;
3405 /* Re-Read the current repository format. All our repo upgrade and
3406 config evaluation strategies are such that existing information in
3407 FS and FFD remains valid.
3409 Although we don't recommend upgrading hot repositories, people may
3410 still do it and we must make sure to either handle them gracefully
3413 Committing pre-format 3 txns will fail after upgrade to format 3+
3414 because the proto-rev cannot be found; no further action needed.
3415 Upgrades from pre-f7 to f7+ means a potential change in addressing
3416 mode for the final rev. We must be sure to detect that cause because
3417 the failure would only manifest once the new revision got committed.
3419 SVN_ERR(svn_fs_fs__read_format_file(cb->fs, pool));
3421 /* Read the current youngest revision and, possibly, the next available
3422 node id and copy id (for old format filesystems). Update the cached
3423 value for the youngest revision, because we have just checked it. */
3424 SVN_ERR(svn_fs_fs__read_current(&old_rev, &start_node_id, &start_copy_id,
3426 ffd->youngest_rev_cache = old_rev;
3428 /* Check to make sure this transaction is based off the most recent
3430 if (cb->txn->base_rev != old_rev)
3431 return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
3432 _("Transaction out of date"));
3434 /* We need the changes list for verification as well as for writing it
3435 to the final rev file. */
3436 SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, cb->fs, txn_id,
3439 /* Locks may have been added (or stolen) between the calling of
3440 previous svn_fs.h functions and svn_fs_commit_txn(), so we need
3441 to re-examine every changed-path in the txn and re-verify all
3442 discovered locks. */
3443 SVN_ERR(verify_locks(cb->fs, txn_id, changed_paths, pool));
3445 /* We are going to be one better than this puny old revision. */
3446 new_rev = old_rev + 1;
3448 /* Get a write handle on the proto revision file. */
3449 SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
3450 cb->fs, txn_id, pool));
3451 SVN_ERR(svn_fs_fs__get_file_offset(&initial_offset, proto_file, pool));
3453 /* Write out all the node-revisions and directory contents. */
3454 root_id = svn_fs_fs__id_txn_create_root(txn_id, pool);
3455 SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
3456 start_node_id, start_copy_id, initial_offset,
3457 cb->reps_to_cache, cb->reps_hash, cb->reps_pool,
3460 /* Write the changed-path information. */
3461 SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
3462 cb->fs, txn_id, changed_paths,
3465 if (svn_fs_fs__use_log_addressing(cb->fs))
3467 /* Append the index data to the rev file. */
3468 SVN_ERR(svn_fs_fs__add_index_data(cb->fs, proto_file,
3469 svn_fs_fs__path_l2p_proto_index(cb->fs, txn_id, pool),
3470 svn_fs_fs__path_p2l_proto_index(cb->fs, txn_id, pool),
3475 /* Write the final line. */
3477 svn_stringbuf_t *trailer
3478 = svn_fs_fs__unparse_revision_trailer
3479 ((apr_off_t)svn_fs_fs__id_item(new_root_id),
3480 changed_path_offset,
3482 SVN_ERR(svn_io_file_write_full(proto_file, trailer->data, trailer->len,
3486 SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
3487 SVN_ERR(svn_io_file_close(proto_file, pool));
3489 /* We don't unlock the prototype revision file immediately to avoid a
3490 race with another caller writing to the prototype revision file
3491 before we commit it. */
3493 /* Create the shard for the rev and revprop file, if we're sharding and
3494 this is the first revision of a new shard. We don't care if this
3495 fails because the shard already existed for some reason. */
3496 if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0)
3498 /* Create the revs shard. */
3501 = svn_fs_fs__path_rev_shard(cb->fs, new_rev, pool);
3503 = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
3504 if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
3505 return svn_error_trace(err);
3506 svn_error_clear(err);
3507 SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
3513 /* Create the revprops shard. */
3514 SVN_ERR_ASSERT(! svn_fs_fs__is_packed_revprop(cb->fs, new_rev));
3517 = svn_fs_fs__path_revprops_shard(cb->fs, new_rev, pool);
3519 = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
3520 if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
3521 return svn_error_trace(err);
3522 svn_error_clear(err);
3523 SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
3530 /* Move the finished rev file into place.
3532 ### This "breaks" the transaction by removing the protorev file
3533 ### but the revision is not yet complete. If this commit does
3534 ### not complete for any reason the transaction will be lost. */
3535 old_rev_filename = svn_fs_fs__path_rev_absolute(cb->fs, old_rev, pool);
3536 rev_filename = svn_fs_fs__path_rev(cb->fs, new_rev, pool);
3537 proto_filename = svn_fs_fs__path_txn_proto_rev(cb->fs, txn_id, pool);
3538 SVN_ERR(svn_fs_fs__move_into_place(proto_filename, rev_filename,
3539 old_rev_filename, pool));
3541 /* Now that we've moved the prototype revision file out of the way,
3542 we can unlock it (since further attempts to write to the file
3543 will fail as it no longer exists). We must do this so that we can
3544 remove the transaction directory later. */
3545 SVN_ERR(unlock_proto_rev(cb->fs, txn_id, proto_file_lockcookie, pool));
3547 /* Move the revprops file into place. */
3548 SVN_ERR_ASSERT(! svn_fs_fs__is_packed_revprop(cb->fs, new_rev));
3549 SVN_ERR(write_final_revprop(&revprop_filename, cb->txn, txn_id, pool));
3550 final_revprop = svn_fs_fs__path_revprops(cb->fs, new_rev, pool);
3551 SVN_ERR(svn_fs_fs__move_into_place(revprop_filename, final_revprop,
3552 old_rev_filename, pool));
3554 /* Update the 'current' file. */
3555 SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool));
3556 SVN_ERR(write_final_current(cb->fs, txn_id, new_rev, start_node_id,
3557 start_copy_id, pool));
3559 /* At this point the new revision is committed and globally visible
3560 so let the caller know it succeeded by giving it the new revision
3561 number, which fulfills svn_fs_commit_txn() contract. Any errors
3562 after this point do not change the fact that a new revision was
3564 *cb->new_rev_p = new_rev;
3566 ffd->youngest_rev_cache = new_rev;
3568 /* Remove this transaction directory. */
3569 SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool));
3571 return SVN_NO_ERROR;
3574 /* Add the representations in REPS_TO_CACHE (an array of representation_t *)
3575 * to the rep-cache database of FS. */
3576 static svn_error_t *
3577 write_reps_to_cache(svn_fs_t *fs,
3578 const apr_array_header_t *reps_to_cache,
3579 apr_pool_t *scratch_pool)
3583 for (i = 0; i < reps_to_cache->nelts; i++)
3585 representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *);
3587 SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, scratch_pool));
3590 return SVN_NO_ERROR;
3594 svn_fs_fs__commit(svn_revnum_t *new_rev_p,
3599 struct commit_baton cb;
3600 fs_fs_data_t *ffd = fs->fsap_data;
3602 cb.new_rev_p = new_rev_p;
3606 if (ffd->rep_sharing_allowed)
3608 cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *));
3609 cb.reps_hash = apr_hash_make(pool);
3610 cb.reps_pool = pool;
3614 cb.reps_to_cache = NULL;
3615 cb.reps_hash = NULL;
3616 cb.reps_pool = NULL;
3619 SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool));
3621 /* At this point, *NEW_REV_P has been set, so errors below won't affect
3622 the success of the commit. (See svn_fs_commit_txn().) */
3624 if (ffd->rep_sharing_allowed)
3626 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
3628 /* Write new entries to the rep-sharing database.
3630 * We use an sqlite transaction to speed things up;
3631 * see <http://www.sqlite.org/faq.html#q19>.
3633 /* ### A commit that touches thousands of files will starve other
3634 (reader/writer) commits for the duration of the below call.
3635 Maybe write in batches? */
3636 SVN_SQLITE__WITH_TXN(
3637 write_reps_to_cache(fs, cb.reps_to_cache, pool),
3641 return SVN_NO_ERROR;
3646 svn_fs_fs__list_transactions(apr_array_header_t **names_p,
3650 const char *txn_dir;
3651 apr_hash_t *dirents;
3652 apr_hash_index_t *hi;
3653 apr_array_header_t *names;
3654 apr_size_t ext_len = strlen(PATH_EXT_TXN);
3656 names = apr_array_make(pool, 1, sizeof(const char *));
3658 /* Get the transactions directory. */
3659 txn_dir = svn_fs_fs__path_txns_dir(fs, pool);
3661 /* Now find a listing of this directory. */
3662 SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool));
3664 /* Loop through all the entries and return anything that ends with '.txn'. */
3665 for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
3667 const char *name = apr_hash_this_key(hi);
3668 apr_ssize_t klen = apr_hash_this_key_len(hi);
3671 /* The name must end with ".txn" to be considered a transaction. */
3672 if ((apr_size_t) klen <= ext_len
3673 || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0)
3676 /* Truncate the ".txn" extension and store the ID. */
3677 id = apr_pstrndup(pool, name, strlen(name) - ext_len);
3678 APR_ARRAY_PUSH(names, const char *) = id;
3683 return SVN_NO_ERROR;
3687 svn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
3694 svn_node_kind_t kind;
3695 transaction_t *local_txn;
3696 svn_fs_fs__id_part_t txn_id;
3698 SVN_ERR(svn_fs_fs__id_txn_parse(&txn_id, name));
3700 /* First check to see if the directory exists. */
3701 SVN_ERR(svn_io_check_path(svn_fs_fs__path_txn_dir(fs, &txn_id, pool),
3704 /* Did we find it? */
3705 if (kind != svn_node_dir)
3706 return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL,
3707 _("No such transaction '%s'"),
3710 txn = apr_pcalloc(pool, sizeof(*txn));
3711 ftd = apr_pcalloc(pool, sizeof(*ftd));
3712 ftd->txn_id = txn_id;
3714 /* Read in the root node of this transaction. */
3715 txn->id = apr_pstrdup(pool, name);
3718 SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, &txn_id, pool));
3720 txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id);
3722 txn->vtable = &txn_vtable;
3723 txn->fsap_data = ftd;
3726 return SVN_NO_ERROR;
3730 svn_fs_fs__txn_proplist(apr_hash_t **table_p,
3734 apr_hash_t *proplist = apr_hash_make(pool);
3735 SVN_ERR(get_txn_proplist(proplist, txn->fs, svn_fs_fs__txn_get_id(txn),
3737 *table_p = proplist;
3739 return SVN_NO_ERROR;
3744 svn_fs_fs__delete_node_revision(svn_fs_t *fs,
3745 const svn_fs_id_t *id,
3748 node_revision_t *noderev;
3750 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool, pool));
3752 /* Delete any mutable property representation. */
3753 if (noderev->prop_rep && is_txn_rep(noderev->prop_rep))
3754 SVN_ERR(svn_io_remove_file2(svn_fs_fs__path_txn_node_props(fs, id, pool),
3757 /* Delete any mutable data representation. */
3758 if (noderev->data_rep && is_txn_rep(noderev->data_rep)
3759 && noderev->kind == svn_node_dir)
3761 fs_fs_data_t *ffd = fs->fsap_data;
3762 SVN_ERR(svn_io_remove_file2(svn_fs_fs__path_txn_node_children(fs, id,
3766 /* remove the corresponding entry from the cache, if such exists */
3767 if (ffd->txn_dir_cache)
3769 const char *key = svn_fs_fs__id_unparse(id, pool)->data;
3770 SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL, pool));
3774 return svn_io_remove_file2(svn_fs_fs__path_txn_node_rev(fs, id, pool),
3780 /*** Transactions ***/
3783 svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
3784 const svn_fs_id_t **base_root_id_p,
3786 const svn_fs_fs__id_part_t *txn_id,
3790 SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_id, pool));
3791 *root_id_p = txn->root_id;
3792 *base_root_id_p = txn->base_id;
3793 return SVN_NO_ERROR;
3797 /* Generic transaction operations. */
3800 svn_fs_fs__txn_prop(svn_string_t **value_p,
3802 const char *propname,
3806 svn_fs_t *fs = txn->fs;
3808 SVN_ERR(svn_fs__check_fs(fs, TRUE));
3809 SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool));
3811 *value_p = svn_hash_gets(table, propname);
3813 return SVN_NO_ERROR;
3817 svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
3825 apr_hash_t *props = apr_hash_make(pool);
3827 SVN_ERR(svn_fs__check_fs(fs, TRUE));
3829 SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool));
3831 /* Put a datestamp on the newly created txn, so we always know
3832 exactly how old it is. (This will help sysadmins identify
3833 long-abandoned txns that may need to be manually removed.) When
3834 a txn is promoted to a revision, this property will be
3835 automatically overwritten with a revision datestamp. */
3836 date.data = svn_time_to_cstring(apr_time_now(), pool);
3837 date.len = strlen(date.data);
3839 svn_hash_sets(props, SVN_PROP_REVISION_DATE, &date);
3841 /* Set temporary txn props that represent the requested 'flags'
3843 if (flags & SVN_FS_TXN_CHECK_OOD)
3844 svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_OOD,
3845 svn_string_create("true", pool));
3847 if (flags & SVN_FS_TXN_CHECK_LOCKS)
3848 svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_LOCKS,
3849 svn_string_create("true", pool));
3851 if (flags & SVN_FS_TXN_CLIENT_DATE)
3852 svn_hash_sets(props, SVN_FS__PROP_TXN_CLIENT_DATE,
3853 svn_string_create("0", pool));
3855 ftd = (*txn_p)->fsap_data;
3856 return svn_error_trace(set_txn_proplist(fs, &ftd->txn_id, props, FALSE,