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
txn->proplist = apr_hash_make(pool);
1252
SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool));
1253
root_id = svn_fs_fs__id_txn_create_root(txn_id, pool);
1255
SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool, pool));
1257
txn->root_id = svn_fs_fs__id_copy(noderev->id, pool);
1258
txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
1263
return SVN_NO_ERROR;
1266
/* Write out the currently available next node_id NODE_ID and copy_id
1267
COPY_ID for transaction TXN_ID in filesystem FS. The next node-id is
1268
used both for creating new unique nodes for the given transaction, as
1269
well as uniquifying representations. Perform temporary allocations in
1271
static svn_error_t *
1272
write_next_ids(svn_fs_t *fs,
1273
const svn_fs_fs__id_part_t *txn_id,
1274
apr_uint64_t node_id,
1275
apr_uint64_t copy_id,
1279
char buffer[2 * SVN_INT64_BUFFER_SIZE + 2];
1282
p += svn__ui64tobase36(p, node_id);
1284
p += svn__ui64tobase36(p, copy_id);
1288
SVN_ERR(svn_io_file_open(&file,
1289
path_txn_next_ids(fs, txn_id, pool),
1290
APR_WRITE | APR_TRUNCATE,
1291
APR_OS_DEFAULT, pool));
1292
SVN_ERR(svn_io_file_write_full(file, buffer, p - buffer, NULL, pool));
1293
return svn_io_file_close(file, pool);
1296
/* Find out what the next unique node-id and copy-id are for
1297
transaction TXN_ID in filesystem FS. Store the results in *NODE_ID
1298
and *COPY_ID. The next node-id is used both for creating new unique
1299
nodes for the given transaction, as well as uniquifying representations.
1300
Perform all allocations in POOL. */
1301
static svn_error_t *
1302
read_next_ids(apr_uint64_t *node_id,
1303
apr_uint64_t *copy_id,
1305
const svn_fs_fs__id_part_t *txn_id,
1308
svn_stringbuf_t *buf;
1310
SVN_ERR(svn_fs_fs__read_content(&buf,
1311
path_txn_next_ids(fs, txn_id, pool),
1314
/* Parse this into two separate strings. */
1317
*node_id = svn__base36toui64(&str, str);
1319
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1320
_("next-id file corrupt"));
1323
*copy_id = svn__base36toui64(&str, str);
1325
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1326
_("next-id file corrupt"));
1328
return SVN_NO_ERROR;
1331
/* Get a new and unique to this transaction node-id for transaction
1332
TXN_ID in filesystem FS. Store the new node-id in *NODE_ID_P.
1333
Node-ids are guaranteed to be unique to this transction, but may
1334
not necessarily be sequential. Perform all allocations in POOL. */
1335
static svn_error_t *
1336
get_new_txn_node_id(svn_fs_fs__id_part_t *node_id_p,
1338
const svn_fs_fs__id_part_t *txn_id,
1341
apr_uint64_t node_id, copy_id;
1343
/* First read in the current next-ids file. */
1344
SVN_ERR(read_next_ids(&node_id, ©_id, fs, txn_id, pool));
1346
node_id_p->revision = SVN_INVALID_REVNUM;
1347
node_id_p->number = node_id;
1349
SVN_ERR(write_next_ids(fs, txn_id, ++node_id, copy_id, pool));
1351
return SVN_NO_ERROR;
1355
svn_fs_fs__reserve_copy_id(svn_fs_fs__id_part_t *copy_id_p,
1357
const svn_fs_fs__id_part_t *txn_id,
1360
apr_uint64_t node_id, copy_id;
1362
/* First read in the current next-ids file. */
1363
SVN_ERR(read_next_ids(&node_id, ©_id, fs, txn_id, pool));
1365
/* this is an in-txn ID now */
1366
copy_id_p->revision = SVN_INVALID_REVNUM;
1367
copy_id_p->number = copy_id;
1369
/* Update the ID counter file */
1370
SVN_ERR(write_next_ids(fs, txn_id, node_id, ++copy_id, pool));
1372
return SVN_NO_ERROR;
1376
svn_fs_fs__create_node(const svn_fs_id_t **id_p,
1378
node_revision_t *noderev,
1379
const svn_fs_fs__id_part_t *copy_id,
1380
const svn_fs_fs__id_part_t *txn_id,
1383
svn_fs_fs__id_part_t node_id;
1384
const svn_fs_id_t *id;
1386
/* Get a new node-id for this node. */
1387
SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool));
1389
id = svn_fs_fs__id_txn_create(&node_id, copy_id, txn_id, pool);
1393
SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
1397
return SVN_NO_ERROR;
1401
svn_fs_fs__purge_txn(svn_fs_t *fs,
1402
const char *txn_id_str,
1405
fs_fs_data_t *ffd = fs->fsap_data;
1406
svn_fs_fs__id_part_t txn_id;
1407
SVN_ERR(svn_fs_fs__id_txn_parse(&txn_id, txn_id_str));
1409
/* Remove the shared transaction object associated with this transaction. */
1410
SVN_ERR(purge_shared_txn(fs, &txn_id, pool));
1411
/* Remove the directory associated with this transaction. */
1412
SVN_ERR(svn_io_remove_dir2(svn_fs_fs__path_txn_dir(fs, &txn_id, pool),
1413
FALSE, NULL, NULL, pool));
1414
if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1416
/* Delete protorev and its lock, which aren't in the txn
1417
directory. It's OK if they don't exist (for example, if this
1418
is post-commit and the proto-rev has been moved into
1420
SVN_ERR(svn_io_remove_file2(
1421
svn_fs_fs__path_txn_proto_rev(fs, &txn_id, pool),
1423
SVN_ERR(svn_io_remove_file2(
1424
svn_fs_fs__path_txn_proto_rev_lock(fs, &txn_id, pool),
1427
return SVN_NO_ERROR;
1432
svn_fs_fs__abort_txn(svn_fs_txn_t *txn,
1435
SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
1437
/* Now, purge the transaction. */
1438
SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool),
1439
apr_psprintf(pool, _("Transaction '%s' cleanup failed"),
1442
return SVN_NO_ERROR;
1445
/* Assign the UNIQUIFIER member of REP based on the current state of TXN_ID
1446
* in FS. Allocate the uniquifier in POOL.
1448
static svn_error_t *
1449
set_uniquifier(svn_fs_t *fs,
1450
representation_t *rep,
1453
svn_fs_fs__id_part_t temp;
1454
fs_fs_data_t *ffd = fs->fsap_data;
1456
if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
1458
SVN_ERR(get_new_txn_node_id(&temp, fs, &rep->txn_id, pool));
1459
rep->uniquifier.noderev_txn_id = rep->txn_id;
1460
rep->uniquifier.number = temp.number;
1463
return SVN_NO_ERROR;
1466
/* Return TRUE if the TXN_ID member of REP is in use.
1468
static svn_boolean_t
1469
is_txn_rep(const representation_t *rep)
1471
return svn_fs_fs__id_txn_used(&rep->txn_id);
1474
/* Mark the TXN_ID member of REP as "unused".
1477
reset_txn_in_rep(representation_t *rep)
1479
svn_fs_fs__id_txn_reset(&rep->txn_id);
1483
svn_fs_fs__set_entry(svn_fs_t *fs,
1484
const svn_fs_fs__id_part_t *txn_id,
1485
node_revision_t *parent_noderev,
1487
const svn_fs_id_t *id,
1488
svn_node_kind_t kind,
1491
representation_t *rep = parent_noderev->data_rep;
1492
const char *filename
1493
= svn_fs_fs__path_txn_node_children(fs, parent_noderev->id, pool);
1496
fs_fs_data_t *ffd = fs->fsap_data;
1497
apr_pool_t *subpool = svn_pool_create(pool);
1499
if (!rep || !is_txn_rep(rep))
1501
apr_array_header_t *entries;
1503
/* Before we can modify the directory, we need to dump its old
1504
contents into a mutable representation file. */
1505
SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
1507
SVN_ERR(svn_io_file_open(&file, filename,
1508
APR_WRITE | APR_CREATE | APR_BUFFERED,
1509
APR_OS_DEFAULT, pool));
1510
out = svn_stream_from_aprfile2(file, TRUE, pool);
1511
SVN_ERR(unparse_dir_entries(entries, out, subpool));
1513
svn_pool_clear(subpool);
1515
/* Mark the node-rev's data rep as mutable. */
1516
rep = apr_pcalloc(pool, sizeof(*rep));
1517
rep->revision = SVN_INVALID_REVNUM;
1518
rep->txn_id = *txn_id;
1519
SVN_ERR(set_uniquifier(fs, rep, pool));
1520
parent_noderev->data_rep = rep;
1521
SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id,
1522
parent_noderev, FALSE, pool));
1526
/* The directory rep is already mutable, so just open it for append. */
1527
SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
1528
APR_OS_DEFAULT, pool));
1529
out = svn_stream_from_aprfile2(file, TRUE, pool);
1532
/* if we have a directory cache for this transaction, update it */
1533
if (ffd->txn_dir_cache)
1535
/* build parameters: (name, new entry) pair */
1537
svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data;
1538
replace_baton_t baton;
1541
baton.new_entry = NULL;
1545
baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry));
1546
baton.new_entry->name = name;
1547
baton.new_entry->kind = kind;
1548
baton.new_entry->id = id;
1551
/* actually update the cached directory (if cached) */
1552
SVN_ERR(svn_cache__set_partial(ffd->txn_dir_cache, key,
1553
svn_fs_fs__replace_dir_entry, &baton,
1556
svn_pool_clear(subpool);
1558
/* Append an incremental hash entry for the entry change. */
1561
svn_fs_dirent_t entry;
1566
SVN_ERR(unparse_dir_entry(&entry, out, subpool));
1570
SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
1571
strlen(name), name));
1574
SVN_ERR(svn_io_file_close(file, subpool));
1575
svn_pool_destroy(subpool);
1576
return SVN_NO_ERROR;
1580
svn_fs_fs__add_change(svn_fs_t *fs,
1581
const svn_fs_fs__id_part_t *txn_id,
1583
const svn_fs_id_t *id,
1584
svn_fs_path_change_kind_t change_kind,
1585
svn_boolean_t text_mod,
1586
svn_boolean_t prop_mod,
1587
svn_boolean_t mergeinfo_mod,
1588
svn_node_kind_t node_kind,
1589
svn_revnum_t copyfrom_rev,
1590
const char *copyfrom_path,
1594
svn_fs_path_change2_t *change;
1595
apr_hash_t *changes = apr_hash_make(pool);
1597
/* Not using APR_BUFFERED to append change in one atomic write operation. */
1598
SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
1599
APR_APPEND | APR_WRITE | APR_CREATE,
1600
APR_OS_DEFAULT, pool));
1602
change = svn_fs__path_change_create_internal(id, change_kind, pool);
1603
change->text_mod = text_mod;
1604
change->prop_mod = prop_mod;
1605
change->mergeinfo_mod = mergeinfo_mod
1607
: svn_tristate_false;
1608
change->node_kind = node_kind;
1609
change->copyfrom_known = TRUE;
1610
change->copyfrom_rev = copyfrom_rev;
1612
change->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
1614
svn_hash_sets(changes, path, change);
1615
SVN_ERR(svn_fs_fs__write_changes(svn_stream_from_aprfile2(file, TRUE, pool),
1616
fs, changes, FALSE, pool));
1618
return svn_io_file_close(file, pool);
1621
/* If the transaction TXN_ID in FS uses logical addressing, store the
1622
* (ITEM_INDEX, OFFSET) pair in the txn's log-to-phys proto index file.
1623
* Use POOL for allocations.
1625
static svn_error_t *
1626
store_l2p_index_entry(svn_fs_t *fs,
1627
const svn_fs_fs__id_part_t *txn_id,
1629
apr_uint64_t item_index,
1632
if (svn_fs_fs__use_log_addressing(fs))
1634
const char *path = svn_fs_fs__path_l2p_proto_index(fs, txn_id, pool);
1636
SVN_ERR(svn_fs_fs__l2p_proto_index_open(&file, path, pool));
1637
SVN_ERR(svn_fs_fs__l2p_proto_index_add_entry(file, offset,
1639
SVN_ERR(svn_io_file_close(file, pool));
1642
return SVN_NO_ERROR;
1645
/* If the transaction TXN_ID in FS uses logical addressing, store ENTRY
1646
* in the phys-to-log proto index file of transaction TXN_ID.
1647
* Use POOL for allocations.
1649
static svn_error_t *
1650
store_p2l_index_entry(svn_fs_t *fs,
1651
const svn_fs_fs__id_part_t *txn_id,
1652
svn_fs_fs__p2l_entry_t *entry,
1655
if (svn_fs_fs__use_log_addressing(fs))
1657
const char *path = svn_fs_fs__path_p2l_proto_index(fs, txn_id, pool);
1659
SVN_ERR(svn_fs_fs__p2l_proto_index_open(&file, path, pool));
1660
SVN_ERR(svn_fs_fs__p2l_proto_index_add_entry(file, entry, pool));
1661
SVN_ERR(svn_io_file_close(file, pool));
1664
return SVN_NO_ERROR;
1667
/* Allocate an item index for the given MY_OFFSET in the transaction TXN_ID
1668
* of file system FS and return it in *ITEM_INDEX. For old formats, it
1669
* will simply return the offset as item index; in new formats, it will
1670
* increment the txn's item index counter file and store the mapping in
1671
* the proto index file. Use POOL for allocations.
1673
static svn_error_t *
1674
allocate_item_index(apr_uint64_t *item_index,
1676
const svn_fs_fs__id_part_t *txn_id,
1677
apr_off_t my_offset,
1680
if (svn_fs_fs__use_log_addressing(fs))
1683
char buffer[SVN_INT64_BUFFER_SIZE] = { 0 };
1684
svn_boolean_t eof = FALSE;
1685
apr_size_t to_write;
1687
apr_off_t offset = 0;
1689
/* read number, increment it and write it back to disk */
1690
SVN_ERR(svn_io_file_open(&file,
1691
svn_fs_fs__path_txn_item_index(fs, txn_id, pool),
1692
APR_READ | APR_WRITE | APR_CREATE | APR_BUFFERED,
1693
APR_OS_DEFAULT, pool));
1694
SVN_ERR(svn_io_file_read_full2(file, buffer, sizeof(buffer)-1,
1695
&read, &eof, pool));
1697
SVN_ERR(svn_cstring_atoui64(item_index, buffer));
1699
*item_index = SVN_FS_FS__ITEM_INDEX_FIRST_USER;
1701
to_write = svn__ui64toa(buffer, *item_index + 1);
1702
SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, pool));
1703
SVN_ERR(svn_io_file_write_full(file, buffer, to_write, NULL, pool));
1704
SVN_ERR(svn_io_file_close(file, pool));
1706
/* write log-to-phys index */
1707
SVN_ERR(store_l2p_index_entry(fs, txn_id, my_offset, *item_index, pool));
1711
*item_index = (apr_uint64_t)my_offset;
1714
return SVN_NO_ERROR;
1717
/* Baton used by fnv1a_write_handler to calculate the FNV checksum
1718
* before passing the data on to the INNER_STREAM.
1720
typedef struct fnv1a_stream_baton_t
1722
svn_stream_t *inner_stream;
1723
svn_checksum_ctx_t *context;
1724
} fnv1a_stream_baton_t;
1726
/* Implement svn_write_fn_t.
1727
* Update checksum and pass data on to inner stream.
1729
static svn_error_t *
1730
fnv1a_write_handler(void *baton,
1734
fnv1a_stream_baton_t *b = baton;
1736
SVN_ERR(svn_checksum_update(b->context, data, *len));
1737
SVN_ERR(svn_stream_write(b->inner_stream, data, len));
1739
return SVN_NO_ERROR;
1742
/* Return a stream that calculates a FNV checksum in *CONTEXT
1743
* over all data written to the stream and passes that data on
1744
* to INNER_STREAM. Allocate objects in POOL.
1746
static svn_stream_t *
1747
fnv1a_wrap_stream(svn_checksum_ctx_t **context,
1748
svn_stream_t *inner_stream,
1751
svn_stream_t *outer_stream;
1753
fnv1a_stream_baton_t *baton = apr_pcalloc(pool, sizeof(*baton));
1754
baton->inner_stream = inner_stream;
1755
baton->context = svn_checksum_ctx_create(svn_checksum_fnv1a_32x4, pool);
1756
*context = baton->context;
1758
outer_stream = svn_stream_create(baton, pool);
1759
svn_stream_set_write(outer_stream, fnv1a_write_handler);
1761
return outer_stream;
1764
/* Set *DIGEST to the FNV checksum calculated in CONTEXT.
1765
* Use SCRATCH_POOL for temporary allocations.
1767
static svn_error_t *
1768
fnv1a_checksum_finalize(apr_uint32_t *digest,
1769
svn_checksum_ctx_t *context,
1770
apr_pool_t *scratch_pool)
1772
svn_checksum_t *checksum;
1774
SVN_ERR(svn_checksum_final(&checksum, context, scratch_pool));
1775
SVN_ERR_ASSERT(checksum->kind == svn_checksum_fnv1a_32x4);
1776
*digest = ntohl(*(const apr_uint32_t *)(checksum->digest));
1778
return SVN_NO_ERROR;
1781
/* This baton is used by the representation writing streams. It keeps
1782
track of the checksum information as well as the total size of the
1783
representation so far. */
1784
struct rep_write_baton
1786
/* The FS we are writing to. */
1789
/* Actual file to which we are writing. */
1790
svn_stream_t *rep_stream;
1792
/* A stream from the delta combiner. Data written here gets
1793
deltified, then eventually written to rep_stream. */
1794
svn_stream_t *delta_stream;
1796
/* Where is this representation header stored. */
1797
apr_off_t rep_offset;
1799
/* Start of the actual data. */
1800
apr_off_t delta_start;
1802
/* How many bytes have been written to this rep already. */
1803
svn_filesize_t rep_size;
1805
/* The node revision for which we're writing out info. */
1806
node_revision_t *noderev;
1808
/* Actual output file. */
1810
/* Lock 'cookie' used to unlock the output file once we've finished
1814
svn_checksum_ctx_t *md5_checksum_ctx;
1815
svn_checksum_ctx_t *sha1_checksum_ctx;
1817
/* calculate a modified FNV-1a checksum of the on-disk representation */
1818
svn_checksum_ctx_t *fnv1a_checksum_ctx;
1820
/* Local / scratch pool, available for temporary allocations. */
1821
apr_pool_t *scratch_pool;
1823
/* Outer / result pool. */
1824
apr_pool_t *result_pool;
1827
/* Handler for the write method of the representation writable stream.
1828
BATON is a rep_write_baton, DATA is the data to write, and *LEN is
1829
the length of this data. */
1830
static svn_error_t *
1831
rep_write_contents(void *baton,
1835
struct rep_write_baton *b = baton;
1837
SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len));
1838
SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len));
1839
b->rep_size += *len;
1841
/* If we are writing a delta, use that stream. */
1842
if (b->delta_stream)
1843
return svn_stream_write(b->delta_stream, data, len);
1845
return svn_stream_write(b->rep_stream, data, len);
1848
/* Set *SPANNED to the number of shards touched when walking WALK steps on
1849
* NODEREV's predecessor chain in FS. Use POOL for temporary allocations.
1851
static svn_error_t *
1852
shards_spanned(int *spanned,
1854
node_revision_t *noderev,
1858
fs_fs_data_t *ffd = fs->fsap_data;
1859
int shard_size = ffd->max_files_per_dir ? ffd->max_files_per_dir : 1;
1860
apr_pool_t *iterpool;
1862
int count = walk ? 1 : 0; /* The start of a walk already touches a shard. */
1863
svn_revnum_t shard, last_shard = ffd->youngest_rev_cache / shard_size;
1864
iterpool = svn_pool_create(pool);
1865
while (walk-- && noderev->predecessor_count)
1867
svn_pool_clear(iterpool);
1868
SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs,
1869
noderev->predecessor_id, pool,
1871
shard = svn_fs_fs__id_rev(noderev->id) / shard_size;
1872
if (shard != last_shard)
1878
svn_pool_destroy(iterpool);
1881
return SVN_NO_ERROR;
1884
/* Given a node-revision NODEREV in filesystem FS, return the
1885
representation in *REP to use as the base for a text representation
1886
delta if PROPS is FALSE. If PROPS has been set, a suitable props
1887
base representation will be returned. Perform temporary allocations
1889
static svn_error_t *
1890
choose_delta_base(representation_t **rep,
1892
node_revision_t *noderev,
1893
svn_boolean_t props,
1896
/* The zero-based index (counting from the "oldest" end), along NODEREVs line
1897
* predecessors, of the node-rev we will use as delta base. */
1899
/* The length of the linear part of a delta chain. (Delta chains use
1900
* skip-delta bits for the high-order bits and are linear in the low-order
1903
node_revision_t *base;
1904
fs_fs_data_t *ffd = fs->fsap_data;
1905
apr_pool_t *iterpool;
1907
/* If we have no predecessors, or that one is empty, then use the empty
1908
* stream as a base. */
1909
if (! noderev->predecessor_count)
1912
return SVN_NO_ERROR;
1915
/* Flip the rightmost '1' bit of the predecessor count to determine
1916
which file rev (counting from 0) we want to use. (To see why
1917
count & (count - 1) unsets the rightmost set bit, think about how
1918
you decrement a binary number.) */
1919
count = noderev->predecessor_count;
1920
count = count & (count - 1);
1922
/* Finding the delta base over a very long distance can become extremely
1923
expensive for very deep histories, possibly causing client timeouts etc.
1924
OTOH, this is a rare operation and its gains are minimal. Lets simply
1925
start deltification anew close every other 1000 changes or so. */
1926
walk = noderev->predecessor_count - count;
1927
if (walk > (int)ffd->max_deltification_walk)
1930
return SVN_NO_ERROR;
1933
/* We use skip delta for limiting the number of delta operations
1934
along very long node histories. Close to HEAD however, we create
1935
a linear history to minimize delta size. */
1936
if (walk < (int)ffd->max_linear_deltification)
1939
SVN_ERR(shards_spanned(&shards, fs, noderev, walk, pool));
1941
/* We also don't want the linear deltification to span more shards
1942
than if deltas we used in a simple skip-delta scheme. */
1943
if ((1 << (--shards)) <= walk)
1944
count = noderev->predecessor_count - 1;
1947
/* Walk back a number of predecessors equal to the difference
1948
between count and the original predecessor count. (For example,
1949
if noderev has ten predecessors and we want the eighth file rev,
1950
walk back two predecessors.) */
1952
iterpool = svn_pool_create(pool);
1953
while ((count++) < noderev->predecessor_count)
1955
svn_pool_clear(iterpool);
1956
SVN_ERR(svn_fs_fs__get_node_revision(&base, fs,
1957
base->predecessor_id, pool,
1960
svn_pool_destroy(iterpool);
1962
/* return a suitable base representation */
1963
*rep = props ? base->prop_rep : base->data_rep;
1965
/* if we encountered a shared rep, its parent chain may be different
1966
* from the node-rev parent chain. */
1969
int chain_length = 0;
1970
int shard_count = 0;
1972
/* Very short rep bases are simply not worth it as we are unlikely
1973
* to re-coup the deltification space overhead of 20+ bytes. */
1974
svn_filesize_t rep_size = (*rep)->expanded_size
1975
? (*rep)->expanded_size
1980
return SVN_NO_ERROR;
1983
/* Check whether the length of the deltification chain is acceptable.
1984
* Otherwise, shared reps may form a non-skipping delta chain in
1986
SVN_ERR(svn_fs_fs__rep_chain_length(&chain_length, &shard_count,
1989
/* Some reasonable limit, depending on how acceptable longer linear
1990
* chains are in this repo. Also, allow for some minimal chain. */
1991
if (chain_length >= 2 * (int)ffd->max_linear_deltification + 2)
1994
/* To make it worth opening additional shards / pack files, we
1995
* require that the reps have a certain minimal size. To deltify
1996
* against a rep in different shard, the lower limit is 512 bytes
1997
* and doubles with every extra shard to visit along the delta
1999
if ( shard_count > 1
2000
&& ((svn_filesize_t)128 << shard_count) >= rep_size)
2004
return SVN_NO_ERROR;
2007
/* Something went wrong and the pool for the rep write is being
2008
cleared before we've finished writing the rep. So we need
2009
to remove the rep from the protorevfile and we need to unlock
2010
the protorevfile. */
2012
rep_write_cleanup(void *data)
2014
struct rep_write_baton *b = data;
2017
/* Truncate and close the protorevfile. */
2018
err = svn_io_file_trunc(b->file, b->rep_offset, b->scratch_pool);
2019
err = svn_error_compose_create(err, svn_io_file_close(b->file,
2022
/* Remove our lock regardless of any preceding errors so that the
2023
being_written flag is always removed and stays consistent with the
2024
file lock which will be removed no matter what since the pool is
2026
err = svn_error_compose_create(err,
2027
unlock_proto_rev(b->fs,
2028
svn_fs_fs__id_txn_id(b->noderev->id),
2029
b->lockcookie, b->scratch_pool));
2032
apr_status_t rc = err->apr_err;
2033
svn_error_clear(err);
2040
/* Get a rep_write_baton and store it in *WB_P for the representation
2041
indicated by NODEREV in filesystem FS. Perform allocations in
2042
POOL. Only appropriate for file contents, not for props or
2043
directory contents. */
2044
static svn_error_t *
2045
rep_write_get_baton(struct rep_write_baton **wb_p,
2047
node_revision_t *noderev,
2050
struct rep_write_baton *b;
2052
representation_t *base_rep;
2053
svn_stream_t *source;
2054
svn_txdelta_window_handler_t wh;
2056
fs_fs_data_t *ffd = fs->fsap_data;
2057
int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
2058
svn_fs_fs__rep_header_t header = { 0 };
2060
b = apr_pcalloc(pool, sizeof(*b));
2062
b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
2063
b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
2066
b->result_pool = pool;
2067
b->scratch_pool = svn_pool_create(pool);
2069
b->noderev = noderev;
2071
/* Open the prototype rev file and seek to its end. */
2072
SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie,
2073
fs, svn_fs_fs__id_txn_id(noderev->id),
2077
b->rep_stream = fnv1a_wrap_stream(&b->fnv1a_checksum_ctx,
2078
svn_stream_from_aprfile2(file, TRUE,
2082
SVN_ERR(svn_fs_fs__get_file_offset(&b->rep_offset, file, b->scratch_pool));
2084
/* Get the base for this delta. */
2085
SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->scratch_pool));
2086
SVN_ERR(svn_fs_fs__get_contents(&source, fs, base_rep, TRUE,
2089
/* Write out the rep header. */
2092
header.base_revision = base_rep->revision;
2093
header.base_item_index = base_rep->item_index;
2094
header.base_length = base_rep->size;
2095
header.type = svn_fs_fs__rep_delta;
2099
header.type = svn_fs_fs__rep_self_delta;
2101
SVN_ERR(svn_fs_fs__write_rep_header(&header, b->rep_stream,
2104
/* Now determine the offset of the actual svndiff data. */
2105
SVN_ERR(svn_fs_fs__get_file_offset(&b->delta_start, file,
2108
/* Cleanup in case something goes wrong. */
2109
apr_pool_cleanup_register(b->scratch_pool, b, rep_write_cleanup,
2110
apr_pool_cleanup_null);
2112
/* Prepare to write the svndiff data. */
2113
svn_txdelta_to_svndiff3(&wh,
2117
ffd->delta_compression_level,
2120
b->delta_stream = svn_txdelta_target_push(wh, whb, source,
2125
return SVN_NO_ERROR;
2128
/* For REP->SHA1_CHECKSUM, try to find an already existing representation
2129
in FS and return it in *OUT_REP. If no such representation exists or
2130
if rep sharing has been disabled for FS, NULL will be returned. Since
2131
there may be new duplicate representations within the same uncommitted
2132
revision, those can be passed in REPS_HASH (maps a sha1 digest onto
2133
representation_t*), otherwise pass in NULL for REPS_HASH.
2134
Use RESULT_POOL for *OLD_REP allocations and SCRATCH_POOL for temporaries.
2135
The lifetime of *OLD_REP is limited by both, RESULT_POOL and REP lifetime.
2137
static svn_error_t *
2138
get_shared_rep(representation_t **old_rep,
2140
representation_t *rep,
2141
apr_hash_t *reps_hash,
2142
apr_pool_t *result_pool,
2143
apr_pool_t *scratch_pool)
2146
fs_fs_data_t *ffd = fs->fsap_data;
2148
/* Return NULL, if rep sharing has been disabled. */
2150
if (!ffd->rep_sharing_allowed)
2151
return SVN_NO_ERROR;
2153
/* Check and see if we already have a representation somewhere that's
2154
identical to the one we just wrote out. Start with the hash lookup
2155
because it is cheepest. */
2157
*old_rep = apr_hash_get(reps_hash,
2159
APR_SHA1_DIGESTSIZE);
2161
/* If we haven't found anything yet, try harder and consult our DB. */
2162
if (*old_rep == NULL)
2164
svn_checksum_t checksum;
2165
checksum.digest = rep->sha1_digest;
2166
checksum.kind = svn_checksum_sha1;
2167
err = svn_fs_fs__get_rep_reference(old_rep, fs, &checksum, result_pool);
2168
/* ### Other error codes that we shouldn't mask out? */
2169
if (err == SVN_NO_ERROR)
2172
SVN_ERR(svn_fs_fs__check_rep(*old_rep, fs, NULL, scratch_pool));
2174
else if (err->apr_err == SVN_ERR_FS_CORRUPT
2175
|| SVN_ERROR_IN_CATEGORY(err->apr_err,
2176
SVN_ERR_MALFUNC_CATEGORY_START))
2178
/* Fatal error; don't mask it.
2180
In particular, this block is triggered when the rep-cache refers
2181
to revisions in the future. We signal that as a corruption situation
2182
since, once those revisions are less than youngest (because of more
2183
commits), the rep-cache would be invalid.
2189
/* Something's wrong with the rep-sharing index. We can continue
2190
without rep-sharing, but warn.
2192
(fs->warning)(fs->warning_baton, err);
2193
svn_error_clear(err);
2198
/* look for intra-revision matches (usually data reps but not limited
2199
to them in case props happen to look like some data rep)
2201
if (*old_rep == NULL && is_txn_rep(rep))
2203
svn_node_kind_t kind;
2204
const char *file_name
2205
= path_txn_sha1(fs, &rep->txn_id, rep->sha1_digest, scratch_pool);
2207
/* in our txn, is there a rep file named with the wanted SHA1?
2208
If so, read it and use that rep.
2210
SVN_ERR(svn_io_check_path(file_name, &kind, scratch_pool));
2211
if (kind == svn_node_file)
2213
svn_stringbuf_t *rep_string;
2214
SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name,
2216
SVN_ERR(svn_fs_fs__parse_representation(old_rep, rep_string,
2217
result_pool, scratch_pool));
2222
return SVN_NO_ERROR;
2224
/* We don't want 0-length PLAIN representations to replace non-0-length
2225
ones (see issue #4554). Take into account that EXPANDED_SIZE may be
2226
0 in which case we have to check the on-disk SIZE. Also, this doubles
2227
as a simple guard against general rep-cache induced corruption. */
2228
if ( ((*old_rep)->expanded_size != rep->expanded_size)
2229
|| ((rep->expanded_size == 0) && ((*old_rep)->size != rep->size)))
2235
/* Add information that is missing in the cached data.
2236
Use the old rep for this content. */
2237
memcpy((*old_rep)->md5_digest, rep->md5_digest, sizeof(rep->md5_digest));
2238
(*old_rep)->uniquifier = rep->uniquifier;
2241
return SVN_NO_ERROR;
2244
/* Copy the hash sum calculation results from MD5_CTX, SHA1_CTX into REP.
2245
* Use POOL for allocations.
2247
static svn_error_t *
2248
digests_final(representation_t *rep,
2249
const svn_checksum_ctx_t *md5_ctx,
2250
const svn_checksum_ctx_t *sha1_ctx,
2253
svn_checksum_t *checksum;
2255
SVN_ERR(svn_checksum_final(&checksum, md5_ctx, pool));
2256
memcpy(rep->md5_digest, checksum->digest, svn_checksum_size(checksum));
2257
SVN_ERR(svn_checksum_final(&checksum, sha1_ctx, pool));
2258
rep->has_sha1 = checksum != NULL;
2260
memcpy(rep->sha1_digest, checksum->digest, svn_checksum_size(checksum));
2262
return SVN_NO_ERROR;
2265
/* Close handler for the representation write stream. BATON is a
2266
rep_write_baton. Writes out a new node-rev that correctly
2267
references the representation we just finished writing. */
2268
static svn_error_t *
2269
rep_write_contents_close(void *baton)
2271
struct rep_write_baton *b = baton;
2272
representation_t *rep;
2273
representation_t *old_rep;
2276
rep = apr_pcalloc(b->result_pool, sizeof(*rep));
2278
/* Close our delta stream so the last bits of svndiff are written
2280
if (b->delta_stream)
2281
SVN_ERR(svn_stream_close(b->delta_stream));
2283
/* Determine the length of the svndiff data. */
2284
SVN_ERR(svn_fs_fs__get_file_offset(&offset, b->file, b->scratch_pool));
2285
rep->size = offset - b->delta_start;
2287
/* Fill in the rest of the representation field. */
2288
rep->expanded_size = b->rep_size;
2289
rep->txn_id = *svn_fs_fs__id_txn_id(b->noderev->id);
2290
SVN_ERR(set_uniquifier(b->fs, rep, b->scratch_pool));
2291
rep->revision = SVN_INVALID_REVNUM;
2293
/* Finalize the checksum. */
2294
SVN_ERR(digests_final(rep, b->md5_checksum_ctx, b->sha1_checksum_ctx,
2297
/* Check and see if we already have a representation somewhere that's
2298
identical to the one we just wrote out. */
2299
SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->result_pool,
2304
/* We need to erase from the protorev the data we just wrote. */
2305
SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->scratch_pool));
2307
/* Use the old rep for this content. */
2308
b->noderev->data_rep = old_rep;
2312
/* Write out our cosmetic end marker. */
2313
SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n"));
2314
SVN_ERR(allocate_item_index(&rep->item_index, b->fs, &rep->txn_id,
2315
b->rep_offset, b->scratch_pool));
2317
b->noderev->data_rep = rep;
2320
/* Remove cleanup callback. */
2321
apr_pool_cleanup_kill(b->scratch_pool, b, rep_write_cleanup);
2323
/* Write out the new node-rev information. */
2324
SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev,
2325
FALSE, b->scratch_pool));
2328
svn_fs_fs__p2l_entry_t entry;
2330
entry.offset = b->rep_offset;
2331
SVN_ERR(svn_fs_fs__get_file_offset(&offset, b->file, b->scratch_pool));
2332
entry.size = offset - b->rep_offset;
2333
entry.type = SVN_FS_FS__ITEM_TYPE_FILE_REP;
2334
entry.item.revision = SVN_INVALID_REVNUM;
2335
entry.item.number = rep->item_index;
2336
SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
2337
b->fnv1a_checksum_ctx,
2340
SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->scratch_pool));
2341
SVN_ERR(store_p2l_index_entry(b->fs, &rep->txn_id, &entry,
2345
SVN_ERR(svn_io_file_close(b->file, b->scratch_pool));
2346
SVN_ERR(unlock_proto_rev(b->fs, &rep->txn_id, b->lockcookie,
2348
svn_pool_destroy(b->scratch_pool);
2350
return SVN_NO_ERROR;
2353
/* Store a writable stream in *CONTENTS_P that will receive all data
2354
written and store it as the file data representation referenced by
2355
NODEREV in filesystem FS. Perform temporary allocations in
2356
POOL. Only appropriate for file data, not props or directory
2358
static svn_error_t *
2359
set_representation(svn_stream_t **contents_p,
2361
node_revision_t *noderev,
2364
struct rep_write_baton *wb;
2366
if (! svn_fs_fs__id_is_txn(noderev->id))
2367
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2368
_("Attempted to write to non-transaction '%s'"),
2369
svn_fs_fs__id_unparse(noderev->id, pool)->data);
2371
SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool));
2373
*contents_p = svn_stream_create(wb, pool);
2374
svn_stream_set_write(*contents_p, rep_write_contents);
2375
svn_stream_set_close(*contents_p, rep_write_contents_close);
2377
return SVN_NO_ERROR;
2381
svn_fs_fs__set_contents(svn_stream_t **stream,
2383
node_revision_t *noderev,
2386
if (noderev->kind != svn_node_file)
2387
return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
2388
_("Can't set text contents of a directory"));
2390
return set_representation(stream, fs, noderev, pool);
2394
svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
2396
const svn_fs_id_t *old_idp,
2397
node_revision_t *new_noderev,
2398
const svn_fs_fs__id_part_t *copy_id,
2399
const svn_fs_fs__id_part_t *txn_id,
2402
const svn_fs_id_t *id;
2405
copy_id = svn_fs_fs__id_copy_id(old_idp);
2406
id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id,
2409
new_noderev->id = id;
2411
if (! new_noderev->copyroot_path)
2413
new_noderev->copyroot_path = apr_pstrdup(pool,
2414
new_noderev->created_path);
2415
new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id);
2418
SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE,
2423
return SVN_NO_ERROR;
2427
svn_fs_fs__set_proplist(svn_fs_t *fs,
2428
node_revision_t *noderev,
2429
apr_hash_t *proplist,
2432
const char *filename
2433
= svn_fs_fs__path_txn_node_props(fs, noderev->id, pool);
2437
/* Dump the property list to the mutable property file. */
2438
SVN_ERR(svn_io_file_open(&file, filename,
2439
APR_WRITE | APR_CREATE | APR_TRUNCATE
2440
| APR_BUFFERED, APR_OS_DEFAULT, pool));
2441
out = svn_stream_from_aprfile2(file, TRUE, pool);
2442
SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool));
2443
SVN_ERR(svn_io_file_close(file, pool));
2445
/* Mark the node-rev's prop rep as mutable, if not already done. */
2446
if (!noderev->prop_rep || !is_txn_rep(noderev->prop_rep))
2448
noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep));
2449
noderev->prop_rep->txn_id = *svn_fs_fs__id_txn_id(noderev->id);
2450
SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE,
2454
return SVN_NO_ERROR;
2457
/* This baton is used by the stream created for write_container_rep. */
2458
struct write_container_baton
2460
svn_stream_t *stream;
2464
svn_checksum_ctx_t *md5_ctx;
2465
svn_checksum_ctx_t *sha1_ctx;
2468
/* The handler for the write_container_rep stream. BATON is a
2469
write_container_baton, DATA has the data to write and *LEN is the number
2470
of bytes to write. */
2471
static svn_error_t *
2472
write_container_handler(void *baton,
2476
struct write_container_baton *whb = baton;
2478
SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
2479
SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
2481
SVN_ERR(svn_stream_write(whb->stream, data, len));
2484
return SVN_NO_ERROR;
2487
/* Callback function type. Write the data provided by BATON into STREAM. */
2488
typedef svn_error_t *
2489
(* collection_writer_t)(svn_stream_t *stream, void *baton, apr_pool_t *pool);
2491
/* Implement collection_writer_t writing the C string->svn_string_t hash
2493
static svn_error_t *
2494
write_hash_to_stream(svn_stream_t *stream,
2498
apr_hash_t *hash = baton;
2499
SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
2501
return SVN_NO_ERROR;
2504
/* Implement collection_writer_t writing the svn_fs_dirent_t* array given
2506
static svn_error_t *
2507
write_directory_to_stream(svn_stream_t *stream,
2511
apr_array_header_t *dir = baton;
2512
SVN_ERR(unparse_dir_entries(dir, stream, pool));
2514
return SVN_NO_ERROR;
2517
/* Write out the COLLECTION as a text representation to file FILE using
2518
WRITER. In the process, record position, the total size of the dump and
2519
MD5 as well as SHA1 in REP. Add the representation of type ITEM_TYPE to
2520
the indexes if necessary. If rep sharing has been enabled and REPS_HASH
2521
is not NULL, it will be used in addition to the on-disk cache to find
2522
earlier reps with the same content. When such existing reps can be
2523
found, we will truncate the one just written from the file and return
2524
the existing rep. Perform temporary allocations in SCRATCH_POOL. */
2525
static svn_error_t *
2526
write_container_rep(representation_t *rep,
2529
collection_writer_t writer,
2531
apr_hash_t *reps_hash,
2532
apr_uint32_t item_type,
2533
apr_pool_t *scratch_pool)
2535
svn_stream_t *stream;
2536
struct write_container_baton *whb;
2537
svn_checksum_ctx_t *fnv1a_checksum_ctx;
2538
representation_t *old_rep;
2539
apr_off_t offset = 0;
2541
SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
2543
whb = apr_pcalloc(scratch_pool, sizeof(*whb));
2545
whb->stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
2546
svn_stream_from_aprfile2(file, TRUE,
2550
whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool);
2551
whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool);
2553
stream = svn_stream_create(whb, scratch_pool);
2554
svn_stream_set_write(stream, write_container_handler);
2556
SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n"));
2558
SVN_ERR(writer(stream, collection, scratch_pool));
2560
/* Store the results. */
2561
SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool));
2563
/* Check and see if we already have a representation somewhere that's
2564
identical to the one we just wrote out. */
2565
SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, scratch_pool,
2570
/* We need to erase from the protorev the data we just wrote. */
2571
SVN_ERR(svn_io_file_trunc(file, offset, scratch_pool));
2573
/* Use the old rep for this content. */
2574
memcpy(rep, old_rep, sizeof (*rep));
2578
svn_fs_fs__p2l_entry_t entry;
2580
/* Write out our cosmetic end marker. */
2581
SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n"));
2583
SVN_ERR(allocate_item_index(&rep->item_index, fs, &rep->txn_id,
2584
offset, scratch_pool));
2586
entry.offset = offset;
2587
SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
2588
entry.size = offset - entry.offset;
2589
entry.type = item_type;
2590
entry.item.revision = SVN_INVALID_REVNUM;
2591
entry.item.number = rep->item_index;
2592
SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
2596
SVN_ERR(store_p2l_index_entry(fs, &rep->txn_id, &entry, scratch_pool));
2598
/* update the representation */
2599
rep->size = whb->size;
2600
rep->expanded_size = whb->size;
2603
return SVN_NO_ERROR;
2606
/* Write out the COLLECTION pertaining to the NODEREV in FS as a deltified
2607
text representation to file FILE using WRITER. In the process, record the
2608
total size and the md5 digest in REP and add the representation of type
2609
ITEM_TYPE to the indexes if necessary. If rep sharing has been enabled and
2610
REPS_HASH is not NULL, it will be used in addition to the on-disk cache to
2611
find earlier reps with the same content. When such existing reps can be
2612
found, we will truncate the one just written from the file and return the
2615
If ITEM_TYPE is IS_PROPS equals SVN_FS_FS__ITEM_TYPE_*_PROPS, assume
2616
that we want to a props representation as the base for our delta.
2617
Perform temporary allocations in SCRATCH_POOL.
2619
static svn_error_t *
2620
write_container_delta_rep(representation_t *rep,
2623
collection_writer_t writer,
2625
node_revision_t *noderev,
2626
apr_hash_t *reps_hash,
2627
apr_uint32_t item_type,
2628
apr_pool_t *scratch_pool)
2630
svn_txdelta_window_handler_t diff_wh;
2633
svn_stream_t *file_stream;
2634
svn_stream_t *stream;
2635
representation_t *base_rep;
2636
representation_t *old_rep;
2637
svn_checksum_ctx_t *fnv1a_checksum_ctx;
2638
svn_stream_t *source;
2639
svn_fs_fs__rep_header_t header = { 0 };
2641
apr_off_t rep_end = 0;
2642
apr_off_t delta_start = 0;
2643
apr_off_t offset = 0;
2645
struct write_container_baton *whb;
2646
fs_fs_data_t *ffd = fs->fsap_data;
2647
int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
2648
svn_boolean_t is_props = (item_type == SVN_FS_FS__ITEM_TYPE_FILE_PROPS)
2649
|| (item_type == SVN_FS_FS__ITEM_TYPE_DIR_PROPS);
2651
/* Get the base for this delta. */
2652
SVN_ERR(choose_delta_base(&base_rep, fs, noderev, is_props, scratch_pool));
2653
SVN_ERR(svn_fs_fs__get_contents(&source, fs, base_rep, FALSE, scratch_pool));
2655
SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
2657
/* Write out the rep header. */
2660
header.base_revision = base_rep->revision;
2661
header.base_item_index = base_rep->item_index;
2662
header.base_length = base_rep->size;
2663
header.type = svn_fs_fs__rep_delta;
2667
header.type = svn_fs_fs__rep_self_delta;
2670
file_stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
2671
svn_stream_from_aprfile2(file, TRUE,
2674
SVN_ERR(svn_fs_fs__write_rep_header(&header, file_stream, scratch_pool));
2675
SVN_ERR(svn_fs_fs__get_file_offset(&delta_start, file, scratch_pool));
2677
/* Prepare to write the svndiff data. */
2678
svn_txdelta_to_svndiff3(&diff_wh,
2682
ffd->delta_compression_level,
2685
whb = apr_pcalloc(scratch_pool, sizeof(*whb));
2686
whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source,
2689
whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool);
2690
whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool);
2692
/* serialize the hash */
2693
stream = svn_stream_create(whb, scratch_pool);
2694
svn_stream_set_write(stream, write_container_handler);
2696
SVN_ERR(writer(stream, collection, scratch_pool));
2697
SVN_ERR(svn_stream_close(whb->stream));
2699
/* Store the results. */
2700
SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool));
2702
/* Check and see if we already have a representation somewhere that's
2703
identical to the one we just wrote out. */
2704
SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, scratch_pool,
2709
/* We need to erase from the protorev the data we just wrote. */
2710
SVN_ERR(svn_io_file_trunc(file, offset, scratch_pool));
2712
/* Use the old rep for this content. */
2713
memcpy(rep, old_rep, sizeof (*rep));
2717
svn_fs_fs__p2l_entry_t entry;
2719
/* Write out our cosmetic end marker. */
2720
SVN_ERR(svn_fs_fs__get_file_offset(&rep_end, file, scratch_pool));
2721
SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n"));
2723
SVN_ERR(allocate_item_index(&rep->item_index, fs, &rep->txn_id,
2724
offset, scratch_pool));
2726
entry.offset = offset;
2727
SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
2728
entry.size = offset - entry.offset;
2729
entry.type = item_type;
2730
entry.item.revision = SVN_INVALID_REVNUM;
2731
entry.item.number = rep->item_index;
2732
SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
2736
SVN_ERR(store_p2l_index_entry(fs, &rep->txn_id, &entry, scratch_pool));
2738
/* update the representation */
2739
rep->expanded_size = whb->size;
2740
rep->size = rep_end - delta_start;
2743
return SVN_NO_ERROR;
2746
/* Sanity check ROOT_NODEREV, a candidate for being the root node-revision
2747
of (not yet committed) revision REV in FS. Use POOL for temporary
2750
If you change this function, consider updating svn_fs_fs__verify() too.
2752
static svn_error_t *
2753
validate_root_noderev(svn_fs_t *fs,
2754
node_revision_t *root_noderev,
2758
svn_revnum_t head_revnum = rev-1;
2759
int head_predecessor_count;
2761
SVN_ERR_ASSERT(rev > 0);
2763
/* Compute HEAD_PREDECESSOR_COUNT. */
2765
svn_fs_root_t *head_revision;
2766
const svn_fs_id_t *head_root_id;
2767
node_revision_t *head_root_noderev;
2769
/* Get /@HEAD's noderev. */
2770
SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool));
2771
SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool));
2772
SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id,
2774
head_predecessor_count = head_root_noderev->predecessor_count;
2777
/* Check that the root noderev's predecessor count equals REV.
2779
This kind of corruption was seen on svn.apache.org (both on
2780
the root noderev and on other fspaths' noderevs); see
2783
Normally (rev == root_noderev->predecessor_count), but here we
2784
use a more roundabout check that should only trigger on new instances
2785
of the corruption, rather then trigger on each and every new commit
2786
to a repository that has triggered the bug somewhere in its root
2789
if (root_noderev->predecessor_count != -1
2790
&& (root_noderev->predecessor_count - head_predecessor_count)
2791
!= (rev - head_revnum))
2793
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2794
_("predecessor count for "
2795
"the root node-revision is wrong: "
2796
"found (%d+%ld != %d), committing r%ld"),
2797
head_predecessor_count,
2798
rev - head_revnum, /* This is equal to 1. */
2799
root_noderev->predecessor_count,
2803
return SVN_NO_ERROR;
2806
/* Given the potentially txn-local id PART, update that to a permanent ID
2807
* based on the REVISION currently being written and the START_ID for that
2808
* revision. Use the repo FORMAT to decide which implementation to use.
2811
get_final_id(svn_fs_fs__id_part_t *part,
2812
svn_revnum_t revision,
2813
apr_uint64_t start_id,
2816
if (part->revision == SVN_INVALID_REVNUM)
2818
if (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
2820
part->revision = revision;
2825
part->number += start_id;
2830
/* Copy a node-revision specified by id ID in fileystem FS from a
2831
transaction into the proto-rev-file FILE. Set *NEW_ID_P to a
2832
pointer to the new node-id which will be allocated in POOL.
2833
If this is a directory, copy all children as well.
2835
START_NODE_ID and START_COPY_ID are
2836
the first available node and copy ids for this filesystem, for older
2839
REV is the revision number that this proto-rev-file will represent.
2841
INITIAL_OFFSET is the offset of the proto-rev-file on entry to
2844
If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
2845
REPS_POOL) of each data rep that is new in this revision.
2847
If REPS_HASH is not NULL, append copies (allocated in REPS_POOL)
2848
of the representations of each property rep that is new in this
2851
AT_ROOT is true if the node revision being written is the root
2852
node-revision. It is only controls additional sanity checking
2855
Temporary allocations are also from POOL. */
2856
static svn_error_t *
2857
write_final_rev(const svn_fs_id_t **new_id_p,
2861
const svn_fs_id_t *id,
2862
apr_uint64_t start_node_id,
2863
apr_uint64_t start_copy_id,
2864
apr_off_t initial_offset,
2865
apr_array_header_t *reps_to_cache,
2866
apr_hash_t *reps_hash,
2867
apr_pool_t *reps_pool,
2868
svn_boolean_t at_root,
2871
node_revision_t *noderev;
2872
apr_off_t my_offset;
2873
const svn_fs_id_t *new_id;
2874
svn_fs_fs__id_part_t node_id, copy_id, rev_item;
2875
fs_fs_data_t *ffd = fs->fsap_data;
2876
const svn_fs_fs__id_part_t *txn_id = svn_fs_fs__id_txn_id(id);
2877
svn_stream_t *file_stream;
2878
svn_checksum_ctx_t *fnv1a_checksum_ctx;
2879
apr_pool_t *subpool;
2883
/* Check to see if this is a transaction node. */
2884
if (! svn_fs_fs__id_is_txn(id))
2885
return SVN_NO_ERROR;
2887
subpool = svn_pool_create(pool);
2888
SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool, subpool));
2890
if (noderev->kind == svn_node_dir)
2892
apr_array_header_t *entries;
2895
/* This is a directory. Write out all the children first. */
2897
SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool,
2899
for (i = 0; i < entries->nelts; ++i)
2901
svn_fs_dirent_t *dirent
2902
= APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
2904
svn_pool_clear(subpool);
2905
SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
2906
start_node_id, start_copy_id, initial_offset,
2907
reps_to_cache, reps_hash, reps_pool, FALSE,
2909
if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
2910
dirent->id = svn_fs_fs__id_copy(new_id, pool);
2913
if (noderev->data_rep && is_txn_rep(noderev->data_rep))
2915
/* Write out the contents of this directory as a text rep. */
2916
noderev->data_rep->revision = rev;
2917
if (ffd->deltify_directories)
2918
SVN_ERR(write_container_delta_rep(noderev->data_rep, file,
2920
write_directory_to_stream,
2922
SVN_FS_FS__ITEM_TYPE_DIR_REP,
2925
SVN_ERR(write_container_rep(noderev->data_rep, file, entries,
2926
write_directory_to_stream, fs, NULL,
2927
SVN_FS_FS__ITEM_TYPE_DIR_REP, pool));
2929
reset_txn_in_rep(noderev->data_rep);
2934
/* This is a file. We should make sure the data rep, if it
2935
exists in a "this" state, gets rewritten to our new revision
2938
if (noderev->data_rep && is_txn_rep(noderev->data_rep))
2940
reset_txn_in_rep(noderev->data_rep);
2941
noderev->data_rep->revision = rev;
2943
if (!svn_fs_fs__use_log_addressing(fs))
2945
/* See issue 3845. Some unknown mechanism caused the
2946
protorev file to get truncated, so check for that
2948
if (noderev->data_rep->item_index + noderev->data_rep->size
2950
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2951
_("Truncated protorev file detected"));
2956
svn_pool_destroy(subpool);
2958
/* Fix up the property reps. */
2959
if (noderev->prop_rep && is_txn_rep(noderev->prop_rep))
2961
apr_hash_t *proplist;
2962
apr_uint32_t item_type = noderev->kind == svn_node_dir
2963
? SVN_FS_FS__ITEM_TYPE_DIR_PROPS
2964
: SVN_FS_FS__ITEM_TYPE_FILE_PROPS;
2965
SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
2967
noderev->prop_rep->revision = rev;
2969
if (ffd->deltify_properties)
2970
SVN_ERR(write_container_delta_rep(noderev->prop_rep, file, proplist,
2971
write_hash_to_stream, fs, noderev,
2972
reps_hash, item_type, pool));
2974
SVN_ERR(write_container_rep(noderev->prop_rep, file, proplist,
2975
write_hash_to_stream, fs, reps_hash,
2978
reset_txn_in_rep(noderev->prop_rep);
2981
/* Convert our temporary ID into a permanent revision one. */
2982
node_id = *svn_fs_fs__id_node_id(noderev->id);
2983
get_final_id(&node_id, rev, start_node_id, ffd->format);
2984
copy_id = *svn_fs_fs__id_copy_id(noderev->id);
2985
get_final_id(©_id, rev, start_copy_id, ffd->format);
2987
if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
2988
noderev->copyroot_rev = rev;
2990
/* root nodes have a fixed ID in log addressing mode */
2991
SVN_ERR(svn_fs_fs__get_file_offset(&my_offset, file, pool));
2992
if (svn_fs_fs__use_log_addressing(fs) && at_root)
2994
/* reference the root noderev from the log-to-phys index */
2995
rev_item.number = SVN_FS_FS__ITEM_INDEX_ROOT_NODE;
2996
SVN_ERR(store_l2p_index_entry(fs, txn_id, my_offset,
2997
rev_item.number, pool));
3000
SVN_ERR(allocate_item_index(&rev_item.number, fs, txn_id,
3003
rev_item.revision = rev;
3004
new_id = svn_fs_fs__id_rev_create(&node_id, ©_id, &rev_item, pool);
3006
noderev->id = new_id;
3008
if (ffd->rep_sharing_allowed)
3010
/* Save the data representation's hash in the rep cache. */
3011
if ( noderev->data_rep && noderev->kind == svn_node_file
3012
&& noderev->data_rep->revision == rev)
3014
SVN_ERR_ASSERT(reps_to_cache && reps_pool);
3015
APR_ARRAY_PUSH(reps_to_cache, representation_t *)
3016
= svn_fs_fs__rep_copy(noderev->data_rep, reps_pool);
3019
if (noderev->prop_rep && noderev->prop_rep->revision == rev)
3021
/* Add new property reps to hash and on-disk cache. */
3022
representation_t *copy
3023
= svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool);
3025
SVN_ERR_ASSERT(reps_to_cache && reps_pool);
3026
APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy;
3028
apr_hash_set(reps_hash,
3030
APR_SHA1_DIGESTSIZE,
3035
/* don't serialize SHA1 for dirs to disk (waste of space) */
3036
if (noderev->data_rep && noderev->kind == svn_node_dir)
3037
noderev->data_rep->has_sha1 = FALSE;
3039
/* don't serialize SHA1 for props to disk (waste of space) */
3040
if (noderev->prop_rep)
3041
noderev->prop_rep->has_sha1 = FALSE;
3043
/* Workaround issue #4031: is-fresh-txn-root in revision files. */
3044
noderev->is_fresh_txn_root = FALSE;
3046
/* Write out our new node-revision. */
3048
SVN_ERR(validate_root_noderev(fs, noderev, rev, pool));
3050
file_stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
3051
svn_stream_from_aprfile2(file, TRUE, pool),
3053
SVN_ERR(svn_fs_fs__write_noderev(file_stream, noderev, ffd->format,
3054
svn_fs_fs__fs_supports_mergeinfo(fs),
3057
/* reference the root noderev from the log-to-phys index */
3058
if (svn_fs_fs__use_log_addressing(fs))
3060
svn_fs_fs__p2l_entry_t entry;
3061
rev_item.revision = SVN_INVALID_REVNUM;
3063
entry.offset = my_offset;
3064
SVN_ERR(svn_fs_fs__get_file_offset(&my_offset, file, pool));
3065
entry.size = my_offset - entry.offset;
3066
entry.type = SVN_FS_FS__ITEM_TYPE_NODEREV;
3067
entry.item = rev_item;
3068
SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
3072
SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, pool));
3075
/* Return our ID that references the revision file. */
3076
*new_id_p = noderev->id;
3078
return SVN_NO_ERROR;
3081
/* Write the changed path info CHANGED_PATHS from transaction TXN_ID to the
3082
permanent rev-file FILE in filesystem FS. *OFFSET_P is set the to offset
3083
in the file of the beginning of this information. Perform temporary
3084
allocations in POOL. */
3085
static svn_error_t *
3086
write_final_changed_path_info(apr_off_t *offset_p,
3089
const svn_fs_fs__id_part_t *txn_id,
3090
apr_hash_t *changed_paths,
3094
svn_stream_t *stream;
3095
svn_checksum_ctx_t *fnv1a_checksum_ctx;
3097
SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool));
3099
/* write to target file & calculate checksum */
3100
stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
3101
svn_stream_from_aprfile2(file, TRUE, pool),
3103
SVN_ERR(svn_fs_fs__write_changes(stream, fs, changed_paths, TRUE, pool));
3107
/* reference changes from the indexes */
3108
if (svn_fs_fs__use_log_addressing(fs))
3110
svn_fs_fs__p2l_entry_t entry;
3112
entry.offset = offset;
3113
SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool));
3114
entry.size = offset - entry.offset;
3115
entry.type = SVN_FS_FS__ITEM_TYPE_CHANGES;
3116
entry.item.revision = SVN_INVALID_REVNUM;
3117
entry.item.number = SVN_FS_FS__ITEM_INDEX_CHANGES;
3118
SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
3122
SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, pool));
3123
SVN_ERR(store_l2p_index_entry(fs, txn_id, entry.offset,
3124
SVN_FS_FS__ITEM_INDEX_CHANGES, pool));
3127
return SVN_NO_ERROR;
3130
/* Open a new svn_fs_t handle to FS, set that handle's concept of "current
3131
youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on
3132
NEW_REV's revision root.
3134
Intended to be called as the very last step in a commit before 'current'
3135
is bumped. This implies that we are holding the write lock. */
3136
static svn_error_t *
3137
verify_as_revision_before_current_plus_plus(svn_fs_t *fs,
3138
svn_revnum_t new_rev,
3142
fs_fs_data_t *ffd = fs->fsap_data;
3143
svn_fs_t *ft; /* fs++ == ft */
3144
svn_fs_root_t *root;
3145
fs_fs_data_t *ft_ffd;
3146
apr_hash_t *fs_config;
3148
SVN_ERR_ASSERT(ffd->svn_fs_open_);
3150
/* make sure FT does not simply return data cached by other instances
3151
* but actually retrieves it from disk at least once.
3153
fs_config = apr_hash_make(pool);
3154
svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
3155
svn_uuid_generate(pool));
3156
SVN_ERR(ffd->svn_fs_open_(&ft, fs->path,
3160
ft_ffd = ft->fsap_data;
3161
/* Don't let FT consult rep-cache.db, either. */
3162
ft_ffd->rep_sharing_allowed = FALSE;
3165
ft_ffd->youngest_rev_cache = new_rev;
3167
SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool));
3168
SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev);
3169
SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev);
3170
SVN_ERR(svn_fs_fs__verify_root(root, pool));
3171
#endif /* SVN_DEBUG */
3173
return SVN_NO_ERROR;
3176
/* Update the 'current' file to hold the correct next node and copy_ids
3177
from transaction TXN_ID in filesystem FS. The current revision is
3178
set to REV. Perform temporary allocations in POOL. */
3179
static svn_error_t *
3180
write_final_current(svn_fs_t *fs,
3181
const svn_fs_fs__id_part_t *txn_id,
3183
apr_uint64_t start_node_id,
3184
apr_uint64_t start_copy_id,
3187
apr_uint64_t txn_node_id;
3188
apr_uint64_t txn_copy_id;
3189
fs_fs_data_t *ffd = fs->fsap_data;
3191
if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
3192
return svn_fs_fs__write_current(fs, rev, 0, 0, pool);
3194
/* To find the next available ids, we add the id that used to be in
3195
the 'current' file, to the next ids from the transaction file. */
3196
SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool));
3198
start_node_id += txn_node_id;
3199
start_copy_id += txn_copy_id;
3201
return svn_fs_fs__write_current(fs, rev, start_node_id, start_copy_id,
3205
/* Verify that the user registered with FS has all the locks necessary to
3206
permit all the changes associated with TXN_NAME.
3207
The FS write lock is assumed to be held by the caller. */
3208
static svn_error_t *
3209
verify_locks(svn_fs_t *fs,
3210
const svn_fs_fs__id_part_t *txn_id,
3211
apr_hash_t *changed_paths,
3214
apr_pool_t *iterpool;
3215
apr_array_header_t *changed_paths_sorted;
3216
svn_stringbuf_t *last_recursed = NULL;
3219
/* Make an array of the changed paths, and sort them depth-first-ily. */
3220
changed_paths_sorted = svn_sort__hash(changed_paths,
3221
svn_sort_compare_items_as_paths,
3224
/* Now, traverse the array of changed paths, verify locks. Note
3225
that if we need to do a recursive verification a path, we'll skip
3226
over children of that path when we get to them. */
3227
iterpool = svn_pool_create(pool);
3228
for (i = 0; i < changed_paths_sorted->nelts; i++)
3230
const svn_sort__item_t *item;
3232
svn_fs_path_change2_t *change;
3233
svn_boolean_t recurse = TRUE;
3235
svn_pool_clear(iterpool);
3237
item = &APR_ARRAY_IDX(changed_paths_sorted, i, svn_sort__item_t);
3239
/* Fetch the change associated with our path. */
3241
change = item->value;
3243
/* If this path has already been verified as part of a recursive
3244
check of one of its parents, no need to do it again. */
3246
&& svn_fspath__skip_ancestor(last_recursed->data, path))
3249
/* What does it mean to succeed at lock verification for a given
3250
path? For an existing file or directory getting modified
3251
(text, props), it means we hold the lock on the file or
3252
directory. For paths being added or removed, we need to hold
3253
the locks for that path and any children of that path.
3255
WHEW! We have no reliable way to determine the node kind
3256
of deleted items, but fortunately we are going to do a
3257
recursive check on deleted paths regardless of their kind. */
3258
if (change->change_kind == svn_fs_path_change_modify)
3260
SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE,
3263
/* If we just did a recursive check, remember the path we
3264
checked (so children can be skipped). */
3267
if (! last_recursed)
3268
last_recursed = svn_stringbuf_create(path, pool);
3270
svn_stringbuf_set(last_recursed, path);
3273
svn_pool_destroy(iterpool);
3274
return SVN_NO_ERROR;
3277
/* Return in *PATH the path to a file containing the properties that
3278
make up the final revision properties file. This involves setting
3279
svn:date and removing any temporary properties associated with the
3281
static svn_error_t *
3282
write_final_revprop(const char **path,
3284
const svn_fs_fs__id_part_t *txn_id,
3287
apr_hash_t *txnprops;
3288
svn_boolean_t final_mods = FALSE;
3290
svn_string_t *client_date;
3292
SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, txn, pool));
3294
/* Remove any temporary txn props representing 'flags'. */
3295
if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
3297
svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD, NULL);
3301
if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
3303
svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL);
3307
client_date = svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE);
3310
svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE, NULL);
3314
/* Update commit time to ensure that svn:date revprops remain ordered if
3316
if (!client_date || strcmp(client_date->data, "1"))
3318
date.data = svn_time_to_cstring(apr_time_now(), pool);
3319
date.len = strlen(date.data);
3320
svn_hash_sets(txnprops, SVN_PROP_REVISION_DATE, &date);
3326
SVN_ERR(set_txn_proplist(txn->fs, txn_id, txnprops, TRUE, pool));
3327
*path = path_txn_props_final(txn->fs, txn_id, pool);
3331
*path = path_txn_props(txn->fs, txn_id, pool);
3334
return SVN_NO_ERROR;
3338
svn_fs_fs__add_index_data(svn_fs_t *fs,
3340
const char *l2p_proto_index,
3341
const char *p2l_proto_index,
3342
svn_revnum_t revision,
3345
apr_off_t l2p_offset;
3346
apr_off_t p2l_offset;
3347
svn_stringbuf_t *footer;
3348
unsigned char footer_length;
3349
svn_checksum_t *l2p_checksum;
3350
svn_checksum_t *p2l_checksum;
3352
/* Append the actual index data to the pack file. */
3354
SVN_ERR(svn_io_file_seek(file, APR_END, &l2p_offset, pool));
3355
SVN_ERR(svn_fs_fs__l2p_index_append(&l2p_checksum, fs, file,
3356
l2p_proto_index, revision,
3360
SVN_ERR(svn_io_file_seek(file, APR_END, &p2l_offset, pool));
3361
SVN_ERR(svn_fs_fs__p2l_index_append(&p2l_checksum, fs, file,
3362
p2l_proto_index, revision,
3365
/* Append footer. */
3366
footer = svn_fs_fs__unparse_footer(l2p_offset, l2p_checksum,
3367
p2l_offset, p2l_checksum, pool, pool);
3368
SVN_ERR(svn_io_file_write_full(file, footer->data, footer->len, NULL,
3371
footer_length = footer->len;
3372
SVN_ERR_ASSERT(footer_length == footer->len);
3373
SVN_ERR(svn_io_file_write_full(file, &footer_length, 1, NULL, pool));
3375
return SVN_NO_ERROR;
3378
/* Baton used for commit_body below. */
3379
struct commit_baton {
3380
svn_revnum_t *new_rev_p;
3383
apr_array_header_t *reps_to_cache;
3384
apr_hash_t *reps_hash;
3385
apr_pool_t *reps_pool;
3388
/* The work-horse for svn_fs_fs__commit, called with the FS write lock.
3389
This implements the svn_fs_fs__with_write_lock() 'body' callback
3390
type. BATON is a 'struct commit_baton *'. */
3391
static svn_error_t *
3392
commit_body(void *baton, apr_pool_t *pool)
3394
struct commit_baton *cb = baton;
3395
fs_fs_data_t *ffd = cb->fs->fsap_data;
3396
const char *old_rev_filename, *rev_filename, *proto_filename;
3397
const char *revprop_filename, *final_revprop;
3398
const svn_fs_id_t *root_id, *new_root_id;
3399
apr_uint64_t start_node_id;
3400
apr_uint64_t start_copy_id;
3401
svn_revnum_t old_rev, new_rev;
3402
apr_file_t *proto_file;
3403
void *proto_file_lockcookie;
3404
apr_off_t initial_offset, changed_path_offset;
3405
const svn_fs_fs__id_part_t *txn_id = svn_fs_fs__txn_get_id(cb->txn);
3406
apr_hash_t *changed_paths;
3408
/* Re-Read the current repository format. All our repo upgrade and
3409
config evaluation strategies are such that existing information in
3410
FS and FFD remains valid.
3412
Although we don't recommend upgrading hot repositories, people may
3413
still do it and we must make sure to either handle them gracefully
3416
Committing pre-format 3 txns will fail after upgrade to format 3+
3417
because the proto-rev cannot be found; no further action needed.
3418
Upgrades from pre-f7 to f7+ means a potential change in addressing
3419
mode for the final rev. We must be sure to detect that cause because
3420
the failure would only manifest once the new revision got committed.
3422
SVN_ERR(svn_fs_fs__read_format_file(cb->fs, pool));
3424
/* Read the current youngest revision and, possibly, the next available
3425
node id and copy id (for old format filesystems). Update the cached
3426
value for the youngest revision, because we have just checked it. */
3427
SVN_ERR(svn_fs_fs__read_current(&old_rev, &start_node_id, &start_copy_id,
3429
ffd->youngest_rev_cache = old_rev;
3431
/* Check to make sure this transaction is based off the most recent
3433
if (cb->txn->base_rev != old_rev)
3434
return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
3435
_("Transaction out of date"));
3437
/* We need the changes list for verification as well as for writing it
3438
to the final rev file. */
3439
SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, cb->fs, txn_id,
3442
/* Locks may have been added (or stolen) between the calling of
3443
previous svn_fs.h functions and svn_fs_commit_txn(), so we need
3444
to re-examine every changed-path in the txn and re-verify all
3445
discovered locks. */
3446
SVN_ERR(verify_locks(cb->fs, txn_id, changed_paths, pool));
3448
/* We are going to be one better than this puny old revision. */
3449
new_rev = old_rev + 1;
3451
/* Get a write handle on the proto revision file. */
3452
SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
3453
cb->fs, txn_id, pool));
3454
SVN_ERR(svn_fs_fs__get_file_offset(&initial_offset, proto_file, pool));
3456
/* Write out all the node-revisions and directory contents. */
3457
root_id = svn_fs_fs__id_txn_create_root(txn_id, pool);
3458
SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
3459
start_node_id, start_copy_id, initial_offset,
3460
cb->reps_to_cache, cb->reps_hash, cb->reps_pool,
3463
/* Write the changed-path information. */
3464
SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
3465
cb->fs, txn_id, changed_paths,
3468
if (svn_fs_fs__use_log_addressing(cb->fs))
3470
/* Append the index data to the rev file. */
3471
SVN_ERR(svn_fs_fs__add_index_data(cb->fs, proto_file,
3472
svn_fs_fs__path_l2p_proto_index(cb->fs, txn_id, pool),
3473
svn_fs_fs__path_p2l_proto_index(cb->fs, txn_id, pool),
3478
/* Write the final line. */
3480
svn_stringbuf_t *trailer
3481
= svn_fs_fs__unparse_revision_trailer
3482
((apr_off_t)svn_fs_fs__id_item(new_root_id),
3483
changed_path_offset,
3485
SVN_ERR(svn_io_file_write_full(proto_file, trailer->data, trailer->len,
3489
SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
3490
SVN_ERR(svn_io_file_close(proto_file, pool));
3492
/* We don't unlock the prototype revision file immediately to avoid a
3493
race with another caller writing to the prototype revision file
3494
before we commit it. */
3496
/* Create the shard for the rev and revprop file, if we're sharding and
3497
this is the first revision of a new shard. We don't care if this
3498
fails because the shard already existed for some reason. */
3499
if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0)
3501
/* Create the revs shard. */
3504
= svn_fs_fs__path_rev_shard(cb->fs, new_rev, pool);
3506
= svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
3507
if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
3508
return svn_error_trace(err);
3509
svn_error_clear(err);
3510
SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
3516
/* Create the revprops shard. */
3517
SVN_ERR_ASSERT(! svn_fs_fs__is_packed_revprop(cb->fs, new_rev));
3520
= svn_fs_fs__path_revprops_shard(cb->fs, new_rev, pool);
3522
= svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
3523
if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
3524
return svn_error_trace(err);
3525
svn_error_clear(err);
3526
SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
3533
/* Move the finished rev file into place.
3535
### This "breaks" the transaction by removing the protorev file
3536
### but the revision is not yet complete. If this commit does
3537
### not complete for any reason the transaction will be lost. */
3538
old_rev_filename = svn_fs_fs__path_rev_absolute(cb->fs, old_rev, pool);
3539
rev_filename = svn_fs_fs__path_rev(cb->fs, new_rev, pool);
3540
proto_filename = svn_fs_fs__path_txn_proto_rev(cb->fs, txn_id, pool);
3541
SVN_ERR(svn_fs_fs__move_into_place(proto_filename, rev_filename,
3542
old_rev_filename, pool));
3544
/* Now that we've moved the prototype revision file out of the way,
3545
we can unlock it (since further attempts to write to the file
3546
will fail as it no longer exists). We must do this so that we can
3547
remove the transaction directory later. */
3548
SVN_ERR(unlock_proto_rev(cb->fs, txn_id, proto_file_lockcookie, pool));
3550
/* Move the revprops file into place. */
3551
SVN_ERR_ASSERT(! svn_fs_fs__is_packed_revprop(cb->fs, new_rev));
3552
SVN_ERR(write_final_revprop(&revprop_filename, cb->txn, txn_id, pool));
3553
final_revprop = svn_fs_fs__path_revprops(cb->fs, new_rev, pool);
3554
SVN_ERR(svn_fs_fs__move_into_place(revprop_filename, final_revprop,
3555
old_rev_filename, pool));
3557
/* Update the 'current' file. */
3558
SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool));
3559
SVN_ERR(write_final_current(cb->fs, txn_id, new_rev, start_node_id,
3560
start_copy_id, pool));
3562
/* At this point the new revision is committed and globally visible
3563
so let the caller know it succeeded by giving it the new revision
3564
number, which fulfills svn_fs_commit_txn() contract. Any errors
3565
after this point do not change the fact that a new revision was
3567
*cb->new_rev_p = new_rev;
3569
ffd->youngest_rev_cache = new_rev;
3571
/* Remove this transaction directory. */
3572
SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool));
3574
return SVN_NO_ERROR;
3577
/* Add the representations in REPS_TO_CACHE (an array of representation_t *)
3578
* to the rep-cache database of FS. */
3579
static svn_error_t *
3580
write_reps_to_cache(svn_fs_t *fs,
3581
const apr_array_header_t *reps_to_cache,
3582
apr_pool_t *scratch_pool)
3586
for (i = 0; i < reps_to_cache->nelts; i++)
3588
representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *);
3590
SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, scratch_pool));
3593
return SVN_NO_ERROR;
3597
svn_fs_fs__commit(svn_revnum_t *new_rev_p,
3602
struct commit_baton cb;
3603
fs_fs_data_t *ffd = fs->fsap_data;
3605
cb.new_rev_p = new_rev_p;
3609
if (ffd->rep_sharing_allowed)
3611
cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *));
3612
cb.reps_hash = apr_hash_make(pool);
3613
cb.reps_pool = pool;
3617
cb.reps_to_cache = NULL;
3618
cb.reps_hash = NULL;
3619
cb.reps_pool = NULL;
3622
SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool));
3624
/* At this point, *NEW_REV_P has been set, so errors below won't affect
3625
the success of the commit. (See svn_fs_commit_txn().) */
3627
if (ffd->rep_sharing_allowed)
3629
SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
3631
/* Write new entries to the rep-sharing database.
3633
* We use an sqlite transaction to speed things up;
3634
* see <http://www.sqlite.org/faq.html#q19>.
3636
/* ### A commit that touches thousands of files will starve other
3637
(reader/writer) commits for the duration of the below call.
3638
Maybe write in batches? */
3639
SVN_SQLITE__WITH_TXN(
3640
write_reps_to_cache(fs, cb.reps_to_cache, pool),
3644
return SVN_NO_ERROR;
3649
svn_fs_fs__list_transactions(apr_array_header_t **names_p,
3653
const char *txn_dir;
3654
apr_hash_t *dirents;
3655
apr_hash_index_t *hi;
3656
apr_array_header_t *names;
3657
apr_size_t ext_len = strlen(PATH_EXT_TXN);
3659
names = apr_array_make(pool, 1, sizeof(const char *));
3661
/* Get the transactions directory. */
3662
txn_dir = svn_fs_fs__path_txns_dir(fs, pool);
3664
/* Now find a listing of this directory. */
3665
SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool));
3667
/* Loop through all the entries and return anything that ends with '.txn'. */
3668
for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
3670
const char *name = apr_hash_this_key(hi);
3671
apr_ssize_t klen = apr_hash_this_key_len(hi);
3674
/* The name must end with ".txn" to be considered a transaction. */
3675
if ((apr_size_t) klen <= ext_len
3676
|| (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0)
3679
/* Truncate the ".txn" extension and store the ID. */
3680
id = apr_pstrndup(pool, name, strlen(name) - ext_len);
3681
APR_ARRAY_PUSH(names, const char *) = id;
3686
return SVN_NO_ERROR;
3690
svn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
3697
svn_node_kind_t kind;
3698
transaction_t *local_txn;
3699
svn_fs_fs__id_part_t txn_id;
3701
SVN_ERR(svn_fs_fs__id_txn_parse(&txn_id, name));
3703
/* First check to see if the directory exists. */
3704
SVN_ERR(svn_io_check_path(svn_fs_fs__path_txn_dir(fs, &txn_id, pool),
3707
/* Did we find it? */
3708
if (kind != svn_node_dir)
3709
return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL,
3710
_("No such transaction '%s'"),
3713
txn = apr_pcalloc(pool, sizeof(*txn));
3714
ftd = apr_pcalloc(pool, sizeof(*ftd));
3715
ftd->txn_id = txn_id;
3717
/* Read in the root node of this transaction. */
3718
txn->id = apr_pstrdup(pool, name);
3721
SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, &txn_id, pool));
3723
txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id);
3725
txn->vtable = &txn_vtable;
3726
txn->fsap_data = ftd;
3729
return SVN_NO_ERROR;
3733
svn_fs_fs__txn_proplist(apr_hash_t **table_p,
3737
apr_hash_t *proplist = apr_hash_make(pool);
3738
SVN_ERR(get_txn_proplist(proplist, txn->fs, svn_fs_fs__txn_get_id(txn),
3740
*table_p = proplist;
3742
return SVN_NO_ERROR;
3747
svn_fs_fs__delete_node_revision(svn_fs_t *fs,
3748
const svn_fs_id_t *id,
3751
node_revision_t *noderev;
3753
SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool, pool));
3755
/* Delete any mutable property representation. */
3756
if (noderev->prop_rep && is_txn_rep(noderev->prop_rep))
3757
SVN_ERR(svn_io_remove_file2(svn_fs_fs__path_txn_node_props(fs, id, pool),
3760
/* Delete any mutable data representation. */
3761
if (noderev->data_rep && is_txn_rep(noderev->data_rep)
3762
&& noderev->kind == svn_node_dir)
3764
fs_fs_data_t *ffd = fs->fsap_data;
3765
SVN_ERR(svn_io_remove_file2(svn_fs_fs__path_txn_node_children(fs, id,
3769
/* remove the corresponding entry from the cache, if such exists */
3770
if (ffd->txn_dir_cache)
3772
const char *key = svn_fs_fs__id_unparse(id, pool)->data;
3773
SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL, pool));
3777
return svn_io_remove_file2(svn_fs_fs__path_txn_node_rev(fs, id, pool),
3783
/*** Transactions ***/
3786
svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
3787
const svn_fs_id_t **base_root_id_p,
3789
const svn_fs_fs__id_part_t *txn_id,
3793
SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_id, pool));
3794
*root_id_p = txn->root_id;
3795
*base_root_id_p = txn->base_id;
3796
return SVN_NO_ERROR;
3800
/* Generic transaction operations. */
3803
svn_fs_fs__txn_prop(svn_string_t **value_p,
3805
const char *propname,
3809
svn_fs_t *fs = txn->fs;
3811
SVN_ERR(svn_fs__check_fs(fs, TRUE));
3812
SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool));
3814
*value_p = svn_hash_gets(table, propname);
3816
return SVN_NO_ERROR;
3820
svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
3828
apr_hash_t *props = apr_hash_make(pool);
3830
SVN_ERR(svn_fs__check_fs(fs, TRUE));
3832
SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool));
3834
/* Put a datestamp on the newly created txn, so we always know
3835
exactly how old it is. (This will help sysadmins identify
3836
long-abandoned txns that may need to be manually removed.) When
3837
a txn is promoted to a revision, this property will be
3838
automatically overwritten with a revision datestamp. */
3839
date.data = svn_time_to_cstring(apr_time_now(), pool);
3840
date.len = strlen(date.data);
3842
svn_hash_sets(props, SVN_PROP_REVISION_DATE, &date);
3844
/* Set temporary txn props that represent the requested 'flags'
3846
if (flags & SVN_FS_TXN_CHECK_OOD)
3847
svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_OOD,
3848
svn_string_create("true", pool));
3850
if (flags & SVN_FS_TXN_CHECK_LOCKS)
3851
svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_LOCKS,
3852
svn_string_create("true", pool));
3854
if (flags & SVN_FS_TXN_CLIENT_DATE)
3855
svn_hash_sets(props, SVN_FS__PROP_TXN_CLIENT_DATE,
3856
svn_string_create("0", pool));
3858
ftd = (*txn_p)->fsap_data;
3859
return svn_error_trace(set_txn_proplist(fs, &ftd->txn_id, props, FALSE,