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