1251881Speter/* fs_fs.c --- filesystem operations specific to fs_fs 2251881Speter * 3251881Speter * ==================================================================== 4251881Speter * Licensed to the Apache Software Foundation (ASF) under one 5251881Speter * or more contributor license agreements. See the NOTICE file 6251881Speter * distributed with this work for additional information 7251881Speter * regarding copyright ownership. The ASF licenses this file 8251881Speter * to you under the Apache License, Version 2.0 (the 9251881Speter * "License"); you may not use this file except in compliance 10251881Speter * with the License. You may obtain a copy of the License at 11251881Speter * 12251881Speter * http://www.apache.org/licenses/LICENSE-2.0 13251881Speter * 14251881Speter * Unless required by applicable law or agreed to in writing, 15251881Speter * software distributed under the License is distributed on an 16251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17251881Speter * KIND, either express or implied. See the License for the 18251881Speter * specific language governing permissions and limitations 19251881Speter * under the License. 20251881Speter * ==================================================================== 21251881Speter */ 22251881Speter 23299742Sdim#include "fs_fs.h" 24251881Speter 25251881Speter#include <apr_uuid.h> 26251881Speter 27299742Sdim#include "svn_private_config.h" 28299742Sdim 29299742Sdim#include "svn_checksum.h" 30251881Speter#include "svn_hash.h" 31251881Speter#include "svn_props.h" 32299742Sdim#include "svn_time.h" 33299742Sdim#include "svn_dirent_uri.h" 34251881Speter#include "svn_sorts.h" 35251881Speter#include "svn_version.h" 36251881Speter 37299742Sdim#include "cached_data.h" 38251881Speter#include "id.h" 39299742Sdim#include "index.h" 40251881Speter#include "rep-cache.h" 41299742Sdim#include "revprops.h" 42299742Sdim#include "transaction.h" 43299742Sdim#include "tree.h" 44299742Sdim#include "util.h" 45251881Speter 46299742Sdim#include "private/svn_fs_util.h" 47299742Sdim#include "private/svn_io_private.h" 48251881Speter#include "private/svn_string_private.h" 49251881Speter#include "private/svn_subr_private.h" 50251881Speter#include "../libsvn_fs/fs-loader.h" 51251881Speter 52251881Speter/* The default maximum number of files per directory to store in the 53251881Speter rev and revprops directory. The number below is somewhat arbitrary, 54251881Speter and can be overridden by defining the macro while compiling; the 55251881Speter figure of 1000 is reasonable for VFAT filesystems, which are by far 56251881Speter the worst performers in this area. */ 57251881Speter#ifndef SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR 58251881Speter#define SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR 1000 59251881Speter#endif 60251881Speter 61251881Speter/* Begin deltification after a node history exceeded this this limit. 62251881Speter Useful values are 4 to 64 with 16 being a good compromise between 63251881Speter computational overhead and repository size savings. 64251881Speter Should be a power of 2. 65251881Speter Values < 2 will result in standard skip-delta behavior. */ 66251881Speter#define SVN_FS_FS_MAX_LINEAR_DELTIFICATION 16 67251881Speter 68251881Speter/* Finding a deltification base takes operations proportional to the 69251881Speter number of changes being skipped. To prevent exploding runtime 70251881Speter during commits, limit the deltification range to this value. 71251881Speter Should be a power of 2 minus one. 72251881Speter Values < 1 disable deltification. */ 73251881Speter#define SVN_FS_FS_MAX_DELTIFICATION_WALK 1023 74251881Speter 75251881Speter/* Notes: 76251881Speter 77251881SpeterTo avoid opening and closing the rev-files all the time, it would 78251881Speterprobably be advantageous to keep each rev-file open for the 79251881Speterlifetime of the transaction object. I'll leave that as a later 80251881Speteroptimization for now. 81251881Speter 82251881SpeterI didn't keep track of pool lifetimes at all in this code. There 83251881Speterare likely some errors because of that. 84251881Speter 85251881Speter*/ 86251881Speter 87251881Speter/* Declarations. */ 88251881Speter 89251881Speterstatic svn_error_t * 90299742Sdimget_youngest(svn_revnum_t *youngest_p, svn_fs_t *fs, apr_pool_t *pool); 91251881Speter 92251881Speter/* Pathname helper functions */ 93251881Speter 94251881Speterstatic const char * 95251881Speterpath_format(svn_fs_t *fs, apr_pool_t *pool) 96251881Speter{ 97251881Speter return svn_dirent_join(fs->path, PATH_FORMAT, pool); 98251881Speter} 99251881Speter 100251881Speterstatic APR_INLINE const char * 101251881Speterpath_uuid(svn_fs_t *fs, apr_pool_t *pool) 102251881Speter{ 103251881Speter return svn_dirent_join(fs->path, PATH_UUID, pool); 104251881Speter} 105251881Speter 106251881Speterconst char * 107251881Spetersvn_fs_fs__path_current(svn_fs_t *fs, apr_pool_t *pool) 108251881Speter{ 109251881Speter return svn_dirent_join(fs->path, PATH_CURRENT, pool); 110251881Speter} 111251881Speter 112251881Speter 113299742Sdim 114299742Sdim/* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */ 115299742Sdimstatic svn_error_t * 116299742Sdimget_lock_on_filesystem(const char *lock_filename, 117299742Sdim apr_pool_t *pool) 118251881Speter{ 119299742Sdim return svn_error_trace(svn_io__file_lock_autocreate(lock_filename, pool)); 120251881Speter} 121251881Speter 122299742Sdim/* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID. 123299742Sdim When registered with the pool holding the lock on the lock file, 124299742Sdim this makes sure the flag gets reset just before we release the lock. */ 125299742Sdimstatic apr_status_t 126299742Sdimreset_lock_flag(void *baton_void) 127251881Speter{ 128299742Sdim fs_fs_data_t *ffd = baton_void; 129299742Sdim ffd->has_write_lock = FALSE; 130299742Sdim return APR_SUCCESS; 131251881Speter} 132251881Speter 133299742Sdim/* Structure defining a file system lock to be acquired and the function 134299742Sdim to be executed while the lock is held. 135251881Speter 136299742Sdim Instances of this structure may be nested to allow for multiple locks to 137299742Sdim be taken out before executing the user-provided body. In that case, BODY 138299742Sdim and BATON of the outer instances will be with_lock and a with_lock_baton_t 139299742Sdim instance (transparently, no special treatment is required.). It is 140299742Sdim illegal to attempt to acquire the same lock twice within the same lock 141299742Sdim chain or via nesting calls using separate lock chains. 142299742Sdim 143299742Sdim All instances along the chain share the same LOCK_POOL such that only one 144299742Sdim pool needs to be created and cleared for all locks. We also allocate as 145299742Sdim much data from that lock pool as possible to minimize memory usage in 146299742Sdim caller pools. */ 147299742Sdimtypedef struct with_lock_baton_t 148251881Speter{ 149299742Sdim /* The filesystem we operate on. Same for all instances along the chain. */ 150299742Sdim svn_fs_t *fs; 151251881Speter 152299742Sdim /* Mutex to complement the lock file in an APR threaded process. 153299742Sdim No-op object for non-threaded processes but never NULL. */ 154299742Sdim svn_mutex__t *mutex; 155251881Speter 156299742Sdim /* Path to the file to lock. */ 157299742Sdim const char *lock_path; 158251881Speter 159299742Sdim /* If true, set FS->HAS_WRITE_LOCK after we acquired the lock. */ 160299742Sdim svn_boolean_t is_global_lock; 161251881Speter 162299742Sdim /* Function body to execute after we acquired the lock. 163299742Sdim This may be user-provided or a nested call to with_lock(). */ 164299742Sdim svn_error_t *(*body)(void *baton, 165299742Sdim apr_pool_t *pool); 166251881Speter 167299742Sdim /* Baton to pass to BODY; possibly NULL. 168299742Sdim This may be user-provided or a nested lock baton instance. */ 169299742Sdim void *baton; 170251881Speter 171299742Sdim /* Pool for all allocations along the lock chain and BODY. Will hold the 172299742Sdim file locks and gets destroyed after the outermost BODY returned, 173299742Sdim releasing all file locks. 174299742Sdim Same for all instances along the chain. */ 175299742Sdim apr_pool_t *lock_pool; 176251881Speter 177299742Sdim /* TRUE, iff BODY is the user-provided body. */ 178299742Sdim svn_boolean_t is_inner_most_lock; 179251881Speter 180299742Sdim /* TRUE, iff this is not a nested lock. 181299742Sdim Then responsible for destroying LOCK_POOL. */ 182299742Sdim svn_boolean_t is_outer_most_lock; 183299742Sdim} with_lock_baton_t; 184251881Speter 185299742Sdim/* Obtain a write lock on the file BATON->LOCK_PATH and call BATON->BODY 186299742Sdim with BATON->BATON. If this is the outermost lock call, release all file 187299742Sdim locks after the body returned. If BATON->IS_GLOBAL_LOCK is set, set the 188299742Sdim HAS_WRITE_LOCK flag while we keep the write lock. */ 189299742Sdimstatic svn_error_t * 190299742Sdimwith_some_lock_file(with_lock_baton_t *baton) 191251881Speter{ 192299742Sdim apr_pool_t *pool = baton->lock_pool; 193299742Sdim svn_error_t *err = get_lock_on_filesystem(baton->lock_path, pool); 194251881Speter 195299742Sdim if (!err) 196251881Speter { 197299742Sdim svn_fs_t *fs = baton->fs; 198299742Sdim fs_fs_data_t *ffd = fs->fsap_data; 199251881Speter 200299742Sdim if (baton->is_global_lock) 201299742Sdim { 202299742Sdim /* set the "got the lock" flag and register reset function */ 203299742Sdim apr_pool_cleanup_register(pool, 204299742Sdim ffd, 205299742Sdim reset_lock_flag, 206299742Sdim apr_pool_cleanup_null); 207299742Sdim ffd->has_write_lock = TRUE; 208299742Sdim } 209251881Speter 210299742Sdim /* nobody else will modify the repo state 211299742Sdim => read HEAD & pack info once */ 212299742Sdim if (baton->is_inner_most_lock) 213299742Sdim { 214299742Sdim if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 215299742Sdim err = svn_fs_fs__update_min_unpacked_rev(fs, pool); 216299742Sdim if (!err) 217299742Sdim err = get_youngest(&ffd->youngest_rev_cache, fs, pool); 218299742Sdim } 219251881Speter 220299742Sdim if (!err) 221299742Sdim err = baton->body(baton->baton, pool); 222251881Speter } 223251881Speter 224299742Sdim if (baton->is_outer_most_lock) 225299742Sdim svn_pool_destroy(pool); 226251881Speter 227299742Sdim return svn_error_trace(err); 228251881Speter} 229251881Speter 230299742Sdim/* Wraps with_some_lock_file, protecting it with BATON->MUTEX. 231251881Speter 232299742Sdim POOL is unused here and only provided for signature compatibility with 233299742Sdim WITH_LOCK_BATON_T.BODY. */ 234299742Sdimstatic svn_error_t * 235299742Sdimwith_lock(void *baton, 236299742Sdim apr_pool_t *pool) 237251881Speter{ 238299742Sdim with_lock_baton_t *lock_baton = baton; 239299742Sdim SVN_MUTEX__WITH_LOCK(lock_baton->mutex, with_some_lock_file(lock_baton)); 240251881Speter 241299742Sdim return SVN_NO_ERROR; 242251881Speter} 243251881Speter 244299742Sdim/* Enum identifying a filesystem lock. */ 245299742Sdimtypedef enum lock_id_t 246251881Speter{ 247299742Sdim write_lock, 248299742Sdim txn_lock, 249299742Sdim pack_lock 250299742Sdim} lock_id_t; 251251881Speter 252299742Sdim/* Initialize BATON->MUTEX, BATON->LOCK_PATH and BATON->IS_GLOBAL_LOCK 253299742Sdim according to the LOCK_ID. All other members of BATON must already be 254299742Sdim valid. */ 255299742Sdimstatic void 256299742Sdiminit_lock_baton(with_lock_baton_t *baton, 257299742Sdim lock_id_t lock_id) 258251881Speter{ 259299742Sdim fs_fs_data_t *ffd = baton->fs->fsap_data; 260251881Speter fs_fs_shared_data_t *ffsd = ffd->shared; 261251881Speter 262299742Sdim switch (lock_id) 263299742Sdim { 264299742Sdim case write_lock: 265299742Sdim baton->mutex = ffsd->fs_write_lock; 266299742Sdim baton->lock_path = svn_fs_fs__path_lock(baton->fs, baton->lock_pool); 267299742Sdim baton->is_global_lock = TRUE; 268251881Speter break; 269251881Speter 270299742Sdim case txn_lock: 271299742Sdim baton->mutex = ffsd->txn_current_lock; 272299742Sdim baton->lock_path = svn_fs_fs__path_txn_current_lock(baton->fs, 273299742Sdim baton->lock_pool); 274299742Sdim baton->is_global_lock = FALSE; 275299742Sdim break; 276251881Speter 277299742Sdim case pack_lock: 278299742Sdim baton->mutex = ffsd->fs_pack_lock; 279299742Sdim baton->lock_path = svn_fs_fs__path_pack_lock(baton->fs, 280299742Sdim baton->lock_pool); 281299742Sdim baton->is_global_lock = FALSE; 282299742Sdim break; 283251881Speter } 284251881Speter} 285251881Speter 286299742Sdim/* Return the baton for the innermost lock of a (potential) lock chain. 287299742Sdim The baton shall take out LOCK_ID from FS and execute BODY with BATON 288299742Sdim while the lock is being held. Allocate the result in a sub-pool of POOL. 289299742Sdim */ 290299742Sdimstatic with_lock_baton_t * 291299742Sdimcreate_lock_baton(svn_fs_t *fs, 292299742Sdim lock_id_t lock_id, 293299742Sdim svn_error_t *(*body)(void *baton, 294251881Speter apr_pool_t *pool), 295299742Sdim void *baton, 296251881Speter apr_pool_t *pool) 297251881Speter{ 298299742Sdim /* Allocate everything along the lock chain into a single sub-pool. 299299742Sdim This minimizes memory usage and cleanup overhead. */ 300299742Sdim apr_pool_t *lock_pool = svn_pool_create(pool); 301299742Sdim with_lock_baton_t *result = apr_pcalloc(lock_pool, sizeof(*result)); 302251881Speter 303299742Sdim /* Store parameters. */ 304299742Sdim result->fs = fs; 305299742Sdim result->body = body; 306299742Sdim result->baton = baton; 307251881Speter 308299742Sdim /* File locks etc. will use this pool as well for easy cleanup. */ 309299742Sdim result->lock_pool = lock_pool; 310251881Speter 311299742Sdim /* Right now, we are the first, (only, ) and last struct in the chain. */ 312299742Sdim result->is_inner_most_lock = TRUE; 313299742Sdim result->is_outer_most_lock = TRUE; 314251881Speter 315299742Sdim /* Select mutex and lock file path depending on LOCK_ID. 316299742Sdim Also, initialize dependent members (IS_GLOBAL_LOCK only, ATM). */ 317299742Sdim init_lock_baton(result, lock_id); 318251881Speter 319299742Sdim return result; 320251881Speter} 321251881Speter 322299742Sdim/* Return a baton that wraps NESTED and requests LOCK_ID as additional lock. 323299742Sdim * 324299742Sdim * That means, when you create a lock chain, start with the last / innermost 325299742Sdim * lock to take out and add the first / outermost lock last. 326299742Sdim */ 327299742Sdimstatic with_lock_baton_t * 328299742Sdimchain_lock_baton(lock_id_t lock_id, 329299742Sdim with_lock_baton_t *nested) 330251881Speter{ 331299742Sdim /* Use the same pool for batons along the lock chain. */ 332299742Sdim apr_pool_t *lock_pool = nested->lock_pool; 333299742Sdim with_lock_baton_t *result = apr_pcalloc(lock_pool, sizeof(*result)); 334251881Speter 335299742Sdim /* All locks along the chain operate on the same FS. */ 336299742Sdim result->fs = nested->fs; 337251881Speter 338299742Sdim /* Execution of this baton means acquiring the nested lock and its 339299742Sdim execution. */ 340299742Sdim result->body = with_lock; 341299742Sdim result->baton = nested; 342251881Speter 343299742Sdim /* Shared among all locks along the chain. */ 344299742Sdim result->lock_pool = lock_pool; 345251881Speter 346299742Sdim /* We are the new outermost lock but surely not the innermost lock. */ 347299742Sdim result->is_inner_most_lock = FALSE; 348299742Sdim result->is_outer_most_lock = TRUE; 349299742Sdim nested->is_outer_most_lock = FALSE; 350251881Speter 351299742Sdim /* Select mutex and lock file path depending on LOCK_ID. 352299742Sdim Also, initialize dependent members (IS_GLOBAL_LOCK only, ATM). */ 353299742Sdim init_lock_baton(result, lock_id); 354251881Speter 355299742Sdim return result; 356251881Speter} 357251881Speter 358251881Spetersvn_error_t * 359251881Spetersvn_fs_fs__with_write_lock(svn_fs_t *fs, 360251881Speter svn_error_t *(*body)(void *baton, 361251881Speter apr_pool_t *pool), 362251881Speter void *baton, 363251881Speter apr_pool_t *pool) 364251881Speter{ 365299742Sdim return svn_error_trace( 366299742Sdim with_lock(create_lock_baton(fs, write_lock, body, baton, pool), 367299742Sdim pool)); 368251881Speter} 369251881Speter 370299742Sdimsvn_error_t * 371299742Sdimsvn_fs_fs__with_pack_lock(svn_fs_t *fs, 372299742Sdim svn_error_t *(*body)(void *baton, 373299742Sdim apr_pool_t *pool), 374299742Sdim void *baton, 375299742Sdim apr_pool_t *pool) 376251881Speter{ 377299742Sdim return svn_error_trace( 378299742Sdim with_lock(create_lock_baton(fs, pack_lock, body, baton, pool), 379299742Sdim pool)); 380251881Speter} 381251881Speter 382299742Sdimsvn_error_t * 383299742Sdimsvn_fs_fs__with_txn_current_lock(svn_fs_t *fs, 384299742Sdim svn_error_t *(*body)(void *baton, 385299742Sdim apr_pool_t *pool), 386299742Sdim void *baton, 387299742Sdim apr_pool_t *pool) 388251881Speter{ 389299742Sdim return svn_error_trace( 390299742Sdim with_lock(create_lock_baton(fs, txn_lock, body, baton, pool), 391299742Sdim pool)); 392251881Speter} 393251881Speter 394299742Sdimsvn_error_t * 395299742Sdimsvn_fs_fs__with_all_locks(svn_fs_t *fs, 396299742Sdim svn_error_t *(*body)(void *baton, 397299742Sdim apr_pool_t *pool), 398299742Sdim void *baton, 399299742Sdim apr_pool_t *pool) 400251881Speter{ 401299742Sdim fs_fs_data_t *ffd = fs->fsap_data; 402251881Speter 403299742Sdim /* Be sure to use the correct lock ordering as documented in 404299742Sdim fs_fs_shared_data_t. The lock chain is being created in 405299742Sdim innermost (last to acquire) -> outermost (first to acquire) order. */ 406299742Sdim with_lock_baton_t *lock_baton 407299742Sdim = create_lock_baton(fs, write_lock, body, baton, pool); 408251881Speter 409299742Sdim if (ffd->format >= SVN_FS_FS__MIN_PACK_LOCK_FORMAT) 410299742Sdim lock_baton = chain_lock_baton(pack_lock, lock_baton); 411251881Speter 412299742Sdim if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 413299742Sdim lock_baton = chain_lock_baton(txn_lock, lock_baton); 414251881Speter 415299742Sdim return svn_error_trace(with_lock(lock_baton, pool)); 416251881Speter} 417251881Speter 418251881Speter 419251881Speter 420251881Speter 421251881Speter 422251881Speter/* Check that BUF, a nul-terminated buffer of text from format file PATH, 423251881Speter contains only digits at OFFSET and beyond, raising an error if not. 424251881Speter 425251881Speter Uses POOL for temporary allocation. */ 426251881Speterstatic svn_error_t * 427251881Spetercheck_format_file_buffer_numeric(const char *buf, apr_off_t offset, 428251881Speter const char *path, apr_pool_t *pool) 429251881Speter{ 430299742Sdim return svn_fs_fs__check_file_buffer_numeric(buf, offset, path, "Format", 431299742Sdim pool); 432251881Speter} 433251881Speter 434262253Speter/* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format 435262253Speter number is not the same as a format number supported by this 436262253Speter Subversion. */ 437262253Speterstatic svn_error_t * 438262253Spetercheck_format(int format) 439262253Speter{ 440262253Speter /* Blacklist. These formats may be either younger or older than 441262253Speter SVN_FS_FS__FORMAT_NUMBER, but we don't support them. */ 442262253Speter if (format == SVN_FS_FS__PACKED_REVPROP_SQLITE_DEV_FORMAT) 443262253Speter return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL, 444262253Speter _("Found format '%d', only created by " 445262253Speter "unreleased dev builds; see " 446262253Speter "http://subversion.apache.org" 447262253Speter "/docs/release-notes/1.7#revprop-packing"), 448262253Speter format); 449262253Speter 450262253Speter /* We support all formats from 1-current simultaneously */ 451262253Speter if (1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER) 452262253Speter return SVN_NO_ERROR; 453262253Speter 454262253Speter return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL, 455262253Speter _("Expected FS format between '1' and '%d'; found format '%d'"), 456262253Speter SVN_FS_FS__FORMAT_NUMBER, format); 457262253Speter} 458262253Speter 459251881Speter/* Read the format number and maximum number of files per directory 460299742Sdim from PATH and return them in *PFORMAT, *MAX_FILES_PER_DIR and 461299742Sdim USE_LOG_ADDRESSIONG respectively. 462251881Speter 463251881Speter *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and 464251881Speter will be set to zero if a linear scheme should be used. 465299742Sdim *USE_LOG_ADDRESSIONG is obtained from the 'addressing' format option, 466299742Sdim and will be set to FALSE for physical addressing. 467251881Speter 468251881Speter Use POOL for temporary allocation. */ 469251881Speterstatic svn_error_t * 470299742Sdimread_format(int *pformat, 471299742Sdim int *max_files_per_dir, 472299742Sdim svn_boolean_t *use_log_addressing, 473299742Sdim const char *path, 474299742Sdim apr_pool_t *pool) 475251881Speter{ 476251881Speter svn_error_t *err; 477251881Speter svn_stream_t *stream; 478251881Speter svn_stringbuf_t *content; 479251881Speter svn_stringbuf_t *buf; 480251881Speter svn_boolean_t eos = FALSE; 481251881Speter 482251881Speter err = svn_stringbuf_from_file2(&content, path, pool); 483251881Speter if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 484251881Speter { 485251881Speter /* Treat an absent format file as format 1. Do not try to 486251881Speter create the format file on the fly, because the repository 487251881Speter might be read-only for us, or this might be a read-only 488251881Speter operation, and the spirit of FSFS is to make no changes 489251881Speter whatseover in read-only operations. See thread starting at 490251881Speter http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=97600 491251881Speter for more. */ 492251881Speter svn_error_clear(err); 493251881Speter *pformat = 1; 494251881Speter *max_files_per_dir = 0; 495309512Speter *use_log_addressing = FALSE; 496251881Speter 497251881Speter return SVN_NO_ERROR; 498251881Speter } 499251881Speter SVN_ERR(err); 500251881Speter 501251881Speter stream = svn_stream_from_stringbuf(content, pool); 502251881Speter SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool)); 503251881Speter if (buf->len == 0 && eos) 504251881Speter { 505251881Speter /* Return a more useful error message. */ 506251881Speter return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL, 507251881Speter _("Can't read first line of format file '%s'"), 508251881Speter svn_dirent_local_style(path, pool)); 509251881Speter } 510251881Speter 511251881Speter /* Check that the first line contains only digits. */ 512251881Speter SVN_ERR(check_format_file_buffer_numeric(buf->data, 0, path, pool)); 513251881Speter SVN_ERR(svn_cstring_atoi(pformat, buf->data)); 514251881Speter 515262253Speter /* Check that we support this format at all */ 516262253Speter SVN_ERR(check_format(*pformat)); 517262253Speter 518251881Speter /* Set the default values for anything that can be set via an option. */ 519251881Speter *max_files_per_dir = 0; 520299742Sdim *use_log_addressing = FALSE; 521251881Speter 522251881Speter /* Read any options. */ 523251881Speter while (!eos) 524251881Speter { 525251881Speter SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool)); 526251881Speter if (buf->len == 0) 527251881Speter break; 528251881Speter 529251881Speter if (*pformat >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT && 530251881Speter strncmp(buf->data, "layout ", 7) == 0) 531251881Speter { 532251881Speter if (strcmp(buf->data + 7, "linear") == 0) 533251881Speter { 534251881Speter *max_files_per_dir = 0; 535251881Speter continue; 536251881Speter } 537251881Speter 538251881Speter if (strncmp(buf->data + 7, "sharded ", 8) == 0) 539251881Speter { 540251881Speter /* Check that the argument is numeric. */ 541251881Speter SVN_ERR(check_format_file_buffer_numeric(buf->data, 15, path, pool)); 542251881Speter SVN_ERR(svn_cstring_atoi(max_files_per_dir, buf->data + 15)); 543251881Speter continue; 544251881Speter } 545251881Speter } 546251881Speter 547299742Sdim if (*pformat >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT && 548299742Sdim strncmp(buf->data, "addressing ", 11) == 0) 549299742Sdim { 550299742Sdim if (strcmp(buf->data + 11, "physical") == 0) 551299742Sdim { 552299742Sdim *use_log_addressing = FALSE; 553299742Sdim continue; 554299742Sdim } 555299742Sdim 556299742Sdim if (strcmp(buf->data + 11, "logical") == 0) 557299742Sdim { 558299742Sdim *use_log_addressing = TRUE; 559299742Sdim continue; 560299742Sdim } 561299742Sdim } 562299742Sdim 563251881Speter return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL, 564251881Speter _("'%s' contains invalid filesystem format option '%s'"), 565251881Speter svn_dirent_local_style(path, pool), buf->data); 566251881Speter } 567251881Speter 568299742Sdim /* Non-sharded repositories never use logical addressing. 569299742Sdim * If the format file is inconsistent in that respect, something 570299742Sdim * probably went wrong. 571299742Sdim */ 572299742Sdim if (*use_log_addressing && !*max_files_per_dir) 573299742Sdim return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL, 574299742Sdim _("'%s' specifies logical addressing for a non-sharded repository"), 575299742Sdim svn_dirent_local_style(path, pool)); 576299742Sdim 577251881Speter return SVN_NO_ERROR; 578251881Speter} 579251881Speter 580299742Sdim/* Write the format number, maximum number of files per directory and 581299742Sdim the addressing scheme to a new format file in PATH, possibly expecting 582299742Sdim to overwrite a previously existing file. 583251881Speter 584251881Speter Use POOL for temporary allocation. */ 585299742Sdimsvn_error_t * 586299742Sdimsvn_fs_fs__write_format(svn_fs_t *fs, 587299742Sdim svn_boolean_t overwrite, 588299742Sdim apr_pool_t *pool) 589251881Speter{ 590251881Speter svn_stringbuf_t *sb; 591299742Sdim fs_fs_data_t *ffd = fs->fsap_data; 592299742Sdim const char *path = path_format(fs, pool); 593251881Speter 594299742Sdim SVN_ERR_ASSERT(1 <= ffd->format 595299742Sdim && ffd->format <= SVN_FS_FS__FORMAT_NUMBER); 596251881Speter 597299742Sdim sb = svn_stringbuf_createf(pool, "%d\n", ffd->format); 598251881Speter 599299742Sdim if (ffd->format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT) 600251881Speter { 601299742Sdim if (ffd->max_files_per_dir) 602251881Speter svn_stringbuf_appendcstr(sb, apr_psprintf(pool, "layout sharded %d\n", 603299742Sdim ffd->max_files_per_dir)); 604251881Speter else 605251881Speter svn_stringbuf_appendcstr(sb, "layout linear\n"); 606251881Speter } 607251881Speter 608299742Sdim if (ffd->format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT) 609299742Sdim { 610299742Sdim if (ffd->use_log_addressing) 611299742Sdim svn_stringbuf_appendcstr(sb, "addressing logical\n"); 612299742Sdim else 613299742Sdim svn_stringbuf_appendcstr(sb, "addressing physical\n"); 614299742Sdim } 615299742Sdim 616251881Speter /* svn_io_write_version_file() does a load of magic to allow it to 617251881Speter replace version files that already exist. We only need to do 618251881Speter that when we're allowed to overwrite an existing file. */ 619251881Speter if (! overwrite) 620251881Speter { 621251881Speter /* Create the file */ 622251881Speter SVN_ERR(svn_io_file_create(path, sb->data, pool)); 623251881Speter } 624251881Speter else 625251881Speter { 626299742Sdim SVN_ERR(svn_io_write_atomic(path, sb->data, sb->len, 627299742Sdim NULL /* copy_perms_path */, pool)); 628251881Speter } 629251881Speter 630251881Speter /* And set the perms to make it read only */ 631251881Speter return svn_io_set_file_read_only(path, FALSE, pool); 632251881Speter} 633251881Speter 634251881Spetersvn_boolean_t 635251881Spetersvn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs) 636251881Speter{ 637251881Speter fs_fs_data_t *ffd = fs->fsap_data; 638251881Speter return ffd->format >= SVN_FS_FS__MIN_MERGEINFO_FORMAT; 639251881Speter} 640251881Speter 641299742Sdim/* Check that BLOCK_SIZE is a valid block / page size, i.e. it is within 642299742Sdim * the range of what the current system may address in RAM and it is a 643299742Sdim * power of 2. Assume that the element size within the block is ITEM_SIZE. 644299742Sdim * Use SCRATCH_POOL for temporary allocations. 645299742Sdim */ 646299742Sdimstatic svn_error_t * 647299742Sdimverify_block_size(apr_int64_t block_size, 648299742Sdim apr_size_t item_size, 649299742Sdim const char *name, 650299742Sdim apr_pool_t *scratch_pool 651299742Sdim ) 652299742Sdim{ 653299742Sdim /* Limit range. */ 654299742Sdim if (block_size <= 0) 655299742Sdim return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL, 656299742Sdim _("%s is too small for fsfs.conf setting '%s'."), 657299742Sdim apr_psprintf(scratch_pool, 658299742Sdim "%" APR_INT64_T_FMT, 659299742Sdim block_size), 660299742Sdim name); 661299742Sdim 662299742Sdim if (block_size > SVN_MAX_OBJECT_SIZE / item_size) 663299742Sdim return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL, 664299742Sdim _("%s is too large for fsfs.conf setting '%s'."), 665299742Sdim apr_psprintf(scratch_pool, 666299742Sdim "%" APR_INT64_T_FMT, 667299742Sdim block_size), 668299742Sdim name); 669299742Sdim 670299742Sdim /* Ensure it is a power of two. 671299742Sdim * For positive X, X & (X-1) will reset the lowest bit set. 672299742Sdim * If the result is 0, at most one bit has been set. */ 673299742Sdim if (0 != (block_size & (block_size - 1))) 674299742Sdim return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL, 675299742Sdim _("%s is invalid for fsfs.conf setting '%s' " 676299742Sdim "because it is not a power of 2."), 677299742Sdim apr_psprintf(scratch_pool, 678299742Sdim "%" APR_INT64_T_FMT, 679299742Sdim block_size), 680299742Sdim name); 681299742Sdim 682299742Sdim return SVN_NO_ERROR; 683299742Sdim} 684299742Sdim 685251881Speter/* Read the configuration information of the file system at FS_PATH 686299742Sdim * and set the respective values in FFD. Use pools as usual. 687251881Speter */ 688251881Speterstatic svn_error_t * 689251881Speterread_config(fs_fs_data_t *ffd, 690251881Speter const char *fs_path, 691299742Sdim apr_pool_t *result_pool, 692299742Sdim apr_pool_t *scratch_pool) 693251881Speter{ 694299742Sdim svn_config_t *config; 695251881Speter 696299742Sdim SVN_ERR(svn_config_read3(&config, 697299742Sdim svn_dirent_join(fs_path, PATH_CONFIG, scratch_pool), 698299742Sdim FALSE, FALSE, FALSE, scratch_pool)); 699299742Sdim 700251881Speter /* Initialize ffd->rep_sharing_allowed. */ 701251881Speter if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT) 702299742Sdim SVN_ERR(svn_config_get_bool(config, &ffd->rep_sharing_allowed, 703251881Speter CONFIG_SECTION_REP_SHARING, 704251881Speter CONFIG_OPTION_ENABLE_REP_SHARING, TRUE)); 705251881Speter else 706251881Speter ffd->rep_sharing_allowed = FALSE; 707251881Speter 708251881Speter /* Initialize deltification settings in ffd. */ 709251881Speter if (ffd->format >= SVN_FS_FS__MIN_DELTIFICATION_FORMAT) 710251881Speter { 711299742Sdim apr_int64_t compression_level; 712299742Sdim 713299742Sdim SVN_ERR(svn_config_get_bool(config, &ffd->deltify_directories, 714251881Speter CONFIG_SECTION_DELTIFICATION, 715251881Speter CONFIG_OPTION_ENABLE_DIR_DELTIFICATION, 716299742Sdim TRUE)); 717299742Sdim SVN_ERR(svn_config_get_bool(config, &ffd->deltify_properties, 718251881Speter CONFIG_SECTION_DELTIFICATION, 719251881Speter CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION, 720299742Sdim TRUE)); 721299742Sdim SVN_ERR(svn_config_get_int64(config, &ffd->max_deltification_walk, 722251881Speter CONFIG_SECTION_DELTIFICATION, 723251881Speter CONFIG_OPTION_MAX_DELTIFICATION_WALK, 724251881Speter SVN_FS_FS_MAX_DELTIFICATION_WALK)); 725299742Sdim SVN_ERR(svn_config_get_int64(config, &ffd->max_linear_deltification, 726251881Speter CONFIG_SECTION_DELTIFICATION, 727251881Speter CONFIG_OPTION_MAX_LINEAR_DELTIFICATION, 728251881Speter SVN_FS_FS_MAX_LINEAR_DELTIFICATION)); 729299742Sdim 730299742Sdim SVN_ERR(svn_config_get_int64(config, &compression_level, 731299742Sdim CONFIG_SECTION_DELTIFICATION, 732299742Sdim CONFIG_OPTION_COMPRESSION_LEVEL, 733299742Sdim SVN_DELTA_COMPRESSION_LEVEL_DEFAULT)); 734299742Sdim ffd->delta_compression_level 735299742Sdim = (int)MIN(MAX(SVN_DELTA_COMPRESSION_LEVEL_NONE, compression_level), 736299742Sdim SVN_DELTA_COMPRESSION_LEVEL_MAX); 737251881Speter } 738251881Speter else 739251881Speter { 740251881Speter ffd->deltify_directories = FALSE; 741251881Speter ffd->deltify_properties = FALSE; 742251881Speter ffd->max_deltification_walk = SVN_FS_FS_MAX_DELTIFICATION_WALK; 743251881Speter ffd->max_linear_deltification = SVN_FS_FS_MAX_LINEAR_DELTIFICATION; 744299742Sdim ffd->delta_compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT; 745251881Speter } 746251881Speter 747251881Speter /* Initialize revprop packing settings in ffd. */ 748251881Speter if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT) 749251881Speter { 750299742Sdim SVN_ERR(svn_config_get_bool(config, &ffd->compress_packed_revprops, 751251881Speter CONFIG_SECTION_PACKED_REVPROPS, 752251881Speter CONFIG_OPTION_COMPRESS_PACKED_REVPROPS, 753251881Speter FALSE)); 754299742Sdim SVN_ERR(svn_config_get_int64(config, &ffd->revprop_pack_size, 755251881Speter CONFIG_SECTION_PACKED_REVPROPS, 756251881Speter CONFIG_OPTION_REVPROP_PACK_SIZE, 757251881Speter ffd->compress_packed_revprops 758299742Sdim ? 0x10 759299742Sdim : 0x4)); 760251881Speter 761251881Speter ffd->revprop_pack_size *= 1024; 762251881Speter } 763251881Speter else 764251881Speter { 765251881Speter ffd->revprop_pack_size = 0x10000; 766251881Speter ffd->compress_packed_revprops = FALSE; 767251881Speter } 768251881Speter 769299742Sdim if (ffd->format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT) 770299742Sdim { 771299742Sdim SVN_ERR(svn_config_get_int64(config, &ffd->block_size, 772299742Sdim CONFIG_SECTION_IO, 773299742Sdim CONFIG_OPTION_BLOCK_SIZE, 774299742Sdim 64)); 775299742Sdim SVN_ERR(svn_config_get_int64(config, &ffd->l2p_page_size, 776299742Sdim CONFIG_SECTION_IO, 777299742Sdim CONFIG_OPTION_L2P_PAGE_SIZE, 778299742Sdim 0x2000)); 779299742Sdim SVN_ERR(svn_config_get_int64(config, &ffd->p2l_page_size, 780299742Sdim CONFIG_SECTION_IO, 781299742Sdim CONFIG_OPTION_P2L_PAGE_SIZE, 782299742Sdim 0x400)); 783299742Sdim 784299742Sdim /* Don't accept unreasonable or illegal values. 785299742Sdim * Block size and P2L page size are in kbytes; 786299742Sdim * L2P blocks are arrays of apr_off_t. */ 787299742Sdim SVN_ERR(verify_block_size(ffd->block_size, 0x400, 788299742Sdim CONFIG_OPTION_BLOCK_SIZE, scratch_pool)); 789299742Sdim SVN_ERR(verify_block_size(ffd->p2l_page_size, 0x400, 790299742Sdim CONFIG_OPTION_P2L_PAGE_SIZE, scratch_pool)); 791299742Sdim SVN_ERR(verify_block_size(ffd->l2p_page_size, sizeof(apr_off_t), 792299742Sdim CONFIG_OPTION_L2P_PAGE_SIZE, scratch_pool)); 793299742Sdim 794299742Sdim /* convert kBytes to bytes */ 795299742Sdim ffd->block_size *= 0x400; 796299742Sdim ffd->p2l_page_size *= 0x400; 797299742Sdim /* L2P pages are in entries - not in (k)Bytes */ 798299742Sdim } 799299742Sdim else 800299742Sdim { 801299742Sdim /* should be irrelevant but we initialize them anyway */ 802299742Sdim ffd->block_size = 0x1000; /* Matches default APR file buffer size. */ 803299742Sdim ffd->l2p_page_size = 0x2000; /* Matches above default. */ 804299742Sdim ffd->p2l_page_size = 0x100000; /* Matches above default in bytes. */ 805299742Sdim } 806299742Sdim 807299742Sdim if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 808299742Sdim { 809299742Sdim SVN_ERR(svn_config_get_bool(config, &ffd->pack_after_commit, 810299742Sdim CONFIG_SECTION_DEBUG, 811299742Sdim CONFIG_OPTION_PACK_AFTER_COMMIT, 812299742Sdim FALSE)); 813299742Sdim } 814299742Sdim else 815299742Sdim { 816299742Sdim ffd->pack_after_commit = FALSE; 817299742Sdim } 818299742Sdim 819299742Sdim /* memcached configuration */ 820299742Sdim SVN_ERR(svn_cache__make_memcache_from_config(&ffd->memcache, config, 821299742Sdim result_pool, scratch_pool)); 822299742Sdim 823299742Sdim SVN_ERR(svn_config_get_bool(config, &ffd->fail_stop, 824299742Sdim CONFIG_SECTION_CACHES, CONFIG_OPTION_FAIL_STOP, 825299742Sdim FALSE)); 826299742Sdim 827251881Speter return SVN_NO_ERROR; 828251881Speter} 829251881Speter 830251881Speterstatic svn_error_t * 831251881Speterwrite_config(svn_fs_t *fs, 832251881Speter apr_pool_t *pool) 833251881Speter{ 834251881Speter#define NL APR_EOL_STR 835251881Speter static const char * const fsfs_conf_contents = 836251881Speter"### This file controls the configuration of the FSFS filesystem." NL 837251881Speter"" NL 838251881Speter"[" SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "]" NL 839251881Speter"### These options name memcached servers used to cache internal FSFS" NL 840251881Speter"### data. See http://www.danga.com/memcached/ for more information on" NL 841251881Speter"### memcached. To use memcached with FSFS, run one or more memcached" NL 842251881Speter"### servers, and specify each of them as an option like so:" NL 843251881Speter"# first-server = 127.0.0.1:11211" NL 844251881Speter"# remote-memcached = mymemcached.corp.example.com:11212" NL 845251881Speter"### The option name is ignored; the value is of the form HOST:PORT." NL 846251881Speter"### memcached servers can be shared between multiple repositories;" NL 847251881Speter"### however, if you do this, you *must* ensure that repositories have" NL 848251881Speter"### distinct UUIDs and paths, or else cached data from one repository" NL 849251881Speter"### might be used by another accidentally. Note also that memcached has" NL 850251881Speter"### no authentication for reads or writes, so you must ensure that your" NL 851251881Speter"### memcached servers are only accessible by trusted users." NL 852251881Speter"" NL 853251881Speter"[" CONFIG_SECTION_CACHES "]" NL 854251881Speter"### When a cache-related error occurs, normally Subversion ignores it" NL 855251881Speter"### and continues, logging an error if the server is appropriately" NL 856251881Speter"### configured (and ignoring it with file:// access). To make" NL 857251881Speter"### Subversion never ignore cache errors, uncomment this line." NL 858251881Speter"# " CONFIG_OPTION_FAIL_STOP " = true" NL 859251881Speter"" NL 860251881Speter"[" CONFIG_SECTION_REP_SHARING "]" NL 861251881Speter"### To conserve space, the filesystem can optionally avoid storing" NL 862251881Speter"### duplicate representations. This comes at a slight cost in" NL 863251881Speter"### performance, as maintaining a database of shared representations can" NL 864251881Speter"### increase commit times. The space savings are dependent upon the size" NL 865251881Speter"### of the repository, the number of objects it contains and the amount of" NL 866251881Speter"### duplication between them, usually a function of the branching and" NL 867251881Speter"### merging process." NL 868251881Speter"###" NL 869251881Speter"### The following parameter enables rep-sharing in the repository. It can" NL 870251881Speter"### be switched on and off at will, but for best space-saving results" NL 871251881Speter"### should be enabled consistently over the life of the repository." NL 872251881Speter"### 'svnadmin verify' will check the rep-cache regardless of this setting." NL 873251881Speter"### rep-sharing is enabled by default." NL 874251881Speter"# " CONFIG_OPTION_ENABLE_REP_SHARING " = true" NL 875251881Speter"" NL 876251881Speter"[" CONFIG_SECTION_DELTIFICATION "]" NL 877251881Speter"### To conserve space, the filesystem stores data as differences against" NL 878251881Speter"### existing representations. This comes at a slight cost in performance," NL 879251881Speter"### as calculating differences can increase commit times. Reading data" NL 880251881Speter"### will also create higher CPU load and the data will be fragmented." NL 881251881Speter"### Since deltification tends to save significant amounts of disk space," NL 882251881Speter"### the overall I/O load can actually be lower." NL 883251881Speter"###" NL 884251881Speter"### The options in this section allow for tuning the deltification" NL 885251881Speter"### strategy. Their effects on data size and server performance may vary" NL 886251881Speter"### from one repository to another. Versions prior to 1.8 will ignore" NL 887251881Speter"### this section." NL 888251881Speter"###" NL 889251881Speter"### The following parameter enables deltification for directories. It can" NL 890251881Speter"### be switched on and off at will, but for best space-saving results" NL 891299742Sdim"### should be enabled consistently over the lifetime of the repository." NL 892251881Speter"### Repositories containing large directories will benefit greatly." NL 893299742Sdim"### In rarely accessed repositories, the I/O overhead may be significant" NL 894299742Sdim"### as caches will most likely be low." NL 895299742Sdim"### directory deltification is enabled by default." NL 896299742Sdim"# " CONFIG_OPTION_ENABLE_DIR_DELTIFICATION " = true" NL 897251881Speter"###" NL 898251881Speter"### The following parameter enables deltification for properties on files" NL 899251881Speter"### and directories. Overall, this is a minor tuning option but can save" NL 900251881Speter"### some disk space if you merge frequently or frequently change node" NL 901251881Speter"### properties. You should not activate this if rep-sharing has been" NL 902251881Speter"### disabled because this may result in a net increase in repository size." NL 903299742Sdim"### property deltification is enabled by default." NL 904299742Sdim"# " CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION " = true" NL 905251881Speter"###" NL 906251881Speter"### During commit, the server may need to walk the whole change history of" NL 907251881Speter"### of a given node to find a suitable deltification base. This linear" NL 908251881Speter"### process can impact commit times, svnadmin load and similar operations." NL 909251881Speter"### This setting limits the depth of the deltification history. If the" NL 910251881Speter"### threshold has been reached, the node will be stored as fulltext and a" NL 911251881Speter"### new deltification history begins." NL 912251881Speter"### Note, this is unrelated to svn log." NL 913251881Speter"### Very large values rarely provide significant additional savings but" NL 914251881Speter"### can impact performance greatly - in particular if directory" NL 915251881Speter"### deltification has been activated. Very small values may be useful in" NL 916251881Speter"### repositories that are dominated by large, changing binaries." NL 917251881Speter"### Should be a power of two minus 1. A value of 0 will effectively" NL 918251881Speter"### disable deltification." NL 919251881Speter"### For 1.8, the default value is 1023; earlier versions have no limit." NL 920251881Speter"# " CONFIG_OPTION_MAX_DELTIFICATION_WALK " = 1023" NL 921251881Speter"###" NL 922251881Speter"### The skip-delta scheme used by FSFS tends to repeatably store redundant" NL 923251881Speter"### delta information where a simple delta against the latest version is" NL 924251881Speter"### often smaller. By default, 1.8+ will therefore use skip deltas only" NL 925251881Speter"### after the linear chain of deltas has grown beyond the threshold" NL 926251881Speter"### specified by this setting." NL 927251881Speter"### Values up to 64 can result in some reduction in repository size for" NL 928251881Speter"### the cost of quickly increasing I/O and CPU costs. Similarly, smaller" NL 929251881Speter"### numbers can reduce those costs at the cost of more disk space. For" NL 930251881Speter"### rarely read repositories or those containing larger binaries, this may" NL 931251881Speter"### present a better trade-off." NL 932251881Speter"### Should be a power of two. A value of 1 or smaller will cause the" NL 933251881Speter"### exclusive use of skip-deltas (as in pre-1.8)." NL 934251881Speter"### For 1.8, the default value is 16; earlier versions use 1." NL 935251881Speter"# " CONFIG_OPTION_MAX_LINEAR_DELTIFICATION " = 16" NL 936299742Sdim"###" NL 937299742Sdim"### After deltification, we compress the data through zlib to minimize on-" NL 938299742Sdim"### disk size. That can be an expensive and ineffective process. This" NL 939299742Sdim"### setting controls the usage of zlib in future revisions." NL 940299742Sdim"### Revisions with highly compressible data in them may shrink in size" NL 941299742Sdim"### if the setting is increased but may take much longer to commit. The" NL 942299742Sdim"### time taken to uncompress that data again is widely independent of the" NL 943299742Sdim"### compression level." NL 944299742Sdim"### Compression will be ineffective if the incoming content is already" NL 945299742Sdim"### highly compressed. In that case, disabling the compression entirely" NL 946299742Sdim"### will speed up commits as well as reading the data. Repositories with" NL 947299742Sdim"### many small compressible files (source code) but also a high percentage" NL 948299742Sdim"### of large incompressible ones (artwork) may benefit from compression" NL 949299742Sdim"### levels lowered to e.g. 1." NL 950299742Sdim"### Valid values are 0 to 9 with 9 providing the highest compression ratio" NL 951299742Sdim"### and 0 disabling it altogether." NL 952299742Sdim"### The default value is 5." NL 953299742Sdim"# " CONFIG_OPTION_COMPRESSION_LEVEL " = 5" NL 954251881Speter"" NL 955251881Speter"[" CONFIG_SECTION_PACKED_REVPROPS "]" NL 956251881Speter"### This parameter controls the size (in kBytes) of packed revprop files." NL 957251881Speter"### Revprops of consecutive revisions will be concatenated into a single" NL 958251881Speter"### file up to but not exceeding the threshold given here. However, each" NL 959251881Speter"### pack file may be much smaller and revprops of a single revision may be" NL 960251881Speter"### much larger than the limit set here. The threshold will be applied" NL 961251881Speter"### before optional compression takes place." NL 962251881Speter"### Large values will reduce disk space usage at the expense of increased" NL 963299742Sdim"### latency and CPU usage reading and changing individual revprops." NL 964299742Sdim"### Values smaller than 4 kByte will not improve latency any further and " NL 965299742Sdim"### quickly render revprop packing ineffective." NL 966299742Sdim"### revprop-pack-size is 4 kBytes by default for non-compressed revprop" NL 967299742Sdim"### pack files and 16 kBytes when compression has been enabled." NL 968299742Sdim"# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 4" NL 969251881Speter"###" NL 970251881Speter"### To save disk space, packed revprop files may be compressed. Standard" NL 971251881Speter"### revprops tend to allow for very effective compression. Reading and" NL 972299742Sdim"### even more so writing, become significantly more CPU intensive." NL 973251881Speter"### Compressing packed revprops is disabled by default." NL 974251881Speter"# " CONFIG_OPTION_COMPRESS_PACKED_REVPROPS " = false" NL 975299742Sdim"" NL 976299742Sdim"[" CONFIG_SECTION_IO "]" NL 977299742Sdim"### Parameters in this section control the data access granularity in" NL 978299742Sdim"### format 7 repositories and later. The defaults should translate into" NL 979299742Sdim"### decent performance over a wide range of setups." NL 980299742Sdim"###" NL 981299742Sdim"### When a specific piece of information needs to be read from disk, a" NL 982299742Sdim"### data block is being read at once and its contents are being cached." NL 983299742Sdim"### If the repository is being stored on a RAID, the block size should be" NL 984299742Sdim"### either 50% or 100% of RAID block size / granularity. Also, your file" NL 985299742Sdim"### system blocks/clusters should be properly aligned and sized. In that" NL 986299742Sdim"### setup, each access will hit only one disk (minimizes I/O load) but" NL 987299742Sdim"### uses all the data provided by the disk in a single access." NL 988299742Sdim"### For SSD-based storage systems, slightly lower values around 16 kB" NL 989299742Sdim"### may improve latency while still maximizing throughput. If block-read" NL 990299742Sdim"### has not been enabled, this will be capped to 4 kBytes." NL 991299742Sdim"### Can be changed at any time but must be a power of 2." NL 992299742Sdim"### block-size is given in kBytes and with a default of 64 kBytes." NL 993299742Sdim"# " CONFIG_OPTION_BLOCK_SIZE " = 64" NL 994299742Sdim"###" NL 995299742Sdim"### The log-to-phys index maps data item numbers to offsets within the" NL 996299742Sdim"### rev or pack file. This index is organized in pages of a fixed maximum" NL 997299742Sdim"### capacity. To access an item, the page table and the respective page" NL 998299742Sdim"### must be read." NL 999299742Sdim"### This parameter only affects revisions with thousands of changed paths." NL 1000299742Sdim"### If you have several extremely large revisions (~1 mio changes), think" NL 1001299742Sdim"### about increasing this setting. Reducing the value will rarely result" NL 1002299742Sdim"### in a net speedup." NL 1003299742Sdim"### This is an expert setting. Must be a power of 2." NL 1004299742Sdim"### l2p-page-size is 8192 entries by default." NL 1005299742Sdim"# " CONFIG_OPTION_L2P_PAGE_SIZE " = 8192" NL 1006299742Sdim"###" NL 1007299742Sdim"### The phys-to-log index maps positions within the rev or pack file to" NL 1008299742Sdim"### to data items, i.e. describes what piece of information is being" NL 1009299742Sdim"### stored at any particular offset. The index describes the rev file" NL 1010299742Sdim"### in chunks (pages) and keeps a global list of all those pages. Large" NL 1011299742Sdim"### pages mean a shorter page table but a larger per-page description of" NL 1012299742Sdim"### data items in it. The latency sweetspot depends on the change size" NL 1013299742Sdim"### distribution but covers a relatively wide range." NL 1014299742Sdim"### If the repository contains very large files, i.e. individual changes" NL 1015299742Sdim"### of tens of MB each, increasing the page size will shorten the index" NL 1016299742Sdim"### file at the expense of a slightly increased latency in sections with" NL 1017299742Sdim"### smaller changes." NL 1018299742Sdim"### For source code repositories, this should be about 16x the block-size." NL 1019299742Sdim"### Must be a power of 2." NL 1020299742Sdim"### p2l-page-size is given in kBytes and with a default of 1024 kBytes." NL 1021299742Sdim"# " CONFIG_OPTION_P2L_PAGE_SIZE " = 1024" NL 1022251881Speter; 1023251881Speter#undef NL 1024251881Speter return svn_io_file_create(svn_dirent_join(fs->path, PATH_CONFIG, pool), 1025251881Speter fsfs_conf_contents, pool); 1026251881Speter} 1027251881Speter 1028299742Sdim/* Read / Evaluate the global configuration in FS->CONFIG to set up 1029299742Sdim * parameters in FS. */ 1030251881Speterstatic svn_error_t * 1031299742Sdimread_global_config(svn_fs_t *fs) 1032251881Speter{ 1033299742Sdim fs_fs_data_t *ffd = fs->fsap_data; 1034251881Speter 1035299742Sdim /* Providing a config hash is optional. */ 1036299742Sdim if (fs->config) 1037299742Sdim ffd->use_block_read = svn_hash__get_bool(fs->config, 1038299742Sdim SVN_FS_CONFIG_FSFS_BLOCK_READ, 1039299742Sdim FALSE); 1040299742Sdim else 1041299742Sdim ffd->use_block_read = FALSE; 1042251881Speter 1043299742Sdim /* Ignore the user-specified larger block size if we don't use block-read. 1044299742Sdim Defaulting to 4k gives us the same access granularity in format 7 as in 1045299742Sdim older formats. */ 1046299742Sdim if (!ffd->use_block_read) 1047299742Sdim ffd->block_size = MIN(0x1000, ffd->block_size); 1048299742Sdim 1049251881Speter return SVN_NO_ERROR; 1050251881Speter} 1051251881Speter 1052299742Sdim/* Read FS's UUID file and store the data in the FS struct. */ 1053251881Speterstatic svn_error_t * 1054299742Sdimread_uuid(svn_fs_t *fs, 1055299742Sdim apr_pool_t *scratch_pool) 1056251881Speter{ 1057251881Speter fs_fs_data_t *ffd = fs->fsap_data; 1058299742Sdim apr_file_t *uuid_file; 1059299742Sdim char buf[APR_UUID_FORMATTED_LENGTH + 2]; 1060299742Sdim apr_size_t limit; 1061251881Speter 1062299742Sdim /* Read the repository uuid. */ 1063299742Sdim SVN_ERR(svn_io_file_open(&uuid_file, path_uuid(fs, scratch_pool), 1064299742Sdim APR_READ | APR_BUFFERED, APR_OS_DEFAULT, 1065299742Sdim scratch_pool)); 1066251881Speter 1067299742Sdim limit = sizeof(buf); 1068299742Sdim SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, scratch_pool)); 1069299742Sdim fs->uuid = apr_pstrdup(fs->pool, buf); 1070299742Sdim 1071299742Sdim /* Read the instance ID. */ 1072299742Sdim if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT) 1073299742Sdim { 1074299742Sdim limit = sizeof(buf); 1075299742Sdim SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, 1076299742Sdim scratch_pool)); 1077299742Sdim ffd->instance_id = apr_pstrdup(fs->pool, buf); 1078299742Sdim } 1079299742Sdim else 1080299742Sdim { 1081299742Sdim ffd->instance_id = fs->uuid; 1082299742Sdim } 1083299742Sdim 1084299742Sdim SVN_ERR(svn_io_file_close(uuid_file, scratch_pool)); 1085299742Sdim 1086299742Sdim return SVN_NO_ERROR; 1087251881Speter} 1088251881Speter 1089251881Spetersvn_error_t * 1090299742Sdimsvn_fs_fs__read_format_file(svn_fs_t *fs, apr_pool_t *scratch_pool) 1091251881Speter{ 1092251881Speter fs_fs_data_t *ffd = fs->fsap_data; 1093251881Speter int format, max_files_per_dir; 1094299742Sdim svn_boolean_t use_log_addressing; 1095251881Speter 1096299742Sdim /* Read info from format file. */ 1097299742Sdim SVN_ERR(read_format(&format, &max_files_per_dir, &use_log_addressing, 1098299742Sdim path_format(fs, scratch_pool), scratch_pool)); 1099251881Speter 1100299742Sdim /* Now that we've got *all* info, store / update values in FFD. */ 1101251881Speter ffd->format = format; 1102251881Speter ffd->max_files_per_dir = max_files_per_dir; 1103299742Sdim ffd->use_log_addressing = use_log_addressing; 1104251881Speter 1105299742Sdim return SVN_NO_ERROR; 1106299742Sdim} 1107251881Speter 1108299742Sdimsvn_error_t * 1109299742Sdimsvn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool) 1110299742Sdim{ 1111299742Sdim fs_fs_data_t *ffd = fs->fsap_data; 1112299742Sdim fs->path = apr_pstrdup(fs->pool, path); 1113251881Speter 1114299742Sdim /* Read the FS format file. */ 1115299742Sdim SVN_ERR(svn_fs_fs__read_format_file(fs, pool)); 1116251881Speter 1117299742Sdim /* Read in and cache the repository uuid. */ 1118299742Sdim SVN_ERR(read_uuid(fs, pool)); 1119299742Sdim 1120251881Speter /* Read the min unpacked revision. */ 1121251881Speter if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 1122299742Sdim SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, pool)); 1123251881Speter 1124251881Speter /* Read the configuration file. */ 1125299742Sdim SVN_ERR(read_config(ffd, fs->path, fs->pool, pool)); 1126251881Speter 1127299742Sdim /* Global configuration options. */ 1128299742Sdim SVN_ERR(read_global_config(fs)); 1129299742Sdim 1130299742Sdim return get_youngest(&(ffd->youngest_rev_cache), fs, pool); 1131251881Speter} 1132251881Speter 1133251881Speter/* Wrapper around svn_io_file_create which ignores EEXIST. */ 1134251881Speterstatic svn_error_t * 1135251881Spetercreate_file_ignore_eexist(const char *file, 1136251881Speter const char *contents, 1137251881Speter apr_pool_t *pool) 1138251881Speter{ 1139251881Speter svn_error_t *err = svn_io_file_create(file, contents, pool); 1140251881Speter if (err && APR_STATUS_IS_EEXIST(err->apr_err)) 1141251881Speter { 1142251881Speter svn_error_clear(err); 1143251881Speter err = SVN_NO_ERROR; 1144251881Speter } 1145251881Speter return svn_error_trace(err); 1146251881Speter} 1147251881Speter 1148299742Sdim/* Baton type bridging svn_fs_fs__upgrade and upgrade_body carrying 1149299742Sdim * parameters over between them. */ 1150299742Sdimstruct upgrade_baton_t 1151251881Speter{ 1152299742Sdim svn_fs_t *fs; 1153299742Sdim svn_fs_upgrade_notify_t notify_func; 1154299742Sdim void *notify_baton; 1155299742Sdim svn_cancel_func_t cancel_func; 1156299742Sdim void *cancel_baton; 1157299742Sdim}; 1158251881Speter 1159253734Speterstatic svn_error_t * 1160299742Sdimupgrade_body(void *baton, apr_pool_t *pool) 1161253734Speter{ 1162299742Sdim struct upgrade_baton_t *upgrade_baton = baton; 1163299742Sdim svn_fs_t *fs = upgrade_baton->fs; 1164253734Speter fs_fs_data_t *ffd = fs->fsap_data; 1165251881Speter int format, max_files_per_dir; 1166299742Sdim svn_boolean_t use_log_addressing; 1167251881Speter const char *format_path = path_format(fs, pool); 1168251881Speter svn_node_kind_t kind; 1169253734Speter svn_boolean_t needs_revprop_shard_cleanup = FALSE; 1170251881Speter 1171251881Speter /* Read the FS format number and max-files-per-dir setting. */ 1172299742Sdim SVN_ERR(read_format(&format, &max_files_per_dir, &use_log_addressing, 1173299742Sdim format_path, pool)); 1174251881Speter 1175251881Speter /* If the config file does not exist, create one. */ 1176251881Speter SVN_ERR(svn_io_check_path(svn_dirent_join(fs->path, PATH_CONFIG, pool), 1177251881Speter &kind, pool)); 1178251881Speter switch (kind) 1179251881Speter { 1180251881Speter case svn_node_none: 1181251881Speter SVN_ERR(write_config(fs, pool)); 1182251881Speter break; 1183251881Speter case svn_node_file: 1184251881Speter break; 1185251881Speter default: 1186251881Speter return svn_error_createf(SVN_ERR_FS_GENERAL, NULL, 1187251881Speter _("'%s' is not a regular file." 1188251881Speter " Please move it out of " 1189251881Speter "the way and try again"), 1190251881Speter svn_dirent_join(fs->path, PATH_CONFIG, pool)); 1191251881Speter } 1192251881Speter 1193251881Speter /* If we're already up-to-date, there's nothing else to be done here. */ 1194251881Speter if (format == SVN_FS_FS__FORMAT_NUMBER) 1195251881Speter return SVN_NO_ERROR; 1196251881Speter 1197299742Sdim /* If our filesystem predates the existence of the 'txn-current 1198251881Speter file', make that file and its corresponding lock file. */ 1199251881Speter if (format < SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 1200251881Speter { 1201299742Sdim SVN_ERR(create_file_ignore_eexist( 1202299742Sdim svn_fs_fs__path_txn_current(fs, pool), "0\n", 1203299742Sdim pool)); 1204299742Sdim SVN_ERR(create_file_ignore_eexist( 1205299742Sdim svn_fs_fs__path_txn_current_lock(fs, pool), "", 1206299742Sdim pool)); 1207251881Speter } 1208251881Speter 1209299742Sdim /* If our filesystem predates the existence of the 'txn-protorevs' 1210251881Speter dir, make that directory. */ 1211251881Speter if (format < SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) 1212251881Speter { 1213251881Speter SVN_ERR(svn_io_make_dir_recursively( 1214299742Sdim svn_fs_fs__path_txn_proto_revs(fs, pool), pool)); 1215251881Speter } 1216251881Speter 1217251881Speter /* If our filesystem is new enough, write the min unpacked rev file. */ 1218251881Speter if (format < SVN_FS_FS__MIN_PACKED_FORMAT) 1219299742Sdim SVN_ERR(svn_io_file_create(svn_fs_fs__path_min_unpacked_rev(fs, pool), 1220299742Sdim "0\n", pool)); 1221251881Speter 1222253734Speter /* If the file system supports revision packing but not revprop packing 1223253734Speter *and* the FS has been sharded, pack the revprops up to the point that 1224253734Speter revision data has been packed. However, keep the non-packed revprop 1225253734Speter files around until after the format bump */ 1226251881Speter if ( format >= SVN_FS_FS__MIN_PACKED_FORMAT 1227253734Speter && format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT 1228253734Speter && max_files_per_dir > 0) 1229253734Speter { 1230253734Speter needs_revprop_shard_cleanup = TRUE; 1231299742Sdim SVN_ERR(svn_fs_fs__upgrade_pack_revprops(fs, 1232299742Sdim upgrade_baton->notify_func, 1233299742Sdim upgrade_baton->notify_baton, 1234299742Sdim upgrade_baton->cancel_func, 1235299742Sdim upgrade_baton->cancel_baton, 1236299742Sdim pool)); 1237253734Speter } 1238251881Speter 1239299742Sdim /* We will need the UUID info shortly ... 1240299742Sdim Read it before the format bump as the UUID file still uses the old 1241299742Sdim format. */ 1242299742Sdim SVN_ERR(read_uuid(fs, pool)); 1243299742Sdim 1244299742Sdim /* Update the format info in the FS struct. Upgrade steps further 1245299742Sdim down will use the format from FS to create missing info. */ 1246299742Sdim ffd->format = SVN_FS_FS__FORMAT_NUMBER; 1247299742Sdim ffd->max_files_per_dir = max_files_per_dir; 1248299742Sdim ffd->use_log_addressing = use_log_addressing; 1249299742Sdim 1250299742Sdim /* Always add / bump the instance ID such that no form of caching 1251299742Sdim accidentally uses outdated information. Keep the UUID. */ 1252299742Sdim SVN_ERR(svn_fs_fs__set_uuid(fs, fs->uuid, NULL, pool)); 1253299742Sdim 1254251881Speter /* Bump the format file. */ 1255299742Sdim SVN_ERR(svn_fs_fs__write_format(fs, TRUE, pool)); 1256253734Speter 1257299742Sdim if (upgrade_baton->notify_func) 1258299742Sdim SVN_ERR(upgrade_baton->notify_func(upgrade_baton->notify_baton, 1259299742Sdim SVN_FS_FS__FORMAT_NUMBER, 1260299742Sdim svn_fs_upgrade_format_bumped, 1261299742Sdim pool)); 1262299742Sdim 1263253734Speter /* Now, it is safe to remove the redundant revprop files. */ 1264253734Speter if (needs_revprop_shard_cleanup) 1265299742Sdim SVN_ERR(svn_fs_fs__upgrade_cleanup_pack_revprops(fs, 1266299742Sdim upgrade_baton->notify_func, 1267299742Sdim upgrade_baton->notify_baton, 1268299742Sdim upgrade_baton->cancel_func, 1269299742Sdim upgrade_baton->cancel_baton, 1270299742Sdim pool)); 1271253734Speter 1272253734Speter /* Done */ 1273253734Speter return SVN_NO_ERROR; 1274251881Speter} 1275251881Speter 1276251881Speter 1277251881Spetersvn_error_t * 1278299742Sdimsvn_fs_fs__upgrade(svn_fs_t *fs, 1279299742Sdim svn_fs_upgrade_notify_t notify_func, 1280299742Sdim void *notify_baton, 1281299742Sdim svn_cancel_func_t cancel_func, 1282299742Sdim void *cancel_baton, 1283299742Sdim apr_pool_t *pool) 1284251881Speter{ 1285299742Sdim struct upgrade_baton_t baton; 1286299742Sdim baton.fs = fs; 1287299742Sdim baton.notify_func = notify_func; 1288299742Sdim baton.notify_baton = notify_baton; 1289299742Sdim baton.cancel_func = cancel_func; 1290299742Sdim baton.cancel_baton = cancel_baton; 1291251881Speter 1292299742Sdim return svn_fs_fs__with_all_locks(fs, upgrade_body, (void *)&baton, pool); 1293251881Speter} 1294251881Speter 1295251881Speter/* Find the youngest revision in a repository at path FS_PATH and 1296251881Speter return it in *YOUNGEST_P. Perform temporary allocations in 1297251881Speter POOL. */ 1298251881Speterstatic svn_error_t * 1299251881Speterget_youngest(svn_revnum_t *youngest_p, 1300299742Sdim svn_fs_t *fs, 1301251881Speter apr_pool_t *pool) 1302251881Speter{ 1303299742Sdim apr_uint64_t dummy; 1304299742Sdim SVN_ERR(svn_fs_fs__read_current(youngest_p, &dummy, &dummy, fs, pool)); 1305251881Speter return SVN_NO_ERROR; 1306251881Speter} 1307251881Speter 1308251881Speter 1309251881Spetersvn_error_t * 1310251881Spetersvn_fs_fs__youngest_rev(svn_revnum_t *youngest_p, 1311251881Speter svn_fs_t *fs, 1312251881Speter apr_pool_t *pool) 1313251881Speter{ 1314251881Speter fs_fs_data_t *ffd = fs->fsap_data; 1315251881Speter 1316299742Sdim SVN_ERR(get_youngest(youngest_p, fs, pool)); 1317251881Speter ffd->youngest_rev_cache = *youngest_p; 1318251881Speter 1319251881Speter return SVN_NO_ERROR; 1320251881Speter} 1321251881Speter 1322299742Sdimint 1323299742Sdimsvn_fs_fs__shard_size(svn_fs_t *fs) 1324251881Speter{ 1325299742Sdim fs_fs_data_t *ffd = fs->fsap_data; 1326251881Speter 1327299742Sdim return ffd->max_files_per_dir; 1328299742Sdim} 1329251881Speter 1330299742Sdimsvn_error_t * 1331299742Sdimsvn_fs_fs__min_unpacked_rev(svn_revnum_t *min_unpacked, 1332299742Sdim svn_fs_t *fs, 1333299742Sdim apr_pool_t *pool) 1334299742Sdim{ 1335299742Sdim fs_fs_data_t *ffd = fs->fsap_data; 1336251881Speter 1337299742Sdim SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, pool)); 1338299742Sdim *min_unpacked = ffd->min_unpacked_rev; 1339251881Speter 1340251881Speter return SVN_NO_ERROR; 1341251881Speter} 1342251881Speter 1343299742Sdimsvn_error_t * 1344299742Sdimsvn_fs_fs__ensure_revision_exists(svn_revnum_t rev, 1345299742Sdim svn_fs_t *fs, 1346299742Sdim apr_pool_t *pool) 1347251881Speter{ 1348251881Speter fs_fs_data_t *ffd = fs->fsap_data; 1349251881Speter 1350251881Speter if (! SVN_IS_VALID_REVNUM(rev)) 1351251881Speter return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 1352251881Speter _("Invalid revision number '%ld'"), rev); 1353251881Speter 1354251881Speter 1355251881Speter /* Did the revision exist the last time we checked the current 1356251881Speter file? */ 1357251881Speter if (rev <= ffd->youngest_rev_cache) 1358251881Speter return SVN_NO_ERROR; 1359251881Speter 1360299742Sdim SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs, pool)); 1361251881Speter 1362251881Speter /* Check again. */ 1363251881Speter if (rev <= ffd->youngest_rev_cache) 1364251881Speter return SVN_NO_ERROR; 1365251881Speter 1366251881Speter return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 1367251881Speter _("No such revision %ld"), rev); 1368251881Speter} 1369251881Speter 1370251881Spetersvn_error_t * 1371299742Sdimsvn_fs_fs__file_length(svn_filesize_t *length, 1372251881Speter node_revision_t *noderev, 1373251881Speter apr_pool_t *pool) 1374251881Speter{ 1375299742Sdim representation_t *data_rep = noderev->data_rep; 1376299742Sdim if (!data_rep) 1377251881Speter { 1378299742Sdim /* Treat "no representation" as "empty file". */ 1379299742Sdim *length = 0; 1380251881Speter } 1381299742Sdim else if (data_rep->expanded_size) 1382251881Speter { 1383299742Sdim /* Standard case: a non-empty file. */ 1384299742Sdim *length = data_rep->expanded_size; 1385251881Speter } 1386251881Speter else 1387251881Speter { 1388299742Sdim /* Work around a FSFS format quirk (see issue #4554). 1389251881Speter 1390299742Sdim A plain representation may specify its EXPANDED LENGTH as "0" 1391299742Sdim in which case, the SIZE value is what we want. 1392251881Speter 1393299742Sdim Because EXPANDED_LENGTH will also be 0 for empty files, while 1394299742Sdim SIZE is non-null, we need to check wether the content is 1395299742Sdim actually empty. We simply compare with the MD5 checksum of 1396299742Sdim empty content (sha-1 is not always available). 1397251881Speter */ 1398299742Sdim svn_checksum_t *empty_md5 1399299742Sdim = svn_checksum_empty_checksum(svn_checksum_md5, pool); 1400251881Speter 1401299742Sdim if (memcmp(empty_md5->digest, data_rep->md5_digest, 1402299742Sdim sizeof(data_rep->md5_digest))) 1403251881Speter { 1404299742Sdim /* Contents is not empty, i.e. EXPANDED_LENGTH cannot be the 1405299742Sdim actual file length. */ 1406299742Sdim *length = data_rep->size; 1407251881Speter } 1408251881Speter else 1409251881Speter { 1410299742Sdim /* Contents is empty. */ 1411299742Sdim *length = 0; 1412251881Speter } 1413251881Speter } 1414251881Speter 1415251881Speter return SVN_NO_ERROR; 1416251881Speter} 1417251881Speter 1418251881Spetersvn_boolean_t 1419251881Spetersvn_fs_fs__noderev_same_rep_key(representation_t *a, 1420251881Speter representation_t *b) 1421251881Speter{ 1422251881Speter if (a == b) 1423251881Speter return TRUE; 1424251881Speter 1425251881Speter if (a == NULL || b == NULL) 1426251881Speter return FALSE; 1427251881Speter 1428299742Sdim if (a->item_index != b->item_index) 1429251881Speter return FALSE; 1430251881Speter 1431251881Speter if (a->revision != b->revision) 1432251881Speter return FALSE; 1433251881Speter 1434299742Sdim return memcmp(&a->uniquifier, &b->uniquifier, sizeof(a->uniquifier)) == 0; 1435251881Speter} 1436251881Speter 1437251881Spetersvn_error_t * 1438299742Sdimsvn_fs_fs__file_text_rep_equal(svn_boolean_t *equal, 1439299742Sdim svn_fs_t *fs, 1440299742Sdim node_revision_t *a, 1441299742Sdim node_revision_t *b, 1442299742Sdim apr_pool_t *scratch_pool) 1443251881Speter{ 1444299742Sdim svn_stream_t *contents_a, *contents_b; 1445299742Sdim representation_t *rep_a = a->data_rep; 1446299742Sdim representation_t *rep_b = b->data_rep; 1447299742Sdim svn_boolean_t a_empty = !rep_a; 1448299742Sdim svn_boolean_t b_empty = !rep_b; 1449251881Speter 1450299742Sdim /* This makes sure that neither rep will be NULL later on */ 1451299742Sdim if (a_empty && b_empty) 1452251881Speter { 1453299742Sdim *equal = TRUE; 1454299742Sdim return SVN_NO_ERROR; 1455251881Speter } 1456251881Speter 1457299742Sdim /* Same path in same rev or txn? */ 1458299742Sdim if (svn_fs_fs__id_eq(a->id, b->id)) 1459251881Speter { 1460299742Sdim *equal = TRUE; 1461299742Sdim return SVN_NO_ERROR; 1462251881Speter } 1463251881Speter 1464299742Sdim /* Beware of the combination NULL rep and possibly empty rep. 1465299742Sdim * Due to EXPANDED_SIZE not being reliable, we can't easily detect empty 1466299742Sdim * reps. So, we can only take further shortcuts if both reps are given. */ 1467299742Sdim if (!a_empty && !b_empty) 1468251881Speter { 1469299742Sdim /* File text representations always know their checksums - 1470299742Sdim * even in a txn. */ 1471299742Sdim if (memcmp(rep_a->md5_digest, rep_b->md5_digest, 1472299742Sdim sizeof(rep_a->md5_digest))) 1473251881Speter { 1474299742Sdim *equal = FALSE; 1475251881Speter return SVN_NO_ERROR; 1476251881Speter } 1477251881Speter 1478299742Sdim /* Paranoia. Compare SHA1 checksums because that's the level of 1479299742Sdim confidence we require for e.g. the working copy. */ 1480299742Sdim if (rep_a->has_sha1 && rep_b->has_sha1) 1481251881Speter { 1482299742Sdim *equal = memcmp(rep_a->sha1_digest, rep_b->sha1_digest, 1483299742Sdim sizeof(rep_a->sha1_digest)) == 0; 1484251881Speter return SVN_NO_ERROR; 1485251881Speter } 1486251881Speter } 1487251881Speter 1488299742Sdim SVN_ERR(svn_fs_fs__get_contents(&contents_a, fs, rep_a, TRUE, 1489299742Sdim scratch_pool)); 1490299742Sdim SVN_ERR(svn_fs_fs__get_contents(&contents_b, fs, rep_b, TRUE, 1491299742Sdim scratch_pool)); 1492299742Sdim SVN_ERR(svn_stream_contents_same2(equal, contents_a, contents_b, 1493299742Sdim scratch_pool)); 1494251881Speter 1495251881Speter return SVN_NO_ERROR; 1496251881Speter} 1497251881Speter 1498251881Spetersvn_error_t * 1499299742Sdimsvn_fs_fs__prop_rep_equal(svn_boolean_t *equal, 1500299742Sdim svn_fs_t *fs, 1501299742Sdim node_revision_t *a, 1502299742Sdim node_revision_t *b, 1503299742Sdim apr_pool_t *scratch_pool) 1504251881Speter{ 1505299742Sdim representation_t *rep_a = a->prop_rep; 1506299742Sdim representation_t *rep_b = b->prop_rep; 1507299742Sdim apr_hash_t *proplist_a; 1508299742Sdim apr_hash_t *proplist_b; 1509251881Speter 1510299742Sdim /* Mainly for a==b==NULL */ 1511299742Sdim if (rep_a == rep_b) 1512251881Speter { 1513299742Sdim *equal = TRUE; 1514299742Sdim return SVN_NO_ERROR; 1515251881Speter } 1516251881Speter 1517299742Sdim /* Committed property lists can be compared quickly */ 1518299742Sdim if ( rep_a && rep_b 1519299742Sdim && !svn_fs_fs__id_txn_used(&rep_a->txn_id) 1520299742Sdim && !svn_fs_fs__id_txn_used(&rep_b->txn_id)) 1521251881Speter { 1522299742Sdim /* MD5 must be given. Having the same checksum is good enough for 1523299742Sdim accepting the prop lists as equal. */ 1524299742Sdim *equal = memcmp(rep_a->md5_digest, rep_b->md5_digest, 1525299742Sdim sizeof(rep_a->md5_digest)) == 0; 1526251881Speter return SVN_NO_ERROR; 1527251881Speter } 1528251881Speter 1529299742Sdim /* Same path in same txn? */ 1530299742Sdim if (svn_fs_fs__id_eq(a->id, b->id)) 1531251881Speter { 1532299742Sdim *equal = TRUE; 1533251881Speter return SVN_NO_ERROR; 1534251881Speter } 1535251881Speter 1536299742Sdim /* At least one of the reps has been modified in a txn. 1537299742Sdim Fetch and compare them. */ 1538299742Sdim SVN_ERR(svn_fs_fs__get_proplist(&proplist_a, fs, a, scratch_pool)); 1539299742Sdim SVN_ERR(svn_fs_fs__get_proplist(&proplist_b, fs, b, scratch_pool)); 1540251881Speter 1541299742Sdim *equal = svn_fs__prop_lists_equal(proplist_a, proplist_b, scratch_pool); 1542251881Speter return SVN_NO_ERROR; 1543251881Speter} 1544251881Speter 1545251881Speter 1546251881Spetersvn_error_t * 1547299742Sdimsvn_fs_fs__file_checksum(svn_checksum_t **checksum, 1548299742Sdim node_revision_t *noderev, 1549299742Sdim svn_checksum_kind_t kind, 1550299742Sdim apr_pool_t *pool) 1551251881Speter{ 1552299742Sdim *checksum = NULL; 1553251881Speter 1554299742Sdim if (noderev->data_rep) 1555251881Speter { 1556299742Sdim svn_checksum_t temp; 1557299742Sdim temp.kind = kind; 1558251881Speter 1559299742Sdim switch(kind) 1560251881Speter { 1561299742Sdim case svn_checksum_md5: 1562299742Sdim temp.digest = noderev->data_rep->md5_digest; 1563299742Sdim break; 1564251881Speter 1565299742Sdim case svn_checksum_sha1: 1566299742Sdim if (! noderev->data_rep->has_sha1) 1567299742Sdim return SVN_NO_ERROR; 1568251881Speter 1569299742Sdim temp.digest = noderev->data_rep->sha1_digest; 1570299742Sdim break; 1571251881Speter 1572299742Sdim default: 1573299742Sdim return SVN_NO_ERROR; 1574251881Speter } 1575251881Speter 1576299742Sdim *checksum = svn_checksum_dup(&temp, pool); 1577251881Speter } 1578251881Speter 1579251881Speter return SVN_NO_ERROR; 1580251881Speter} 1581251881Speter 1582299742Sdimrepresentation_t * 1583299742Sdimsvn_fs_fs__rep_copy(representation_t *rep, 1584251881Speter apr_pool_t *pool) 1585251881Speter{ 1586299742Sdim if (rep == NULL) 1587299742Sdim return NULL; 1588251881Speter 1589299742Sdim return apr_pmemdup(pool, rep, sizeof(*rep)); 1590251881Speter} 1591251881Speter 1592251881Speter 1593299742Sdim/* Write out the zeroth revision for filesystem FS. 1594299742Sdim Perform temporary allocations in SCRATCH_POOL. */ 1595251881Speterstatic svn_error_t * 1596299742Sdimwrite_revision_zero(svn_fs_t *fs, 1597299742Sdim apr_pool_t *scratch_pool) 1598251881Speter{ 1599299742Sdim /* Use an explicit sub-pool to have full control over temp file lifetimes. 1600299742Sdim * Since we have it, use it for everything else as well. */ 1601299742Sdim apr_pool_t *subpool = svn_pool_create(scratch_pool); 1602299742Sdim const char *path_revision_zero = svn_fs_fs__path_rev(fs, 0, subpool); 1603299742Sdim apr_hash_t *proplist; 1604251881Speter svn_string_t date; 1605251881Speter 1606299742Sdim /* Write out a rev file for revision 0. */ 1607299742Sdim if (svn_fs_fs__use_log_addressing(fs)) 1608251881Speter { 1609299742Sdim apr_array_header_t *index_entries; 1610299742Sdim svn_fs_fs__p2l_entry_t *entry; 1611299742Sdim svn_fs_fs__revision_file_t *rev_file; 1612299742Sdim const char *l2p_proto_index, *p2l_proto_index; 1613251881Speter 1614299742Sdim /* Write a skeleton r0 with no indexes. */ 1615299742Sdim SVN_ERR(svn_io_file_create(path_revision_zero, 1616299742Sdim "PLAIN\nEND\nENDREP\n" 1617299742Sdim "id: 0.0.r0/2\n" 1618299742Sdim "type: dir\n" 1619299742Sdim "count: 0\n" 1620299742Sdim "text: 0 3 4 4 " 1621299742Sdim "2d2977d1c96f487abe4a1e202dd03b4e\n" 1622299742Sdim "cpath: /\n" 1623299742Sdim "\n\n", subpool)); 1624251881Speter 1625299742Sdim /* Construct the index P2L contents: describe the 3 items we have. 1626299742Sdim Be sure to create them in on-disk order. */ 1627299742Sdim index_entries = apr_array_make(subpool, 3, sizeof(entry)); 1628251881Speter 1629299742Sdim entry = apr_pcalloc(subpool, sizeof(*entry)); 1630299742Sdim entry->offset = 0; 1631299742Sdim entry->size = 17; 1632299742Sdim entry->type = SVN_FS_FS__ITEM_TYPE_DIR_REP; 1633299742Sdim entry->item.revision = 0; 1634299742Sdim entry->item.number = SVN_FS_FS__ITEM_INDEX_FIRST_USER; 1635299742Sdim APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry; 1636251881Speter 1637299742Sdim entry = apr_pcalloc(subpool, sizeof(*entry)); 1638299742Sdim entry->offset = 17; 1639299742Sdim entry->size = 89; 1640299742Sdim entry->type = SVN_FS_FS__ITEM_TYPE_NODEREV; 1641299742Sdim entry->item.revision = 0; 1642299742Sdim entry->item.number = SVN_FS_FS__ITEM_INDEX_ROOT_NODE; 1643299742Sdim APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry; 1644251881Speter 1645299742Sdim entry = apr_pcalloc(subpool, sizeof(*entry)); 1646299742Sdim entry->offset = 106; 1647299742Sdim entry->size = 1; 1648299742Sdim entry->type = SVN_FS_FS__ITEM_TYPE_CHANGES; 1649299742Sdim entry->item.revision = 0; 1650299742Sdim entry->item.number = SVN_FS_FS__ITEM_INDEX_CHANGES; 1651299742Sdim APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry; 1652251881Speter 1653299742Sdim /* Now re-open r0, create proto-index files from our entries and 1654299742Sdim rewrite the index section of r0. */ 1655299742Sdim SVN_ERR(svn_fs_fs__open_pack_or_rev_file_writable(&rev_file, fs, 0, 1656299742Sdim subpool, subpool)); 1657299742Sdim SVN_ERR(svn_fs_fs__p2l_index_from_p2l_entries(&p2l_proto_index, fs, 1658299742Sdim rev_file, index_entries, 1659299742Sdim subpool, subpool)); 1660299742Sdim SVN_ERR(svn_fs_fs__l2p_index_from_p2l_entries(&l2p_proto_index, fs, 1661299742Sdim index_entries, 1662299742Sdim subpool, subpool)); 1663299742Sdim SVN_ERR(svn_fs_fs__add_index_data(fs, rev_file->file, l2p_proto_index, 1664299742Sdim p2l_proto_index, 0, subpool)); 1665299742Sdim SVN_ERR(svn_fs_fs__close_revision_file(rev_file)); 1666251881Speter } 1667251881Speter else 1668299742Sdim SVN_ERR(svn_io_file_create(path_revision_zero, 1669299742Sdim "PLAIN\nEND\nENDREP\n" 1670299742Sdim "id: 0.0.r0/17\n" 1671299742Sdim "type: dir\n" 1672299742Sdim "count: 0\n" 1673299742Sdim "text: 0 0 4 4 " 1674299742Sdim "2d2977d1c96f487abe4a1e202dd03b4e\n" 1675299742Sdim "cpath: /\n" 1676299742Sdim "\n\n17 107\n", subpool)); 1677251881Speter 1678299742Sdim SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, subpool)); 1679251881Speter 1680251881Speter /* Set a date on revision 0. */ 1681299742Sdim date.data = svn_time_to_cstring(apr_time_now(), subpool); 1682251881Speter date.len = strlen(date.data); 1683299742Sdim proplist = apr_hash_make(subpool); 1684251881Speter svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date); 1685299742Sdim SVN_ERR(svn_fs_fs__set_revision_proplist(fs, 0, proplist, subpool)); 1686299742Sdim 1687299742Sdim svn_pool_destroy(subpool); 1688299742Sdim return SVN_NO_ERROR; 1689251881Speter} 1690251881Speter 1691251881Spetersvn_error_t * 1692299742Sdimsvn_fs_fs__create_file_tree(svn_fs_t *fs, 1693299742Sdim const char *path, 1694299742Sdim int format, 1695299742Sdim int shard_size, 1696299742Sdim svn_boolean_t use_log_addressing, 1697299742Sdim apr_pool_t *pool) 1698251881Speter{ 1699251881Speter fs_fs_data_t *ffd = fs->fsap_data; 1700251881Speter 1701299742Sdim fs->path = apr_pstrdup(fs->pool, path); 1702251881Speter ffd->format = format; 1703251881Speter 1704299742Sdim /* Use an appropriate sharding mode if supported by the format. */ 1705251881Speter if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT) 1706299742Sdim ffd->max_files_per_dir = shard_size; 1707299742Sdim else 1708299742Sdim ffd->max_files_per_dir = 0; 1709251881Speter 1710299742Sdim /* Select the addressing mode depending on the format. */ 1711299742Sdim if (format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT) 1712299742Sdim ffd->use_log_addressing = use_log_addressing; 1713299742Sdim else 1714299742Sdim ffd->use_log_addressing = FALSE; 1715299742Sdim 1716251881Speter /* Create the revision data directories. */ 1717251881Speter if (ffd->max_files_per_dir) 1718299742Sdim SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_rev_shard(fs, 0, 1719299742Sdim pool), 1720299742Sdim pool)); 1721251881Speter else 1722251881Speter SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_REVS_DIR, 1723251881Speter pool), 1724251881Speter pool)); 1725251881Speter 1726251881Speter /* Create the revprops directory. */ 1727251881Speter if (ffd->max_files_per_dir) 1728299742Sdim SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_revprops_shard(fs, 0, 1729299742Sdim pool), 1730251881Speter pool)); 1731251881Speter else 1732251881Speter SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, 1733251881Speter PATH_REVPROPS_DIR, 1734251881Speter pool), 1735251881Speter pool)); 1736251881Speter 1737251881Speter /* Create the transaction directory. */ 1738299742Sdim SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_txns_dir(fs, pool), 1739251881Speter pool)); 1740251881Speter 1741251881Speter /* Create the protorevs directory. */ 1742251881Speter if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) 1743299742Sdim SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_txn_proto_revs(fs, 1744299742Sdim pool), 1745251881Speter pool)); 1746251881Speter 1747251881Speter /* Create the 'current' file. */ 1748299742Sdim SVN_ERR(svn_io_file_create_empty(svn_fs_fs__path_current(fs, pool), pool)); 1749299742Sdim SVN_ERR(svn_fs_fs__write_current(fs, 0, 1, 1, pool)); 1750251881Speter 1751299742Sdim /* Create the 'uuid' file. */ 1752299742Sdim SVN_ERR(svn_io_file_create_empty(svn_fs_fs__path_lock(fs, pool), pool)); 1753299742Sdim SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, NULL, pool)); 1754251881Speter 1755269847Speter /* Create the fsfs.conf file if supported. Older server versions would 1756269847Speter simply ignore the file but that might result in a different behavior 1757269847Speter than with the later releases. Also, hotcopy would ignore, i.e. not 1758269847Speter copy, a fsfs.conf with old formats. */ 1759269847Speter if (ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE) 1760269847Speter SVN_ERR(write_config(fs, pool)); 1761251881Speter 1762299742Sdim SVN_ERR(read_config(ffd, fs->path, fs->pool, pool)); 1763251881Speter 1764299742Sdim /* Global configuration options. */ 1765299742Sdim SVN_ERR(read_global_config(fs)); 1766299742Sdim 1767299742Sdim /* Add revision 0. */ 1768299742Sdim SVN_ERR(write_revision_zero(fs, pool)); 1769299742Sdim 1770251881Speter /* Create the min unpacked rev file. */ 1771251881Speter if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 1772299742Sdim SVN_ERR(svn_io_file_create(svn_fs_fs__path_min_unpacked_rev(fs, pool), 1773299742Sdim "0\n", pool)); 1774251881Speter 1775251881Speter /* Create the txn-current file if the repository supports 1776251881Speter the transaction sequence file. */ 1777251881Speter if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 1778251881Speter { 1779299742Sdim SVN_ERR(svn_io_file_create(svn_fs_fs__path_txn_current(fs, pool), 1780251881Speter "0\n", pool)); 1781299742Sdim SVN_ERR(svn_io_file_create_empty( 1782299742Sdim svn_fs_fs__path_txn_current_lock(fs, pool), 1783299742Sdim pool)); 1784251881Speter } 1785251881Speter 1786251881Speter ffd->youngest_rev_cache = 0; 1787251881Speter return SVN_NO_ERROR; 1788251881Speter} 1789251881Speter 1790299742Sdimsvn_error_t * 1791299742Sdimsvn_fs_fs__create(svn_fs_t *fs, 1792299742Sdim const char *path, 1793299742Sdim apr_pool_t *pool) 1794251881Speter{ 1795299742Sdim int format = SVN_FS_FS__FORMAT_NUMBER; 1796299742Sdim int shard_size = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR; 1797299742Sdim svn_boolean_t log_addressing; 1798251881Speter 1799299742Sdim /* Process the given filesystem config. */ 1800299742Sdim if (fs->config) 1801251881Speter { 1802299742Sdim svn_version_t *compatible_version; 1803299742Sdim const char *shard_size_str; 1804299742Sdim SVN_ERR(svn_fs__compatible_version(&compatible_version, fs->config, 1805299742Sdim pool)); 1806251881Speter 1807299742Sdim /* select format number */ 1808299742Sdim switch(compatible_version->minor) 1809251881Speter { 1810299742Sdim case 0: return svn_error_create(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL, 1811299742Sdim _("FSFS is not compatible with Subversion prior to 1.1")); 1812251881Speter 1813299742Sdim case 1: 1814299742Sdim case 2: 1815299742Sdim case 3: format = 1; 1816299742Sdim break; 1817251881Speter 1818299742Sdim case 4: format = 2; 1819299742Sdim break; 1820251881Speter 1821299742Sdim case 5: format = 3; 1822299742Sdim break; 1823251881Speter 1824299742Sdim case 6: 1825299742Sdim case 7: format = 4; 1826299742Sdim break; 1827251881Speter 1828299742Sdim case 8: format = 6; 1829299742Sdim break; 1830251881Speter 1831299742Sdim default:format = SVN_FS_FS__FORMAT_NUMBER; 1832251881Speter } 1833251881Speter 1834299742Sdim shard_size_str = svn_hash_gets(fs->config, SVN_FS_CONFIG_FSFS_SHARD_SIZE); 1835299742Sdim if (shard_size_str) 1836251881Speter { 1837299742Sdim apr_int64_t val; 1838299742Sdim SVN_ERR(svn_cstring_strtoi64(&val, shard_size_str, 0, 1839299742Sdim APR_INT32_MAX, 10)); 1840251881Speter 1841299742Sdim shard_size = (int) val; 1842251881Speter } 1843251881Speter } 1844251881Speter 1845299742Sdim log_addressing = svn_hash__get_bool(fs->config, 1846299742Sdim SVN_FS_CONFIG_FSFS_LOG_ADDRESSING, 1847299742Sdim TRUE); 1848251881Speter 1849299742Sdim /* Actual FS creation. */ 1850299742Sdim SVN_ERR(svn_fs_fs__create_file_tree(fs, path, format, shard_size, 1851299742Sdim log_addressing, pool)); 1852251881Speter 1853299742Sdim /* This filesystem is ready. Stamp it with a format number. */ 1854299742Sdim SVN_ERR(svn_fs_fs__write_format(fs, FALSE, pool)); 1855251881Speter 1856299742Sdim return SVN_NO_ERROR; 1857251881Speter} 1858251881Speter 1859251881Spetersvn_error_t * 1860251881Spetersvn_fs_fs__set_uuid(svn_fs_t *fs, 1861251881Speter const char *uuid, 1862299742Sdim const char *instance_id, 1863251881Speter apr_pool_t *pool) 1864251881Speter{ 1865299742Sdim fs_fs_data_t *ffd = fs->fsap_data; 1866251881Speter const char *uuid_path = path_uuid(fs, pool); 1867299742Sdim svn_stringbuf_t *contents = svn_stringbuf_create_empty(pool); 1868251881Speter 1869251881Speter if (! uuid) 1870251881Speter uuid = svn_uuid_generate(pool); 1871251881Speter 1872299742Sdim if (! instance_id) 1873299742Sdim instance_id = svn_uuid_generate(pool); 1874251881Speter 1875299742Sdim svn_stringbuf_appendcstr(contents, uuid); 1876299742Sdim svn_stringbuf_appendcstr(contents, "\n"); 1877251881Speter 1878299742Sdim if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT) 1879299742Sdim { 1880299742Sdim svn_stringbuf_appendcstr(contents, instance_id); 1881299742Sdim svn_stringbuf_appendcstr(contents, "\n"); 1882299742Sdim } 1883299742Sdim 1884251881Speter /* We use the permissions of the 'current' file, because the 'uuid' 1885251881Speter file does not exist during repository creation. */ 1886299742Sdim SVN_ERR(svn_io_write_atomic(uuid_path, contents->data, contents->len, 1887299742Sdim svn_fs_fs__path_current(fs, pool) /* perms */, 1888299742Sdim pool)); 1889251881Speter 1890299742Sdim fs->uuid = apr_pstrdup(fs->pool, uuid); 1891251881Speter 1892299742Sdim if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT) 1893299742Sdim ffd->instance_id = apr_pstrdup(fs->pool, instance_id); 1894299742Sdim else 1895299742Sdim ffd->instance_id = fs->uuid; 1896299742Sdim 1897251881Speter return SVN_NO_ERROR; 1898251881Speter} 1899251881Speter 1900251881Speter/** Node origin lazy cache. */ 1901251881Speter 1902251881Speter/* If directory PATH does not exist, create it and give it the same 1903251881Speter permissions as FS_path.*/ 1904251881Spetersvn_error_t * 1905251881Spetersvn_fs_fs__ensure_dir_exists(const char *path, 1906251881Speter const char *fs_path, 1907251881Speter apr_pool_t *pool) 1908251881Speter{ 1909251881Speter svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool); 1910251881Speter if (err && APR_STATUS_IS_EEXIST(err->apr_err)) 1911251881Speter { 1912251881Speter svn_error_clear(err); 1913251881Speter return SVN_NO_ERROR; 1914251881Speter } 1915251881Speter SVN_ERR(err); 1916251881Speter 1917251881Speter /* We successfully created a new directory. Dup the permissions 1918251881Speter from FS->path. */ 1919251881Speter return svn_io_copy_perms(fs_path, path, pool); 1920251881Speter} 1921251881Speter 1922251881Speter/* Set *NODE_ORIGINS to a hash mapping 'const char *' node IDs to 1923251881Speter 'svn_string_t *' node revision IDs. Use POOL for allocations. */ 1924251881Speterstatic svn_error_t * 1925251881Speterget_node_origins_from_file(svn_fs_t *fs, 1926251881Speter apr_hash_t **node_origins, 1927251881Speter const char *node_origins_file, 1928251881Speter apr_pool_t *pool) 1929251881Speter{ 1930251881Speter apr_file_t *fd; 1931251881Speter svn_error_t *err; 1932251881Speter svn_stream_t *stream; 1933251881Speter 1934251881Speter *node_origins = NULL; 1935251881Speter err = svn_io_file_open(&fd, node_origins_file, 1936251881Speter APR_READ, APR_OS_DEFAULT, pool); 1937251881Speter if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 1938251881Speter { 1939251881Speter svn_error_clear(err); 1940251881Speter return SVN_NO_ERROR; 1941251881Speter } 1942251881Speter SVN_ERR(err); 1943251881Speter 1944251881Speter stream = svn_stream_from_aprfile2(fd, FALSE, pool); 1945251881Speter *node_origins = apr_hash_make(pool); 1946299742Sdim err = svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool); 1947299742Sdim if (err) 1948299742Sdim return svn_error_quick_wrapf(err, _("malformed node origin data in '%s'"), 1949299742Sdim node_origins_file); 1950251881Speter return svn_stream_close(stream); 1951251881Speter} 1952251881Speter 1953251881Spetersvn_error_t * 1954251881Spetersvn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id, 1955251881Speter svn_fs_t *fs, 1956299742Sdim const svn_fs_fs__id_part_t *node_id, 1957251881Speter apr_pool_t *pool) 1958251881Speter{ 1959251881Speter apr_hash_t *node_origins; 1960251881Speter 1961251881Speter *origin_id = NULL; 1962251881Speter SVN_ERR(get_node_origins_from_file(fs, &node_origins, 1963299742Sdim svn_fs_fs__path_node_origin(fs, node_id, 1964299742Sdim pool), 1965251881Speter pool)); 1966251881Speter if (node_origins) 1967251881Speter { 1968299742Sdim char node_id_ptr[SVN_INT64_BUFFER_SIZE]; 1969299742Sdim apr_size_t len = svn__ui64tobase36(node_id_ptr, node_id->number); 1970299742Sdim svn_string_t *origin_id_str 1971299742Sdim = apr_hash_get(node_origins, node_id_ptr, len); 1972299742Sdim 1973251881Speter if (origin_id_str) 1974299742Sdim SVN_ERR(svn_fs_fs__id_parse(origin_id, 1975299742Sdim apr_pstrdup(pool, origin_id_str->data), 1976299742Sdim pool)); 1977251881Speter } 1978251881Speter return SVN_NO_ERROR; 1979251881Speter} 1980251881Speter 1981251881Speter 1982251881Speter/* Helper for svn_fs_fs__set_node_origin. Takes a NODE_ID/NODE_REV_ID 1983251881Speter pair and adds it to the NODE_ORIGINS_PATH file. */ 1984251881Speterstatic svn_error_t * 1985251881Speterset_node_origins_for_file(svn_fs_t *fs, 1986251881Speter const char *node_origins_path, 1987299742Sdim const svn_fs_fs__id_part_t *node_id, 1988251881Speter svn_string_t *node_rev_id, 1989251881Speter apr_pool_t *pool) 1990251881Speter{ 1991251881Speter const char *path_tmp; 1992251881Speter svn_stream_t *stream; 1993251881Speter apr_hash_t *origins_hash; 1994251881Speter svn_string_t *old_node_rev_id; 1995251881Speter 1996299742Sdim /* the hash serialization functions require strings as keys */ 1997299742Sdim char node_id_ptr[SVN_INT64_BUFFER_SIZE]; 1998299742Sdim apr_size_t len = svn__ui64tobase36(node_id_ptr, node_id->number); 1999299742Sdim 2000251881Speter SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs->path, 2001251881Speter PATH_NODE_ORIGINS_DIR, 2002251881Speter pool), 2003251881Speter fs->path, pool)); 2004251881Speter 2005251881Speter /* Read the previously existing origins (if any), and merge our 2006251881Speter update with it. */ 2007251881Speter SVN_ERR(get_node_origins_from_file(fs, &origins_hash, 2008251881Speter node_origins_path, pool)); 2009251881Speter if (! origins_hash) 2010251881Speter origins_hash = apr_hash_make(pool); 2011251881Speter 2012299742Sdim old_node_rev_id = apr_hash_get(origins_hash, node_id_ptr, len); 2013251881Speter 2014251881Speter if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id)) 2015251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2016251881Speter _("Node origin for '%s' exists with a different " 2017251881Speter "value (%s) than what we were about to store " 2018251881Speter "(%s)"), 2019299742Sdim node_id_ptr, old_node_rev_id->data, 2020299742Sdim node_rev_id->data); 2021251881Speter 2022299742Sdim apr_hash_set(origins_hash, node_id_ptr, len, node_rev_id); 2023251881Speter 2024251881Speter /* Sure, there's a race condition here. Two processes could be 2025251881Speter trying to add different cache elements to the same file at the 2026251881Speter same time, and the entries added by the first one to write will 2027251881Speter be lost. But this is just a cache of reconstructible data, so 2028251881Speter we'll accept this problem in return for not having to deal with 2029251881Speter locking overhead. */ 2030251881Speter 2031251881Speter /* Create a temporary file, write out our hash, and close the file. */ 2032251881Speter SVN_ERR(svn_stream_open_unique(&stream, &path_tmp, 2033251881Speter svn_dirent_dirname(node_origins_path, pool), 2034251881Speter svn_io_file_del_none, pool, pool)); 2035251881Speter SVN_ERR(svn_hash_write2(origins_hash, stream, SVN_HASH_TERMINATOR, pool)); 2036251881Speter SVN_ERR(svn_stream_close(stream)); 2037251881Speter 2038251881Speter /* Rename the temp file as the real destination */ 2039251881Speter return svn_io_file_rename(path_tmp, node_origins_path, pool); 2040251881Speter} 2041251881Speter 2042251881Speter 2043251881Spetersvn_error_t * 2044251881Spetersvn_fs_fs__set_node_origin(svn_fs_t *fs, 2045299742Sdim const svn_fs_fs__id_part_t *node_id, 2046251881Speter const svn_fs_id_t *node_rev_id, 2047251881Speter apr_pool_t *pool) 2048251881Speter{ 2049251881Speter svn_error_t *err; 2050299742Sdim const char *filename = svn_fs_fs__path_node_origin(fs, node_id, pool); 2051251881Speter 2052251881Speter err = set_node_origins_for_file(fs, filename, 2053251881Speter node_id, 2054251881Speter svn_fs_fs__id_unparse(node_rev_id, pool), 2055251881Speter pool); 2056251881Speter if (err && APR_STATUS_IS_EACCES(err->apr_err)) 2057251881Speter { 2058251881Speter /* It's just a cache; stop trying if I can't write. */ 2059251881Speter svn_error_clear(err); 2060251881Speter err = NULL; 2061251881Speter } 2062251881Speter return svn_error_trace(err); 2063251881Speter} 2064251881Speter 2065251881Speter 2066251881Speter 2067251881Speter/*** Revisions ***/ 2068251881Speter 2069251881Spetersvn_error_t * 2070251881Spetersvn_fs_fs__revision_prop(svn_string_t **value_p, 2071251881Speter svn_fs_t *fs, 2072251881Speter svn_revnum_t rev, 2073251881Speter const char *propname, 2074251881Speter apr_pool_t *pool) 2075251881Speter{ 2076251881Speter apr_hash_t *table; 2077251881Speter 2078251881Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 2079299742Sdim SVN_ERR(svn_fs_fs__get_revision_proplist(&table, fs, rev, pool)); 2080251881Speter 2081251881Speter *value_p = svn_hash_gets(table, propname); 2082251881Speter 2083251881Speter return SVN_NO_ERROR; 2084251881Speter} 2085251881Speter 2086251881Speter 2087251881Speter/* Baton used for change_rev_prop_body below. */ 2088251881Speterstruct change_rev_prop_baton { 2089251881Speter svn_fs_t *fs; 2090251881Speter svn_revnum_t rev; 2091251881Speter const char *name; 2092251881Speter const svn_string_t *const *old_value_p; 2093251881Speter const svn_string_t *value; 2094251881Speter}; 2095251881Speter 2096251881Speter/* The work-horse for svn_fs_fs__change_rev_prop, called with the FS 2097251881Speter write lock. This implements the svn_fs_fs__with_write_lock() 2098251881Speter 'body' callback type. BATON is a 'struct change_rev_prop_baton *'. */ 2099251881Speterstatic svn_error_t * 2100251881Speterchange_rev_prop_body(void *baton, apr_pool_t *pool) 2101251881Speter{ 2102251881Speter struct change_rev_prop_baton *cb = baton; 2103251881Speter apr_hash_t *table; 2104251881Speter 2105299742Sdim SVN_ERR(svn_fs_fs__get_revision_proplist(&table, cb->fs, cb->rev, pool)); 2106251881Speter 2107251881Speter if (cb->old_value_p) 2108251881Speter { 2109251881Speter const svn_string_t *wanted_value = *cb->old_value_p; 2110251881Speter const svn_string_t *present_value = svn_hash_gets(table, cb->name); 2111251881Speter if ((!wanted_value != !present_value) 2112251881Speter || (wanted_value && present_value 2113251881Speter && !svn_string_compare(wanted_value, present_value))) 2114251881Speter { 2115251881Speter /* What we expected isn't what we found. */ 2116251881Speter return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL, 2117251881Speter _("revprop '%s' has unexpected value in " 2118251881Speter "filesystem"), 2119251881Speter cb->name); 2120251881Speter } 2121251881Speter /* Fall through. */ 2122251881Speter } 2123251881Speter svn_hash_sets(table, cb->name, cb->value); 2124251881Speter 2125299742Sdim return svn_fs_fs__set_revision_proplist(cb->fs, cb->rev, table, pool); 2126251881Speter} 2127251881Speter 2128251881Spetersvn_error_t * 2129251881Spetersvn_fs_fs__change_rev_prop(svn_fs_t *fs, 2130251881Speter svn_revnum_t rev, 2131251881Speter const char *name, 2132251881Speter const svn_string_t *const *old_value_p, 2133251881Speter const svn_string_t *value, 2134251881Speter apr_pool_t *pool) 2135251881Speter{ 2136251881Speter struct change_rev_prop_baton cb; 2137251881Speter 2138251881Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 2139251881Speter 2140251881Speter cb.fs = fs; 2141251881Speter cb.rev = rev; 2142251881Speter cb.name = name; 2143251881Speter cb.old_value_p = old_value_p; 2144251881Speter cb.value = value; 2145251881Speter 2146251881Speter return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool); 2147251881Speter} 2148251881Speter 2149251881Speter 2150251881Spetersvn_error_t * 2151299742Sdimsvn_fs_fs__info_format(int *fs_format, 2152299742Sdim svn_version_t **supports_version, 2153251881Speter svn_fs_t *fs, 2154299742Sdim apr_pool_t *result_pool, 2155299742Sdim apr_pool_t *scratch_pool) 2156251881Speter{ 2157299742Sdim fs_fs_data_t *ffd = fs->fsap_data; 2158299742Sdim *fs_format = ffd->format; 2159299742Sdim *supports_version = apr_palloc(result_pool, sizeof(svn_version_t)); 2160251881Speter 2161299742Sdim (*supports_version)->major = SVN_VER_MAJOR; 2162299742Sdim (*supports_version)->minor = 1; 2163299742Sdim (*supports_version)->patch = 0; 2164299742Sdim (*supports_version)->tag = ""; 2165251881Speter 2166299742Sdim switch (ffd->format) 2167251881Speter { 2168299742Sdim case 1: 2169299742Sdim break; 2170299742Sdim case 2: 2171299742Sdim (*supports_version)->minor = 4; 2172299742Sdim break; 2173299742Sdim case 3: 2174299742Sdim (*supports_version)->minor = 5; 2175299742Sdim break; 2176299742Sdim case 4: 2177299742Sdim (*supports_version)->minor = 6; 2178299742Sdim break; 2179299742Sdim case 6: 2180299742Sdim (*supports_version)->minor = 8; 2181299742Sdim break; 2182299742Sdim case 7: 2183299742Sdim (*supports_version)->minor = 9; 2184299742Sdim break; 2185299742Sdim#ifdef SVN_DEBUG 2186299742Sdim# if SVN_FS_FS__FORMAT_NUMBER != 7 2187299742Sdim# error "Need to add a 'case' statement here" 2188299742Sdim# endif 2189299742Sdim#endif 2190251881Speter } 2191251881Speter 2192251881Speter return SVN_NO_ERROR; 2193251881Speter} 2194251881Speter 2195251881Spetersvn_error_t * 2196299742Sdimsvn_fs_fs__info_config_files(apr_array_header_t **files, 2197299742Sdim svn_fs_t *fs, 2198299742Sdim apr_pool_t *result_pool, 2199262253Speter apr_pool_t *scratch_pool) 2200262253Speter{ 2201299742Sdim *files = apr_array_make(result_pool, 1, sizeof(const char *)); 2202299742Sdim APR_ARRAY_PUSH(*files, const char *) = svn_dirent_join(fs->path, PATH_CONFIG, 2203299742Sdim result_pool); 2204262253Speter return SVN_NO_ERROR; 2205262253Speter} 2206