1289177Speter/* transaction.c --- transaction-related functions of FSFS 2289177Speter * 3289177Speter * ==================================================================== 4289177Speter * Licensed to the Apache Software Foundation (ASF) under one 5289177Speter * or more contributor license agreements. See the NOTICE file 6289177Speter * distributed with this work for additional information 7289177Speter * regarding copyright ownership. The ASF licenses this file 8289177Speter * to you under the Apache License, Version 2.0 (the 9289177Speter * "License"); you may not use this file except in compliance 10289177Speter * with the License. You may obtain a copy of the License at 11289177Speter * 12289177Speter * http://www.apache.org/licenses/LICENSE-2.0 13289177Speter * 14289177Speter * Unless required by applicable law or agreed to in writing, 15289177Speter * software distributed under the License is distributed on an 16289177Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17289177Speter * KIND, either express or implied. See the License for the 18289177Speter * specific language governing permissions and limitations 19289177Speter * under the License. 20289177Speter * ==================================================================== 21289177Speter */ 22289177Speter 23289177Speter#include "transaction.h" 24289177Speter 25289177Speter#include <assert.h> 26289177Speter#include <apr_sha1.h> 27289177Speter 28289177Speter#include "svn_hash.h" 29289177Speter#include "svn_props.h" 30289177Speter#include "svn_sorts.h" 31289177Speter#include "svn_time.h" 32289177Speter#include "svn_dirent_uri.h" 33289177Speter 34289177Speter#include "fs_fs.h" 35289177Speter#include "index.h" 36289177Speter#include "tree.h" 37289177Speter#include "util.h" 38289177Speter#include "id.h" 39289177Speter#include "low_level.h" 40289177Speter#include "temp_serializer.h" 41289177Speter#include "cached_data.h" 42289177Speter#include "lock.h" 43289177Speter#include "rep-cache.h" 44289177Speter 45289177Speter#include "private/svn_fs_util.h" 46289177Speter#include "private/svn_fspath.h" 47289177Speter#include "private/svn_sorts_private.h" 48289177Speter#include "private/svn_subr_private.h" 49289177Speter#include "private/svn_string_private.h" 50289177Speter#include "../libsvn_fs/fs-loader.h" 51289177Speter 52289177Speter#include "svn_private_config.h" 53289177Speter 54289177Speter/* Return the name of the sha1->rep mapping file in transaction TXN_ID 55289177Speter * within FS for the given SHA1 checksum. Use POOL for allocations. 56289177Speter */ 57289177Speterstatic APR_INLINE const char * 58289177Speterpath_txn_sha1(svn_fs_t *fs, 59289177Speter const svn_fs_fs__id_part_t *txn_id, 60289177Speter const unsigned char *sha1, 61289177Speter apr_pool_t *pool) 62289177Speter{ 63289177Speter svn_checksum_t checksum; 64289177Speter checksum.digest = sha1; 65289177Speter checksum.kind = svn_checksum_sha1; 66289177Speter 67289177Speter return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool), 68289177Speter svn_checksum_to_cstring(&checksum, pool), 69289177Speter pool); 70289177Speter} 71289177Speter 72289177Speterstatic APR_INLINE const char * 73289177Speterpath_txn_changes(svn_fs_t *fs, 74289177Speter const svn_fs_fs__id_part_t *txn_id, 75289177Speter apr_pool_t *pool) 76289177Speter{ 77289177Speter return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool), 78289177Speter PATH_CHANGES, pool); 79289177Speter} 80289177Speter 81289177Speterstatic APR_INLINE const char * 82289177Speterpath_txn_props(svn_fs_t *fs, 83289177Speter const svn_fs_fs__id_part_t *txn_id, 84289177Speter apr_pool_t *pool) 85289177Speter{ 86289177Speter return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool), 87289177Speter PATH_TXN_PROPS, pool); 88289177Speter} 89289177Speter 90289177Speterstatic APR_INLINE const char * 91289177Speterpath_txn_props_final(svn_fs_t *fs, 92289177Speter const svn_fs_fs__id_part_t *txn_id, 93289177Speter apr_pool_t *pool) 94289177Speter{ 95289177Speter return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool), 96289177Speter PATH_TXN_PROPS_FINAL, pool); 97289177Speter} 98289177Speter 99289177Speterstatic APR_INLINE const char * 100289177Speterpath_txn_next_ids(svn_fs_t *fs, 101289177Speter const svn_fs_fs__id_part_t *txn_id, 102289177Speter apr_pool_t *pool) 103289177Speter{ 104289177Speter return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool), 105289177Speter PATH_NEXT_IDS, pool); 106289177Speter} 107289177Speter 108289177Speter 109289177Speter/* The vtable associated with an open transaction object. */ 110289177Speterstatic txn_vtable_t txn_vtable = { 111289177Speter svn_fs_fs__commit_txn, 112289177Speter svn_fs_fs__abort_txn, 113289177Speter svn_fs_fs__txn_prop, 114289177Speter svn_fs_fs__txn_proplist, 115289177Speter svn_fs_fs__change_txn_prop, 116289177Speter svn_fs_fs__txn_root, 117289177Speter svn_fs_fs__change_txn_props 118289177Speter}; 119289177Speter 120289177Speter/* FSFS-specific data being attached to svn_fs_txn_t. 121289177Speter */ 122289177Spetertypedef struct fs_txn_data_t 123289177Speter{ 124289177Speter /* Strongly typed representation of the TXN's ID member. */ 125289177Speter svn_fs_fs__id_part_t txn_id; 126289177Speter} fs_txn_data_t; 127289177Speter 128289177Speterconst svn_fs_fs__id_part_t * 129289177Spetersvn_fs_fs__txn_get_id(svn_fs_txn_t *txn) 130289177Speter{ 131289177Speter fs_txn_data_t *ftd = txn->fsap_data; 132289177Speter return &ftd->txn_id; 133289177Speter} 134289177Speter 135289177Speter/* Functions for working with shared transaction data. */ 136289177Speter 137289177Speter/* Return the transaction object for transaction TXN_ID from the 138289177Speter transaction list of filesystem FS (which must already be locked via the 139289177Speter txn_list_lock mutex). If the transaction does not exist in the list, 140289177Speter then create a new transaction object and return it (if CREATE_NEW is 141289177Speter true) or return NULL (otherwise). */ 142289177Speterstatic fs_fs_shared_txn_data_t * 143289177Speterget_shared_txn(svn_fs_t *fs, 144289177Speter const svn_fs_fs__id_part_t *txn_id, 145289177Speter svn_boolean_t create_new) 146289177Speter{ 147289177Speter fs_fs_data_t *ffd = fs->fsap_data; 148289177Speter fs_fs_shared_data_t *ffsd = ffd->shared; 149289177Speter fs_fs_shared_txn_data_t *txn; 150289177Speter 151289177Speter for (txn = ffsd->txns; txn; txn = txn->next) 152289177Speter if (svn_fs_fs__id_part_eq(&txn->txn_id, txn_id)) 153289177Speter break; 154289177Speter 155289177Speter if (txn || !create_new) 156289177Speter return txn; 157289177Speter 158289177Speter /* Use the transaction object from the (single-object) freelist, 159289177Speter if one is available, or otherwise create a new object. */ 160289177Speter if (ffsd->free_txn) 161289177Speter { 162289177Speter txn = ffsd->free_txn; 163289177Speter ffsd->free_txn = NULL; 164289177Speter } 165289177Speter else 166289177Speter { 167289177Speter apr_pool_t *subpool = svn_pool_create(ffsd->common_pool); 168289177Speter txn = apr_palloc(subpool, sizeof(*txn)); 169289177Speter txn->pool = subpool; 170289177Speter } 171289177Speter 172289177Speter txn->txn_id = *txn_id; 173289177Speter txn->being_written = FALSE; 174289177Speter 175289177Speter /* Link this transaction into the head of the list. We will typically 176289177Speter be dealing with only one active transaction at a time, so it makes 177289177Speter sense for searches through the transaction list to look at the 178289177Speter newest transactions first. */ 179289177Speter txn->next = ffsd->txns; 180289177Speter ffsd->txns = txn; 181289177Speter 182289177Speter return txn; 183289177Speter} 184289177Speter 185289177Speter/* Free the transaction object for transaction TXN_ID, and remove it 186289177Speter from the transaction list of filesystem FS (which must already be 187289177Speter locked via the txn_list_lock mutex). Do nothing if the transaction 188289177Speter does not exist. */ 189289177Speterstatic void 190289177Speterfree_shared_txn(svn_fs_t *fs, const svn_fs_fs__id_part_t *txn_id) 191289177Speter{ 192289177Speter fs_fs_data_t *ffd = fs->fsap_data; 193289177Speter fs_fs_shared_data_t *ffsd = ffd->shared; 194289177Speter fs_fs_shared_txn_data_t *txn, *prev = NULL; 195289177Speter 196289177Speter for (txn = ffsd->txns; txn; prev = txn, txn = txn->next) 197289177Speter if (svn_fs_fs__id_part_eq(&txn->txn_id, txn_id)) 198289177Speter break; 199289177Speter 200289177Speter if (!txn) 201289177Speter return; 202289177Speter 203289177Speter if (prev) 204289177Speter prev->next = txn->next; 205289177Speter else 206289177Speter ffsd->txns = txn->next; 207289177Speter 208289177Speter /* As we typically will be dealing with one transaction after another, 209289177Speter we will maintain a single-object free list so that we can hopefully 210289177Speter keep reusing the same transaction object. */ 211289177Speter if (!ffsd->free_txn) 212289177Speter ffsd->free_txn = txn; 213289177Speter else 214289177Speter svn_pool_destroy(txn->pool); 215289177Speter} 216289177Speter 217289177Speter 218289177Speter/* Obtain a lock on the transaction list of filesystem FS, call BODY 219289177Speter with FS, BATON, and POOL, and then unlock the transaction list. 220289177Speter Return what BODY returned. */ 221289177Speterstatic svn_error_t * 222289177Speterwith_txnlist_lock(svn_fs_t *fs, 223289177Speter svn_error_t *(*body)(svn_fs_t *fs, 224289177Speter const void *baton, 225289177Speter apr_pool_t *pool), 226289177Speter const void *baton, 227289177Speter apr_pool_t *pool) 228289177Speter{ 229289177Speter fs_fs_data_t *ffd = fs->fsap_data; 230289177Speter fs_fs_shared_data_t *ffsd = ffd->shared; 231289177Speter 232289177Speter SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock, 233289177Speter body(fs, baton, pool)); 234289177Speter 235289177Speter return SVN_NO_ERROR; 236289177Speter} 237289177Speter 238289177Speter 239289177Speter/* A structure used by unlock_proto_rev() and unlock_proto_rev_body(), 240289177Speter which see. */ 241289177Speterstruct unlock_proto_rev_baton 242289177Speter{ 243289177Speter svn_fs_fs__id_part_t txn_id; 244289177Speter void *lockcookie; 245289177Speter}; 246289177Speter 247289177Speter/* Callback used in the implementation of unlock_proto_rev(). */ 248289177Speterstatic svn_error_t * 249289177Speterunlock_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool) 250289177Speter{ 251289177Speter const struct unlock_proto_rev_baton *b = baton; 252289177Speter apr_file_t *lockfile = b->lockcookie; 253289177Speter fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, &b->txn_id, FALSE); 254289177Speter apr_status_t apr_err; 255289177Speter 256289177Speter if (!txn) 257289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 258289177Speter _("Can't unlock unknown transaction '%s'"), 259289177Speter svn_fs_fs__id_txn_unparse(&b->txn_id, pool)); 260289177Speter if (!txn->being_written) 261289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 262289177Speter _("Can't unlock nonlocked transaction '%s'"), 263289177Speter svn_fs_fs__id_txn_unparse(&b->txn_id, pool)); 264289177Speter 265289177Speter apr_err = apr_file_unlock(lockfile); 266289177Speter if (apr_err) 267289177Speter return svn_error_wrap_apr 268289177Speter (apr_err, 269289177Speter _("Can't unlock prototype revision lockfile for transaction '%s'"), 270289177Speter svn_fs_fs__id_txn_unparse(&b->txn_id, pool)); 271289177Speter apr_err = apr_file_close(lockfile); 272289177Speter if (apr_err) 273289177Speter return svn_error_wrap_apr 274289177Speter (apr_err, 275289177Speter _("Can't close prototype revision lockfile for transaction '%s'"), 276289177Speter svn_fs_fs__id_txn_unparse(&b->txn_id, pool)); 277289177Speter 278289177Speter txn->being_written = FALSE; 279289177Speter 280289177Speter return SVN_NO_ERROR; 281289177Speter} 282289177Speter 283289177Speter/* Unlock the prototype revision file for transaction TXN_ID in filesystem 284289177Speter FS using cookie LOCKCOOKIE. The original prototype revision file must 285289177Speter have been closed _before_ calling this function. 286289177Speter 287289177Speter Perform temporary allocations in POOL. */ 288289177Speterstatic svn_error_t * 289289177Speterunlock_proto_rev(svn_fs_t *fs, 290289177Speter const svn_fs_fs__id_part_t *txn_id, 291289177Speter void *lockcookie, 292289177Speter apr_pool_t *pool) 293289177Speter{ 294289177Speter struct unlock_proto_rev_baton b; 295289177Speter 296289177Speter b.txn_id = *txn_id; 297289177Speter b.lockcookie = lockcookie; 298289177Speter return with_txnlist_lock(fs, unlock_proto_rev_body, &b, pool); 299289177Speter} 300289177Speter 301289177Speter/* A structure used by get_writable_proto_rev() and 302289177Speter get_writable_proto_rev_body(), which see. */ 303289177Speterstruct get_writable_proto_rev_baton 304289177Speter{ 305289177Speter void **lockcookie; 306289177Speter svn_fs_fs__id_part_t txn_id; 307289177Speter}; 308289177Speter 309289177Speter/* Callback used in the implementation of get_writable_proto_rev(). */ 310289177Speterstatic svn_error_t * 311289177Speterget_writable_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool) 312289177Speter{ 313289177Speter const struct get_writable_proto_rev_baton *b = baton; 314289177Speter void **lockcookie = b->lockcookie; 315289177Speter fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, &b->txn_id, TRUE); 316289177Speter 317289177Speter /* First, ensure that no thread in this process (including this one) 318289177Speter is currently writing to this transaction's proto-rev file. */ 319289177Speter if (txn->being_written) 320289177Speter return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL, 321289177Speter _("Cannot write to the prototype revision file " 322289177Speter "of transaction '%s' because a previous " 323289177Speter "representation is currently being written by " 324289177Speter "this process"), 325289177Speter svn_fs_fs__id_txn_unparse(&b->txn_id, pool)); 326289177Speter 327289177Speter 328289177Speter /* We know that no thread in this process is writing to the proto-rev 329289177Speter file, and by extension, that no thread in this process is holding a 330289177Speter lock on the prototype revision lock file. It is therefore safe 331289177Speter for us to attempt to lock this file, to see if any other process 332289177Speter is holding a lock. */ 333289177Speter 334289177Speter { 335289177Speter apr_file_t *lockfile; 336289177Speter apr_status_t apr_err; 337289177Speter const char *lockfile_path 338289177Speter = svn_fs_fs__path_txn_proto_rev_lock(fs, &b->txn_id, pool); 339289177Speter 340289177Speter /* Open the proto-rev lockfile, creating it if necessary, as it may 341289177Speter not exist if the transaction dates from before the lockfiles were 342289177Speter introduced. 343289177Speter 344289177Speter ### We'd also like to use something like svn_io_file_lock2(), but 345289177Speter that forces us to create a subpool just to be able to unlock 346289177Speter the file, which seems a waste. */ 347289177Speter SVN_ERR(svn_io_file_open(&lockfile, lockfile_path, 348289177Speter APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool)); 349289177Speter 350289177Speter apr_err = apr_file_lock(lockfile, 351289177Speter APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK); 352289177Speter if (apr_err) 353289177Speter { 354289177Speter svn_error_clear(svn_io_file_close(lockfile, pool)); 355289177Speter 356289177Speter if (APR_STATUS_IS_EAGAIN(apr_err)) 357289177Speter return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL, 358289177Speter _("Cannot write to the prototype revision " 359289177Speter "file of transaction '%s' because a " 360289177Speter "previous representation is currently " 361289177Speter "being written by another process"), 362289177Speter svn_fs_fs__id_txn_unparse(&b->txn_id, 363289177Speter pool)); 364289177Speter 365289177Speter return svn_error_wrap_apr(apr_err, 366289177Speter _("Can't get exclusive lock on file '%s'"), 367289177Speter svn_dirent_local_style(lockfile_path, pool)); 368289177Speter } 369289177Speter 370289177Speter *lockcookie = lockfile; 371289177Speter } 372289177Speter 373289177Speter /* We've successfully locked the transaction; mark it as such. */ 374289177Speter txn->being_written = TRUE; 375289177Speter 376289177Speter return SVN_NO_ERROR; 377289177Speter} 378289177Speter 379289177Speter/* Make sure the length ACTUAL_LENGTH of the proto-revision file PROTO_REV 380289177Speter of transaction TXN_ID in filesystem FS matches the proto-index file. 381289177Speter Trim any crash / failure related extra data from the proto-rev file. 382289177Speter 383289177Speter If the prototype revision file is too short, we can't do much but bail out. 384289177Speter 385289177Speter Perform all allocations in POOL. */ 386289177Speterstatic svn_error_t * 387289177Speterauto_truncate_proto_rev(svn_fs_t *fs, 388289177Speter apr_file_t *proto_rev, 389289177Speter apr_off_t actual_length, 390289177Speter const svn_fs_fs__id_part_t *txn_id, 391289177Speter apr_pool_t *pool) 392289177Speter{ 393289177Speter /* Only relevant for newer FSFS formats. */ 394289177Speter if (svn_fs_fs__use_log_addressing(fs)) 395289177Speter { 396289177Speter /* Determine file range covered by the proto-index so far. Note that 397289177Speter we always append to both file, i.e. the last index entry also 398289177Speter corresponds to the last addition in the rev file. */ 399289177Speter const char *path = svn_fs_fs__path_p2l_proto_index(fs, txn_id, pool); 400289177Speter apr_file_t *file; 401289177Speter apr_off_t indexed_length; 402289177Speter 403289177Speter SVN_ERR(svn_fs_fs__p2l_proto_index_open(&file, path, pool)); 404289177Speter SVN_ERR(svn_fs_fs__p2l_proto_index_next_offset(&indexed_length, file, 405289177Speter pool)); 406289177Speter SVN_ERR(svn_io_file_close(file, pool)); 407289177Speter 408289177Speter /* Handle mismatches. */ 409289177Speter if (indexed_length < actual_length) 410289177Speter SVN_ERR(svn_io_file_trunc(proto_rev, indexed_length, pool)); 411289177Speter else if (indexed_length > actual_length) 412289177Speter return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT, 413289177Speter NULL, 414289177Speter _("p2l proto index offset %s beyond proto" 415289177Speter "rev file size %s for TXN %s"), 416289177Speter apr_off_t_toa(pool, indexed_length), 417289177Speter apr_off_t_toa(pool, actual_length), 418289177Speter svn_fs_fs__id_txn_unparse(txn_id, pool)); 419289177Speter } 420289177Speter 421289177Speter return SVN_NO_ERROR; 422289177Speter} 423289177Speter 424289177Speter/* Get a handle to the prototype revision file for transaction TXN_ID in 425289177Speter filesystem FS, and lock it for writing. Return FILE, a file handle 426289177Speter positioned at the end of the file, and LOCKCOOKIE, a cookie that 427289177Speter should be passed to unlock_proto_rev() to unlock the file once FILE 428289177Speter has been closed. 429289177Speter 430289177Speter If the prototype revision file is already locked, return error 431289177Speter SVN_ERR_FS_REP_BEING_WRITTEN. 432289177Speter 433289177Speter Perform all allocations in POOL. */ 434289177Speterstatic svn_error_t * 435289177Speterget_writable_proto_rev(apr_file_t **file, 436289177Speter void **lockcookie, 437289177Speter svn_fs_t *fs, 438289177Speter const svn_fs_fs__id_part_t *txn_id, 439289177Speter apr_pool_t *pool) 440289177Speter{ 441289177Speter struct get_writable_proto_rev_baton b; 442289177Speter svn_error_t *err; 443289177Speter apr_off_t end_offset = 0; 444289177Speter 445289177Speter b.lockcookie = lockcookie; 446289177Speter b.txn_id = *txn_id; 447289177Speter 448289177Speter SVN_ERR(with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool)); 449289177Speter 450289177Speter /* Now open the prototype revision file and seek to the end. */ 451289177Speter err = svn_io_file_open(file, 452289177Speter svn_fs_fs__path_txn_proto_rev(fs, txn_id, pool), 453289177Speter APR_READ | APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT, 454289177Speter pool); 455289177Speter 456289177Speter /* You might expect that we could dispense with the following seek 457289177Speter and achieve the same thing by opening the file using APR_APPEND. 458289177Speter Unfortunately, APR's buffered file implementation unconditionally 459289177Speter places its initial file pointer at the start of the file (even for 460289177Speter files opened with APR_APPEND), so we need this seek to reconcile 461289177Speter the APR file pointer to the OS file pointer (since we need to be 462289177Speter able to read the current file position later). */ 463289177Speter if (!err) 464289177Speter err = svn_io_file_seek(*file, APR_END, &end_offset, pool); 465289177Speter 466289177Speter /* We don't want unused sections (such as leftovers from failed delta 467289177Speter stream) in our file. If we use log addressing, we would need an 468289177Speter index entry for the unused section and that section would need to 469289177Speter be all NUL by convention. So, detect and fix those cases by truncating 470289177Speter the protorev file. */ 471289177Speter if (!err) 472289177Speter err = auto_truncate_proto_rev(fs, *file, end_offset, txn_id, pool); 473289177Speter 474289177Speter if (err) 475289177Speter { 476289177Speter err = svn_error_compose_create( 477289177Speter err, 478289177Speter unlock_proto_rev(fs, txn_id, *lockcookie, pool)); 479289177Speter 480289177Speter *lockcookie = NULL; 481289177Speter } 482289177Speter 483289177Speter return svn_error_trace(err); 484289177Speter} 485289177Speter 486289177Speter/* Callback used in the implementation of purge_shared_txn(). */ 487289177Speterstatic svn_error_t * 488289177Speterpurge_shared_txn_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool) 489289177Speter{ 490289177Speter const svn_fs_fs__id_part_t *txn_id = baton; 491289177Speter 492289177Speter free_shared_txn(fs, txn_id); 493289177Speter svn_fs_fs__reset_txn_caches(fs); 494289177Speter 495289177Speter return SVN_NO_ERROR; 496289177Speter} 497289177Speter 498289177Speter/* Purge the shared data for transaction TXN_ID in filesystem FS. 499289177Speter Perform all allocations in POOL. */ 500289177Speterstatic svn_error_t * 501289177Speterpurge_shared_txn(svn_fs_t *fs, 502289177Speter const svn_fs_fs__id_part_t *txn_id, 503289177Speter apr_pool_t *pool) 504289177Speter{ 505289177Speter return with_txnlist_lock(fs, purge_shared_txn_body, txn_id, pool); 506289177Speter} 507289177Speter 508289177Speter 509289177Spetersvn_error_t * 510289177Spetersvn_fs_fs__put_node_revision(svn_fs_t *fs, 511289177Speter const svn_fs_id_t *id, 512289177Speter node_revision_t *noderev, 513289177Speter svn_boolean_t fresh_txn_root, 514289177Speter apr_pool_t *pool) 515289177Speter{ 516289177Speter fs_fs_data_t *ffd = fs->fsap_data; 517289177Speter apr_file_t *noderev_file; 518289177Speter 519289177Speter noderev->is_fresh_txn_root = fresh_txn_root; 520289177Speter 521289177Speter if (! svn_fs_fs__id_is_txn(id)) 522289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 523289177Speter _("Attempted to write to non-transaction '%s'"), 524289177Speter svn_fs_fs__id_unparse(id, pool)->data); 525289177Speter 526289177Speter SVN_ERR(svn_io_file_open(&noderev_file, 527289177Speter svn_fs_fs__path_txn_node_rev(fs, id, pool), 528289177Speter APR_WRITE | APR_CREATE | APR_TRUNCATE 529289177Speter | APR_BUFFERED, APR_OS_DEFAULT, pool)); 530289177Speter 531289177Speter SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE, 532289177Speter pool), 533289177Speter noderev, ffd->format, 534289177Speter svn_fs_fs__fs_supports_mergeinfo(fs), 535289177Speter pool)); 536289177Speter 537289177Speter SVN_ERR(svn_io_file_close(noderev_file, pool)); 538289177Speter 539289177Speter return SVN_NO_ERROR; 540289177Speter} 541289177Speter 542289177Speter/* For the in-transaction NODEREV within FS, write the sha1->rep mapping 543289177Speter * file in the respective transaction, if rep sharing has been enabled etc. 544289177Speter * Use SCATCH_POOL for temporary allocations. 545289177Speter */ 546289177Speterstatic svn_error_t * 547289177Speterstore_sha1_rep_mapping(svn_fs_t *fs, 548289177Speter node_revision_t *noderev, 549289177Speter apr_pool_t *scratch_pool) 550289177Speter{ 551289177Speter fs_fs_data_t *ffd = fs->fsap_data; 552289177Speter 553289177Speter /* if rep sharing has been enabled and the noderev has a data rep and 554289177Speter * its SHA-1 is known, store the rep struct under its SHA1. */ 555289177Speter if ( ffd->rep_sharing_allowed 556289177Speter && noderev->data_rep 557289177Speter && noderev->data_rep->has_sha1) 558289177Speter { 559289177Speter apr_file_t *rep_file; 560289177Speter const char *file_name = path_txn_sha1(fs, 561289177Speter &noderev->data_rep->txn_id, 562289177Speter noderev->data_rep->sha1_digest, 563289177Speter scratch_pool); 564289177Speter svn_stringbuf_t *rep_string 565289177Speter = svn_fs_fs__unparse_representation(noderev->data_rep, 566289177Speter ffd->format, 567289177Speter (noderev->kind == svn_node_dir), 568289177Speter scratch_pool, scratch_pool); 569289177Speter SVN_ERR(svn_io_file_open(&rep_file, file_name, 570289177Speter APR_WRITE | APR_CREATE | APR_TRUNCATE 571289177Speter | APR_BUFFERED, APR_OS_DEFAULT, scratch_pool)); 572289177Speter 573289177Speter SVN_ERR(svn_io_file_write_full(rep_file, rep_string->data, 574289177Speter rep_string->len, NULL, scratch_pool)); 575289177Speter 576289177Speter SVN_ERR(svn_io_file_close(rep_file, scratch_pool)); 577289177Speter } 578289177Speter 579289177Speter return SVN_NO_ERROR; 580289177Speter} 581289177Speter 582289177Speterstatic svn_error_t * 583289177Speterunparse_dir_entry(svn_fs_dirent_t *dirent, 584289177Speter svn_stream_t *stream, 585289177Speter apr_pool_t *pool) 586289177Speter{ 587289177Speter const char *val 588289177Speter = apr_psprintf(pool, "%s %s", 589289177Speter (dirent->kind == svn_node_file) ? SVN_FS_FS__KIND_FILE 590289177Speter : SVN_FS_FS__KIND_DIR, 591289177Speter svn_fs_fs__id_unparse(dirent->id, pool)->data); 592289177Speter 593289177Speter SVN_ERR(svn_stream_printf(stream, pool, "K %" APR_SIZE_T_FMT "\n%s\n" 594289177Speter "V %" APR_SIZE_T_FMT "\n%s\n", 595289177Speter strlen(dirent->name), dirent->name, 596289177Speter strlen(val), val)); 597289177Speter return SVN_NO_ERROR; 598289177Speter} 599289177Speter 600289177Speter/* Write the directory given as array of dirent structs in ENTRIES to STREAM. 601289177Speter Perform temporary allocations in POOL. */ 602289177Speterstatic svn_error_t * 603289177Speterunparse_dir_entries(apr_array_header_t *entries, 604289177Speter svn_stream_t *stream, 605289177Speter apr_pool_t *pool) 606289177Speter{ 607289177Speter apr_pool_t *iterpool = svn_pool_create(pool); 608289177Speter int i; 609289177Speter for (i = 0; i < entries->nelts; ++i) 610289177Speter { 611289177Speter svn_fs_dirent_t *dirent; 612289177Speter 613289177Speter svn_pool_clear(iterpool); 614289177Speter dirent = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *); 615289177Speter SVN_ERR(unparse_dir_entry(dirent, stream, iterpool)); 616289177Speter } 617289177Speter 618289177Speter SVN_ERR(svn_stream_printf(stream, pool, "%s\n", SVN_HASH_TERMINATOR)); 619289177Speter 620289177Speter svn_pool_destroy(iterpool); 621289177Speter return SVN_NO_ERROR; 622289177Speter} 623289177Speter 624289177Speter/* Return a deep copy of SOURCE and allocate it in RESULT_POOL. 625289177Speter */ 626289177Speterstatic svn_fs_path_change2_t * 627289177Speterpath_change_dup(const svn_fs_path_change2_t *source, 628289177Speter apr_pool_t *result_pool) 629289177Speter{ 630289177Speter svn_fs_path_change2_t *result = apr_pmemdup(result_pool, source, 631289177Speter sizeof(*source)); 632289177Speter result->node_rev_id = svn_fs_fs__id_copy(source->node_rev_id, result_pool); 633289177Speter if (source->copyfrom_path) 634289177Speter result->copyfrom_path = apr_pstrdup(result_pool, source->copyfrom_path); 635289177Speter 636289177Speter return result; 637289177Speter} 638289177Speter 639289177Speter/* Merge the internal-use-only CHANGE into a hash of public-FS 640289177Speter svn_fs_path_change2_t CHANGED_PATHS, collapsing multiple changes into a 641289177Speter single summarical (is that real word?) change per path. DELETIONS is 642289177Speter also a path->svn_fs_path_change2_t hash and contains all the deletions 643289177Speter that got turned into a replacement. */ 644289177Speterstatic svn_error_t * 645289177Speterfold_change(apr_hash_t *changed_paths, 646289177Speter apr_hash_t *deletions, 647289177Speter const change_t *change) 648289177Speter{ 649289177Speter apr_pool_t *pool = apr_hash_pool_get(changed_paths); 650289177Speter svn_fs_path_change2_t *old_change, *new_change; 651289177Speter const svn_string_t *path = &change->path; 652289177Speter const svn_fs_path_change2_t *info = &change->info; 653289177Speter 654289177Speter if ((old_change = apr_hash_get(changed_paths, path->data, path->len))) 655289177Speter { 656289177Speter /* This path already exists in the hash, so we have to merge 657289177Speter this change into the already existing one. */ 658289177Speter 659289177Speter /* Sanity check: only allow NULL node revision ID in the 660289177Speter `reset' case. */ 661289177Speter if ((! info->node_rev_id) 662289177Speter && (info->change_kind != svn_fs_path_change_reset)) 663289177Speter return svn_error_create 664289177Speter (SVN_ERR_FS_CORRUPT, NULL, 665289177Speter _("Missing required node revision ID")); 666289177Speter 667289177Speter /* Sanity check: we should be talking about the same node 668289177Speter revision ID as our last change except where the last change 669289177Speter was a deletion. */ 670289177Speter if (info->node_rev_id 671289177Speter && (! svn_fs_fs__id_eq(old_change->node_rev_id, info->node_rev_id)) 672289177Speter && (old_change->change_kind != svn_fs_path_change_delete)) 673289177Speter return svn_error_create 674289177Speter (SVN_ERR_FS_CORRUPT, NULL, 675289177Speter _("Invalid change ordering: new node revision ID " 676289177Speter "without delete")); 677289177Speter 678289177Speter /* Sanity check: an add, replacement, or reset must be the first 679289177Speter thing to follow a deletion. */ 680289177Speter if ((old_change->change_kind == svn_fs_path_change_delete) 681289177Speter && (! ((info->change_kind == svn_fs_path_change_replace) 682289177Speter || (info->change_kind == svn_fs_path_change_reset) 683289177Speter || (info->change_kind == svn_fs_path_change_add)))) 684289177Speter return svn_error_create 685289177Speter (SVN_ERR_FS_CORRUPT, NULL, 686289177Speter _("Invalid change ordering: non-add change on deleted path")); 687289177Speter 688289177Speter /* Sanity check: an add can't follow anything except 689289177Speter a delete or reset. */ 690289177Speter if ((info->change_kind == svn_fs_path_change_add) 691289177Speter && (old_change->change_kind != svn_fs_path_change_delete) 692289177Speter && (old_change->change_kind != svn_fs_path_change_reset)) 693289177Speter return svn_error_create 694289177Speter (SVN_ERR_FS_CORRUPT, NULL, 695289177Speter _("Invalid change ordering: add change on preexisting path")); 696289177Speter 697289177Speter /* Now, merge that change in. */ 698289177Speter switch (info->change_kind) 699289177Speter { 700289177Speter case svn_fs_path_change_reset: 701289177Speter /* A reset here will simply remove the path change from the 702289177Speter hash. */ 703289177Speter apr_hash_set(changed_paths, path->data, path->len, NULL); 704289177Speter break; 705289177Speter 706289177Speter case svn_fs_path_change_delete: 707289177Speter if (old_change->change_kind == svn_fs_path_change_add) 708289177Speter { 709289177Speter /* If the path was introduced in this transaction via an 710289177Speter add, and we are deleting it, just remove the path 711289177Speter altogether. (The caller will delete any child paths.) */ 712289177Speter apr_hash_set(changed_paths, path->data, path->len, NULL); 713289177Speter } 714289177Speter else if (old_change->change_kind == svn_fs_path_change_replace) 715289177Speter { 716289177Speter /* A deleting a 'replace' restore the original deletion. */ 717289177Speter new_change = apr_hash_get(deletions, path->data, path->len); 718289177Speter SVN_ERR_ASSERT(new_change); 719289177Speter apr_hash_set(changed_paths, path->data, path->len, new_change); 720289177Speter } 721289177Speter else 722289177Speter { 723289177Speter /* A deletion overrules a previous change (modify). */ 724289177Speter new_change = path_change_dup(info, pool); 725289177Speter apr_hash_set(changed_paths, path->data, path->len, new_change); 726289177Speter } 727289177Speter break; 728289177Speter 729289177Speter case svn_fs_path_change_add: 730289177Speter case svn_fs_path_change_replace: 731289177Speter /* An add at this point must be following a previous delete, 732289177Speter so treat it just like a replace. Remember the original 733289177Speter deletion such that we are able to delete this path again 734289177Speter (the replacement may have changed node kind and id). */ 735289177Speter new_change = path_change_dup(info, pool); 736289177Speter new_change->change_kind = svn_fs_path_change_replace; 737289177Speter 738289177Speter apr_hash_set(changed_paths, path->data, path->len, new_change); 739289177Speter 740289177Speter /* Remember the original change. 741289177Speter * Make sure to allocate the hash key in a durable pool. */ 742289177Speter apr_hash_set(deletions, 743289177Speter apr_pstrmemdup(apr_hash_pool_get(deletions), 744289177Speter path->data, path->len), 745289177Speter path->len, old_change); 746289177Speter break; 747289177Speter 748289177Speter case svn_fs_path_change_modify: 749289177Speter default: 750289177Speter /* If the new change modifies some attribute of the node, set 751289177Speter the corresponding flag, whether it already was set or not. 752289177Speter Note: We do not reset a flag to FALSE if a change is undone. */ 753289177Speter if (info->text_mod) 754289177Speter old_change->text_mod = TRUE; 755289177Speter if (info->prop_mod) 756289177Speter old_change->prop_mod = TRUE; 757289177Speter if (info->mergeinfo_mod == svn_tristate_true) 758289177Speter old_change->mergeinfo_mod = svn_tristate_true; 759289177Speter break; 760289177Speter } 761289177Speter } 762289177Speter else 763289177Speter { 764289177Speter /* Add this path. The API makes no guarantees that this (new) key 765289177Speter will not be retained. Thus, we copy the key into the target pool 766289177Speter to ensure a proper lifetime. */ 767289177Speter apr_hash_set(changed_paths, 768289177Speter apr_pstrmemdup(pool, path->data, path->len), path->len, 769289177Speter path_change_dup(info, pool)); 770289177Speter } 771289177Speter 772289177Speter return SVN_NO_ERROR; 773289177Speter} 774289177Speter 775289177Speter/* Baton type to be used with process_changes(). */ 776289177Spetertypedef struct process_changes_baton_t 777289177Speter{ 778289177Speter /* Folded list of path changes. */ 779289177Speter apr_hash_t *changed_paths; 780289177Speter 781289177Speter /* Path changes that are deletions and have been turned into 782289177Speter replacements. If those replacements get deleted again, this 783289177Speter container contains the record that we have to revert to. */ 784289177Speter apr_hash_t *deletions; 785289177Speter} process_changes_baton_t; 786289177Speter 787289177Speter/* An implementation of svn_fs_fs__change_receiver_t. 788289177Speter Examine all the changed path entries in CHANGES and store them in 789289177Speter *CHANGED_PATHS. Folding is done to remove redundant or unnecessary 790289177Speter data. Do all allocations in POOL. */ 791289177Speterstatic svn_error_t * 792289177Speterprocess_changes(void *baton_p, 793289177Speter change_t *change, 794289177Speter apr_pool_t *scratch_pool) 795289177Speter{ 796289177Speter process_changes_baton_t *baton = baton_p; 797289177Speter 798289177Speter SVN_ERR(fold_change(baton->changed_paths, baton->deletions, change)); 799289177Speter 800289177Speter /* Now, if our change was a deletion or replacement, we have to 801289177Speter blow away any changes thus far on paths that are (or, were) 802289177Speter children of this path. 803289177Speter ### i won't bother with another iteration pool here -- at 804289177Speter most we talking about a few extra dups of paths into what 805289177Speter is already a temporary subpool. 806289177Speter */ 807289177Speter 808289177Speter if ((change->info.change_kind == svn_fs_path_change_delete) 809289177Speter || (change->info.change_kind == svn_fs_path_change_replace)) 810289177Speter { 811289177Speter apr_hash_index_t *hi; 812289177Speter 813289177Speter /* a potential child path must contain at least 2 more chars 814289177Speter (the path separator plus at least one char for the name). 815289177Speter Also, we should not assume that all paths have been normalized 816289177Speter i.e. some might have trailing path separators. 817289177Speter */ 818289177Speter apr_ssize_t path_len = change->path.len; 819289177Speter apr_ssize_t min_child_len = path_len == 0 820289177Speter ? 1 821289177Speter : change->path.data[path_len-1] == '/' 822289177Speter ? path_len + 1 823289177Speter : path_len + 2; 824289177Speter 825289177Speter /* CAUTION: This is the inner loop of an O(n^2) algorithm. 826289177Speter The number of changes to process may be >> 1000. 827289177Speter Therefore, keep the inner loop as tight as possible. 828289177Speter */ 829289177Speter for (hi = apr_hash_first(scratch_pool, baton->changed_paths); 830289177Speter hi; 831289177Speter hi = apr_hash_next(hi)) 832289177Speter { 833289177Speter /* KEY is the path. */ 834289177Speter const void *path; 835289177Speter apr_ssize_t klen; 836289177Speter svn_fs_path_change2_t *old_change; 837289177Speter apr_hash_this(hi, &path, &klen, (void**)&old_change); 838289177Speter 839289177Speter /* If we come across a child of our path, remove it. 840289177Speter Call svn_fspath__skip_ancestor only if there is a chance that 841289177Speter this is actually a sub-path. 842289177Speter */ 843289177Speter if (klen >= min_child_len) 844289177Speter { 845289177Speter const char *child; 846289177Speter 847289177Speter child = svn_fspath__skip_ancestor(change->path.data, path); 848289177Speter if (child && child[0] != '\0') 849289177Speter { 850289177Speter apr_hash_set(baton->changed_paths, path, klen, NULL); 851289177Speter } 852289177Speter } 853289177Speter } 854289177Speter } 855289177Speter 856289177Speter return SVN_NO_ERROR; 857289177Speter} 858289177Speter 859289177Spetersvn_error_t * 860289177Spetersvn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p, 861289177Speter svn_fs_t *fs, 862289177Speter const svn_fs_fs__id_part_t *txn_id, 863289177Speter apr_pool_t *pool) 864289177Speter{ 865289177Speter apr_file_t *file; 866289177Speter apr_hash_t *changed_paths = apr_hash_make(pool); 867289177Speter apr_pool_t *scratch_pool = svn_pool_create(pool); 868289177Speter process_changes_baton_t baton; 869289177Speter 870289177Speter baton.changed_paths = changed_paths; 871289177Speter baton.deletions = apr_hash_make(scratch_pool); 872289177Speter 873289177Speter SVN_ERR(svn_io_file_open(&file, 874289177Speter path_txn_changes(fs, txn_id, scratch_pool), 875289177Speter APR_READ | APR_BUFFERED, APR_OS_DEFAULT, 876289177Speter scratch_pool)); 877289177Speter 878289177Speter SVN_ERR(svn_fs_fs__read_changes_incrementally( 879289177Speter svn_stream_from_aprfile2(file, TRUE, 880289177Speter scratch_pool), 881289177Speter process_changes, &baton, 882289177Speter scratch_pool)); 883289177Speter svn_pool_destroy(scratch_pool); 884289177Speter 885289177Speter *changed_paths_p = changed_paths; 886289177Speter 887289177Speter return SVN_NO_ERROR; 888289177Speter} 889289177Speter 890289177Speter 891289177Spetersvn_error_t * 892289177Spetersvn_fs_fs__paths_changed(apr_hash_t **changed_paths_p, 893289177Speter svn_fs_t *fs, 894289177Speter svn_revnum_t rev, 895289177Speter apr_pool_t *pool) 896289177Speter{ 897289177Speter apr_hash_t *changed_paths; 898289177Speter apr_array_header_t *changes; 899289177Speter int i; 900289177Speter 901289177Speter SVN_ERR(svn_fs_fs__get_changes(&changes, fs, rev, pool)); 902289177Speter 903289177Speter changed_paths = svn_hash__make(pool); 904289177Speter for (i = 0; i < changes->nelts; ++i) 905289177Speter { 906289177Speter change_t *change = APR_ARRAY_IDX(changes, i, change_t *); 907289177Speter apr_hash_set(changed_paths, change->path.data, change->path.len, 908289177Speter &change->info); 909289177Speter } 910289177Speter 911289177Speter *changed_paths_p = changed_paths; 912289177Speter 913289177Speter return SVN_NO_ERROR; 914289177Speter} 915289177Speter 916289177Speter/* Copy a revision node-rev SRC into the current transaction TXN_ID in 917289177Speter the filesystem FS. This is only used to create the root of a transaction. 918289177Speter Allocations are from POOL. */ 919289177Speterstatic svn_error_t * 920289177Spetercreate_new_txn_noderev_from_rev(svn_fs_t *fs, 921289177Speter const svn_fs_fs__id_part_t *txn_id, 922289177Speter svn_fs_id_t *src, 923289177Speter apr_pool_t *pool) 924289177Speter{ 925289177Speter node_revision_t *noderev; 926289177Speter const svn_fs_fs__id_part_t *node_id, *copy_id; 927289177Speter 928289177Speter SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool, pool)); 929289177Speter 930289177Speter if (svn_fs_fs__id_is_txn(noderev->id)) 931289177Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 932289177Speter _("Copying from transactions not allowed")); 933289177Speter 934289177Speter noderev->predecessor_id = noderev->id; 935289177Speter noderev->predecessor_count++; 936289177Speter noderev->copyfrom_path = NULL; 937289177Speter noderev->copyfrom_rev = SVN_INVALID_REVNUM; 938289177Speter 939289177Speter /* For the transaction root, the copyroot never changes. */ 940289177Speter 941289177Speter node_id = svn_fs_fs__id_node_id(noderev->id); 942289177Speter copy_id = svn_fs_fs__id_copy_id(noderev->id); 943289177Speter noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool); 944289177Speter 945289177Speter return svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool); 946289177Speter} 947289177Speter 948289177Speter/* A structure used by get_and_increment_txn_key_body(). */ 949289177Speterstruct get_and_increment_txn_key_baton { 950289177Speter svn_fs_t *fs; 951289177Speter apr_uint64_t txn_number; 952289177Speter apr_pool_t *pool; 953289177Speter}; 954289177Speter 955289177Speter/* Callback used in the implementation of create_txn_dir(). This gets 956289177Speter the current base 36 value in PATH_TXN_CURRENT and increments it. 957289177Speter It returns the original value by the baton. */ 958289177Speterstatic svn_error_t * 959289177Speterget_and_increment_txn_key_body(void *baton, apr_pool_t *pool) 960289177Speter{ 961289177Speter struct get_and_increment_txn_key_baton *cb = baton; 962289177Speter const char *txn_current_filename 963289177Speter = svn_fs_fs__path_txn_current(cb->fs, pool); 964289177Speter char new_id_str[SVN_INT64_BUFFER_SIZE + 1]; /* add space for a newline */ 965289177Speter apr_size_t line_length; 966289177Speter 967289177Speter svn_stringbuf_t *buf; 968289177Speter SVN_ERR(svn_fs_fs__read_content(&buf, txn_current_filename, cb->pool)); 969289177Speter 970289177Speter /* assign the current txn counter value to our result */ 971289177Speter cb->txn_number = svn__base36toui64(NULL, buf->data); 972289177Speter 973289177Speter /* remove trailing newlines */ 974289177Speter line_length = svn__ui64tobase36(new_id_str, cb->txn_number+1); 975289177Speter new_id_str[line_length] = '\n'; 976289177Speter 977289177Speter /* Increment the key and add a trailing \n to the string so the 978289177Speter txn-current file has a newline in it. */ 979289177Speter SVN_ERR(svn_io_write_atomic(txn_current_filename, new_id_str, 980289177Speter line_length + 1, 981289177Speter txn_current_filename /* copy_perms path */, 982289177Speter pool)); 983289177Speter 984289177Speter return SVN_NO_ERROR; 985289177Speter} 986289177Speter 987289177Speter/* Create a unique directory for a transaction in FS based on revision REV. 988289177Speter Return the ID for this transaction in *ID_P and *TXN_ID. Use a sequence 989289177Speter value in the transaction ID to prevent reuse of transaction IDs. */ 990289177Speterstatic svn_error_t * 991289177Spetercreate_txn_dir(const char **id_p, 992289177Speter svn_fs_fs__id_part_t *txn_id, 993289177Speter svn_fs_t *fs, 994289177Speter svn_revnum_t rev, 995289177Speter apr_pool_t *pool) 996289177Speter{ 997289177Speter struct get_and_increment_txn_key_baton cb; 998289177Speter const char *txn_dir; 999289177Speter 1000289177Speter /* Get the current transaction sequence value, which is a base-36 1001289177Speter number, from the txn-current file, and write an 1002289177Speter incremented value back out to the file. Place the revision 1003289177Speter number the transaction is based off into the transaction id. */ 1004289177Speter cb.pool = pool; 1005289177Speter cb.fs = fs; 1006289177Speter SVN_ERR(svn_fs_fs__with_txn_current_lock(fs, 1007289177Speter get_and_increment_txn_key_body, 1008289177Speter &cb, 1009289177Speter pool)); 1010289177Speter txn_id->revision = rev; 1011289177Speter txn_id->number = cb.txn_number; 1012289177Speter 1013289177Speter *id_p = svn_fs_fs__id_txn_unparse(txn_id, pool); 1014289177Speter txn_dir = svn_fs_fs__path_txn_dir(fs, txn_id, pool); 1015289177Speter 1016289177Speter return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool); 1017289177Speter} 1018289177Speter 1019289177Speter/* Create a unique directory for a transaction in FS based on revision 1020289177Speter REV. Return the ID for this transaction in *ID_P and *TXN_ID. This 1021289177Speter implementation is used in svn 1.4 and earlier repositories and is 1022289177Speter kept in 1.5 and greater to support the --pre-1.4-compatible and 1023289177Speter --pre-1.5-compatible repository creation options. Reused 1024289177Speter transaction IDs are possible with this implementation. */ 1025289177Speterstatic svn_error_t * 1026289177Spetercreate_txn_dir_pre_1_5(const char **id_p, 1027289177Speter svn_fs_fs__id_part_t *txn_id, 1028289177Speter svn_fs_t *fs, 1029289177Speter svn_revnum_t rev, 1030289177Speter apr_pool_t *pool) 1031289177Speter{ 1032289177Speter unsigned int i; 1033289177Speter apr_pool_t *subpool; 1034289177Speter const char *unique_path, *prefix; 1035289177Speter 1036289177Speter /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */ 1037289177Speter prefix = svn_dirent_join(svn_fs_fs__path_txns_dir(fs, pool), 1038289177Speter apr_psprintf(pool, "%ld", rev), pool); 1039289177Speter 1040289177Speter subpool = svn_pool_create(pool); 1041289177Speter for (i = 1; i <= 99999; i++) 1042289177Speter { 1043289177Speter svn_error_t *err; 1044289177Speter 1045289177Speter svn_pool_clear(subpool); 1046289177Speter unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i); 1047289177Speter err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool); 1048289177Speter if (! err) 1049289177Speter { 1050289177Speter /* We succeeded. Return the basename minus the ".txn" extension. */ 1051289177Speter const char *name = svn_dirent_basename(unique_path, subpool); 1052289177Speter *id_p = apr_pstrndup(pool, name, 1053289177Speter strlen(name) - strlen(PATH_EXT_TXN)); 1054289177Speter SVN_ERR(svn_fs_fs__id_txn_parse(txn_id, *id_p)); 1055289177Speter svn_pool_destroy(subpool); 1056289177Speter return SVN_NO_ERROR; 1057289177Speter } 1058289177Speter if (! APR_STATUS_IS_EEXIST(err->apr_err)) 1059289177Speter return svn_error_trace(err); 1060289177Speter svn_error_clear(err); 1061289177Speter } 1062289177Speter 1063289177Speter return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED, 1064289177Speter NULL, 1065289177Speter _("Unable to create transaction directory " 1066289177Speter "in '%s' for revision %ld"), 1067289177Speter svn_dirent_local_style(fs->path, pool), 1068289177Speter rev); 1069289177Speter} 1070289177Speter 1071289177Spetersvn_error_t * 1072289177Spetersvn_fs_fs__create_txn(svn_fs_txn_t **txn_p, 1073289177Speter svn_fs_t *fs, 1074289177Speter svn_revnum_t rev, 1075289177Speter apr_pool_t *pool) 1076289177Speter{ 1077289177Speter fs_fs_data_t *ffd = fs->fsap_data; 1078289177Speter svn_fs_txn_t *txn; 1079289177Speter fs_txn_data_t *ftd; 1080289177Speter svn_fs_id_t *root_id; 1081289177Speter 1082289177Speter txn = apr_pcalloc(pool, sizeof(*txn)); 1083289177Speter ftd = apr_pcalloc(pool, sizeof(*ftd)); 1084289177Speter 1085289177Speter /* Get the txn_id. */ 1086289177Speter if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 1087289177Speter SVN_ERR(create_txn_dir(&txn->id, &ftd->txn_id, fs, rev, pool)); 1088289177Speter else 1089289177Speter SVN_ERR(create_txn_dir_pre_1_5(&txn->id, &ftd->txn_id, fs, rev, pool)); 1090289177Speter 1091289177Speter txn->fs = fs; 1092289177Speter txn->base_rev = rev; 1093289177Speter 1094289177Speter txn->vtable = &txn_vtable; 1095289177Speter txn->fsap_data = ftd; 1096289177Speter *txn_p = txn; 1097289177Speter 1098289177Speter /* Create a new root node for this transaction. */ 1099289177Speter SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool, pool)); 1100289177Speter SVN_ERR(create_new_txn_noderev_from_rev(fs, &ftd->txn_id, root_id, pool)); 1101289177Speter 1102289177Speter /* Create an empty rev file. */ 1103289177Speter SVN_ERR(svn_io_file_create_empty( 1104289177Speter svn_fs_fs__path_txn_proto_rev(fs, &ftd->txn_id, pool), 1105289177Speter pool)); 1106289177Speter 1107289177Speter /* Create an empty rev-lock file. */ 1108289177Speter SVN_ERR(svn_io_file_create_empty( 1109289177Speter svn_fs_fs__path_txn_proto_rev_lock(fs, &ftd->txn_id, pool), 1110289177Speter pool)); 1111289177Speter 1112289177Speter /* Create an empty changes file. */ 1113289177Speter SVN_ERR(svn_io_file_create_empty(path_txn_changes(fs, &ftd->txn_id, pool), 1114289177Speter pool)); 1115289177Speter 1116289177Speter /* Create the next-ids file. */ 1117289177Speter return svn_io_file_create(path_txn_next_ids(fs, &ftd->txn_id, pool), 1118289177Speter "0 0\n", pool); 1119289177Speter} 1120289177Speter 1121289177Speter/* Store the property list for transaction TXN_ID in PROPLIST. 1122289177Speter Perform temporary allocations in POOL. */ 1123289177Speterstatic svn_error_t * 1124289177Speterget_txn_proplist(apr_hash_t *proplist, 1125289177Speter svn_fs_t *fs, 1126289177Speter const svn_fs_fs__id_part_t *txn_id, 1127289177Speter apr_pool_t *pool) 1128289177Speter{ 1129289177Speter svn_stream_t *stream; 1130289177Speter svn_error_t *err; 1131289177Speter 1132289177Speter /* Check for issue #3696. (When we find and fix the cause, we can change 1133289177Speter * this to an assertion.) */ 1134289177Speter if (!txn_id || !svn_fs_fs__id_txn_used(txn_id)) 1135289177Speter return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 1136289177Speter _("Internal error: a null transaction id was " 1137289177Speter "passed to get_txn_proplist()")); 1138289177Speter 1139289177Speter /* Open the transaction properties file. */ 1140289177Speter SVN_ERR(svn_stream_open_readonly(&stream, path_txn_props(fs, txn_id, pool), 1141289177Speter pool, pool)); 1142289177Speter 1143289177Speter /* Read in the property list. */ 1144289177Speter err = svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool); 1145289177Speter if (err) 1146289177Speter { 1147289177Speter svn_error_clear(svn_stream_close(stream)); 1148289177Speter return svn_error_quick_wrapf(err, 1149289177Speter _("malformed property list in transaction '%s'"), 1150289177Speter path_txn_props(fs, txn_id, pool)); 1151289177Speter } 1152289177Speter 1153289177Speter return svn_stream_close(stream); 1154289177Speter} 1155289177Speter 1156289177Speter/* Save the property list PROPS as the revprops for transaction TXN_ID 1157289177Speter in FS. Perform temporary allocations in POOL. */ 1158289177Speterstatic svn_error_t * 1159289177Speterset_txn_proplist(svn_fs_t *fs, 1160289177Speter const svn_fs_fs__id_part_t *txn_id, 1161289177Speter apr_hash_t *props, 1162289177Speter svn_boolean_t final, 1163289177Speter apr_pool_t *pool) 1164289177Speter{ 1165289177Speter svn_stringbuf_t *buf; 1166289177Speter svn_stream_t *stream; 1167289177Speter 1168289177Speter /* Write out the new file contents to BUF. */ 1169289177Speter buf = svn_stringbuf_create_ensure(1024, pool); 1170289177Speter stream = svn_stream_from_stringbuf(buf, pool); 1171289177Speter SVN_ERR(svn_hash_write2(props, stream, SVN_HASH_TERMINATOR, pool)); 1172289177Speter SVN_ERR(svn_stream_close(stream)); 1173289177Speter 1174289177Speter /* Open the transaction properties file and write new contents to it. */ 1175289177Speter SVN_ERR(svn_io_write_atomic((final 1176289177Speter ? path_txn_props_final(fs, txn_id, pool) 1177289177Speter : path_txn_props(fs, txn_id, pool)), 1178289177Speter buf->data, buf->len, 1179289177Speter NULL /* copy_perms_path */, pool)); 1180289177Speter return SVN_NO_ERROR; 1181289177Speter} 1182289177Speter 1183289177Speter 1184289177Spetersvn_error_t * 1185289177Spetersvn_fs_fs__change_txn_prop(svn_fs_txn_t *txn, 1186289177Speter const char *name, 1187289177Speter const svn_string_t *value, 1188289177Speter apr_pool_t *pool) 1189289177Speter{ 1190289177Speter apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t)); 1191289177Speter svn_prop_t prop; 1192289177Speter 1193289177Speter prop.name = name; 1194289177Speter prop.value = value; 1195289177Speter APR_ARRAY_PUSH(props, svn_prop_t) = prop; 1196289177Speter 1197289177Speter return svn_fs_fs__change_txn_props(txn, props, pool); 1198289177Speter} 1199289177Speter 1200289177Spetersvn_error_t * 1201289177Spetersvn_fs_fs__change_txn_props(svn_fs_txn_t *txn, 1202289177Speter const apr_array_header_t *props, 1203289177Speter apr_pool_t *pool) 1204289177Speter{ 1205289177Speter fs_txn_data_t *ftd = txn->fsap_data; 1206289177Speter apr_hash_t *txn_prop = apr_hash_make(pool); 1207289177Speter int i; 1208289177Speter svn_error_t *err; 1209289177Speter 1210289177Speter err = get_txn_proplist(txn_prop, txn->fs, &ftd->txn_id, pool); 1211289177Speter /* Here - and here only - we need to deal with the possibility that the 1212289177Speter transaction property file doesn't yet exist. The rest of the 1213289177Speter implementation assumes that the file exists, but we're called to set the 1214289177Speter initial transaction properties as the transaction is being created. */ 1215289177Speter if (err && (APR_STATUS_IS_ENOENT(err->apr_err))) 1216289177Speter svn_error_clear(err); 1217289177Speter else if (err) 1218289177Speter return svn_error_trace(err); 1219289177Speter 1220289177Speter for (i = 0; i < props->nelts; i++) 1221289177Speter { 1222289177Speter svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t); 1223289177Speter 1224289177Speter if (svn_hash_gets(txn_prop, SVN_FS__PROP_TXN_CLIENT_DATE) 1225289177Speter && !strcmp(prop->name, SVN_PROP_REVISION_DATE)) 1226289177Speter svn_hash_sets(txn_prop, SVN_FS__PROP_TXN_CLIENT_DATE, 1227289177Speter svn_string_create("1", pool)); 1228289177Speter 1229289177Speter svn_hash_sets(txn_prop, prop->name, prop->value); 1230289177Speter } 1231289177Speter 1232289177Speter /* Create a new version of the file and write out the new props. */ 1233289177Speter /* Open the transaction properties file. */ 1234289177Speter SVN_ERR(set_txn_proplist(txn->fs, &ftd->txn_id, txn_prop, FALSE, pool)); 1235289177Speter 1236289177Speter return SVN_NO_ERROR; 1237289177Speter} 1238289177Speter 1239289177Spetersvn_error_t * 1240289177Spetersvn_fs_fs__get_txn(transaction_t **txn_p, 1241289177Speter svn_fs_t *fs, 1242289177Speter const svn_fs_fs__id_part_t *txn_id, 1243289177Speter apr_pool_t *pool) 1244289177Speter{ 1245289177Speter transaction_t *txn; 1246289177Speter node_revision_t *noderev; 1247289177Speter svn_fs_id_t *root_id; 1248289177Speter 1249289177Speter txn = apr_pcalloc(pool, sizeof(*txn)); 1250289177Speter root_id = svn_fs_fs__id_txn_create_root(txn_id, pool); 1251289177Speter 1252289177Speter SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool, pool)); 1253289177Speter 1254289177Speter txn->root_id = svn_fs_fs__id_copy(noderev->id, pool); 1255289177Speter txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool); 1256289177Speter txn->copies = NULL; 1257289177Speter 1258289177Speter *txn_p = txn; 1259289177Speter 1260289177Speter return SVN_NO_ERROR; 1261289177Speter} 1262289177Speter 1263289177Speter/* Write out the currently available next node_id NODE_ID and copy_id 1264289177Speter COPY_ID for transaction TXN_ID in filesystem FS. The next node-id is 1265289177Speter used both for creating new unique nodes for the given transaction, as 1266289177Speter well as uniquifying representations. Perform temporary allocations in 1267289177Speter POOL. */ 1268289177Speterstatic svn_error_t * 1269289177Speterwrite_next_ids(svn_fs_t *fs, 1270289177Speter const svn_fs_fs__id_part_t *txn_id, 1271289177Speter apr_uint64_t node_id, 1272289177Speter apr_uint64_t copy_id, 1273289177Speter apr_pool_t *pool) 1274289177Speter{ 1275289177Speter apr_file_t *file; 1276289177Speter char buffer[2 * SVN_INT64_BUFFER_SIZE + 2]; 1277289177Speter char *p = buffer; 1278289177Speter 1279289177Speter p += svn__ui64tobase36(p, node_id); 1280289177Speter *(p++) = ' '; 1281289177Speter p += svn__ui64tobase36(p, copy_id); 1282289177Speter *(p++) = '\n'; 1283289177Speter *(p++) = '\0'; 1284289177Speter 1285289177Speter SVN_ERR(svn_io_file_open(&file, 1286289177Speter path_txn_next_ids(fs, txn_id, pool), 1287289177Speter APR_WRITE | APR_TRUNCATE, 1288289177Speter APR_OS_DEFAULT, pool)); 1289289177Speter SVN_ERR(svn_io_file_write_full(file, buffer, p - buffer, NULL, pool)); 1290289177Speter return svn_io_file_close(file, pool); 1291289177Speter} 1292289177Speter 1293289177Speter/* Find out what the next unique node-id and copy-id are for 1294289177Speter transaction TXN_ID in filesystem FS. Store the results in *NODE_ID 1295289177Speter and *COPY_ID. The next node-id is used both for creating new unique 1296289177Speter nodes for the given transaction, as well as uniquifying representations. 1297289177Speter Perform all allocations in POOL. */ 1298289177Speterstatic svn_error_t * 1299289177Speterread_next_ids(apr_uint64_t *node_id, 1300289177Speter apr_uint64_t *copy_id, 1301289177Speter svn_fs_t *fs, 1302289177Speter const svn_fs_fs__id_part_t *txn_id, 1303289177Speter apr_pool_t *pool) 1304289177Speter{ 1305289177Speter svn_stringbuf_t *buf; 1306289177Speter const char *str; 1307289177Speter SVN_ERR(svn_fs_fs__read_content(&buf, 1308289177Speter path_txn_next_ids(fs, txn_id, pool), 1309289177Speter pool)); 1310289177Speter 1311289177Speter /* Parse this into two separate strings. */ 1312289177Speter 1313289177Speter str = buf->data; 1314289177Speter *node_id = svn__base36toui64(&str, str); 1315289177Speter if (*str != ' ') 1316289177Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 1317289177Speter _("next-id file corrupt")); 1318289177Speter 1319289177Speter ++str; 1320289177Speter *copy_id = svn__base36toui64(&str, str); 1321289177Speter if (*str != '\n') 1322289177Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 1323289177Speter _("next-id file corrupt")); 1324289177Speter 1325289177Speter return SVN_NO_ERROR; 1326289177Speter} 1327289177Speter 1328289177Speter/* Get a new and unique to this transaction node-id for transaction 1329289177Speter TXN_ID in filesystem FS. Store the new node-id in *NODE_ID_P. 1330289177Speter Node-ids are guaranteed to be unique to this transction, but may 1331289177Speter not necessarily be sequential. Perform all allocations in POOL. */ 1332289177Speterstatic svn_error_t * 1333289177Speterget_new_txn_node_id(svn_fs_fs__id_part_t *node_id_p, 1334289177Speter svn_fs_t *fs, 1335289177Speter const svn_fs_fs__id_part_t *txn_id, 1336289177Speter apr_pool_t *pool) 1337289177Speter{ 1338289177Speter apr_uint64_t node_id, copy_id; 1339289177Speter 1340289177Speter /* First read in the current next-ids file. */ 1341289177Speter SVN_ERR(read_next_ids(&node_id, ©_id, fs, txn_id, pool)); 1342289177Speter 1343289177Speter node_id_p->revision = SVN_INVALID_REVNUM; 1344289177Speter node_id_p->number = node_id; 1345289177Speter 1346289177Speter SVN_ERR(write_next_ids(fs, txn_id, ++node_id, copy_id, pool)); 1347289177Speter 1348289177Speter return SVN_NO_ERROR; 1349289177Speter} 1350289177Speter 1351289177Spetersvn_error_t * 1352289177Spetersvn_fs_fs__reserve_copy_id(svn_fs_fs__id_part_t *copy_id_p, 1353289177Speter svn_fs_t *fs, 1354289177Speter const svn_fs_fs__id_part_t *txn_id, 1355289177Speter apr_pool_t *pool) 1356289177Speter{ 1357289177Speter apr_uint64_t node_id, copy_id; 1358289177Speter 1359289177Speter /* First read in the current next-ids file. */ 1360289177Speter SVN_ERR(read_next_ids(&node_id, ©_id, fs, txn_id, pool)); 1361289177Speter 1362289177Speter /* this is an in-txn ID now */ 1363289177Speter copy_id_p->revision = SVN_INVALID_REVNUM; 1364289177Speter copy_id_p->number = copy_id; 1365289177Speter 1366289177Speter /* Update the ID counter file */ 1367289177Speter SVN_ERR(write_next_ids(fs, txn_id, node_id, ++copy_id, pool)); 1368289177Speter 1369289177Speter return SVN_NO_ERROR; 1370289177Speter} 1371289177Speter 1372289177Spetersvn_error_t * 1373289177Spetersvn_fs_fs__create_node(const svn_fs_id_t **id_p, 1374289177Speter svn_fs_t *fs, 1375289177Speter node_revision_t *noderev, 1376289177Speter const svn_fs_fs__id_part_t *copy_id, 1377289177Speter const svn_fs_fs__id_part_t *txn_id, 1378289177Speter apr_pool_t *pool) 1379289177Speter{ 1380289177Speter svn_fs_fs__id_part_t node_id; 1381289177Speter const svn_fs_id_t *id; 1382289177Speter 1383289177Speter /* Get a new node-id for this node. */ 1384289177Speter SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool)); 1385289177Speter 1386289177Speter id = svn_fs_fs__id_txn_create(&node_id, copy_id, txn_id, pool); 1387289177Speter 1388289177Speter noderev->id = id; 1389289177Speter 1390289177Speter SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool)); 1391289177Speter 1392289177Speter *id_p = id; 1393289177Speter 1394289177Speter return SVN_NO_ERROR; 1395289177Speter} 1396289177Speter 1397289177Spetersvn_error_t * 1398289177Spetersvn_fs_fs__purge_txn(svn_fs_t *fs, 1399289177Speter const char *txn_id_str, 1400289177Speter apr_pool_t *pool) 1401289177Speter{ 1402289177Speter fs_fs_data_t *ffd = fs->fsap_data; 1403289177Speter svn_fs_fs__id_part_t txn_id; 1404289177Speter SVN_ERR(svn_fs_fs__id_txn_parse(&txn_id, txn_id_str)); 1405289177Speter 1406289177Speter /* Remove the shared transaction object associated with this transaction. */ 1407289177Speter SVN_ERR(purge_shared_txn(fs, &txn_id, pool)); 1408289177Speter /* Remove the directory associated with this transaction. */ 1409289177Speter SVN_ERR(svn_io_remove_dir2(svn_fs_fs__path_txn_dir(fs, &txn_id, pool), 1410289177Speter FALSE, NULL, NULL, pool)); 1411289177Speter if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) 1412289177Speter { 1413289177Speter /* Delete protorev and its lock, which aren't in the txn 1414289177Speter directory. It's OK if they don't exist (for example, if this 1415289177Speter is post-commit and the proto-rev has been moved into 1416289177Speter place). */ 1417289177Speter SVN_ERR(svn_io_remove_file2( 1418289177Speter svn_fs_fs__path_txn_proto_rev(fs, &txn_id, pool), 1419289177Speter TRUE, pool)); 1420289177Speter SVN_ERR(svn_io_remove_file2( 1421289177Speter svn_fs_fs__path_txn_proto_rev_lock(fs, &txn_id, pool), 1422289177Speter TRUE, pool)); 1423289177Speter } 1424289177Speter return SVN_NO_ERROR; 1425289177Speter} 1426289177Speter 1427289177Speter 1428289177Spetersvn_error_t * 1429289177Spetersvn_fs_fs__abort_txn(svn_fs_txn_t *txn, 1430289177Speter apr_pool_t *pool) 1431289177Speter{ 1432289177Speter SVN_ERR(svn_fs__check_fs(txn->fs, TRUE)); 1433289177Speter 1434289177Speter /* Now, purge the transaction. */ 1435289177Speter SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool), 1436289177Speter apr_psprintf(pool, _("Transaction '%s' cleanup failed"), 1437289177Speter txn->id)); 1438289177Speter 1439289177Speter return SVN_NO_ERROR; 1440289177Speter} 1441289177Speter 1442289177Speter/* Assign the UNIQUIFIER member of REP based on the current state of TXN_ID 1443289177Speter * in FS. Allocate the uniquifier in POOL. 1444289177Speter */ 1445289177Speterstatic svn_error_t * 1446289177Speterset_uniquifier(svn_fs_t *fs, 1447289177Speter representation_t *rep, 1448289177Speter apr_pool_t *pool) 1449289177Speter{ 1450289177Speter svn_fs_fs__id_part_t temp; 1451289177Speter fs_fs_data_t *ffd = fs->fsap_data; 1452289177Speter 1453289177Speter if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT) 1454289177Speter { 1455289177Speter SVN_ERR(get_new_txn_node_id(&temp, fs, &rep->txn_id, pool)); 1456289177Speter rep->uniquifier.noderev_txn_id = rep->txn_id; 1457289177Speter rep->uniquifier.number = temp.number; 1458289177Speter } 1459289177Speter 1460289177Speter return SVN_NO_ERROR; 1461289177Speter} 1462289177Speter 1463289177Speter/* Return TRUE if the TXN_ID member of REP is in use. 1464289177Speter */ 1465289177Speterstatic svn_boolean_t 1466289177Speteris_txn_rep(const representation_t *rep) 1467289177Speter{ 1468289177Speter return svn_fs_fs__id_txn_used(&rep->txn_id); 1469289177Speter} 1470289177Speter 1471289177Speter/* Mark the TXN_ID member of REP as "unused". 1472289177Speter */ 1473289177Speterstatic void 1474289177Speterreset_txn_in_rep(representation_t *rep) 1475289177Speter{ 1476289177Speter svn_fs_fs__id_txn_reset(&rep->txn_id); 1477289177Speter} 1478289177Speter 1479289177Spetersvn_error_t * 1480289177Spetersvn_fs_fs__set_entry(svn_fs_t *fs, 1481289177Speter const svn_fs_fs__id_part_t *txn_id, 1482289177Speter node_revision_t *parent_noderev, 1483289177Speter const char *name, 1484289177Speter const svn_fs_id_t *id, 1485289177Speter svn_node_kind_t kind, 1486289177Speter apr_pool_t *pool) 1487289177Speter{ 1488289177Speter representation_t *rep = parent_noderev->data_rep; 1489289177Speter const char *filename 1490289177Speter = svn_fs_fs__path_txn_node_children(fs, parent_noderev->id, pool); 1491289177Speter apr_file_t *file; 1492289177Speter svn_stream_t *out; 1493289177Speter fs_fs_data_t *ffd = fs->fsap_data; 1494289177Speter apr_pool_t *subpool = svn_pool_create(pool); 1495289177Speter 1496289177Speter if (!rep || !is_txn_rep(rep)) 1497289177Speter { 1498289177Speter apr_array_header_t *entries; 1499289177Speter 1500289177Speter /* Before we can modify the directory, we need to dump its old 1501289177Speter contents into a mutable representation file. */ 1502289177Speter SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev, 1503289177Speter subpool, subpool)); 1504289177Speter SVN_ERR(svn_io_file_open(&file, filename, 1505289177Speter APR_WRITE | APR_CREATE | APR_BUFFERED, 1506289177Speter APR_OS_DEFAULT, pool)); 1507289177Speter out = svn_stream_from_aprfile2(file, TRUE, pool); 1508289177Speter SVN_ERR(unparse_dir_entries(entries, out, subpool)); 1509289177Speter 1510289177Speter svn_pool_clear(subpool); 1511289177Speter 1512289177Speter /* Mark the node-rev's data rep as mutable. */ 1513289177Speter rep = apr_pcalloc(pool, sizeof(*rep)); 1514289177Speter rep->revision = SVN_INVALID_REVNUM; 1515289177Speter rep->txn_id = *txn_id; 1516289177Speter SVN_ERR(set_uniquifier(fs, rep, pool)); 1517289177Speter parent_noderev->data_rep = rep; 1518289177Speter SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id, 1519289177Speter parent_noderev, FALSE, pool)); 1520289177Speter } 1521289177Speter else 1522289177Speter { 1523289177Speter /* The directory rep is already mutable, so just open it for append. */ 1524289177Speter SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND, 1525289177Speter APR_OS_DEFAULT, pool)); 1526289177Speter out = svn_stream_from_aprfile2(file, TRUE, pool); 1527289177Speter } 1528289177Speter 1529289177Speter /* if we have a directory cache for this transaction, update it */ 1530289177Speter if (ffd->txn_dir_cache) 1531289177Speter { 1532289177Speter /* build parameters: (name, new entry) pair */ 1533289177Speter const char *key = 1534289177Speter svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data; 1535289177Speter replace_baton_t baton; 1536289177Speter 1537289177Speter baton.name = name; 1538289177Speter baton.new_entry = NULL; 1539289177Speter 1540289177Speter if (id) 1541289177Speter { 1542289177Speter baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry)); 1543289177Speter baton.new_entry->name = name; 1544289177Speter baton.new_entry->kind = kind; 1545289177Speter baton.new_entry->id = id; 1546289177Speter } 1547289177Speter 1548289177Speter /* actually update the cached directory (if cached) */ 1549289177Speter SVN_ERR(svn_cache__set_partial(ffd->txn_dir_cache, key, 1550289177Speter svn_fs_fs__replace_dir_entry, &baton, 1551289177Speter subpool)); 1552289177Speter } 1553289177Speter svn_pool_clear(subpool); 1554289177Speter 1555289177Speter /* Append an incremental hash entry for the entry change. */ 1556289177Speter if (id) 1557289177Speter { 1558289177Speter svn_fs_dirent_t entry; 1559289177Speter entry.name = name; 1560289177Speter entry.id = id; 1561289177Speter entry.kind = kind; 1562289177Speter 1563289177Speter SVN_ERR(unparse_dir_entry(&entry, out, subpool)); 1564289177Speter } 1565289177Speter else 1566289177Speter { 1567289177Speter SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n", 1568289177Speter strlen(name), name)); 1569289177Speter } 1570289177Speter 1571289177Speter SVN_ERR(svn_io_file_close(file, subpool)); 1572289177Speter svn_pool_destroy(subpool); 1573289177Speter return SVN_NO_ERROR; 1574289177Speter} 1575289177Speter 1576289177Spetersvn_error_t * 1577289177Spetersvn_fs_fs__add_change(svn_fs_t *fs, 1578289177Speter const svn_fs_fs__id_part_t *txn_id, 1579289177Speter const char *path, 1580289177Speter const svn_fs_id_t *id, 1581289177Speter svn_fs_path_change_kind_t change_kind, 1582289177Speter svn_boolean_t text_mod, 1583289177Speter svn_boolean_t prop_mod, 1584289177Speter svn_boolean_t mergeinfo_mod, 1585289177Speter svn_node_kind_t node_kind, 1586289177Speter svn_revnum_t copyfrom_rev, 1587289177Speter const char *copyfrom_path, 1588289177Speter apr_pool_t *pool) 1589289177Speter{ 1590289177Speter apr_file_t *file; 1591289177Speter svn_fs_path_change2_t *change; 1592289177Speter apr_hash_t *changes = apr_hash_make(pool); 1593289177Speter 1594289177Speter /* Not using APR_BUFFERED to append change in one atomic write operation. */ 1595289177Speter SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool), 1596289177Speter APR_APPEND | APR_WRITE | APR_CREATE, 1597289177Speter APR_OS_DEFAULT, pool)); 1598289177Speter 1599289177Speter change = svn_fs__path_change_create_internal(id, change_kind, pool); 1600289177Speter change->text_mod = text_mod; 1601289177Speter change->prop_mod = prop_mod; 1602289177Speter change->mergeinfo_mod = mergeinfo_mod 1603289177Speter ? svn_tristate_true 1604289177Speter : svn_tristate_false; 1605289177Speter change->node_kind = node_kind; 1606289177Speter change->copyfrom_known = TRUE; 1607289177Speter change->copyfrom_rev = copyfrom_rev; 1608289177Speter if (copyfrom_path) 1609289177Speter change->copyfrom_path = apr_pstrdup(pool, copyfrom_path); 1610289177Speter 1611289177Speter svn_hash_sets(changes, path, change); 1612289177Speter SVN_ERR(svn_fs_fs__write_changes(svn_stream_from_aprfile2(file, TRUE, pool), 1613289177Speter fs, changes, FALSE, pool)); 1614289177Speter 1615289177Speter return svn_io_file_close(file, pool); 1616289177Speter} 1617289177Speter 1618289177Speter/* If the transaction TXN_ID in FS uses logical addressing, store the 1619289177Speter * (ITEM_INDEX, OFFSET) pair in the txn's log-to-phys proto index file. 1620289177Speter * Use POOL for allocations. 1621289177Speter */ 1622289177Speterstatic svn_error_t * 1623289177Speterstore_l2p_index_entry(svn_fs_t *fs, 1624289177Speter const svn_fs_fs__id_part_t *txn_id, 1625289177Speter apr_off_t offset, 1626289177Speter apr_uint64_t item_index, 1627289177Speter apr_pool_t *pool) 1628289177Speter{ 1629289177Speter if (svn_fs_fs__use_log_addressing(fs)) 1630289177Speter { 1631289177Speter const char *path = svn_fs_fs__path_l2p_proto_index(fs, txn_id, pool); 1632289177Speter apr_file_t *file; 1633289177Speter SVN_ERR(svn_fs_fs__l2p_proto_index_open(&file, path, pool)); 1634289177Speter SVN_ERR(svn_fs_fs__l2p_proto_index_add_entry(file, offset, 1635289177Speter item_index, pool)); 1636289177Speter SVN_ERR(svn_io_file_close(file, pool)); 1637289177Speter } 1638289177Speter 1639289177Speter return SVN_NO_ERROR; 1640289177Speter} 1641289177Speter 1642289177Speter/* If the transaction TXN_ID in FS uses logical addressing, store ENTRY 1643289177Speter * in the phys-to-log proto index file of transaction TXN_ID. 1644289177Speter * Use POOL for allocations. 1645289177Speter */ 1646289177Speterstatic svn_error_t * 1647289177Speterstore_p2l_index_entry(svn_fs_t *fs, 1648289177Speter const svn_fs_fs__id_part_t *txn_id, 1649289177Speter svn_fs_fs__p2l_entry_t *entry, 1650289177Speter apr_pool_t *pool) 1651289177Speter{ 1652289177Speter if (svn_fs_fs__use_log_addressing(fs)) 1653289177Speter { 1654289177Speter const char *path = svn_fs_fs__path_p2l_proto_index(fs, txn_id, pool); 1655289177Speter apr_file_t *file; 1656289177Speter SVN_ERR(svn_fs_fs__p2l_proto_index_open(&file, path, pool)); 1657289177Speter SVN_ERR(svn_fs_fs__p2l_proto_index_add_entry(file, entry, pool)); 1658289177Speter SVN_ERR(svn_io_file_close(file, pool)); 1659289177Speter } 1660289177Speter 1661289177Speter return SVN_NO_ERROR; 1662289177Speter} 1663289177Speter 1664289177Speter/* Allocate an item index for the given MY_OFFSET in the transaction TXN_ID 1665289177Speter * of file system FS and return it in *ITEM_INDEX. For old formats, it 1666289177Speter * will simply return the offset as item index; in new formats, it will 1667289177Speter * increment the txn's item index counter file and store the mapping in 1668289177Speter * the proto index file. Use POOL for allocations. 1669289177Speter */ 1670289177Speterstatic svn_error_t * 1671289177Speterallocate_item_index(apr_uint64_t *item_index, 1672289177Speter svn_fs_t *fs, 1673289177Speter const svn_fs_fs__id_part_t *txn_id, 1674289177Speter apr_off_t my_offset, 1675289177Speter apr_pool_t *pool) 1676289177Speter{ 1677289177Speter if (svn_fs_fs__use_log_addressing(fs)) 1678289177Speter { 1679289177Speter apr_file_t *file; 1680289177Speter char buffer[SVN_INT64_BUFFER_SIZE] = { 0 }; 1681289177Speter svn_boolean_t eof = FALSE; 1682289177Speter apr_size_t to_write; 1683289177Speter apr_size_t read; 1684289177Speter apr_off_t offset = 0; 1685289177Speter 1686289177Speter /* read number, increment it and write it back to disk */ 1687289177Speter SVN_ERR(svn_io_file_open(&file, 1688289177Speter svn_fs_fs__path_txn_item_index(fs, txn_id, pool), 1689289177Speter APR_READ | APR_WRITE | APR_CREATE | APR_BUFFERED, 1690289177Speter APR_OS_DEFAULT, pool)); 1691289177Speter SVN_ERR(svn_io_file_read_full2(file, buffer, sizeof(buffer)-1, 1692289177Speter &read, &eof, pool)); 1693289177Speter if (read) 1694289177Speter SVN_ERR(svn_cstring_atoui64(item_index, buffer)); 1695289177Speter else 1696289177Speter *item_index = SVN_FS_FS__ITEM_INDEX_FIRST_USER; 1697289177Speter 1698289177Speter to_write = svn__ui64toa(buffer, *item_index + 1); 1699289177Speter SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, pool)); 1700289177Speter SVN_ERR(svn_io_file_write_full(file, buffer, to_write, NULL, pool)); 1701289177Speter SVN_ERR(svn_io_file_close(file, pool)); 1702289177Speter 1703289177Speter /* write log-to-phys index */ 1704289177Speter SVN_ERR(store_l2p_index_entry(fs, txn_id, my_offset, *item_index, pool)); 1705289177Speter } 1706289177Speter else 1707289177Speter { 1708289177Speter *item_index = (apr_uint64_t)my_offset; 1709289177Speter } 1710289177Speter 1711289177Speter return SVN_NO_ERROR; 1712289177Speter} 1713289177Speter 1714289177Speter/* Baton used by fnv1a_write_handler to calculate the FNV checksum 1715289177Speter * before passing the data on to the INNER_STREAM. 1716289177Speter */ 1717289177Spetertypedef struct fnv1a_stream_baton_t 1718289177Speter{ 1719289177Speter svn_stream_t *inner_stream; 1720289177Speter svn_checksum_ctx_t *context; 1721289177Speter} fnv1a_stream_baton_t; 1722289177Speter 1723289177Speter/* Implement svn_write_fn_t. 1724289177Speter * Update checksum and pass data on to inner stream. 1725289177Speter */ 1726289177Speterstatic svn_error_t * 1727289177Speterfnv1a_write_handler(void *baton, 1728289177Speter const char *data, 1729289177Speter apr_size_t *len) 1730289177Speter{ 1731289177Speter fnv1a_stream_baton_t *b = baton; 1732289177Speter 1733289177Speter SVN_ERR(svn_checksum_update(b->context, data, *len)); 1734289177Speter SVN_ERR(svn_stream_write(b->inner_stream, data, len)); 1735289177Speter 1736289177Speter return SVN_NO_ERROR; 1737289177Speter} 1738289177Speter 1739289177Speter/* Return a stream that calculates a FNV checksum in *CONTEXT 1740289177Speter * over all data written to the stream and passes that data on 1741289177Speter * to INNER_STREAM. Allocate objects in POOL. 1742289177Speter */ 1743289177Speterstatic svn_stream_t * 1744289177Speterfnv1a_wrap_stream(svn_checksum_ctx_t **context, 1745289177Speter svn_stream_t *inner_stream, 1746289177Speter apr_pool_t *pool) 1747289177Speter{ 1748289177Speter svn_stream_t *outer_stream; 1749289177Speter 1750289177Speter fnv1a_stream_baton_t *baton = apr_pcalloc(pool, sizeof(*baton)); 1751289177Speter baton->inner_stream = inner_stream; 1752289177Speter baton->context = svn_checksum_ctx_create(svn_checksum_fnv1a_32x4, pool); 1753289177Speter *context = baton->context; 1754289177Speter 1755289177Speter outer_stream = svn_stream_create(baton, pool); 1756289177Speter svn_stream_set_write(outer_stream, fnv1a_write_handler); 1757289177Speter 1758289177Speter return outer_stream; 1759289177Speter} 1760289177Speter 1761289177Speter/* Set *DIGEST to the FNV checksum calculated in CONTEXT. 1762289177Speter * Use SCRATCH_POOL for temporary allocations. 1763289177Speter */ 1764289177Speterstatic svn_error_t * 1765289177Speterfnv1a_checksum_finalize(apr_uint32_t *digest, 1766289177Speter svn_checksum_ctx_t *context, 1767289177Speter apr_pool_t *scratch_pool) 1768289177Speter{ 1769289177Speter svn_checksum_t *checksum; 1770289177Speter 1771289177Speter SVN_ERR(svn_checksum_final(&checksum, context, scratch_pool)); 1772289177Speter SVN_ERR_ASSERT(checksum->kind == svn_checksum_fnv1a_32x4); 1773289177Speter *digest = ntohl(*(const apr_uint32_t *)(checksum->digest)); 1774289177Speter 1775289177Speter return SVN_NO_ERROR; 1776289177Speter} 1777289177Speter 1778289177Speter/* This baton is used by the representation writing streams. It keeps 1779289177Speter track of the checksum information as well as the total size of the 1780289177Speter representation so far. */ 1781289177Speterstruct rep_write_baton 1782289177Speter{ 1783289177Speter /* The FS we are writing to. */ 1784289177Speter svn_fs_t *fs; 1785289177Speter 1786289177Speter /* Actual file to which we are writing. */ 1787289177Speter svn_stream_t *rep_stream; 1788289177Speter 1789289177Speter /* A stream from the delta combiner. Data written here gets 1790289177Speter deltified, then eventually written to rep_stream. */ 1791289177Speter svn_stream_t *delta_stream; 1792289177Speter 1793289177Speter /* Where is this representation header stored. */ 1794289177Speter apr_off_t rep_offset; 1795289177Speter 1796289177Speter /* Start of the actual data. */ 1797289177Speter apr_off_t delta_start; 1798289177Speter 1799289177Speter /* How many bytes have been written to this rep already. */ 1800289177Speter svn_filesize_t rep_size; 1801289177Speter 1802289177Speter /* The node revision for which we're writing out info. */ 1803289177Speter node_revision_t *noderev; 1804289177Speter 1805289177Speter /* Actual output file. */ 1806289177Speter apr_file_t *file; 1807289177Speter /* Lock 'cookie' used to unlock the output file once we've finished 1808289177Speter writing to it. */ 1809289177Speter void *lockcookie; 1810289177Speter 1811289177Speter svn_checksum_ctx_t *md5_checksum_ctx; 1812289177Speter svn_checksum_ctx_t *sha1_checksum_ctx; 1813289177Speter 1814289177Speter /* calculate a modified FNV-1a checksum of the on-disk representation */ 1815289177Speter svn_checksum_ctx_t *fnv1a_checksum_ctx; 1816289177Speter 1817289177Speter /* Local / scratch pool, available for temporary allocations. */ 1818289177Speter apr_pool_t *scratch_pool; 1819289177Speter 1820289177Speter /* Outer / result pool. */ 1821289177Speter apr_pool_t *result_pool; 1822289177Speter}; 1823289177Speter 1824289177Speter/* Handler for the write method of the representation writable stream. 1825289177Speter BATON is a rep_write_baton, DATA is the data to write, and *LEN is 1826289177Speter the length of this data. */ 1827289177Speterstatic svn_error_t * 1828289177Speterrep_write_contents(void *baton, 1829289177Speter const char *data, 1830289177Speter apr_size_t *len) 1831289177Speter{ 1832289177Speter struct rep_write_baton *b = baton; 1833289177Speter 1834289177Speter SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len)); 1835289177Speter SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len)); 1836289177Speter b->rep_size += *len; 1837289177Speter 1838289177Speter /* If we are writing a delta, use that stream. */ 1839289177Speter if (b->delta_stream) 1840289177Speter return svn_stream_write(b->delta_stream, data, len); 1841289177Speter else 1842289177Speter return svn_stream_write(b->rep_stream, data, len); 1843289177Speter} 1844289177Speter 1845289177Speter/* Set *SPANNED to the number of shards touched when walking WALK steps on 1846289177Speter * NODEREV's predecessor chain in FS. Use POOL for temporary allocations. 1847289177Speter */ 1848289177Speterstatic svn_error_t * 1849289177Spetershards_spanned(int *spanned, 1850289177Speter svn_fs_t *fs, 1851289177Speter node_revision_t *noderev, 1852289177Speter int walk, 1853289177Speter apr_pool_t *pool) 1854289177Speter{ 1855289177Speter fs_fs_data_t *ffd = fs->fsap_data; 1856289177Speter int shard_size = ffd->max_files_per_dir ? ffd->max_files_per_dir : 1; 1857289177Speter apr_pool_t *iterpool; 1858289177Speter 1859289177Speter int count = walk ? 1 : 0; /* The start of a walk already touches a shard. */ 1860289177Speter svn_revnum_t shard, last_shard = ffd->youngest_rev_cache / shard_size; 1861289177Speter iterpool = svn_pool_create(pool); 1862289177Speter while (walk-- && noderev->predecessor_count) 1863289177Speter { 1864289177Speter svn_pool_clear(iterpool); 1865289177Speter SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, 1866289177Speter noderev->predecessor_id, pool, 1867289177Speter iterpool)); 1868289177Speter shard = svn_fs_fs__id_rev(noderev->id) / shard_size; 1869289177Speter if (shard != last_shard) 1870289177Speter { 1871289177Speter ++count; 1872289177Speter last_shard = shard; 1873289177Speter } 1874289177Speter } 1875289177Speter svn_pool_destroy(iterpool); 1876289177Speter 1877289177Speter *spanned = count; 1878289177Speter return SVN_NO_ERROR; 1879289177Speter} 1880289177Speter 1881289177Speter/* Given a node-revision NODEREV in filesystem FS, return the 1882289177Speter representation in *REP to use as the base for a text representation 1883289177Speter delta if PROPS is FALSE. If PROPS has been set, a suitable props 1884289177Speter base representation will be returned. Perform temporary allocations 1885289177Speter in *POOL. */ 1886289177Speterstatic svn_error_t * 1887289177Speterchoose_delta_base(representation_t **rep, 1888289177Speter svn_fs_t *fs, 1889289177Speter node_revision_t *noderev, 1890289177Speter svn_boolean_t props, 1891289177Speter apr_pool_t *pool) 1892289177Speter{ 1893289177Speter /* The zero-based index (counting from the "oldest" end), along NODEREVs line 1894289177Speter * predecessors, of the node-rev we will use as delta base. */ 1895289177Speter int count; 1896289177Speter /* The length of the linear part of a delta chain. (Delta chains use 1897289177Speter * skip-delta bits for the high-order bits and are linear in the low-order 1898289177Speter * bits.) */ 1899289177Speter int walk; 1900289177Speter node_revision_t *base; 1901289177Speter fs_fs_data_t *ffd = fs->fsap_data; 1902289177Speter apr_pool_t *iterpool; 1903289177Speter 1904289177Speter /* If we have no predecessors, or that one is empty, then use the empty 1905289177Speter * stream as a base. */ 1906289177Speter if (! noderev->predecessor_count) 1907289177Speter { 1908289177Speter *rep = NULL; 1909289177Speter return SVN_NO_ERROR; 1910289177Speter } 1911289177Speter 1912289177Speter /* Flip the rightmost '1' bit of the predecessor count to determine 1913289177Speter which file rev (counting from 0) we want to use. (To see why 1914289177Speter count & (count - 1) unsets the rightmost set bit, think about how 1915289177Speter you decrement a binary number.) */ 1916289177Speter count = noderev->predecessor_count; 1917289177Speter count = count & (count - 1); 1918289177Speter 1919289177Speter /* Finding the delta base over a very long distance can become extremely 1920289177Speter expensive for very deep histories, possibly causing client timeouts etc. 1921289177Speter OTOH, this is a rare operation and its gains are minimal. Lets simply 1922289177Speter start deltification anew close every other 1000 changes or so. */ 1923289177Speter walk = noderev->predecessor_count - count; 1924289177Speter if (walk > (int)ffd->max_deltification_walk) 1925289177Speter { 1926289177Speter *rep = NULL; 1927289177Speter return SVN_NO_ERROR; 1928289177Speter } 1929289177Speter 1930289177Speter /* We use skip delta for limiting the number of delta operations 1931289177Speter along very long node histories. Close to HEAD however, we create 1932289177Speter a linear history to minimize delta size. */ 1933289177Speter if (walk < (int)ffd->max_linear_deltification) 1934289177Speter { 1935289177Speter int shards; 1936289177Speter SVN_ERR(shards_spanned(&shards, fs, noderev, walk, pool)); 1937289177Speter 1938289177Speter /* We also don't want the linear deltification to span more shards 1939289177Speter than if deltas we used in a simple skip-delta scheme. */ 1940289177Speter if ((1 << (--shards)) <= walk) 1941289177Speter count = noderev->predecessor_count - 1; 1942289177Speter } 1943289177Speter 1944289177Speter /* Walk back a number of predecessors equal to the difference 1945289177Speter between count and the original predecessor count. (For example, 1946289177Speter if noderev has ten predecessors and we want the eighth file rev, 1947289177Speter walk back two predecessors.) */ 1948289177Speter base = noderev; 1949289177Speter iterpool = svn_pool_create(pool); 1950289177Speter while ((count++) < noderev->predecessor_count) 1951289177Speter { 1952289177Speter svn_pool_clear(iterpool); 1953289177Speter SVN_ERR(svn_fs_fs__get_node_revision(&base, fs, 1954289177Speter base->predecessor_id, pool, 1955289177Speter iterpool)); 1956289177Speter } 1957289177Speter svn_pool_destroy(iterpool); 1958289177Speter 1959289177Speter /* return a suitable base representation */ 1960289177Speter *rep = props ? base->prop_rep : base->data_rep; 1961289177Speter 1962289177Speter /* if we encountered a shared rep, its parent chain may be different 1963289177Speter * from the node-rev parent chain. */ 1964289177Speter if (*rep) 1965289177Speter { 1966289177Speter int chain_length = 0; 1967289177Speter int shard_count = 0; 1968289177Speter 1969289177Speter /* Very short rep bases are simply not worth it as we are unlikely 1970289177Speter * to re-coup the deltification space overhead of 20+ bytes. */ 1971289177Speter svn_filesize_t rep_size = (*rep)->expanded_size 1972289177Speter ? (*rep)->expanded_size 1973289177Speter : (*rep)->size; 1974289177Speter if (rep_size < 64) 1975289177Speter { 1976289177Speter *rep = NULL; 1977289177Speter return SVN_NO_ERROR; 1978289177Speter } 1979289177Speter 1980289177Speter /* Check whether the length of the deltification chain is acceptable. 1981289177Speter * Otherwise, shared reps may form a non-skipping delta chain in 1982289177Speter * extreme cases. */ 1983289177Speter SVN_ERR(svn_fs_fs__rep_chain_length(&chain_length, &shard_count, 1984289177Speter *rep, fs, pool)); 1985289177Speter 1986289177Speter /* Some reasonable limit, depending on how acceptable longer linear 1987289177Speter * chains are in this repo. Also, allow for some minimal chain. */ 1988289177Speter if (chain_length >= 2 * (int)ffd->max_linear_deltification + 2) 1989289177Speter *rep = NULL; 1990289177Speter else 1991289177Speter /* To make it worth opening additional shards / pack files, we 1992289177Speter * require that the reps have a certain minimal size. To deltify 1993289177Speter * against a rep in different shard, the lower limit is 512 bytes 1994289177Speter * and doubles with every extra shard to visit along the delta 1995289177Speter * chain. */ 1996289177Speter if ( shard_count > 1 1997289177Speter && ((svn_filesize_t)128 << shard_count) >= rep_size) 1998289177Speter *rep = NULL; 1999289177Speter } 2000289177Speter 2001289177Speter return SVN_NO_ERROR; 2002289177Speter} 2003289177Speter 2004289177Speter/* Something went wrong and the pool for the rep write is being 2005289177Speter cleared before we've finished writing the rep. So we need 2006289177Speter to remove the rep from the protorevfile and we need to unlock 2007289177Speter the protorevfile. */ 2008289177Speterstatic apr_status_t 2009289177Speterrep_write_cleanup(void *data) 2010289177Speter{ 2011289177Speter struct rep_write_baton *b = data; 2012289177Speter svn_error_t *err; 2013289177Speter 2014289177Speter /* Truncate and close the protorevfile. */ 2015289177Speter err = svn_io_file_trunc(b->file, b->rep_offset, b->scratch_pool); 2016289177Speter err = svn_error_compose_create(err, svn_io_file_close(b->file, 2017289177Speter b->scratch_pool)); 2018289177Speter 2019289177Speter /* Remove our lock regardless of any preceding errors so that the 2020289177Speter being_written flag is always removed and stays consistent with the 2021289177Speter file lock which will be removed no matter what since the pool is 2022289177Speter going away. */ 2023289177Speter err = svn_error_compose_create(err, 2024289177Speter unlock_proto_rev(b->fs, 2025289177Speter svn_fs_fs__id_txn_id(b->noderev->id), 2026289177Speter b->lockcookie, b->scratch_pool)); 2027289177Speter if (err) 2028289177Speter { 2029289177Speter apr_status_t rc = err->apr_err; 2030289177Speter svn_error_clear(err); 2031289177Speter return rc; 2032289177Speter } 2033289177Speter 2034289177Speter return APR_SUCCESS; 2035289177Speter} 2036289177Speter 2037289177Speter/* Get a rep_write_baton and store it in *WB_P for the representation 2038289177Speter indicated by NODEREV in filesystem FS. Perform allocations in 2039289177Speter POOL. Only appropriate for file contents, not for props or 2040289177Speter directory contents. */ 2041289177Speterstatic svn_error_t * 2042289177Speterrep_write_get_baton(struct rep_write_baton **wb_p, 2043289177Speter svn_fs_t *fs, 2044289177Speter node_revision_t *noderev, 2045289177Speter apr_pool_t *pool) 2046289177Speter{ 2047289177Speter struct rep_write_baton *b; 2048289177Speter apr_file_t *file; 2049289177Speter representation_t *base_rep; 2050289177Speter svn_stream_t *source; 2051289177Speter svn_txdelta_window_handler_t wh; 2052289177Speter void *whb; 2053289177Speter fs_fs_data_t *ffd = fs->fsap_data; 2054289177Speter int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0; 2055289177Speter svn_fs_fs__rep_header_t header = { 0 }; 2056289177Speter 2057289177Speter b = apr_pcalloc(pool, sizeof(*b)); 2058289177Speter 2059289177Speter b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool); 2060289177Speter b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); 2061289177Speter 2062289177Speter b->fs = fs; 2063289177Speter b->result_pool = pool; 2064289177Speter b->scratch_pool = svn_pool_create(pool); 2065289177Speter b->rep_size = 0; 2066289177Speter b->noderev = noderev; 2067289177Speter 2068289177Speter /* Open the prototype rev file and seek to its end. */ 2069289177Speter SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie, 2070289177Speter fs, svn_fs_fs__id_txn_id(noderev->id), 2071289177Speter b->scratch_pool)); 2072289177Speter 2073289177Speter b->file = file; 2074289177Speter b->rep_stream = fnv1a_wrap_stream(&b->fnv1a_checksum_ctx, 2075289177Speter svn_stream_from_aprfile2(file, TRUE, 2076289177Speter b->scratch_pool), 2077289177Speter b->scratch_pool); 2078289177Speter 2079289177Speter SVN_ERR(svn_fs_fs__get_file_offset(&b->rep_offset, file, b->scratch_pool)); 2080289177Speter 2081289177Speter /* Get the base for this delta. */ 2082289177Speter SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->scratch_pool)); 2083289177Speter SVN_ERR(svn_fs_fs__get_contents(&source, fs, base_rep, TRUE, 2084289177Speter b->scratch_pool)); 2085289177Speter 2086289177Speter /* Write out the rep header. */ 2087289177Speter if (base_rep) 2088289177Speter { 2089289177Speter header.base_revision = base_rep->revision; 2090289177Speter header.base_item_index = base_rep->item_index; 2091289177Speter header.base_length = base_rep->size; 2092289177Speter header.type = svn_fs_fs__rep_delta; 2093289177Speter } 2094289177Speter else 2095289177Speter { 2096289177Speter header.type = svn_fs_fs__rep_self_delta; 2097289177Speter } 2098289177Speter SVN_ERR(svn_fs_fs__write_rep_header(&header, b->rep_stream, 2099289177Speter b->scratch_pool)); 2100289177Speter 2101289177Speter /* Now determine the offset of the actual svndiff data. */ 2102289177Speter SVN_ERR(svn_fs_fs__get_file_offset(&b->delta_start, file, 2103289177Speter b->scratch_pool)); 2104289177Speter 2105289177Speter /* Cleanup in case something goes wrong. */ 2106289177Speter apr_pool_cleanup_register(b->scratch_pool, b, rep_write_cleanup, 2107289177Speter apr_pool_cleanup_null); 2108289177Speter 2109289177Speter /* Prepare to write the svndiff data. */ 2110289177Speter svn_txdelta_to_svndiff3(&wh, 2111289177Speter &whb, 2112289177Speter b->rep_stream, 2113289177Speter diff_version, 2114289177Speter ffd->delta_compression_level, 2115289177Speter pool); 2116289177Speter 2117289177Speter b->delta_stream = svn_txdelta_target_push(wh, whb, source, 2118289177Speter b->scratch_pool); 2119289177Speter 2120289177Speter *wb_p = b; 2121289177Speter 2122289177Speter return SVN_NO_ERROR; 2123289177Speter} 2124289177Speter 2125289177Speter/* For REP->SHA1_CHECKSUM, try to find an already existing representation 2126289177Speter in FS and return it in *OUT_REP. If no such representation exists or 2127289177Speter if rep sharing has been disabled for FS, NULL will be returned. Since 2128289177Speter there may be new duplicate representations within the same uncommitted 2129289177Speter revision, those can be passed in REPS_HASH (maps a sha1 digest onto 2130289177Speter representation_t*), otherwise pass in NULL for REPS_HASH. 2131289177Speter Use RESULT_POOL for *OLD_REP allocations and SCRATCH_POOL for temporaries. 2132289177Speter The lifetime of *OLD_REP is limited by both, RESULT_POOL and REP lifetime. 2133289177Speter */ 2134289177Speterstatic svn_error_t * 2135289177Speterget_shared_rep(representation_t **old_rep, 2136289177Speter svn_fs_t *fs, 2137289177Speter representation_t *rep, 2138289177Speter apr_hash_t *reps_hash, 2139289177Speter apr_pool_t *result_pool, 2140289177Speter apr_pool_t *scratch_pool) 2141289177Speter{ 2142289177Speter svn_error_t *err; 2143289177Speter fs_fs_data_t *ffd = fs->fsap_data; 2144289177Speter 2145289177Speter /* Return NULL, if rep sharing has been disabled. */ 2146289177Speter *old_rep = NULL; 2147289177Speter if (!ffd->rep_sharing_allowed) 2148289177Speter return SVN_NO_ERROR; 2149289177Speter 2150289177Speter /* Check and see if we already have a representation somewhere that's 2151289177Speter identical to the one we just wrote out. Start with the hash lookup 2152289177Speter because it is cheepest. */ 2153289177Speter if (reps_hash) 2154289177Speter *old_rep = apr_hash_get(reps_hash, 2155289177Speter rep->sha1_digest, 2156289177Speter APR_SHA1_DIGESTSIZE); 2157289177Speter 2158289177Speter /* If we haven't found anything yet, try harder and consult our DB. */ 2159289177Speter if (*old_rep == NULL) 2160289177Speter { 2161289177Speter svn_checksum_t checksum; 2162289177Speter checksum.digest = rep->sha1_digest; 2163289177Speter checksum.kind = svn_checksum_sha1; 2164289177Speter err = svn_fs_fs__get_rep_reference(old_rep, fs, &checksum, result_pool); 2165289177Speter /* ### Other error codes that we shouldn't mask out? */ 2166289177Speter if (err == SVN_NO_ERROR) 2167289177Speter { 2168289177Speter if (*old_rep) 2169289177Speter SVN_ERR(svn_fs_fs__check_rep(*old_rep, fs, NULL, scratch_pool)); 2170289177Speter } 2171289177Speter else if (err->apr_err == SVN_ERR_FS_CORRUPT 2172289177Speter || SVN_ERROR_IN_CATEGORY(err->apr_err, 2173289177Speter SVN_ERR_MALFUNC_CATEGORY_START)) 2174289177Speter { 2175289177Speter /* Fatal error; don't mask it. 2176289177Speter 2177289177Speter In particular, this block is triggered when the rep-cache refers 2178289177Speter to revisions in the future. We signal that as a corruption situation 2179289177Speter since, once those revisions are less than youngest (because of more 2180289177Speter commits), the rep-cache would be invalid. 2181289177Speter */ 2182289177Speter SVN_ERR(err); 2183289177Speter } 2184289177Speter else 2185289177Speter { 2186289177Speter /* Something's wrong with the rep-sharing index. We can continue 2187289177Speter without rep-sharing, but warn. 2188289177Speter */ 2189289177Speter (fs->warning)(fs->warning_baton, err); 2190289177Speter svn_error_clear(err); 2191289177Speter *old_rep = NULL; 2192289177Speter } 2193289177Speter } 2194289177Speter 2195289177Speter /* look for intra-revision matches (usually data reps but not limited 2196289177Speter to them in case props happen to look like some data rep) 2197289177Speter */ 2198289177Speter if (*old_rep == NULL && is_txn_rep(rep)) 2199289177Speter { 2200289177Speter svn_node_kind_t kind; 2201289177Speter const char *file_name 2202289177Speter = path_txn_sha1(fs, &rep->txn_id, rep->sha1_digest, scratch_pool); 2203289177Speter 2204289177Speter /* in our txn, is there a rep file named with the wanted SHA1? 2205289177Speter If so, read it and use that rep. 2206289177Speter */ 2207289177Speter SVN_ERR(svn_io_check_path(file_name, &kind, scratch_pool)); 2208289177Speter if (kind == svn_node_file) 2209289177Speter { 2210289177Speter svn_stringbuf_t *rep_string; 2211289177Speter SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name, 2212289177Speter scratch_pool)); 2213289177Speter SVN_ERR(svn_fs_fs__parse_representation(old_rep, rep_string, 2214289177Speter result_pool, scratch_pool)); 2215289177Speter } 2216289177Speter } 2217289177Speter 2218289177Speter if (!*old_rep) 2219289177Speter return SVN_NO_ERROR; 2220289177Speter 2221289177Speter /* We don't want 0-length PLAIN representations to replace non-0-length 2222289177Speter ones (see issue #4554). Take into account that EXPANDED_SIZE may be 2223289177Speter 0 in which case we have to check the on-disk SIZE. Also, this doubles 2224289177Speter as a simple guard against general rep-cache induced corruption. */ 2225289177Speter if ( ((*old_rep)->expanded_size != rep->expanded_size) 2226289177Speter || ((rep->expanded_size == 0) && ((*old_rep)->size != rep->size))) 2227289177Speter { 2228289177Speter *old_rep = NULL; 2229289177Speter } 2230289177Speter else 2231289177Speter { 2232289177Speter /* Add information that is missing in the cached data. 2233289177Speter Use the old rep for this content. */ 2234289177Speter memcpy((*old_rep)->md5_digest, rep->md5_digest, sizeof(rep->md5_digest)); 2235289177Speter (*old_rep)->uniquifier = rep->uniquifier; 2236289177Speter } 2237289177Speter 2238289177Speter return SVN_NO_ERROR; 2239289177Speter} 2240289177Speter 2241289177Speter/* Copy the hash sum calculation results from MD5_CTX, SHA1_CTX into REP. 2242289177Speter * Use POOL for allocations. 2243289177Speter */ 2244289177Speterstatic svn_error_t * 2245289177Speterdigests_final(representation_t *rep, 2246289177Speter const svn_checksum_ctx_t *md5_ctx, 2247289177Speter const svn_checksum_ctx_t *sha1_ctx, 2248289177Speter apr_pool_t *pool) 2249289177Speter{ 2250289177Speter svn_checksum_t *checksum; 2251289177Speter 2252289177Speter SVN_ERR(svn_checksum_final(&checksum, md5_ctx, pool)); 2253289177Speter memcpy(rep->md5_digest, checksum->digest, svn_checksum_size(checksum)); 2254289177Speter SVN_ERR(svn_checksum_final(&checksum, sha1_ctx, pool)); 2255289177Speter rep->has_sha1 = checksum != NULL; 2256289177Speter if (rep->has_sha1) 2257289177Speter memcpy(rep->sha1_digest, checksum->digest, svn_checksum_size(checksum)); 2258289177Speter 2259289177Speter return SVN_NO_ERROR; 2260289177Speter} 2261289177Speter 2262289177Speter/* Close handler for the representation write stream. BATON is a 2263289177Speter rep_write_baton. Writes out a new node-rev that correctly 2264289177Speter references the representation we just finished writing. */ 2265289177Speterstatic svn_error_t * 2266289177Speterrep_write_contents_close(void *baton) 2267289177Speter{ 2268289177Speter struct rep_write_baton *b = baton; 2269289177Speter representation_t *rep; 2270289177Speter representation_t *old_rep; 2271289177Speter apr_off_t offset; 2272289177Speter 2273289177Speter rep = apr_pcalloc(b->result_pool, sizeof(*rep)); 2274289177Speter 2275289177Speter /* Close our delta stream so the last bits of svndiff are written 2276289177Speter out. */ 2277289177Speter if (b->delta_stream) 2278289177Speter SVN_ERR(svn_stream_close(b->delta_stream)); 2279289177Speter 2280289177Speter /* Determine the length of the svndiff data. */ 2281289177Speter SVN_ERR(svn_fs_fs__get_file_offset(&offset, b->file, b->scratch_pool)); 2282289177Speter rep->size = offset - b->delta_start; 2283289177Speter 2284289177Speter /* Fill in the rest of the representation field. */ 2285289177Speter rep->expanded_size = b->rep_size; 2286289177Speter rep->txn_id = *svn_fs_fs__id_txn_id(b->noderev->id); 2287289177Speter SVN_ERR(set_uniquifier(b->fs, rep, b->scratch_pool)); 2288289177Speter rep->revision = SVN_INVALID_REVNUM; 2289289177Speter 2290289177Speter /* Finalize the checksum. */ 2291289177Speter SVN_ERR(digests_final(rep, b->md5_checksum_ctx, b->sha1_checksum_ctx, 2292289177Speter b->result_pool)); 2293289177Speter 2294289177Speter /* Check and see if we already have a representation somewhere that's 2295289177Speter identical to the one we just wrote out. */ 2296289177Speter SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->result_pool, 2297289177Speter b->scratch_pool)); 2298289177Speter 2299289177Speter if (old_rep) 2300289177Speter { 2301289177Speter /* We need to erase from the protorev the data we just wrote. */ 2302289177Speter SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->scratch_pool)); 2303289177Speter 2304289177Speter /* Use the old rep for this content. */ 2305289177Speter b->noderev->data_rep = old_rep; 2306289177Speter } 2307289177Speter else 2308289177Speter { 2309289177Speter /* Write out our cosmetic end marker. */ 2310289177Speter SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n")); 2311289177Speter SVN_ERR(allocate_item_index(&rep->item_index, b->fs, &rep->txn_id, 2312289177Speter b->rep_offset, b->scratch_pool)); 2313289177Speter 2314289177Speter b->noderev->data_rep = rep; 2315289177Speter } 2316289177Speter 2317289177Speter /* Remove cleanup callback. */ 2318289177Speter apr_pool_cleanup_kill(b->scratch_pool, b, rep_write_cleanup); 2319289177Speter 2320289177Speter /* Write out the new node-rev information. */ 2321289177Speter SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev, 2322289177Speter FALSE, b->scratch_pool)); 2323289177Speter if (!old_rep) 2324289177Speter { 2325289177Speter svn_fs_fs__p2l_entry_t entry; 2326289177Speter 2327289177Speter entry.offset = b->rep_offset; 2328289177Speter SVN_ERR(svn_fs_fs__get_file_offset(&offset, b->file, b->scratch_pool)); 2329289177Speter entry.size = offset - b->rep_offset; 2330289177Speter entry.type = SVN_FS_FS__ITEM_TYPE_FILE_REP; 2331289177Speter entry.item.revision = SVN_INVALID_REVNUM; 2332289177Speter entry.item.number = rep->item_index; 2333289177Speter SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum, 2334289177Speter b->fnv1a_checksum_ctx, 2335289177Speter b->scratch_pool)); 2336289177Speter 2337289177Speter SVN_ERR(store_p2l_index_entry(b->fs, &rep->txn_id, &entry, 2338289177Speter b->scratch_pool)); 2339289177Speter } 2340289177Speter 2341289177Speter SVN_ERR(svn_io_file_close(b->file, b->scratch_pool)); 2342309512Speter 2343309512Speter /* Write the sha1->rep mapping *after* we successfully written node 2344309512Speter * revision to disk. */ 2345309512Speter if (!old_rep) 2346309512Speter SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->scratch_pool)); 2347309512Speter 2348289177Speter SVN_ERR(unlock_proto_rev(b->fs, &rep->txn_id, b->lockcookie, 2349289177Speter b->scratch_pool)); 2350289177Speter svn_pool_destroy(b->scratch_pool); 2351289177Speter 2352289177Speter return SVN_NO_ERROR; 2353289177Speter} 2354289177Speter 2355289177Speter/* Store a writable stream in *CONTENTS_P that will receive all data 2356289177Speter written and store it as the file data representation referenced by 2357289177Speter NODEREV in filesystem FS. Perform temporary allocations in 2358289177Speter POOL. Only appropriate for file data, not props or directory 2359289177Speter contents. */ 2360289177Speterstatic svn_error_t * 2361289177Speterset_representation(svn_stream_t **contents_p, 2362289177Speter svn_fs_t *fs, 2363289177Speter node_revision_t *noderev, 2364289177Speter apr_pool_t *pool) 2365289177Speter{ 2366289177Speter struct rep_write_baton *wb; 2367289177Speter 2368289177Speter if (! svn_fs_fs__id_is_txn(noderev->id)) 2369289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2370289177Speter _("Attempted to write to non-transaction '%s'"), 2371289177Speter svn_fs_fs__id_unparse(noderev->id, pool)->data); 2372289177Speter 2373289177Speter SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool)); 2374289177Speter 2375289177Speter *contents_p = svn_stream_create(wb, pool); 2376289177Speter svn_stream_set_write(*contents_p, rep_write_contents); 2377289177Speter svn_stream_set_close(*contents_p, rep_write_contents_close); 2378289177Speter 2379289177Speter return SVN_NO_ERROR; 2380289177Speter} 2381289177Speter 2382289177Spetersvn_error_t * 2383289177Spetersvn_fs_fs__set_contents(svn_stream_t **stream, 2384289177Speter svn_fs_t *fs, 2385289177Speter node_revision_t *noderev, 2386289177Speter apr_pool_t *pool) 2387289177Speter{ 2388289177Speter if (noderev->kind != svn_node_file) 2389289177Speter return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL, 2390289177Speter _("Can't set text contents of a directory")); 2391289177Speter 2392289177Speter return set_representation(stream, fs, noderev, pool); 2393289177Speter} 2394289177Speter 2395289177Spetersvn_error_t * 2396289177Spetersvn_fs_fs__create_successor(const svn_fs_id_t **new_id_p, 2397289177Speter svn_fs_t *fs, 2398289177Speter const svn_fs_id_t *old_idp, 2399289177Speter node_revision_t *new_noderev, 2400289177Speter const svn_fs_fs__id_part_t *copy_id, 2401289177Speter const svn_fs_fs__id_part_t *txn_id, 2402289177Speter apr_pool_t *pool) 2403289177Speter{ 2404289177Speter const svn_fs_id_t *id; 2405289177Speter 2406289177Speter if (! copy_id) 2407289177Speter copy_id = svn_fs_fs__id_copy_id(old_idp); 2408289177Speter id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id, 2409289177Speter txn_id, pool); 2410289177Speter 2411289177Speter new_noderev->id = id; 2412289177Speter 2413289177Speter if (! new_noderev->copyroot_path) 2414289177Speter { 2415289177Speter new_noderev->copyroot_path = apr_pstrdup(pool, 2416289177Speter new_noderev->created_path); 2417289177Speter new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id); 2418289177Speter } 2419289177Speter 2420289177Speter SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE, 2421289177Speter pool)); 2422289177Speter 2423289177Speter *new_id_p = id; 2424289177Speter 2425289177Speter return SVN_NO_ERROR; 2426289177Speter} 2427289177Speter 2428289177Spetersvn_error_t * 2429289177Spetersvn_fs_fs__set_proplist(svn_fs_t *fs, 2430289177Speter node_revision_t *noderev, 2431289177Speter apr_hash_t *proplist, 2432289177Speter apr_pool_t *pool) 2433289177Speter{ 2434289177Speter const char *filename 2435289177Speter = svn_fs_fs__path_txn_node_props(fs, noderev->id, pool); 2436289177Speter apr_file_t *file; 2437289177Speter svn_stream_t *out; 2438289177Speter 2439289177Speter /* Dump the property list to the mutable property file. */ 2440289177Speter SVN_ERR(svn_io_file_open(&file, filename, 2441289177Speter APR_WRITE | APR_CREATE | APR_TRUNCATE 2442289177Speter | APR_BUFFERED, APR_OS_DEFAULT, pool)); 2443289177Speter out = svn_stream_from_aprfile2(file, TRUE, pool); 2444289177Speter SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool)); 2445289177Speter SVN_ERR(svn_io_file_close(file, pool)); 2446289177Speter 2447289177Speter /* Mark the node-rev's prop rep as mutable, if not already done. */ 2448289177Speter if (!noderev->prop_rep || !is_txn_rep(noderev->prop_rep)) 2449289177Speter { 2450289177Speter noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep)); 2451289177Speter noderev->prop_rep->txn_id = *svn_fs_fs__id_txn_id(noderev->id); 2452289177Speter SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, 2453289177Speter pool)); 2454289177Speter } 2455289177Speter 2456289177Speter return SVN_NO_ERROR; 2457289177Speter} 2458289177Speter 2459289177Speter/* This baton is used by the stream created for write_container_rep. */ 2460289177Speterstruct write_container_baton 2461289177Speter{ 2462289177Speter svn_stream_t *stream; 2463289177Speter 2464289177Speter apr_size_t size; 2465289177Speter 2466289177Speter svn_checksum_ctx_t *md5_ctx; 2467289177Speter svn_checksum_ctx_t *sha1_ctx; 2468289177Speter}; 2469289177Speter 2470289177Speter/* The handler for the write_container_rep stream. BATON is a 2471289177Speter write_container_baton, DATA has the data to write and *LEN is the number 2472289177Speter of bytes to write. */ 2473289177Speterstatic svn_error_t * 2474289177Speterwrite_container_handler(void *baton, 2475289177Speter const char *data, 2476289177Speter apr_size_t *len) 2477289177Speter{ 2478289177Speter struct write_container_baton *whb = baton; 2479289177Speter 2480289177Speter SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len)); 2481289177Speter SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len)); 2482289177Speter 2483289177Speter SVN_ERR(svn_stream_write(whb->stream, data, len)); 2484289177Speter whb->size += *len; 2485289177Speter 2486289177Speter return SVN_NO_ERROR; 2487289177Speter} 2488289177Speter 2489289177Speter/* Callback function type. Write the data provided by BATON into STREAM. */ 2490289177Spetertypedef svn_error_t * 2491289177Speter(* collection_writer_t)(svn_stream_t *stream, void *baton, apr_pool_t *pool); 2492289177Speter 2493289177Speter/* Implement collection_writer_t writing the C string->svn_string_t hash 2494289177Speter given as BATON. */ 2495289177Speterstatic svn_error_t * 2496289177Speterwrite_hash_to_stream(svn_stream_t *stream, 2497289177Speter void *baton, 2498289177Speter apr_pool_t *pool) 2499289177Speter{ 2500289177Speter apr_hash_t *hash = baton; 2501289177Speter SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool)); 2502289177Speter 2503289177Speter return SVN_NO_ERROR; 2504289177Speter} 2505289177Speter 2506289177Speter/* Implement collection_writer_t writing the svn_fs_dirent_t* array given 2507289177Speter as BATON. */ 2508289177Speterstatic svn_error_t * 2509289177Speterwrite_directory_to_stream(svn_stream_t *stream, 2510289177Speter void *baton, 2511289177Speter apr_pool_t *pool) 2512289177Speter{ 2513289177Speter apr_array_header_t *dir = baton; 2514289177Speter SVN_ERR(unparse_dir_entries(dir, stream, pool)); 2515289177Speter 2516289177Speter return SVN_NO_ERROR; 2517289177Speter} 2518289177Speter 2519289177Speter/* Write out the COLLECTION as a text representation to file FILE using 2520289177Speter WRITER. In the process, record position, the total size of the dump and 2521289177Speter MD5 as well as SHA1 in REP. Add the representation of type ITEM_TYPE to 2522289177Speter the indexes if necessary. If rep sharing has been enabled and REPS_HASH 2523289177Speter is not NULL, it will be used in addition to the on-disk cache to find 2524289177Speter earlier reps with the same content. When such existing reps can be 2525289177Speter found, we will truncate the one just written from the file and return 2526289177Speter the existing rep. Perform temporary allocations in SCRATCH_POOL. */ 2527289177Speterstatic svn_error_t * 2528289177Speterwrite_container_rep(representation_t *rep, 2529289177Speter apr_file_t *file, 2530289177Speter void *collection, 2531289177Speter collection_writer_t writer, 2532289177Speter svn_fs_t *fs, 2533289177Speter apr_hash_t *reps_hash, 2534289177Speter apr_uint32_t item_type, 2535289177Speter apr_pool_t *scratch_pool) 2536289177Speter{ 2537289177Speter svn_stream_t *stream; 2538289177Speter struct write_container_baton *whb; 2539289177Speter svn_checksum_ctx_t *fnv1a_checksum_ctx; 2540289177Speter representation_t *old_rep; 2541289177Speter apr_off_t offset = 0; 2542289177Speter 2543289177Speter SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool)); 2544289177Speter 2545289177Speter whb = apr_pcalloc(scratch_pool, sizeof(*whb)); 2546289177Speter 2547289177Speter whb->stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx, 2548289177Speter svn_stream_from_aprfile2(file, TRUE, 2549289177Speter scratch_pool), 2550289177Speter scratch_pool); 2551289177Speter whb->size = 0; 2552289177Speter whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool); 2553289177Speter whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool); 2554289177Speter 2555289177Speter stream = svn_stream_create(whb, scratch_pool); 2556289177Speter svn_stream_set_write(stream, write_container_handler); 2557289177Speter 2558289177Speter SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n")); 2559289177Speter 2560289177Speter SVN_ERR(writer(stream, collection, scratch_pool)); 2561289177Speter 2562289177Speter /* Store the results. */ 2563289177Speter SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool)); 2564289177Speter 2565289177Speter /* Check and see if we already have a representation somewhere that's 2566289177Speter identical to the one we just wrote out. */ 2567289177Speter SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, scratch_pool, 2568289177Speter scratch_pool)); 2569289177Speter 2570289177Speter if (old_rep) 2571289177Speter { 2572289177Speter /* We need to erase from the protorev the data we just wrote. */ 2573289177Speter SVN_ERR(svn_io_file_trunc(file, offset, scratch_pool)); 2574289177Speter 2575289177Speter /* Use the old rep for this content. */ 2576289177Speter memcpy(rep, old_rep, sizeof (*rep)); 2577289177Speter } 2578289177Speter else 2579289177Speter { 2580289177Speter svn_fs_fs__p2l_entry_t entry; 2581289177Speter 2582289177Speter /* Write out our cosmetic end marker. */ 2583289177Speter SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n")); 2584289177Speter 2585289177Speter SVN_ERR(allocate_item_index(&rep->item_index, fs, &rep->txn_id, 2586289177Speter offset, scratch_pool)); 2587289177Speter 2588289177Speter entry.offset = offset; 2589289177Speter SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool)); 2590289177Speter entry.size = offset - entry.offset; 2591289177Speter entry.type = item_type; 2592289177Speter entry.item.revision = SVN_INVALID_REVNUM; 2593289177Speter entry.item.number = rep->item_index; 2594289177Speter SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum, 2595289177Speter fnv1a_checksum_ctx, 2596289177Speter scratch_pool)); 2597289177Speter 2598289177Speter SVN_ERR(store_p2l_index_entry(fs, &rep->txn_id, &entry, scratch_pool)); 2599289177Speter 2600289177Speter /* update the representation */ 2601289177Speter rep->size = whb->size; 2602289177Speter rep->expanded_size = whb->size; 2603289177Speter } 2604289177Speter 2605289177Speter return SVN_NO_ERROR; 2606289177Speter} 2607289177Speter 2608289177Speter/* Write out the COLLECTION pertaining to the NODEREV in FS as a deltified 2609289177Speter text representation to file FILE using WRITER. In the process, record the 2610289177Speter total size and the md5 digest in REP and add the representation of type 2611289177Speter ITEM_TYPE to the indexes if necessary. If rep sharing has been enabled and 2612289177Speter REPS_HASH is not NULL, it will be used in addition to the on-disk cache to 2613289177Speter find earlier reps with the same content. When such existing reps can be 2614289177Speter found, we will truncate the one just written from the file and return the 2615289177Speter existing rep. 2616289177Speter 2617289177Speter If ITEM_TYPE is IS_PROPS equals SVN_FS_FS__ITEM_TYPE_*_PROPS, assume 2618289177Speter that we want to a props representation as the base for our delta. 2619289177Speter Perform temporary allocations in SCRATCH_POOL. 2620289177Speter */ 2621289177Speterstatic svn_error_t * 2622289177Speterwrite_container_delta_rep(representation_t *rep, 2623289177Speter apr_file_t *file, 2624289177Speter void *collection, 2625289177Speter collection_writer_t writer, 2626289177Speter svn_fs_t *fs, 2627289177Speter node_revision_t *noderev, 2628289177Speter apr_hash_t *reps_hash, 2629289177Speter apr_uint32_t item_type, 2630289177Speter apr_pool_t *scratch_pool) 2631289177Speter{ 2632289177Speter svn_txdelta_window_handler_t diff_wh; 2633289177Speter void *diff_whb; 2634289177Speter 2635289177Speter svn_stream_t *file_stream; 2636289177Speter svn_stream_t *stream; 2637289177Speter representation_t *base_rep; 2638289177Speter representation_t *old_rep; 2639289177Speter svn_checksum_ctx_t *fnv1a_checksum_ctx; 2640289177Speter svn_stream_t *source; 2641289177Speter svn_fs_fs__rep_header_t header = { 0 }; 2642289177Speter 2643289177Speter apr_off_t rep_end = 0; 2644289177Speter apr_off_t delta_start = 0; 2645289177Speter apr_off_t offset = 0; 2646289177Speter 2647289177Speter struct write_container_baton *whb; 2648289177Speter fs_fs_data_t *ffd = fs->fsap_data; 2649289177Speter int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0; 2650289177Speter svn_boolean_t is_props = (item_type == SVN_FS_FS__ITEM_TYPE_FILE_PROPS) 2651289177Speter || (item_type == SVN_FS_FS__ITEM_TYPE_DIR_PROPS); 2652289177Speter 2653289177Speter /* Get the base for this delta. */ 2654289177Speter SVN_ERR(choose_delta_base(&base_rep, fs, noderev, is_props, scratch_pool)); 2655289177Speter SVN_ERR(svn_fs_fs__get_contents(&source, fs, base_rep, FALSE, scratch_pool)); 2656289177Speter 2657289177Speter SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool)); 2658289177Speter 2659289177Speter /* Write out the rep header. */ 2660289177Speter if (base_rep) 2661289177Speter { 2662289177Speter header.base_revision = base_rep->revision; 2663289177Speter header.base_item_index = base_rep->item_index; 2664289177Speter header.base_length = base_rep->size; 2665289177Speter header.type = svn_fs_fs__rep_delta; 2666289177Speter } 2667289177Speter else 2668289177Speter { 2669289177Speter header.type = svn_fs_fs__rep_self_delta; 2670289177Speter } 2671289177Speter 2672289177Speter file_stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx, 2673289177Speter svn_stream_from_aprfile2(file, TRUE, 2674289177Speter scratch_pool), 2675289177Speter scratch_pool); 2676289177Speter SVN_ERR(svn_fs_fs__write_rep_header(&header, file_stream, scratch_pool)); 2677289177Speter SVN_ERR(svn_fs_fs__get_file_offset(&delta_start, file, scratch_pool)); 2678289177Speter 2679289177Speter /* Prepare to write the svndiff data. */ 2680289177Speter svn_txdelta_to_svndiff3(&diff_wh, 2681289177Speter &diff_whb, 2682289177Speter file_stream, 2683289177Speter diff_version, 2684289177Speter ffd->delta_compression_level, 2685289177Speter scratch_pool); 2686289177Speter 2687289177Speter whb = apr_pcalloc(scratch_pool, sizeof(*whb)); 2688289177Speter whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source, 2689289177Speter scratch_pool); 2690289177Speter whb->size = 0; 2691289177Speter whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool); 2692289177Speter whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool); 2693289177Speter 2694289177Speter /* serialize the hash */ 2695289177Speter stream = svn_stream_create(whb, scratch_pool); 2696289177Speter svn_stream_set_write(stream, write_container_handler); 2697289177Speter 2698289177Speter SVN_ERR(writer(stream, collection, scratch_pool)); 2699289177Speter SVN_ERR(svn_stream_close(whb->stream)); 2700289177Speter 2701289177Speter /* Store the results. */ 2702289177Speter SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool)); 2703289177Speter 2704289177Speter /* Check and see if we already have a representation somewhere that's 2705289177Speter identical to the one we just wrote out. */ 2706289177Speter SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, scratch_pool, 2707289177Speter scratch_pool)); 2708289177Speter 2709289177Speter if (old_rep) 2710289177Speter { 2711289177Speter /* We need to erase from the protorev the data we just wrote. */ 2712289177Speter SVN_ERR(svn_io_file_trunc(file, offset, scratch_pool)); 2713289177Speter 2714289177Speter /* Use the old rep for this content. */ 2715289177Speter memcpy(rep, old_rep, sizeof (*rep)); 2716289177Speter } 2717289177Speter else 2718289177Speter { 2719289177Speter svn_fs_fs__p2l_entry_t entry; 2720289177Speter 2721289177Speter /* Write out our cosmetic end marker. */ 2722289177Speter SVN_ERR(svn_fs_fs__get_file_offset(&rep_end, file, scratch_pool)); 2723289177Speter SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n")); 2724289177Speter 2725289177Speter SVN_ERR(allocate_item_index(&rep->item_index, fs, &rep->txn_id, 2726289177Speter offset, scratch_pool)); 2727289177Speter 2728289177Speter entry.offset = offset; 2729289177Speter SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool)); 2730289177Speter entry.size = offset - entry.offset; 2731289177Speter entry.type = item_type; 2732289177Speter entry.item.revision = SVN_INVALID_REVNUM; 2733289177Speter entry.item.number = rep->item_index; 2734289177Speter SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum, 2735289177Speter fnv1a_checksum_ctx, 2736289177Speter scratch_pool)); 2737289177Speter 2738289177Speter SVN_ERR(store_p2l_index_entry(fs, &rep->txn_id, &entry, scratch_pool)); 2739289177Speter 2740289177Speter /* update the representation */ 2741289177Speter rep->expanded_size = whb->size; 2742289177Speter rep->size = rep_end - delta_start; 2743289177Speter } 2744289177Speter 2745289177Speter return SVN_NO_ERROR; 2746289177Speter} 2747289177Speter 2748289177Speter/* Sanity check ROOT_NODEREV, a candidate for being the root node-revision 2749289177Speter of (not yet committed) revision REV in FS. Use POOL for temporary 2750289177Speter allocations. 2751289177Speter 2752289177Speter If you change this function, consider updating svn_fs_fs__verify() too. 2753289177Speter */ 2754289177Speterstatic svn_error_t * 2755289177Spetervalidate_root_noderev(svn_fs_t *fs, 2756289177Speter node_revision_t *root_noderev, 2757289177Speter svn_revnum_t rev, 2758289177Speter apr_pool_t *pool) 2759289177Speter{ 2760289177Speter svn_revnum_t head_revnum = rev-1; 2761289177Speter int head_predecessor_count; 2762289177Speter 2763289177Speter SVN_ERR_ASSERT(rev > 0); 2764289177Speter 2765289177Speter /* Compute HEAD_PREDECESSOR_COUNT. */ 2766289177Speter { 2767289177Speter svn_fs_root_t *head_revision; 2768289177Speter const svn_fs_id_t *head_root_id; 2769289177Speter node_revision_t *head_root_noderev; 2770289177Speter 2771289177Speter /* Get /@HEAD's noderev. */ 2772289177Speter SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool)); 2773289177Speter SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool)); 2774289177Speter SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id, 2775289177Speter pool, pool)); 2776289177Speter head_predecessor_count = head_root_noderev->predecessor_count; 2777289177Speter } 2778289177Speter 2779289177Speter /* Check that the root noderev's predecessor count equals REV. 2780289177Speter 2781289177Speter This kind of corruption was seen on svn.apache.org (both on 2782289177Speter the root noderev and on other fspaths' noderevs); see 2783289177Speter issue #4129. 2784289177Speter 2785289177Speter Normally (rev == root_noderev->predecessor_count), but here we 2786289177Speter use a more roundabout check that should only trigger on new instances 2787289177Speter of the corruption, rather then trigger on each and every new commit 2788289177Speter to a repository that has triggered the bug somewhere in its root 2789289177Speter noderev's history. 2790289177Speter */ 2791289177Speter if (root_noderev->predecessor_count != -1 2792289177Speter && (root_noderev->predecessor_count - head_predecessor_count) 2793289177Speter != (rev - head_revnum)) 2794289177Speter { 2795289177Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2796289177Speter _("predecessor count for " 2797289177Speter "the root node-revision is wrong: " 2798289177Speter "found (%d+%ld != %d), committing r%ld"), 2799289177Speter head_predecessor_count, 2800289177Speter rev - head_revnum, /* This is equal to 1. */ 2801289177Speter root_noderev->predecessor_count, 2802289177Speter rev); 2803289177Speter } 2804289177Speter 2805289177Speter return SVN_NO_ERROR; 2806289177Speter} 2807289177Speter 2808289177Speter/* Given the potentially txn-local id PART, update that to a permanent ID 2809289177Speter * based on the REVISION currently being written and the START_ID for that 2810289177Speter * revision. Use the repo FORMAT to decide which implementation to use. 2811289177Speter */ 2812289177Speterstatic void 2813289177Speterget_final_id(svn_fs_fs__id_part_t *part, 2814289177Speter svn_revnum_t revision, 2815289177Speter apr_uint64_t start_id, 2816289177Speter int format) 2817289177Speter{ 2818289177Speter if (part->revision == SVN_INVALID_REVNUM) 2819289177Speter { 2820289177Speter if (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 2821289177Speter { 2822289177Speter part->revision = revision; 2823289177Speter } 2824289177Speter else 2825289177Speter { 2826289177Speter part->revision = 0; 2827289177Speter part->number += start_id; 2828289177Speter } 2829289177Speter } 2830289177Speter} 2831289177Speter 2832289177Speter/* Copy a node-revision specified by id ID in fileystem FS from a 2833289177Speter transaction into the proto-rev-file FILE. Set *NEW_ID_P to a 2834289177Speter pointer to the new node-id which will be allocated in POOL. 2835289177Speter If this is a directory, copy all children as well. 2836289177Speter 2837289177Speter START_NODE_ID and START_COPY_ID are 2838289177Speter the first available node and copy ids for this filesystem, for older 2839289177Speter FS formats. 2840289177Speter 2841289177Speter REV is the revision number that this proto-rev-file will represent. 2842289177Speter 2843289177Speter INITIAL_OFFSET is the offset of the proto-rev-file on entry to 2844289177Speter commit_body. 2845289177Speter 2846289177Speter If REPS_TO_CACHE is not NULL, append to it a copy (allocated in 2847289177Speter REPS_POOL) of each data rep that is new in this revision. 2848289177Speter 2849289177Speter If REPS_HASH is not NULL, append copies (allocated in REPS_POOL) 2850289177Speter of the representations of each property rep that is new in this 2851289177Speter revision. 2852289177Speter 2853289177Speter AT_ROOT is true if the node revision being written is the root 2854289177Speter node-revision. It is only controls additional sanity checking 2855289177Speter logic. 2856289177Speter 2857289177Speter Temporary allocations are also from POOL. */ 2858289177Speterstatic svn_error_t * 2859289177Speterwrite_final_rev(const svn_fs_id_t **new_id_p, 2860289177Speter apr_file_t *file, 2861289177Speter svn_revnum_t rev, 2862289177Speter svn_fs_t *fs, 2863289177Speter const svn_fs_id_t *id, 2864289177Speter apr_uint64_t start_node_id, 2865289177Speter apr_uint64_t start_copy_id, 2866289177Speter apr_off_t initial_offset, 2867289177Speter apr_array_header_t *reps_to_cache, 2868289177Speter apr_hash_t *reps_hash, 2869289177Speter apr_pool_t *reps_pool, 2870289177Speter svn_boolean_t at_root, 2871289177Speter apr_pool_t *pool) 2872289177Speter{ 2873289177Speter node_revision_t *noderev; 2874289177Speter apr_off_t my_offset; 2875289177Speter const svn_fs_id_t *new_id; 2876289177Speter svn_fs_fs__id_part_t node_id, copy_id, rev_item; 2877289177Speter fs_fs_data_t *ffd = fs->fsap_data; 2878289177Speter const svn_fs_fs__id_part_t *txn_id = svn_fs_fs__id_txn_id(id); 2879289177Speter svn_stream_t *file_stream; 2880289177Speter svn_checksum_ctx_t *fnv1a_checksum_ctx; 2881289177Speter apr_pool_t *subpool; 2882289177Speter 2883289177Speter *new_id_p = NULL; 2884289177Speter 2885289177Speter /* Check to see if this is a transaction node. */ 2886289177Speter if (! svn_fs_fs__id_is_txn(id)) 2887289177Speter return SVN_NO_ERROR; 2888289177Speter 2889289177Speter subpool = svn_pool_create(pool); 2890289177Speter SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool, subpool)); 2891289177Speter 2892289177Speter if (noderev->kind == svn_node_dir) 2893289177Speter { 2894289177Speter apr_array_header_t *entries; 2895289177Speter int i; 2896289177Speter 2897289177Speter /* This is a directory. Write out all the children first. */ 2898289177Speter 2899289177Speter SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool, 2900289177Speter subpool)); 2901289177Speter for (i = 0; i < entries->nelts; ++i) 2902289177Speter { 2903289177Speter svn_fs_dirent_t *dirent 2904289177Speter = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *); 2905289177Speter 2906289177Speter svn_pool_clear(subpool); 2907289177Speter SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id, 2908289177Speter start_node_id, start_copy_id, initial_offset, 2909289177Speter reps_to_cache, reps_hash, reps_pool, FALSE, 2910289177Speter subpool)); 2911289177Speter if (new_id && (svn_fs_fs__id_rev(new_id) == rev)) 2912289177Speter dirent->id = svn_fs_fs__id_copy(new_id, pool); 2913289177Speter } 2914289177Speter 2915289177Speter if (noderev->data_rep && is_txn_rep(noderev->data_rep)) 2916289177Speter { 2917289177Speter /* Write out the contents of this directory as a text rep. */ 2918289177Speter noderev->data_rep->revision = rev; 2919289177Speter if (ffd->deltify_directories) 2920289177Speter SVN_ERR(write_container_delta_rep(noderev->data_rep, file, 2921289177Speter entries, 2922289177Speter write_directory_to_stream, 2923289177Speter fs, noderev, NULL, 2924289177Speter SVN_FS_FS__ITEM_TYPE_DIR_REP, 2925289177Speter pool)); 2926289177Speter else 2927289177Speter SVN_ERR(write_container_rep(noderev->data_rep, file, entries, 2928289177Speter write_directory_to_stream, fs, NULL, 2929289177Speter SVN_FS_FS__ITEM_TYPE_DIR_REP, pool)); 2930289177Speter 2931289177Speter reset_txn_in_rep(noderev->data_rep); 2932289177Speter } 2933289177Speter } 2934289177Speter else 2935289177Speter { 2936289177Speter /* This is a file. We should make sure the data rep, if it 2937289177Speter exists in a "this" state, gets rewritten to our new revision 2938289177Speter num. */ 2939289177Speter 2940289177Speter if (noderev->data_rep && is_txn_rep(noderev->data_rep)) 2941289177Speter { 2942289177Speter reset_txn_in_rep(noderev->data_rep); 2943289177Speter noderev->data_rep->revision = rev; 2944289177Speter 2945289177Speter if (!svn_fs_fs__use_log_addressing(fs)) 2946289177Speter { 2947289177Speter /* See issue 3845. Some unknown mechanism caused the 2948289177Speter protorev file to get truncated, so check for that 2949289177Speter here. */ 2950289177Speter if (noderev->data_rep->item_index + noderev->data_rep->size 2951289177Speter > initial_offset) 2952289177Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 2953289177Speter _("Truncated protorev file detected")); 2954289177Speter } 2955289177Speter } 2956289177Speter } 2957289177Speter 2958289177Speter svn_pool_destroy(subpool); 2959289177Speter 2960289177Speter /* Fix up the property reps. */ 2961289177Speter if (noderev->prop_rep && is_txn_rep(noderev->prop_rep)) 2962289177Speter { 2963289177Speter apr_hash_t *proplist; 2964289177Speter apr_uint32_t item_type = noderev->kind == svn_node_dir 2965289177Speter ? SVN_FS_FS__ITEM_TYPE_DIR_PROPS 2966289177Speter : SVN_FS_FS__ITEM_TYPE_FILE_PROPS; 2967289177Speter SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool)); 2968289177Speter 2969289177Speter noderev->prop_rep->revision = rev; 2970289177Speter 2971289177Speter if (ffd->deltify_properties) 2972289177Speter SVN_ERR(write_container_delta_rep(noderev->prop_rep, file, proplist, 2973289177Speter write_hash_to_stream, fs, noderev, 2974289177Speter reps_hash, item_type, pool)); 2975289177Speter else 2976289177Speter SVN_ERR(write_container_rep(noderev->prop_rep, file, proplist, 2977289177Speter write_hash_to_stream, fs, reps_hash, 2978289177Speter item_type, pool)); 2979289177Speter 2980289177Speter reset_txn_in_rep(noderev->prop_rep); 2981289177Speter } 2982289177Speter 2983289177Speter /* Convert our temporary ID into a permanent revision one. */ 2984289177Speter node_id = *svn_fs_fs__id_node_id(noderev->id); 2985289177Speter get_final_id(&node_id, rev, start_node_id, ffd->format); 2986289177Speter copy_id = *svn_fs_fs__id_copy_id(noderev->id); 2987289177Speter get_final_id(©_id, rev, start_copy_id, ffd->format); 2988289177Speter 2989289177Speter if (noderev->copyroot_rev == SVN_INVALID_REVNUM) 2990289177Speter noderev->copyroot_rev = rev; 2991289177Speter 2992289177Speter /* root nodes have a fixed ID in log addressing mode */ 2993289177Speter SVN_ERR(svn_fs_fs__get_file_offset(&my_offset, file, pool)); 2994289177Speter if (svn_fs_fs__use_log_addressing(fs) && at_root) 2995289177Speter { 2996289177Speter /* reference the root noderev from the log-to-phys index */ 2997289177Speter rev_item.number = SVN_FS_FS__ITEM_INDEX_ROOT_NODE; 2998289177Speter SVN_ERR(store_l2p_index_entry(fs, txn_id, my_offset, 2999289177Speter rev_item.number, pool)); 3000289177Speter } 3001289177Speter else 3002289177Speter SVN_ERR(allocate_item_index(&rev_item.number, fs, txn_id, 3003289177Speter my_offset, pool)); 3004289177Speter 3005289177Speter rev_item.revision = rev; 3006289177Speter new_id = svn_fs_fs__id_rev_create(&node_id, ©_id, &rev_item, pool); 3007289177Speter 3008289177Speter noderev->id = new_id; 3009289177Speter 3010289177Speter if (ffd->rep_sharing_allowed) 3011289177Speter { 3012289177Speter /* Save the data representation's hash in the rep cache. */ 3013289177Speter if ( noderev->data_rep && noderev->kind == svn_node_file 3014289177Speter && noderev->data_rep->revision == rev) 3015289177Speter { 3016289177Speter SVN_ERR_ASSERT(reps_to_cache && reps_pool); 3017289177Speter APR_ARRAY_PUSH(reps_to_cache, representation_t *) 3018289177Speter = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool); 3019289177Speter } 3020289177Speter 3021289177Speter if (noderev->prop_rep && noderev->prop_rep->revision == rev) 3022289177Speter { 3023289177Speter /* Add new property reps to hash and on-disk cache. */ 3024289177Speter representation_t *copy 3025289177Speter = svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool); 3026289177Speter 3027289177Speter SVN_ERR_ASSERT(reps_to_cache && reps_pool); 3028289177Speter APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy; 3029289177Speter 3030289177Speter apr_hash_set(reps_hash, 3031289177Speter copy->sha1_digest, 3032289177Speter APR_SHA1_DIGESTSIZE, 3033289177Speter copy); 3034289177Speter } 3035289177Speter } 3036289177Speter 3037289177Speter /* don't serialize SHA1 for dirs to disk (waste of space) */ 3038289177Speter if (noderev->data_rep && noderev->kind == svn_node_dir) 3039289177Speter noderev->data_rep->has_sha1 = FALSE; 3040289177Speter 3041289177Speter /* don't serialize SHA1 for props to disk (waste of space) */ 3042289177Speter if (noderev->prop_rep) 3043289177Speter noderev->prop_rep->has_sha1 = FALSE; 3044289177Speter 3045289177Speter /* Workaround issue #4031: is-fresh-txn-root in revision files. */ 3046289177Speter noderev->is_fresh_txn_root = FALSE; 3047289177Speter 3048289177Speter /* Write out our new node-revision. */ 3049289177Speter if (at_root) 3050289177Speter SVN_ERR(validate_root_noderev(fs, noderev, rev, pool)); 3051289177Speter 3052289177Speter file_stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx, 3053289177Speter svn_stream_from_aprfile2(file, TRUE, pool), 3054289177Speter pool); 3055289177Speter SVN_ERR(svn_fs_fs__write_noderev(file_stream, noderev, ffd->format, 3056289177Speter svn_fs_fs__fs_supports_mergeinfo(fs), 3057289177Speter pool)); 3058289177Speter 3059289177Speter /* reference the root noderev from the log-to-phys index */ 3060289177Speter if (svn_fs_fs__use_log_addressing(fs)) 3061289177Speter { 3062289177Speter svn_fs_fs__p2l_entry_t entry; 3063289177Speter rev_item.revision = SVN_INVALID_REVNUM; 3064289177Speter 3065289177Speter entry.offset = my_offset; 3066289177Speter SVN_ERR(svn_fs_fs__get_file_offset(&my_offset, file, pool)); 3067289177Speter entry.size = my_offset - entry.offset; 3068289177Speter entry.type = SVN_FS_FS__ITEM_TYPE_NODEREV; 3069289177Speter entry.item = rev_item; 3070289177Speter SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum, 3071289177Speter fnv1a_checksum_ctx, 3072289177Speter pool)); 3073289177Speter 3074289177Speter SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, pool)); 3075289177Speter } 3076289177Speter 3077289177Speter /* Return our ID that references the revision file. */ 3078289177Speter *new_id_p = noderev->id; 3079289177Speter 3080289177Speter return SVN_NO_ERROR; 3081289177Speter} 3082289177Speter 3083289177Speter/* Write the changed path info CHANGED_PATHS from transaction TXN_ID to the 3084289177Speter permanent rev-file FILE in filesystem FS. *OFFSET_P is set the to offset 3085289177Speter in the file of the beginning of this information. Perform temporary 3086289177Speter allocations in POOL. */ 3087289177Speterstatic svn_error_t * 3088289177Speterwrite_final_changed_path_info(apr_off_t *offset_p, 3089289177Speter apr_file_t *file, 3090289177Speter svn_fs_t *fs, 3091289177Speter const svn_fs_fs__id_part_t *txn_id, 3092289177Speter apr_hash_t *changed_paths, 3093289177Speter apr_pool_t *pool) 3094289177Speter{ 3095289177Speter apr_off_t offset; 3096289177Speter svn_stream_t *stream; 3097289177Speter svn_checksum_ctx_t *fnv1a_checksum_ctx; 3098289177Speter 3099289177Speter SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool)); 3100289177Speter 3101289177Speter /* write to target file & calculate checksum */ 3102289177Speter stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx, 3103289177Speter svn_stream_from_aprfile2(file, TRUE, pool), 3104289177Speter pool); 3105289177Speter SVN_ERR(svn_fs_fs__write_changes(stream, fs, changed_paths, TRUE, pool)); 3106289177Speter 3107289177Speter *offset_p = offset; 3108289177Speter 3109289177Speter /* reference changes from the indexes */ 3110289177Speter if (svn_fs_fs__use_log_addressing(fs)) 3111289177Speter { 3112289177Speter svn_fs_fs__p2l_entry_t entry; 3113289177Speter 3114289177Speter entry.offset = offset; 3115289177Speter SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool)); 3116289177Speter entry.size = offset - entry.offset; 3117289177Speter entry.type = SVN_FS_FS__ITEM_TYPE_CHANGES; 3118289177Speter entry.item.revision = SVN_INVALID_REVNUM; 3119289177Speter entry.item.number = SVN_FS_FS__ITEM_INDEX_CHANGES; 3120289177Speter SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum, 3121289177Speter fnv1a_checksum_ctx, 3122289177Speter pool)); 3123289177Speter 3124289177Speter SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, pool)); 3125289177Speter SVN_ERR(store_l2p_index_entry(fs, txn_id, entry.offset, 3126289177Speter SVN_FS_FS__ITEM_INDEX_CHANGES, pool)); 3127289177Speter } 3128289177Speter 3129289177Speter return SVN_NO_ERROR; 3130289177Speter} 3131289177Speter 3132289177Speter/* Open a new svn_fs_t handle to FS, set that handle's concept of "current 3133289177Speter youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on 3134289177Speter NEW_REV's revision root. 3135289177Speter 3136289177Speter Intended to be called as the very last step in a commit before 'current' 3137289177Speter is bumped. This implies that we are holding the write lock. */ 3138289177Speterstatic svn_error_t * 3139289177Speterverify_as_revision_before_current_plus_plus(svn_fs_t *fs, 3140289177Speter svn_revnum_t new_rev, 3141289177Speter apr_pool_t *pool) 3142289177Speter{ 3143289177Speter#ifdef SVN_DEBUG 3144289177Speter fs_fs_data_t *ffd = fs->fsap_data; 3145289177Speter svn_fs_t *ft; /* fs++ == ft */ 3146289177Speter svn_fs_root_t *root; 3147289177Speter fs_fs_data_t *ft_ffd; 3148289177Speter apr_hash_t *fs_config; 3149289177Speter 3150289177Speter SVN_ERR_ASSERT(ffd->svn_fs_open_); 3151289177Speter 3152289177Speter /* make sure FT does not simply return data cached by other instances 3153289177Speter * but actually retrieves it from disk at least once. 3154289177Speter */ 3155289177Speter fs_config = apr_hash_make(pool); 3156289177Speter svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS, 3157289177Speter svn_uuid_generate(pool)); 3158289177Speter SVN_ERR(ffd->svn_fs_open_(&ft, fs->path, 3159289177Speter fs_config, 3160289177Speter pool, 3161289177Speter pool)); 3162289177Speter ft_ffd = ft->fsap_data; 3163289177Speter /* Don't let FT consult rep-cache.db, either. */ 3164289177Speter ft_ffd->rep_sharing_allowed = FALSE; 3165289177Speter 3166289177Speter /* Time travel! */ 3167289177Speter ft_ffd->youngest_rev_cache = new_rev; 3168289177Speter 3169289177Speter SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool)); 3170289177Speter SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev); 3171289177Speter SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev); 3172289177Speter SVN_ERR(svn_fs_fs__verify_root(root, pool)); 3173289177Speter#endif /* SVN_DEBUG */ 3174289177Speter 3175289177Speter return SVN_NO_ERROR; 3176289177Speter} 3177289177Speter 3178289177Speter/* Update the 'current' file to hold the correct next node and copy_ids 3179289177Speter from transaction TXN_ID in filesystem FS. The current revision is 3180289177Speter set to REV. Perform temporary allocations in POOL. */ 3181289177Speterstatic svn_error_t * 3182289177Speterwrite_final_current(svn_fs_t *fs, 3183289177Speter const svn_fs_fs__id_part_t *txn_id, 3184289177Speter svn_revnum_t rev, 3185289177Speter apr_uint64_t start_node_id, 3186289177Speter apr_uint64_t start_copy_id, 3187289177Speter apr_pool_t *pool) 3188289177Speter{ 3189289177Speter apr_uint64_t txn_node_id; 3190289177Speter apr_uint64_t txn_copy_id; 3191289177Speter fs_fs_data_t *ffd = fs->fsap_data; 3192289177Speter 3193289177Speter if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 3194289177Speter return svn_fs_fs__write_current(fs, rev, 0, 0, pool); 3195289177Speter 3196289177Speter /* To find the next available ids, we add the id that used to be in 3197289177Speter the 'current' file, to the next ids from the transaction file. */ 3198289177Speter SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool)); 3199289177Speter 3200289177Speter start_node_id += txn_node_id; 3201289177Speter start_copy_id += txn_copy_id; 3202289177Speter 3203289177Speter return svn_fs_fs__write_current(fs, rev, start_node_id, start_copy_id, 3204289177Speter pool); 3205289177Speter} 3206289177Speter 3207289177Speter/* Verify that the user registered with FS has all the locks necessary to 3208289177Speter permit all the changes associated with TXN_NAME. 3209289177Speter The FS write lock is assumed to be held by the caller. */ 3210289177Speterstatic svn_error_t * 3211289177Speterverify_locks(svn_fs_t *fs, 3212289177Speter const svn_fs_fs__id_part_t *txn_id, 3213289177Speter apr_hash_t *changed_paths, 3214289177Speter apr_pool_t *pool) 3215289177Speter{ 3216289177Speter apr_pool_t *iterpool; 3217289177Speter apr_array_header_t *changed_paths_sorted; 3218289177Speter svn_stringbuf_t *last_recursed = NULL; 3219289177Speter int i; 3220289177Speter 3221289177Speter /* Make an array of the changed paths, and sort them depth-first-ily. */ 3222289177Speter changed_paths_sorted = svn_sort__hash(changed_paths, 3223289177Speter svn_sort_compare_items_as_paths, 3224289177Speter pool); 3225289177Speter 3226289177Speter /* Now, traverse the array of changed paths, verify locks. Note 3227289177Speter that if we need to do a recursive verification a path, we'll skip 3228289177Speter over children of that path when we get to them. */ 3229289177Speter iterpool = svn_pool_create(pool); 3230289177Speter for (i = 0; i < changed_paths_sorted->nelts; i++) 3231289177Speter { 3232289177Speter const svn_sort__item_t *item; 3233289177Speter const char *path; 3234289177Speter svn_fs_path_change2_t *change; 3235289177Speter svn_boolean_t recurse = TRUE; 3236289177Speter 3237289177Speter svn_pool_clear(iterpool); 3238289177Speter 3239289177Speter item = &APR_ARRAY_IDX(changed_paths_sorted, i, svn_sort__item_t); 3240289177Speter 3241289177Speter /* Fetch the change associated with our path. */ 3242289177Speter path = item->key; 3243289177Speter change = item->value; 3244289177Speter 3245289177Speter /* If this path has already been verified as part of a recursive 3246289177Speter check of one of its parents, no need to do it again. */ 3247289177Speter if (last_recursed 3248289177Speter && svn_fspath__skip_ancestor(last_recursed->data, path)) 3249289177Speter continue; 3250289177Speter 3251289177Speter /* What does it mean to succeed at lock verification for a given 3252289177Speter path? For an existing file or directory getting modified 3253289177Speter (text, props), it means we hold the lock on the file or 3254289177Speter directory. For paths being added or removed, we need to hold 3255289177Speter the locks for that path and any children of that path. 3256289177Speter 3257289177Speter WHEW! We have no reliable way to determine the node kind 3258289177Speter of deleted items, but fortunately we are going to do a 3259289177Speter recursive check on deleted paths regardless of their kind. */ 3260289177Speter if (change->change_kind == svn_fs_path_change_modify) 3261289177Speter recurse = FALSE; 3262289177Speter SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE, 3263289177Speter iterpool)); 3264289177Speter 3265289177Speter /* If we just did a recursive check, remember the path we 3266289177Speter checked (so children can be skipped). */ 3267289177Speter if (recurse) 3268289177Speter { 3269289177Speter if (! last_recursed) 3270289177Speter last_recursed = svn_stringbuf_create(path, pool); 3271289177Speter else 3272289177Speter svn_stringbuf_set(last_recursed, path); 3273289177Speter } 3274289177Speter } 3275289177Speter svn_pool_destroy(iterpool); 3276289177Speter return SVN_NO_ERROR; 3277289177Speter} 3278289177Speter 3279289177Speter/* Return in *PATH the path to a file containing the properties that 3280289177Speter make up the final revision properties file. This involves setting 3281289177Speter svn:date and removing any temporary properties associated with the 3282289177Speter commit flags. */ 3283289177Speterstatic svn_error_t * 3284289177Speterwrite_final_revprop(const char **path, 3285289177Speter svn_fs_txn_t *txn, 3286289177Speter const svn_fs_fs__id_part_t *txn_id, 3287289177Speter apr_pool_t *pool) 3288289177Speter{ 3289289177Speter apr_hash_t *txnprops; 3290289177Speter svn_boolean_t final_mods = FALSE; 3291289177Speter svn_string_t date; 3292289177Speter svn_string_t *client_date; 3293289177Speter 3294289177Speter SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, txn, pool)); 3295289177Speter 3296289177Speter /* Remove any temporary txn props representing 'flags'. */ 3297289177Speter if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD)) 3298289177Speter { 3299289177Speter svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD, NULL); 3300289177Speter final_mods = TRUE; 3301289177Speter } 3302289177Speter 3303289177Speter if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS)) 3304289177Speter { 3305289177Speter svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL); 3306289177Speter final_mods = TRUE; 3307289177Speter } 3308289177Speter 3309289177Speter client_date = svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE); 3310289177Speter if (client_date) 3311289177Speter { 3312289177Speter svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE, NULL); 3313289177Speter final_mods = TRUE; 3314289177Speter } 3315289177Speter 3316289177Speter /* Update commit time to ensure that svn:date revprops remain ordered if 3317289177Speter requested. */ 3318289177Speter if (!client_date || strcmp(client_date->data, "1")) 3319289177Speter { 3320289177Speter date.data = svn_time_to_cstring(apr_time_now(), pool); 3321289177Speter date.len = strlen(date.data); 3322289177Speter svn_hash_sets(txnprops, SVN_PROP_REVISION_DATE, &date); 3323289177Speter final_mods = TRUE; 3324289177Speter } 3325289177Speter 3326289177Speter if (final_mods) 3327289177Speter { 3328289177Speter SVN_ERR(set_txn_proplist(txn->fs, txn_id, txnprops, TRUE, pool)); 3329289177Speter *path = path_txn_props_final(txn->fs, txn_id, pool); 3330289177Speter } 3331289177Speter else 3332289177Speter { 3333289177Speter *path = path_txn_props(txn->fs, txn_id, pool); 3334289177Speter } 3335289177Speter 3336289177Speter return SVN_NO_ERROR; 3337289177Speter} 3338289177Speter 3339289177Spetersvn_error_t * 3340289177Spetersvn_fs_fs__add_index_data(svn_fs_t *fs, 3341289177Speter apr_file_t *file, 3342289177Speter const char *l2p_proto_index, 3343289177Speter const char *p2l_proto_index, 3344289177Speter svn_revnum_t revision, 3345289177Speter apr_pool_t *pool) 3346289177Speter{ 3347289177Speter apr_off_t l2p_offset; 3348289177Speter apr_off_t p2l_offset; 3349289177Speter svn_stringbuf_t *footer; 3350289177Speter unsigned char footer_length; 3351289177Speter svn_checksum_t *l2p_checksum; 3352289177Speter svn_checksum_t *p2l_checksum; 3353289177Speter 3354289177Speter /* Append the actual index data to the pack file. */ 3355289177Speter l2p_offset = 0; 3356289177Speter SVN_ERR(svn_io_file_seek(file, APR_END, &l2p_offset, pool)); 3357289177Speter SVN_ERR(svn_fs_fs__l2p_index_append(&l2p_checksum, fs, file, 3358289177Speter l2p_proto_index, revision, 3359289177Speter pool, pool)); 3360289177Speter 3361289177Speter p2l_offset = 0; 3362289177Speter SVN_ERR(svn_io_file_seek(file, APR_END, &p2l_offset, pool)); 3363289177Speter SVN_ERR(svn_fs_fs__p2l_index_append(&p2l_checksum, fs, file, 3364289177Speter p2l_proto_index, revision, 3365289177Speter pool, pool)); 3366289177Speter 3367289177Speter /* Append footer. */ 3368289177Speter footer = svn_fs_fs__unparse_footer(l2p_offset, l2p_checksum, 3369289177Speter p2l_offset, p2l_checksum, pool, pool); 3370289177Speter SVN_ERR(svn_io_file_write_full(file, footer->data, footer->len, NULL, 3371289177Speter pool)); 3372289177Speter 3373289177Speter footer_length = footer->len; 3374289177Speter SVN_ERR_ASSERT(footer_length == footer->len); 3375289177Speter SVN_ERR(svn_io_file_write_full(file, &footer_length, 1, NULL, pool)); 3376289177Speter 3377289177Speter return SVN_NO_ERROR; 3378289177Speter} 3379289177Speter 3380289177Speter/* Baton used for commit_body below. */ 3381289177Speterstruct commit_baton { 3382289177Speter svn_revnum_t *new_rev_p; 3383289177Speter svn_fs_t *fs; 3384289177Speter svn_fs_txn_t *txn; 3385289177Speter apr_array_header_t *reps_to_cache; 3386289177Speter apr_hash_t *reps_hash; 3387289177Speter apr_pool_t *reps_pool; 3388289177Speter}; 3389289177Speter 3390289177Speter/* The work-horse for svn_fs_fs__commit, called with the FS write lock. 3391289177Speter This implements the svn_fs_fs__with_write_lock() 'body' callback 3392289177Speter type. BATON is a 'struct commit_baton *'. */ 3393289177Speterstatic svn_error_t * 3394289177Spetercommit_body(void *baton, apr_pool_t *pool) 3395289177Speter{ 3396289177Speter struct commit_baton *cb = baton; 3397289177Speter fs_fs_data_t *ffd = cb->fs->fsap_data; 3398289177Speter const char *old_rev_filename, *rev_filename, *proto_filename; 3399289177Speter const char *revprop_filename, *final_revprop; 3400289177Speter const svn_fs_id_t *root_id, *new_root_id; 3401289177Speter apr_uint64_t start_node_id; 3402289177Speter apr_uint64_t start_copy_id; 3403289177Speter svn_revnum_t old_rev, new_rev; 3404289177Speter apr_file_t *proto_file; 3405289177Speter void *proto_file_lockcookie; 3406289177Speter apr_off_t initial_offset, changed_path_offset; 3407289177Speter const svn_fs_fs__id_part_t *txn_id = svn_fs_fs__txn_get_id(cb->txn); 3408289177Speter apr_hash_t *changed_paths; 3409289177Speter 3410289177Speter /* Re-Read the current repository format. All our repo upgrade and 3411289177Speter config evaluation strategies are such that existing information in 3412289177Speter FS and FFD remains valid. 3413289177Speter 3414289177Speter Although we don't recommend upgrading hot repositories, people may 3415289177Speter still do it and we must make sure to either handle them gracefully 3416289177Speter or to error out. 3417289177Speter 3418289177Speter Committing pre-format 3 txns will fail after upgrade to format 3+ 3419289177Speter because the proto-rev cannot be found; no further action needed. 3420289177Speter Upgrades from pre-f7 to f7+ means a potential change in addressing 3421289177Speter mode for the final rev. We must be sure to detect that cause because 3422289177Speter the failure would only manifest once the new revision got committed. 3423289177Speter */ 3424289177Speter SVN_ERR(svn_fs_fs__read_format_file(cb->fs, pool)); 3425289177Speter 3426289177Speter /* Read the current youngest revision and, possibly, the next available 3427289177Speter node id and copy id (for old format filesystems). Update the cached 3428289177Speter value for the youngest revision, because we have just checked it. */ 3429289177Speter SVN_ERR(svn_fs_fs__read_current(&old_rev, &start_node_id, &start_copy_id, 3430289177Speter cb->fs, pool)); 3431289177Speter ffd->youngest_rev_cache = old_rev; 3432289177Speter 3433289177Speter /* Check to make sure this transaction is based off the most recent 3434289177Speter revision. */ 3435289177Speter if (cb->txn->base_rev != old_rev) 3436289177Speter return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL, 3437289177Speter _("Transaction out of date")); 3438289177Speter 3439289177Speter /* We need the changes list for verification as well as for writing it 3440289177Speter to the final rev file. */ 3441289177Speter SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, cb->fs, txn_id, 3442289177Speter pool)); 3443289177Speter 3444289177Speter /* Locks may have been added (or stolen) between the calling of 3445289177Speter previous svn_fs.h functions and svn_fs_commit_txn(), so we need 3446289177Speter to re-examine every changed-path in the txn and re-verify all 3447289177Speter discovered locks. */ 3448289177Speter SVN_ERR(verify_locks(cb->fs, txn_id, changed_paths, pool)); 3449289177Speter 3450289177Speter /* We are going to be one better than this puny old revision. */ 3451289177Speter new_rev = old_rev + 1; 3452289177Speter 3453289177Speter /* Get a write handle on the proto revision file. */ 3454289177Speter SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie, 3455289177Speter cb->fs, txn_id, pool)); 3456289177Speter SVN_ERR(svn_fs_fs__get_file_offset(&initial_offset, proto_file, pool)); 3457289177Speter 3458289177Speter /* Write out all the node-revisions and directory contents. */ 3459289177Speter root_id = svn_fs_fs__id_txn_create_root(txn_id, pool); 3460289177Speter SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id, 3461289177Speter start_node_id, start_copy_id, initial_offset, 3462289177Speter cb->reps_to_cache, cb->reps_hash, cb->reps_pool, 3463289177Speter TRUE, pool)); 3464289177Speter 3465289177Speter /* Write the changed-path information. */ 3466289177Speter SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file, 3467289177Speter cb->fs, txn_id, changed_paths, 3468289177Speter pool)); 3469289177Speter 3470289177Speter if (svn_fs_fs__use_log_addressing(cb->fs)) 3471289177Speter { 3472289177Speter /* Append the index data to the rev file. */ 3473289177Speter SVN_ERR(svn_fs_fs__add_index_data(cb->fs, proto_file, 3474289177Speter svn_fs_fs__path_l2p_proto_index(cb->fs, txn_id, pool), 3475289177Speter svn_fs_fs__path_p2l_proto_index(cb->fs, txn_id, pool), 3476289177Speter new_rev, pool)); 3477289177Speter } 3478289177Speter else 3479289177Speter { 3480289177Speter /* Write the final line. */ 3481289177Speter 3482289177Speter svn_stringbuf_t *trailer 3483289177Speter = svn_fs_fs__unparse_revision_trailer 3484289177Speter ((apr_off_t)svn_fs_fs__id_item(new_root_id), 3485289177Speter changed_path_offset, 3486289177Speter pool); 3487289177Speter SVN_ERR(svn_io_file_write_full(proto_file, trailer->data, trailer->len, 3488289177Speter NULL, pool)); 3489289177Speter } 3490289177Speter 3491289177Speter SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool)); 3492289177Speter SVN_ERR(svn_io_file_close(proto_file, pool)); 3493289177Speter 3494289177Speter /* We don't unlock the prototype revision file immediately to avoid a 3495289177Speter race with another caller writing to the prototype revision file 3496289177Speter before we commit it. */ 3497289177Speter 3498289177Speter /* Create the shard for the rev and revprop file, if we're sharding and 3499289177Speter this is the first revision of a new shard. We don't care if this 3500289177Speter fails because the shard already existed for some reason. */ 3501289177Speter if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0) 3502289177Speter { 3503289177Speter /* Create the revs shard. */ 3504289177Speter { 3505289177Speter const char *new_dir 3506289177Speter = svn_fs_fs__path_rev_shard(cb->fs, new_rev, pool); 3507289177Speter svn_error_t *err 3508289177Speter = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool); 3509289177Speter if (err && !APR_STATUS_IS_EEXIST(err->apr_err)) 3510289177Speter return svn_error_trace(err); 3511289177Speter svn_error_clear(err); 3512289177Speter SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path, 3513289177Speter PATH_REVS_DIR, 3514289177Speter pool), 3515289177Speter new_dir, pool)); 3516289177Speter } 3517289177Speter 3518289177Speter /* Create the revprops shard. */ 3519289177Speter SVN_ERR_ASSERT(! svn_fs_fs__is_packed_revprop(cb->fs, new_rev)); 3520289177Speter { 3521289177Speter const char *new_dir 3522289177Speter = svn_fs_fs__path_revprops_shard(cb->fs, new_rev, pool); 3523289177Speter svn_error_t *err 3524289177Speter = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool); 3525289177Speter if (err && !APR_STATUS_IS_EEXIST(err->apr_err)) 3526289177Speter return svn_error_trace(err); 3527289177Speter svn_error_clear(err); 3528289177Speter SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path, 3529289177Speter PATH_REVPROPS_DIR, 3530289177Speter pool), 3531289177Speter new_dir, pool)); 3532289177Speter } 3533289177Speter } 3534289177Speter 3535289177Speter /* Move the finished rev file into place. 3536289177Speter 3537289177Speter ### This "breaks" the transaction by removing the protorev file 3538289177Speter ### but the revision is not yet complete. If this commit does 3539289177Speter ### not complete for any reason the transaction will be lost. */ 3540289177Speter old_rev_filename = svn_fs_fs__path_rev_absolute(cb->fs, old_rev, pool); 3541289177Speter rev_filename = svn_fs_fs__path_rev(cb->fs, new_rev, pool); 3542289177Speter proto_filename = svn_fs_fs__path_txn_proto_rev(cb->fs, txn_id, pool); 3543289177Speter SVN_ERR(svn_fs_fs__move_into_place(proto_filename, rev_filename, 3544289177Speter old_rev_filename, pool)); 3545289177Speter 3546289177Speter /* Now that we've moved the prototype revision file out of the way, 3547289177Speter we can unlock it (since further attempts to write to the file 3548289177Speter will fail as it no longer exists). We must do this so that we can 3549289177Speter remove the transaction directory later. */ 3550289177Speter SVN_ERR(unlock_proto_rev(cb->fs, txn_id, proto_file_lockcookie, pool)); 3551289177Speter 3552289177Speter /* Move the revprops file into place. */ 3553289177Speter SVN_ERR_ASSERT(! svn_fs_fs__is_packed_revprop(cb->fs, new_rev)); 3554289177Speter SVN_ERR(write_final_revprop(&revprop_filename, cb->txn, txn_id, pool)); 3555289177Speter final_revprop = svn_fs_fs__path_revprops(cb->fs, new_rev, pool); 3556289177Speter SVN_ERR(svn_fs_fs__move_into_place(revprop_filename, final_revprop, 3557289177Speter old_rev_filename, pool)); 3558289177Speter 3559289177Speter /* Update the 'current' file. */ 3560289177Speter SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool)); 3561289177Speter SVN_ERR(write_final_current(cb->fs, txn_id, new_rev, start_node_id, 3562289177Speter start_copy_id, pool)); 3563289177Speter 3564289177Speter /* At this point the new revision is committed and globally visible 3565289177Speter so let the caller know it succeeded by giving it the new revision 3566289177Speter number, which fulfills svn_fs_commit_txn() contract. Any errors 3567289177Speter after this point do not change the fact that a new revision was 3568289177Speter created. */ 3569289177Speter *cb->new_rev_p = new_rev; 3570289177Speter 3571289177Speter ffd->youngest_rev_cache = new_rev; 3572289177Speter 3573289177Speter /* Remove this transaction directory. */ 3574289177Speter SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool)); 3575289177Speter 3576289177Speter return SVN_NO_ERROR; 3577289177Speter} 3578289177Speter 3579289177Speter/* Add the representations in REPS_TO_CACHE (an array of representation_t *) 3580289177Speter * to the rep-cache database of FS. */ 3581289177Speterstatic svn_error_t * 3582289177Speterwrite_reps_to_cache(svn_fs_t *fs, 3583289177Speter const apr_array_header_t *reps_to_cache, 3584289177Speter apr_pool_t *scratch_pool) 3585289177Speter{ 3586289177Speter int i; 3587289177Speter 3588289177Speter for (i = 0; i < reps_to_cache->nelts; i++) 3589289177Speter { 3590289177Speter representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *); 3591289177Speter 3592289177Speter SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, scratch_pool)); 3593289177Speter } 3594289177Speter 3595289177Speter return SVN_NO_ERROR; 3596289177Speter} 3597289177Speter 3598289177Spetersvn_error_t * 3599289177Spetersvn_fs_fs__commit(svn_revnum_t *new_rev_p, 3600289177Speter svn_fs_t *fs, 3601289177Speter svn_fs_txn_t *txn, 3602289177Speter apr_pool_t *pool) 3603289177Speter{ 3604289177Speter struct commit_baton cb; 3605289177Speter fs_fs_data_t *ffd = fs->fsap_data; 3606289177Speter 3607289177Speter cb.new_rev_p = new_rev_p; 3608289177Speter cb.fs = fs; 3609289177Speter cb.txn = txn; 3610289177Speter 3611289177Speter if (ffd->rep_sharing_allowed) 3612289177Speter { 3613289177Speter cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *)); 3614289177Speter cb.reps_hash = apr_hash_make(pool); 3615289177Speter cb.reps_pool = pool; 3616289177Speter } 3617289177Speter else 3618289177Speter { 3619289177Speter cb.reps_to_cache = NULL; 3620289177Speter cb.reps_hash = NULL; 3621289177Speter cb.reps_pool = NULL; 3622289177Speter } 3623289177Speter 3624289177Speter SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool)); 3625289177Speter 3626289177Speter /* At this point, *NEW_REV_P has been set, so errors below won't affect 3627289177Speter the success of the commit. (See svn_fs_commit_txn().) */ 3628289177Speter 3629289177Speter if (ffd->rep_sharing_allowed) 3630289177Speter { 3631309512Speter svn_error_t *err; 3632309512Speter 3633289177Speter SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); 3634289177Speter 3635289177Speter /* Write new entries to the rep-sharing database. 3636289177Speter * 3637289177Speter * We use an sqlite transaction to speed things up; 3638289177Speter * see <http://www.sqlite.org/faq.html#q19>. 3639289177Speter */ 3640289177Speter /* ### A commit that touches thousands of files will starve other 3641289177Speter (reader/writer) commits for the duration of the below call. 3642289177Speter Maybe write in batches? */ 3643309512Speter SVN_ERR(svn_sqlite__begin_transaction(ffd->rep_cache_db)); 3644309512Speter err = write_reps_to_cache(fs, cb.reps_to_cache, pool); 3645309512Speter err = svn_sqlite__finish_transaction(ffd->rep_cache_db, err); 3646309512Speter 3647309512Speter if (svn_error_find_cause(err, SVN_SQLITE__ERR_ROLLBACK_FAILED)) 3648309512Speter { 3649309512Speter /* Failed rollback means that our db connection is unusable, and 3650309512Speter the only thing we can do is close it. The connection will be 3651309512Speter reopened during the next operation with rep-cache.db. */ 3652309512Speter return svn_error_trace( 3653309512Speter svn_error_compose_create(err, 3654309512Speter svn_fs_fs__close_rep_cache(fs))); 3655309512Speter } 3656309512Speter else if (err) 3657309512Speter return svn_error_trace(err); 3658289177Speter } 3659289177Speter 3660289177Speter return SVN_NO_ERROR; 3661289177Speter} 3662289177Speter 3663289177Speter 3664289177Spetersvn_error_t * 3665289177Spetersvn_fs_fs__list_transactions(apr_array_header_t **names_p, 3666289177Speter svn_fs_t *fs, 3667289177Speter apr_pool_t *pool) 3668289177Speter{ 3669289177Speter const char *txn_dir; 3670289177Speter apr_hash_t *dirents; 3671289177Speter apr_hash_index_t *hi; 3672289177Speter apr_array_header_t *names; 3673289177Speter apr_size_t ext_len = strlen(PATH_EXT_TXN); 3674289177Speter 3675289177Speter names = apr_array_make(pool, 1, sizeof(const char *)); 3676289177Speter 3677289177Speter /* Get the transactions directory. */ 3678289177Speter txn_dir = svn_fs_fs__path_txns_dir(fs, pool); 3679289177Speter 3680289177Speter /* Now find a listing of this directory. */ 3681289177Speter SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool)); 3682289177Speter 3683289177Speter /* Loop through all the entries and return anything that ends with '.txn'. */ 3684289177Speter for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi)) 3685289177Speter { 3686289177Speter const char *name = apr_hash_this_key(hi); 3687289177Speter apr_ssize_t klen = apr_hash_this_key_len(hi); 3688289177Speter const char *id; 3689289177Speter 3690289177Speter /* The name must end with ".txn" to be considered a transaction. */ 3691289177Speter if ((apr_size_t) klen <= ext_len 3692289177Speter || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0) 3693289177Speter continue; 3694289177Speter 3695289177Speter /* Truncate the ".txn" extension and store the ID. */ 3696289177Speter id = apr_pstrndup(pool, name, strlen(name) - ext_len); 3697289177Speter APR_ARRAY_PUSH(names, const char *) = id; 3698289177Speter } 3699289177Speter 3700289177Speter *names_p = names; 3701289177Speter 3702289177Speter return SVN_NO_ERROR; 3703289177Speter} 3704289177Speter 3705289177Spetersvn_error_t * 3706289177Spetersvn_fs_fs__open_txn(svn_fs_txn_t **txn_p, 3707289177Speter svn_fs_t *fs, 3708289177Speter const char *name, 3709289177Speter apr_pool_t *pool) 3710289177Speter{ 3711289177Speter svn_fs_txn_t *txn; 3712289177Speter fs_txn_data_t *ftd; 3713289177Speter svn_node_kind_t kind; 3714289177Speter transaction_t *local_txn; 3715289177Speter svn_fs_fs__id_part_t txn_id; 3716289177Speter 3717289177Speter SVN_ERR(svn_fs_fs__id_txn_parse(&txn_id, name)); 3718289177Speter 3719289177Speter /* First check to see if the directory exists. */ 3720289177Speter SVN_ERR(svn_io_check_path(svn_fs_fs__path_txn_dir(fs, &txn_id, pool), 3721289177Speter &kind, pool)); 3722289177Speter 3723289177Speter /* Did we find it? */ 3724289177Speter if (kind != svn_node_dir) 3725289177Speter return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL, 3726289177Speter _("No such transaction '%s'"), 3727289177Speter name); 3728289177Speter 3729289177Speter txn = apr_pcalloc(pool, sizeof(*txn)); 3730289177Speter ftd = apr_pcalloc(pool, sizeof(*ftd)); 3731289177Speter ftd->txn_id = txn_id; 3732289177Speter 3733289177Speter /* Read in the root node of this transaction. */ 3734289177Speter txn->id = apr_pstrdup(pool, name); 3735289177Speter txn->fs = fs; 3736289177Speter 3737289177Speter SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, &txn_id, pool)); 3738289177Speter 3739289177Speter txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id); 3740289177Speter 3741289177Speter txn->vtable = &txn_vtable; 3742289177Speter txn->fsap_data = ftd; 3743289177Speter *txn_p = txn; 3744289177Speter 3745289177Speter return SVN_NO_ERROR; 3746289177Speter} 3747289177Speter 3748289177Spetersvn_error_t * 3749289177Spetersvn_fs_fs__txn_proplist(apr_hash_t **table_p, 3750289177Speter svn_fs_txn_t *txn, 3751289177Speter apr_pool_t *pool) 3752289177Speter{ 3753289177Speter apr_hash_t *proplist = apr_hash_make(pool); 3754289177Speter SVN_ERR(get_txn_proplist(proplist, txn->fs, svn_fs_fs__txn_get_id(txn), 3755289177Speter pool)); 3756289177Speter *table_p = proplist; 3757289177Speter 3758289177Speter return SVN_NO_ERROR; 3759289177Speter} 3760289177Speter 3761289177Speter 3762289177Spetersvn_error_t * 3763289177Spetersvn_fs_fs__delete_node_revision(svn_fs_t *fs, 3764289177Speter const svn_fs_id_t *id, 3765289177Speter apr_pool_t *pool) 3766289177Speter{ 3767289177Speter node_revision_t *noderev; 3768289177Speter 3769289177Speter SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool, pool)); 3770289177Speter 3771289177Speter /* Delete any mutable property representation. */ 3772289177Speter if (noderev->prop_rep && is_txn_rep(noderev->prop_rep)) 3773289177Speter SVN_ERR(svn_io_remove_file2(svn_fs_fs__path_txn_node_props(fs, id, pool), 3774289177Speter FALSE, pool)); 3775289177Speter 3776289177Speter /* Delete any mutable data representation. */ 3777289177Speter if (noderev->data_rep && is_txn_rep(noderev->data_rep) 3778289177Speter && noderev->kind == svn_node_dir) 3779289177Speter { 3780289177Speter fs_fs_data_t *ffd = fs->fsap_data; 3781289177Speter SVN_ERR(svn_io_remove_file2(svn_fs_fs__path_txn_node_children(fs, id, 3782289177Speter pool), 3783289177Speter FALSE, pool)); 3784289177Speter 3785289177Speter /* remove the corresponding entry from the cache, if such exists */ 3786289177Speter if (ffd->txn_dir_cache) 3787289177Speter { 3788289177Speter const char *key = svn_fs_fs__id_unparse(id, pool)->data; 3789289177Speter SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL, pool)); 3790289177Speter } 3791289177Speter } 3792289177Speter 3793289177Speter return svn_io_remove_file2(svn_fs_fs__path_txn_node_rev(fs, id, pool), 3794289177Speter FALSE, pool); 3795289177Speter} 3796289177Speter 3797289177Speter 3798289177Speter 3799289177Speter/*** Transactions ***/ 3800289177Speter 3801289177Spetersvn_error_t * 3802289177Spetersvn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p, 3803289177Speter const svn_fs_id_t **base_root_id_p, 3804289177Speter svn_fs_t *fs, 3805289177Speter const svn_fs_fs__id_part_t *txn_id, 3806289177Speter apr_pool_t *pool) 3807289177Speter{ 3808289177Speter transaction_t *txn; 3809289177Speter SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_id, pool)); 3810289177Speter *root_id_p = txn->root_id; 3811289177Speter *base_root_id_p = txn->base_id; 3812289177Speter return SVN_NO_ERROR; 3813289177Speter} 3814289177Speter 3815289177Speter 3816289177Speter/* Generic transaction operations. */ 3817289177Speter 3818289177Spetersvn_error_t * 3819289177Spetersvn_fs_fs__txn_prop(svn_string_t **value_p, 3820289177Speter svn_fs_txn_t *txn, 3821289177Speter const char *propname, 3822289177Speter apr_pool_t *pool) 3823289177Speter{ 3824289177Speter apr_hash_t *table; 3825289177Speter svn_fs_t *fs = txn->fs; 3826289177Speter 3827289177Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 3828289177Speter SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool)); 3829289177Speter 3830289177Speter *value_p = svn_hash_gets(table, propname); 3831289177Speter 3832289177Speter return SVN_NO_ERROR; 3833289177Speter} 3834289177Speter 3835289177Spetersvn_error_t * 3836289177Spetersvn_fs_fs__begin_txn(svn_fs_txn_t **txn_p, 3837289177Speter svn_fs_t *fs, 3838289177Speter svn_revnum_t rev, 3839289177Speter apr_uint32_t flags, 3840289177Speter apr_pool_t *pool) 3841289177Speter{ 3842289177Speter svn_string_t date; 3843289177Speter fs_txn_data_t *ftd; 3844289177Speter apr_hash_t *props = apr_hash_make(pool); 3845289177Speter 3846289177Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 3847289177Speter 3848289177Speter SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool)); 3849289177Speter 3850289177Speter /* Put a datestamp on the newly created txn, so we always know 3851289177Speter exactly how old it is. (This will help sysadmins identify 3852289177Speter long-abandoned txns that may need to be manually removed.) When 3853289177Speter a txn is promoted to a revision, this property will be 3854289177Speter automatically overwritten with a revision datestamp. */ 3855289177Speter date.data = svn_time_to_cstring(apr_time_now(), pool); 3856289177Speter date.len = strlen(date.data); 3857289177Speter 3858289177Speter svn_hash_sets(props, SVN_PROP_REVISION_DATE, &date); 3859289177Speter 3860289177Speter /* Set temporary txn props that represent the requested 'flags' 3861289177Speter behaviors. */ 3862289177Speter if (flags & SVN_FS_TXN_CHECK_OOD) 3863289177Speter svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_OOD, 3864289177Speter svn_string_create("true", pool)); 3865289177Speter 3866289177Speter if (flags & SVN_FS_TXN_CHECK_LOCKS) 3867289177Speter svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_LOCKS, 3868289177Speter svn_string_create("true", pool)); 3869289177Speter 3870289177Speter if (flags & SVN_FS_TXN_CLIENT_DATE) 3871289177Speter svn_hash_sets(props, SVN_FS__PROP_TXN_CLIENT_DATE, 3872289177Speter svn_string_create("0", pool)); 3873289177Speter 3874289177Speter ftd = (*txn_p)->fsap_data; 3875289177Speter return svn_error_trace(set_txn_proplist(fs, &ftd->txn_id, props, FALSE, 3876289177Speter pool)); 3877289177Speter} 3878