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 23251881Speter#include <stdlib.h> 24251881Speter#include <stdio.h> 25251881Speter#include <string.h> 26251881Speter#include <ctype.h> 27251881Speter#include <assert.h> 28251881Speter#include <errno.h> 29251881Speter 30251881Speter#include <apr_general.h> 31251881Speter#include <apr_pools.h> 32251881Speter#include <apr_file_io.h> 33251881Speter#include <apr_uuid.h> 34251881Speter#include <apr_lib.h> 35251881Speter#include <apr_md5.h> 36251881Speter#include <apr_sha1.h> 37251881Speter#include <apr_strings.h> 38251881Speter#include <apr_thread_mutex.h> 39251881Speter 40251881Speter#include "svn_pools.h" 41251881Speter#include "svn_fs.h" 42251881Speter#include "svn_dirent_uri.h" 43251881Speter#include "svn_path.h" 44251881Speter#include "svn_hash.h" 45251881Speter#include "svn_props.h" 46251881Speter#include "svn_sorts.h" 47251881Speter#include "svn_string.h" 48251881Speter#include "svn_time.h" 49251881Speter#include "svn_mergeinfo.h" 50251881Speter#include "svn_config.h" 51251881Speter#include "svn_ctype.h" 52251881Speter#include "svn_version.h" 53251881Speter 54251881Speter#include "fs.h" 55251881Speter#include "tree.h" 56251881Speter#include "lock.h" 57251881Speter#include "key-gen.h" 58251881Speter#include "fs_fs.h" 59251881Speter#include "id.h" 60251881Speter#include "rep-cache.h" 61251881Speter#include "temp_serializer.h" 62251881Speter 63251881Speter#include "private/svn_string_private.h" 64251881Speter#include "private/svn_fs_util.h" 65251881Speter#include "private/svn_subr_private.h" 66251881Speter#include "private/svn_delta_private.h" 67251881Speter#include "../libsvn_fs/fs-loader.h" 68251881Speter 69251881Speter#include "svn_private_config.h" 70251881Speter#include "temp_serializer.h" 71251881Speter 72251881Speter/* An arbitrary maximum path length, so clients can't run us out of memory 73251881Speter * by giving us arbitrarily large paths. */ 74251881Speter#define FSFS_MAX_PATH_LEN 4096 75251881Speter 76251881Speter/* The default maximum number of files per directory to store in the 77251881Speter rev and revprops directory. The number below is somewhat arbitrary, 78251881Speter and can be overridden by defining the macro while compiling; the 79251881Speter figure of 1000 is reasonable for VFAT filesystems, which are by far 80251881Speter the worst performers in this area. */ 81251881Speter#ifndef SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR 82251881Speter#define SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR 1000 83251881Speter#endif 84251881Speter 85251881Speter/* Begin deltification after a node history exceeded this this limit. 86251881Speter Useful values are 4 to 64 with 16 being a good compromise between 87251881Speter computational overhead and repository size savings. 88251881Speter Should be a power of 2. 89251881Speter Values < 2 will result in standard skip-delta behavior. */ 90251881Speter#define SVN_FS_FS_MAX_LINEAR_DELTIFICATION 16 91251881Speter 92251881Speter/* Finding a deltification base takes operations proportional to the 93251881Speter number of changes being skipped. To prevent exploding runtime 94251881Speter during commits, limit the deltification range to this value. 95251881Speter Should be a power of 2 minus one. 96251881Speter Values < 1 disable deltification. */ 97251881Speter#define SVN_FS_FS_MAX_DELTIFICATION_WALK 1023 98251881Speter 99251881Speter/* Give writing processes 10 seconds to replace an existing revprop 100251881Speter file with a new one. After that time, we assume that the writing 101251881Speter process got aborted and that we have re-read revprops. */ 102251881Speter#define REVPROP_CHANGE_TIMEOUT (10 * 1000000) 103251881Speter 104251881Speter/* The following are names of atomics that will be used to communicate 105251881Speter * revprop updates across all processes on this machine. */ 106251881Speter#define ATOMIC_REVPROP_GENERATION "rev-prop-generation" 107251881Speter#define ATOMIC_REVPROP_TIMEOUT "rev-prop-timeout" 108251881Speter#define ATOMIC_REVPROP_NAMESPACE "rev-prop-atomics" 109251881Speter 110251881Speter/* Following are defines that specify the textual elements of the 111251881Speter native filesystem directories and revision files. */ 112251881Speter 113251881Speter/* Headers used to describe node-revision in the revision file. */ 114251881Speter#define HEADER_ID "id" 115251881Speter#define HEADER_TYPE "type" 116251881Speter#define HEADER_COUNT "count" 117251881Speter#define HEADER_PROPS "props" 118251881Speter#define HEADER_TEXT "text" 119251881Speter#define HEADER_CPATH "cpath" 120251881Speter#define HEADER_PRED "pred" 121251881Speter#define HEADER_COPYFROM "copyfrom" 122251881Speter#define HEADER_COPYROOT "copyroot" 123251881Speter#define HEADER_FRESHTXNRT "is-fresh-txn-root" 124251881Speter#define HEADER_MINFO_HERE "minfo-here" 125251881Speter#define HEADER_MINFO_CNT "minfo-cnt" 126251881Speter 127251881Speter/* Kinds that a change can be. */ 128251881Speter#define ACTION_MODIFY "modify" 129251881Speter#define ACTION_ADD "add" 130251881Speter#define ACTION_DELETE "delete" 131251881Speter#define ACTION_REPLACE "replace" 132251881Speter#define ACTION_RESET "reset" 133251881Speter 134251881Speter/* True and False flags. */ 135251881Speter#define FLAG_TRUE "true" 136251881Speter#define FLAG_FALSE "false" 137251881Speter 138251881Speter/* Kinds that a node-rev can be. */ 139251881Speter#define KIND_FILE "file" 140251881Speter#define KIND_DIR "dir" 141251881Speter 142251881Speter/* Kinds of representation. */ 143251881Speter#define REP_PLAIN "PLAIN" 144251881Speter#define REP_DELTA "DELTA" 145251881Speter 146251881Speter/* Notes: 147251881Speter 148251881SpeterTo avoid opening and closing the rev-files all the time, it would 149251881Speterprobably be advantageous to keep each rev-file open for the 150251881Speterlifetime of the transaction object. I'll leave that as a later 151251881Speteroptimization for now. 152251881Speter 153251881SpeterI didn't keep track of pool lifetimes at all in this code. There 154251881Speterare likely some errors because of that. 155251881Speter 156251881Speter*/ 157251881Speter 158251881Speter/* The vtable associated with an open transaction object. */ 159251881Speterstatic txn_vtable_t txn_vtable = { 160251881Speter svn_fs_fs__commit_txn, 161251881Speter svn_fs_fs__abort_txn, 162251881Speter svn_fs_fs__txn_prop, 163251881Speter svn_fs_fs__txn_proplist, 164251881Speter svn_fs_fs__change_txn_prop, 165251881Speter svn_fs_fs__txn_root, 166251881Speter svn_fs_fs__change_txn_props 167251881Speter}; 168251881Speter 169251881Speter/* Declarations. */ 170251881Speter 171251881Speterstatic svn_error_t * 172251881Speterread_min_unpacked_rev(svn_revnum_t *min_unpacked_rev, 173251881Speter const char *path, 174251881Speter apr_pool_t *pool); 175251881Speter 176251881Speterstatic svn_error_t * 177251881Speterupdate_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool); 178251881Speter 179251881Speterstatic svn_error_t * 180251881Speterget_youngest(svn_revnum_t *youngest_p, const char *fs_path, apr_pool_t *pool); 181251881Speter 182251881Speterstatic svn_error_t * 183251881Speterverify_walker(representation_t *rep, 184251881Speter void *baton, 185251881Speter svn_fs_t *fs, 186251881Speter apr_pool_t *scratch_pool); 187251881Speter 188251881Speter/* Pathname helper functions */ 189251881Speter 190251881Speter/* Return TRUE is REV is packed in FS, FALSE otherwise. */ 191251881Speterstatic svn_boolean_t 192251881Speteris_packed_rev(svn_fs_t *fs, svn_revnum_t rev) 193251881Speter{ 194251881Speter fs_fs_data_t *ffd = fs->fsap_data; 195251881Speter 196251881Speter return (rev < ffd->min_unpacked_rev); 197251881Speter} 198251881Speter 199251881Speter/* Return TRUE is REV is packed in FS, FALSE otherwise. */ 200251881Speterstatic svn_boolean_t 201251881Speteris_packed_revprop(svn_fs_t *fs, svn_revnum_t rev) 202251881Speter{ 203251881Speter fs_fs_data_t *ffd = fs->fsap_data; 204251881Speter 205251881Speter /* rev 0 will not be packed */ 206251881Speter return (rev < ffd->min_unpacked_rev) 207251881Speter && (rev != 0) 208251881Speter && (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT); 209251881Speter} 210251881Speter 211251881Speterstatic const char * 212251881Speterpath_format(svn_fs_t *fs, apr_pool_t *pool) 213251881Speter{ 214251881Speter return svn_dirent_join(fs->path, PATH_FORMAT, pool); 215251881Speter} 216251881Speter 217251881Speterstatic APR_INLINE const char * 218251881Speterpath_uuid(svn_fs_t *fs, apr_pool_t *pool) 219251881Speter{ 220251881Speter return svn_dirent_join(fs->path, PATH_UUID, pool); 221251881Speter} 222251881Speter 223251881Speterconst char * 224251881Spetersvn_fs_fs__path_current(svn_fs_t *fs, apr_pool_t *pool) 225251881Speter{ 226251881Speter return svn_dirent_join(fs->path, PATH_CURRENT, pool); 227251881Speter} 228251881Speter 229251881Speterstatic APR_INLINE const char * 230251881Speterpath_txn_current(svn_fs_t *fs, apr_pool_t *pool) 231251881Speter{ 232251881Speter return svn_dirent_join(fs->path, PATH_TXN_CURRENT, pool); 233251881Speter} 234251881Speter 235251881Speterstatic APR_INLINE const char * 236251881Speterpath_txn_current_lock(svn_fs_t *fs, apr_pool_t *pool) 237251881Speter{ 238251881Speter return svn_dirent_join(fs->path, PATH_TXN_CURRENT_LOCK, pool); 239251881Speter} 240251881Speter 241251881Speterstatic APR_INLINE const char * 242251881Speterpath_lock(svn_fs_t *fs, apr_pool_t *pool) 243251881Speter{ 244251881Speter return svn_dirent_join(fs->path, PATH_LOCK_FILE, pool); 245251881Speter} 246251881Speter 247251881Speterstatic const char * 248251881Speterpath_revprop_generation(svn_fs_t *fs, apr_pool_t *pool) 249251881Speter{ 250251881Speter return svn_dirent_join(fs->path, PATH_REVPROP_GENERATION, pool); 251251881Speter} 252251881Speter 253251881Speterstatic const char * 254251881Speterpath_rev_packed(svn_fs_t *fs, svn_revnum_t rev, const char *kind, 255251881Speter apr_pool_t *pool) 256251881Speter{ 257251881Speter fs_fs_data_t *ffd = fs->fsap_data; 258251881Speter 259251881Speter assert(ffd->max_files_per_dir); 260251881Speter assert(is_packed_rev(fs, rev)); 261251881Speter 262251881Speter return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR, 263251881Speter apr_psprintf(pool, 264251881Speter "%ld" PATH_EXT_PACKED_SHARD, 265251881Speter rev / ffd->max_files_per_dir), 266251881Speter kind, NULL); 267251881Speter} 268251881Speter 269251881Speterstatic const char * 270251881Speterpath_rev_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool) 271251881Speter{ 272251881Speter fs_fs_data_t *ffd = fs->fsap_data; 273251881Speter 274251881Speter assert(ffd->max_files_per_dir); 275251881Speter return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR, 276251881Speter apr_psprintf(pool, "%ld", 277251881Speter rev / ffd->max_files_per_dir), 278251881Speter NULL); 279251881Speter} 280251881Speter 281251881Speterstatic const char * 282251881Speterpath_rev(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool) 283251881Speter{ 284251881Speter fs_fs_data_t *ffd = fs->fsap_data; 285251881Speter 286251881Speter assert(! is_packed_rev(fs, rev)); 287251881Speter 288251881Speter if (ffd->max_files_per_dir) 289251881Speter { 290251881Speter return svn_dirent_join(path_rev_shard(fs, rev, pool), 291251881Speter apr_psprintf(pool, "%ld", rev), 292251881Speter pool); 293251881Speter } 294251881Speter 295251881Speter return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR, 296251881Speter apr_psprintf(pool, "%ld", rev), NULL); 297251881Speter} 298251881Speter 299251881Spetersvn_error_t * 300251881Spetersvn_fs_fs__path_rev_absolute(const char **path, 301251881Speter svn_fs_t *fs, 302251881Speter svn_revnum_t rev, 303251881Speter apr_pool_t *pool) 304251881Speter{ 305251881Speter fs_fs_data_t *ffd = fs->fsap_data; 306251881Speter 307251881Speter if (ffd->format < SVN_FS_FS__MIN_PACKED_FORMAT 308251881Speter || ! is_packed_rev(fs, rev)) 309251881Speter { 310251881Speter *path = path_rev(fs, rev, pool); 311251881Speter } 312251881Speter else 313251881Speter { 314251881Speter *path = path_rev_packed(fs, rev, PATH_PACKED, pool); 315251881Speter } 316251881Speter 317251881Speter return SVN_NO_ERROR; 318251881Speter} 319251881Speter 320251881Speterstatic const char * 321251881Speterpath_revprops_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool) 322251881Speter{ 323251881Speter fs_fs_data_t *ffd = fs->fsap_data; 324251881Speter 325251881Speter assert(ffd->max_files_per_dir); 326251881Speter return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR, 327251881Speter apr_psprintf(pool, "%ld", 328251881Speter rev / ffd->max_files_per_dir), 329251881Speter NULL); 330251881Speter} 331251881Speter 332251881Speterstatic const char * 333251881Speterpath_revprops_pack_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool) 334251881Speter{ 335251881Speter fs_fs_data_t *ffd = fs->fsap_data; 336251881Speter 337251881Speter assert(ffd->max_files_per_dir); 338251881Speter return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR, 339251881Speter apr_psprintf(pool, "%ld" PATH_EXT_PACKED_SHARD, 340251881Speter rev / ffd->max_files_per_dir), 341251881Speter NULL); 342251881Speter} 343251881Speter 344251881Speterstatic const char * 345251881Speterpath_revprops(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool) 346251881Speter{ 347251881Speter fs_fs_data_t *ffd = fs->fsap_data; 348251881Speter 349251881Speter if (ffd->max_files_per_dir) 350251881Speter { 351251881Speter return svn_dirent_join(path_revprops_shard(fs, rev, pool), 352251881Speter apr_psprintf(pool, "%ld", rev), 353251881Speter pool); 354251881Speter } 355251881Speter 356251881Speter return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR, 357251881Speter apr_psprintf(pool, "%ld", rev), NULL); 358251881Speter} 359251881Speter 360251881Speterstatic APR_INLINE const char * 361251881Speterpath_txn_dir(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool) 362251881Speter{ 363251881Speter SVN_ERR_ASSERT_NO_RETURN(txn_id != NULL); 364251881Speter return svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR, 365251881Speter apr_pstrcat(pool, txn_id, PATH_EXT_TXN, 366251881Speter (char *)NULL), 367251881Speter NULL); 368251881Speter} 369251881Speter 370251881Speter/* Return the name of the sha1->rep mapping file in transaction TXN_ID 371251881Speter * within FS for the given SHA1 checksum. Use POOL for allocations. 372251881Speter */ 373251881Speterstatic APR_INLINE const char * 374251881Speterpath_txn_sha1(svn_fs_t *fs, const char *txn_id, svn_checksum_t *sha1, 375251881Speter apr_pool_t *pool) 376251881Speter{ 377251881Speter return svn_dirent_join(path_txn_dir(fs, txn_id, pool), 378251881Speter svn_checksum_to_cstring(sha1, pool), 379251881Speter pool); 380251881Speter} 381251881Speter 382251881Speterstatic APR_INLINE const char * 383251881Speterpath_txn_changes(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool) 384251881Speter{ 385251881Speter return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_CHANGES, pool); 386251881Speter} 387251881Speter 388251881Speterstatic APR_INLINE const char * 389251881Speterpath_txn_props(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool) 390251881Speter{ 391251881Speter return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_TXN_PROPS, pool); 392251881Speter} 393251881Speter 394251881Speterstatic APR_INLINE const char * 395251881Speterpath_txn_next_ids(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool) 396251881Speter{ 397251881Speter return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_NEXT_IDS, pool); 398251881Speter} 399251881Speter 400251881Speterstatic APR_INLINE const char * 401251881Speterpath_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool) 402251881Speter{ 403251881Speter return svn_dirent_join(fs->path, PATH_MIN_UNPACKED_REV, pool); 404251881Speter} 405251881Speter 406251881Speter 407251881Speterstatic APR_INLINE const char * 408251881Speterpath_txn_proto_rev(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool) 409251881Speter{ 410251881Speter fs_fs_data_t *ffd = fs->fsap_data; 411251881Speter if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) 412251881Speter return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR, 413251881Speter apr_pstrcat(pool, txn_id, PATH_EXT_REV, 414251881Speter (char *)NULL), 415251881Speter NULL); 416251881Speter else 417251881Speter return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV, pool); 418251881Speter} 419251881Speter 420251881Speterstatic APR_INLINE const char * 421251881Speterpath_txn_proto_rev_lock(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool) 422251881Speter{ 423251881Speter fs_fs_data_t *ffd = fs->fsap_data; 424251881Speter if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) 425251881Speter return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR, 426251881Speter apr_pstrcat(pool, txn_id, PATH_EXT_REV_LOCK, 427251881Speter (char *)NULL), 428251881Speter NULL); 429251881Speter else 430251881Speter return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV_LOCK, 431251881Speter pool); 432251881Speter} 433251881Speter 434251881Speterstatic const char * 435251881Speterpath_txn_node_rev(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool) 436251881Speter{ 437251881Speter const char *txn_id = svn_fs_fs__id_txn_id(id); 438251881Speter const char *node_id = svn_fs_fs__id_node_id(id); 439251881Speter const char *copy_id = svn_fs_fs__id_copy_id(id); 440251881Speter const char *name = apr_psprintf(pool, PATH_PREFIX_NODE "%s.%s", 441251881Speter node_id, copy_id); 442251881Speter 443251881Speter return svn_dirent_join(path_txn_dir(fs, txn_id, pool), name, pool); 444251881Speter} 445251881Speter 446251881Speterstatic APR_INLINE const char * 447251881Speterpath_txn_node_props(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool) 448251881Speter{ 449251881Speter return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool), PATH_EXT_PROPS, 450251881Speter (char *)NULL); 451251881Speter} 452251881Speter 453251881Speterstatic APR_INLINE const char * 454251881Speterpath_txn_node_children(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool) 455251881Speter{ 456251881Speter return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool), 457251881Speter PATH_EXT_CHILDREN, (char *)NULL); 458251881Speter} 459251881Speter 460251881Speterstatic APR_INLINE const char * 461251881Speterpath_node_origin(svn_fs_t *fs, const char *node_id, apr_pool_t *pool) 462251881Speter{ 463251881Speter size_t len = strlen(node_id); 464251881Speter const char *node_id_minus_last_char = 465251881Speter (len == 1) ? "0" : apr_pstrmemdup(pool, node_id, len - 1); 466251881Speter return svn_dirent_join_many(pool, fs->path, PATH_NODE_ORIGINS_DIR, 467251881Speter node_id_minus_last_char, NULL); 468251881Speter} 469251881Speter 470251881Speterstatic APR_INLINE const char * 471251881Speterpath_and_offset_of(apr_file_t *file, apr_pool_t *pool) 472251881Speter{ 473251881Speter const char *path; 474251881Speter apr_off_t offset = 0; 475251881Speter 476251881Speter if (apr_file_name_get(&path, file) != APR_SUCCESS) 477251881Speter path = "(unknown)"; 478251881Speter 479251881Speter if (apr_file_seek(file, APR_CUR, &offset) != APR_SUCCESS) 480251881Speter offset = -1; 481251881Speter 482251881Speter return apr_psprintf(pool, "%s:%" APR_OFF_T_FMT, path, offset); 483251881Speter} 484251881Speter 485251881Speter 486251881Speter 487251881Speter/* Functions for working with shared transaction data. */ 488251881Speter 489251881Speter/* Return the transaction object for transaction TXN_ID from the 490251881Speter transaction list of filesystem FS (which must already be locked via the 491251881Speter txn_list_lock mutex). If the transaction does not exist in the list, 492251881Speter then create a new transaction object and return it (if CREATE_NEW is 493251881Speter true) or return NULL (otherwise). */ 494251881Speterstatic fs_fs_shared_txn_data_t * 495251881Speterget_shared_txn(svn_fs_t *fs, const char *txn_id, svn_boolean_t create_new) 496251881Speter{ 497251881Speter fs_fs_data_t *ffd = fs->fsap_data; 498251881Speter fs_fs_shared_data_t *ffsd = ffd->shared; 499251881Speter fs_fs_shared_txn_data_t *txn; 500251881Speter 501251881Speter for (txn = ffsd->txns; txn; txn = txn->next) 502251881Speter if (strcmp(txn->txn_id, txn_id) == 0) 503251881Speter break; 504251881Speter 505251881Speter if (txn || !create_new) 506251881Speter return txn; 507251881Speter 508251881Speter /* Use the transaction object from the (single-object) freelist, 509251881Speter if one is available, or otherwise create a new object. */ 510251881Speter if (ffsd->free_txn) 511251881Speter { 512251881Speter txn = ffsd->free_txn; 513251881Speter ffsd->free_txn = NULL; 514251881Speter } 515251881Speter else 516251881Speter { 517251881Speter apr_pool_t *subpool = svn_pool_create(ffsd->common_pool); 518251881Speter txn = apr_palloc(subpool, sizeof(*txn)); 519251881Speter txn->pool = subpool; 520251881Speter } 521251881Speter 522251881Speter assert(strlen(txn_id) < sizeof(txn->txn_id)); 523251881Speter apr_cpystrn(txn->txn_id, txn_id, sizeof(txn->txn_id)); 524251881Speter txn->being_written = FALSE; 525251881Speter 526251881Speter /* Link this transaction into the head of the list. We will typically 527251881Speter be dealing with only one active transaction at a time, so it makes 528251881Speter sense for searches through the transaction list to look at the 529251881Speter newest transactions first. */ 530251881Speter txn->next = ffsd->txns; 531251881Speter ffsd->txns = txn; 532251881Speter 533251881Speter return txn; 534251881Speter} 535251881Speter 536251881Speter/* Free the transaction object for transaction TXN_ID, and remove it 537251881Speter from the transaction list of filesystem FS (which must already be 538251881Speter locked via the txn_list_lock mutex). Do nothing if the transaction 539251881Speter does not exist. */ 540251881Speterstatic void 541251881Speterfree_shared_txn(svn_fs_t *fs, const char *txn_id) 542251881Speter{ 543251881Speter fs_fs_data_t *ffd = fs->fsap_data; 544251881Speter fs_fs_shared_data_t *ffsd = ffd->shared; 545251881Speter fs_fs_shared_txn_data_t *txn, *prev = NULL; 546251881Speter 547251881Speter for (txn = ffsd->txns; txn; prev = txn, txn = txn->next) 548251881Speter if (strcmp(txn->txn_id, txn_id) == 0) 549251881Speter break; 550251881Speter 551251881Speter if (!txn) 552251881Speter return; 553251881Speter 554251881Speter if (prev) 555251881Speter prev->next = txn->next; 556251881Speter else 557251881Speter ffsd->txns = txn->next; 558251881Speter 559251881Speter /* As we typically will be dealing with one transaction after another, 560251881Speter we will maintain a single-object free list so that we can hopefully 561251881Speter keep reusing the same transaction object. */ 562251881Speter if (!ffsd->free_txn) 563251881Speter ffsd->free_txn = txn; 564251881Speter else 565251881Speter svn_pool_destroy(txn->pool); 566251881Speter} 567251881Speter 568251881Speter 569251881Speter/* Obtain a lock on the transaction list of filesystem FS, call BODY 570251881Speter with FS, BATON, and POOL, and then unlock the transaction list. 571251881Speter Return what BODY returned. */ 572251881Speterstatic svn_error_t * 573251881Speterwith_txnlist_lock(svn_fs_t *fs, 574251881Speter svn_error_t *(*body)(svn_fs_t *fs, 575251881Speter const void *baton, 576251881Speter apr_pool_t *pool), 577251881Speter const void *baton, 578251881Speter apr_pool_t *pool) 579251881Speter{ 580251881Speter fs_fs_data_t *ffd = fs->fsap_data; 581251881Speter fs_fs_shared_data_t *ffsd = ffd->shared; 582251881Speter 583251881Speter SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock, 584251881Speter body(fs, baton, pool)); 585251881Speter 586251881Speter return SVN_NO_ERROR; 587251881Speter} 588251881Speter 589251881Speter 590251881Speter/* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */ 591251881Speterstatic svn_error_t * 592251881Speterget_lock_on_filesystem(const char *lock_filename, 593251881Speter apr_pool_t *pool) 594251881Speter{ 595251881Speter svn_error_t *err = svn_io_file_lock2(lock_filename, TRUE, FALSE, pool); 596251881Speter 597251881Speter if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 598251881Speter { 599251881Speter /* No lock file? No big deal; these are just empty files 600251881Speter anyway. Create it and try again. */ 601251881Speter svn_error_clear(err); 602251881Speter err = NULL; 603251881Speter 604251881Speter SVN_ERR(svn_io_file_create(lock_filename, "", pool)); 605251881Speter SVN_ERR(svn_io_file_lock2(lock_filename, TRUE, FALSE, pool)); 606251881Speter } 607251881Speter 608251881Speter return svn_error_trace(err); 609251881Speter} 610251881Speter 611251881Speter/* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID. 612251881Speter When registered with the pool holding the lock on the lock file, 613251881Speter this makes sure the flag gets reset just before we release the lock. */ 614251881Speterstatic apr_status_t 615251881Speterreset_lock_flag(void *baton_void) 616251881Speter{ 617251881Speter fs_fs_data_t *ffd = baton_void; 618251881Speter ffd->has_write_lock = FALSE; 619251881Speter return APR_SUCCESS; 620251881Speter} 621251881Speter 622251881Speter/* Obtain a write lock on the file LOCK_FILENAME (protecting with 623251881Speter LOCK_MUTEX if APR is threaded) in a subpool of POOL, call BODY with 624251881Speter BATON and that subpool, destroy the subpool (releasing the write 625251881Speter lock) and return what BODY returned. If IS_GLOBAL_LOCK is set, 626251881Speter set the HAS_WRITE_LOCK flag while we keep the write lock. */ 627251881Speterstatic svn_error_t * 628251881Speterwith_some_lock_file(svn_fs_t *fs, 629251881Speter svn_error_t *(*body)(void *baton, 630251881Speter apr_pool_t *pool), 631251881Speter void *baton, 632251881Speter const char *lock_filename, 633251881Speter svn_boolean_t is_global_lock, 634251881Speter apr_pool_t *pool) 635251881Speter{ 636251881Speter apr_pool_t *subpool = svn_pool_create(pool); 637251881Speter svn_error_t *err = get_lock_on_filesystem(lock_filename, subpool); 638251881Speter 639251881Speter if (!err) 640251881Speter { 641251881Speter fs_fs_data_t *ffd = fs->fsap_data; 642251881Speter 643251881Speter if (is_global_lock) 644251881Speter { 645251881Speter /* set the "got the lock" flag and register reset function */ 646251881Speter apr_pool_cleanup_register(subpool, 647251881Speter ffd, 648251881Speter reset_lock_flag, 649251881Speter apr_pool_cleanup_null); 650251881Speter ffd->has_write_lock = TRUE; 651251881Speter } 652251881Speter 653251881Speter /* nobody else will modify the repo state 654251881Speter => read HEAD & pack info once */ 655251881Speter if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 656251881Speter SVN_ERR(update_min_unpacked_rev(fs, pool)); 657251881Speter SVN_ERR(get_youngest(&ffd->youngest_rev_cache, fs->path, 658251881Speter pool)); 659251881Speter err = body(baton, subpool); 660251881Speter } 661251881Speter 662251881Speter svn_pool_destroy(subpool); 663251881Speter 664251881Speter return svn_error_trace(err); 665251881Speter} 666251881Speter 667251881Spetersvn_error_t * 668251881Spetersvn_fs_fs__with_write_lock(svn_fs_t *fs, 669251881Speter svn_error_t *(*body)(void *baton, 670251881Speter apr_pool_t *pool), 671251881Speter void *baton, 672251881Speter apr_pool_t *pool) 673251881Speter{ 674251881Speter fs_fs_data_t *ffd = fs->fsap_data; 675251881Speter fs_fs_shared_data_t *ffsd = ffd->shared; 676251881Speter 677251881Speter SVN_MUTEX__WITH_LOCK(ffsd->fs_write_lock, 678251881Speter with_some_lock_file(fs, body, baton, 679251881Speter path_lock(fs, pool), 680251881Speter TRUE, 681251881Speter pool)); 682251881Speter 683251881Speter return SVN_NO_ERROR; 684251881Speter} 685251881Speter 686251881Speter/* Run BODY (with BATON and POOL) while the txn-current file 687251881Speter of FS is locked. */ 688251881Speterstatic svn_error_t * 689251881Speterwith_txn_current_lock(svn_fs_t *fs, 690251881Speter svn_error_t *(*body)(void *baton, 691251881Speter apr_pool_t *pool), 692251881Speter void *baton, 693251881Speter apr_pool_t *pool) 694251881Speter{ 695251881Speter fs_fs_data_t *ffd = fs->fsap_data; 696251881Speter fs_fs_shared_data_t *ffsd = ffd->shared; 697251881Speter 698251881Speter SVN_MUTEX__WITH_LOCK(ffsd->txn_current_lock, 699251881Speter with_some_lock_file(fs, body, baton, 700251881Speter path_txn_current_lock(fs, pool), 701251881Speter FALSE, 702251881Speter pool)); 703251881Speter 704251881Speter return SVN_NO_ERROR; 705251881Speter} 706251881Speter 707251881Speter/* A structure used by unlock_proto_rev() and unlock_proto_rev_body(), 708251881Speter which see. */ 709251881Speterstruct unlock_proto_rev_baton 710251881Speter{ 711251881Speter const char *txn_id; 712251881Speter void *lockcookie; 713251881Speter}; 714251881Speter 715251881Speter/* Callback used in the implementation of unlock_proto_rev(). */ 716251881Speterstatic svn_error_t * 717251881Speterunlock_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool) 718251881Speter{ 719251881Speter const struct unlock_proto_rev_baton *b = baton; 720251881Speter const char *txn_id = b->txn_id; 721251881Speter apr_file_t *lockfile = b->lockcookie; 722251881Speter fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, FALSE); 723251881Speter apr_status_t apr_err; 724251881Speter 725251881Speter if (!txn) 726251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 727251881Speter _("Can't unlock unknown transaction '%s'"), 728251881Speter txn_id); 729251881Speter if (!txn->being_written) 730251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 731251881Speter _("Can't unlock nonlocked transaction '%s'"), 732251881Speter txn_id); 733251881Speter 734251881Speter apr_err = apr_file_unlock(lockfile); 735251881Speter if (apr_err) 736251881Speter return svn_error_wrap_apr 737251881Speter (apr_err, 738251881Speter _("Can't unlock prototype revision lockfile for transaction '%s'"), 739251881Speter txn_id); 740251881Speter apr_err = apr_file_close(lockfile); 741251881Speter if (apr_err) 742251881Speter return svn_error_wrap_apr 743251881Speter (apr_err, 744251881Speter _("Can't close prototype revision lockfile for transaction '%s'"), 745251881Speter txn_id); 746251881Speter 747251881Speter txn->being_written = FALSE; 748251881Speter 749251881Speter return SVN_NO_ERROR; 750251881Speter} 751251881Speter 752251881Speter/* Unlock the prototype revision file for transaction TXN_ID in filesystem 753251881Speter FS using cookie LOCKCOOKIE. The original prototype revision file must 754251881Speter have been closed _before_ calling this function. 755251881Speter 756251881Speter Perform temporary allocations in POOL. */ 757251881Speterstatic svn_error_t * 758251881Speterunlock_proto_rev(svn_fs_t *fs, const char *txn_id, void *lockcookie, 759251881Speter apr_pool_t *pool) 760251881Speter{ 761251881Speter struct unlock_proto_rev_baton b; 762251881Speter 763251881Speter b.txn_id = txn_id; 764251881Speter b.lockcookie = lockcookie; 765251881Speter return with_txnlist_lock(fs, unlock_proto_rev_body, &b, pool); 766251881Speter} 767251881Speter 768251881Speter/* Same as unlock_proto_rev(), but requires that the transaction list 769251881Speter lock is already held. */ 770251881Speterstatic svn_error_t * 771251881Speterunlock_proto_rev_list_locked(svn_fs_t *fs, const char *txn_id, 772251881Speter void *lockcookie, 773251881Speter apr_pool_t *pool) 774251881Speter{ 775251881Speter struct unlock_proto_rev_baton b; 776251881Speter 777251881Speter b.txn_id = txn_id; 778251881Speter b.lockcookie = lockcookie; 779251881Speter return unlock_proto_rev_body(fs, &b, pool); 780251881Speter} 781251881Speter 782251881Speter/* A structure used by get_writable_proto_rev() and 783251881Speter get_writable_proto_rev_body(), which see. */ 784251881Speterstruct get_writable_proto_rev_baton 785251881Speter{ 786251881Speter apr_file_t **file; 787251881Speter void **lockcookie; 788251881Speter const char *txn_id; 789251881Speter}; 790251881Speter 791251881Speter/* Callback used in the implementation of get_writable_proto_rev(). */ 792251881Speterstatic svn_error_t * 793251881Speterget_writable_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool) 794251881Speter{ 795251881Speter const struct get_writable_proto_rev_baton *b = baton; 796251881Speter apr_file_t **file = b->file; 797251881Speter void **lockcookie = b->lockcookie; 798251881Speter const char *txn_id = b->txn_id; 799251881Speter svn_error_t *err; 800251881Speter fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, TRUE); 801251881Speter 802251881Speter /* First, ensure that no thread in this process (including this one) 803251881Speter is currently writing to this transaction's proto-rev file. */ 804251881Speter if (txn->being_written) 805251881Speter return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL, 806251881Speter _("Cannot write to the prototype revision file " 807251881Speter "of transaction '%s' because a previous " 808251881Speter "representation is currently being written by " 809251881Speter "this process"), 810251881Speter txn_id); 811251881Speter 812251881Speter 813251881Speter /* We know that no thread in this process is writing to the proto-rev 814251881Speter file, and by extension, that no thread in this process is holding a 815251881Speter lock on the prototype revision lock file. It is therefore safe 816251881Speter for us to attempt to lock this file, to see if any other process 817251881Speter is holding a lock. */ 818251881Speter 819251881Speter { 820251881Speter apr_file_t *lockfile; 821251881Speter apr_status_t apr_err; 822251881Speter const char *lockfile_path = path_txn_proto_rev_lock(fs, txn_id, pool); 823251881Speter 824251881Speter /* Open the proto-rev lockfile, creating it if necessary, as it may 825251881Speter not exist if the transaction dates from before the lockfiles were 826251881Speter introduced. 827251881Speter 828251881Speter ### We'd also like to use something like svn_io_file_lock2(), but 829251881Speter that forces us to create a subpool just to be able to unlock 830251881Speter the file, which seems a waste. */ 831251881Speter SVN_ERR(svn_io_file_open(&lockfile, lockfile_path, 832251881Speter APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool)); 833251881Speter 834251881Speter apr_err = apr_file_lock(lockfile, 835251881Speter APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK); 836251881Speter if (apr_err) 837251881Speter { 838251881Speter svn_error_clear(svn_io_file_close(lockfile, pool)); 839251881Speter 840251881Speter if (APR_STATUS_IS_EAGAIN(apr_err)) 841251881Speter return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL, 842251881Speter _("Cannot write to the prototype revision " 843251881Speter "file of transaction '%s' because a " 844251881Speter "previous representation is currently " 845251881Speter "being written by another process"), 846251881Speter txn_id); 847251881Speter 848251881Speter return svn_error_wrap_apr(apr_err, 849251881Speter _("Can't get exclusive lock on file '%s'"), 850251881Speter svn_dirent_local_style(lockfile_path, pool)); 851251881Speter } 852251881Speter 853251881Speter *lockcookie = lockfile; 854251881Speter } 855251881Speter 856251881Speter /* We've successfully locked the transaction; mark it as such. */ 857251881Speter txn->being_written = TRUE; 858251881Speter 859251881Speter 860251881Speter /* Now open the prototype revision file and seek to the end. */ 861251881Speter err = svn_io_file_open(file, path_txn_proto_rev(fs, txn_id, pool), 862251881Speter APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT, pool); 863251881Speter 864251881Speter /* You might expect that we could dispense with the following seek 865251881Speter and achieve the same thing by opening the file using APR_APPEND. 866251881Speter Unfortunately, APR's buffered file implementation unconditionally 867251881Speter places its initial file pointer at the start of the file (even for 868251881Speter files opened with APR_APPEND), so we need this seek to reconcile 869251881Speter the APR file pointer to the OS file pointer (since we need to be 870251881Speter able to read the current file position later). */ 871251881Speter if (!err) 872251881Speter { 873251881Speter apr_off_t offset = 0; 874251881Speter err = svn_io_file_seek(*file, APR_END, &offset, pool); 875251881Speter } 876251881Speter 877251881Speter if (err) 878251881Speter { 879251881Speter err = svn_error_compose_create( 880251881Speter err, 881251881Speter unlock_proto_rev_list_locked(fs, txn_id, *lockcookie, pool)); 882251881Speter 883251881Speter *lockcookie = NULL; 884251881Speter } 885251881Speter 886251881Speter return svn_error_trace(err); 887251881Speter} 888251881Speter 889251881Speter/* Get a handle to the prototype revision file for transaction TXN_ID in 890251881Speter filesystem FS, and lock it for writing. Return FILE, a file handle 891251881Speter positioned at the end of the file, and LOCKCOOKIE, a cookie that 892251881Speter should be passed to unlock_proto_rev() to unlock the file once FILE 893251881Speter has been closed. 894251881Speter 895251881Speter If the prototype revision file is already locked, return error 896251881Speter SVN_ERR_FS_REP_BEING_WRITTEN. 897251881Speter 898251881Speter Perform all allocations in POOL. */ 899251881Speterstatic svn_error_t * 900251881Speterget_writable_proto_rev(apr_file_t **file, 901251881Speter void **lockcookie, 902251881Speter svn_fs_t *fs, const char *txn_id, 903251881Speter apr_pool_t *pool) 904251881Speter{ 905251881Speter struct get_writable_proto_rev_baton b; 906251881Speter 907251881Speter b.file = file; 908251881Speter b.lockcookie = lockcookie; 909251881Speter b.txn_id = txn_id; 910251881Speter 911251881Speter return with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool); 912251881Speter} 913251881Speter 914251881Speter/* Callback used in the implementation of purge_shared_txn(). */ 915251881Speterstatic svn_error_t * 916251881Speterpurge_shared_txn_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool) 917251881Speter{ 918251881Speter const char *txn_id = baton; 919251881Speter 920251881Speter free_shared_txn(fs, txn_id); 921251881Speter svn_fs_fs__reset_txn_caches(fs); 922251881Speter 923251881Speter return SVN_NO_ERROR; 924251881Speter} 925251881Speter 926251881Speter/* Purge the shared data for transaction TXN_ID in filesystem FS. 927251881Speter Perform all allocations in POOL. */ 928251881Speterstatic svn_error_t * 929251881Speterpurge_shared_txn(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool) 930251881Speter{ 931251881Speter return with_txnlist_lock(fs, purge_shared_txn_body, txn_id, pool); 932251881Speter} 933251881Speter 934251881Speter 935251881Speter 936251881Speter/* Fetch the current offset of FILE into *OFFSET_P. */ 937251881Speterstatic svn_error_t * 938251881Speterget_file_offset(apr_off_t *offset_p, apr_file_t *file, apr_pool_t *pool) 939251881Speter{ 940251881Speter apr_off_t offset; 941251881Speter 942251881Speter /* Note that, for buffered files, one (possibly surprising) side-effect 943251881Speter of this call is to flush any unwritten data to disk. */ 944251881Speter offset = 0; 945251881Speter SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool)); 946251881Speter *offset_p = offset; 947251881Speter 948251881Speter return SVN_NO_ERROR; 949251881Speter} 950251881Speter 951251881Speter 952251881Speter/* Check that BUF, a nul-terminated buffer of text from file PATH, 953251881Speter contains only digits at OFFSET and beyond, raising an error if not. 954251881Speter TITLE contains a user-visible description of the file, usually the 955251881Speter short file name. 956251881Speter 957251881Speter Uses POOL for temporary allocation. */ 958251881Speterstatic svn_error_t * 959251881Spetercheck_file_buffer_numeric(const char *buf, apr_off_t offset, 960251881Speter const char *path, const char *title, 961251881Speter apr_pool_t *pool) 962251881Speter{ 963251881Speter const char *p; 964251881Speter 965251881Speter for (p = buf + offset; *p; p++) 966251881Speter if (!svn_ctype_isdigit(*p)) 967251881Speter return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL, 968251881Speter _("%s file '%s' contains unexpected non-digit '%c' within '%s'"), 969251881Speter title, svn_dirent_local_style(path, pool), *p, buf); 970251881Speter 971251881Speter return SVN_NO_ERROR; 972251881Speter} 973251881Speter 974251881Speter/* Check that BUF, a nul-terminated buffer of text from format file PATH, 975251881Speter contains only digits at OFFSET and beyond, raising an error if not. 976251881Speter 977251881Speter Uses POOL for temporary allocation. */ 978251881Speterstatic svn_error_t * 979251881Spetercheck_format_file_buffer_numeric(const char *buf, apr_off_t offset, 980251881Speter const char *path, apr_pool_t *pool) 981251881Speter{ 982251881Speter return check_file_buffer_numeric(buf, offset, path, "Format", pool); 983251881Speter} 984251881Speter 985262253Speter/* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format 986262253Speter number is not the same as a format number supported by this 987262253Speter Subversion. */ 988262253Speterstatic svn_error_t * 989262253Spetercheck_format(int format) 990262253Speter{ 991262253Speter /* Blacklist. These formats may be either younger or older than 992262253Speter SVN_FS_FS__FORMAT_NUMBER, but we don't support them. */ 993262253Speter if (format == SVN_FS_FS__PACKED_REVPROP_SQLITE_DEV_FORMAT) 994262253Speter return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL, 995262253Speter _("Found format '%d', only created by " 996262253Speter "unreleased dev builds; see " 997262253Speter "http://subversion.apache.org" 998262253Speter "/docs/release-notes/1.7#revprop-packing"), 999262253Speter format); 1000262253Speter 1001262253Speter /* We support all formats from 1-current simultaneously */ 1002262253Speter if (1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER) 1003262253Speter return SVN_NO_ERROR; 1004262253Speter 1005262253Speter return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL, 1006262253Speter _("Expected FS format between '1' and '%d'; found format '%d'"), 1007262253Speter SVN_FS_FS__FORMAT_NUMBER, format); 1008262253Speter} 1009262253Speter 1010251881Speter/* Read the format number and maximum number of files per directory 1011251881Speter from PATH and return them in *PFORMAT and *MAX_FILES_PER_DIR 1012251881Speter respectively. 1013251881Speter 1014251881Speter *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and 1015251881Speter will be set to zero if a linear scheme should be used. 1016251881Speter 1017251881Speter Use POOL for temporary allocation. */ 1018251881Speterstatic svn_error_t * 1019251881Speterread_format(int *pformat, int *max_files_per_dir, 1020251881Speter const char *path, apr_pool_t *pool) 1021251881Speter{ 1022251881Speter svn_error_t *err; 1023251881Speter svn_stream_t *stream; 1024251881Speter svn_stringbuf_t *content; 1025251881Speter svn_stringbuf_t *buf; 1026251881Speter svn_boolean_t eos = FALSE; 1027251881Speter 1028251881Speter err = svn_stringbuf_from_file2(&content, path, pool); 1029251881Speter if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 1030251881Speter { 1031251881Speter /* Treat an absent format file as format 1. Do not try to 1032251881Speter create the format file on the fly, because the repository 1033251881Speter might be read-only for us, or this might be a read-only 1034251881Speter operation, and the spirit of FSFS is to make no changes 1035251881Speter whatseover in read-only operations. See thread starting at 1036251881Speter http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=97600 1037251881Speter for more. */ 1038251881Speter svn_error_clear(err); 1039251881Speter *pformat = 1; 1040251881Speter *max_files_per_dir = 0; 1041251881Speter 1042251881Speter return SVN_NO_ERROR; 1043251881Speter } 1044251881Speter SVN_ERR(err); 1045251881Speter 1046251881Speter stream = svn_stream_from_stringbuf(content, pool); 1047251881Speter SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool)); 1048251881Speter if (buf->len == 0 && eos) 1049251881Speter { 1050251881Speter /* Return a more useful error message. */ 1051251881Speter return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL, 1052251881Speter _("Can't read first line of format file '%s'"), 1053251881Speter svn_dirent_local_style(path, pool)); 1054251881Speter } 1055251881Speter 1056251881Speter /* Check that the first line contains only digits. */ 1057251881Speter SVN_ERR(check_format_file_buffer_numeric(buf->data, 0, path, pool)); 1058251881Speter SVN_ERR(svn_cstring_atoi(pformat, buf->data)); 1059251881Speter 1060262253Speter /* Check that we support this format at all */ 1061262253Speter SVN_ERR(check_format(*pformat)); 1062262253Speter 1063251881Speter /* Set the default values for anything that can be set via an option. */ 1064251881Speter *max_files_per_dir = 0; 1065251881Speter 1066251881Speter /* Read any options. */ 1067251881Speter while (!eos) 1068251881Speter { 1069251881Speter SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool)); 1070251881Speter if (buf->len == 0) 1071251881Speter break; 1072251881Speter 1073251881Speter if (*pformat >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT && 1074251881Speter strncmp(buf->data, "layout ", 7) == 0) 1075251881Speter { 1076251881Speter if (strcmp(buf->data + 7, "linear") == 0) 1077251881Speter { 1078251881Speter *max_files_per_dir = 0; 1079251881Speter continue; 1080251881Speter } 1081251881Speter 1082251881Speter if (strncmp(buf->data + 7, "sharded ", 8) == 0) 1083251881Speter { 1084251881Speter /* Check that the argument is numeric. */ 1085251881Speter SVN_ERR(check_format_file_buffer_numeric(buf->data, 15, path, pool)); 1086251881Speter SVN_ERR(svn_cstring_atoi(max_files_per_dir, buf->data + 15)); 1087251881Speter continue; 1088251881Speter } 1089251881Speter } 1090251881Speter 1091251881Speter return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL, 1092251881Speter _("'%s' contains invalid filesystem format option '%s'"), 1093251881Speter svn_dirent_local_style(path, pool), buf->data); 1094251881Speter } 1095251881Speter 1096251881Speter return SVN_NO_ERROR; 1097251881Speter} 1098251881Speter 1099251881Speter/* Write the format number and maximum number of files per directory 1100251881Speter to a new format file in PATH, possibly expecting to overwrite a 1101251881Speter previously existing file. 1102251881Speter 1103251881Speter Use POOL for temporary allocation. */ 1104251881Speterstatic svn_error_t * 1105251881Speterwrite_format(const char *path, int format, int max_files_per_dir, 1106251881Speter svn_boolean_t overwrite, apr_pool_t *pool) 1107251881Speter{ 1108251881Speter svn_stringbuf_t *sb; 1109251881Speter 1110251881Speter SVN_ERR_ASSERT(1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER); 1111251881Speter 1112251881Speter sb = svn_stringbuf_createf(pool, "%d\n", format); 1113251881Speter 1114251881Speter if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT) 1115251881Speter { 1116251881Speter if (max_files_per_dir) 1117251881Speter svn_stringbuf_appendcstr(sb, apr_psprintf(pool, "layout sharded %d\n", 1118251881Speter max_files_per_dir)); 1119251881Speter else 1120251881Speter svn_stringbuf_appendcstr(sb, "layout linear\n"); 1121251881Speter } 1122251881Speter 1123251881Speter /* svn_io_write_version_file() does a load of magic to allow it to 1124251881Speter replace version files that already exist. We only need to do 1125251881Speter that when we're allowed to overwrite an existing file. */ 1126251881Speter if (! overwrite) 1127251881Speter { 1128251881Speter /* Create the file */ 1129251881Speter SVN_ERR(svn_io_file_create(path, sb->data, pool)); 1130251881Speter } 1131251881Speter else 1132251881Speter { 1133251881Speter const char *path_tmp; 1134251881Speter 1135251881Speter SVN_ERR(svn_io_write_unique(&path_tmp, 1136251881Speter svn_dirent_dirname(path, pool), 1137251881Speter sb->data, sb->len, 1138251881Speter svn_io_file_del_none, pool)); 1139251881Speter 1140251881Speter /* rename the temp file as the real destination */ 1141251881Speter SVN_ERR(svn_io_file_rename(path_tmp, path, pool)); 1142251881Speter } 1143251881Speter 1144251881Speter /* And set the perms to make it read only */ 1145251881Speter return svn_io_set_file_read_only(path, FALSE, pool); 1146251881Speter} 1147251881Speter 1148251881Spetersvn_boolean_t 1149251881Spetersvn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs) 1150251881Speter{ 1151251881Speter fs_fs_data_t *ffd = fs->fsap_data; 1152251881Speter return ffd->format >= SVN_FS_FS__MIN_MERGEINFO_FORMAT; 1153251881Speter} 1154251881Speter 1155251881Speter/* Read the configuration information of the file system at FS_PATH 1156251881Speter * and set the respective values in FFD. Use POOL for allocations. 1157251881Speter */ 1158251881Speterstatic svn_error_t * 1159251881Speterread_config(fs_fs_data_t *ffd, 1160251881Speter const char *fs_path, 1161251881Speter apr_pool_t *pool) 1162251881Speter{ 1163251881Speter SVN_ERR(svn_config_read3(&ffd->config, 1164251881Speter svn_dirent_join(fs_path, PATH_CONFIG, pool), 1165251881Speter FALSE, FALSE, FALSE, pool)); 1166251881Speter 1167251881Speter /* Initialize ffd->rep_sharing_allowed. */ 1168251881Speter if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT) 1169251881Speter SVN_ERR(svn_config_get_bool(ffd->config, &ffd->rep_sharing_allowed, 1170251881Speter CONFIG_SECTION_REP_SHARING, 1171251881Speter CONFIG_OPTION_ENABLE_REP_SHARING, TRUE)); 1172251881Speter else 1173251881Speter ffd->rep_sharing_allowed = FALSE; 1174251881Speter 1175251881Speter /* Initialize deltification settings in ffd. */ 1176251881Speter if (ffd->format >= SVN_FS_FS__MIN_DELTIFICATION_FORMAT) 1177251881Speter { 1178251881Speter SVN_ERR(svn_config_get_bool(ffd->config, &ffd->deltify_directories, 1179251881Speter CONFIG_SECTION_DELTIFICATION, 1180251881Speter CONFIG_OPTION_ENABLE_DIR_DELTIFICATION, 1181251881Speter FALSE)); 1182251881Speter SVN_ERR(svn_config_get_bool(ffd->config, &ffd->deltify_properties, 1183251881Speter CONFIG_SECTION_DELTIFICATION, 1184251881Speter CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION, 1185251881Speter FALSE)); 1186251881Speter SVN_ERR(svn_config_get_int64(ffd->config, &ffd->max_deltification_walk, 1187251881Speter CONFIG_SECTION_DELTIFICATION, 1188251881Speter CONFIG_OPTION_MAX_DELTIFICATION_WALK, 1189251881Speter SVN_FS_FS_MAX_DELTIFICATION_WALK)); 1190251881Speter SVN_ERR(svn_config_get_int64(ffd->config, &ffd->max_linear_deltification, 1191251881Speter CONFIG_SECTION_DELTIFICATION, 1192251881Speter CONFIG_OPTION_MAX_LINEAR_DELTIFICATION, 1193251881Speter SVN_FS_FS_MAX_LINEAR_DELTIFICATION)); 1194251881Speter } 1195251881Speter else 1196251881Speter { 1197251881Speter ffd->deltify_directories = FALSE; 1198251881Speter ffd->deltify_properties = FALSE; 1199251881Speter ffd->max_deltification_walk = SVN_FS_FS_MAX_DELTIFICATION_WALK; 1200251881Speter ffd->max_linear_deltification = SVN_FS_FS_MAX_LINEAR_DELTIFICATION; 1201251881Speter } 1202251881Speter 1203251881Speter /* Initialize revprop packing settings in ffd. */ 1204251881Speter if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT) 1205251881Speter { 1206251881Speter SVN_ERR(svn_config_get_bool(ffd->config, &ffd->compress_packed_revprops, 1207251881Speter CONFIG_SECTION_PACKED_REVPROPS, 1208251881Speter CONFIG_OPTION_COMPRESS_PACKED_REVPROPS, 1209251881Speter FALSE)); 1210251881Speter SVN_ERR(svn_config_get_int64(ffd->config, &ffd->revprop_pack_size, 1211251881Speter CONFIG_SECTION_PACKED_REVPROPS, 1212251881Speter CONFIG_OPTION_REVPROP_PACK_SIZE, 1213251881Speter ffd->compress_packed_revprops 1214251881Speter ? 0x100 1215251881Speter : 0x40)); 1216251881Speter 1217251881Speter ffd->revprop_pack_size *= 1024; 1218251881Speter } 1219251881Speter else 1220251881Speter { 1221251881Speter ffd->revprop_pack_size = 0x10000; 1222251881Speter ffd->compress_packed_revprops = FALSE; 1223251881Speter } 1224251881Speter 1225251881Speter return SVN_NO_ERROR; 1226251881Speter} 1227251881Speter 1228251881Speterstatic svn_error_t * 1229251881Speterwrite_config(svn_fs_t *fs, 1230251881Speter apr_pool_t *pool) 1231251881Speter{ 1232251881Speter#define NL APR_EOL_STR 1233251881Speter static const char * const fsfs_conf_contents = 1234251881Speter"### This file controls the configuration of the FSFS filesystem." NL 1235251881Speter"" NL 1236251881Speter"[" SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "]" NL 1237251881Speter"### These options name memcached servers used to cache internal FSFS" NL 1238251881Speter"### data. See http://www.danga.com/memcached/ for more information on" NL 1239251881Speter"### memcached. To use memcached with FSFS, run one or more memcached" NL 1240251881Speter"### servers, and specify each of them as an option like so:" NL 1241251881Speter"# first-server = 127.0.0.1:11211" NL 1242251881Speter"# remote-memcached = mymemcached.corp.example.com:11212" NL 1243251881Speter"### The option name is ignored; the value is of the form HOST:PORT." NL 1244251881Speter"### memcached servers can be shared between multiple repositories;" NL 1245251881Speter"### however, if you do this, you *must* ensure that repositories have" NL 1246251881Speter"### distinct UUIDs and paths, or else cached data from one repository" NL 1247251881Speter"### might be used by another accidentally. Note also that memcached has" NL 1248251881Speter"### no authentication for reads or writes, so you must ensure that your" NL 1249251881Speter"### memcached servers are only accessible by trusted users." NL 1250251881Speter"" NL 1251251881Speter"[" CONFIG_SECTION_CACHES "]" NL 1252251881Speter"### When a cache-related error occurs, normally Subversion ignores it" NL 1253251881Speter"### and continues, logging an error if the server is appropriately" NL 1254251881Speter"### configured (and ignoring it with file:// access). To make" NL 1255251881Speter"### Subversion never ignore cache errors, uncomment this line." NL 1256251881Speter"# " CONFIG_OPTION_FAIL_STOP " = true" NL 1257251881Speter"" NL 1258251881Speter"[" CONFIG_SECTION_REP_SHARING "]" NL 1259251881Speter"### To conserve space, the filesystem can optionally avoid storing" NL 1260251881Speter"### duplicate representations. This comes at a slight cost in" NL 1261251881Speter"### performance, as maintaining a database of shared representations can" NL 1262251881Speter"### increase commit times. The space savings are dependent upon the size" NL 1263251881Speter"### of the repository, the number of objects it contains and the amount of" NL 1264251881Speter"### duplication between them, usually a function of the branching and" NL 1265251881Speter"### merging process." NL 1266251881Speter"###" NL 1267251881Speter"### The following parameter enables rep-sharing in the repository. It can" NL 1268251881Speter"### be switched on and off at will, but for best space-saving results" NL 1269251881Speter"### should be enabled consistently over the life of the repository." NL 1270251881Speter"### 'svnadmin verify' will check the rep-cache regardless of this setting." NL 1271251881Speter"### rep-sharing is enabled by default." NL 1272251881Speter"# " CONFIG_OPTION_ENABLE_REP_SHARING " = true" NL 1273251881Speter"" NL 1274251881Speter"[" CONFIG_SECTION_DELTIFICATION "]" NL 1275251881Speter"### To conserve space, the filesystem stores data as differences against" NL 1276251881Speter"### existing representations. This comes at a slight cost in performance," NL 1277251881Speter"### as calculating differences can increase commit times. Reading data" NL 1278251881Speter"### will also create higher CPU load and the data will be fragmented." NL 1279251881Speter"### Since deltification tends to save significant amounts of disk space," NL 1280251881Speter"### the overall I/O load can actually be lower." NL 1281251881Speter"###" NL 1282251881Speter"### The options in this section allow for tuning the deltification" NL 1283251881Speter"### strategy. Their effects on data size and server performance may vary" NL 1284251881Speter"### from one repository to another. Versions prior to 1.8 will ignore" NL 1285251881Speter"### this section." NL 1286251881Speter"###" NL 1287251881Speter"### The following parameter enables deltification for directories. It can" NL 1288251881Speter"### be switched on and off at will, but for best space-saving results" NL 1289251881Speter"### should be enabled consistently over the life of the repository." NL 1290251881Speter"### Repositories containing large directories will benefit greatly." NL 1291251881Speter"### In rarely read repositories, the I/O overhead may be significant as" NL 1292251881Speter"### cache hit rates will most likely be low" NL 1293251881Speter"### directory deltification is disabled by default." NL 1294251881Speter"# " CONFIG_OPTION_ENABLE_DIR_DELTIFICATION " = false" NL 1295251881Speter"###" NL 1296251881Speter"### The following parameter enables deltification for properties on files" NL 1297251881Speter"### and directories. Overall, this is a minor tuning option but can save" NL 1298251881Speter"### some disk space if you merge frequently or frequently change node" NL 1299251881Speter"### properties. You should not activate this if rep-sharing has been" NL 1300251881Speter"### disabled because this may result in a net increase in repository size." NL 1301251881Speter"### property deltification is disabled by default." NL 1302251881Speter"# " CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION " = false" NL 1303251881Speter"###" NL 1304251881Speter"### During commit, the server may need to walk the whole change history of" NL 1305251881Speter"### of a given node to find a suitable deltification base. This linear" NL 1306251881Speter"### process can impact commit times, svnadmin load and similar operations." NL 1307251881Speter"### This setting limits the depth of the deltification history. If the" NL 1308251881Speter"### threshold has been reached, the node will be stored as fulltext and a" NL 1309251881Speter"### new deltification history begins." NL 1310251881Speter"### Note, this is unrelated to svn log." NL 1311251881Speter"### Very large values rarely provide significant additional savings but" NL 1312251881Speter"### can impact performance greatly - in particular if directory" NL 1313251881Speter"### deltification has been activated. Very small values may be useful in" NL 1314251881Speter"### repositories that are dominated by large, changing binaries." NL 1315251881Speter"### Should be a power of two minus 1. A value of 0 will effectively" NL 1316251881Speter"### disable deltification." NL 1317251881Speter"### For 1.8, the default value is 1023; earlier versions have no limit." NL 1318251881Speter"# " CONFIG_OPTION_MAX_DELTIFICATION_WALK " = 1023" NL 1319251881Speter"###" NL 1320251881Speter"### The skip-delta scheme used by FSFS tends to repeatably store redundant" NL 1321251881Speter"### delta information where a simple delta against the latest version is" NL 1322251881Speter"### often smaller. By default, 1.8+ will therefore use skip deltas only" NL 1323251881Speter"### after the linear chain of deltas has grown beyond the threshold" NL 1324251881Speter"### specified by this setting." NL 1325251881Speter"### Values up to 64 can result in some reduction in repository size for" NL 1326251881Speter"### the cost of quickly increasing I/O and CPU costs. Similarly, smaller" NL 1327251881Speter"### numbers can reduce those costs at the cost of more disk space. For" NL 1328251881Speter"### rarely read repositories or those containing larger binaries, this may" NL 1329251881Speter"### present a better trade-off." NL 1330251881Speter"### Should be a power of two. A value of 1 or smaller will cause the" NL 1331251881Speter"### exclusive use of skip-deltas (as in pre-1.8)." NL 1332251881Speter"### For 1.8, the default value is 16; earlier versions use 1." NL 1333251881Speter"# " CONFIG_OPTION_MAX_LINEAR_DELTIFICATION " = 16" NL 1334251881Speter"" NL 1335251881Speter"[" CONFIG_SECTION_PACKED_REVPROPS "]" NL 1336251881Speter"### This parameter controls the size (in kBytes) of packed revprop files." NL 1337251881Speter"### Revprops of consecutive revisions will be concatenated into a single" NL 1338251881Speter"### file up to but not exceeding the threshold given here. However, each" NL 1339251881Speter"### pack file may be much smaller and revprops of a single revision may be" NL 1340251881Speter"### much larger than the limit set here. The threshold will be applied" NL 1341251881Speter"### before optional compression takes place." NL 1342251881Speter"### Large values will reduce disk space usage at the expense of increased" NL 1343251881Speter"### latency and CPU usage reading and changing individual revprops. They" NL 1344251881Speter"### become an advantage when revprop caching has been enabled because a" NL 1345251881Speter"### lot of data can be read in one go. Values smaller than 4 kByte will" NL 1346251881Speter"### not improve latency any further and quickly render revprop packing" NL 1347251881Speter"### ineffective." NL 1348251881Speter"### revprop-pack-size is 64 kBytes by default for non-compressed revprop" NL 1349251881Speter"### pack files and 256 kBytes when compression has been enabled." NL 1350251881Speter"# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 64" NL 1351251881Speter"###" NL 1352251881Speter"### To save disk space, packed revprop files may be compressed. Standard" NL 1353251881Speter"### revprops tend to allow for very effective compression. Reading and" NL 1354251881Speter"### even more so writing, become significantly more CPU intensive. With" NL 1355251881Speter"### revprop caching enabled, the overhead can be offset by reduced I/O" NL 1356251881Speter"### unless you often modify revprops after packing." NL 1357251881Speter"### Compressing packed revprops is disabled by default." NL 1358251881Speter"# " CONFIG_OPTION_COMPRESS_PACKED_REVPROPS " = false" NL 1359251881Speter; 1360251881Speter#undef NL 1361251881Speter return svn_io_file_create(svn_dirent_join(fs->path, PATH_CONFIG, pool), 1362251881Speter fsfs_conf_contents, pool); 1363251881Speter} 1364251881Speter 1365251881Speterstatic svn_error_t * 1366251881Speterread_min_unpacked_rev(svn_revnum_t *min_unpacked_rev, 1367251881Speter const char *path, 1368251881Speter apr_pool_t *pool) 1369251881Speter{ 1370251881Speter char buf[80]; 1371251881Speter apr_file_t *file; 1372251881Speter apr_size_t len; 1373251881Speter 1374251881Speter SVN_ERR(svn_io_file_open(&file, path, APR_READ | APR_BUFFERED, 1375251881Speter APR_OS_DEFAULT, pool)); 1376251881Speter len = sizeof(buf); 1377251881Speter SVN_ERR(svn_io_read_length_line(file, buf, &len, pool)); 1378251881Speter SVN_ERR(svn_io_file_close(file, pool)); 1379251881Speter 1380251881Speter *min_unpacked_rev = SVN_STR_TO_REV(buf); 1381251881Speter return SVN_NO_ERROR; 1382251881Speter} 1383251881Speter 1384251881Speterstatic svn_error_t * 1385251881Speterupdate_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool) 1386251881Speter{ 1387251881Speter fs_fs_data_t *ffd = fs->fsap_data; 1388251881Speter 1389251881Speter SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT); 1390251881Speter 1391251881Speter return read_min_unpacked_rev(&ffd->min_unpacked_rev, 1392251881Speter path_min_unpacked_rev(fs, pool), 1393251881Speter pool); 1394251881Speter} 1395251881Speter 1396251881Spetersvn_error_t * 1397251881Spetersvn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool) 1398251881Speter{ 1399251881Speter fs_fs_data_t *ffd = fs->fsap_data; 1400251881Speter apr_file_t *uuid_file; 1401251881Speter int format, max_files_per_dir; 1402251881Speter char buf[APR_UUID_FORMATTED_LENGTH + 2]; 1403251881Speter apr_size_t limit; 1404251881Speter 1405251881Speter fs->path = apr_pstrdup(fs->pool, path); 1406251881Speter 1407251881Speter /* Read the FS format number. */ 1408251881Speter SVN_ERR(read_format(&format, &max_files_per_dir, 1409251881Speter path_format(fs, pool), pool)); 1410251881Speter 1411251881Speter /* Now we've got a format number no matter what. */ 1412251881Speter ffd->format = format; 1413251881Speter ffd->max_files_per_dir = max_files_per_dir; 1414251881Speter 1415251881Speter /* Read in and cache the repository uuid. */ 1416251881Speter SVN_ERR(svn_io_file_open(&uuid_file, path_uuid(fs, pool), 1417251881Speter APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool)); 1418251881Speter 1419251881Speter limit = sizeof(buf); 1420251881Speter SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, pool)); 1421251881Speter fs->uuid = apr_pstrdup(fs->pool, buf); 1422251881Speter 1423251881Speter SVN_ERR(svn_io_file_close(uuid_file, pool)); 1424251881Speter 1425251881Speter /* Read the min unpacked revision. */ 1426251881Speter if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 1427251881Speter SVN_ERR(update_min_unpacked_rev(fs, pool)); 1428251881Speter 1429251881Speter /* Read the configuration file. */ 1430251881Speter SVN_ERR(read_config(ffd, fs->path, pool)); 1431251881Speter 1432251881Speter return get_youngest(&(ffd->youngest_rev_cache), path, pool); 1433251881Speter} 1434251881Speter 1435251881Speter/* Wrapper around svn_io_file_create which ignores EEXIST. */ 1436251881Speterstatic svn_error_t * 1437251881Spetercreate_file_ignore_eexist(const char *file, 1438251881Speter const char *contents, 1439251881Speter apr_pool_t *pool) 1440251881Speter{ 1441251881Speter svn_error_t *err = svn_io_file_create(file, contents, pool); 1442251881Speter if (err && APR_STATUS_IS_EEXIST(err->apr_err)) 1443251881Speter { 1444251881Speter svn_error_clear(err); 1445251881Speter err = SVN_NO_ERROR; 1446251881Speter } 1447251881Speter return svn_error_trace(err); 1448251881Speter} 1449251881Speter 1450251881Speter/* forward declarations */ 1451251881Speter 1452251881Speterstatic svn_error_t * 1453251881Speterpack_revprops_shard(const char *pack_file_dir, 1454251881Speter const char *shard_path, 1455251881Speter apr_int64_t shard, 1456251881Speter int max_files_per_dir, 1457251881Speter apr_off_t max_pack_size, 1458251881Speter int compression_level, 1459251881Speter svn_cancel_func_t cancel_func, 1460251881Speter void *cancel_baton, 1461251881Speter apr_pool_t *scratch_pool); 1462251881Speter 1463251881Speterstatic svn_error_t * 1464251881Speterdelete_revprops_shard(const char *shard_path, 1465251881Speter apr_int64_t shard, 1466251881Speter int max_files_per_dir, 1467251881Speter svn_cancel_func_t cancel_func, 1468251881Speter void *cancel_baton, 1469251881Speter apr_pool_t *scratch_pool); 1470251881Speter 1471251881Speter/* In the filesystem FS, pack all revprop shards up to min_unpacked_rev. 1472253734Speter * 1473253734Speter * NOTE: Keep the old non-packed shards around until after the format bump. 1474253734Speter * Otherwise, re-running upgrade will drop the packed revprop shard but 1475253734Speter * have no unpacked data anymore. Call upgrade_cleanup_pack_revprops after 1476253734Speter * the bump. 1477253734Speter * 1478251881Speter * Use SCRATCH_POOL for temporary allocations. 1479251881Speter */ 1480251881Speterstatic svn_error_t * 1481251881Speterupgrade_pack_revprops(svn_fs_t *fs, 1482251881Speter apr_pool_t *scratch_pool) 1483251881Speter{ 1484251881Speter fs_fs_data_t *ffd = fs->fsap_data; 1485251881Speter const char *revprops_shard_path; 1486251881Speter const char *revprops_pack_file_dir; 1487251881Speter apr_int64_t shard; 1488251881Speter apr_int64_t first_unpacked_shard 1489251881Speter = ffd->min_unpacked_rev / ffd->max_files_per_dir; 1490251881Speter 1491251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1492251881Speter const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR, 1493251881Speter scratch_pool); 1494251881Speter int compression_level = ffd->compress_packed_revprops 1495251881Speter ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT 1496251881Speter : SVN_DELTA_COMPRESSION_LEVEL_NONE; 1497251881Speter 1498251881Speter /* first, pack all revprops shards to match the packed revision shards */ 1499251881Speter for (shard = 0; shard < first_unpacked_shard; ++shard) 1500251881Speter { 1501251881Speter revprops_pack_file_dir = svn_dirent_join(revsprops_dir, 1502251881Speter apr_psprintf(iterpool, 1503251881Speter "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD, 1504251881Speter shard), 1505251881Speter iterpool); 1506251881Speter revprops_shard_path = svn_dirent_join(revsprops_dir, 1507251881Speter apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard), 1508251881Speter iterpool); 1509251881Speter 1510251881Speter SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path, 1511251881Speter shard, ffd->max_files_per_dir, 1512251881Speter (int)(0.9 * ffd->revprop_pack_size), 1513251881Speter compression_level, 1514251881Speter NULL, NULL, iterpool)); 1515251881Speter svn_pool_clear(iterpool); 1516251881Speter } 1517251881Speter 1518253734Speter svn_pool_destroy(iterpool); 1519253734Speter 1520253734Speter return SVN_NO_ERROR; 1521253734Speter} 1522253734Speter 1523253734Speter/* In the filesystem FS, remove all non-packed revprop shards up to 1524253734Speter * min_unpacked_rev. Use SCRATCH_POOL for temporary allocations. 1525253734Speter * See upgrade_pack_revprops for more info. 1526253734Speter */ 1527253734Speterstatic svn_error_t * 1528253734Speterupgrade_cleanup_pack_revprops(svn_fs_t *fs, 1529253734Speter apr_pool_t *scratch_pool) 1530253734Speter{ 1531253734Speter fs_fs_data_t *ffd = fs->fsap_data; 1532253734Speter const char *revprops_shard_path; 1533253734Speter apr_int64_t shard; 1534253734Speter apr_int64_t first_unpacked_shard 1535253734Speter = ffd->min_unpacked_rev / ffd->max_files_per_dir; 1536253734Speter 1537253734Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1538253734Speter const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR, 1539253734Speter scratch_pool); 1540253734Speter 1541251881Speter /* delete the non-packed revprops shards afterwards */ 1542251881Speter for (shard = 0; shard < first_unpacked_shard; ++shard) 1543251881Speter { 1544251881Speter revprops_shard_path = svn_dirent_join(revsprops_dir, 1545251881Speter apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard), 1546251881Speter iterpool); 1547251881Speter SVN_ERR(delete_revprops_shard(revprops_shard_path, 1548251881Speter shard, ffd->max_files_per_dir, 1549251881Speter NULL, NULL, iterpool)); 1550251881Speter svn_pool_clear(iterpool); 1551251881Speter } 1552251881Speter 1553251881Speter svn_pool_destroy(iterpool); 1554251881Speter 1555251881Speter return SVN_NO_ERROR; 1556251881Speter} 1557251881Speter 1558251881Speterstatic svn_error_t * 1559251881Speterupgrade_body(void *baton, apr_pool_t *pool) 1560251881Speter{ 1561251881Speter svn_fs_t *fs = baton; 1562251881Speter int format, max_files_per_dir; 1563251881Speter const char *format_path = path_format(fs, pool); 1564251881Speter svn_node_kind_t kind; 1565253734Speter svn_boolean_t needs_revprop_shard_cleanup = FALSE; 1566251881Speter 1567251881Speter /* Read the FS format number and max-files-per-dir setting. */ 1568251881Speter SVN_ERR(read_format(&format, &max_files_per_dir, format_path, pool)); 1569251881Speter 1570251881Speter /* If the config file does not exist, create one. */ 1571251881Speter SVN_ERR(svn_io_check_path(svn_dirent_join(fs->path, PATH_CONFIG, pool), 1572251881Speter &kind, pool)); 1573251881Speter switch (kind) 1574251881Speter { 1575251881Speter case svn_node_none: 1576251881Speter SVN_ERR(write_config(fs, pool)); 1577251881Speter break; 1578251881Speter case svn_node_file: 1579251881Speter break; 1580251881Speter default: 1581251881Speter return svn_error_createf(SVN_ERR_FS_GENERAL, NULL, 1582251881Speter _("'%s' is not a regular file." 1583251881Speter " Please move it out of " 1584251881Speter "the way and try again"), 1585251881Speter svn_dirent_join(fs->path, PATH_CONFIG, pool)); 1586251881Speter } 1587251881Speter 1588251881Speter /* If we're already up-to-date, there's nothing else to be done here. */ 1589251881Speter if (format == SVN_FS_FS__FORMAT_NUMBER) 1590251881Speter return SVN_NO_ERROR; 1591251881Speter 1592251881Speter /* If our filesystem predates the existance of the 'txn-current 1593251881Speter file', make that file and its corresponding lock file. */ 1594251881Speter if (format < SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 1595251881Speter { 1596251881Speter SVN_ERR(create_file_ignore_eexist(path_txn_current(fs, pool), "0\n", 1597251881Speter pool)); 1598251881Speter SVN_ERR(create_file_ignore_eexist(path_txn_current_lock(fs, pool), "", 1599251881Speter pool)); 1600251881Speter } 1601251881Speter 1602251881Speter /* If our filesystem predates the existance of the 'txn-protorevs' 1603251881Speter dir, make that directory. */ 1604251881Speter if (format < SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) 1605251881Speter { 1606251881Speter /* We don't use path_txn_proto_rev() here because it expects 1607251881Speter we've already bumped our format. */ 1608251881Speter SVN_ERR(svn_io_make_dir_recursively( 1609251881Speter svn_dirent_join(fs->path, PATH_TXN_PROTOS_DIR, pool), pool)); 1610251881Speter } 1611251881Speter 1612251881Speter /* If our filesystem is new enough, write the min unpacked rev file. */ 1613251881Speter if (format < SVN_FS_FS__MIN_PACKED_FORMAT) 1614251881Speter SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool)); 1615251881Speter 1616253734Speter /* If the file system supports revision packing but not revprop packing 1617253734Speter *and* the FS has been sharded, pack the revprops up to the point that 1618253734Speter revision data has been packed. However, keep the non-packed revprop 1619253734Speter files around until after the format bump */ 1620251881Speter if ( format >= SVN_FS_FS__MIN_PACKED_FORMAT 1621253734Speter && format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT 1622253734Speter && max_files_per_dir > 0) 1623253734Speter { 1624253734Speter needs_revprop_shard_cleanup = TRUE; 1625253734Speter SVN_ERR(upgrade_pack_revprops(fs, pool)); 1626253734Speter } 1627251881Speter 1628251881Speter /* Bump the format file. */ 1629253734Speter SVN_ERR(write_format(format_path, SVN_FS_FS__FORMAT_NUMBER, 1630253734Speter max_files_per_dir, TRUE, pool)); 1631253734Speter 1632253734Speter /* Now, it is safe to remove the redundant revprop files. */ 1633253734Speter if (needs_revprop_shard_cleanup) 1634253734Speter SVN_ERR(upgrade_cleanup_pack_revprops(fs, pool)); 1635253734Speter 1636253734Speter /* Done */ 1637253734Speter return SVN_NO_ERROR; 1638251881Speter} 1639251881Speter 1640251881Speter 1641251881Spetersvn_error_t * 1642251881Spetersvn_fs_fs__upgrade(svn_fs_t *fs, apr_pool_t *pool) 1643251881Speter{ 1644251881Speter return svn_fs_fs__with_write_lock(fs, upgrade_body, (void *)fs, pool); 1645251881Speter} 1646251881Speter 1647251881Speter 1648251881Speter/* Functions for dealing with recoverable errors on mutable files 1649251881Speter * 1650251881Speter * Revprops, current, and txn-current files are mutable; that is, they 1651251881Speter * change as part of normal fsfs operation, in constrat to revs files, or 1652251881Speter * the format file, which are written once at create (or upgrade) time. 1653251881Speter * When more than one host writes to the same repository, we will 1654251881Speter * sometimes see these recoverable errors when accesssing these files. 1655251881Speter * 1656251881Speter * These errors all relate to NFS, and thus we only use this retry code if 1657251881Speter * ESTALE is defined. 1658251881Speter * 1659251881Speter ** ESTALE 1660251881Speter * 1661251881Speter * In NFS v3 and under, the server doesn't track opened files. If you 1662251881Speter * unlink(2) or rename(2) a file held open by another process *on the 1663251881Speter * same host*, that host's kernel typically renames the file to 1664251881Speter * .nfsXXXX and automatically deletes that when it's no longer open, 1665251881Speter * but this behavior is not required. 1666251881Speter * 1667251881Speter * For obvious reasons, this does not work *across hosts*. No one 1668251881Speter * knows about the opened file; not the server, and not the deleting 1669251881Speter * client. So the file vanishes, and the reader gets stale NFS file 1670251881Speter * handle. 1671251881Speter * 1672251881Speter ** EIO, ENOENT 1673251881Speter * 1674251881Speter * Some client implementations (at least the 2.6.18.5 kernel that ships 1675251881Speter * with Ubuntu Dapper) sometimes give spurious ENOENT (only on open) or 1676251881Speter * even EIO errors when trying to read these files that have been renamed 1677251881Speter * over on some other host. 1678251881Speter * 1679251881Speter ** Solution 1680251881Speter * 1681251881Speter * Try open and read of such files in try_stringbuf_from_file(). Call 1682251881Speter * this function within a loop of RECOVERABLE_RETRY_COUNT iterations 1683251881Speter * (though, realistically, the second try will succeed). 1684251881Speter */ 1685251881Speter 1686251881Speter#define RECOVERABLE_RETRY_COUNT 10 1687251881Speter 1688251881Speter/* Read the file at PATH and return its content in *CONTENT. *CONTENT will 1689251881Speter * not be modified unless the whole file was read successfully. 1690251881Speter * 1691251881Speter * ESTALE, EIO and ENOENT will not cause this function to return an error 1692251881Speter * unless LAST_ATTEMPT has been set. If MISSING is not NULL, indicate 1693251881Speter * missing files (ENOENT) there. 1694251881Speter * 1695251881Speter * Use POOL for allocations. 1696251881Speter */ 1697251881Speterstatic svn_error_t * 1698251881Spetertry_stringbuf_from_file(svn_stringbuf_t **content, 1699251881Speter svn_boolean_t *missing, 1700251881Speter const char *path, 1701251881Speter svn_boolean_t last_attempt, 1702251881Speter apr_pool_t *pool) 1703251881Speter{ 1704251881Speter svn_error_t *err = svn_stringbuf_from_file2(content, path, pool); 1705251881Speter if (missing) 1706251881Speter *missing = FALSE; 1707251881Speter 1708251881Speter if (err) 1709251881Speter { 1710251881Speter *content = NULL; 1711251881Speter 1712251881Speter if (APR_STATUS_IS_ENOENT(err->apr_err)) 1713251881Speter { 1714251881Speter if (!last_attempt) 1715251881Speter { 1716251881Speter svn_error_clear(err); 1717251881Speter if (missing) 1718251881Speter *missing = TRUE; 1719251881Speter return SVN_NO_ERROR; 1720251881Speter } 1721251881Speter } 1722251881Speter#ifdef ESTALE 1723251881Speter else if (APR_TO_OS_ERROR(err->apr_err) == ESTALE 1724251881Speter || APR_TO_OS_ERROR(err->apr_err) == EIO) 1725251881Speter { 1726251881Speter if (!last_attempt) 1727251881Speter { 1728251881Speter svn_error_clear(err); 1729251881Speter return SVN_NO_ERROR; 1730251881Speter } 1731251881Speter } 1732251881Speter#endif 1733251881Speter } 1734251881Speter 1735251881Speter return svn_error_trace(err); 1736251881Speter} 1737251881Speter 1738251881Speter/* Read the 'current' file FNAME and store the contents in *BUF. 1739251881Speter Allocations are performed in POOL. */ 1740251881Speterstatic svn_error_t * 1741251881Speterread_content(svn_stringbuf_t **content, const char *fname, apr_pool_t *pool) 1742251881Speter{ 1743251881Speter int i; 1744251881Speter *content = NULL; 1745251881Speter 1746251881Speter for (i = 0; !*content && (i < RECOVERABLE_RETRY_COUNT); ++i) 1747251881Speter SVN_ERR(try_stringbuf_from_file(content, NULL, 1748251881Speter fname, i + 1 < RECOVERABLE_RETRY_COUNT, 1749251881Speter pool)); 1750251881Speter 1751251881Speter if (!*content) 1752251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 1753251881Speter _("Can't read '%s'"), 1754251881Speter svn_dirent_local_style(fname, pool)); 1755251881Speter 1756251881Speter return SVN_NO_ERROR; 1757251881Speter} 1758251881Speter 1759251881Speter/* Find the youngest revision in a repository at path FS_PATH and 1760251881Speter return it in *YOUNGEST_P. Perform temporary allocations in 1761251881Speter POOL. */ 1762251881Speterstatic svn_error_t * 1763251881Speterget_youngest(svn_revnum_t *youngest_p, 1764251881Speter const char *fs_path, 1765251881Speter apr_pool_t *pool) 1766251881Speter{ 1767251881Speter svn_stringbuf_t *buf; 1768251881Speter SVN_ERR(read_content(&buf, svn_dirent_join(fs_path, PATH_CURRENT, pool), 1769251881Speter pool)); 1770251881Speter 1771251881Speter *youngest_p = SVN_STR_TO_REV(buf->data); 1772251881Speter 1773251881Speter return SVN_NO_ERROR; 1774251881Speter} 1775251881Speter 1776251881Speter 1777251881Spetersvn_error_t * 1778251881Spetersvn_fs_fs__youngest_rev(svn_revnum_t *youngest_p, 1779251881Speter svn_fs_t *fs, 1780251881Speter apr_pool_t *pool) 1781251881Speter{ 1782251881Speter fs_fs_data_t *ffd = fs->fsap_data; 1783251881Speter 1784251881Speter SVN_ERR(get_youngest(youngest_p, fs->path, pool)); 1785251881Speter ffd->youngest_rev_cache = *youngest_p; 1786251881Speter 1787251881Speter return SVN_NO_ERROR; 1788251881Speter} 1789251881Speter 1790251881Speter/* Given a revision file FILE that has been pre-positioned at the 1791251881Speter beginning of a Node-Rev header block, read in that header block and 1792251881Speter store it in the apr_hash_t HEADERS. All allocations will be from 1793251881Speter POOL. */ 1794251881Speterstatic svn_error_t * read_header_block(apr_hash_t **headers, 1795251881Speter svn_stream_t *stream, 1796251881Speter apr_pool_t *pool) 1797251881Speter{ 1798251881Speter *headers = apr_hash_make(pool); 1799251881Speter 1800251881Speter while (1) 1801251881Speter { 1802251881Speter svn_stringbuf_t *header_str; 1803251881Speter const char *name, *value; 1804251881Speter apr_size_t i = 0; 1805251881Speter svn_boolean_t eof; 1806251881Speter 1807251881Speter SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof, pool)); 1808251881Speter 1809251881Speter if (eof || header_str->len == 0) 1810251881Speter break; /* end of header block */ 1811251881Speter 1812251881Speter while (header_str->data[i] != ':') 1813251881Speter { 1814251881Speter if (header_str->data[i] == '\0') 1815251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 1816251881Speter _("Found malformed header '%s' in " 1817251881Speter "revision file"), 1818251881Speter header_str->data); 1819251881Speter i++; 1820251881Speter } 1821251881Speter 1822251881Speter /* Create a 'name' string and point to it. */ 1823251881Speter header_str->data[i] = '\0'; 1824251881Speter name = header_str->data; 1825251881Speter 1826251881Speter /* Skip over the NULL byte and the space following it. */ 1827251881Speter i += 2; 1828251881Speter 1829251881Speter if (i > header_str->len) 1830251881Speter { 1831251881Speter /* Restore the original line for the error. */ 1832251881Speter i -= 2; 1833251881Speter header_str->data[i] = ':'; 1834251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 1835251881Speter _("Found malformed header '%s' in " 1836251881Speter "revision file"), 1837251881Speter header_str->data); 1838251881Speter } 1839251881Speter 1840251881Speter value = header_str->data + i; 1841251881Speter 1842251881Speter /* header_str is safely in our pool, so we can use bits of it as 1843251881Speter key and value. */ 1844251881Speter svn_hash_sets(*headers, name, value); 1845251881Speter } 1846251881Speter 1847251881Speter return SVN_NO_ERROR; 1848251881Speter} 1849251881Speter 1850251881Speter/* Return SVN_ERR_FS_NO_SUCH_REVISION if the given revision is newer 1851251881Speter than the current youngest revision or is simply not a valid 1852251881Speter revision number, else return success. 1853251881Speter 1854251881Speter FSFS is based around the concept that commits only take effect when 1855251881Speter the number in "current" is bumped. Thus if there happens to be a rev 1856251881Speter or revprops file installed for a revision higher than the one recorded 1857251881Speter in "current" (because a commit failed between installing the rev file 1858251881Speter and bumping "current", or because an administrator rolled back the 1859251881Speter repository by resetting "current" without deleting rev files, etc), it 1860251881Speter ought to be completely ignored. This function provides the check 1861251881Speter by which callers can make that decision. */ 1862251881Speterstatic svn_error_t * 1863251881Speterensure_revision_exists(svn_fs_t *fs, 1864251881Speter svn_revnum_t rev, 1865251881Speter apr_pool_t *pool) 1866251881Speter{ 1867251881Speter fs_fs_data_t *ffd = fs->fsap_data; 1868251881Speter 1869251881Speter if (! SVN_IS_VALID_REVNUM(rev)) 1870251881Speter return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 1871251881Speter _("Invalid revision number '%ld'"), rev); 1872251881Speter 1873251881Speter 1874251881Speter /* Did the revision exist the last time we checked the current 1875251881Speter file? */ 1876251881Speter if (rev <= ffd->youngest_rev_cache) 1877251881Speter return SVN_NO_ERROR; 1878251881Speter 1879251881Speter SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs->path, pool)); 1880251881Speter 1881251881Speter /* Check again. */ 1882251881Speter if (rev <= ffd->youngest_rev_cache) 1883251881Speter return SVN_NO_ERROR; 1884251881Speter 1885251881Speter return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 1886251881Speter _("No such revision %ld"), rev); 1887251881Speter} 1888251881Speter 1889251881Spetersvn_error_t * 1890251881Spetersvn_fs_fs__revision_exists(svn_revnum_t rev, 1891251881Speter svn_fs_t *fs, 1892251881Speter apr_pool_t *pool) 1893251881Speter{ 1894251881Speter /* Different order of parameters. */ 1895251881Speter SVN_ERR(ensure_revision_exists(fs, rev, pool)); 1896251881Speter return SVN_NO_ERROR; 1897251881Speter} 1898251881Speter 1899251881Speter/* Open the correct revision file for REV. If the filesystem FS has 1900251881Speter been packed, *FILE will be set to the packed file; otherwise, set *FILE 1901251881Speter to the revision file for REV. Return SVN_ERR_FS_NO_SUCH_REVISION if the 1902251881Speter file doesn't exist. 1903251881Speter 1904251881Speter TODO: Consider returning an indication of whether this is a packed rev 1905251881Speter file, so the caller need not rely on is_packed_rev() which in turn 1906251881Speter relies on the cached FFD->min_unpacked_rev value not having changed 1907251881Speter since the rev file was opened. 1908251881Speter 1909251881Speter Use POOL for allocations. */ 1910251881Speterstatic svn_error_t * 1911251881Speteropen_pack_or_rev_file(apr_file_t **file, 1912251881Speter svn_fs_t *fs, 1913251881Speter svn_revnum_t rev, 1914251881Speter apr_pool_t *pool) 1915251881Speter{ 1916251881Speter fs_fs_data_t *ffd = fs->fsap_data; 1917251881Speter svn_error_t *err; 1918251881Speter const char *path; 1919251881Speter svn_boolean_t retry = FALSE; 1920251881Speter 1921251881Speter do 1922251881Speter { 1923251881Speter err = svn_fs_fs__path_rev_absolute(&path, fs, rev, pool); 1924251881Speter 1925251881Speter /* open the revision file in buffered r/o mode */ 1926251881Speter if (! err) 1927251881Speter err = svn_io_file_open(file, path, 1928251881Speter APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool); 1929251881Speter 1930251881Speter if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 1931251881Speter { 1932251881Speter if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 1933251881Speter { 1934251881Speter /* Could not open the file. This may happen if the 1935251881Speter * file once existed but got packed later. */ 1936251881Speter svn_error_clear(err); 1937251881Speter 1938251881Speter /* if that was our 2nd attempt, leave it at that. */ 1939251881Speter if (retry) 1940251881Speter return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 1941251881Speter _("No such revision %ld"), rev); 1942251881Speter 1943251881Speter /* We failed for the first time. Refresh cache & retry. */ 1944251881Speter SVN_ERR(update_min_unpacked_rev(fs, pool)); 1945251881Speter 1946251881Speter retry = TRUE; 1947251881Speter } 1948251881Speter else 1949251881Speter { 1950251881Speter svn_error_clear(err); 1951251881Speter return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 1952251881Speter _("No such revision %ld"), rev); 1953251881Speter } 1954251881Speter } 1955251881Speter else 1956251881Speter { 1957251881Speter retry = FALSE; 1958251881Speter } 1959251881Speter } 1960251881Speter while (retry); 1961251881Speter 1962251881Speter return svn_error_trace(err); 1963251881Speter} 1964251881Speter 1965251881Speter/* Reads a line from STREAM and converts it to a 64 bit integer to be 1966251881Speter * returned in *RESULT. If we encounter eof, set *HIT_EOF and leave 1967251881Speter * *RESULT unchanged. If HIT_EOF is NULL, EOF causes an "corrupt FS" 1968251881Speter * error return. 1969251881Speter * SCRATCH_POOL is used for temporary allocations. 1970251881Speter */ 1971251881Speterstatic svn_error_t * 1972251881Speterread_number_from_stream(apr_int64_t *result, 1973251881Speter svn_boolean_t *hit_eof, 1974251881Speter svn_stream_t *stream, 1975251881Speter apr_pool_t *scratch_pool) 1976251881Speter{ 1977251881Speter svn_stringbuf_t *sb; 1978251881Speter svn_boolean_t eof; 1979251881Speter svn_error_t *err; 1980251881Speter 1981251881Speter SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, scratch_pool)); 1982251881Speter if (hit_eof) 1983251881Speter *hit_eof = eof; 1984251881Speter else 1985251881Speter if (eof) 1986251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Unexpected EOF")); 1987251881Speter 1988251881Speter if (!eof) 1989251881Speter { 1990251881Speter err = svn_cstring_atoi64(result, sb->data); 1991251881Speter if (err) 1992251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, err, 1993251881Speter _("Number '%s' invalid or too large"), 1994251881Speter sb->data); 1995251881Speter } 1996251881Speter 1997251881Speter return SVN_NO_ERROR; 1998251881Speter} 1999251881Speter 2000251881Speter/* Given REV in FS, set *REV_OFFSET to REV's offset in the packed file. 2001251881Speter Use POOL for temporary allocations. */ 2002251881Speterstatic svn_error_t * 2003251881Speterget_packed_offset(apr_off_t *rev_offset, 2004251881Speter svn_fs_t *fs, 2005251881Speter svn_revnum_t rev, 2006251881Speter apr_pool_t *pool) 2007251881Speter{ 2008251881Speter fs_fs_data_t *ffd = fs->fsap_data; 2009251881Speter svn_stream_t *manifest_stream; 2010251881Speter svn_boolean_t is_cached; 2011251881Speter svn_revnum_t shard; 2012251881Speter apr_int64_t shard_pos; 2013251881Speter apr_array_header_t *manifest; 2014251881Speter apr_pool_t *iterpool; 2015251881Speter 2016251881Speter shard = rev / ffd->max_files_per_dir; 2017251881Speter 2018251881Speter /* position of the shard within the manifest */ 2019251881Speter shard_pos = rev % ffd->max_files_per_dir; 2020251881Speter 2021251881Speter /* fetch exactly that element into *rev_offset, if the manifest is found 2022251881Speter in the cache */ 2023251881Speter SVN_ERR(svn_cache__get_partial((void **) rev_offset, &is_cached, 2024251881Speter ffd->packed_offset_cache, &shard, 2025251881Speter svn_fs_fs__get_sharded_offset, &shard_pos, 2026251881Speter pool)); 2027251881Speter 2028251881Speter if (is_cached) 2029251881Speter return SVN_NO_ERROR; 2030251881Speter 2031251881Speter /* Open the manifest file. */ 2032251881Speter SVN_ERR(svn_stream_open_readonly(&manifest_stream, 2033251881Speter path_rev_packed(fs, rev, PATH_MANIFEST, 2034251881Speter pool), 2035251881Speter pool, pool)); 2036251881Speter 2037251881Speter /* While we're here, let's just read the entire manifest file into an array, 2038251881Speter so we can cache the entire thing. */ 2039251881Speter iterpool = svn_pool_create(pool); 2040251881Speter manifest = apr_array_make(pool, ffd->max_files_per_dir, sizeof(apr_off_t)); 2041251881Speter while (1) 2042251881Speter { 2043251881Speter svn_boolean_t eof; 2044251881Speter apr_int64_t val; 2045251881Speter 2046251881Speter svn_pool_clear(iterpool); 2047251881Speter SVN_ERR(read_number_from_stream(&val, &eof, manifest_stream, iterpool)); 2048251881Speter if (eof) 2049251881Speter break; 2050251881Speter 2051251881Speter APR_ARRAY_PUSH(manifest, apr_off_t) = (apr_off_t)val; 2052251881Speter } 2053251881Speter svn_pool_destroy(iterpool); 2054251881Speter 2055251881Speter *rev_offset = APR_ARRAY_IDX(manifest, rev % ffd->max_files_per_dir, 2056251881Speter apr_off_t); 2057251881Speter 2058251881Speter /* Close up shop and cache the array. */ 2059251881Speter SVN_ERR(svn_stream_close(manifest_stream)); 2060251881Speter return svn_cache__set(ffd->packed_offset_cache, &shard, manifest, pool); 2061251881Speter} 2062251881Speter 2063251881Speter/* Open the revision file for revision REV in filesystem FS and store 2064251881Speter the newly opened file in FILE. Seek to location OFFSET before 2065251881Speter returning. Perform temporary allocations in POOL. */ 2066251881Speterstatic svn_error_t * 2067251881Speteropen_and_seek_revision(apr_file_t **file, 2068251881Speter svn_fs_t *fs, 2069251881Speter svn_revnum_t rev, 2070251881Speter apr_off_t offset, 2071251881Speter apr_pool_t *pool) 2072251881Speter{ 2073251881Speter apr_file_t *rev_file; 2074251881Speter 2075251881Speter SVN_ERR(ensure_revision_exists(fs, rev, pool)); 2076251881Speter 2077251881Speter SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, pool)); 2078251881Speter 2079251881Speter if (is_packed_rev(fs, rev)) 2080251881Speter { 2081251881Speter apr_off_t rev_offset; 2082251881Speter 2083251881Speter SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool)); 2084251881Speter offset += rev_offset; 2085251881Speter } 2086251881Speter 2087251881Speter SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool)); 2088251881Speter 2089251881Speter *file = rev_file; 2090251881Speter 2091251881Speter return SVN_NO_ERROR; 2092251881Speter} 2093251881Speter 2094251881Speter/* Open the representation for a node-revision in transaction TXN_ID 2095251881Speter in filesystem FS and store the newly opened file in FILE. Seek to 2096251881Speter location OFFSET before returning. Perform temporary allocations in 2097251881Speter POOL. Only appropriate for file contents, nor props or directory 2098251881Speter contents. */ 2099251881Speterstatic svn_error_t * 2100251881Speteropen_and_seek_transaction(apr_file_t **file, 2101251881Speter svn_fs_t *fs, 2102251881Speter const char *txn_id, 2103251881Speter representation_t *rep, 2104251881Speter apr_pool_t *pool) 2105251881Speter{ 2106251881Speter apr_file_t *rev_file; 2107251881Speter apr_off_t offset; 2108251881Speter 2109251881Speter SVN_ERR(svn_io_file_open(&rev_file, path_txn_proto_rev(fs, txn_id, pool), 2110251881Speter APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool)); 2111251881Speter 2112251881Speter offset = rep->offset; 2113251881Speter SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool)); 2114251881Speter 2115251881Speter *file = rev_file; 2116251881Speter 2117251881Speter return SVN_NO_ERROR; 2118251881Speter} 2119251881Speter 2120251881Speter/* Given a node-id ID, and a representation REP in filesystem FS, open 2121251881Speter the correct file and seek to the correction location. Store this 2122251881Speter file in *FILE_P. Perform any allocations in POOL. */ 2123251881Speterstatic svn_error_t * 2124251881Speteropen_and_seek_representation(apr_file_t **file_p, 2125251881Speter svn_fs_t *fs, 2126251881Speter representation_t *rep, 2127251881Speter apr_pool_t *pool) 2128251881Speter{ 2129251881Speter if (! rep->txn_id) 2130251881Speter return open_and_seek_revision(file_p, fs, rep->revision, rep->offset, 2131251881Speter pool); 2132251881Speter else 2133251881Speter return open_and_seek_transaction(file_p, fs, rep->txn_id, rep, pool); 2134251881Speter} 2135251881Speter 2136251881Speter/* Parse the description of a representation from STRING and store it 2137251881Speter into *REP_P. If the representation is mutable (the revision is 2138251881Speter given as -1), then use TXN_ID for the representation's txn_id 2139251881Speter field. If MUTABLE_REP_TRUNCATED is true, then this representation 2140251881Speter is for property or directory contents, and no information will be 2141251881Speter expected except the "-1" revision number for a mutable 2142251881Speter representation. Allocate *REP_P in POOL. */ 2143251881Speterstatic svn_error_t * 2144251881Speterread_rep_offsets_body(representation_t **rep_p, 2145251881Speter char *string, 2146251881Speter const char *txn_id, 2147251881Speter svn_boolean_t mutable_rep_truncated, 2148251881Speter apr_pool_t *pool) 2149251881Speter{ 2150251881Speter representation_t *rep; 2151251881Speter char *str; 2152251881Speter apr_int64_t val; 2153251881Speter 2154251881Speter rep = apr_pcalloc(pool, sizeof(*rep)); 2155251881Speter *rep_p = rep; 2156251881Speter 2157251881Speter str = svn_cstring_tokenize(" ", &string); 2158251881Speter if (str == NULL) 2159251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 2160251881Speter _("Malformed text representation offset line in node-rev")); 2161251881Speter 2162251881Speter 2163251881Speter rep->revision = SVN_STR_TO_REV(str); 2164251881Speter if (rep->revision == SVN_INVALID_REVNUM) 2165251881Speter { 2166251881Speter rep->txn_id = txn_id; 2167251881Speter if (mutable_rep_truncated) 2168251881Speter return SVN_NO_ERROR; 2169251881Speter } 2170251881Speter 2171251881Speter str = svn_cstring_tokenize(" ", &string); 2172251881Speter if (str == NULL) 2173251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 2174251881Speter _("Malformed text representation offset line in node-rev")); 2175251881Speter 2176251881Speter SVN_ERR(svn_cstring_atoi64(&val, str)); 2177251881Speter rep->offset = (apr_off_t)val; 2178251881Speter 2179251881Speter str = svn_cstring_tokenize(" ", &string); 2180251881Speter if (str == NULL) 2181251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 2182251881Speter _("Malformed text representation offset line in node-rev")); 2183251881Speter 2184251881Speter SVN_ERR(svn_cstring_atoi64(&val, str)); 2185251881Speter rep->size = (svn_filesize_t)val; 2186251881Speter 2187251881Speter str = svn_cstring_tokenize(" ", &string); 2188251881Speter if (str == NULL) 2189251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 2190251881Speter _("Malformed text representation offset line in node-rev")); 2191251881Speter 2192251881Speter SVN_ERR(svn_cstring_atoi64(&val, str)); 2193251881Speter rep->expanded_size = (svn_filesize_t)val; 2194251881Speter 2195251881Speter /* Read in the MD5 hash. */ 2196251881Speter str = svn_cstring_tokenize(" ", &string); 2197251881Speter if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2))) 2198251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 2199251881Speter _("Malformed text representation offset line in node-rev")); 2200251881Speter 2201251881Speter SVN_ERR(svn_checksum_parse_hex(&rep->md5_checksum, svn_checksum_md5, str, 2202251881Speter pool)); 2203251881Speter 2204251881Speter /* The remaining fields are only used for formats >= 4, so check that. */ 2205251881Speter str = svn_cstring_tokenize(" ", &string); 2206251881Speter if (str == NULL) 2207251881Speter return SVN_NO_ERROR; 2208251881Speter 2209251881Speter /* Read the SHA1 hash. */ 2210251881Speter if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2)) 2211251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 2212251881Speter _("Malformed text representation offset line in node-rev")); 2213251881Speter 2214251881Speter SVN_ERR(svn_checksum_parse_hex(&rep->sha1_checksum, svn_checksum_sha1, str, 2215251881Speter pool)); 2216251881Speter 2217251881Speter /* Read the uniquifier. */ 2218251881Speter str = svn_cstring_tokenize(" ", &string); 2219251881Speter if (str == NULL) 2220251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 2221251881Speter _("Malformed text representation offset line in node-rev")); 2222251881Speter 2223251881Speter rep->uniquifier = apr_pstrdup(pool, str); 2224251881Speter 2225251881Speter return SVN_NO_ERROR; 2226251881Speter} 2227251881Speter 2228251881Speter/* Wrap read_rep_offsets_body(), extracting its TXN_ID from our NODEREV_ID, 2229251881Speter and adding an error message. */ 2230251881Speterstatic svn_error_t * 2231251881Speterread_rep_offsets(representation_t **rep_p, 2232251881Speter char *string, 2233251881Speter const svn_fs_id_t *noderev_id, 2234251881Speter svn_boolean_t mutable_rep_truncated, 2235251881Speter apr_pool_t *pool) 2236251881Speter{ 2237251881Speter svn_error_t *err; 2238251881Speter const char *txn_id; 2239251881Speter 2240251881Speter if (noderev_id) 2241251881Speter txn_id = svn_fs_fs__id_txn_id(noderev_id); 2242251881Speter else 2243251881Speter txn_id = NULL; 2244251881Speter 2245251881Speter err = read_rep_offsets_body(rep_p, string, txn_id, mutable_rep_truncated, 2246251881Speter pool); 2247251881Speter if (err) 2248251881Speter { 2249251881Speter const svn_string_t *id_unparsed = svn_fs_fs__id_unparse(noderev_id, pool); 2250251881Speter const char *where; 2251251881Speter where = apr_psprintf(pool, 2252251881Speter _("While reading representation offsets " 2253251881Speter "for node-revision '%s':"), 2254251881Speter noderev_id ? id_unparsed->data : "(null)"); 2255251881Speter 2256251881Speter return svn_error_quick_wrap(err, where); 2257251881Speter } 2258251881Speter else 2259251881Speter return SVN_NO_ERROR; 2260251881Speter} 2261251881Speter 2262251881Speterstatic svn_error_t * 2263251881Spetererr_dangling_id(svn_fs_t *fs, const svn_fs_id_t *id) 2264251881Speter{ 2265251881Speter svn_string_t *id_str = svn_fs_fs__id_unparse(id, fs->pool); 2266251881Speter return svn_error_createf 2267251881Speter (SVN_ERR_FS_ID_NOT_FOUND, 0, 2268251881Speter _("Reference to non-existent node '%s' in filesystem '%s'"), 2269251881Speter id_str->data, fs->path); 2270251881Speter} 2271251881Speter 2272251881Speter/* Look up the NODEREV_P for ID in FS' node revsion cache. If noderev 2273251881Speter * caching has been enabled and the data can be found, IS_CACHED will 2274251881Speter * be set to TRUE. The noderev will be allocated from POOL. 2275251881Speter * 2276251881Speter * Non-permanent ids (e.g. ids within a TXN) will not be cached. 2277251881Speter */ 2278251881Speterstatic svn_error_t * 2279251881Speterget_cached_node_revision_body(node_revision_t **noderev_p, 2280251881Speter svn_fs_t *fs, 2281251881Speter const svn_fs_id_t *id, 2282251881Speter svn_boolean_t *is_cached, 2283251881Speter apr_pool_t *pool) 2284251881Speter{ 2285251881Speter fs_fs_data_t *ffd = fs->fsap_data; 2286251881Speter if (! ffd->node_revision_cache || svn_fs_fs__id_txn_id(id)) 2287251881Speter { 2288251881Speter *is_cached = FALSE; 2289251881Speter } 2290251881Speter else 2291251881Speter { 2292251881Speter pair_cache_key_t key = { 0 }; 2293251881Speter 2294251881Speter key.revision = svn_fs_fs__id_rev(id); 2295251881Speter key.second = svn_fs_fs__id_offset(id); 2296251881Speter SVN_ERR(svn_cache__get((void **) noderev_p, 2297251881Speter is_cached, 2298251881Speter ffd->node_revision_cache, 2299251881Speter &key, 2300251881Speter pool)); 2301251881Speter } 2302251881Speter 2303251881Speter return SVN_NO_ERROR; 2304251881Speter} 2305251881Speter 2306251881Speter/* If noderev caching has been enabled, store the NODEREV_P for the given ID 2307251881Speter * in FS' node revsion cache. SCRATCH_POOL is used for temporary allcations. 2308251881Speter * 2309251881Speter * Non-permanent ids (e.g. ids within a TXN) will not be cached. 2310251881Speter */ 2311251881Speterstatic svn_error_t * 2312251881Speterset_cached_node_revision_body(node_revision_t *noderev_p, 2313251881Speter svn_fs_t *fs, 2314251881Speter const svn_fs_id_t *id, 2315251881Speter apr_pool_t *scratch_pool) 2316251881Speter{ 2317251881Speter fs_fs_data_t *ffd = fs->fsap_data; 2318251881Speter 2319251881Speter if (ffd->node_revision_cache && !svn_fs_fs__id_txn_id(id)) 2320251881Speter { 2321251881Speter pair_cache_key_t key = { 0 }; 2322251881Speter 2323251881Speter key.revision = svn_fs_fs__id_rev(id); 2324251881Speter key.second = svn_fs_fs__id_offset(id); 2325251881Speter return svn_cache__set(ffd->node_revision_cache, 2326251881Speter &key, 2327251881Speter noderev_p, 2328251881Speter scratch_pool); 2329251881Speter } 2330251881Speter 2331251881Speter return SVN_NO_ERROR; 2332251881Speter} 2333251881Speter 2334251881Speter/* Get the node-revision for the node ID in FS. 2335251881Speter Set *NODEREV_P to the new node-revision structure, allocated in POOL. 2336251881Speter See svn_fs_fs__get_node_revision, which wraps this and adds another 2337251881Speter error. */ 2338251881Speterstatic svn_error_t * 2339251881Speterget_node_revision_body(node_revision_t **noderev_p, 2340251881Speter svn_fs_t *fs, 2341251881Speter const svn_fs_id_t *id, 2342251881Speter apr_pool_t *pool) 2343251881Speter{ 2344251881Speter apr_file_t *revision_file; 2345251881Speter svn_error_t *err; 2346251881Speter svn_boolean_t is_cached = FALSE; 2347251881Speter 2348251881Speter /* First, try a cache lookup. If that succeeds, we are done here. */ 2349251881Speter SVN_ERR(get_cached_node_revision_body(noderev_p, fs, id, &is_cached, pool)); 2350251881Speter if (is_cached) 2351251881Speter return SVN_NO_ERROR; 2352251881Speter 2353251881Speter if (svn_fs_fs__id_txn_id(id)) 2354251881Speter { 2355251881Speter /* This is a transaction node-rev. */ 2356251881Speter err = svn_io_file_open(&revision_file, path_txn_node_rev(fs, id, pool), 2357251881Speter APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool); 2358251881Speter } 2359251881Speter else 2360251881Speter { 2361251881Speter /* This is a revision node-rev. */ 2362251881Speter err = open_and_seek_revision(&revision_file, fs, 2363251881Speter svn_fs_fs__id_rev(id), 2364251881Speter svn_fs_fs__id_offset(id), 2365251881Speter pool); 2366251881Speter } 2367251881Speter 2368251881Speter if (err) 2369251881Speter { 2370251881Speter if (APR_STATUS_IS_ENOENT(err->apr_err)) 2371251881Speter { 2372251881Speter svn_error_clear(err); 2373251881Speter return svn_error_trace(err_dangling_id(fs, id)); 2374251881Speter } 2375251881Speter 2376251881Speter return svn_error_trace(err); 2377251881Speter } 2378251881Speter 2379251881Speter SVN_ERR(svn_fs_fs__read_noderev(noderev_p, 2380251881Speter svn_stream_from_aprfile2(revision_file, FALSE, 2381251881Speter pool), 2382251881Speter pool)); 2383251881Speter 2384251881Speter /* The noderev is not in cache, yet. Add it, if caching has been enabled. */ 2385251881Speter return set_cached_node_revision_body(*noderev_p, fs, id, pool); 2386251881Speter} 2387251881Speter 2388251881Spetersvn_error_t * 2389251881Spetersvn_fs_fs__read_noderev(node_revision_t **noderev_p, 2390251881Speter svn_stream_t *stream, 2391251881Speter apr_pool_t *pool) 2392251881Speter{ 2393251881Speter apr_hash_t *headers; 2394251881Speter node_revision_t *noderev; 2395251881Speter char *value; 2396251881Speter const char *noderev_id; 2397251881Speter 2398251881Speter SVN_ERR(read_header_block(&headers, stream, pool)); 2399251881Speter 2400251881Speter noderev = apr_pcalloc(pool, sizeof(*noderev)); 2401251881Speter 2402251881Speter /* Read the node-rev id. */ 2403251881Speter value = svn_hash_gets(headers, HEADER_ID); 2404251881Speter if (value == NULL) 2405251881Speter /* ### More information: filename/offset coordinates */ 2406251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 2407251881Speter _("Missing id field in node-rev")); 2408251881Speter 2409251881Speter SVN_ERR(svn_stream_close(stream)); 2410251881Speter 2411251881Speter noderev->id = svn_fs_fs__id_parse(value, strlen(value), pool); 2412251881Speter noderev_id = value; /* for error messages later */ 2413251881Speter 2414251881Speter /* Read the type. */ 2415251881Speter value = svn_hash_gets(headers, HEADER_TYPE); 2416251881Speter 2417251881Speter if ((value == NULL) || 2418251881Speter (strcmp(value, KIND_FILE) != 0 && strcmp(value, KIND_DIR))) 2419251881Speter /* ### s/kind/type/ */ 2420251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2421251881Speter _("Missing kind field in node-rev '%s'"), 2422251881Speter noderev_id); 2423251881Speter 2424251881Speter noderev->kind = (strcmp(value, KIND_FILE) == 0) ? svn_node_file 2425251881Speter : svn_node_dir; 2426251881Speter 2427251881Speter /* Read the 'count' field. */ 2428251881Speter value = svn_hash_gets(headers, HEADER_COUNT); 2429251881Speter if (value) 2430251881Speter SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value)); 2431251881Speter else 2432251881Speter noderev->predecessor_count = 0; 2433251881Speter 2434251881Speter /* Get the properties location. */ 2435251881Speter value = svn_hash_gets(headers, HEADER_PROPS); 2436251881Speter if (value) 2437251881Speter { 2438251881Speter SVN_ERR(read_rep_offsets(&noderev->prop_rep, value, 2439251881Speter noderev->id, TRUE, pool)); 2440251881Speter } 2441251881Speter 2442251881Speter /* Get the data location. */ 2443251881Speter value = svn_hash_gets(headers, HEADER_TEXT); 2444251881Speter if (value) 2445251881Speter { 2446251881Speter SVN_ERR(read_rep_offsets(&noderev->data_rep, value, 2447251881Speter noderev->id, 2448251881Speter (noderev->kind == svn_node_dir), pool)); 2449251881Speter } 2450251881Speter 2451251881Speter /* Get the created path. */ 2452251881Speter value = svn_hash_gets(headers, HEADER_CPATH); 2453251881Speter if (value == NULL) 2454251881Speter { 2455251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2456251881Speter _("Missing cpath field in node-rev '%s'"), 2457251881Speter noderev_id); 2458251881Speter } 2459251881Speter else 2460251881Speter { 2461251881Speter noderev->created_path = apr_pstrdup(pool, value); 2462251881Speter } 2463251881Speter 2464251881Speter /* Get the predecessor ID. */ 2465251881Speter value = svn_hash_gets(headers, HEADER_PRED); 2466251881Speter if (value) 2467251881Speter noderev->predecessor_id = svn_fs_fs__id_parse(value, strlen(value), 2468251881Speter pool); 2469251881Speter 2470251881Speter /* Get the copyroot. */ 2471251881Speter value = svn_hash_gets(headers, HEADER_COPYROOT); 2472251881Speter if (value == NULL) 2473251881Speter { 2474251881Speter noderev->copyroot_path = apr_pstrdup(pool, noderev->created_path); 2475251881Speter noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id); 2476251881Speter } 2477251881Speter else 2478251881Speter { 2479251881Speter char *str; 2480251881Speter 2481251881Speter str = svn_cstring_tokenize(" ", &value); 2482251881Speter if (str == NULL) 2483251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2484251881Speter _("Malformed copyroot line in node-rev '%s'"), 2485251881Speter noderev_id); 2486251881Speter 2487251881Speter noderev->copyroot_rev = SVN_STR_TO_REV(str); 2488251881Speter 2489251881Speter if (*value == '\0') 2490251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2491251881Speter _("Malformed copyroot line in node-rev '%s'"), 2492251881Speter noderev_id); 2493251881Speter noderev->copyroot_path = apr_pstrdup(pool, value); 2494251881Speter } 2495251881Speter 2496251881Speter /* Get the copyfrom. */ 2497251881Speter value = svn_hash_gets(headers, HEADER_COPYFROM); 2498251881Speter if (value == NULL) 2499251881Speter { 2500251881Speter noderev->copyfrom_path = NULL; 2501251881Speter noderev->copyfrom_rev = SVN_INVALID_REVNUM; 2502251881Speter } 2503251881Speter else 2504251881Speter { 2505251881Speter char *str = svn_cstring_tokenize(" ", &value); 2506251881Speter if (str == NULL) 2507251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2508251881Speter _("Malformed copyfrom line in node-rev '%s'"), 2509251881Speter noderev_id); 2510251881Speter 2511251881Speter noderev->copyfrom_rev = SVN_STR_TO_REV(str); 2512251881Speter 2513251881Speter if (*value == 0) 2514251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2515251881Speter _("Malformed copyfrom line in node-rev '%s'"), 2516251881Speter noderev_id); 2517251881Speter noderev->copyfrom_path = apr_pstrdup(pool, value); 2518251881Speter } 2519251881Speter 2520251881Speter /* Get whether this is a fresh txn root. */ 2521251881Speter value = svn_hash_gets(headers, HEADER_FRESHTXNRT); 2522251881Speter noderev->is_fresh_txn_root = (value != NULL); 2523251881Speter 2524251881Speter /* Get the mergeinfo count. */ 2525251881Speter value = svn_hash_gets(headers, HEADER_MINFO_CNT); 2526251881Speter if (value) 2527251881Speter SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value)); 2528251881Speter else 2529251881Speter noderev->mergeinfo_count = 0; 2530251881Speter 2531251881Speter /* Get whether *this* node has mergeinfo. */ 2532251881Speter value = svn_hash_gets(headers, HEADER_MINFO_HERE); 2533251881Speter noderev->has_mergeinfo = (value != NULL); 2534251881Speter 2535251881Speter *noderev_p = noderev; 2536251881Speter 2537251881Speter return SVN_NO_ERROR; 2538251881Speter} 2539251881Speter 2540251881Spetersvn_error_t * 2541251881Spetersvn_fs_fs__get_node_revision(node_revision_t **noderev_p, 2542251881Speter svn_fs_t *fs, 2543251881Speter const svn_fs_id_t *id, 2544251881Speter apr_pool_t *pool) 2545251881Speter{ 2546251881Speter svn_error_t *err = get_node_revision_body(noderev_p, fs, id, pool); 2547251881Speter if (err && err->apr_err == SVN_ERR_FS_CORRUPT) 2548251881Speter { 2549251881Speter svn_string_t *id_string = svn_fs_fs__id_unparse(id, pool); 2550251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, err, 2551251881Speter "Corrupt node-revision '%s'", 2552251881Speter id_string->data); 2553251881Speter } 2554251881Speter return svn_error_trace(err); 2555251881Speter} 2556251881Speter 2557251881Speter 2558251881Speter/* Return a formatted string, compatible with filesystem format FORMAT, 2559251881Speter that represents the location of representation REP. If 2560251881Speter MUTABLE_REP_TRUNCATED is given, the rep is for props or dir contents, 2561251881Speter and only a "-1" revision number will be given for a mutable rep. 2562251881Speter If MAY_BE_CORRUPT is true, guard for NULL when constructing the string. 2563251881Speter Perform the allocation from POOL. */ 2564251881Speterstatic const char * 2565251881Speterrepresentation_string(representation_t *rep, 2566251881Speter int format, 2567251881Speter svn_boolean_t mutable_rep_truncated, 2568251881Speter svn_boolean_t may_be_corrupt, 2569251881Speter apr_pool_t *pool) 2570251881Speter{ 2571251881Speter if (rep->txn_id && mutable_rep_truncated) 2572251881Speter return "-1"; 2573251881Speter 2574251881Speter#define DISPLAY_MAYBE_NULL_CHECKSUM(checksum) \ 2575251881Speter ((!may_be_corrupt || (checksum) != NULL) \ 2576251881Speter ? svn_checksum_to_cstring_display((checksum), pool) \ 2577251881Speter : "(null)") 2578251881Speter 2579251881Speter if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT || rep->sha1_checksum == NULL) 2580251881Speter return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT 2581251881Speter " %" SVN_FILESIZE_T_FMT " %s", 2582251881Speter rep->revision, rep->offset, rep->size, 2583251881Speter rep->expanded_size, 2584251881Speter DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum)); 2585251881Speter 2586251881Speter return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT 2587251881Speter " %" SVN_FILESIZE_T_FMT " %s %s %s", 2588251881Speter rep->revision, rep->offset, rep->size, 2589251881Speter rep->expanded_size, 2590251881Speter DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum), 2591251881Speter DISPLAY_MAYBE_NULL_CHECKSUM(rep->sha1_checksum), 2592251881Speter rep->uniquifier); 2593251881Speter 2594251881Speter#undef DISPLAY_MAYBE_NULL_CHECKSUM 2595251881Speter 2596251881Speter} 2597251881Speter 2598251881Speter 2599251881Spetersvn_error_t * 2600251881Spetersvn_fs_fs__write_noderev(svn_stream_t *outfile, 2601251881Speter node_revision_t *noderev, 2602251881Speter int format, 2603251881Speter svn_boolean_t include_mergeinfo, 2604251881Speter apr_pool_t *pool) 2605251881Speter{ 2606251881Speter SVN_ERR(svn_stream_printf(outfile, pool, HEADER_ID ": %s\n", 2607251881Speter svn_fs_fs__id_unparse(noderev->id, 2608251881Speter pool)->data)); 2609251881Speter 2610251881Speter SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TYPE ": %s\n", 2611251881Speter (noderev->kind == svn_node_file) ? 2612251881Speter KIND_FILE : KIND_DIR)); 2613251881Speter 2614251881Speter if (noderev->predecessor_id) 2615251881Speter SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PRED ": %s\n", 2616251881Speter svn_fs_fs__id_unparse(noderev->predecessor_id, 2617251881Speter pool)->data)); 2618251881Speter 2619251881Speter SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COUNT ": %d\n", 2620251881Speter noderev->predecessor_count)); 2621251881Speter 2622251881Speter if (noderev->data_rep) 2623251881Speter SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TEXT ": %s\n", 2624251881Speter representation_string(noderev->data_rep, 2625251881Speter format, 2626251881Speter (noderev->kind 2627251881Speter == svn_node_dir), 2628251881Speter FALSE, 2629251881Speter pool))); 2630251881Speter 2631251881Speter if (noderev->prop_rep) 2632251881Speter SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PROPS ": %s\n", 2633251881Speter representation_string(noderev->prop_rep, format, 2634251881Speter TRUE, FALSE, pool))); 2635251881Speter 2636251881Speter SVN_ERR(svn_stream_printf(outfile, pool, HEADER_CPATH ": %s\n", 2637251881Speter noderev->created_path)); 2638251881Speter 2639251881Speter if (noderev->copyfrom_path) 2640251881Speter SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYFROM ": %ld" 2641251881Speter " %s\n", 2642251881Speter noderev->copyfrom_rev, 2643251881Speter noderev->copyfrom_path)); 2644251881Speter 2645251881Speter if ((noderev->copyroot_rev != svn_fs_fs__id_rev(noderev->id)) || 2646251881Speter (strcmp(noderev->copyroot_path, noderev->created_path) != 0)) 2647251881Speter SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYROOT ": %ld" 2648251881Speter " %s\n", 2649251881Speter noderev->copyroot_rev, 2650251881Speter noderev->copyroot_path)); 2651251881Speter 2652251881Speter if (noderev->is_fresh_txn_root) 2653251881Speter SVN_ERR(svn_stream_puts(outfile, HEADER_FRESHTXNRT ": y\n")); 2654251881Speter 2655251881Speter if (include_mergeinfo) 2656251881Speter { 2657251881Speter if (noderev->mergeinfo_count > 0) 2658251881Speter SVN_ERR(svn_stream_printf(outfile, pool, HEADER_MINFO_CNT ": %" 2659251881Speter APR_INT64_T_FMT "\n", 2660251881Speter noderev->mergeinfo_count)); 2661251881Speter 2662251881Speter if (noderev->has_mergeinfo) 2663251881Speter SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n")); 2664251881Speter } 2665251881Speter 2666251881Speter return svn_stream_puts(outfile, "\n"); 2667251881Speter} 2668251881Speter 2669251881Spetersvn_error_t * 2670251881Spetersvn_fs_fs__put_node_revision(svn_fs_t *fs, 2671251881Speter const svn_fs_id_t *id, 2672251881Speter node_revision_t *noderev, 2673251881Speter svn_boolean_t fresh_txn_root, 2674251881Speter apr_pool_t *pool) 2675251881Speter{ 2676251881Speter fs_fs_data_t *ffd = fs->fsap_data; 2677251881Speter apr_file_t *noderev_file; 2678251881Speter const char *txn_id = svn_fs_fs__id_txn_id(id); 2679251881Speter 2680251881Speter noderev->is_fresh_txn_root = fresh_txn_root; 2681251881Speter 2682251881Speter if (! txn_id) 2683251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2684251881Speter _("Attempted to write to non-transaction '%s'"), 2685251881Speter svn_fs_fs__id_unparse(id, pool)->data); 2686251881Speter 2687251881Speter SVN_ERR(svn_io_file_open(&noderev_file, path_txn_node_rev(fs, id, pool), 2688251881Speter APR_WRITE | APR_CREATE | APR_TRUNCATE 2689251881Speter | APR_BUFFERED, APR_OS_DEFAULT, pool)); 2690251881Speter 2691251881Speter SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE, 2692251881Speter pool), 2693251881Speter noderev, ffd->format, 2694251881Speter svn_fs_fs__fs_supports_mergeinfo(fs), 2695251881Speter pool)); 2696251881Speter 2697251881Speter SVN_ERR(svn_io_file_close(noderev_file, pool)); 2698251881Speter 2699251881Speter return SVN_NO_ERROR; 2700251881Speter} 2701251881Speter 2702251881Speter/* For the in-transaction NODEREV within FS, write the sha1->rep mapping 2703251881Speter * file in the respective transaction, if rep sharing has been enabled etc. 2704251881Speter * Use POOL for temporary allocations. 2705251881Speter */ 2706251881Speterstatic svn_error_t * 2707251881Speterstore_sha1_rep_mapping(svn_fs_t *fs, 2708251881Speter node_revision_t *noderev, 2709251881Speter apr_pool_t *pool) 2710251881Speter{ 2711251881Speter fs_fs_data_t *ffd = fs->fsap_data; 2712251881Speter 2713251881Speter /* if rep sharing has been enabled and the noderev has a data rep and 2714251881Speter * its SHA-1 is known, store the rep struct under its SHA1. */ 2715251881Speter if ( ffd->rep_sharing_allowed 2716251881Speter && noderev->data_rep 2717251881Speter && noderev->data_rep->sha1_checksum) 2718251881Speter { 2719251881Speter apr_file_t *rep_file; 2720251881Speter const char *file_name = path_txn_sha1(fs, 2721251881Speter svn_fs_fs__id_txn_id(noderev->id), 2722251881Speter noderev->data_rep->sha1_checksum, 2723251881Speter pool); 2724251881Speter const char *rep_string = representation_string(noderev->data_rep, 2725251881Speter ffd->format, 2726251881Speter (noderev->kind 2727251881Speter == svn_node_dir), 2728251881Speter FALSE, 2729251881Speter pool); 2730251881Speter SVN_ERR(svn_io_file_open(&rep_file, file_name, 2731251881Speter APR_WRITE | APR_CREATE | APR_TRUNCATE 2732251881Speter | APR_BUFFERED, APR_OS_DEFAULT, pool)); 2733251881Speter 2734251881Speter SVN_ERR(svn_io_file_write_full(rep_file, rep_string, 2735251881Speter strlen(rep_string), NULL, pool)); 2736251881Speter 2737251881Speter SVN_ERR(svn_io_file_close(rep_file, pool)); 2738251881Speter } 2739251881Speter 2740251881Speter return SVN_NO_ERROR; 2741251881Speter} 2742251881Speter 2743251881Speter 2744251881Speter/* This structure is used to hold the information associated with a 2745251881Speter REP line. */ 2746251881Speterstruct rep_args 2747251881Speter{ 2748251881Speter svn_boolean_t is_delta; 2749251881Speter svn_boolean_t is_delta_vs_empty; 2750251881Speter 2751251881Speter svn_revnum_t base_revision; 2752251881Speter apr_off_t base_offset; 2753251881Speter svn_filesize_t base_length; 2754251881Speter}; 2755251881Speter 2756251881Speter/* Read the next line from file FILE and parse it as a text 2757251881Speter representation entry. Return the parsed entry in *REP_ARGS_P. 2758251881Speter Perform all allocations in POOL. */ 2759251881Speterstatic svn_error_t * 2760251881Speterread_rep_line(struct rep_args **rep_args_p, 2761251881Speter apr_file_t *file, 2762251881Speter apr_pool_t *pool) 2763251881Speter{ 2764251881Speter char buffer[160]; 2765251881Speter apr_size_t limit; 2766251881Speter struct rep_args *rep_args; 2767251881Speter char *str, *last_str = buffer; 2768251881Speter apr_int64_t val; 2769251881Speter 2770251881Speter limit = sizeof(buffer); 2771251881Speter SVN_ERR(svn_io_read_length_line(file, buffer, &limit, pool)); 2772251881Speter 2773251881Speter rep_args = apr_pcalloc(pool, sizeof(*rep_args)); 2774251881Speter rep_args->is_delta = FALSE; 2775251881Speter 2776251881Speter if (strcmp(buffer, REP_PLAIN) == 0) 2777251881Speter { 2778251881Speter *rep_args_p = rep_args; 2779251881Speter return SVN_NO_ERROR; 2780251881Speter } 2781251881Speter 2782251881Speter if (strcmp(buffer, REP_DELTA) == 0) 2783251881Speter { 2784251881Speter /* This is a delta against the empty stream. */ 2785251881Speter rep_args->is_delta = TRUE; 2786251881Speter rep_args->is_delta_vs_empty = TRUE; 2787251881Speter *rep_args_p = rep_args; 2788251881Speter return SVN_NO_ERROR; 2789251881Speter } 2790251881Speter 2791251881Speter rep_args->is_delta = TRUE; 2792251881Speter rep_args->is_delta_vs_empty = FALSE; 2793251881Speter 2794251881Speter /* We have hopefully a DELTA vs. a non-empty base revision. */ 2795251881Speter str = svn_cstring_tokenize(" ", &last_str); 2796251881Speter if (! str || (strcmp(str, REP_DELTA) != 0)) 2797251881Speter goto error; 2798251881Speter 2799251881Speter str = svn_cstring_tokenize(" ", &last_str); 2800251881Speter if (! str) 2801251881Speter goto error; 2802251881Speter rep_args->base_revision = SVN_STR_TO_REV(str); 2803251881Speter 2804251881Speter str = svn_cstring_tokenize(" ", &last_str); 2805251881Speter if (! str) 2806251881Speter goto error; 2807251881Speter SVN_ERR(svn_cstring_atoi64(&val, str)); 2808251881Speter rep_args->base_offset = (apr_off_t)val; 2809251881Speter 2810251881Speter str = svn_cstring_tokenize(" ", &last_str); 2811251881Speter if (! str) 2812251881Speter goto error; 2813251881Speter SVN_ERR(svn_cstring_atoi64(&val, str)); 2814251881Speter rep_args->base_length = (svn_filesize_t)val; 2815251881Speter 2816251881Speter *rep_args_p = rep_args; 2817251881Speter return SVN_NO_ERROR; 2818251881Speter 2819251881Speter error: 2820251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2821251881Speter _("Malformed representation header at %s"), 2822251881Speter path_and_offset_of(file, pool)); 2823251881Speter} 2824251881Speter 2825251881Speter/* Given a revision file REV_FILE, opened to REV in FS, find the Node-ID 2826251881Speter of the header located at OFFSET and store it in *ID_P. Allocate 2827251881Speter temporary variables from POOL. */ 2828251881Speterstatic svn_error_t * 2829251881Speterget_fs_id_at_offset(svn_fs_id_t **id_p, 2830251881Speter apr_file_t *rev_file, 2831251881Speter svn_fs_t *fs, 2832251881Speter svn_revnum_t rev, 2833251881Speter apr_off_t offset, 2834251881Speter apr_pool_t *pool) 2835251881Speter{ 2836251881Speter svn_fs_id_t *id; 2837251881Speter apr_hash_t *headers; 2838251881Speter const char *node_id_str; 2839251881Speter 2840251881Speter SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool)); 2841251881Speter 2842251881Speter SVN_ERR(read_header_block(&headers, 2843251881Speter svn_stream_from_aprfile2(rev_file, TRUE, pool), 2844251881Speter pool)); 2845251881Speter 2846251881Speter /* In error messages, the offset is relative to the pack file, 2847251881Speter not to the rev file. */ 2848251881Speter 2849251881Speter node_id_str = svn_hash_gets(headers, HEADER_ID); 2850251881Speter 2851251881Speter if (node_id_str == NULL) 2852251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2853251881Speter _("Missing node-id in node-rev at r%ld " 2854251881Speter "(offset %s)"), 2855251881Speter rev, 2856251881Speter apr_psprintf(pool, "%" APR_OFF_T_FMT, offset)); 2857251881Speter 2858251881Speter id = svn_fs_fs__id_parse(node_id_str, strlen(node_id_str), pool); 2859251881Speter 2860251881Speter if (id == NULL) 2861251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2862251881Speter _("Corrupt node-id '%s' in node-rev at r%ld " 2863251881Speter "(offset %s)"), 2864251881Speter node_id_str, rev, 2865251881Speter apr_psprintf(pool, "%" APR_OFF_T_FMT, offset)); 2866251881Speter 2867251881Speter *id_p = id; 2868251881Speter 2869251881Speter /* ### assert that the txn_id is REV/OFFSET ? */ 2870251881Speter 2871251881Speter return SVN_NO_ERROR; 2872251881Speter} 2873251881Speter 2874251881Speter 2875251881Speter/* Given an open revision file REV_FILE in FS for REV, locate the trailer that 2876251881Speter specifies the offset to the root node-id and to the changed path 2877251881Speter information. Store the root node offset in *ROOT_OFFSET and the 2878251881Speter changed path offset in *CHANGES_OFFSET. If either of these 2879251881Speter pointers is NULL, do nothing with it. 2880251881Speter 2881251881Speter If PACKED is true, REV_FILE should be a packed shard file. 2882251881Speter ### There is currently no such parameter. This function assumes that 2883251881Speter is_packed_rev(FS, REV) will indicate whether REV_FILE is a packed 2884251881Speter file. Therefore FS->fsap_data->min_unpacked_rev must not have been 2885251881Speter refreshed since REV_FILE was opened if there is a possibility that 2886251881Speter revision REV may have become packed since then. 2887251881Speter TODO: Take an IS_PACKED parameter instead, in order to remove this 2888251881Speter requirement. 2889251881Speter 2890251881Speter Allocate temporary variables from POOL. */ 2891251881Speterstatic svn_error_t * 2892251881Speterget_root_changes_offset(apr_off_t *root_offset, 2893251881Speter apr_off_t *changes_offset, 2894251881Speter apr_file_t *rev_file, 2895251881Speter svn_fs_t *fs, 2896251881Speter svn_revnum_t rev, 2897251881Speter apr_pool_t *pool) 2898251881Speter{ 2899251881Speter fs_fs_data_t *ffd = fs->fsap_data; 2900251881Speter apr_off_t offset; 2901251881Speter apr_off_t rev_offset; 2902251881Speter char buf[64]; 2903251881Speter int i, num_bytes; 2904251881Speter const char *str; 2905251881Speter apr_size_t len; 2906251881Speter apr_seek_where_t seek_relative; 2907251881Speter 2908251881Speter /* Determine where to seek to in the file. 2909251881Speter 2910251881Speter If we've got a pack file, we want to seek to the end of the desired 2911251881Speter revision. But we don't track that, so we seek to the beginning of the 2912251881Speter next revision. 2913251881Speter 2914251881Speter Unless the next revision is in a different file, in which case, we can 2915251881Speter just seek to the end of the pack file -- just like we do in the 2916251881Speter non-packed case. */ 2917251881Speter if (is_packed_rev(fs, rev) && ((rev + 1) % ffd->max_files_per_dir != 0)) 2918251881Speter { 2919251881Speter SVN_ERR(get_packed_offset(&offset, fs, rev + 1, pool)); 2920251881Speter seek_relative = APR_SET; 2921251881Speter } 2922251881Speter else 2923251881Speter { 2924251881Speter seek_relative = APR_END; 2925251881Speter offset = 0; 2926251881Speter } 2927251881Speter 2928251881Speter /* Offset of the revision from the start of the pack file, if applicable. */ 2929251881Speter if (is_packed_rev(fs, rev)) 2930251881Speter SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool)); 2931251881Speter else 2932251881Speter rev_offset = 0; 2933251881Speter 2934251881Speter /* We will assume that the last line containing the two offsets 2935251881Speter will never be longer than 64 characters. */ 2936251881Speter SVN_ERR(svn_io_file_seek(rev_file, seek_relative, &offset, pool)); 2937251881Speter 2938251881Speter offset -= sizeof(buf); 2939251881Speter SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool)); 2940251881Speter 2941251881Speter /* Read in this last block, from which we will identify the last line. */ 2942251881Speter len = sizeof(buf); 2943251881Speter SVN_ERR(svn_io_file_read(rev_file, buf, &len, pool)); 2944251881Speter 2945251881Speter /* This cast should be safe since the maximum amount read, 64, will 2946251881Speter never be bigger than the size of an int. */ 2947251881Speter num_bytes = (int) len; 2948251881Speter 2949251881Speter /* The last byte should be a newline. */ 2950251881Speter if (buf[num_bytes - 1] != '\n') 2951251881Speter { 2952251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2953251881Speter _("Revision file (r%ld) lacks trailing newline"), 2954251881Speter rev); 2955251881Speter } 2956251881Speter 2957251881Speter /* Look for the next previous newline. */ 2958251881Speter for (i = num_bytes - 2; i >= 0; i--) 2959251881Speter { 2960251881Speter if (buf[i] == '\n') 2961251881Speter break; 2962251881Speter } 2963251881Speter 2964251881Speter if (i < 0) 2965251881Speter { 2966251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2967251881Speter _("Final line in revision file (r%ld) longer " 2968251881Speter "than 64 characters"), 2969251881Speter rev); 2970251881Speter } 2971251881Speter 2972251881Speter i++; 2973251881Speter str = &buf[i]; 2974251881Speter 2975251881Speter /* find the next space */ 2976251881Speter for ( ; i < (num_bytes - 2) ; i++) 2977251881Speter if (buf[i] == ' ') 2978251881Speter break; 2979251881Speter 2980251881Speter if (i == (num_bytes - 2)) 2981251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 2982251881Speter _("Final line in revision file r%ld missing space"), 2983251881Speter rev); 2984251881Speter 2985251881Speter if (root_offset) 2986251881Speter { 2987251881Speter apr_int64_t val; 2988251881Speter 2989251881Speter buf[i] = '\0'; 2990251881Speter SVN_ERR(svn_cstring_atoi64(&val, str)); 2991251881Speter *root_offset = rev_offset + (apr_off_t)val; 2992251881Speter } 2993251881Speter 2994251881Speter i++; 2995251881Speter str = &buf[i]; 2996251881Speter 2997251881Speter /* find the next newline */ 2998251881Speter for ( ; i < num_bytes; i++) 2999251881Speter if (buf[i] == '\n') 3000251881Speter break; 3001251881Speter 3002251881Speter if (changes_offset) 3003251881Speter { 3004251881Speter apr_int64_t val; 3005251881Speter 3006251881Speter buf[i] = '\0'; 3007251881Speter SVN_ERR(svn_cstring_atoi64(&val, str)); 3008251881Speter *changes_offset = rev_offset + (apr_off_t)val; 3009251881Speter } 3010251881Speter 3011251881Speter return SVN_NO_ERROR; 3012251881Speter} 3013251881Speter 3014251881Speter/* Move a file into place from OLD_FILENAME in the transactions 3015251881Speter directory to its final location NEW_FILENAME in the repository. On 3016251881Speter Unix, match the permissions of the new file to the permissions of 3017251881Speter PERMS_REFERENCE. Temporary allocations are from POOL. 3018251881Speter 3019251881Speter This function almost duplicates svn_io_file_move(), but it tries to 3020251881Speter guarantee a flush. */ 3021251881Speterstatic svn_error_t * 3022251881Spetermove_into_place(const char *old_filename, 3023251881Speter const char *new_filename, 3024251881Speter const char *perms_reference, 3025251881Speter apr_pool_t *pool) 3026251881Speter{ 3027251881Speter svn_error_t *err; 3028251881Speter 3029251881Speter SVN_ERR(svn_io_copy_perms(perms_reference, old_filename, pool)); 3030251881Speter 3031251881Speter /* Move the file into place. */ 3032251881Speter err = svn_io_file_rename(old_filename, new_filename, pool); 3033251881Speter if (err && APR_STATUS_IS_EXDEV(err->apr_err)) 3034251881Speter { 3035251881Speter apr_file_t *file; 3036251881Speter 3037251881Speter /* Can't rename across devices; fall back to copying. */ 3038251881Speter svn_error_clear(err); 3039251881Speter err = SVN_NO_ERROR; 3040251881Speter SVN_ERR(svn_io_copy_file(old_filename, new_filename, TRUE, pool)); 3041251881Speter 3042251881Speter /* Flush the target of the copy to disk. */ 3043251881Speter SVN_ERR(svn_io_file_open(&file, new_filename, APR_READ, 3044251881Speter APR_OS_DEFAULT, pool)); 3045251881Speter /* ### BH: Does this really guarantee a flush of the data written 3046251881Speter ### via a completely different handle on all operating systems? 3047251881Speter ### 3048251881Speter ### Maybe we should perform the copy ourselves instead of making 3049251881Speter ### apr do that and flush the real handle? */ 3050251881Speter SVN_ERR(svn_io_file_flush_to_disk(file, pool)); 3051251881Speter SVN_ERR(svn_io_file_close(file, pool)); 3052251881Speter } 3053251881Speter if (err) 3054251881Speter return svn_error_trace(err); 3055251881Speter 3056251881Speter#ifdef __linux__ 3057251881Speter { 3058251881Speter /* Linux has the unusual feature that fsync() on a file is not 3059251881Speter enough to ensure that a file's directory entries have been 3060251881Speter flushed to disk; you have to fsync the directory as well. 3061251881Speter On other operating systems, we'd only be asking for trouble 3062251881Speter by trying to open and fsync a directory. */ 3063251881Speter const char *dirname; 3064251881Speter apr_file_t *file; 3065251881Speter 3066251881Speter dirname = svn_dirent_dirname(new_filename, pool); 3067251881Speter SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT, 3068251881Speter pool)); 3069251881Speter SVN_ERR(svn_io_file_flush_to_disk(file, pool)); 3070251881Speter SVN_ERR(svn_io_file_close(file, pool)); 3071251881Speter } 3072251881Speter#endif 3073251881Speter 3074251881Speter return SVN_NO_ERROR; 3075251881Speter} 3076251881Speter 3077251881Spetersvn_error_t * 3078251881Spetersvn_fs_fs__rev_get_root(svn_fs_id_t **root_id_p, 3079251881Speter svn_fs_t *fs, 3080251881Speter svn_revnum_t rev, 3081251881Speter apr_pool_t *pool) 3082251881Speter{ 3083251881Speter fs_fs_data_t *ffd = fs->fsap_data; 3084251881Speter apr_file_t *revision_file; 3085251881Speter apr_off_t root_offset; 3086251881Speter svn_fs_id_t *root_id = NULL; 3087251881Speter svn_boolean_t is_cached; 3088251881Speter 3089251881Speter SVN_ERR(ensure_revision_exists(fs, rev, pool)); 3090251881Speter 3091251881Speter SVN_ERR(svn_cache__get((void **) root_id_p, &is_cached, 3092251881Speter ffd->rev_root_id_cache, &rev, pool)); 3093251881Speter if (is_cached) 3094251881Speter return SVN_NO_ERROR; 3095251881Speter 3096251881Speter SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool)); 3097251881Speter SVN_ERR(get_root_changes_offset(&root_offset, NULL, revision_file, fs, rev, 3098251881Speter pool)); 3099251881Speter 3100251881Speter SVN_ERR(get_fs_id_at_offset(&root_id, revision_file, fs, rev, 3101251881Speter root_offset, pool)); 3102251881Speter 3103251881Speter SVN_ERR(svn_io_file_close(revision_file, pool)); 3104251881Speter 3105251881Speter SVN_ERR(svn_cache__set(ffd->rev_root_id_cache, &rev, root_id, pool)); 3106251881Speter 3107251881Speter *root_id_p = root_id; 3108251881Speter 3109251881Speter return SVN_NO_ERROR; 3110251881Speter} 3111251881Speter 3112251881Speter/* Revprop caching management. 3113251881Speter * 3114251881Speter * Mechanism: 3115251881Speter * ---------- 3116251881Speter * 3117251881Speter * Revprop caching needs to be activated and will be deactivated for the 3118251881Speter * respective FS instance if the necessary infrastructure could not be 3119251881Speter * initialized. In deactivated mode, there is almost no runtime overhead 3120251881Speter * associated with revprop caching. As long as no revprops are being read 3121251881Speter * or changed, revprop caching imposes no overhead. 3122251881Speter * 3123251881Speter * When activated, we cache revprops using (revision, generation) pairs 3124251881Speter * as keys with the generation being incremented upon every revprop change. 3125251881Speter * Since the cache is process-local, the generation needs to be tracked 3126251881Speter * for at least as long as the process lives but may be reset afterwards. 3127251881Speter * 3128251881Speter * To track the revprop generation, we use two-layer approach. On the lower 3129251881Speter * level, we use named atomics to have a system-wide consistent value for 3130251881Speter * the current revprop generation. However, those named atomics will only 3131251881Speter * remain valid for as long as at least one process / thread in the system 3132251881Speter * accesses revprops in the respective repository. The underlying shared 3133251881Speter * memory gets cleaned up afterwards. 3134251881Speter * 3135251881Speter * On the second level, we will use a persistent file to track the latest 3136251881Speter * revprop generation. It will be written upon each revprop change but 3137251881Speter * only be read if we are the first process to initialize the named atomics 3138251881Speter * with that value. 3139251881Speter * 3140251881Speter * The overhead for the second and following accesses to revprops is 3141251881Speter * almost zero on most systems. 3142251881Speter * 3143251881Speter * 3144251881Speter * Tech aspects: 3145251881Speter * ------------- 3146251881Speter * 3147251881Speter * A problem is that we need to provide a globally available file name to 3148251881Speter * back the SHM implementation on OSes that need it. We can only assume 3149251881Speter * write access to some file within the respective repositories. Because 3150251881Speter * a given server process may access thousands of repositories during its 3151251881Speter * lifetime, keeping the SHM data alive for all of them is also not an 3152251881Speter * option. 3153251881Speter * 3154251881Speter * So, we store the new revprop generation on disk as part of each 3155251881Speter * setrevprop call, i.e. this write will be serialized and the write order 3156251881Speter * be guaranteed by the repository write lock. 3157251881Speter * 3158251881Speter * The only racy situation occurs when the data is being read again by two 3159251881Speter * processes concurrently but in that situation, the first process to 3160251881Speter * finish that procedure is guaranteed to be the only one that initializes 3161251881Speter * the SHM data. Since even writers will first go through that 3162251881Speter * initialization phase, they will never operate on stale data. 3163251881Speter */ 3164251881Speter 3165251881Speter/* Read revprop generation as stored on disk for repository FS. The result 3166251881Speter * is returned in *CURRENT. Default to 2 if no such file is available. 3167251881Speter */ 3168251881Speterstatic svn_error_t * 3169251881Speterread_revprop_generation_file(apr_int64_t *current, 3170251881Speter svn_fs_t *fs, 3171251881Speter apr_pool_t *pool) 3172251881Speter{ 3173251881Speter svn_error_t *err; 3174251881Speter apr_file_t *file; 3175251881Speter char buf[80]; 3176251881Speter apr_size_t len; 3177251881Speter const char *path = path_revprop_generation(fs, pool); 3178251881Speter 3179251881Speter err = svn_io_file_open(&file, path, 3180251881Speter APR_READ | APR_BUFFERED, 3181251881Speter APR_OS_DEFAULT, pool); 3182251881Speter if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 3183251881Speter { 3184251881Speter svn_error_clear(err); 3185251881Speter *current = 2; 3186251881Speter 3187251881Speter return SVN_NO_ERROR; 3188251881Speter } 3189251881Speter SVN_ERR(err); 3190251881Speter 3191251881Speter len = sizeof(buf); 3192251881Speter SVN_ERR(svn_io_read_length_line(file, buf, &len, pool)); 3193251881Speter 3194251881Speter /* Check that the first line contains only digits. */ 3195251881Speter SVN_ERR(check_file_buffer_numeric(buf, 0, path, 3196251881Speter "Revprop Generation", pool)); 3197251881Speter SVN_ERR(svn_cstring_atoi64(current, buf)); 3198251881Speter 3199251881Speter return svn_io_file_close(file, pool); 3200251881Speter} 3201251881Speter 3202251881Speter/* Write the CURRENT revprop generation to disk for repository FS. 3203251881Speter */ 3204251881Speterstatic svn_error_t * 3205251881Speterwrite_revprop_generation_file(svn_fs_t *fs, 3206251881Speter apr_int64_t current, 3207251881Speter apr_pool_t *pool) 3208251881Speter{ 3209251881Speter apr_file_t *file; 3210251881Speter const char *tmp_path; 3211251881Speter 3212251881Speter char buf[SVN_INT64_BUFFER_SIZE]; 3213251881Speter apr_size_t len = svn__i64toa(buf, current); 3214251881Speter buf[len] = '\n'; 3215251881Speter 3216251881Speter SVN_ERR(svn_io_open_unique_file3(&file, &tmp_path, fs->path, 3217251881Speter svn_io_file_del_none, pool, pool)); 3218251881Speter SVN_ERR(svn_io_file_write_full(file, buf, len + 1, NULL, pool)); 3219251881Speter SVN_ERR(svn_io_file_close(file, pool)); 3220251881Speter 3221251881Speter return move_into_place(tmp_path, path_revprop_generation(fs, pool), 3222251881Speter tmp_path, pool); 3223251881Speter} 3224251881Speter 3225251881Speter/* Make sure the revprop_namespace member in FS is set. */ 3226251881Speterstatic svn_error_t * 3227251881Speterensure_revprop_namespace(svn_fs_t *fs) 3228251881Speter{ 3229251881Speter fs_fs_data_t *ffd = fs->fsap_data; 3230251881Speter 3231251881Speter return ffd->revprop_namespace == NULL 3232251881Speter ? svn_atomic_namespace__create(&ffd->revprop_namespace, 3233251881Speter svn_dirent_join(fs->path, 3234251881Speter ATOMIC_REVPROP_NAMESPACE, 3235251881Speter fs->pool), 3236251881Speter fs->pool) 3237251881Speter : SVN_NO_ERROR; 3238251881Speter} 3239251881Speter 3240251881Speter/* Make sure the revprop_namespace member in FS is set. */ 3241251881Speterstatic svn_error_t * 3242251881Spetercleanup_revprop_namespace(svn_fs_t *fs) 3243251881Speter{ 3244251881Speter const char *name = svn_dirent_join(fs->path, 3245251881Speter ATOMIC_REVPROP_NAMESPACE, 3246251881Speter fs->pool); 3247251881Speter return svn_error_trace(svn_atomic_namespace__cleanup(name, fs->pool)); 3248251881Speter} 3249251881Speter 3250251881Speter/* Make sure the revprop_generation member in FS is set and, if necessary, 3251251881Speter * initialized with the latest value stored on disk. 3252251881Speter */ 3253251881Speterstatic svn_error_t * 3254251881Speterensure_revprop_generation(svn_fs_t *fs, apr_pool_t *pool) 3255251881Speter{ 3256251881Speter fs_fs_data_t *ffd = fs->fsap_data; 3257251881Speter 3258251881Speter SVN_ERR(ensure_revprop_namespace(fs)); 3259251881Speter if (ffd->revprop_generation == NULL) 3260251881Speter { 3261251881Speter apr_int64_t current = 0; 3262251881Speter 3263251881Speter SVN_ERR(svn_named_atomic__get(&ffd->revprop_generation, 3264251881Speter ffd->revprop_namespace, 3265251881Speter ATOMIC_REVPROP_GENERATION, 3266251881Speter TRUE)); 3267251881Speter 3268251881Speter /* If the generation is at 0, we just created a new namespace 3269251881Speter * (it would be at least 2 otherwise). Read the latest generation 3270251881Speter * from disk and if we are the first one to initialize the atomic 3271251881Speter * (i.e. is still 0), set it to the value just gotten. 3272251881Speter */ 3273251881Speter SVN_ERR(svn_named_atomic__read(¤t, ffd->revprop_generation)); 3274251881Speter if (current == 0) 3275251881Speter { 3276251881Speter SVN_ERR(read_revprop_generation_file(¤t, fs, pool)); 3277251881Speter SVN_ERR(svn_named_atomic__cmpxchg(NULL, current, 0, 3278251881Speter ffd->revprop_generation)); 3279251881Speter } 3280251881Speter } 3281251881Speter 3282251881Speter return SVN_NO_ERROR; 3283251881Speter} 3284251881Speter 3285251881Speter/* Make sure the revprop_timeout member in FS is set. */ 3286251881Speterstatic svn_error_t * 3287251881Speterensure_revprop_timeout(svn_fs_t *fs) 3288251881Speter{ 3289251881Speter fs_fs_data_t *ffd = fs->fsap_data; 3290251881Speter 3291251881Speter SVN_ERR(ensure_revprop_namespace(fs)); 3292251881Speter return ffd->revprop_timeout == NULL 3293251881Speter ? svn_named_atomic__get(&ffd->revprop_timeout, 3294251881Speter ffd->revprop_namespace, 3295251881Speter ATOMIC_REVPROP_TIMEOUT, 3296251881Speter TRUE) 3297251881Speter : SVN_NO_ERROR; 3298251881Speter} 3299251881Speter 3300251881Speter/* Create an error object with the given MESSAGE and pass it to the 3301251881Speter WARNING member of FS. */ 3302251881Speterstatic void 3303251881Speterlog_revprop_cache_init_warning(svn_fs_t *fs, 3304251881Speter svn_error_t *underlying_err, 3305251881Speter const char *message) 3306251881Speter{ 3307251881Speter svn_error_t *err = svn_error_createf(SVN_ERR_FS_REVPROP_CACHE_INIT_FAILURE, 3308251881Speter underlying_err, 3309251881Speter message, fs->path); 3310251881Speter 3311251881Speter if (fs->warning) 3312251881Speter (fs->warning)(fs->warning_baton, err); 3313251881Speter 3314251881Speter svn_error_clear(err); 3315251881Speter} 3316251881Speter 3317251881Speter/* Test whether revprop cache and necessary infrastructure are 3318251881Speter available in FS. */ 3319251881Speterstatic svn_boolean_t 3320251881Speterhas_revprop_cache(svn_fs_t *fs, apr_pool_t *pool) 3321251881Speter{ 3322251881Speter fs_fs_data_t *ffd = fs->fsap_data; 3323251881Speter svn_error_t *error; 3324251881Speter 3325251881Speter /* is the cache (still) enabled? */ 3326251881Speter if (ffd->revprop_cache == NULL) 3327251881Speter return FALSE; 3328251881Speter 3329251881Speter /* is it efficient? */ 3330251881Speter if (!svn_named_atomic__is_efficient()) 3331251881Speter { 3332251881Speter /* access to it would be quite slow 3333251881Speter * -> disable the revprop cache for good 3334251881Speter */ 3335251881Speter ffd->revprop_cache = NULL; 3336251881Speter log_revprop_cache_init_warning(fs, NULL, 3337251881Speter "Revprop caching for '%s' disabled" 3338251881Speter " because it would be inefficient."); 3339251881Speter 3340251881Speter return FALSE; 3341251881Speter } 3342251881Speter 3343251881Speter /* try to access our SHM-backed infrastructure */ 3344251881Speter error = ensure_revprop_generation(fs, pool); 3345251881Speter if (error) 3346251881Speter { 3347251881Speter /* failure -> disable revprop cache for good */ 3348251881Speter 3349251881Speter ffd->revprop_cache = NULL; 3350251881Speter log_revprop_cache_init_warning(fs, error, 3351251881Speter "Revprop caching for '%s' disabled " 3352251881Speter "because SHM infrastructure for revprop " 3353251881Speter "caching failed to initialize."); 3354251881Speter 3355251881Speter return FALSE; 3356251881Speter } 3357251881Speter 3358251881Speter return TRUE; 3359251881Speter} 3360251881Speter 3361251881Speter/* Baton structure for revprop_generation_fixup. */ 3362251881Spetertypedef struct revprop_generation_fixup_t 3363251881Speter{ 3364251881Speter /* revprop generation to read */ 3365251881Speter apr_int64_t *generation; 3366251881Speter 3367251881Speter /* containing the revprop_generation member to query */ 3368251881Speter fs_fs_data_t *ffd; 3369251881Speter} revprop_generation_upgrade_t; 3370251881Speter 3371251881Speter/* If the revprop generation has an odd value, it means the original writer 3372251881Speter of the revprop got killed. We don't know whether that process as able 3373251881Speter to change the revprop data but we assume that it was. Therefore, we 3374251881Speter increase the generation in that case to basically invalidate everyones 3375251881Speter cache content. 3376251881Speter Execute this onlx while holding the write lock to the repo in baton->FFD. 3377251881Speter */ 3378251881Speterstatic svn_error_t * 3379251881Speterrevprop_generation_fixup(void *void_baton, 3380251881Speter apr_pool_t *pool) 3381251881Speter{ 3382251881Speter revprop_generation_upgrade_t *baton = void_baton; 3383251881Speter assert(baton->ffd->has_write_lock); 3384251881Speter 3385251881Speter /* Maybe, either the original revprop writer or some other reader has 3386251881Speter already corrected / bumped the revprop generation. Thus, we need 3387251881Speter to read it again. */ 3388251881Speter SVN_ERR(svn_named_atomic__read(baton->generation, 3389251881Speter baton->ffd->revprop_generation)); 3390251881Speter 3391251881Speter /* Cause everyone to re-read revprops upon their next access, if the 3392251881Speter last revprop write did not complete properly. */ 3393251881Speter while (*baton->generation % 2) 3394251881Speter SVN_ERR(svn_named_atomic__add(baton->generation, 3395251881Speter 1, 3396251881Speter baton->ffd->revprop_generation)); 3397251881Speter 3398251881Speter return SVN_NO_ERROR; 3399251881Speter} 3400251881Speter 3401251881Speter/* Read the current revprop generation and return it in *GENERATION. 3402251881Speter Also, detect aborted / crashed writers and recover from that. 3403251881Speter Use the access object in FS to set the shared mem values. */ 3404251881Speterstatic svn_error_t * 3405251881Speterread_revprop_generation(apr_int64_t *generation, 3406251881Speter svn_fs_t *fs, 3407251881Speter apr_pool_t *pool) 3408251881Speter{ 3409251881Speter apr_int64_t current = 0; 3410251881Speter fs_fs_data_t *ffd = fs->fsap_data; 3411251881Speter 3412251881Speter /* read the current revprop generation number */ 3413251881Speter SVN_ERR(ensure_revprop_generation(fs, pool)); 3414251881Speter SVN_ERR(svn_named_atomic__read(¤t, ffd->revprop_generation)); 3415251881Speter 3416251881Speter /* is an unfinished revprop write under the way? */ 3417251881Speter if (current % 2) 3418251881Speter { 3419251881Speter apr_int64_t timeout = 0; 3420251881Speter 3421251881Speter /* read timeout for the write operation */ 3422251881Speter SVN_ERR(ensure_revprop_timeout(fs)); 3423251881Speter SVN_ERR(svn_named_atomic__read(&timeout, ffd->revprop_timeout)); 3424251881Speter 3425251881Speter /* has the writer process been aborted, 3426251881Speter * i.e. has the timeout been reached? 3427251881Speter */ 3428251881Speter if (apr_time_now() > timeout) 3429251881Speter { 3430251881Speter revprop_generation_upgrade_t baton; 3431251881Speter baton.generation = ¤t; 3432251881Speter baton.ffd = ffd; 3433251881Speter 3434251881Speter /* Ensure that the original writer process no longer exists by 3435251881Speter * acquiring the write lock to this repository. Then, fix up 3436251881Speter * the revprop generation. 3437251881Speter */ 3438251881Speter if (ffd->has_write_lock) 3439251881Speter SVN_ERR(revprop_generation_fixup(&baton, pool)); 3440251881Speter else 3441251881Speter SVN_ERR(svn_fs_fs__with_write_lock(fs, revprop_generation_fixup, 3442251881Speter &baton, pool)); 3443251881Speter } 3444251881Speter } 3445251881Speter 3446251881Speter /* return the value we just got */ 3447251881Speter *generation = current; 3448251881Speter return SVN_NO_ERROR; 3449251881Speter} 3450251881Speter 3451251881Speter/* Set the revprop generation to the next odd number to indicate that 3452251881Speter there is a revprop write process under way. If that times out, 3453251881Speter readers shall recover from that state & re-read revprops. 3454251881Speter Use the access object in FS to set the shared mem value. */ 3455251881Speterstatic svn_error_t * 3456251881Speterbegin_revprop_change(svn_fs_t *fs, apr_pool_t *pool) 3457251881Speter{ 3458251881Speter apr_int64_t current; 3459251881Speter fs_fs_data_t *ffd = fs->fsap_data; 3460251881Speter 3461251881Speter /* set the timeout for the write operation */ 3462251881Speter SVN_ERR(ensure_revprop_timeout(fs)); 3463251881Speter SVN_ERR(svn_named_atomic__write(NULL, 3464251881Speter apr_time_now() + REVPROP_CHANGE_TIMEOUT, 3465251881Speter ffd->revprop_timeout)); 3466251881Speter 3467251881Speter /* set the revprop generation to an odd value to indicate 3468251881Speter * that a write is in progress 3469251881Speter */ 3470251881Speter SVN_ERR(ensure_revprop_generation(fs, pool)); 3471251881Speter do 3472251881Speter { 3473251881Speter SVN_ERR(svn_named_atomic__add(¤t, 3474251881Speter 1, 3475251881Speter ffd->revprop_generation)); 3476251881Speter } 3477251881Speter while (current % 2 == 0); 3478251881Speter 3479251881Speter return SVN_NO_ERROR; 3480251881Speter} 3481251881Speter 3482251881Speter/* Set the revprop generation to the next even number to indicate that 3483251881Speter a) readers shall re-read revprops, and 3484251881Speter b) the write process has been completed (no recovery required) 3485251881Speter Use the access object in FS to set the shared mem value. */ 3486251881Speterstatic svn_error_t * 3487251881Speterend_revprop_change(svn_fs_t *fs, apr_pool_t *pool) 3488251881Speter{ 3489251881Speter apr_int64_t current = 1; 3490251881Speter fs_fs_data_t *ffd = fs->fsap_data; 3491251881Speter 3492251881Speter /* set the revprop generation to an even value to indicate 3493251881Speter * that a write has been completed 3494251881Speter */ 3495251881Speter SVN_ERR(ensure_revprop_generation(fs, pool)); 3496251881Speter do 3497251881Speter { 3498251881Speter SVN_ERR(svn_named_atomic__add(¤t, 3499251881Speter 1, 3500251881Speter ffd->revprop_generation)); 3501251881Speter } 3502251881Speter while (current % 2); 3503251881Speter 3504251881Speter /* Save the latest generation to disk. FS is currently in a "locked" 3505251881Speter * state such that we can be sure the be the only ones to write that 3506251881Speter * file. 3507251881Speter */ 3508251881Speter return write_revprop_generation_file(fs, current, pool); 3509251881Speter} 3510251881Speter 3511251881Speter/* Container for all data required to access the packed revprop file 3512251881Speter * for a given REVISION. This structure will be filled incrementally 3513251881Speter * by read_pack_revprops() its sub-routines. 3514251881Speter */ 3515251881Spetertypedef struct packed_revprops_t 3516251881Speter{ 3517251881Speter /* revision number to read (not necessarily the first in the pack) */ 3518251881Speter svn_revnum_t revision; 3519251881Speter 3520251881Speter /* current revprop generation. Used when populating the revprop cache */ 3521251881Speter apr_int64_t generation; 3522251881Speter 3523251881Speter /* the actual revision properties */ 3524251881Speter apr_hash_t *properties; 3525251881Speter 3526251881Speter /* their size when serialized to a single string 3527251881Speter * (as found in PACKED_REVPROPS) */ 3528251881Speter apr_size_t serialized_size; 3529251881Speter 3530251881Speter 3531251881Speter /* name of the pack file (without folder path) */ 3532251881Speter const char *filename; 3533251881Speter 3534251881Speter /* packed shard folder path */ 3535251881Speter const char *folder; 3536251881Speter 3537251881Speter /* sum of values in SIZES */ 3538251881Speter apr_size_t total_size; 3539251881Speter 3540262253Speter /* first revision in the pack (>= MANIFEST_START) */ 3541251881Speter svn_revnum_t start_revision; 3542251881Speter 3543251881Speter /* size of the revprops in PACKED_REVPROPS */ 3544251881Speter apr_array_header_t *sizes; 3545251881Speter 3546251881Speter /* offset of the revprops in PACKED_REVPROPS */ 3547251881Speter apr_array_header_t *offsets; 3548251881Speter 3549251881Speter 3550251881Speter /* concatenation of the serialized representation of all revprops 3551251881Speter * in the pack, i.e. the pack content without header and compression */ 3552251881Speter svn_stringbuf_t *packed_revprops; 3553251881Speter 3554262253Speter /* First revision covered by MANIFEST. 3555262253Speter * Will equal the shard start revision or 1, for the 1st shard. */ 3556262253Speter svn_revnum_t manifest_start; 3557262253Speter 3558251881Speter /* content of the manifest. 3559262253Speter * Maps long(rev - MANIFEST_START) to const char* pack file name */ 3560251881Speter apr_array_header_t *manifest; 3561251881Speter} packed_revprops_t; 3562251881Speter 3563251881Speter/* Parse the serialized revprops in CONTENT and return them in *PROPERTIES. 3564251881Speter * Also, put them into the revprop cache, if activated, for future use. 3565251881Speter * Three more parameters are being used to update the revprop cache: FS is 3566251881Speter * our file system, the revprops belong to REVISION and the global revprop 3567251881Speter * GENERATION is used as well. 3568251881Speter * 3569251881Speter * The returned hash will be allocated in POOL, SCRATCH_POOL is being used 3570251881Speter * for temporary allocations. 3571251881Speter */ 3572251881Speterstatic svn_error_t * 3573251881Speterparse_revprop(apr_hash_t **properties, 3574251881Speter svn_fs_t *fs, 3575251881Speter svn_revnum_t revision, 3576251881Speter apr_int64_t generation, 3577251881Speter svn_string_t *content, 3578251881Speter apr_pool_t *pool, 3579251881Speter apr_pool_t *scratch_pool) 3580251881Speter{ 3581251881Speter svn_stream_t *stream = svn_stream_from_string(content, scratch_pool); 3582251881Speter *properties = apr_hash_make(pool); 3583251881Speter 3584251881Speter SVN_ERR(svn_hash_read2(*properties, stream, SVN_HASH_TERMINATOR, pool)); 3585251881Speter if (has_revprop_cache(fs, pool)) 3586251881Speter { 3587251881Speter fs_fs_data_t *ffd = fs->fsap_data; 3588251881Speter pair_cache_key_t key = { 0 }; 3589251881Speter 3590251881Speter key.revision = revision; 3591251881Speter key.second = generation; 3592251881Speter SVN_ERR(svn_cache__set(ffd->revprop_cache, &key, *properties, 3593251881Speter scratch_pool)); 3594251881Speter } 3595251881Speter 3596251881Speter return SVN_NO_ERROR; 3597251881Speter} 3598251881Speter 3599251881Speter/* Read the non-packed revprops for revision REV in FS, put them into the 3600251881Speter * revprop cache if activated and return them in *PROPERTIES. GENERATION 3601251881Speter * is the current revprop generation. 3602251881Speter * 3603251881Speter * If the data could not be read due to an otherwise recoverable error, 3604251881Speter * leave *PROPERTIES unchanged. No error will be returned in that case. 3605251881Speter * 3606251881Speter * Allocations will be done in POOL. 3607251881Speter */ 3608251881Speterstatic svn_error_t * 3609251881Speterread_non_packed_revprop(apr_hash_t **properties, 3610251881Speter svn_fs_t *fs, 3611251881Speter svn_revnum_t rev, 3612251881Speter apr_int64_t generation, 3613251881Speter apr_pool_t *pool) 3614251881Speter{ 3615251881Speter svn_stringbuf_t *content = NULL; 3616251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 3617251881Speter svn_boolean_t missing = FALSE; 3618251881Speter int i; 3619251881Speter 3620251881Speter for (i = 0; i < RECOVERABLE_RETRY_COUNT && !missing && !content; ++i) 3621251881Speter { 3622251881Speter svn_pool_clear(iterpool); 3623251881Speter SVN_ERR(try_stringbuf_from_file(&content, 3624251881Speter &missing, 3625251881Speter path_revprops(fs, rev, iterpool), 3626251881Speter i + 1 < RECOVERABLE_RETRY_COUNT, 3627251881Speter iterpool)); 3628251881Speter } 3629251881Speter 3630251881Speter if (content) 3631251881Speter SVN_ERR(parse_revprop(properties, fs, rev, generation, 3632251881Speter svn_stringbuf__morph_into_string(content), 3633251881Speter pool, iterpool)); 3634251881Speter 3635251881Speter svn_pool_clear(iterpool); 3636251881Speter 3637251881Speter return SVN_NO_ERROR; 3638251881Speter} 3639251881Speter 3640251881Speter/* Given FS and REVPROPS->REVISION, fill the FILENAME, FOLDER and MANIFEST 3641251881Speter * members. Use POOL for allocating results and SCRATCH_POOL for temporaries. 3642251881Speter */ 3643251881Speterstatic svn_error_t * 3644251881Speterget_revprop_packname(svn_fs_t *fs, 3645251881Speter packed_revprops_t *revprops, 3646251881Speter apr_pool_t *pool, 3647251881Speter apr_pool_t *scratch_pool) 3648251881Speter{ 3649251881Speter fs_fs_data_t *ffd = fs->fsap_data; 3650251881Speter svn_stringbuf_t *content = NULL; 3651251881Speter const char *manifest_file_path; 3652251881Speter int idx; 3653251881Speter 3654251881Speter /* read content of the manifest file */ 3655251881Speter revprops->folder = path_revprops_pack_shard(fs, revprops->revision, pool); 3656251881Speter manifest_file_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool); 3657251881Speter 3658251881Speter SVN_ERR(read_content(&content, manifest_file_path, pool)); 3659251881Speter 3660251881Speter /* parse the manifest. Every line is a file name */ 3661251881Speter revprops->manifest = apr_array_make(pool, ffd->max_files_per_dir, 3662251881Speter sizeof(const char*)); 3663262253Speter 3664262253Speter /* Read all lines. Since the last line ends with a newline, we will 3665262253Speter end up with a valid but empty string after the last entry. */ 3666262253Speter while (content->data && *content->data) 3667251881Speter { 3668251881Speter APR_ARRAY_PUSH(revprops->manifest, const char*) = content->data; 3669251881Speter content->data = strchr(content->data, '\n'); 3670251881Speter if (content->data) 3671251881Speter { 3672251881Speter *content->data = 0; 3673251881Speter content->data++; 3674251881Speter } 3675251881Speter } 3676251881Speter 3677251881Speter /* Index for our revision. Rev 0 is excluded from the first shard. */ 3678262253Speter revprops->manifest_start = revprops->revision 3679262253Speter - (revprops->revision % ffd->max_files_per_dir); 3680262253Speter if (revprops->manifest_start == 0) 3681262253Speter ++revprops->manifest_start; 3682262253Speter idx = (int)(revprops->revision - revprops->manifest_start); 3683251881Speter 3684251881Speter if (revprops->manifest->nelts <= idx) 3685251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 3686262253Speter _("Packed revprop manifest for r%ld too " 3687251881Speter "small"), revprops->revision); 3688251881Speter 3689251881Speter /* Now get the file name */ 3690251881Speter revprops->filename = APR_ARRAY_IDX(revprops->manifest, idx, const char*); 3691251881Speter 3692251881Speter return SVN_NO_ERROR; 3693251881Speter} 3694251881Speter 3695262253Speter/* Return TRUE, if revision R1 and R2 refer to the same shard in FS. 3696262253Speter */ 3697262253Speterstatic svn_boolean_t 3698262253Spetersame_shard(svn_fs_t *fs, 3699262253Speter svn_revnum_t r1, 3700262253Speter svn_revnum_t r2) 3701262253Speter{ 3702262253Speter fs_fs_data_t *ffd = fs->fsap_data; 3703262253Speter return (r1 / ffd->max_files_per_dir) == (r2 / ffd->max_files_per_dir); 3704262253Speter} 3705262253Speter 3706251881Speter/* Given FS and the full packed file content in REVPROPS->PACKED_REVPROPS, 3707251881Speter * fill the START_REVISION, SIZES, OFFSETS members. Also, make 3708251881Speter * PACKED_REVPROPS point to the first serialized revprop. 3709251881Speter * 3710251881Speter * Parse the revprops for REVPROPS->REVISION and set the PROPERTIES as 3711251881Speter * well as the SERIALIZED_SIZE member. If revprop caching has been 3712251881Speter * enabled, parse all revprops in the pack and cache them. 3713251881Speter */ 3714251881Speterstatic svn_error_t * 3715251881Speterparse_packed_revprops(svn_fs_t *fs, 3716251881Speter packed_revprops_t *revprops, 3717251881Speter apr_pool_t *pool, 3718251881Speter apr_pool_t *scratch_pool) 3719251881Speter{ 3720251881Speter svn_stream_t *stream; 3721251881Speter apr_int64_t first_rev, count, i; 3722251881Speter apr_off_t offset; 3723251881Speter const char *header_end; 3724251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 3725251881Speter 3726251881Speter /* decompress (even if the data is only "stored", there is still a 3727251881Speter * length header to remove) */ 3728251881Speter svn_string_t *compressed 3729251881Speter = svn_stringbuf__morph_into_string(revprops->packed_revprops); 3730251881Speter svn_stringbuf_t *uncompressed = svn_stringbuf_create_empty(pool); 3731253734Speter SVN_ERR(svn__decompress(compressed, uncompressed, APR_SIZE_MAX)); 3732251881Speter 3733251881Speter /* read first revision number and number of revisions in the pack */ 3734251881Speter stream = svn_stream_from_stringbuf(uncompressed, scratch_pool); 3735251881Speter SVN_ERR(read_number_from_stream(&first_rev, NULL, stream, iterpool)); 3736251881Speter SVN_ERR(read_number_from_stream(&count, NULL, stream, iterpool)); 3737251881Speter 3738262253Speter /* Check revision range for validity. */ 3739262253Speter if ( !same_shard(fs, revprops->revision, first_rev) 3740262253Speter || !same_shard(fs, revprops->revision, first_rev + count - 1) 3741262253Speter || count < 1) 3742262253Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 3743262253Speter _("Revprop pack for revision r%ld" 3744262253Speter " contains revprops for r%ld .. r%ld"), 3745262253Speter revprops->revision, 3746262253Speter (svn_revnum_t)first_rev, 3747262253Speter (svn_revnum_t)(first_rev + count -1)); 3748262253Speter 3749262253Speter /* Since start & end are in the same shard, it is enough to just test 3750262253Speter * the FIRST_REV for being actually packed. That will also cover the 3751262253Speter * special case of rev 0 never being packed. */ 3752262253Speter if (!is_packed_revprop(fs, first_rev)) 3753262253Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 3754262253Speter _("Revprop pack for revision r%ld" 3755262253Speter " starts at non-packed revisions r%ld"), 3756262253Speter revprops->revision, (svn_revnum_t)first_rev); 3757262253Speter 3758251881Speter /* make PACKED_REVPROPS point to the first char after the header. 3759251881Speter * This is where the serialized revprops are. */ 3760251881Speter header_end = strstr(uncompressed->data, "\n\n"); 3761251881Speter if (header_end == NULL) 3762251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 3763251881Speter _("Header end not found")); 3764251881Speter 3765251881Speter offset = header_end - uncompressed->data + 2; 3766251881Speter 3767251881Speter revprops->packed_revprops = svn_stringbuf_create_empty(pool); 3768251881Speter revprops->packed_revprops->data = uncompressed->data + offset; 3769251881Speter revprops->packed_revprops->len = (apr_size_t)(uncompressed->len - offset); 3770251881Speter revprops->packed_revprops->blocksize = (apr_size_t)(uncompressed->blocksize - offset); 3771251881Speter 3772251881Speter /* STREAM still points to the first entry in the sizes list. 3773251881Speter * Init / construct REVPROPS members. */ 3774251881Speter revprops->start_revision = (svn_revnum_t)first_rev; 3775251881Speter revprops->sizes = apr_array_make(pool, (int)count, sizeof(offset)); 3776251881Speter revprops->offsets = apr_array_make(pool, (int)count, sizeof(offset)); 3777251881Speter 3778251881Speter /* Now parse, revision by revision, the size and content of each 3779251881Speter * revisions' revprops. */ 3780251881Speter for (i = 0, offset = 0, revprops->total_size = 0; i < count; ++i) 3781251881Speter { 3782251881Speter apr_int64_t size; 3783251881Speter svn_string_t serialized; 3784251881Speter apr_hash_t *properties; 3785251881Speter svn_revnum_t revision = (svn_revnum_t)(first_rev + i); 3786251881Speter 3787251881Speter /* read & check the serialized size */ 3788251881Speter SVN_ERR(read_number_from_stream(&size, NULL, stream, iterpool)); 3789251881Speter if (size + offset > (apr_int64_t)revprops->packed_revprops->len) 3790251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 3791251881Speter _("Packed revprop size exceeds pack file size")); 3792251881Speter 3793251881Speter /* Parse this revprops list, if necessary */ 3794251881Speter serialized.data = revprops->packed_revprops->data + offset; 3795251881Speter serialized.len = (apr_size_t)size; 3796251881Speter 3797251881Speter if (revision == revprops->revision) 3798251881Speter { 3799251881Speter SVN_ERR(parse_revprop(&revprops->properties, fs, revision, 3800251881Speter revprops->generation, &serialized, 3801251881Speter pool, iterpool)); 3802251881Speter revprops->serialized_size = serialized.len; 3803251881Speter } 3804251881Speter else 3805251881Speter { 3806251881Speter /* If revprop caching is enabled, parse any revprops. 3807251881Speter * They will get cached as a side-effect of this. */ 3808251881Speter if (has_revprop_cache(fs, pool)) 3809251881Speter SVN_ERR(parse_revprop(&properties, fs, revision, 3810251881Speter revprops->generation, &serialized, 3811251881Speter iterpool, iterpool)); 3812251881Speter } 3813251881Speter 3814251881Speter /* fill REVPROPS data structures */ 3815251881Speter APR_ARRAY_PUSH(revprops->sizes, apr_off_t) = serialized.len; 3816251881Speter APR_ARRAY_PUSH(revprops->offsets, apr_off_t) = offset; 3817251881Speter revprops->total_size += serialized.len; 3818251881Speter 3819251881Speter offset += serialized.len; 3820251881Speter 3821251881Speter svn_pool_clear(iterpool); 3822251881Speter } 3823251881Speter 3824251881Speter return SVN_NO_ERROR; 3825251881Speter} 3826251881Speter 3827251881Speter/* In filesystem FS, read the packed revprops for revision REV into 3828251881Speter * *REVPROPS. Use GENERATION to populate the revprop cache, if enabled. 3829251881Speter * Allocate data in POOL. 3830251881Speter */ 3831251881Speterstatic svn_error_t * 3832251881Speterread_pack_revprop(packed_revprops_t **revprops, 3833251881Speter svn_fs_t *fs, 3834251881Speter svn_revnum_t rev, 3835251881Speter apr_int64_t generation, 3836251881Speter apr_pool_t *pool) 3837251881Speter{ 3838251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 3839251881Speter svn_boolean_t missing = FALSE; 3840251881Speter svn_error_t *err; 3841251881Speter packed_revprops_t *result; 3842251881Speter int i; 3843251881Speter 3844251881Speter /* someone insisted that REV is packed. Double-check if necessary */ 3845251881Speter if (!is_packed_revprop(fs, rev)) 3846251881Speter SVN_ERR(update_min_unpacked_rev(fs, iterpool)); 3847251881Speter 3848251881Speter if (!is_packed_revprop(fs, rev)) 3849251881Speter return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 3850251881Speter _("No such packed revision %ld"), rev); 3851251881Speter 3852251881Speter /* initialize the result data structure */ 3853251881Speter result = apr_pcalloc(pool, sizeof(*result)); 3854251881Speter result->revision = rev; 3855251881Speter result->generation = generation; 3856251881Speter 3857251881Speter /* try to read the packed revprops. This may require retries if we have 3858251881Speter * concurrent writers. */ 3859251881Speter for (i = 0; i < RECOVERABLE_RETRY_COUNT && !result->packed_revprops; ++i) 3860251881Speter { 3861251881Speter const char *file_path; 3862251881Speter 3863251881Speter /* there might have been concurrent writes. 3864251881Speter * Re-read the manifest and the pack file. 3865251881Speter */ 3866251881Speter SVN_ERR(get_revprop_packname(fs, result, pool, iterpool)); 3867251881Speter file_path = svn_dirent_join(result->folder, 3868251881Speter result->filename, 3869251881Speter iterpool); 3870251881Speter SVN_ERR(try_stringbuf_from_file(&result->packed_revprops, 3871251881Speter &missing, 3872251881Speter file_path, 3873251881Speter i + 1 < RECOVERABLE_RETRY_COUNT, 3874251881Speter pool)); 3875251881Speter 3876251881Speter /* If we could not find the file, there was a write. 3877251881Speter * So, we should refresh our revprop generation info as well such 3878251881Speter * that others may find data we will put into the cache. They would 3879251881Speter * consider it outdated, otherwise. 3880251881Speter */ 3881251881Speter if (missing && has_revprop_cache(fs, pool)) 3882251881Speter SVN_ERR(read_revprop_generation(&result->generation, fs, pool)); 3883251881Speter 3884251881Speter svn_pool_clear(iterpool); 3885251881Speter } 3886251881Speter 3887251881Speter /* the file content should be available now */ 3888251881Speter if (!result->packed_revprops) 3889251881Speter return svn_error_createf(SVN_ERR_FS_PACKED_REVPROP_READ_FAILURE, NULL, 3890262253Speter _("Failed to read revprop pack file for r%ld"), rev); 3891251881Speter 3892251881Speter /* parse it. RESULT will be complete afterwards. */ 3893251881Speter err = parse_packed_revprops(fs, result, pool, iterpool); 3894251881Speter svn_pool_destroy(iterpool); 3895251881Speter if (err) 3896251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, err, 3897262253Speter _("Revprop pack file for r%ld is corrupt"), rev); 3898251881Speter 3899251881Speter *revprops = result; 3900251881Speter 3901251881Speter return SVN_NO_ERROR; 3902251881Speter} 3903251881Speter 3904251881Speter/* Read the revprops for revision REV in FS and return them in *PROPERTIES_P. 3905251881Speter * 3906251881Speter * Allocations will be done in POOL. 3907251881Speter */ 3908251881Speterstatic svn_error_t * 3909251881Speterget_revision_proplist(apr_hash_t **proplist_p, 3910251881Speter svn_fs_t *fs, 3911251881Speter svn_revnum_t rev, 3912251881Speter apr_pool_t *pool) 3913251881Speter{ 3914251881Speter fs_fs_data_t *ffd = fs->fsap_data; 3915251881Speter apr_int64_t generation = 0; 3916251881Speter 3917251881Speter /* not found, yet */ 3918251881Speter *proplist_p = NULL; 3919251881Speter 3920251881Speter /* should they be available at all? */ 3921251881Speter SVN_ERR(ensure_revision_exists(fs, rev, pool)); 3922251881Speter 3923251881Speter /* Try cache lookup first. */ 3924251881Speter if (has_revprop_cache(fs, pool)) 3925251881Speter { 3926251881Speter svn_boolean_t is_cached; 3927251881Speter pair_cache_key_t key = { 0 }; 3928251881Speter 3929251881Speter SVN_ERR(read_revprop_generation(&generation, fs, pool)); 3930251881Speter 3931251881Speter key.revision = rev; 3932251881Speter key.second = generation; 3933251881Speter SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached, 3934251881Speter ffd->revprop_cache, &key, pool)); 3935251881Speter if (is_cached) 3936251881Speter return SVN_NO_ERROR; 3937251881Speter } 3938251881Speter 3939251881Speter /* if REV had not been packed when we began, try reading it from the 3940251881Speter * non-packed shard. If that fails, we will fall through to packed 3941251881Speter * shard reads. */ 3942251881Speter if (!is_packed_revprop(fs, rev)) 3943251881Speter { 3944251881Speter svn_error_t *err = read_non_packed_revprop(proplist_p, fs, rev, 3945251881Speter generation, pool); 3946251881Speter if (err) 3947251881Speter { 3948251881Speter if (!APR_STATUS_IS_ENOENT(err->apr_err) 3949251881Speter || ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT) 3950251881Speter return svn_error_trace(err); 3951251881Speter 3952251881Speter svn_error_clear(err); 3953251881Speter *proplist_p = NULL; /* in case read_non_packed_revprop changed it */ 3954251881Speter } 3955251881Speter } 3956251881Speter 3957251881Speter /* if revprop packing is available and we have not read the revprops, yet, 3958251881Speter * try reading them from a packed shard. If that fails, REV is most 3959251881Speter * likely invalid (or its revprops highly contested). */ 3960251881Speter if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT && !*proplist_p) 3961251881Speter { 3962251881Speter packed_revprops_t *packed_revprops; 3963251881Speter SVN_ERR(read_pack_revprop(&packed_revprops, fs, rev, generation, pool)); 3964251881Speter *proplist_p = packed_revprops->properties; 3965251881Speter } 3966251881Speter 3967251881Speter /* The revprops should have been there. Did we get them? */ 3968251881Speter if (!*proplist_p) 3969251881Speter return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 3970251881Speter _("Could not read revprops for revision %ld"), 3971251881Speter rev); 3972251881Speter 3973251881Speter return SVN_NO_ERROR; 3974251881Speter} 3975251881Speter 3976251881Speter/* Serialize the revision property list PROPLIST of revision REV in 3977251881Speter * filesystem FS to a non-packed file. Return the name of that temporary 3978251881Speter * file in *TMP_PATH and the file path that it must be moved to in 3979251881Speter * *FINAL_PATH. 3980251881Speter * 3981251881Speter * Use POOL for allocations. 3982251881Speter */ 3983251881Speterstatic svn_error_t * 3984251881Speterwrite_non_packed_revprop(const char **final_path, 3985251881Speter const char **tmp_path, 3986251881Speter svn_fs_t *fs, 3987251881Speter svn_revnum_t rev, 3988251881Speter apr_hash_t *proplist, 3989251881Speter apr_pool_t *pool) 3990251881Speter{ 3991289166Speter apr_file_t *file; 3992251881Speter svn_stream_t *stream; 3993251881Speter *final_path = path_revprops(fs, rev, pool); 3994251881Speter 3995251881Speter /* ### do we have a directory sitting around already? we really shouldn't 3996251881Speter ### have to get the dirname here. */ 3997289166Speter SVN_ERR(svn_io_open_unique_file3(&file, tmp_path, 3998289166Speter svn_dirent_dirname(*final_path, pool), 3999289166Speter svn_io_file_del_none, pool, pool)); 4000289166Speter stream = svn_stream_from_aprfile2(file, TRUE, pool); 4001251881Speter SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool)); 4002251881Speter SVN_ERR(svn_stream_close(stream)); 4003251881Speter 4004289166Speter /* Flush temporary file to disk and close it. */ 4005289166Speter SVN_ERR(svn_io_file_flush_to_disk(file, pool)); 4006289166Speter SVN_ERR(svn_io_file_close(file, pool)); 4007289166Speter 4008251881Speter return SVN_NO_ERROR; 4009251881Speter} 4010251881Speter 4011251881Speter/* After writing the new revprop file(s), call this function to move the 4012251881Speter * file at TMP_PATH to FINAL_PATH and give it the permissions from 4013251881Speter * PERMS_REFERENCE. 4014251881Speter * 4015251881Speter * If indicated in BUMP_GENERATION, increase FS' revprop generation. 4016251881Speter * Finally, delete all the temporary files given in FILES_TO_DELETE. 4017251881Speter * The latter may be NULL. 4018251881Speter * 4019251881Speter * Use POOL for temporary allocations. 4020251881Speter */ 4021251881Speterstatic svn_error_t * 4022251881Speterswitch_to_new_revprop(svn_fs_t *fs, 4023251881Speter const char *final_path, 4024251881Speter const char *tmp_path, 4025251881Speter const char *perms_reference, 4026251881Speter apr_array_header_t *files_to_delete, 4027251881Speter svn_boolean_t bump_generation, 4028251881Speter apr_pool_t *pool) 4029251881Speter{ 4030251881Speter /* Now, we may actually be replacing revprops. Make sure that all other 4031251881Speter threads and processes will know about this. */ 4032251881Speter if (bump_generation) 4033251881Speter SVN_ERR(begin_revprop_change(fs, pool)); 4034251881Speter 4035251881Speter SVN_ERR(move_into_place(tmp_path, final_path, perms_reference, pool)); 4036251881Speter 4037251881Speter /* Indicate that the update (if relevant) has been completed. */ 4038251881Speter if (bump_generation) 4039251881Speter SVN_ERR(end_revprop_change(fs, pool)); 4040251881Speter 4041251881Speter /* Clean up temporary files, if necessary. */ 4042251881Speter if (files_to_delete) 4043251881Speter { 4044251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 4045251881Speter int i; 4046251881Speter 4047251881Speter for (i = 0; i < files_to_delete->nelts; ++i) 4048251881Speter { 4049251881Speter const char *path = APR_ARRAY_IDX(files_to_delete, i, const char*); 4050251881Speter SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool)); 4051251881Speter svn_pool_clear(iterpool); 4052251881Speter } 4053251881Speter 4054251881Speter svn_pool_destroy(iterpool); 4055251881Speter } 4056251881Speter return SVN_NO_ERROR; 4057251881Speter} 4058251881Speter 4059251881Speter/* Write a pack file header to STREAM that starts at revision START_REVISION 4060251881Speter * and contains the indexes [START,END) of SIZES. 4061251881Speter */ 4062251881Speterstatic svn_error_t * 4063251881Speterserialize_revprops_header(svn_stream_t *stream, 4064251881Speter svn_revnum_t start_revision, 4065251881Speter apr_array_header_t *sizes, 4066251881Speter int start, 4067251881Speter int end, 4068251881Speter apr_pool_t *pool) 4069251881Speter{ 4070251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 4071251881Speter int i; 4072251881Speter 4073251881Speter SVN_ERR_ASSERT(start < end); 4074251881Speter 4075251881Speter /* start revision and entry count */ 4076251881Speter SVN_ERR(svn_stream_printf(stream, pool, "%ld\n", start_revision)); 4077251881Speter SVN_ERR(svn_stream_printf(stream, pool, "%d\n", end - start)); 4078251881Speter 4079251881Speter /* the sizes array */ 4080251881Speter for (i = start; i < end; ++i) 4081251881Speter { 4082251881Speter apr_off_t size = APR_ARRAY_IDX(sizes, i, apr_off_t); 4083251881Speter SVN_ERR(svn_stream_printf(stream, iterpool, "%" APR_OFF_T_FMT "\n", 4084251881Speter size)); 4085251881Speter } 4086251881Speter 4087251881Speter /* the double newline char indicates the end of the header */ 4088251881Speter SVN_ERR(svn_stream_printf(stream, iterpool, "\n")); 4089251881Speter 4090251881Speter svn_pool_clear(iterpool); 4091251881Speter return SVN_NO_ERROR; 4092251881Speter} 4093251881Speter 4094289166Speter/* Writes the a pack file to FILE. It copies the serialized data 4095251881Speter * from REVPROPS for the indexes [START,END) except for index CHANGED_INDEX. 4096251881Speter * 4097251881Speter * The data for the latter is taken from NEW_SERIALIZED. Note, that 4098251881Speter * CHANGED_INDEX may be outside the [START,END) range, i.e. no new data is 4099251881Speter * taken in that case but only a subset of the old data will be copied. 4100251881Speter * 4101251881Speter * NEW_TOTAL_SIZE is a hint for pre-allocating buffers of appropriate size. 4102251881Speter * POOL is used for temporary allocations. 4103251881Speter */ 4104251881Speterstatic svn_error_t * 4105251881Speterrepack_revprops(svn_fs_t *fs, 4106251881Speter packed_revprops_t *revprops, 4107251881Speter int start, 4108251881Speter int end, 4109251881Speter int changed_index, 4110251881Speter svn_stringbuf_t *new_serialized, 4111251881Speter apr_off_t new_total_size, 4112289166Speter apr_file_t *file, 4113251881Speter apr_pool_t *pool) 4114251881Speter{ 4115251881Speter fs_fs_data_t *ffd = fs->fsap_data; 4116251881Speter svn_stream_t *stream; 4117251881Speter int i; 4118251881Speter 4119251881Speter /* create data empty buffers and the stream object */ 4120251881Speter svn_stringbuf_t *uncompressed 4121251881Speter = svn_stringbuf_create_ensure((apr_size_t)new_total_size, pool); 4122251881Speter svn_stringbuf_t *compressed 4123251881Speter = svn_stringbuf_create_empty(pool); 4124251881Speter stream = svn_stream_from_stringbuf(uncompressed, pool); 4125251881Speter 4126251881Speter /* write the header*/ 4127251881Speter SVN_ERR(serialize_revprops_header(stream, revprops->start_revision + start, 4128251881Speter revprops->sizes, start, end, pool)); 4129251881Speter 4130251881Speter /* append the serialized revprops */ 4131251881Speter for (i = start; i < end; ++i) 4132251881Speter if (i == changed_index) 4133251881Speter { 4134251881Speter SVN_ERR(svn_stream_write(stream, 4135251881Speter new_serialized->data, 4136251881Speter &new_serialized->len)); 4137251881Speter } 4138251881Speter else 4139251881Speter { 4140251881Speter apr_size_t size 4141251881Speter = (apr_size_t)APR_ARRAY_IDX(revprops->sizes, i, apr_off_t); 4142251881Speter apr_size_t offset 4143251881Speter = (apr_size_t)APR_ARRAY_IDX(revprops->offsets, i, apr_off_t); 4144251881Speter 4145251881Speter SVN_ERR(svn_stream_write(stream, 4146251881Speter revprops->packed_revprops->data + offset, 4147251881Speter &size)); 4148251881Speter } 4149251881Speter 4150251881Speter /* flush the stream buffer (if any) to our underlying data buffer */ 4151251881Speter SVN_ERR(svn_stream_close(stream)); 4152251881Speter 4153251881Speter /* compress / store the data */ 4154251881Speter SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed), 4155251881Speter compressed, 4156251881Speter ffd->compress_packed_revprops 4157251881Speter ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT 4158251881Speter : SVN_DELTA_COMPRESSION_LEVEL_NONE)); 4159251881Speter 4160289166Speter /* finally, write the content to the target file, flush and close it */ 4161289166Speter SVN_ERR(svn_io_file_write_full(file, compressed->data, compressed->len, 4162289166Speter NULL, pool)); 4163289166Speter SVN_ERR(svn_io_file_flush_to_disk(file, pool)); 4164289166Speter SVN_ERR(svn_io_file_close(file, pool)); 4165251881Speter 4166251881Speter return SVN_NO_ERROR; 4167251881Speter} 4168251881Speter 4169262253Speter/* Allocate a new pack file name for revisions 4170262253Speter * [REVPROPS->START_REVISION + START, REVPROPS->START_REVISION + END - 1] 4171251881Speter * of REVPROPS->MANIFEST. Add the name of old file to FILES_TO_DELETE, 4172289166Speter * auto-create that array if necessary. Return an open file *FILE that is 4173289166Speter * allocated in POOL. 4174251881Speter */ 4175251881Speterstatic svn_error_t * 4176289166Speterrepack_file_open(apr_file_t **file, 4177289166Speter svn_fs_t *fs, 4178289166Speter packed_revprops_t *revprops, 4179289166Speter int start, 4180289166Speter int end, 4181289166Speter apr_array_header_t **files_to_delete, 4182289166Speter apr_pool_t *pool) 4183251881Speter{ 4184251881Speter apr_int64_t tag; 4185251881Speter const char *tag_string; 4186251881Speter svn_string_t *new_filename; 4187251881Speter int i; 4188262253Speter int manifest_offset 4189262253Speter = (int)(revprops->start_revision - revprops->manifest_start); 4190251881Speter 4191251881Speter /* get the old (= current) file name and enlist it for later deletion */ 4192262253Speter const char *old_filename = APR_ARRAY_IDX(revprops->manifest, 4193262253Speter start + manifest_offset, 4194262253Speter const char*); 4195251881Speter 4196251881Speter if (*files_to_delete == NULL) 4197251881Speter *files_to_delete = apr_array_make(pool, 3, sizeof(const char*)); 4198251881Speter 4199251881Speter APR_ARRAY_PUSH(*files_to_delete, const char*) 4200251881Speter = svn_dirent_join(revprops->folder, old_filename, pool); 4201251881Speter 4202251881Speter /* increase the tag part, i.e. the counter after the dot */ 4203251881Speter tag_string = strchr(old_filename, '.'); 4204251881Speter if (tag_string == NULL) 4205251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 4206251881Speter _("Packed file '%s' misses a tag"), 4207251881Speter old_filename); 4208251881Speter 4209251881Speter SVN_ERR(svn_cstring_atoi64(&tag, tag_string + 1)); 4210251881Speter new_filename = svn_string_createf(pool, "%ld.%" APR_INT64_T_FMT, 4211251881Speter revprops->start_revision + start, 4212251881Speter ++tag); 4213251881Speter 4214251881Speter /* update the manifest to point to the new file */ 4215251881Speter for (i = start; i < end; ++i) 4216262253Speter APR_ARRAY_IDX(revprops->manifest, i + manifest_offset, const char*) 4217262253Speter = new_filename->data; 4218251881Speter 4219289166Speter /* open the file */ 4220289166Speter SVN_ERR(svn_io_file_open(file, svn_dirent_join(revprops->folder, 4221289166Speter new_filename->data, 4222289166Speter pool), 4223251881Speter APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool)); 4224251881Speter 4225251881Speter return SVN_NO_ERROR; 4226251881Speter} 4227251881Speter 4228251881Speter/* For revision REV in filesystem FS, set the revision properties to 4229251881Speter * PROPLIST. Return a new file in *TMP_PATH that the caller shall move 4230251881Speter * to *FINAL_PATH to make the change visible. Files to be deleted will 4231251881Speter * be listed in *FILES_TO_DELETE which may remain unchanged / unallocated. 4232251881Speter * Use POOL for allocations. 4233251881Speter */ 4234251881Speterstatic svn_error_t * 4235251881Speterwrite_packed_revprop(const char **final_path, 4236251881Speter const char **tmp_path, 4237251881Speter apr_array_header_t **files_to_delete, 4238251881Speter svn_fs_t *fs, 4239251881Speter svn_revnum_t rev, 4240251881Speter apr_hash_t *proplist, 4241251881Speter apr_pool_t *pool) 4242251881Speter{ 4243251881Speter fs_fs_data_t *ffd = fs->fsap_data; 4244251881Speter packed_revprops_t *revprops; 4245251881Speter apr_int64_t generation = 0; 4246251881Speter svn_stream_t *stream; 4247289166Speter apr_file_t *file; 4248251881Speter svn_stringbuf_t *serialized; 4249251881Speter apr_off_t new_total_size; 4250251881Speter int changed_index; 4251251881Speter 4252251881Speter /* read the current revprop generation. This value will not change 4253251881Speter * while we hold the global write lock to this FS. */ 4254251881Speter if (has_revprop_cache(fs, pool)) 4255251881Speter SVN_ERR(read_revprop_generation(&generation, fs, pool)); 4256251881Speter 4257251881Speter /* read contents of the current pack file */ 4258251881Speter SVN_ERR(read_pack_revprop(&revprops, fs, rev, generation, pool)); 4259251881Speter 4260251881Speter /* serialize the new revprops */ 4261251881Speter serialized = svn_stringbuf_create_empty(pool); 4262251881Speter stream = svn_stream_from_stringbuf(serialized, pool); 4263251881Speter SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool)); 4264251881Speter SVN_ERR(svn_stream_close(stream)); 4265251881Speter 4266251881Speter /* calculate the size of the new data */ 4267251881Speter changed_index = (int)(rev - revprops->start_revision); 4268251881Speter new_total_size = revprops->total_size - revprops->serialized_size 4269251881Speter + serialized->len 4270251881Speter + (revprops->offsets->nelts + 2) * SVN_INT64_BUFFER_SIZE; 4271251881Speter 4272251881Speter APR_ARRAY_IDX(revprops->sizes, changed_index, apr_off_t) = serialized->len; 4273251881Speter 4274251881Speter /* can we put the new data into the same pack as the before? */ 4275251881Speter if ( new_total_size < ffd->revprop_pack_size 4276251881Speter || revprops->sizes->nelts == 1) 4277251881Speter { 4278251881Speter /* simply replace the old pack file with new content as we do it 4279251881Speter * in the non-packed case */ 4280251881Speter 4281251881Speter *final_path = svn_dirent_join(revprops->folder, revprops->filename, 4282251881Speter pool); 4283289166Speter SVN_ERR(svn_io_open_unique_file3(&file, tmp_path, revprops->folder, 4284289166Speter svn_io_file_del_none, pool, pool)); 4285251881Speter SVN_ERR(repack_revprops(fs, revprops, 0, revprops->sizes->nelts, 4286251881Speter changed_index, serialized, new_total_size, 4287289166Speter file, pool)); 4288251881Speter } 4289251881Speter else 4290251881Speter { 4291251881Speter /* split the pack file into two of roughly equal size */ 4292251881Speter int right_count, left_count, i; 4293251881Speter 4294251881Speter int left = 0; 4295251881Speter int right = revprops->sizes->nelts - 1; 4296251881Speter apr_off_t left_size = 2 * SVN_INT64_BUFFER_SIZE; 4297251881Speter apr_off_t right_size = 2 * SVN_INT64_BUFFER_SIZE; 4298251881Speter 4299251881Speter /* let left and right side grow such that their size difference 4300251881Speter * is minimal after each step. */ 4301251881Speter while (left <= right) 4302251881Speter if ( left_size + APR_ARRAY_IDX(revprops->sizes, left, apr_off_t) 4303251881Speter < right_size + APR_ARRAY_IDX(revprops->sizes, right, apr_off_t)) 4304251881Speter { 4305251881Speter left_size += APR_ARRAY_IDX(revprops->sizes, left, apr_off_t) 4306251881Speter + SVN_INT64_BUFFER_SIZE; 4307251881Speter ++left; 4308251881Speter } 4309251881Speter else 4310251881Speter { 4311251881Speter right_size += APR_ARRAY_IDX(revprops->sizes, right, apr_off_t) 4312251881Speter + SVN_INT64_BUFFER_SIZE; 4313251881Speter --right; 4314251881Speter } 4315251881Speter 4316251881Speter /* since the items need much less than SVN_INT64_BUFFER_SIZE 4317251881Speter * bytes to represent their length, the split may not be optimal */ 4318251881Speter left_count = left; 4319251881Speter right_count = revprops->sizes->nelts - left; 4320251881Speter 4321251881Speter /* if new_size is large, one side may exceed the pack size limit. 4322251881Speter * In that case, split before and after the modified revprop.*/ 4323251881Speter if ( left_size > ffd->revprop_pack_size 4324251881Speter || right_size > ffd->revprop_pack_size) 4325251881Speter { 4326251881Speter left_count = changed_index; 4327251881Speter right_count = revprops->sizes->nelts - left_count - 1; 4328251881Speter } 4329251881Speter 4330251881Speter /* write the new, split files */ 4331251881Speter if (left_count) 4332251881Speter { 4333289166Speter SVN_ERR(repack_file_open(&file, fs, revprops, 0, 4334289166Speter left_count, files_to_delete, pool)); 4335251881Speter SVN_ERR(repack_revprops(fs, revprops, 0, left_count, 4336251881Speter changed_index, serialized, new_total_size, 4337289166Speter file, pool)); 4338251881Speter } 4339251881Speter 4340251881Speter if (left_count + right_count < revprops->sizes->nelts) 4341251881Speter { 4342289166Speter SVN_ERR(repack_file_open(&file, fs, revprops, changed_index, 4343289166Speter changed_index + 1, files_to_delete, 4344289166Speter pool)); 4345251881Speter SVN_ERR(repack_revprops(fs, revprops, changed_index, 4346251881Speter changed_index + 1, 4347251881Speter changed_index, serialized, new_total_size, 4348289166Speter file, pool)); 4349251881Speter } 4350251881Speter 4351251881Speter if (right_count) 4352251881Speter { 4353289166Speter SVN_ERR(repack_file_open(&file, fs, revprops, 4354289166Speter revprops->sizes->nelts - right_count, 4355289166Speter revprops->sizes->nelts, 4356289166Speter files_to_delete, pool)); 4357251881Speter SVN_ERR(repack_revprops(fs, revprops, 4358251881Speter revprops->sizes->nelts - right_count, 4359251881Speter revprops->sizes->nelts, changed_index, 4360289166Speter serialized, new_total_size, file, 4361251881Speter pool)); 4362251881Speter } 4363251881Speter 4364251881Speter /* write the new manifest */ 4365251881Speter *final_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool); 4366289166Speter SVN_ERR(svn_io_open_unique_file3(&file, tmp_path, revprops->folder, 4367289166Speter svn_io_file_del_none, pool, pool)); 4368251881Speter 4369251881Speter for (i = 0; i < revprops->manifest->nelts; ++i) 4370251881Speter { 4371251881Speter const char *filename = APR_ARRAY_IDX(revprops->manifest, i, 4372251881Speter const char*); 4373289166Speter SVN_ERR(svn_io_file_write_full(file, filename, strlen(filename), 4374289166Speter NULL, pool)); 4375289166Speter SVN_ERR(svn_io_file_putc('\n', file, pool)); 4376251881Speter } 4377251881Speter 4378289166Speter SVN_ERR(svn_io_file_flush_to_disk(file, pool)); 4379289166Speter SVN_ERR(svn_io_file_close(file, pool)); 4380251881Speter } 4381251881Speter 4382251881Speter return SVN_NO_ERROR; 4383251881Speter} 4384251881Speter 4385251881Speter/* Set the revision property list of revision REV in filesystem FS to 4386251881Speter PROPLIST. Use POOL for temporary allocations. */ 4387251881Speterstatic svn_error_t * 4388251881Speterset_revision_proplist(svn_fs_t *fs, 4389251881Speter svn_revnum_t rev, 4390251881Speter apr_hash_t *proplist, 4391251881Speter apr_pool_t *pool) 4392251881Speter{ 4393251881Speter svn_boolean_t is_packed; 4394251881Speter svn_boolean_t bump_generation = FALSE; 4395251881Speter const char *final_path; 4396251881Speter const char *tmp_path; 4397251881Speter const char *perms_reference; 4398251881Speter apr_array_header_t *files_to_delete = NULL; 4399251881Speter 4400251881Speter SVN_ERR(ensure_revision_exists(fs, rev, pool)); 4401251881Speter 4402251881Speter /* this info will not change while we hold the global FS write lock */ 4403251881Speter is_packed = is_packed_revprop(fs, rev); 4404251881Speter 4405251881Speter /* Test whether revprops already exist for this revision. 4406251881Speter * Only then will we need to bump the revprop generation. */ 4407251881Speter if (has_revprop_cache(fs, pool)) 4408251881Speter { 4409251881Speter if (is_packed) 4410251881Speter { 4411251881Speter bump_generation = TRUE; 4412251881Speter } 4413251881Speter else 4414251881Speter { 4415251881Speter svn_node_kind_t kind; 4416251881Speter SVN_ERR(svn_io_check_path(path_revprops(fs, rev, pool), &kind, 4417251881Speter pool)); 4418251881Speter bump_generation = kind != svn_node_none; 4419251881Speter } 4420251881Speter } 4421251881Speter 4422251881Speter /* Serialize the new revprop data */ 4423251881Speter if (is_packed) 4424251881Speter SVN_ERR(write_packed_revprop(&final_path, &tmp_path, &files_to_delete, 4425251881Speter fs, rev, proplist, pool)); 4426251881Speter else 4427251881Speter SVN_ERR(write_non_packed_revprop(&final_path, &tmp_path, 4428251881Speter fs, rev, proplist, pool)); 4429251881Speter 4430251881Speter /* We use the rev file of this revision as the perms reference, 4431251881Speter * because when setting revprops for the first time, the revprop 4432251881Speter * file won't exist and therefore can't serve as its own reference. 4433251881Speter * (Whereas the rev file should already exist at this point.) 4434251881Speter */ 4435251881Speter SVN_ERR(svn_fs_fs__path_rev_absolute(&perms_reference, fs, rev, pool)); 4436251881Speter 4437251881Speter /* Now, switch to the new revprop data. */ 4438251881Speter SVN_ERR(switch_to_new_revprop(fs, final_path, tmp_path, perms_reference, 4439251881Speter files_to_delete, bump_generation, pool)); 4440251881Speter 4441251881Speter return SVN_NO_ERROR; 4442251881Speter} 4443251881Speter 4444251881Spetersvn_error_t * 4445251881Spetersvn_fs_fs__revision_proplist(apr_hash_t **proplist_p, 4446251881Speter svn_fs_t *fs, 4447251881Speter svn_revnum_t rev, 4448251881Speter apr_pool_t *pool) 4449251881Speter{ 4450251881Speter SVN_ERR(get_revision_proplist(proplist_p, fs, rev, pool)); 4451251881Speter 4452251881Speter return SVN_NO_ERROR; 4453251881Speter} 4454251881Speter 4455251881Speter/* Represents where in the current svndiff data block each 4456251881Speter representation is. */ 4457251881Speterstruct rep_state 4458251881Speter{ 4459251881Speter apr_file_t *file; 4460251881Speter /* The txdelta window cache to use or NULL. */ 4461251881Speter svn_cache__t *window_cache; 4462251881Speter /* Caches un-deltified windows. May be NULL. */ 4463251881Speter svn_cache__t *combined_cache; 4464251881Speter apr_off_t start; /* The starting offset for the raw 4465251881Speter svndiff/plaintext data minus header. */ 4466251881Speter apr_off_t off; /* The current offset into the file. */ 4467251881Speter apr_off_t end; /* The end offset of the raw data. */ 4468251881Speter int ver; /* If a delta, what svndiff version? */ 4469251881Speter int chunk_index; 4470251881Speter}; 4471251881Speter 4472251881Speter/* See create_rep_state, which wraps this and adds another error. */ 4473251881Speterstatic svn_error_t * 4474251881Spetercreate_rep_state_body(struct rep_state **rep_state, 4475251881Speter struct rep_args **rep_args, 4476251881Speter apr_file_t **file_hint, 4477251881Speter svn_revnum_t *rev_hint, 4478251881Speter representation_t *rep, 4479251881Speter svn_fs_t *fs, 4480251881Speter apr_pool_t *pool) 4481251881Speter{ 4482251881Speter fs_fs_data_t *ffd = fs->fsap_data; 4483251881Speter struct rep_state *rs = apr_pcalloc(pool, sizeof(*rs)); 4484251881Speter struct rep_args *ra; 4485251881Speter unsigned char buf[4]; 4486251881Speter 4487251881Speter /* If the hint is 4488251881Speter * - given, 4489251881Speter * - refers to a valid revision, 4490251881Speter * - refers to a packed revision, 4491251881Speter * - as does the rep we want to read, and 4492251881Speter * - refers to the same pack file as the rep 4493251881Speter * ... 4494251881Speter */ 4495251881Speter if ( file_hint && rev_hint && *file_hint 4496251881Speter && SVN_IS_VALID_REVNUM(*rev_hint) 4497251881Speter && *rev_hint < ffd->min_unpacked_rev 4498251881Speter && rep->revision < ffd->min_unpacked_rev 4499251881Speter && ( (*rev_hint / ffd->max_files_per_dir) 4500251881Speter == (rep->revision / ffd->max_files_per_dir))) 4501251881Speter { 4502251881Speter /* ... we can re-use the same, already open file object 4503251881Speter */ 4504251881Speter apr_off_t offset; 4505251881Speter SVN_ERR(get_packed_offset(&offset, fs, rep->revision, pool)); 4506251881Speter 4507251881Speter offset += rep->offset; 4508251881Speter SVN_ERR(svn_io_file_seek(*file_hint, APR_SET, &offset, pool)); 4509251881Speter 4510251881Speter rs->file = *file_hint; 4511251881Speter } 4512251881Speter else 4513251881Speter { 4514251881Speter /* otherwise, create a new file object 4515251881Speter */ 4516251881Speter SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool)); 4517251881Speter } 4518251881Speter 4519251881Speter /* remember the current file, if suggested by the caller */ 4520251881Speter if (file_hint) 4521251881Speter *file_hint = rs->file; 4522251881Speter if (rev_hint) 4523251881Speter *rev_hint = rep->revision; 4524251881Speter 4525251881Speter /* continue constructing RS and RA */ 4526251881Speter rs->window_cache = ffd->txdelta_window_cache; 4527251881Speter rs->combined_cache = ffd->combined_window_cache; 4528251881Speter 4529251881Speter SVN_ERR(read_rep_line(&ra, rs->file, pool)); 4530251881Speter SVN_ERR(get_file_offset(&rs->start, rs->file, pool)); 4531251881Speter rs->off = rs->start; 4532251881Speter rs->end = rs->start + rep->size; 4533251881Speter *rep_state = rs; 4534251881Speter *rep_args = ra; 4535251881Speter 4536251881Speter if (!ra->is_delta) 4537251881Speter /* This is a plaintext, so just return the current rep_state. */ 4538251881Speter return SVN_NO_ERROR; 4539251881Speter 4540251881Speter /* We are dealing with a delta, find out what version. */ 4541251881Speter SVN_ERR(svn_io_file_read_full2(rs->file, buf, sizeof(buf), 4542251881Speter NULL, NULL, pool)); 4543251881Speter /* ### Layering violation */ 4544251881Speter if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N'))) 4545251881Speter return svn_error_create 4546251881Speter (SVN_ERR_FS_CORRUPT, NULL, 4547251881Speter _("Malformed svndiff data in representation")); 4548251881Speter rs->ver = buf[3]; 4549251881Speter rs->chunk_index = 0; 4550251881Speter rs->off += 4; 4551251881Speter 4552251881Speter return SVN_NO_ERROR; 4553251881Speter} 4554251881Speter 4555251881Speter/* Read the rep args for REP in filesystem FS and create a rep_state 4556251881Speter for reading the representation. Return the rep_state in *REP_STATE 4557251881Speter and the rep args in *REP_ARGS, both allocated in POOL. 4558251881Speter 4559251881Speter When reading multiple reps, i.e. a skip delta chain, you may provide 4560251881Speter non-NULL FILE_HINT and REV_HINT. (If FILE_HINT is not NULL, in the first 4561251881Speter call it should be a pointer to NULL.) The function will use these variables 4562251881Speter to store the previous call results and tries to re-use them. This may 4563251881Speter result in significant savings in I/O for packed files. 4564251881Speter */ 4565251881Speterstatic svn_error_t * 4566251881Spetercreate_rep_state(struct rep_state **rep_state, 4567251881Speter struct rep_args **rep_args, 4568251881Speter apr_file_t **file_hint, 4569251881Speter svn_revnum_t *rev_hint, 4570251881Speter representation_t *rep, 4571251881Speter svn_fs_t *fs, 4572251881Speter apr_pool_t *pool) 4573251881Speter{ 4574251881Speter svn_error_t *err = create_rep_state_body(rep_state, rep_args, 4575251881Speter file_hint, rev_hint, 4576251881Speter rep, fs, pool); 4577251881Speter if (err && err->apr_err == SVN_ERR_FS_CORRUPT) 4578251881Speter { 4579251881Speter fs_fs_data_t *ffd = fs->fsap_data; 4580251881Speter 4581251881Speter /* ### This always returns "-1" for transaction reps, because 4582251881Speter ### this particular bit of code doesn't know if the rep is 4583251881Speter ### stored in the protorev or in the mutable area (for props 4584251881Speter ### or dir contents). It is pretty rare for FSFS to *read* 4585251881Speter ### from the protorev file, though, so this is probably OK. 4586251881Speter ### And anyone going to debug corruption errors is probably 4587251881Speter ### going to jump straight to this comment anyway! */ 4588251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, err, 4589251881Speter "Corrupt representation '%s'", 4590251881Speter rep 4591251881Speter ? representation_string(rep, ffd->format, TRUE, 4592251881Speter TRUE, pool) 4593251881Speter : "(null)"); 4594251881Speter } 4595251881Speter /* ### Call representation_string() ? */ 4596251881Speter return svn_error_trace(err); 4597251881Speter} 4598251881Speter 4599251881Speterstruct rep_read_baton 4600251881Speter{ 4601251881Speter /* The FS from which we're reading. */ 4602251881Speter svn_fs_t *fs; 4603251881Speter 4604251881Speter /* If not NULL, this is the base for the first delta window in rs_list */ 4605251881Speter svn_stringbuf_t *base_window; 4606251881Speter 4607251881Speter /* The state of all prior delta representations. */ 4608251881Speter apr_array_header_t *rs_list; 4609251881Speter 4610251881Speter /* The plaintext state, if there is a plaintext. */ 4611251881Speter struct rep_state *src_state; 4612251881Speter 4613251881Speter /* The index of the current delta chunk, if we are reading a delta. */ 4614251881Speter int chunk_index; 4615251881Speter 4616251881Speter /* The buffer where we store undeltified data. */ 4617251881Speter char *buf; 4618251881Speter apr_size_t buf_pos; 4619251881Speter apr_size_t buf_len; 4620251881Speter 4621251881Speter /* A checksum context for summing the data read in order to verify it. 4622251881Speter Note: we don't need to use the sha1 checksum because we're only doing 4623251881Speter data verification, for which md5 is perfectly safe. */ 4624251881Speter svn_checksum_ctx_t *md5_checksum_ctx; 4625251881Speter 4626251881Speter svn_boolean_t checksum_finalized; 4627251881Speter 4628251881Speter /* The stored checksum of the representation we are reading, its 4629251881Speter length, and the amount we've read so far. Some of this 4630251881Speter information is redundant with rs_list and src_state, but it's 4631251881Speter convenient for the checksumming code to have it here. */ 4632251881Speter svn_checksum_t *md5_checksum; 4633251881Speter 4634251881Speter svn_filesize_t len; 4635251881Speter svn_filesize_t off; 4636251881Speter 4637251881Speter /* The key for the fulltext cache for this rep, if there is a 4638251881Speter fulltext cache. */ 4639251881Speter pair_cache_key_t fulltext_cache_key; 4640251881Speter /* The text we've been reading, if we're going to cache it. */ 4641251881Speter svn_stringbuf_t *current_fulltext; 4642251881Speter 4643251881Speter /* Used for temporary allocations during the read. */ 4644251881Speter apr_pool_t *pool; 4645251881Speter 4646251881Speter /* Pool used to store file handles and other data that is persistant 4647251881Speter for the entire stream read. */ 4648251881Speter apr_pool_t *filehandle_pool; 4649251881Speter}; 4650251881Speter 4651251881Speter/* Combine the name of the rev file in RS with the given OFFSET to form 4652251881Speter * a cache lookup key. Allocations will be made from POOL. May return 4653251881Speter * NULL if the key cannot be constructed. */ 4654251881Speterstatic const char* 4655251881Speterget_window_key(struct rep_state *rs, apr_off_t offset, apr_pool_t *pool) 4656251881Speter{ 4657251881Speter const char *name; 4658251881Speter const char *last_part; 4659251881Speter const char *name_last; 4660251881Speter 4661251881Speter /* the rev file name containing the txdelta window. 4662251881Speter * If this fails we are in serious trouble anyways. 4663251881Speter * And if nobody else detects the problems, the file content checksum 4664251881Speter * comparison _will_ find them. 4665251881Speter */ 4666251881Speter if (apr_file_name_get(&name, rs->file)) 4667251881Speter return NULL; 4668251881Speter 4669251881Speter /* Handle packed files as well by scanning backwards until we find the 4670251881Speter * revision or pack number. */ 4671251881Speter name_last = name + strlen(name) - 1; 4672251881Speter while (! svn_ctype_isdigit(*name_last)) 4673251881Speter --name_last; 4674251881Speter 4675251881Speter last_part = name_last; 4676251881Speter while (svn_ctype_isdigit(*last_part)) 4677251881Speter --last_part; 4678251881Speter 4679251881Speter /* We must differentiate between packed files (as of today, the number 4680251881Speter * is being followed by a dot) and non-packed files (followed by \0). 4681251881Speter * Otherwise, there might be overlaps in the numbering range if the 4682251881Speter * repo gets packed after caching the txdeltas of non-packed revs. 4683251881Speter * => add the first non-digit char to the packed number. */ 4684251881Speter if (name_last[1] != '\0') 4685251881Speter ++name_last; 4686251881Speter 4687251881Speter /* copy one char MORE than the actual number to mark packed files, 4688251881Speter * i.e. packed revision file content uses different key space then 4689251881Speter * non-packed ones: keys for packed rev file content ends with a dot 4690251881Speter * for non-packed rev files they end with a digit. */ 4691251881Speter name = apr_pstrndup(pool, last_part + 1, name_last - last_part); 4692251881Speter return svn_fs_fs__combine_number_and_string(offset, name, pool); 4693251881Speter} 4694251881Speter 4695251881Speter/* Read the WINDOW_P for the rep state RS from the current FSFS session's 4696251881Speter * cache. This will be a no-op and IS_CACHED will be set to FALSE if no 4697251881Speter * cache has been given. If a cache is available IS_CACHED will inform 4698251881Speter * the caller about the success of the lookup. Allocations (of the window 4699251881Speter * in particualar) will be made from POOL. 4700251881Speter * 4701251881Speter * If the information could be found, put RS and the position within the 4702251881Speter * rev file into the same state as if the data had just been read from it. 4703251881Speter */ 4704251881Speterstatic svn_error_t * 4705251881Speterget_cached_window(svn_txdelta_window_t **window_p, 4706251881Speter struct rep_state *rs, 4707251881Speter svn_boolean_t *is_cached, 4708251881Speter apr_pool_t *pool) 4709251881Speter{ 4710251881Speter if (! rs->window_cache) 4711251881Speter { 4712251881Speter /* txdelta window has not been enabled */ 4713251881Speter *is_cached = FALSE; 4714251881Speter } 4715251881Speter else 4716251881Speter { 4717251881Speter /* ask the cache for the desired txdelta window */ 4718251881Speter svn_fs_fs__txdelta_cached_window_t *cached_window; 4719251881Speter SVN_ERR(svn_cache__get((void **) &cached_window, 4720251881Speter is_cached, 4721251881Speter rs->window_cache, 4722251881Speter get_window_key(rs, rs->off, pool), 4723251881Speter pool)); 4724251881Speter 4725251881Speter if (*is_cached) 4726251881Speter { 4727251881Speter /* found it. Pass it back to the caller. */ 4728251881Speter *window_p = cached_window->window; 4729251881Speter 4730251881Speter /* manipulate the RS as if we just read the data */ 4731251881Speter rs->chunk_index++; 4732251881Speter rs->off = cached_window->end_offset; 4733251881Speter 4734251881Speter /* manipulate the rev file as if we just read from it */ 4735251881Speter SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool)); 4736251881Speter } 4737251881Speter } 4738251881Speter 4739251881Speter return SVN_NO_ERROR; 4740251881Speter} 4741251881Speter 4742251881Speter/* Store the WINDOW read at OFFSET for the rep state RS in the current 4743251881Speter * FSFS session's cache. This will be a no-op if no cache has been given. 4744251881Speter * Temporary allocations will be made from SCRATCH_POOL. */ 4745251881Speterstatic svn_error_t * 4746251881Speterset_cached_window(svn_txdelta_window_t *window, 4747251881Speter struct rep_state *rs, 4748251881Speter apr_off_t offset, 4749251881Speter apr_pool_t *scratch_pool) 4750251881Speter{ 4751251881Speter if (rs->window_cache) 4752251881Speter { 4753251881Speter /* store the window and the first offset _past_ it */ 4754251881Speter svn_fs_fs__txdelta_cached_window_t cached_window; 4755251881Speter 4756251881Speter cached_window.window = window; 4757251881Speter cached_window.end_offset = rs->off; 4758251881Speter 4759251881Speter /* but key it with the start offset because that is the known state 4760251881Speter * when we will look it up */ 4761251881Speter return svn_cache__set(rs->window_cache, 4762251881Speter get_window_key(rs, offset, scratch_pool), 4763251881Speter &cached_window, 4764251881Speter scratch_pool); 4765251881Speter } 4766251881Speter 4767251881Speter return SVN_NO_ERROR; 4768251881Speter} 4769251881Speter 4770251881Speter/* Read the WINDOW_P for the rep state RS from the current FSFS session's 4771251881Speter * cache. This will be a no-op and IS_CACHED will be set to FALSE if no 4772251881Speter * cache has been given. If a cache is available IS_CACHED will inform 4773251881Speter * the caller about the success of the lookup. Allocations (of the window 4774251881Speter * in particualar) will be made from POOL. 4775251881Speter */ 4776251881Speterstatic svn_error_t * 4777251881Speterget_cached_combined_window(svn_stringbuf_t **window_p, 4778251881Speter struct rep_state *rs, 4779251881Speter svn_boolean_t *is_cached, 4780251881Speter apr_pool_t *pool) 4781251881Speter{ 4782251881Speter if (! rs->combined_cache) 4783251881Speter { 4784251881Speter /* txdelta window has not been enabled */ 4785251881Speter *is_cached = FALSE; 4786251881Speter } 4787251881Speter else 4788251881Speter { 4789251881Speter /* ask the cache for the desired txdelta window */ 4790251881Speter return svn_cache__get((void **)window_p, 4791251881Speter is_cached, 4792251881Speter rs->combined_cache, 4793251881Speter get_window_key(rs, rs->start, pool), 4794251881Speter pool); 4795251881Speter } 4796251881Speter 4797251881Speter return SVN_NO_ERROR; 4798251881Speter} 4799251881Speter 4800251881Speter/* Store the WINDOW read at OFFSET for the rep state RS in the current 4801251881Speter * FSFS session's cache. This will be a no-op if no cache has been given. 4802251881Speter * Temporary allocations will be made from SCRATCH_POOL. */ 4803251881Speterstatic svn_error_t * 4804251881Speterset_cached_combined_window(svn_stringbuf_t *window, 4805251881Speter struct rep_state *rs, 4806251881Speter apr_off_t offset, 4807251881Speter apr_pool_t *scratch_pool) 4808251881Speter{ 4809251881Speter if (rs->combined_cache) 4810251881Speter { 4811251881Speter /* but key it with the start offset because that is the known state 4812251881Speter * when we will look it up */ 4813251881Speter return svn_cache__set(rs->combined_cache, 4814251881Speter get_window_key(rs, offset, scratch_pool), 4815251881Speter window, 4816251881Speter scratch_pool); 4817251881Speter } 4818251881Speter 4819251881Speter return SVN_NO_ERROR; 4820251881Speter} 4821251881Speter 4822251881Speter/* Build an array of rep_state structures in *LIST giving the delta 4823251881Speter reps from first_rep to a plain-text or self-compressed rep. Set 4824251881Speter *SRC_STATE to the plain-text rep we find at the end of the chain, 4825251881Speter or to NULL if the final delta representation is self-compressed. 4826251881Speter The representation to start from is designated by filesystem FS, id 4827251881Speter ID, and representation REP. 4828251881Speter Also, set *WINDOW_P to the base window content for *LIST, if it 4829251881Speter could be found in cache. Otherwise, *LIST will contain the base 4830251881Speter representation for the whole delta chain. 4831251881Speter Finally, return the expanded size of the representation in 4832251881Speter *EXPANDED_SIZE. It will take care of cases where only the on-disk 4833251881Speter size is known. */ 4834251881Speterstatic svn_error_t * 4835251881Speterbuild_rep_list(apr_array_header_t **list, 4836251881Speter svn_stringbuf_t **window_p, 4837251881Speter struct rep_state **src_state, 4838251881Speter svn_filesize_t *expanded_size, 4839251881Speter svn_fs_t *fs, 4840251881Speter representation_t *first_rep, 4841251881Speter apr_pool_t *pool) 4842251881Speter{ 4843251881Speter representation_t rep; 4844251881Speter struct rep_state *rs = NULL; 4845251881Speter struct rep_args *rep_args; 4846251881Speter svn_boolean_t is_cached = FALSE; 4847251881Speter apr_file_t *last_file = NULL; 4848251881Speter svn_revnum_t last_revision; 4849251881Speter 4850251881Speter *list = apr_array_make(pool, 1, sizeof(struct rep_state *)); 4851251881Speter rep = *first_rep; 4852251881Speter 4853251881Speter /* The value as stored in the data struct. 4854251881Speter 0 is either for unknown length or actually zero length. */ 4855251881Speter *expanded_size = first_rep->expanded_size; 4856251881Speter 4857251881Speter /* for the top-level rep, we need the rep_args */ 4858251881Speter SVN_ERR(create_rep_state(&rs, &rep_args, &last_file, 4859251881Speter &last_revision, &rep, fs, pool)); 4860251881Speter 4861251881Speter /* Unknown size or empty representation? 4862251881Speter That implies the this being the first iteration. 4863251881Speter Usually size equals on-disk size, except for empty, 4864251881Speter compressed representations (delta, size = 4). 4865251881Speter Please note that for all non-empty deltas have 4866251881Speter a 4-byte header _plus_ some data. */ 4867251881Speter if (*expanded_size == 0) 4868251881Speter if (! rep_args->is_delta || first_rep->size != 4) 4869251881Speter *expanded_size = first_rep->size; 4870251881Speter 4871251881Speter while (1) 4872251881Speter { 4873251881Speter /* fetch state, if that has not been done already */ 4874251881Speter if (!rs) 4875251881Speter SVN_ERR(create_rep_state(&rs, &rep_args, &last_file, 4876251881Speter &last_revision, &rep, fs, pool)); 4877251881Speter 4878251881Speter SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool)); 4879251881Speter if (is_cached) 4880251881Speter { 4881251881Speter /* We already have a reconstructed window in our cache. 4882251881Speter Write a pseudo rep_state with the full length. */ 4883251881Speter rs->off = rs->start; 4884251881Speter rs->end = rs->start + (*window_p)->len; 4885251881Speter *src_state = rs; 4886251881Speter return SVN_NO_ERROR; 4887251881Speter } 4888251881Speter 4889251881Speter if (!rep_args->is_delta) 4890251881Speter { 4891251881Speter /* This is a plaintext, so just return the current rep_state. */ 4892251881Speter *src_state = rs; 4893251881Speter return SVN_NO_ERROR; 4894251881Speter } 4895251881Speter 4896251881Speter /* Push this rep onto the list. If it's self-compressed, we're done. */ 4897251881Speter APR_ARRAY_PUSH(*list, struct rep_state *) = rs; 4898251881Speter if (rep_args->is_delta_vs_empty) 4899251881Speter { 4900251881Speter *src_state = NULL; 4901251881Speter return SVN_NO_ERROR; 4902251881Speter } 4903251881Speter 4904251881Speter rep.revision = rep_args->base_revision; 4905251881Speter rep.offset = rep_args->base_offset; 4906251881Speter rep.size = rep_args->base_length; 4907251881Speter rep.txn_id = NULL; 4908251881Speter 4909251881Speter rs = NULL; 4910251881Speter } 4911251881Speter} 4912251881Speter 4913251881Speter 4914251881Speter/* Create a rep_read_baton structure for node revision NODEREV in 4915251881Speter filesystem FS and store it in *RB_P. If FULLTEXT_CACHE_KEY is not 4916251881Speter NULL, it is the rep's key in the fulltext cache, and a stringbuf 4917251881Speter must be allocated to store the text. Perform all allocations in 4918251881Speter POOL. If rep is mutable, it must be for file contents. */ 4919251881Speterstatic svn_error_t * 4920251881Speterrep_read_get_baton(struct rep_read_baton **rb_p, 4921251881Speter svn_fs_t *fs, 4922251881Speter representation_t *rep, 4923251881Speter pair_cache_key_t fulltext_cache_key, 4924251881Speter apr_pool_t *pool) 4925251881Speter{ 4926251881Speter struct rep_read_baton *b; 4927251881Speter 4928251881Speter b = apr_pcalloc(pool, sizeof(*b)); 4929251881Speter b->fs = fs; 4930251881Speter b->base_window = NULL; 4931251881Speter b->chunk_index = 0; 4932251881Speter b->buf = NULL; 4933251881Speter b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); 4934251881Speter b->checksum_finalized = FALSE; 4935251881Speter b->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool); 4936251881Speter b->len = rep->expanded_size; 4937251881Speter b->off = 0; 4938251881Speter b->fulltext_cache_key = fulltext_cache_key; 4939251881Speter b->pool = svn_pool_create(pool); 4940251881Speter b->filehandle_pool = svn_pool_create(pool); 4941251881Speter 4942251881Speter SVN_ERR(build_rep_list(&b->rs_list, &b->base_window, 4943251881Speter &b->src_state, &b->len, fs, rep, 4944251881Speter b->filehandle_pool)); 4945251881Speter 4946251881Speter if (SVN_IS_VALID_REVNUM(fulltext_cache_key.revision)) 4947251881Speter b->current_fulltext = svn_stringbuf_create_ensure 4948251881Speter ((apr_size_t)b->len, 4949251881Speter b->filehandle_pool); 4950251881Speter else 4951251881Speter b->current_fulltext = NULL; 4952251881Speter 4953251881Speter /* Save our output baton. */ 4954251881Speter *rb_p = b; 4955251881Speter 4956251881Speter return SVN_NO_ERROR; 4957251881Speter} 4958251881Speter 4959251881Speter/* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta 4960251881Speter window into *NWIN. */ 4961251881Speterstatic svn_error_t * 4962251881Speterread_delta_window(svn_txdelta_window_t **nwin, int this_chunk, 4963251881Speter struct rep_state *rs, apr_pool_t *pool) 4964251881Speter{ 4965251881Speter svn_stream_t *stream; 4966251881Speter svn_boolean_t is_cached; 4967251881Speter apr_off_t old_offset; 4968251881Speter 4969251881Speter SVN_ERR_ASSERT(rs->chunk_index <= this_chunk); 4970251881Speter 4971251881Speter /* RS->FILE may be shared between RS instances -> make sure we point 4972251881Speter * to the right data. */ 4973251881Speter SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool)); 4974251881Speter 4975251881Speter /* Skip windows to reach the current chunk if we aren't there yet. */ 4976251881Speter while (rs->chunk_index < this_chunk) 4977251881Speter { 4978251881Speter SVN_ERR(svn_txdelta_skip_svndiff_window(rs->file, rs->ver, pool)); 4979251881Speter rs->chunk_index++; 4980251881Speter SVN_ERR(get_file_offset(&rs->off, rs->file, pool)); 4981251881Speter if (rs->off >= rs->end) 4982251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 4983251881Speter _("Reading one svndiff window read " 4984251881Speter "beyond the end of the " 4985251881Speter "representation")); 4986251881Speter } 4987251881Speter 4988251881Speter /* Read the next window. But first, try to find it in the cache. */ 4989251881Speter SVN_ERR(get_cached_window(nwin, rs, &is_cached, pool)); 4990251881Speter if (is_cached) 4991251881Speter return SVN_NO_ERROR; 4992251881Speter 4993251881Speter /* Actually read the next window. */ 4994251881Speter old_offset = rs->off; 4995251881Speter stream = svn_stream_from_aprfile2(rs->file, TRUE, pool); 4996251881Speter SVN_ERR(svn_txdelta_read_svndiff_window(nwin, stream, rs->ver, pool)); 4997251881Speter rs->chunk_index++; 4998251881Speter SVN_ERR(get_file_offset(&rs->off, rs->file, pool)); 4999251881Speter 5000251881Speter if (rs->off > rs->end) 5001251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 5002251881Speter _("Reading one svndiff window read beyond " 5003251881Speter "the end of the representation")); 5004251881Speter 5005251881Speter /* the window has not been cached before, thus cache it now 5006251881Speter * (if caching is used for them at all) */ 5007251881Speter return set_cached_window(*nwin, rs, old_offset, pool); 5008251881Speter} 5009251881Speter 5010251881Speter/* Read SIZE bytes from the representation RS and return it in *NWIN. */ 5011251881Speterstatic svn_error_t * 5012251881Speterread_plain_window(svn_stringbuf_t **nwin, struct rep_state *rs, 5013251881Speter apr_size_t size, apr_pool_t *pool) 5014251881Speter{ 5015251881Speter /* RS->FILE may be shared between RS instances -> make sure we point 5016251881Speter * to the right data. */ 5017251881Speter SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool)); 5018251881Speter 5019251881Speter /* Read the plain data. */ 5020251881Speter *nwin = svn_stringbuf_create_ensure(size, pool); 5021251881Speter SVN_ERR(svn_io_file_read_full2(rs->file, (*nwin)->data, size, NULL, NULL, 5022251881Speter pool)); 5023251881Speter (*nwin)->data[size] = 0; 5024251881Speter 5025251881Speter /* Update RS. */ 5026251881Speter rs->off += (apr_off_t)size; 5027251881Speter 5028251881Speter return SVN_NO_ERROR; 5029251881Speter} 5030251881Speter 5031251881Speter/* Get the undeltified window that is a result of combining all deltas 5032251881Speter from the current desired representation identified in *RB with its 5033251881Speter base representation. Store the window in *RESULT. */ 5034251881Speterstatic svn_error_t * 5035251881Speterget_combined_window(svn_stringbuf_t **result, 5036251881Speter struct rep_read_baton *rb) 5037251881Speter{ 5038251881Speter apr_pool_t *pool, *new_pool, *window_pool; 5039251881Speter int i; 5040251881Speter svn_txdelta_window_t *window; 5041251881Speter apr_array_header_t *windows; 5042251881Speter svn_stringbuf_t *source, *buf = rb->base_window; 5043251881Speter struct rep_state *rs; 5044251881Speter 5045251881Speter /* Read all windows that we need to combine. This is fine because 5046251881Speter the size of each window is relatively small (100kB) and skip- 5047251881Speter delta limits the number of deltas in a chain to well under 100. 5048251881Speter Stop early if one of them does not depend on its predecessors. */ 5049251881Speter window_pool = svn_pool_create(rb->pool); 5050251881Speter windows = apr_array_make(window_pool, 0, sizeof(svn_txdelta_window_t *)); 5051251881Speter for (i = 0; i < rb->rs_list->nelts; ++i) 5052251881Speter { 5053251881Speter rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *); 5054251881Speter SVN_ERR(read_delta_window(&window, rb->chunk_index, rs, window_pool)); 5055251881Speter 5056251881Speter APR_ARRAY_PUSH(windows, svn_txdelta_window_t *) = window; 5057251881Speter if (window->src_ops == 0) 5058251881Speter { 5059251881Speter ++i; 5060251881Speter break; 5061251881Speter } 5062251881Speter } 5063251881Speter 5064251881Speter /* Combine in the windows from the other delta reps. */ 5065251881Speter pool = svn_pool_create(rb->pool); 5066251881Speter for (--i; i >= 0; --i) 5067251881Speter { 5068251881Speter 5069251881Speter rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *); 5070251881Speter window = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *); 5071251881Speter 5072251881Speter /* Maybe, we've got a PLAIN start representation. If we do, read 5073251881Speter as much data from it as the needed for the txdelta window's source 5074251881Speter view. 5075289166Speter Note that BUF / SOURCE may only be NULL in the first iteration. 5076289166Speter Also note that we may have short-cut reading the delta chain -- 5077289166Speter in which case SRC_OPS is 0 and it might not be a PLAIN rep. */ 5078251881Speter source = buf; 5079289166Speter if (source == NULL && rb->src_state != NULL && window->src_ops) 5080251881Speter SVN_ERR(read_plain_window(&source, rb->src_state, window->sview_len, 5081251881Speter pool)); 5082251881Speter 5083251881Speter /* Combine this window with the current one. */ 5084251881Speter new_pool = svn_pool_create(rb->pool); 5085251881Speter buf = svn_stringbuf_create_ensure(window->tview_len, new_pool); 5086251881Speter buf->len = window->tview_len; 5087251881Speter 5088251881Speter svn_txdelta_apply_instructions(window, source ? source->data : NULL, 5089251881Speter buf->data, &buf->len); 5090251881Speter if (buf->len != window->tview_len) 5091251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 5092251881Speter _("svndiff window length is " 5093251881Speter "corrupt")); 5094251881Speter 5095251881Speter /* Cache windows only if the whole rep content could be read as a 5096251881Speter single chunk. Only then will no other chunk need a deeper RS 5097251881Speter list than the cached chunk. */ 5098251881Speter if ((rb->chunk_index == 0) && (rs->off == rs->end)) 5099251881Speter SVN_ERR(set_cached_combined_window(buf, rs, rs->start, new_pool)); 5100251881Speter 5101251881Speter /* Cycle pools so that we only need to hold three windows at a time. */ 5102251881Speter svn_pool_destroy(pool); 5103251881Speter pool = new_pool; 5104251881Speter } 5105251881Speter 5106251881Speter svn_pool_destroy(window_pool); 5107251881Speter 5108251881Speter *result = buf; 5109251881Speter return SVN_NO_ERROR; 5110251881Speter} 5111251881Speter 5112251881Speter/* Returns whether or not the expanded fulltext of the file is cachable 5113251881Speter * based on its size SIZE. The decision depends on the cache used by RB. 5114251881Speter */ 5115251881Speterstatic svn_boolean_t 5116251881Speterfulltext_size_is_cachable(fs_fs_data_t *ffd, svn_filesize_t size) 5117251881Speter{ 5118251881Speter return (size < APR_SIZE_MAX) 5119251881Speter && svn_cache__is_cachable(ffd->fulltext_cache, (apr_size_t)size); 5120251881Speter} 5121251881Speter 5122251881Speter/* Close method used on streams returned by read_representation(). 5123251881Speter */ 5124251881Speterstatic svn_error_t * 5125251881Speterrep_read_contents_close(void *baton) 5126251881Speter{ 5127251881Speter struct rep_read_baton *rb = baton; 5128251881Speter 5129251881Speter svn_pool_destroy(rb->pool); 5130251881Speter svn_pool_destroy(rb->filehandle_pool); 5131251881Speter 5132251881Speter return SVN_NO_ERROR; 5133251881Speter} 5134251881Speter 5135251881Speter/* Return the next *LEN bytes of the rep and store them in *BUF. */ 5136251881Speterstatic svn_error_t * 5137251881Speterget_contents(struct rep_read_baton *rb, 5138251881Speter char *buf, 5139251881Speter apr_size_t *len) 5140251881Speter{ 5141251881Speter apr_size_t copy_len, remaining = *len; 5142251881Speter char *cur = buf; 5143251881Speter struct rep_state *rs; 5144251881Speter 5145251881Speter /* Special case for when there are no delta reps, only a plain 5146251881Speter text. */ 5147251881Speter if (rb->rs_list->nelts == 0) 5148251881Speter { 5149251881Speter copy_len = remaining; 5150251881Speter rs = rb->src_state; 5151251881Speter 5152251881Speter if (rb->base_window != NULL) 5153251881Speter { 5154251881Speter /* We got the desired rep directly from the cache. 5155251881Speter This is where we need the pseudo rep_state created 5156251881Speter by build_rep_list(). */ 5157251881Speter apr_size_t offset = (apr_size_t)(rs->off - rs->start); 5158251881Speter if (copy_len + offset > rb->base_window->len) 5159251881Speter copy_len = offset < rb->base_window->len 5160251881Speter ? rb->base_window->len - offset 5161251881Speter : 0ul; 5162251881Speter 5163251881Speter memcpy (cur, rb->base_window->data + offset, copy_len); 5164251881Speter } 5165251881Speter else 5166251881Speter { 5167251881Speter if (((apr_off_t) copy_len) > rs->end - rs->off) 5168251881Speter copy_len = (apr_size_t) (rs->end - rs->off); 5169251881Speter SVN_ERR(svn_io_file_read_full2(rs->file, cur, copy_len, NULL, 5170251881Speter NULL, rb->pool)); 5171251881Speter } 5172251881Speter 5173251881Speter rs->off += copy_len; 5174251881Speter *len = copy_len; 5175251881Speter return SVN_NO_ERROR; 5176251881Speter } 5177251881Speter 5178251881Speter while (remaining > 0) 5179251881Speter { 5180251881Speter /* If we have buffered data from a previous chunk, use that. */ 5181251881Speter if (rb->buf) 5182251881Speter { 5183251881Speter /* Determine how much to copy from the buffer. */ 5184251881Speter copy_len = rb->buf_len - rb->buf_pos; 5185251881Speter if (copy_len > remaining) 5186251881Speter copy_len = remaining; 5187251881Speter 5188251881Speter /* Actually copy the data. */ 5189251881Speter memcpy(cur, rb->buf + rb->buf_pos, copy_len); 5190251881Speter rb->buf_pos += copy_len; 5191251881Speter cur += copy_len; 5192251881Speter remaining -= copy_len; 5193251881Speter 5194251881Speter /* If the buffer is all used up, clear it and empty the 5195251881Speter local pool. */ 5196251881Speter if (rb->buf_pos == rb->buf_len) 5197251881Speter { 5198251881Speter svn_pool_clear(rb->pool); 5199251881Speter rb->buf = NULL; 5200251881Speter } 5201251881Speter } 5202251881Speter else 5203251881Speter { 5204251881Speter svn_stringbuf_t *sbuf = NULL; 5205251881Speter 5206251881Speter rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *); 5207251881Speter if (rs->off == rs->end) 5208251881Speter break; 5209251881Speter 5210251881Speter /* Get more buffered data by evaluating a chunk. */ 5211251881Speter SVN_ERR(get_combined_window(&sbuf, rb)); 5212251881Speter 5213251881Speter rb->chunk_index++; 5214251881Speter rb->buf_len = sbuf->len; 5215251881Speter rb->buf = sbuf->data; 5216251881Speter rb->buf_pos = 0; 5217251881Speter } 5218251881Speter } 5219251881Speter 5220251881Speter *len = cur - buf; 5221251881Speter 5222251881Speter return SVN_NO_ERROR; 5223251881Speter} 5224251881Speter 5225251881Speter/* BATON is of type `rep_read_baton'; read the next *LEN bytes of the 5226251881Speter representation and store them in *BUF. Sum as we read and verify 5227251881Speter the MD5 sum at the end. */ 5228251881Speterstatic svn_error_t * 5229251881Speterrep_read_contents(void *baton, 5230251881Speter char *buf, 5231251881Speter apr_size_t *len) 5232251881Speter{ 5233251881Speter struct rep_read_baton *rb = baton; 5234251881Speter 5235251881Speter /* Get the next block of data. */ 5236251881Speter SVN_ERR(get_contents(rb, buf, len)); 5237251881Speter 5238251881Speter if (rb->current_fulltext) 5239251881Speter svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len); 5240251881Speter 5241251881Speter /* Perform checksumming. We want to check the checksum as soon as 5242251881Speter the last byte of data is read, in case the caller never performs 5243251881Speter a short read, but we don't want to finalize the MD5 context 5244251881Speter twice. */ 5245251881Speter if (!rb->checksum_finalized) 5246251881Speter { 5247251881Speter SVN_ERR(svn_checksum_update(rb->md5_checksum_ctx, buf, *len)); 5248251881Speter rb->off += *len; 5249251881Speter if (rb->off == rb->len) 5250251881Speter { 5251251881Speter svn_checksum_t *md5_checksum; 5252251881Speter 5253251881Speter rb->checksum_finalized = TRUE; 5254251881Speter SVN_ERR(svn_checksum_final(&md5_checksum, rb->md5_checksum_ctx, 5255251881Speter rb->pool)); 5256251881Speter if (!svn_checksum_match(md5_checksum, rb->md5_checksum)) 5257251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, 5258251881Speter svn_checksum_mismatch_err(rb->md5_checksum, md5_checksum, 5259251881Speter rb->pool, 5260251881Speter _("Checksum mismatch while reading representation")), 5261251881Speter NULL); 5262251881Speter } 5263251881Speter } 5264251881Speter 5265251881Speter if (rb->off == rb->len && rb->current_fulltext) 5266251881Speter { 5267251881Speter fs_fs_data_t *ffd = rb->fs->fsap_data; 5268251881Speter SVN_ERR(svn_cache__set(ffd->fulltext_cache, &rb->fulltext_cache_key, 5269251881Speter rb->current_fulltext, rb->pool)); 5270251881Speter rb->current_fulltext = NULL; 5271251881Speter } 5272251881Speter 5273251881Speter return SVN_NO_ERROR; 5274251881Speter} 5275251881Speter 5276251881Speter 5277251881Speter/* Return a stream in *CONTENTS_P that will read the contents of a 5278251881Speter representation stored at the location given by REP. Appropriate 5279251881Speter for any kind of immutable representation, but only for file 5280251881Speter contents (not props or directory contents) in mutable 5281251881Speter representations. 5282251881Speter 5283251881Speter If REP is NULL, the representation is assumed to be empty, and the 5284251881Speter empty stream is returned. 5285251881Speter*/ 5286251881Speterstatic svn_error_t * 5287251881Speterread_representation(svn_stream_t **contents_p, 5288251881Speter svn_fs_t *fs, 5289251881Speter representation_t *rep, 5290251881Speter apr_pool_t *pool) 5291251881Speter{ 5292251881Speter if (! rep) 5293251881Speter { 5294251881Speter *contents_p = svn_stream_empty(pool); 5295251881Speter } 5296251881Speter else 5297251881Speter { 5298251881Speter fs_fs_data_t *ffd = fs->fsap_data; 5299251881Speter pair_cache_key_t fulltext_cache_key = { 0 }; 5300251881Speter svn_filesize_t len = rep->expanded_size ? rep->expanded_size : rep->size; 5301251881Speter struct rep_read_baton *rb; 5302251881Speter 5303251881Speter fulltext_cache_key.revision = rep->revision; 5304251881Speter fulltext_cache_key.second = rep->offset; 5305251881Speter if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision) 5306251881Speter && fulltext_size_is_cachable(ffd, len)) 5307251881Speter { 5308251881Speter svn_stringbuf_t *fulltext; 5309251881Speter svn_boolean_t is_cached; 5310251881Speter SVN_ERR(svn_cache__get((void **) &fulltext, &is_cached, 5311251881Speter ffd->fulltext_cache, &fulltext_cache_key, 5312251881Speter pool)); 5313251881Speter if (is_cached) 5314251881Speter { 5315251881Speter *contents_p = svn_stream_from_stringbuf(fulltext, pool); 5316251881Speter return SVN_NO_ERROR; 5317251881Speter } 5318251881Speter } 5319251881Speter else 5320251881Speter fulltext_cache_key.revision = SVN_INVALID_REVNUM; 5321251881Speter 5322251881Speter SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool)); 5323251881Speter 5324251881Speter *contents_p = svn_stream_create(rb, pool); 5325251881Speter svn_stream_set_read(*contents_p, rep_read_contents); 5326251881Speter svn_stream_set_close(*contents_p, rep_read_contents_close); 5327251881Speter } 5328251881Speter 5329251881Speter return SVN_NO_ERROR; 5330251881Speter} 5331251881Speter 5332251881Spetersvn_error_t * 5333251881Spetersvn_fs_fs__get_contents(svn_stream_t **contents_p, 5334251881Speter svn_fs_t *fs, 5335251881Speter node_revision_t *noderev, 5336251881Speter apr_pool_t *pool) 5337251881Speter{ 5338251881Speter return read_representation(contents_p, fs, noderev->data_rep, pool); 5339251881Speter} 5340251881Speter 5341251881Speter/* Baton used when reading delta windows. */ 5342251881Speterstruct delta_read_baton 5343251881Speter{ 5344251881Speter struct rep_state *rs; 5345251881Speter svn_checksum_t *checksum; 5346251881Speter}; 5347251881Speter 5348251881Speter/* This implements the svn_txdelta_next_window_fn_t interface. */ 5349251881Speterstatic svn_error_t * 5350251881Speterdelta_read_next_window(svn_txdelta_window_t **window, void *baton, 5351251881Speter apr_pool_t *pool) 5352251881Speter{ 5353251881Speter struct delta_read_baton *drb = baton; 5354251881Speter 5355251881Speter if (drb->rs->off == drb->rs->end) 5356251881Speter { 5357251881Speter *window = NULL; 5358251881Speter return SVN_NO_ERROR; 5359251881Speter } 5360251881Speter 5361251881Speter return read_delta_window(window, drb->rs->chunk_index, drb->rs, pool); 5362251881Speter} 5363251881Speter 5364251881Speter/* This implements the svn_txdelta_md5_digest_fn_t interface. */ 5365251881Speterstatic const unsigned char * 5366251881Speterdelta_read_md5_digest(void *baton) 5367251881Speter{ 5368251881Speter struct delta_read_baton *drb = baton; 5369251881Speter 5370251881Speter if (drb->checksum->kind == svn_checksum_md5) 5371251881Speter return drb->checksum->digest; 5372251881Speter else 5373251881Speter return NULL; 5374251881Speter} 5375251881Speter 5376251881Spetersvn_error_t * 5377251881Spetersvn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p, 5378251881Speter svn_fs_t *fs, 5379251881Speter node_revision_t *source, 5380251881Speter node_revision_t *target, 5381251881Speter apr_pool_t *pool) 5382251881Speter{ 5383251881Speter svn_stream_t *source_stream, *target_stream; 5384251881Speter 5385251881Speter /* Try a shortcut: if the target is stored as a delta against the source, 5386251881Speter then just use that delta. */ 5387251881Speter if (source && source->data_rep && target->data_rep) 5388251881Speter { 5389251881Speter struct rep_state *rep_state; 5390251881Speter struct rep_args *rep_args; 5391251881Speter 5392251881Speter /* Read target's base rep if any. */ 5393251881Speter SVN_ERR(create_rep_state(&rep_state, &rep_args, NULL, NULL, 5394251881Speter target->data_rep, fs, pool)); 5395262253Speter 5396262253Speter /* If that matches source, then use this delta as is. 5397262253Speter Note that we want an actual delta here. E.g. a self-delta would 5398262253Speter not be good enough. */ 5399251881Speter if (rep_args->is_delta 5400262253Speter && rep_args->base_revision == source->data_rep->revision 5401262253Speter && rep_args->base_offset == source->data_rep->offset) 5402251881Speter { 5403251881Speter /* Create the delta read baton. */ 5404251881Speter struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb)); 5405251881Speter drb->rs = rep_state; 5406251881Speter drb->checksum = svn_checksum_dup(target->data_rep->md5_checksum, 5407251881Speter pool); 5408251881Speter *stream_p = svn_txdelta_stream_create(drb, delta_read_next_window, 5409251881Speter delta_read_md5_digest, pool); 5410251881Speter return SVN_NO_ERROR; 5411251881Speter } 5412251881Speter else 5413251881Speter SVN_ERR(svn_io_file_close(rep_state->file, pool)); 5414251881Speter } 5415251881Speter 5416251881Speter /* Read both fulltexts and construct a delta. */ 5417251881Speter if (source) 5418251881Speter SVN_ERR(read_representation(&source_stream, fs, source->data_rep, pool)); 5419251881Speter else 5420251881Speter source_stream = svn_stream_empty(pool); 5421251881Speter SVN_ERR(read_representation(&target_stream, fs, target->data_rep, pool)); 5422251881Speter 5423251881Speter /* Because source and target stream will already verify their content, 5424251881Speter * there is no need to do this once more. In particular if the stream 5425251881Speter * content is being fetched from cache. */ 5426251881Speter svn_txdelta2(stream_p, source_stream, target_stream, FALSE, pool); 5427251881Speter 5428251881Speter return SVN_NO_ERROR; 5429251881Speter} 5430251881Speter 5431251881Speter/* Baton for cache_access_wrapper. Wraps the original parameters of 5432251881Speter * svn_fs_fs__try_process_file_content(). 5433251881Speter */ 5434251881Spetertypedef struct cache_access_wrapper_baton_t 5435251881Speter{ 5436251881Speter svn_fs_process_contents_func_t func; 5437251881Speter void* baton; 5438251881Speter} cache_access_wrapper_baton_t; 5439251881Speter 5440251881Speter/* Wrapper to translate between svn_fs_process_contents_func_t and 5441251881Speter * svn_cache__partial_getter_func_t. 5442251881Speter */ 5443251881Speterstatic svn_error_t * 5444251881Spetercache_access_wrapper(void **out, 5445251881Speter const void *data, 5446251881Speter apr_size_t data_len, 5447251881Speter void *baton, 5448251881Speter apr_pool_t *pool) 5449251881Speter{ 5450251881Speter cache_access_wrapper_baton_t *wrapper_baton = baton; 5451251881Speter 5452251881Speter SVN_ERR(wrapper_baton->func((const unsigned char *)data, 5453251881Speter data_len - 1, /* cache adds terminating 0 */ 5454251881Speter wrapper_baton->baton, 5455251881Speter pool)); 5456251881Speter 5457251881Speter /* non-NULL value to signal the calling cache that all went well */ 5458251881Speter *out = baton; 5459251881Speter 5460251881Speter return SVN_NO_ERROR; 5461251881Speter} 5462251881Speter 5463251881Spetersvn_error_t * 5464251881Spetersvn_fs_fs__try_process_file_contents(svn_boolean_t *success, 5465251881Speter svn_fs_t *fs, 5466251881Speter node_revision_t *noderev, 5467251881Speter svn_fs_process_contents_func_t processor, 5468251881Speter void* baton, 5469251881Speter apr_pool_t *pool) 5470251881Speter{ 5471251881Speter representation_t *rep = noderev->data_rep; 5472251881Speter if (rep) 5473251881Speter { 5474251881Speter fs_fs_data_t *ffd = fs->fsap_data; 5475251881Speter pair_cache_key_t fulltext_cache_key = { 0 }; 5476251881Speter 5477251881Speter fulltext_cache_key.revision = rep->revision; 5478251881Speter fulltext_cache_key.second = rep->offset; 5479251881Speter if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision) 5480251881Speter && fulltext_size_is_cachable(ffd, rep->expanded_size)) 5481251881Speter { 5482251881Speter cache_access_wrapper_baton_t wrapper_baton; 5483251881Speter void *dummy = NULL; 5484251881Speter 5485251881Speter wrapper_baton.func = processor; 5486251881Speter wrapper_baton.baton = baton; 5487251881Speter return svn_cache__get_partial(&dummy, success, 5488251881Speter ffd->fulltext_cache, 5489251881Speter &fulltext_cache_key, 5490251881Speter cache_access_wrapper, 5491251881Speter &wrapper_baton, 5492251881Speter pool); 5493251881Speter } 5494251881Speter } 5495251881Speter 5496251881Speter *success = FALSE; 5497251881Speter return SVN_NO_ERROR; 5498251881Speter} 5499251881Speter 5500251881Speter/* Fetch the contents of a directory into ENTRIES. Values are stored 5501251881Speter as filename to string mappings; further conversion is necessary to 5502251881Speter convert them into svn_fs_dirent_t values. */ 5503251881Speterstatic svn_error_t * 5504251881Speterget_dir_contents(apr_hash_t *entries, 5505251881Speter svn_fs_t *fs, 5506251881Speter node_revision_t *noderev, 5507251881Speter apr_pool_t *pool) 5508251881Speter{ 5509251881Speter svn_stream_t *contents; 5510251881Speter 5511251881Speter if (noderev->data_rep && noderev->data_rep->txn_id) 5512251881Speter { 5513251881Speter const char *filename = path_txn_node_children(fs, noderev->id, pool); 5514251881Speter 5515251881Speter /* The representation is mutable. Read the old directory 5516251881Speter contents from the mutable children file, followed by the 5517251881Speter changes we've made in this transaction. */ 5518251881Speter SVN_ERR(svn_stream_open_readonly(&contents, filename, pool, pool)); 5519251881Speter SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool)); 5520251881Speter SVN_ERR(svn_hash_read_incremental(entries, contents, NULL, pool)); 5521251881Speter SVN_ERR(svn_stream_close(contents)); 5522251881Speter } 5523251881Speter else if (noderev->data_rep) 5524251881Speter { 5525251881Speter /* use a temporary pool for temp objects. 5526251881Speter * Also undeltify content before parsing it. Otherwise, we could only 5527251881Speter * parse it byte-by-byte. 5528251881Speter */ 5529251881Speter apr_pool_t *text_pool = svn_pool_create(pool); 5530251881Speter apr_size_t len = noderev->data_rep->expanded_size 5531251881Speter ? (apr_size_t)noderev->data_rep->expanded_size 5532251881Speter : (apr_size_t)noderev->data_rep->size; 5533251881Speter svn_stringbuf_t *text = svn_stringbuf_create_ensure(len, text_pool); 5534251881Speter text->len = len; 5535251881Speter 5536251881Speter /* The representation is immutable. Read it normally. */ 5537251881Speter SVN_ERR(read_representation(&contents, fs, noderev->data_rep, text_pool)); 5538251881Speter SVN_ERR(svn_stream_read(contents, text->data, &text->len)); 5539251881Speter SVN_ERR(svn_stream_close(contents)); 5540251881Speter 5541251881Speter /* de-serialize hash */ 5542251881Speter contents = svn_stream_from_stringbuf(text, text_pool); 5543251881Speter SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool)); 5544251881Speter 5545251881Speter svn_pool_destroy(text_pool); 5546251881Speter } 5547251881Speter 5548251881Speter return SVN_NO_ERROR; 5549251881Speter} 5550251881Speter 5551251881Speter 5552251881Speterstatic const char * 5553251881Speterunparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id, 5554251881Speter apr_pool_t *pool) 5555251881Speter{ 5556251881Speter return apr_psprintf(pool, "%s %s", 5557251881Speter (kind == svn_node_file) ? KIND_FILE : KIND_DIR, 5558251881Speter svn_fs_fs__id_unparse(id, pool)->data); 5559251881Speter} 5560251881Speter 5561251881Speter/* Given a hash ENTRIES of dirent structions, return a hash in 5562251881Speter *STR_ENTRIES_P, that has svn_string_t as the values in the format 5563251881Speter specified by the fs_fs directory contents file. Perform 5564251881Speter allocations in POOL. */ 5565251881Speterstatic svn_error_t * 5566251881Speterunparse_dir_entries(apr_hash_t **str_entries_p, 5567251881Speter apr_hash_t *entries, 5568251881Speter apr_pool_t *pool) 5569251881Speter{ 5570251881Speter apr_hash_index_t *hi; 5571251881Speter 5572251881Speter /* For now, we use a our own hash function to ensure that we get a 5573251881Speter * (largely) stable order when serializing the data. It also gives 5574251881Speter * us some performance improvement. 5575251881Speter * 5576251881Speter * ### TODO ### 5577251881Speter * Use some sorted or other fixed order data container. 5578251881Speter */ 5579251881Speter *str_entries_p = svn_hash__make(pool); 5580251881Speter 5581251881Speter for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) 5582251881Speter { 5583251881Speter const void *key; 5584251881Speter apr_ssize_t klen; 5585251881Speter svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi); 5586251881Speter const char *new_val; 5587251881Speter 5588251881Speter apr_hash_this(hi, &key, &klen, NULL); 5589251881Speter new_val = unparse_dir_entry(dirent->kind, dirent->id, pool); 5590251881Speter apr_hash_set(*str_entries_p, key, klen, 5591251881Speter svn_string_create(new_val, pool)); 5592251881Speter } 5593251881Speter 5594251881Speter return SVN_NO_ERROR; 5595251881Speter} 5596251881Speter 5597251881Speter 5598251881Speter/* Given a hash STR_ENTRIES with values as svn_string_t as specified 5599251881Speter in an FSFS directory contents listing, return a hash of dirents in 5600251881Speter *ENTRIES_P. Perform allocations in POOL. */ 5601251881Speterstatic svn_error_t * 5602251881Speterparse_dir_entries(apr_hash_t **entries_p, 5603251881Speter apr_hash_t *str_entries, 5604251881Speter const char *unparsed_id, 5605251881Speter apr_pool_t *pool) 5606251881Speter{ 5607251881Speter apr_hash_index_t *hi; 5608251881Speter 5609251881Speter *entries_p = apr_hash_make(pool); 5610251881Speter 5611251881Speter /* Translate the string dir entries into real entries. */ 5612251881Speter for (hi = apr_hash_first(pool, str_entries); hi; hi = apr_hash_next(hi)) 5613251881Speter { 5614251881Speter const char *name = svn__apr_hash_index_key(hi); 5615251881Speter svn_string_t *str_val = svn__apr_hash_index_val(hi); 5616251881Speter char *str, *last_str; 5617251881Speter svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent)); 5618251881Speter 5619251881Speter last_str = apr_pstrdup(pool, str_val->data); 5620251881Speter dirent->name = apr_pstrdup(pool, name); 5621251881Speter 5622251881Speter str = svn_cstring_tokenize(" ", &last_str); 5623251881Speter if (str == NULL) 5624251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 5625251881Speter _("Directory entry corrupt in '%s'"), 5626251881Speter unparsed_id); 5627251881Speter 5628251881Speter if (strcmp(str, KIND_FILE) == 0) 5629251881Speter { 5630251881Speter dirent->kind = svn_node_file; 5631251881Speter } 5632251881Speter else if (strcmp(str, KIND_DIR) == 0) 5633251881Speter { 5634251881Speter dirent->kind = svn_node_dir; 5635251881Speter } 5636251881Speter else 5637251881Speter { 5638251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 5639251881Speter _("Directory entry corrupt in '%s'"), 5640251881Speter unparsed_id); 5641251881Speter } 5642251881Speter 5643251881Speter str = svn_cstring_tokenize(" ", &last_str); 5644251881Speter if (str == NULL) 5645251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 5646251881Speter _("Directory entry corrupt in '%s'"), 5647251881Speter unparsed_id); 5648251881Speter 5649251881Speter dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool); 5650251881Speter 5651251881Speter svn_hash_sets(*entries_p, dirent->name, dirent); 5652251881Speter } 5653251881Speter 5654251881Speter return SVN_NO_ERROR; 5655251881Speter} 5656251881Speter 5657251881Speter/* Return the cache object in FS responsible to storing the directory 5658251881Speter * the NODEREV. If none exists, return NULL. */ 5659251881Speterstatic svn_cache__t * 5660251881Speterlocate_dir_cache(svn_fs_t *fs, 5661251881Speter node_revision_t *noderev) 5662251881Speter{ 5663251881Speter fs_fs_data_t *ffd = fs->fsap_data; 5664251881Speter return svn_fs_fs__id_txn_id(noderev->id) 5665251881Speter ? ffd->txn_dir_cache 5666251881Speter : ffd->dir_cache; 5667251881Speter} 5668251881Speter 5669251881Spetersvn_error_t * 5670251881Spetersvn_fs_fs__rep_contents_dir(apr_hash_t **entries_p, 5671251881Speter svn_fs_t *fs, 5672251881Speter node_revision_t *noderev, 5673251881Speter apr_pool_t *pool) 5674251881Speter{ 5675251881Speter const char *unparsed_id = NULL; 5676251881Speter apr_hash_t *unparsed_entries, *parsed_entries; 5677251881Speter 5678251881Speter /* find the cache we may use */ 5679251881Speter svn_cache__t *cache = locate_dir_cache(fs, noderev); 5680251881Speter if (cache) 5681251881Speter { 5682251881Speter svn_boolean_t found; 5683251881Speter 5684251881Speter unparsed_id = svn_fs_fs__id_unparse(noderev->id, pool)->data; 5685251881Speter SVN_ERR(svn_cache__get((void **) entries_p, &found, cache, 5686251881Speter unparsed_id, pool)); 5687251881Speter if (found) 5688251881Speter return SVN_NO_ERROR; 5689251881Speter } 5690251881Speter 5691251881Speter /* Read in the directory hash. */ 5692251881Speter unparsed_entries = apr_hash_make(pool); 5693251881Speter SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool)); 5694251881Speter SVN_ERR(parse_dir_entries(&parsed_entries, unparsed_entries, 5695251881Speter unparsed_id, pool)); 5696251881Speter 5697251881Speter /* Update the cache, if we are to use one. */ 5698251881Speter if (cache) 5699251881Speter SVN_ERR(svn_cache__set(cache, unparsed_id, parsed_entries, pool)); 5700251881Speter 5701251881Speter *entries_p = parsed_entries; 5702251881Speter return SVN_NO_ERROR; 5703251881Speter} 5704251881Speter 5705251881Spetersvn_error_t * 5706251881Spetersvn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent, 5707251881Speter svn_fs_t *fs, 5708251881Speter node_revision_t *noderev, 5709251881Speter const char *name, 5710251881Speter apr_pool_t *result_pool, 5711251881Speter apr_pool_t *scratch_pool) 5712251881Speter{ 5713251881Speter svn_boolean_t found = FALSE; 5714251881Speter 5715251881Speter /* find the cache we may use */ 5716251881Speter svn_cache__t *cache = locate_dir_cache(fs, noderev); 5717251881Speter if (cache) 5718251881Speter { 5719251881Speter const char *unparsed_id = 5720251881Speter svn_fs_fs__id_unparse(noderev->id, scratch_pool)->data; 5721251881Speter 5722251881Speter /* Cache lookup. */ 5723251881Speter SVN_ERR(svn_cache__get_partial((void **)dirent, 5724251881Speter &found, 5725251881Speter cache, 5726251881Speter unparsed_id, 5727251881Speter svn_fs_fs__extract_dir_entry, 5728251881Speter (void*)name, 5729251881Speter result_pool)); 5730251881Speter } 5731251881Speter 5732251881Speter /* fetch data from disk if we did not find it in the cache */ 5733251881Speter if (! found) 5734251881Speter { 5735251881Speter apr_hash_t *entries; 5736251881Speter svn_fs_dirent_t *entry; 5737251881Speter svn_fs_dirent_t *entry_copy = NULL; 5738251881Speter 5739251881Speter /* read the dir from the file system. It will probably be put it 5740251881Speter into the cache for faster lookup in future calls. */ 5741251881Speter SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, 5742251881Speter scratch_pool)); 5743251881Speter 5744251881Speter /* find desired entry and return a copy in POOL, if found */ 5745251881Speter entry = svn_hash_gets(entries, name); 5746251881Speter if (entry != NULL) 5747251881Speter { 5748251881Speter entry_copy = apr_palloc(result_pool, sizeof(*entry_copy)); 5749251881Speter entry_copy->name = apr_pstrdup(result_pool, entry->name); 5750251881Speter entry_copy->id = svn_fs_fs__id_copy(entry->id, result_pool); 5751251881Speter entry_copy->kind = entry->kind; 5752251881Speter } 5753251881Speter 5754251881Speter *dirent = entry_copy; 5755251881Speter } 5756251881Speter 5757251881Speter return SVN_NO_ERROR; 5758251881Speter} 5759251881Speter 5760251881Spetersvn_error_t * 5761251881Spetersvn_fs_fs__get_proplist(apr_hash_t **proplist_p, 5762251881Speter svn_fs_t *fs, 5763251881Speter node_revision_t *noderev, 5764251881Speter apr_pool_t *pool) 5765251881Speter{ 5766251881Speter apr_hash_t *proplist; 5767251881Speter svn_stream_t *stream; 5768251881Speter 5769251881Speter if (noderev->prop_rep && noderev->prop_rep->txn_id) 5770251881Speter { 5771251881Speter const char *filename = path_txn_node_props(fs, noderev->id, pool); 5772251881Speter proplist = apr_hash_make(pool); 5773251881Speter 5774251881Speter SVN_ERR(svn_stream_open_readonly(&stream, filename, pool, pool)); 5775251881Speter SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool)); 5776251881Speter SVN_ERR(svn_stream_close(stream)); 5777251881Speter } 5778251881Speter else if (noderev->prop_rep) 5779251881Speter { 5780251881Speter fs_fs_data_t *ffd = fs->fsap_data; 5781251881Speter representation_t *rep = noderev->prop_rep; 5782251881Speter pair_cache_key_t key = { 0 }; 5783251881Speter 5784251881Speter key.revision = rep->revision; 5785251881Speter key.second = rep->offset; 5786251881Speter if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision)) 5787251881Speter { 5788251881Speter svn_boolean_t is_cached; 5789251881Speter SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached, 5790251881Speter ffd->properties_cache, &key, pool)); 5791251881Speter if (is_cached) 5792251881Speter return SVN_NO_ERROR; 5793251881Speter } 5794251881Speter 5795251881Speter proplist = apr_hash_make(pool); 5796251881Speter SVN_ERR(read_representation(&stream, fs, noderev->prop_rep, pool)); 5797251881Speter SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool)); 5798251881Speter SVN_ERR(svn_stream_close(stream)); 5799251881Speter 5800251881Speter if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision)) 5801251881Speter SVN_ERR(svn_cache__set(ffd->properties_cache, &key, proplist, pool)); 5802251881Speter } 5803251881Speter else 5804251881Speter { 5805251881Speter /* return an empty prop list if the node doesn't have any props */ 5806251881Speter proplist = apr_hash_make(pool); 5807251881Speter } 5808251881Speter 5809251881Speter *proplist_p = proplist; 5810251881Speter 5811251881Speter return SVN_NO_ERROR; 5812251881Speter} 5813251881Speter 5814251881Spetersvn_error_t * 5815251881Spetersvn_fs_fs__file_length(svn_filesize_t *length, 5816251881Speter node_revision_t *noderev, 5817251881Speter apr_pool_t *pool) 5818251881Speter{ 5819251881Speter if (noderev->data_rep) 5820251881Speter *length = noderev->data_rep->expanded_size; 5821251881Speter else 5822251881Speter *length = 0; 5823251881Speter 5824251881Speter return SVN_NO_ERROR; 5825251881Speter} 5826251881Speter 5827251881Spetersvn_boolean_t 5828251881Spetersvn_fs_fs__noderev_same_rep_key(representation_t *a, 5829251881Speter representation_t *b) 5830251881Speter{ 5831251881Speter if (a == b) 5832251881Speter return TRUE; 5833251881Speter 5834251881Speter if (a == NULL || b == NULL) 5835251881Speter return FALSE; 5836251881Speter 5837251881Speter if (a->offset != b->offset) 5838251881Speter return FALSE; 5839251881Speter 5840251881Speter if (a->revision != b->revision) 5841251881Speter return FALSE; 5842251881Speter 5843251881Speter if (a->uniquifier == b->uniquifier) 5844251881Speter return TRUE; 5845251881Speter 5846251881Speter if (a->uniquifier == NULL || b->uniquifier == NULL) 5847251881Speter return FALSE; 5848251881Speter 5849251881Speter return strcmp(a->uniquifier, b->uniquifier) == 0; 5850251881Speter} 5851251881Speter 5852251881Spetersvn_error_t * 5853251881Spetersvn_fs_fs__file_checksum(svn_checksum_t **checksum, 5854251881Speter node_revision_t *noderev, 5855251881Speter svn_checksum_kind_t kind, 5856251881Speter apr_pool_t *pool) 5857251881Speter{ 5858251881Speter if (noderev->data_rep) 5859251881Speter { 5860251881Speter switch(kind) 5861251881Speter { 5862251881Speter case svn_checksum_md5: 5863251881Speter *checksum = svn_checksum_dup(noderev->data_rep->md5_checksum, 5864251881Speter pool); 5865251881Speter break; 5866251881Speter case svn_checksum_sha1: 5867251881Speter *checksum = svn_checksum_dup(noderev->data_rep->sha1_checksum, 5868251881Speter pool); 5869251881Speter break; 5870251881Speter default: 5871251881Speter *checksum = NULL; 5872251881Speter } 5873251881Speter } 5874251881Speter else 5875251881Speter *checksum = NULL; 5876251881Speter 5877251881Speter return SVN_NO_ERROR; 5878251881Speter} 5879251881Speter 5880251881Speterrepresentation_t * 5881251881Spetersvn_fs_fs__rep_copy(representation_t *rep, 5882251881Speter apr_pool_t *pool) 5883251881Speter{ 5884251881Speter representation_t *rep_new; 5885251881Speter 5886251881Speter if (rep == NULL) 5887251881Speter return NULL; 5888251881Speter 5889251881Speter rep_new = apr_pcalloc(pool, sizeof(*rep_new)); 5890251881Speter 5891251881Speter memcpy(rep_new, rep, sizeof(*rep_new)); 5892251881Speter rep_new->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool); 5893251881Speter rep_new->sha1_checksum = svn_checksum_dup(rep->sha1_checksum, pool); 5894251881Speter rep_new->uniquifier = apr_pstrdup(pool, rep->uniquifier); 5895251881Speter 5896251881Speter return rep_new; 5897251881Speter} 5898251881Speter 5899251881Speter/* Merge the internal-use-only CHANGE into a hash of public-FS 5900251881Speter svn_fs_path_change2_t CHANGES, collapsing multiple changes into a 5901251881Speter single summarical (is that real word?) change per path. Also keep 5902251881Speter the COPYFROM_CACHE up to date with new adds and replaces. */ 5903251881Speterstatic svn_error_t * 5904251881Speterfold_change(apr_hash_t *changes, 5905251881Speter const change_t *change, 5906251881Speter apr_hash_t *copyfrom_cache) 5907251881Speter{ 5908251881Speter apr_pool_t *pool = apr_hash_pool_get(changes); 5909251881Speter svn_fs_path_change2_t *old_change, *new_change; 5910251881Speter const char *path; 5911251881Speter apr_size_t path_len = strlen(change->path); 5912251881Speter 5913251881Speter if ((old_change = apr_hash_get(changes, change->path, path_len))) 5914251881Speter { 5915251881Speter /* This path already exists in the hash, so we have to merge 5916251881Speter this change into the already existing one. */ 5917251881Speter 5918251881Speter /* Sanity check: only allow NULL node revision ID in the 5919251881Speter `reset' case. */ 5920251881Speter if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset)) 5921251881Speter return svn_error_create 5922251881Speter (SVN_ERR_FS_CORRUPT, NULL, 5923251881Speter _("Missing required node revision ID")); 5924251881Speter 5925251881Speter /* Sanity check: we should be talking about the same node 5926251881Speter revision ID as our last change except where the last change 5927251881Speter was a deletion. */ 5928251881Speter if (change->noderev_id 5929251881Speter && (! svn_fs_fs__id_eq(old_change->node_rev_id, change->noderev_id)) 5930251881Speter && (old_change->change_kind != svn_fs_path_change_delete)) 5931251881Speter return svn_error_create 5932251881Speter (SVN_ERR_FS_CORRUPT, NULL, 5933251881Speter _("Invalid change ordering: new node revision ID " 5934251881Speter "without delete")); 5935251881Speter 5936251881Speter /* Sanity check: an add, replacement, or reset must be the first 5937251881Speter thing to follow a deletion. */ 5938251881Speter if ((old_change->change_kind == svn_fs_path_change_delete) 5939251881Speter && (! ((change->kind == svn_fs_path_change_replace) 5940251881Speter || (change->kind == svn_fs_path_change_reset) 5941251881Speter || (change->kind == svn_fs_path_change_add)))) 5942251881Speter return svn_error_create 5943251881Speter (SVN_ERR_FS_CORRUPT, NULL, 5944251881Speter _("Invalid change ordering: non-add change on deleted path")); 5945251881Speter 5946251881Speter /* Sanity check: an add can't follow anything except 5947251881Speter a delete or reset. */ 5948251881Speter if ((change->kind == svn_fs_path_change_add) 5949251881Speter && (old_change->change_kind != svn_fs_path_change_delete) 5950251881Speter && (old_change->change_kind != svn_fs_path_change_reset)) 5951251881Speter return svn_error_create 5952251881Speter (SVN_ERR_FS_CORRUPT, NULL, 5953251881Speter _("Invalid change ordering: add change on preexisting path")); 5954251881Speter 5955251881Speter /* Now, merge that change in. */ 5956251881Speter switch (change->kind) 5957251881Speter { 5958251881Speter case svn_fs_path_change_reset: 5959251881Speter /* A reset here will simply remove the path change from the 5960251881Speter hash. */ 5961251881Speter old_change = NULL; 5962251881Speter break; 5963251881Speter 5964251881Speter case svn_fs_path_change_delete: 5965251881Speter if (old_change->change_kind == svn_fs_path_change_add) 5966251881Speter { 5967251881Speter /* If the path was introduced in this transaction via an 5968251881Speter add, and we are deleting it, just remove the path 5969251881Speter altogether. */ 5970251881Speter old_change = NULL; 5971251881Speter } 5972251881Speter else 5973251881Speter { 5974251881Speter /* A deletion overrules all previous changes. */ 5975251881Speter old_change->change_kind = svn_fs_path_change_delete; 5976251881Speter old_change->text_mod = change->text_mod; 5977251881Speter old_change->prop_mod = change->prop_mod; 5978251881Speter old_change->copyfrom_rev = SVN_INVALID_REVNUM; 5979251881Speter old_change->copyfrom_path = NULL; 5980251881Speter } 5981251881Speter break; 5982251881Speter 5983251881Speter case svn_fs_path_change_add: 5984251881Speter case svn_fs_path_change_replace: 5985251881Speter /* An add at this point must be following a previous delete, 5986251881Speter so treat it just like a replace. */ 5987251881Speter old_change->change_kind = svn_fs_path_change_replace; 5988251881Speter old_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id, 5989251881Speter pool); 5990251881Speter old_change->text_mod = change->text_mod; 5991251881Speter old_change->prop_mod = change->prop_mod; 5992251881Speter if (change->copyfrom_rev == SVN_INVALID_REVNUM) 5993251881Speter { 5994251881Speter old_change->copyfrom_rev = SVN_INVALID_REVNUM; 5995251881Speter old_change->copyfrom_path = NULL; 5996251881Speter } 5997251881Speter else 5998251881Speter { 5999251881Speter old_change->copyfrom_rev = change->copyfrom_rev; 6000251881Speter old_change->copyfrom_path = apr_pstrdup(pool, 6001251881Speter change->copyfrom_path); 6002251881Speter } 6003251881Speter break; 6004251881Speter 6005251881Speter case svn_fs_path_change_modify: 6006251881Speter default: 6007251881Speter if (change->text_mod) 6008251881Speter old_change->text_mod = TRUE; 6009251881Speter if (change->prop_mod) 6010251881Speter old_change->prop_mod = TRUE; 6011251881Speter break; 6012251881Speter } 6013251881Speter 6014251881Speter /* Point our new_change to our (possibly modified) old_change. */ 6015251881Speter new_change = old_change; 6016251881Speter } 6017251881Speter else 6018251881Speter { 6019251881Speter /* This change is new to the hash, so make a new public change 6020251881Speter structure from the internal one (in the hash's pool), and dup 6021251881Speter the path into the hash's pool, too. */ 6022251881Speter new_change = apr_pcalloc(pool, sizeof(*new_change)); 6023251881Speter new_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id, pool); 6024251881Speter new_change->change_kind = change->kind; 6025251881Speter new_change->text_mod = change->text_mod; 6026251881Speter new_change->prop_mod = change->prop_mod; 6027251881Speter /* In FSFS, copyfrom_known is *always* true, since we've always 6028251881Speter * stored copyfroms in changed paths lists. */ 6029251881Speter new_change->copyfrom_known = TRUE; 6030251881Speter if (change->copyfrom_rev != SVN_INVALID_REVNUM) 6031251881Speter { 6032251881Speter new_change->copyfrom_rev = change->copyfrom_rev; 6033251881Speter new_change->copyfrom_path = apr_pstrdup(pool, change->copyfrom_path); 6034251881Speter } 6035251881Speter else 6036251881Speter { 6037251881Speter new_change->copyfrom_rev = SVN_INVALID_REVNUM; 6038251881Speter new_change->copyfrom_path = NULL; 6039251881Speter } 6040251881Speter } 6041251881Speter 6042251881Speter if (new_change) 6043251881Speter new_change->node_kind = change->node_kind; 6044251881Speter 6045251881Speter /* Add (or update) this path. 6046251881Speter 6047251881Speter Note: this key might already be present, and it would be nice to 6048251881Speter re-use its value, but there is no way to fetch it. The API makes no 6049251881Speter guarantees that this (new) key will not be retained. Thus, we (again) 6050251881Speter copy the key into the target pool to ensure a proper lifetime. */ 6051251881Speter path = apr_pstrmemdup(pool, change->path, path_len); 6052251881Speter apr_hash_set(changes, path, path_len, new_change); 6053251881Speter 6054251881Speter /* Update the copyfrom cache, if any. */ 6055251881Speter if (copyfrom_cache) 6056251881Speter { 6057251881Speter apr_pool_t *copyfrom_pool = apr_hash_pool_get(copyfrom_cache); 6058251881Speter const char *copyfrom_string = NULL, *copyfrom_key = path; 6059251881Speter if (new_change) 6060251881Speter { 6061251881Speter if (SVN_IS_VALID_REVNUM(new_change->copyfrom_rev)) 6062251881Speter copyfrom_string = apr_psprintf(copyfrom_pool, "%ld %s", 6063251881Speter new_change->copyfrom_rev, 6064251881Speter new_change->copyfrom_path); 6065251881Speter else 6066251881Speter copyfrom_string = ""; 6067251881Speter } 6068251881Speter /* We need to allocate a copy of the key in the copyfrom_pool if 6069251881Speter * we're not doing a deletion and if it isn't already there. */ 6070251881Speter if ( copyfrom_string 6071251881Speter && ( ! apr_hash_count(copyfrom_cache) 6072251881Speter || ! apr_hash_get(copyfrom_cache, copyfrom_key, path_len))) 6073251881Speter copyfrom_key = apr_pstrmemdup(copyfrom_pool, copyfrom_key, path_len); 6074251881Speter 6075251881Speter apr_hash_set(copyfrom_cache, copyfrom_key, path_len, 6076251881Speter copyfrom_string); 6077251881Speter } 6078251881Speter 6079251881Speter return SVN_NO_ERROR; 6080251881Speter} 6081251881Speter 6082251881Speter/* The 256 is an arbitrary size large enough to hold the node id and the 6083251881Speter * various flags. */ 6084251881Speter#define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256 6085251881Speter 6086251881Speter/* Read the next entry in the changes record from file FILE and store 6087251881Speter the resulting change in *CHANGE_P. If there is no next record, 6088251881Speter store NULL there. Perform all allocations from POOL. */ 6089251881Speterstatic svn_error_t * 6090251881Speterread_change(change_t **change_p, 6091251881Speter apr_file_t *file, 6092251881Speter apr_pool_t *pool) 6093251881Speter{ 6094251881Speter char buf[MAX_CHANGE_LINE_LEN]; 6095251881Speter apr_size_t len = sizeof(buf); 6096251881Speter change_t *change; 6097251881Speter char *str, *last_str = buf, *kind_str; 6098251881Speter svn_error_t *err; 6099251881Speter 6100251881Speter /* Default return value. */ 6101251881Speter *change_p = NULL; 6102251881Speter 6103251881Speter err = svn_io_read_length_line(file, buf, &len, pool); 6104251881Speter 6105251881Speter /* Check for a blank line. */ 6106251881Speter if (err || (len == 0)) 6107251881Speter { 6108251881Speter if (err && APR_STATUS_IS_EOF(err->apr_err)) 6109251881Speter { 6110251881Speter svn_error_clear(err); 6111251881Speter return SVN_NO_ERROR; 6112251881Speter } 6113251881Speter if ((len == 0) && (! err)) 6114251881Speter return SVN_NO_ERROR; 6115251881Speter return svn_error_trace(err); 6116251881Speter } 6117251881Speter 6118251881Speter change = apr_pcalloc(pool, sizeof(*change)); 6119251881Speter 6120251881Speter /* Get the node-id of the change. */ 6121251881Speter str = svn_cstring_tokenize(" ", &last_str); 6122251881Speter if (str == NULL) 6123251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6124251881Speter _("Invalid changes line in rev-file")); 6125251881Speter 6126251881Speter change->noderev_id = svn_fs_fs__id_parse(str, strlen(str), pool); 6127251881Speter if (change->noderev_id == NULL) 6128251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6129251881Speter _("Invalid changes line in rev-file")); 6130251881Speter 6131251881Speter /* Get the change type. */ 6132251881Speter str = svn_cstring_tokenize(" ", &last_str); 6133251881Speter if (str == NULL) 6134251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6135251881Speter _("Invalid changes line in rev-file")); 6136251881Speter 6137251881Speter /* Don't bother to check the format number before looking for 6138251881Speter * node-kinds: just read them if you find them. */ 6139251881Speter change->node_kind = svn_node_unknown; 6140251881Speter kind_str = strchr(str, '-'); 6141251881Speter if (kind_str) 6142251881Speter { 6143251881Speter /* Cap off the end of "str" (the action). */ 6144251881Speter *kind_str = '\0'; 6145251881Speter kind_str++; 6146251881Speter if (strcmp(kind_str, KIND_FILE) == 0) 6147251881Speter change->node_kind = svn_node_file; 6148251881Speter else if (strcmp(kind_str, KIND_DIR) == 0) 6149251881Speter change->node_kind = svn_node_dir; 6150251881Speter else 6151251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6152251881Speter _("Invalid changes line in rev-file")); 6153251881Speter } 6154251881Speter 6155251881Speter if (strcmp(str, ACTION_MODIFY) == 0) 6156251881Speter { 6157251881Speter change->kind = svn_fs_path_change_modify; 6158251881Speter } 6159251881Speter else if (strcmp(str, ACTION_ADD) == 0) 6160251881Speter { 6161251881Speter change->kind = svn_fs_path_change_add; 6162251881Speter } 6163251881Speter else if (strcmp(str, ACTION_DELETE) == 0) 6164251881Speter { 6165251881Speter change->kind = svn_fs_path_change_delete; 6166251881Speter } 6167251881Speter else if (strcmp(str, ACTION_REPLACE) == 0) 6168251881Speter { 6169251881Speter change->kind = svn_fs_path_change_replace; 6170251881Speter } 6171251881Speter else if (strcmp(str, ACTION_RESET) == 0) 6172251881Speter { 6173251881Speter change->kind = svn_fs_path_change_reset; 6174251881Speter } 6175251881Speter else 6176251881Speter { 6177251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6178251881Speter _("Invalid change kind in rev file")); 6179251881Speter } 6180251881Speter 6181251881Speter /* Get the text-mod flag. */ 6182251881Speter str = svn_cstring_tokenize(" ", &last_str); 6183251881Speter if (str == NULL) 6184251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6185251881Speter _("Invalid changes line in rev-file")); 6186251881Speter 6187251881Speter if (strcmp(str, FLAG_TRUE) == 0) 6188251881Speter { 6189251881Speter change->text_mod = TRUE; 6190251881Speter } 6191251881Speter else if (strcmp(str, FLAG_FALSE) == 0) 6192251881Speter { 6193251881Speter change->text_mod = FALSE; 6194251881Speter } 6195251881Speter else 6196251881Speter { 6197251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6198251881Speter _("Invalid text-mod flag in rev-file")); 6199251881Speter } 6200251881Speter 6201251881Speter /* Get the prop-mod flag. */ 6202251881Speter str = svn_cstring_tokenize(" ", &last_str); 6203251881Speter if (str == NULL) 6204251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6205251881Speter _("Invalid changes line in rev-file")); 6206251881Speter 6207251881Speter if (strcmp(str, FLAG_TRUE) == 0) 6208251881Speter { 6209251881Speter change->prop_mod = TRUE; 6210251881Speter } 6211251881Speter else if (strcmp(str, FLAG_FALSE) == 0) 6212251881Speter { 6213251881Speter change->prop_mod = FALSE; 6214251881Speter } 6215251881Speter else 6216251881Speter { 6217251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6218251881Speter _("Invalid prop-mod flag in rev-file")); 6219251881Speter } 6220251881Speter 6221251881Speter /* Get the changed path. */ 6222251881Speter change->path = apr_pstrdup(pool, last_str); 6223251881Speter 6224251881Speter 6225251881Speter /* Read the next line, the copyfrom line. */ 6226251881Speter len = sizeof(buf); 6227251881Speter SVN_ERR(svn_io_read_length_line(file, buf, &len, pool)); 6228251881Speter 6229251881Speter if (len == 0) 6230251881Speter { 6231251881Speter change->copyfrom_rev = SVN_INVALID_REVNUM; 6232251881Speter change->copyfrom_path = NULL; 6233251881Speter } 6234251881Speter else 6235251881Speter { 6236251881Speter last_str = buf; 6237251881Speter str = svn_cstring_tokenize(" ", &last_str); 6238251881Speter if (! str) 6239251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6240251881Speter _("Invalid changes line in rev-file")); 6241251881Speter change->copyfrom_rev = SVN_STR_TO_REV(str); 6242251881Speter 6243251881Speter if (! last_str) 6244251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6245251881Speter _("Invalid changes line in rev-file")); 6246251881Speter 6247251881Speter change->copyfrom_path = apr_pstrdup(pool, last_str); 6248251881Speter } 6249251881Speter 6250251881Speter *change_p = change; 6251251881Speter 6252251881Speter return SVN_NO_ERROR; 6253251881Speter} 6254251881Speter 6255251881Speter/* Examine all the changed path entries in CHANGES and store them in 6256251881Speter *CHANGED_PATHS. Folding is done to remove redundant or unnecessary 6257251881Speter *data. Store a hash of paths to copyfrom "REV PATH" strings in 6258251881Speter COPYFROM_HASH if it is non-NULL. If PREFOLDED is true, assume that 6259251881Speter the changed-path entries have already been folded (by 6260251881Speter write_final_changed_path_info) and may be out of order, so we shouldn't 6261251881Speter remove children of replaced or deleted directories. Do all 6262251881Speter allocations in POOL. */ 6263251881Speterstatic svn_error_t * 6264251881Speterprocess_changes(apr_hash_t *changed_paths, 6265251881Speter apr_hash_t *copyfrom_cache, 6266251881Speter apr_array_header_t *changes, 6267251881Speter svn_boolean_t prefolded, 6268251881Speter apr_pool_t *pool) 6269251881Speter{ 6270251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 6271251881Speter int i; 6272251881Speter 6273251881Speter /* Read in the changes one by one, folding them into our local hash 6274251881Speter as necessary. */ 6275251881Speter 6276251881Speter for (i = 0; i < changes->nelts; ++i) 6277251881Speter { 6278251881Speter change_t *change = APR_ARRAY_IDX(changes, i, change_t *); 6279251881Speter 6280251881Speter SVN_ERR(fold_change(changed_paths, change, copyfrom_cache)); 6281251881Speter 6282251881Speter /* Now, if our change was a deletion or replacement, we have to 6283251881Speter blow away any changes thus far on paths that are (or, were) 6284251881Speter children of this path. 6285251881Speter ### i won't bother with another iteration pool here -- at 6286251881Speter most we talking about a few extra dups of paths into what 6287251881Speter is already a temporary subpool. 6288251881Speter */ 6289251881Speter 6290251881Speter if (((change->kind == svn_fs_path_change_delete) 6291251881Speter || (change->kind == svn_fs_path_change_replace)) 6292251881Speter && ! prefolded) 6293251881Speter { 6294251881Speter apr_hash_index_t *hi; 6295251881Speter 6296251881Speter /* a potential child path must contain at least 2 more chars 6297251881Speter (the path separator plus at least one char for the name). 6298251881Speter Also, we should not assume that all paths have been normalized 6299251881Speter i.e. some might have trailing path separators. 6300251881Speter */ 6301251881Speter apr_ssize_t change_path_len = strlen(change->path); 6302251881Speter apr_ssize_t min_child_len = change_path_len == 0 6303251881Speter ? 1 6304251881Speter : change->path[change_path_len-1] == '/' 6305251881Speter ? change_path_len + 1 6306251881Speter : change_path_len + 2; 6307251881Speter 6308251881Speter /* CAUTION: This is the inner loop of an O(n^2) algorithm. 6309251881Speter The number of changes to process may be >> 1000. 6310251881Speter Therefore, keep the inner loop as tight as possible. 6311251881Speter */ 6312251881Speter for (hi = apr_hash_first(iterpool, changed_paths); 6313251881Speter hi; 6314251881Speter hi = apr_hash_next(hi)) 6315251881Speter { 6316251881Speter /* KEY is the path. */ 6317251881Speter const void *path; 6318251881Speter apr_ssize_t klen; 6319251881Speter apr_hash_this(hi, &path, &klen, NULL); 6320251881Speter 6321251881Speter /* If we come across a child of our path, remove it. 6322251881Speter Call svn_dirent_is_child only if there is a chance that 6323251881Speter this is actually a sub-path. 6324251881Speter */ 6325251881Speter if ( klen >= min_child_len 6326251881Speter && svn_dirent_is_child(change->path, path, iterpool)) 6327251881Speter apr_hash_set(changed_paths, path, klen, NULL); 6328251881Speter } 6329251881Speter } 6330251881Speter 6331251881Speter /* Clear the per-iteration subpool. */ 6332251881Speter svn_pool_clear(iterpool); 6333251881Speter } 6334251881Speter 6335251881Speter /* Destroy the per-iteration subpool. */ 6336251881Speter svn_pool_destroy(iterpool); 6337251881Speter 6338251881Speter return SVN_NO_ERROR; 6339251881Speter} 6340251881Speter 6341251881Speter/* Fetch all the changes from FILE and store them in *CHANGES. Do all 6342251881Speter allocations in POOL. */ 6343251881Speterstatic svn_error_t * 6344251881Speterread_all_changes(apr_array_header_t **changes, 6345251881Speter apr_file_t *file, 6346251881Speter apr_pool_t *pool) 6347251881Speter{ 6348251881Speter change_t *change; 6349251881Speter 6350251881Speter /* pre-allocate enough room for most change lists 6351251881Speter (will be auto-expanded as necessary) */ 6352251881Speter *changes = apr_array_make(pool, 30, sizeof(change_t *)); 6353251881Speter 6354251881Speter SVN_ERR(read_change(&change, file, pool)); 6355251881Speter while (change) 6356251881Speter { 6357251881Speter APR_ARRAY_PUSH(*changes, change_t*) = change; 6358251881Speter SVN_ERR(read_change(&change, file, pool)); 6359251881Speter } 6360251881Speter 6361251881Speter return SVN_NO_ERROR; 6362251881Speter} 6363251881Speter 6364251881Spetersvn_error_t * 6365251881Spetersvn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p, 6366251881Speter svn_fs_t *fs, 6367251881Speter const char *txn_id, 6368251881Speter apr_pool_t *pool) 6369251881Speter{ 6370251881Speter apr_file_t *file; 6371251881Speter apr_hash_t *changed_paths = apr_hash_make(pool); 6372251881Speter apr_array_header_t *changes; 6373251881Speter apr_pool_t *scratch_pool = svn_pool_create(pool); 6374251881Speter 6375251881Speter SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool), 6376251881Speter APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool)); 6377251881Speter 6378251881Speter SVN_ERR(read_all_changes(&changes, file, scratch_pool)); 6379251881Speter SVN_ERR(process_changes(changed_paths, NULL, changes, FALSE, pool)); 6380251881Speter svn_pool_destroy(scratch_pool); 6381251881Speter 6382251881Speter SVN_ERR(svn_io_file_close(file, pool)); 6383251881Speter 6384251881Speter *changed_paths_p = changed_paths; 6385251881Speter 6386251881Speter return SVN_NO_ERROR; 6387251881Speter} 6388251881Speter 6389251881Speter/* Fetch the list of change in revision REV in FS and return it in *CHANGES. 6390251881Speter * Allocate the result in POOL. 6391251881Speter */ 6392251881Speterstatic svn_error_t * 6393251881Speterget_changes(apr_array_header_t **changes, 6394251881Speter svn_fs_t *fs, 6395251881Speter svn_revnum_t rev, 6396251881Speter apr_pool_t *pool) 6397251881Speter{ 6398251881Speter apr_off_t changes_offset; 6399251881Speter apr_file_t *revision_file; 6400251881Speter svn_boolean_t found; 6401251881Speter fs_fs_data_t *ffd = fs->fsap_data; 6402251881Speter 6403251881Speter /* try cache lookup first */ 6404251881Speter 6405251881Speter if (ffd->changes_cache) 6406251881Speter { 6407251881Speter SVN_ERR(svn_cache__get((void **) changes, &found, ffd->changes_cache, 6408251881Speter &rev, pool)); 6409251881Speter if (found) 6410251881Speter return SVN_NO_ERROR; 6411251881Speter } 6412251881Speter 6413251881Speter /* read changes from revision file */ 6414251881Speter 6415251881Speter SVN_ERR(ensure_revision_exists(fs, rev, pool)); 6416251881Speter 6417251881Speter SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool)); 6418251881Speter 6419251881Speter SVN_ERR(get_root_changes_offset(NULL, &changes_offset, revision_file, fs, 6420251881Speter rev, pool)); 6421251881Speter 6422251881Speter SVN_ERR(svn_io_file_seek(revision_file, APR_SET, &changes_offset, pool)); 6423251881Speter SVN_ERR(read_all_changes(changes, revision_file, pool)); 6424251881Speter 6425251881Speter SVN_ERR(svn_io_file_close(revision_file, pool)); 6426251881Speter 6427251881Speter /* cache for future reference */ 6428251881Speter 6429251881Speter if (ffd->changes_cache) 6430251881Speter SVN_ERR(svn_cache__set(ffd->changes_cache, &rev, *changes, pool)); 6431251881Speter 6432251881Speter return SVN_NO_ERROR; 6433251881Speter} 6434251881Speter 6435251881Speter 6436251881Spetersvn_error_t * 6437251881Spetersvn_fs_fs__paths_changed(apr_hash_t **changed_paths_p, 6438251881Speter svn_fs_t *fs, 6439251881Speter svn_revnum_t rev, 6440251881Speter apr_hash_t *copyfrom_cache, 6441251881Speter apr_pool_t *pool) 6442251881Speter{ 6443251881Speter apr_hash_t *changed_paths; 6444251881Speter apr_array_header_t *changes; 6445251881Speter apr_pool_t *scratch_pool = svn_pool_create(pool); 6446251881Speter 6447251881Speter SVN_ERR(get_changes(&changes, fs, rev, scratch_pool)); 6448251881Speter 6449251881Speter changed_paths = svn_hash__make(pool); 6450251881Speter 6451251881Speter SVN_ERR(process_changes(changed_paths, copyfrom_cache, changes, 6452251881Speter TRUE, pool)); 6453251881Speter svn_pool_destroy(scratch_pool); 6454251881Speter 6455251881Speter *changed_paths_p = changed_paths; 6456251881Speter 6457251881Speter return SVN_NO_ERROR; 6458251881Speter} 6459251881Speter 6460251881Speter/* Copy a revision node-rev SRC into the current transaction TXN_ID in 6461251881Speter the filesystem FS. This is only used to create the root of a transaction. 6462251881Speter Allocations are from POOL. */ 6463251881Speterstatic svn_error_t * 6464251881Spetercreate_new_txn_noderev_from_rev(svn_fs_t *fs, 6465251881Speter const char *txn_id, 6466251881Speter svn_fs_id_t *src, 6467251881Speter apr_pool_t *pool) 6468251881Speter{ 6469251881Speter node_revision_t *noderev; 6470251881Speter const char *node_id, *copy_id; 6471251881Speter 6472251881Speter SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool)); 6473251881Speter 6474251881Speter if (svn_fs_fs__id_txn_id(noderev->id)) 6475251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6476251881Speter _("Copying from transactions not allowed")); 6477251881Speter 6478251881Speter noderev->predecessor_id = noderev->id; 6479251881Speter noderev->predecessor_count++; 6480251881Speter noderev->copyfrom_path = NULL; 6481251881Speter noderev->copyfrom_rev = SVN_INVALID_REVNUM; 6482251881Speter 6483251881Speter /* For the transaction root, the copyroot never changes. */ 6484251881Speter 6485251881Speter node_id = svn_fs_fs__id_node_id(noderev->id); 6486251881Speter copy_id = svn_fs_fs__id_copy_id(noderev->id); 6487251881Speter noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool); 6488251881Speter 6489251881Speter return svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool); 6490251881Speter} 6491251881Speter 6492251881Speter/* A structure used by get_and_increment_txn_key_body(). */ 6493251881Speterstruct get_and_increment_txn_key_baton { 6494251881Speter svn_fs_t *fs; 6495251881Speter char *txn_id; 6496251881Speter apr_pool_t *pool; 6497251881Speter}; 6498251881Speter 6499251881Speter/* Callback used in the implementation of create_txn_dir(). This gets 6500251881Speter the current base 36 value in PATH_TXN_CURRENT and increments it. 6501251881Speter It returns the original value by the baton. */ 6502251881Speterstatic svn_error_t * 6503251881Speterget_and_increment_txn_key_body(void *baton, apr_pool_t *pool) 6504251881Speter{ 6505251881Speter struct get_and_increment_txn_key_baton *cb = baton; 6506251881Speter const char *txn_current_filename = path_txn_current(cb->fs, pool); 6507251881Speter const char *tmp_filename; 6508251881Speter char next_txn_id[MAX_KEY_SIZE+3]; 6509251881Speter apr_size_t len; 6510251881Speter 6511251881Speter svn_stringbuf_t *buf; 6512251881Speter SVN_ERR(read_content(&buf, txn_current_filename, cb->pool)); 6513251881Speter 6514251881Speter /* remove trailing newlines */ 6515251881Speter svn_stringbuf_strip_whitespace(buf); 6516251881Speter cb->txn_id = buf->data; 6517251881Speter len = buf->len; 6518251881Speter 6519251881Speter /* Increment the key and add a trailing \n to the string so the 6520251881Speter txn-current file has a newline in it. */ 6521251881Speter svn_fs_fs__next_key(cb->txn_id, &len, next_txn_id); 6522251881Speter next_txn_id[len] = '\n'; 6523251881Speter ++len; 6524251881Speter next_txn_id[len] = '\0'; 6525251881Speter 6526251881Speter SVN_ERR(svn_io_write_unique(&tmp_filename, 6527251881Speter svn_dirent_dirname(txn_current_filename, pool), 6528251881Speter next_txn_id, len, svn_io_file_del_none, pool)); 6529251881Speter SVN_ERR(move_into_place(tmp_filename, txn_current_filename, 6530251881Speter txn_current_filename, pool)); 6531251881Speter 6532251881Speter return SVN_NO_ERROR; 6533251881Speter} 6534251881Speter 6535251881Speter/* Create a unique directory for a transaction in FS based on revision 6536251881Speter REV. Return the ID for this transaction in *ID_P. Use a sequence 6537251881Speter value in the transaction ID to prevent reuse of transaction IDs. */ 6538251881Speterstatic svn_error_t * 6539251881Spetercreate_txn_dir(const char **id_p, svn_fs_t *fs, svn_revnum_t rev, 6540251881Speter apr_pool_t *pool) 6541251881Speter{ 6542251881Speter struct get_and_increment_txn_key_baton cb; 6543251881Speter const char *txn_dir; 6544251881Speter 6545251881Speter /* Get the current transaction sequence value, which is a base-36 6546251881Speter number, from the txn-current file, and write an 6547251881Speter incremented value back out to the file. Place the revision 6548251881Speter number the transaction is based off into the transaction id. */ 6549251881Speter cb.pool = pool; 6550251881Speter cb.fs = fs; 6551251881Speter SVN_ERR(with_txn_current_lock(fs, 6552251881Speter get_and_increment_txn_key_body, 6553251881Speter &cb, 6554251881Speter pool)); 6555251881Speter *id_p = apr_psprintf(pool, "%ld-%s", rev, cb.txn_id); 6556251881Speter 6557251881Speter txn_dir = svn_dirent_join_many(pool, 6558251881Speter fs->path, 6559251881Speter PATH_TXNS_DIR, 6560251881Speter apr_pstrcat(pool, *id_p, PATH_EXT_TXN, 6561251881Speter (char *)NULL), 6562251881Speter NULL); 6563251881Speter 6564251881Speter return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool); 6565251881Speter} 6566251881Speter 6567251881Speter/* Create a unique directory for a transaction in FS based on revision 6568251881Speter REV. Return the ID for this transaction in *ID_P. This 6569251881Speter implementation is used in svn 1.4 and earlier repositories and is 6570251881Speter kept in 1.5 and greater to support the --pre-1.4-compatible and 6571251881Speter --pre-1.5-compatible repository creation options. Reused 6572251881Speter transaction IDs are possible with this implementation. */ 6573251881Speterstatic svn_error_t * 6574251881Spetercreate_txn_dir_pre_1_5(const char **id_p, svn_fs_t *fs, svn_revnum_t rev, 6575251881Speter apr_pool_t *pool) 6576251881Speter{ 6577251881Speter unsigned int i; 6578251881Speter apr_pool_t *subpool; 6579251881Speter const char *unique_path, *prefix; 6580251881Speter 6581251881Speter /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */ 6582251881Speter prefix = svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR, 6583251881Speter apr_psprintf(pool, "%ld", rev), NULL); 6584251881Speter 6585251881Speter subpool = svn_pool_create(pool); 6586251881Speter for (i = 1; i <= 99999; i++) 6587251881Speter { 6588251881Speter svn_error_t *err; 6589251881Speter 6590251881Speter svn_pool_clear(subpool); 6591251881Speter unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i); 6592251881Speter err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool); 6593251881Speter if (! err) 6594251881Speter { 6595251881Speter /* We succeeded. Return the basename minus the ".txn" extension. */ 6596251881Speter const char *name = svn_dirent_basename(unique_path, subpool); 6597251881Speter *id_p = apr_pstrndup(pool, name, 6598251881Speter strlen(name) - strlen(PATH_EXT_TXN)); 6599251881Speter svn_pool_destroy(subpool); 6600251881Speter return SVN_NO_ERROR; 6601251881Speter } 6602251881Speter if (! APR_STATUS_IS_EEXIST(err->apr_err)) 6603251881Speter return svn_error_trace(err); 6604251881Speter svn_error_clear(err); 6605251881Speter } 6606251881Speter 6607251881Speter return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED, 6608251881Speter NULL, 6609251881Speter _("Unable to create transaction directory " 6610251881Speter "in '%s' for revision %ld"), 6611251881Speter svn_dirent_local_style(fs->path, pool), 6612251881Speter rev); 6613251881Speter} 6614251881Speter 6615251881Spetersvn_error_t * 6616251881Spetersvn_fs_fs__create_txn(svn_fs_txn_t **txn_p, 6617251881Speter svn_fs_t *fs, 6618251881Speter svn_revnum_t rev, 6619251881Speter apr_pool_t *pool) 6620251881Speter{ 6621251881Speter fs_fs_data_t *ffd = fs->fsap_data; 6622251881Speter svn_fs_txn_t *txn; 6623251881Speter svn_fs_id_t *root_id; 6624251881Speter 6625251881Speter txn = apr_pcalloc(pool, sizeof(*txn)); 6626251881Speter 6627251881Speter /* Get the txn_id. */ 6628251881Speter if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 6629251881Speter SVN_ERR(create_txn_dir(&txn->id, fs, rev, pool)); 6630251881Speter else 6631251881Speter SVN_ERR(create_txn_dir_pre_1_5(&txn->id, fs, rev, pool)); 6632251881Speter 6633251881Speter txn->fs = fs; 6634251881Speter txn->base_rev = rev; 6635251881Speter 6636251881Speter txn->vtable = &txn_vtable; 6637251881Speter *txn_p = txn; 6638251881Speter 6639251881Speter /* Create a new root node for this transaction. */ 6640251881Speter SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool)); 6641251881Speter SVN_ERR(create_new_txn_noderev_from_rev(fs, txn->id, root_id, pool)); 6642251881Speter 6643251881Speter /* Create an empty rev file. */ 6644251881Speter SVN_ERR(svn_io_file_create(path_txn_proto_rev(fs, txn->id, pool), "", 6645251881Speter pool)); 6646251881Speter 6647251881Speter /* Create an empty rev-lock file. */ 6648251881Speter SVN_ERR(svn_io_file_create(path_txn_proto_rev_lock(fs, txn->id, pool), "", 6649251881Speter pool)); 6650251881Speter 6651251881Speter /* Create an empty changes file. */ 6652251881Speter SVN_ERR(svn_io_file_create(path_txn_changes(fs, txn->id, pool), "", 6653251881Speter pool)); 6654251881Speter 6655251881Speter /* Create the next-ids file. */ 6656251881Speter return svn_io_file_create(path_txn_next_ids(fs, txn->id, pool), "0 0\n", 6657251881Speter pool); 6658251881Speter} 6659251881Speter 6660251881Speter/* Store the property list for transaction TXN_ID in PROPLIST. 6661251881Speter Perform temporary allocations in POOL. */ 6662251881Speterstatic svn_error_t * 6663251881Speterget_txn_proplist(apr_hash_t *proplist, 6664251881Speter svn_fs_t *fs, 6665251881Speter const char *txn_id, 6666251881Speter apr_pool_t *pool) 6667251881Speter{ 6668251881Speter svn_stream_t *stream; 6669251881Speter 6670251881Speter /* Check for issue #3696. (When we find and fix the cause, we can change 6671251881Speter * this to an assertion.) */ 6672251881Speter if (txn_id == NULL) 6673251881Speter return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 6674251881Speter _("Internal error: a null transaction id was " 6675251881Speter "passed to get_txn_proplist()")); 6676251881Speter 6677251881Speter /* Open the transaction properties file. */ 6678251881Speter SVN_ERR(svn_stream_open_readonly(&stream, path_txn_props(fs, txn_id, pool), 6679251881Speter pool, pool)); 6680251881Speter 6681251881Speter /* Read in the property list. */ 6682251881Speter SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool)); 6683251881Speter 6684251881Speter return svn_stream_close(stream); 6685251881Speter} 6686251881Speter 6687251881Spetersvn_error_t * 6688251881Spetersvn_fs_fs__change_txn_prop(svn_fs_txn_t *txn, 6689251881Speter const char *name, 6690251881Speter const svn_string_t *value, 6691251881Speter apr_pool_t *pool) 6692251881Speter{ 6693251881Speter apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t)); 6694251881Speter svn_prop_t prop; 6695251881Speter 6696251881Speter prop.name = name; 6697251881Speter prop.value = value; 6698251881Speter APR_ARRAY_PUSH(props, svn_prop_t) = prop; 6699251881Speter 6700251881Speter return svn_fs_fs__change_txn_props(txn, props, pool); 6701251881Speter} 6702251881Speter 6703251881Spetersvn_error_t * 6704251881Spetersvn_fs_fs__change_txn_props(svn_fs_txn_t *txn, 6705251881Speter const apr_array_header_t *props, 6706251881Speter apr_pool_t *pool) 6707251881Speter{ 6708251881Speter const char *txn_prop_filename; 6709251881Speter svn_stringbuf_t *buf; 6710251881Speter svn_stream_t *stream; 6711251881Speter apr_hash_t *txn_prop = apr_hash_make(pool); 6712251881Speter int i; 6713251881Speter svn_error_t *err; 6714251881Speter 6715251881Speter err = get_txn_proplist(txn_prop, txn->fs, txn->id, pool); 6716251881Speter /* Here - and here only - we need to deal with the possibility that the 6717251881Speter transaction property file doesn't yet exist. The rest of the 6718251881Speter implementation assumes that the file exists, but we're called to set the 6719251881Speter initial transaction properties as the transaction is being created. */ 6720251881Speter if (err && (APR_STATUS_IS_ENOENT(err->apr_err))) 6721251881Speter svn_error_clear(err); 6722251881Speter else if (err) 6723251881Speter return svn_error_trace(err); 6724251881Speter 6725251881Speter for (i = 0; i < props->nelts; i++) 6726251881Speter { 6727251881Speter svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t); 6728251881Speter 6729251881Speter svn_hash_sets(txn_prop, prop->name, prop->value); 6730251881Speter } 6731251881Speter 6732251881Speter /* Create a new version of the file and write out the new props. */ 6733251881Speter /* Open the transaction properties file. */ 6734251881Speter buf = svn_stringbuf_create_ensure(1024, pool); 6735251881Speter stream = svn_stream_from_stringbuf(buf, pool); 6736251881Speter SVN_ERR(svn_hash_write2(txn_prop, stream, SVN_HASH_TERMINATOR, pool)); 6737251881Speter SVN_ERR(svn_stream_close(stream)); 6738251881Speter SVN_ERR(svn_io_write_unique(&txn_prop_filename, 6739251881Speter path_txn_dir(txn->fs, txn->id, pool), 6740251881Speter buf->data, 6741251881Speter buf->len, 6742251881Speter svn_io_file_del_none, 6743251881Speter pool)); 6744251881Speter return svn_io_file_rename(txn_prop_filename, 6745251881Speter path_txn_props(txn->fs, txn->id, pool), 6746251881Speter pool); 6747251881Speter} 6748251881Speter 6749251881Spetersvn_error_t * 6750251881Spetersvn_fs_fs__get_txn(transaction_t **txn_p, 6751251881Speter svn_fs_t *fs, 6752251881Speter const char *txn_id, 6753251881Speter apr_pool_t *pool) 6754251881Speter{ 6755251881Speter transaction_t *txn; 6756251881Speter node_revision_t *noderev; 6757251881Speter svn_fs_id_t *root_id; 6758251881Speter 6759251881Speter txn = apr_pcalloc(pool, sizeof(*txn)); 6760251881Speter txn->proplist = apr_hash_make(pool); 6761251881Speter 6762251881Speter SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool)); 6763251881Speter root_id = svn_fs_fs__id_txn_create("0", "0", txn_id, pool); 6764251881Speter 6765251881Speter SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool)); 6766251881Speter 6767251881Speter txn->root_id = svn_fs_fs__id_copy(noderev->id, pool); 6768251881Speter txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool); 6769251881Speter txn->copies = NULL; 6770251881Speter 6771251881Speter *txn_p = txn; 6772251881Speter 6773251881Speter return SVN_NO_ERROR; 6774251881Speter} 6775251881Speter 6776251881Speter/* Write out the currently available next node_id NODE_ID and copy_id 6777251881Speter COPY_ID for transaction TXN_ID in filesystem FS. The next node-id is 6778251881Speter used both for creating new unique nodes for the given transaction, as 6779251881Speter well as uniquifying representations. Perform temporary allocations in 6780251881Speter POOL. */ 6781251881Speterstatic svn_error_t * 6782251881Speterwrite_next_ids(svn_fs_t *fs, 6783251881Speter const char *txn_id, 6784251881Speter const char *node_id, 6785251881Speter const char *copy_id, 6786251881Speter apr_pool_t *pool) 6787251881Speter{ 6788251881Speter apr_file_t *file; 6789251881Speter svn_stream_t *out_stream; 6790251881Speter 6791251881Speter SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool), 6792251881Speter APR_WRITE | APR_TRUNCATE, 6793251881Speter APR_OS_DEFAULT, pool)); 6794251881Speter 6795251881Speter out_stream = svn_stream_from_aprfile2(file, TRUE, pool); 6796251881Speter 6797251881Speter SVN_ERR(svn_stream_printf(out_stream, pool, "%s %s\n", node_id, copy_id)); 6798251881Speter 6799251881Speter SVN_ERR(svn_stream_close(out_stream)); 6800251881Speter return svn_io_file_close(file, pool); 6801251881Speter} 6802251881Speter 6803251881Speter/* Find out what the next unique node-id and copy-id are for 6804251881Speter transaction TXN_ID in filesystem FS. Store the results in *NODE_ID 6805251881Speter and *COPY_ID. The next node-id is used both for creating new unique 6806251881Speter nodes for the given transaction, as well as uniquifying representations. 6807251881Speter Perform all allocations in POOL. */ 6808251881Speterstatic svn_error_t * 6809251881Speterread_next_ids(const char **node_id, 6810251881Speter const char **copy_id, 6811251881Speter svn_fs_t *fs, 6812251881Speter const char *txn_id, 6813251881Speter apr_pool_t *pool) 6814251881Speter{ 6815251881Speter apr_file_t *file; 6816251881Speter char buf[MAX_KEY_SIZE*2+3]; 6817251881Speter apr_size_t limit; 6818251881Speter char *str, *last_str = buf; 6819251881Speter 6820251881Speter SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool), 6821251881Speter APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool)); 6822251881Speter 6823251881Speter limit = sizeof(buf); 6824251881Speter SVN_ERR(svn_io_read_length_line(file, buf, &limit, pool)); 6825251881Speter 6826251881Speter SVN_ERR(svn_io_file_close(file, pool)); 6827251881Speter 6828251881Speter /* Parse this into two separate strings. */ 6829251881Speter 6830251881Speter str = svn_cstring_tokenize(" ", &last_str); 6831251881Speter if (! str) 6832251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6833251881Speter _("next-id file corrupt")); 6834251881Speter 6835251881Speter *node_id = apr_pstrdup(pool, str); 6836251881Speter 6837251881Speter str = svn_cstring_tokenize(" ", &last_str); 6838251881Speter if (! str) 6839251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 6840251881Speter _("next-id file corrupt")); 6841251881Speter 6842251881Speter *copy_id = apr_pstrdup(pool, str); 6843251881Speter 6844251881Speter return SVN_NO_ERROR; 6845251881Speter} 6846251881Speter 6847251881Speter/* Get a new and unique to this transaction node-id for transaction 6848251881Speter TXN_ID in filesystem FS. Store the new node-id in *NODE_ID_P. 6849251881Speter Node-ids are guaranteed to be unique to this transction, but may 6850251881Speter not necessarily be sequential. Perform all allocations in POOL. */ 6851251881Speterstatic svn_error_t * 6852251881Speterget_new_txn_node_id(const char **node_id_p, 6853251881Speter svn_fs_t *fs, 6854251881Speter const char *txn_id, 6855251881Speter apr_pool_t *pool) 6856251881Speter{ 6857251881Speter const char *cur_node_id, *cur_copy_id; 6858251881Speter char *node_id; 6859251881Speter apr_size_t len; 6860251881Speter 6861251881Speter /* First read in the current next-ids file. */ 6862251881Speter SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool)); 6863251881Speter 6864251881Speter node_id = apr_pcalloc(pool, strlen(cur_node_id) + 2); 6865251881Speter 6866251881Speter len = strlen(cur_node_id); 6867251881Speter svn_fs_fs__next_key(cur_node_id, &len, node_id); 6868251881Speter 6869251881Speter SVN_ERR(write_next_ids(fs, txn_id, node_id, cur_copy_id, pool)); 6870251881Speter 6871251881Speter *node_id_p = apr_pstrcat(pool, "_", cur_node_id, (char *)NULL); 6872251881Speter 6873251881Speter return SVN_NO_ERROR; 6874251881Speter} 6875251881Speter 6876251881Spetersvn_error_t * 6877251881Spetersvn_fs_fs__create_node(const svn_fs_id_t **id_p, 6878251881Speter svn_fs_t *fs, 6879251881Speter node_revision_t *noderev, 6880251881Speter const char *copy_id, 6881251881Speter const char *txn_id, 6882251881Speter apr_pool_t *pool) 6883251881Speter{ 6884251881Speter const char *node_id; 6885251881Speter const svn_fs_id_t *id; 6886251881Speter 6887251881Speter /* Get a new node-id for this node. */ 6888251881Speter SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool)); 6889251881Speter 6890251881Speter id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool); 6891251881Speter 6892251881Speter noderev->id = id; 6893251881Speter 6894251881Speter SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool)); 6895251881Speter 6896251881Speter *id_p = id; 6897251881Speter 6898251881Speter return SVN_NO_ERROR; 6899251881Speter} 6900251881Speter 6901251881Spetersvn_error_t * 6902251881Spetersvn_fs_fs__purge_txn(svn_fs_t *fs, 6903251881Speter const char *txn_id, 6904251881Speter apr_pool_t *pool) 6905251881Speter{ 6906251881Speter fs_fs_data_t *ffd = fs->fsap_data; 6907251881Speter 6908251881Speter /* Remove the shared transaction object associated with this transaction. */ 6909251881Speter SVN_ERR(purge_shared_txn(fs, txn_id, pool)); 6910251881Speter /* Remove the directory associated with this transaction. */ 6911251881Speter SVN_ERR(svn_io_remove_dir2(path_txn_dir(fs, txn_id, pool), FALSE, 6912251881Speter NULL, NULL, pool)); 6913251881Speter if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) 6914251881Speter { 6915251881Speter /* Delete protorev and its lock, which aren't in the txn 6916251881Speter directory. It's OK if they don't exist (for example, if this 6917251881Speter is post-commit and the proto-rev has been moved into 6918251881Speter place). */ 6919251881Speter SVN_ERR(svn_io_remove_file2(path_txn_proto_rev(fs, txn_id, pool), 6920251881Speter TRUE, pool)); 6921251881Speter SVN_ERR(svn_io_remove_file2(path_txn_proto_rev_lock(fs, txn_id, pool), 6922251881Speter TRUE, pool)); 6923251881Speter } 6924251881Speter return SVN_NO_ERROR; 6925251881Speter} 6926251881Speter 6927251881Speter 6928251881Spetersvn_error_t * 6929251881Spetersvn_fs_fs__abort_txn(svn_fs_txn_t *txn, 6930251881Speter apr_pool_t *pool) 6931251881Speter{ 6932251881Speter SVN_ERR(svn_fs__check_fs(txn->fs, TRUE)); 6933251881Speter 6934251881Speter /* Now, purge the transaction. */ 6935251881Speter SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool), 6936251881Speter apr_psprintf(pool, _("Transaction '%s' cleanup failed"), 6937251881Speter txn->id)); 6938251881Speter 6939251881Speter return SVN_NO_ERROR; 6940251881Speter} 6941251881Speter 6942251881Speter 6943251881Spetersvn_error_t * 6944251881Spetersvn_fs_fs__set_entry(svn_fs_t *fs, 6945251881Speter const char *txn_id, 6946251881Speter node_revision_t *parent_noderev, 6947251881Speter const char *name, 6948251881Speter const svn_fs_id_t *id, 6949251881Speter svn_node_kind_t kind, 6950251881Speter apr_pool_t *pool) 6951251881Speter{ 6952251881Speter representation_t *rep = parent_noderev->data_rep; 6953251881Speter const char *filename = path_txn_node_children(fs, parent_noderev->id, pool); 6954251881Speter apr_file_t *file; 6955251881Speter svn_stream_t *out; 6956251881Speter fs_fs_data_t *ffd = fs->fsap_data; 6957251881Speter apr_pool_t *subpool = svn_pool_create(pool); 6958251881Speter 6959251881Speter if (!rep || !rep->txn_id) 6960251881Speter { 6961251881Speter const char *unique_suffix; 6962251881Speter apr_hash_t *entries; 6963251881Speter 6964251881Speter /* Before we can modify the directory, we need to dump its old 6965251881Speter contents into a mutable representation file. */ 6966251881Speter SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev, 6967251881Speter subpool)); 6968251881Speter SVN_ERR(unparse_dir_entries(&entries, entries, subpool)); 6969251881Speter SVN_ERR(svn_io_file_open(&file, filename, 6970251881Speter APR_WRITE | APR_CREATE | APR_BUFFERED, 6971251881Speter APR_OS_DEFAULT, pool)); 6972251881Speter out = svn_stream_from_aprfile2(file, TRUE, pool); 6973251881Speter SVN_ERR(svn_hash_write2(entries, out, SVN_HASH_TERMINATOR, subpool)); 6974251881Speter 6975251881Speter svn_pool_clear(subpool); 6976251881Speter 6977251881Speter /* Mark the node-rev's data rep as mutable. */ 6978251881Speter rep = apr_pcalloc(pool, sizeof(*rep)); 6979251881Speter rep->revision = SVN_INVALID_REVNUM; 6980251881Speter rep->txn_id = txn_id; 6981289166Speter 6982289166Speter if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT) 6983289166Speter { 6984289166Speter SVN_ERR(get_new_txn_node_id(&unique_suffix, fs, txn_id, pool)); 6985289166Speter rep->uniquifier = apr_psprintf(pool, "%s/%s", txn_id, unique_suffix); 6986289166Speter } 6987289166Speter 6988251881Speter parent_noderev->data_rep = rep; 6989251881Speter SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id, 6990251881Speter parent_noderev, FALSE, pool)); 6991251881Speter } 6992251881Speter else 6993251881Speter { 6994251881Speter /* The directory rep is already mutable, so just open it for append. */ 6995251881Speter SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND, 6996251881Speter APR_OS_DEFAULT, pool)); 6997251881Speter out = svn_stream_from_aprfile2(file, TRUE, pool); 6998251881Speter } 6999251881Speter 7000251881Speter /* if we have a directory cache for this transaction, update it */ 7001251881Speter if (ffd->txn_dir_cache) 7002251881Speter { 7003251881Speter /* build parameters: (name, new entry) pair */ 7004251881Speter const char *key = 7005251881Speter svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data; 7006251881Speter replace_baton_t baton; 7007251881Speter 7008251881Speter baton.name = name; 7009251881Speter baton.new_entry = NULL; 7010251881Speter 7011251881Speter if (id) 7012251881Speter { 7013251881Speter baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry)); 7014251881Speter baton.new_entry->name = name; 7015251881Speter baton.new_entry->kind = kind; 7016251881Speter baton.new_entry->id = id; 7017251881Speter } 7018251881Speter 7019251881Speter /* actually update the cached directory (if cached) */ 7020251881Speter SVN_ERR(svn_cache__set_partial(ffd->txn_dir_cache, key, 7021251881Speter svn_fs_fs__replace_dir_entry, &baton, 7022251881Speter subpool)); 7023251881Speter } 7024251881Speter svn_pool_clear(subpool); 7025251881Speter 7026251881Speter /* Append an incremental hash entry for the entry change. */ 7027251881Speter if (id) 7028251881Speter { 7029251881Speter const char *val = unparse_dir_entry(kind, id, subpool); 7030251881Speter 7031251881Speter SVN_ERR(svn_stream_printf(out, subpool, "K %" APR_SIZE_T_FMT "\n%s\n" 7032251881Speter "V %" APR_SIZE_T_FMT "\n%s\n", 7033251881Speter strlen(name), name, 7034251881Speter strlen(val), val)); 7035251881Speter } 7036251881Speter else 7037251881Speter { 7038251881Speter SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n", 7039251881Speter strlen(name), name)); 7040251881Speter } 7041251881Speter 7042251881Speter SVN_ERR(svn_io_file_close(file, subpool)); 7043251881Speter svn_pool_destroy(subpool); 7044251881Speter return SVN_NO_ERROR; 7045251881Speter} 7046251881Speter 7047251881Speter/* Write a single change entry, path PATH, change CHANGE, and copyfrom 7048251881Speter string COPYFROM, into the file specified by FILE. Only include the 7049251881Speter node kind field if INCLUDE_NODE_KIND is true. All temporary 7050251881Speter allocations are in POOL. */ 7051251881Speterstatic svn_error_t * 7052251881Speterwrite_change_entry(apr_file_t *file, 7053251881Speter const char *path, 7054251881Speter svn_fs_path_change2_t *change, 7055251881Speter svn_boolean_t include_node_kind, 7056251881Speter apr_pool_t *pool) 7057251881Speter{ 7058251881Speter const char *idstr, *buf; 7059251881Speter const char *change_string = NULL; 7060251881Speter const char *kind_string = ""; 7061251881Speter 7062251881Speter switch (change->change_kind) 7063251881Speter { 7064251881Speter case svn_fs_path_change_modify: 7065251881Speter change_string = ACTION_MODIFY; 7066251881Speter break; 7067251881Speter case svn_fs_path_change_add: 7068251881Speter change_string = ACTION_ADD; 7069251881Speter break; 7070251881Speter case svn_fs_path_change_delete: 7071251881Speter change_string = ACTION_DELETE; 7072251881Speter break; 7073251881Speter case svn_fs_path_change_replace: 7074251881Speter change_string = ACTION_REPLACE; 7075251881Speter break; 7076251881Speter case svn_fs_path_change_reset: 7077251881Speter change_string = ACTION_RESET; 7078251881Speter break; 7079251881Speter default: 7080251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 7081251881Speter _("Invalid change type %d"), 7082251881Speter change->change_kind); 7083251881Speter } 7084251881Speter 7085251881Speter if (change->node_rev_id) 7086251881Speter idstr = svn_fs_fs__id_unparse(change->node_rev_id, pool)->data; 7087251881Speter else 7088251881Speter idstr = ACTION_RESET; 7089251881Speter 7090251881Speter if (include_node_kind) 7091251881Speter { 7092251881Speter SVN_ERR_ASSERT(change->node_kind == svn_node_dir 7093251881Speter || change->node_kind == svn_node_file); 7094251881Speter kind_string = apr_psprintf(pool, "-%s", 7095251881Speter change->node_kind == svn_node_dir 7096251881Speter ? KIND_DIR : KIND_FILE); 7097251881Speter } 7098251881Speter buf = apr_psprintf(pool, "%s %s%s %s %s %s\n", 7099251881Speter idstr, change_string, kind_string, 7100251881Speter change->text_mod ? FLAG_TRUE : FLAG_FALSE, 7101251881Speter change->prop_mod ? FLAG_TRUE : FLAG_FALSE, 7102251881Speter path); 7103251881Speter 7104251881Speter SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool)); 7105251881Speter 7106251881Speter if (SVN_IS_VALID_REVNUM(change->copyfrom_rev)) 7107251881Speter { 7108251881Speter buf = apr_psprintf(pool, "%ld %s", change->copyfrom_rev, 7109251881Speter change->copyfrom_path); 7110251881Speter SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool)); 7111251881Speter } 7112251881Speter 7113251881Speter return svn_io_file_write_full(file, "\n", 1, NULL, pool); 7114251881Speter} 7115251881Speter 7116251881Spetersvn_error_t * 7117251881Spetersvn_fs_fs__add_change(svn_fs_t *fs, 7118251881Speter const char *txn_id, 7119251881Speter const char *path, 7120251881Speter const svn_fs_id_t *id, 7121251881Speter svn_fs_path_change_kind_t change_kind, 7122251881Speter svn_boolean_t text_mod, 7123251881Speter svn_boolean_t prop_mod, 7124251881Speter svn_node_kind_t node_kind, 7125251881Speter svn_revnum_t copyfrom_rev, 7126251881Speter const char *copyfrom_path, 7127251881Speter apr_pool_t *pool) 7128251881Speter{ 7129251881Speter apr_file_t *file; 7130251881Speter svn_fs_path_change2_t *change; 7131251881Speter 7132251881Speter SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool), 7133251881Speter APR_APPEND | APR_WRITE | APR_CREATE 7134251881Speter | APR_BUFFERED, APR_OS_DEFAULT, pool)); 7135251881Speter 7136251881Speter change = svn_fs__path_change_create_internal(id, change_kind, pool); 7137251881Speter change->text_mod = text_mod; 7138251881Speter change->prop_mod = prop_mod; 7139251881Speter change->node_kind = node_kind; 7140251881Speter change->copyfrom_rev = copyfrom_rev; 7141251881Speter change->copyfrom_path = apr_pstrdup(pool, copyfrom_path); 7142251881Speter 7143251881Speter SVN_ERR(write_change_entry(file, path, change, TRUE, pool)); 7144251881Speter 7145251881Speter return svn_io_file_close(file, pool); 7146251881Speter} 7147251881Speter 7148251881Speter/* This baton is used by the representation writing streams. It keeps 7149251881Speter track of the checksum information as well as the total size of the 7150251881Speter representation so far. */ 7151251881Speterstruct rep_write_baton 7152251881Speter{ 7153251881Speter /* The FS we are writing to. */ 7154251881Speter svn_fs_t *fs; 7155251881Speter 7156251881Speter /* Actual file to which we are writing. */ 7157251881Speter svn_stream_t *rep_stream; 7158251881Speter 7159251881Speter /* A stream from the delta combiner. Data written here gets 7160251881Speter deltified, then eventually written to rep_stream. */ 7161251881Speter svn_stream_t *delta_stream; 7162251881Speter 7163251881Speter /* Where is this representation header stored. */ 7164251881Speter apr_off_t rep_offset; 7165251881Speter 7166251881Speter /* Start of the actual data. */ 7167251881Speter apr_off_t delta_start; 7168251881Speter 7169251881Speter /* How many bytes have been written to this rep already. */ 7170251881Speter svn_filesize_t rep_size; 7171251881Speter 7172251881Speter /* The node revision for which we're writing out info. */ 7173251881Speter node_revision_t *noderev; 7174251881Speter 7175251881Speter /* Actual output file. */ 7176251881Speter apr_file_t *file; 7177251881Speter /* Lock 'cookie' used to unlock the output file once we've finished 7178251881Speter writing to it. */ 7179251881Speter void *lockcookie; 7180251881Speter 7181251881Speter svn_checksum_ctx_t *md5_checksum_ctx; 7182251881Speter svn_checksum_ctx_t *sha1_checksum_ctx; 7183251881Speter 7184251881Speter apr_pool_t *pool; 7185251881Speter 7186251881Speter apr_pool_t *parent_pool; 7187251881Speter}; 7188251881Speter 7189251881Speter/* Handler for the write method of the representation writable stream. 7190251881Speter BATON is a rep_write_baton, DATA is the data to write, and *LEN is 7191251881Speter the length of this data. */ 7192251881Speterstatic svn_error_t * 7193251881Speterrep_write_contents(void *baton, 7194251881Speter const char *data, 7195251881Speter apr_size_t *len) 7196251881Speter{ 7197251881Speter struct rep_write_baton *b = baton; 7198251881Speter 7199251881Speter SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len)); 7200251881Speter SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len)); 7201251881Speter b->rep_size += *len; 7202251881Speter 7203251881Speter /* If we are writing a delta, use that stream. */ 7204251881Speter if (b->delta_stream) 7205251881Speter return svn_stream_write(b->delta_stream, data, len); 7206251881Speter else 7207251881Speter return svn_stream_write(b->rep_stream, data, len); 7208251881Speter} 7209251881Speter 7210251881Speter/* Given a node-revision NODEREV in filesystem FS, return the 7211251881Speter representation in *REP to use as the base for a text representation 7212251881Speter delta if PROPS is FALSE. If PROPS has been set, a suitable props 7213251881Speter base representation will be returned. Perform temporary allocations 7214251881Speter in *POOL. */ 7215251881Speterstatic svn_error_t * 7216251881Speterchoose_delta_base(representation_t **rep, 7217251881Speter svn_fs_t *fs, 7218251881Speter node_revision_t *noderev, 7219251881Speter svn_boolean_t props, 7220251881Speter apr_pool_t *pool) 7221251881Speter{ 7222251881Speter int count; 7223251881Speter int walk; 7224251881Speter node_revision_t *base; 7225251881Speter fs_fs_data_t *ffd = fs->fsap_data; 7226251881Speter svn_boolean_t maybe_shared_rep = FALSE; 7227251881Speter 7228251881Speter /* If we have no predecessors, then use the empty stream as a 7229251881Speter base. */ 7230251881Speter if (! noderev->predecessor_count) 7231251881Speter { 7232251881Speter *rep = NULL; 7233251881Speter return SVN_NO_ERROR; 7234251881Speter } 7235251881Speter 7236251881Speter /* Flip the rightmost '1' bit of the predecessor count to determine 7237251881Speter which file rev (counting from 0) we want to use. (To see why 7238251881Speter count & (count - 1) unsets the rightmost set bit, think about how 7239251881Speter you decrement a binary number.) */ 7240251881Speter count = noderev->predecessor_count; 7241251881Speter count = count & (count - 1); 7242251881Speter 7243251881Speter /* We use skip delta for limiting the number of delta operations 7244251881Speter along very long node histories. Close to HEAD however, we create 7245251881Speter a linear history to minimize delta size. */ 7246251881Speter walk = noderev->predecessor_count - count; 7247251881Speter if (walk < (int)ffd->max_linear_deltification) 7248251881Speter count = noderev->predecessor_count - 1; 7249251881Speter 7250251881Speter /* Finding the delta base over a very long distance can become extremely 7251251881Speter expensive for very deep histories, possibly causing client timeouts etc. 7252251881Speter OTOH, this is a rare operation and its gains are minimal. Lets simply 7253251881Speter start deltification anew close every other 1000 changes or so. */ 7254251881Speter if (walk > (int)ffd->max_deltification_walk) 7255251881Speter { 7256251881Speter *rep = NULL; 7257251881Speter return SVN_NO_ERROR; 7258251881Speter } 7259251881Speter 7260251881Speter /* Walk back a number of predecessors equal to the difference 7261251881Speter between count and the original predecessor count. (For example, 7262251881Speter if noderev has ten predecessors and we want the eighth file rev, 7263251881Speter walk back two predecessors.) */ 7264251881Speter base = noderev; 7265251881Speter while ((count++) < noderev->predecessor_count) 7266251881Speter { 7267251881Speter SVN_ERR(svn_fs_fs__get_node_revision(&base, fs, 7268251881Speter base->predecessor_id, pool)); 7269251881Speter 7270251881Speter /* If there is a shared rep along the way, we need to limit the 7271251881Speter * length of the deltification chain. 7272251881Speter * 7273251881Speter * Please note that copied nodes - such as branch directories - will 7274251881Speter * look the same (false positive) while reps shared within the same 7275251881Speter * revision will not be caught (false negative). 7276251881Speter */ 7277251881Speter if (props) 7278251881Speter { 7279251881Speter if ( base->prop_rep 7280251881Speter && svn_fs_fs__id_rev(base->id) > base->prop_rep->revision) 7281251881Speter maybe_shared_rep = TRUE; 7282251881Speter } 7283251881Speter else 7284251881Speter { 7285251881Speter if ( base->data_rep 7286251881Speter && svn_fs_fs__id_rev(base->id) > base->data_rep->revision) 7287251881Speter maybe_shared_rep = TRUE; 7288251881Speter } 7289251881Speter } 7290251881Speter 7291251881Speter /* return a suitable base representation */ 7292251881Speter *rep = props ? base->prop_rep : base->data_rep; 7293251881Speter 7294251881Speter /* if we encountered a shared rep, it's parent chain may be different 7295251881Speter * from the node-rev parent chain. */ 7296251881Speter if (*rep && maybe_shared_rep) 7297251881Speter { 7298251881Speter /* Check whether the length of the deltification chain is acceptable. 7299251881Speter * Otherwise, shared reps may form a non-skipping delta chain in 7300251881Speter * extreme cases. */ 7301251881Speter apr_pool_t *sub_pool = svn_pool_create(pool); 7302251881Speter representation_t base_rep = **rep; 7303251881Speter 7304251881Speter /* Some reasonable limit, depending on how acceptable longer linear 7305251881Speter * chains are in this repo. Also, allow for some minimal chain. */ 7306251881Speter int max_chain_length = 2 * (int)ffd->max_linear_deltification + 2; 7307251881Speter 7308251881Speter /* re-use open files between iterations */ 7309251881Speter svn_revnum_t rev_hint = SVN_INVALID_REVNUM; 7310251881Speter apr_file_t *file_hint = NULL; 7311251881Speter 7312251881Speter /* follow the delta chain towards the end but for at most 7313251881Speter * MAX_CHAIN_LENGTH steps. */ 7314251881Speter for (; max_chain_length; --max_chain_length) 7315251881Speter { 7316251881Speter struct rep_state *rep_state; 7317251881Speter struct rep_args *rep_args; 7318251881Speter 7319251881Speter SVN_ERR(create_rep_state_body(&rep_state, 7320251881Speter &rep_args, 7321251881Speter &file_hint, 7322251881Speter &rev_hint, 7323251881Speter &base_rep, 7324251881Speter fs, 7325251881Speter sub_pool)); 7326251881Speter if (!rep_args->is_delta || !rep_args->base_revision) 7327251881Speter break; 7328251881Speter 7329251881Speter base_rep.revision = rep_args->base_revision; 7330251881Speter base_rep.offset = rep_args->base_offset; 7331251881Speter base_rep.size = rep_args->base_length; 7332251881Speter base_rep.txn_id = NULL; 7333251881Speter } 7334251881Speter 7335251881Speter /* start new delta chain if the current one has grown too long */ 7336251881Speter if (max_chain_length == 0) 7337251881Speter *rep = NULL; 7338251881Speter 7339251881Speter svn_pool_destroy(sub_pool); 7340251881Speter } 7341251881Speter 7342251881Speter /* verify that the reps don't form a degenerated '*/ 7343251881Speter return SVN_NO_ERROR; 7344251881Speter} 7345251881Speter 7346251881Speter/* Something went wrong and the pool for the rep write is being 7347251881Speter cleared before we've finished writing the rep. So we need 7348251881Speter to remove the rep from the protorevfile and we need to unlock 7349251881Speter the protorevfile. */ 7350251881Speterstatic apr_status_t 7351251881Speterrep_write_cleanup(void *data) 7352251881Speter{ 7353251881Speter struct rep_write_baton *b = data; 7354251881Speter const char *txn_id = svn_fs_fs__id_txn_id(b->noderev->id); 7355251881Speter svn_error_t *err; 7356251881Speter 7357251881Speter /* Truncate and close the protorevfile. */ 7358251881Speter err = svn_io_file_trunc(b->file, b->rep_offset, b->pool); 7359251881Speter err = svn_error_compose_create(err, svn_io_file_close(b->file, b->pool)); 7360251881Speter 7361251881Speter /* Remove our lock regardless of any preceeding errors so that the 7362251881Speter being_written flag is always removed and stays consistent with the 7363251881Speter file lock which will be removed no matter what since the pool is 7364251881Speter going away. */ 7365251881Speter err = svn_error_compose_create(err, unlock_proto_rev(b->fs, txn_id, 7366251881Speter b->lockcookie, b->pool)); 7367251881Speter if (err) 7368251881Speter { 7369251881Speter apr_status_t rc = err->apr_err; 7370251881Speter svn_error_clear(err); 7371251881Speter return rc; 7372251881Speter } 7373251881Speter 7374251881Speter return APR_SUCCESS; 7375251881Speter} 7376251881Speter 7377251881Speter 7378251881Speter/* Get a rep_write_baton and store it in *WB_P for the representation 7379251881Speter indicated by NODEREV in filesystem FS. Perform allocations in 7380251881Speter POOL. Only appropriate for file contents, not for props or 7381251881Speter directory contents. */ 7382251881Speterstatic svn_error_t * 7383251881Speterrep_write_get_baton(struct rep_write_baton **wb_p, 7384251881Speter svn_fs_t *fs, 7385251881Speter node_revision_t *noderev, 7386251881Speter apr_pool_t *pool) 7387251881Speter{ 7388251881Speter struct rep_write_baton *b; 7389251881Speter apr_file_t *file; 7390251881Speter representation_t *base_rep; 7391251881Speter svn_stream_t *source; 7392251881Speter const char *header; 7393251881Speter svn_txdelta_window_handler_t wh; 7394251881Speter void *whb; 7395251881Speter fs_fs_data_t *ffd = fs->fsap_data; 7396251881Speter int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0; 7397251881Speter 7398251881Speter b = apr_pcalloc(pool, sizeof(*b)); 7399251881Speter 7400251881Speter b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool); 7401251881Speter b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); 7402251881Speter 7403251881Speter b->fs = fs; 7404251881Speter b->parent_pool = pool; 7405251881Speter b->pool = svn_pool_create(pool); 7406251881Speter b->rep_size = 0; 7407251881Speter b->noderev = noderev; 7408251881Speter 7409251881Speter /* Open the prototype rev file and seek to its end. */ 7410251881Speter SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie, 7411251881Speter fs, svn_fs_fs__id_txn_id(noderev->id), 7412251881Speter b->pool)); 7413251881Speter 7414251881Speter b->file = file; 7415251881Speter b->rep_stream = svn_stream_from_aprfile2(file, TRUE, b->pool); 7416251881Speter 7417251881Speter SVN_ERR(get_file_offset(&b->rep_offset, file, b->pool)); 7418251881Speter 7419251881Speter /* Get the base for this delta. */ 7420251881Speter SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->pool)); 7421251881Speter SVN_ERR(read_representation(&source, fs, base_rep, b->pool)); 7422251881Speter 7423251881Speter /* Write out the rep header. */ 7424251881Speter if (base_rep) 7425251881Speter { 7426251881Speter header = apr_psprintf(b->pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %" 7427251881Speter SVN_FILESIZE_T_FMT "\n", 7428251881Speter base_rep->revision, base_rep->offset, 7429251881Speter base_rep->size); 7430251881Speter } 7431251881Speter else 7432251881Speter { 7433251881Speter header = REP_DELTA "\n"; 7434251881Speter } 7435251881Speter SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL, 7436251881Speter b->pool)); 7437251881Speter 7438251881Speter /* Now determine the offset of the actual svndiff data. */ 7439251881Speter SVN_ERR(get_file_offset(&b->delta_start, file, b->pool)); 7440251881Speter 7441251881Speter /* Cleanup in case something goes wrong. */ 7442251881Speter apr_pool_cleanup_register(b->pool, b, rep_write_cleanup, 7443251881Speter apr_pool_cleanup_null); 7444251881Speter 7445251881Speter /* Prepare to write the svndiff data. */ 7446251881Speter svn_txdelta_to_svndiff3(&wh, 7447251881Speter &whb, 7448251881Speter b->rep_stream, 7449251881Speter diff_version, 7450251881Speter SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 7451251881Speter pool); 7452251881Speter 7453251881Speter b->delta_stream = svn_txdelta_target_push(wh, whb, source, b->pool); 7454251881Speter 7455251881Speter *wb_p = b; 7456251881Speter 7457251881Speter return SVN_NO_ERROR; 7458251881Speter} 7459251881Speter 7460251881Speter/* For the hash REP->SHA1, try to find an already existing representation 7461251881Speter in FS and return it in *OUT_REP. If no such representation exists or 7462251881Speter if rep sharing has been disabled for FS, NULL will be returned. Since 7463251881Speter there may be new duplicate representations within the same uncommitted 7464251881Speter revision, those can be passed in REPS_HASH (maps a sha1 digest onto 7465251881Speter representation_t*), otherwise pass in NULL for REPS_HASH. 7466251881Speter POOL will be used for allocations. The lifetime of the returned rep is 7467251881Speter limited by both, POOL and REP lifetime. 7468251881Speter */ 7469251881Speterstatic svn_error_t * 7470251881Speterget_shared_rep(representation_t **old_rep, 7471251881Speter svn_fs_t *fs, 7472251881Speter representation_t *rep, 7473251881Speter apr_hash_t *reps_hash, 7474251881Speter apr_pool_t *pool) 7475251881Speter{ 7476251881Speter svn_error_t *err; 7477251881Speter fs_fs_data_t *ffd = fs->fsap_data; 7478251881Speter 7479251881Speter /* Return NULL, if rep sharing has been disabled. */ 7480251881Speter *old_rep = NULL; 7481251881Speter if (!ffd->rep_sharing_allowed) 7482251881Speter return SVN_NO_ERROR; 7483251881Speter 7484251881Speter /* Check and see if we already have a representation somewhere that's 7485251881Speter identical to the one we just wrote out. Start with the hash lookup 7486251881Speter because it is cheepest. */ 7487251881Speter if (reps_hash) 7488251881Speter *old_rep = apr_hash_get(reps_hash, 7489251881Speter rep->sha1_checksum->digest, 7490251881Speter APR_SHA1_DIGESTSIZE); 7491251881Speter 7492251881Speter /* If we haven't found anything yet, try harder and consult our DB. */ 7493251881Speter if (*old_rep == NULL) 7494251881Speter { 7495251881Speter err = svn_fs_fs__get_rep_reference(old_rep, fs, rep->sha1_checksum, 7496251881Speter pool); 7497251881Speter /* ### Other error codes that we shouldn't mask out? */ 7498251881Speter if (err == SVN_NO_ERROR) 7499251881Speter { 7500251881Speter if (*old_rep) 7501251881Speter SVN_ERR(verify_walker(*old_rep, NULL, fs, pool)); 7502251881Speter } 7503251881Speter else if (err->apr_err == SVN_ERR_FS_CORRUPT 7504251881Speter || SVN_ERROR_IN_CATEGORY(err->apr_err, 7505251881Speter SVN_ERR_MALFUNC_CATEGORY_START)) 7506251881Speter { 7507251881Speter /* Fatal error; don't mask it. 7508251881Speter 7509251881Speter In particular, this block is triggered when the rep-cache refers 7510251881Speter to revisions in the future. We signal that as a corruption situation 7511251881Speter since, once those revisions are less than youngest (because of more 7512251881Speter commits), the rep-cache would be invalid. 7513251881Speter */ 7514251881Speter SVN_ERR(err); 7515251881Speter } 7516251881Speter else 7517251881Speter { 7518251881Speter /* Something's wrong with the rep-sharing index. We can continue 7519251881Speter without rep-sharing, but warn. 7520251881Speter */ 7521251881Speter (fs->warning)(fs->warning_baton, err); 7522251881Speter svn_error_clear(err); 7523251881Speter *old_rep = NULL; 7524251881Speter } 7525251881Speter } 7526251881Speter 7527251881Speter /* look for intra-revision matches (usually data reps but not limited 7528251881Speter to them in case props happen to look like some data rep) 7529251881Speter */ 7530251881Speter if (*old_rep == NULL && rep->txn_id) 7531251881Speter { 7532251881Speter svn_node_kind_t kind; 7533251881Speter const char *file_name 7534251881Speter = path_txn_sha1(fs, rep->txn_id, rep->sha1_checksum, pool); 7535251881Speter 7536251881Speter /* in our txn, is there a rep file named with the wanted SHA1? 7537251881Speter If so, read it and use that rep. 7538251881Speter */ 7539251881Speter SVN_ERR(svn_io_check_path(file_name, &kind, pool)); 7540251881Speter if (kind == svn_node_file) 7541251881Speter { 7542251881Speter svn_stringbuf_t *rep_string; 7543251881Speter SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name, pool)); 7544251881Speter SVN_ERR(read_rep_offsets_body(old_rep, rep_string->data, 7545251881Speter rep->txn_id, FALSE, pool)); 7546251881Speter } 7547251881Speter } 7548251881Speter 7549251881Speter /* Add information that is missing in the cached data. */ 7550251881Speter if (*old_rep) 7551251881Speter { 7552251881Speter /* Use the old rep for this content. */ 7553251881Speter (*old_rep)->md5_checksum = rep->md5_checksum; 7554251881Speter (*old_rep)->uniquifier = rep->uniquifier; 7555251881Speter } 7556251881Speter 7557251881Speter return SVN_NO_ERROR; 7558251881Speter} 7559251881Speter 7560251881Speter/* Close handler for the representation write stream. BATON is a 7561251881Speter rep_write_baton. Writes out a new node-rev that correctly 7562251881Speter references the representation we just finished writing. */ 7563251881Speterstatic svn_error_t * 7564251881Speterrep_write_contents_close(void *baton) 7565251881Speter{ 7566251881Speter struct rep_write_baton *b = baton; 7567251881Speter const char *unique_suffix; 7568251881Speter representation_t *rep; 7569251881Speter representation_t *old_rep; 7570251881Speter apr_off_t offset; 7571289166Speter fs_fs_data_t *ffd = b->fs->fsap_data; 7572251881Speter 7573251881Speter rep = apr_pcalloc(b->parent_pool, sizeof(*rep)); 7574251881Speter rep->offset = b->rep_offset; 7575251881Speter 7576251881Speter /* Close our delta stream so the last bits of svndiff are written 7577251881Speter out. */ 7578251881Speter if (b->delta_stream) 7579251881Speter SVN_ERR(svn_stream_close(b->delta_stream)); 7580251881Speter 7581251881Speter /* Determine the length of the svndiff data. */ 7582251881Speter SVN_ERR(get_file_offset(&offset, b->file, b->pool)); 7583251881Speter rep->size = offset - b->delta_start; 7584251881Speter 7585251881Speter /* Fill in the rest of the representation field. */ 7586251881Speter rep->expanded_size = b->rep_size; 7587251881Speter rep->txn_id = svn_fs_fs__id_txn_id(b->noderev->id); 7588289166Speter 7589289166Speter if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT) 7590289166Speter { 7591289166Speter SVN_ERR(get_new_txn_node_id(&unique_suffix, b->fs, rep->txn_id, b->pool)); 7592289166Speter rep->uniquifier = apr_psprintf(b->parent_pool, "%s/%s", rep->txn_id, 7593289166Speter unique_suffix); 7594289166Speter } 7595251881Speter rep->revision = SVN_INVALID_REVNUM; 7596251881Speter 7597251881Speter /* Finalize the checksum. */ 7598251881Speter SVN_ERR(svn_checksum_final(&rep->md5_checksum, b->md5_checksum_ctx, 7599251881Speter b->parent_pool)); 7600251881Speter SVN_ERR(svn_checksum_final(&rep->sha1_checksum, b->sha1_checksum_ctx, 7601251881Speter b->parent_pool)); 7602251881Speter 7603251881Speter /* Check and see if we already have a representation somewhere that's 7604251881Speter identical to the one we just wrote out. */ 7605251881Speter SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->parent_pool)); 7606251881Speter 7607251881Speter if (old_rep) 7608251881Speter { 7609251881Speter /* We need to erase from the protorev the data we just wrote. */ 7610251881Speter SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->pool)); 7611251881Speter 7612251881Speter /* Use the old rep for this content. */ 7613251881Speter b->noderev->data_rep = old_rep; 7614251881Speter } 7615251881Speter else 7616251881Speter { 7617251881Speter /* Write out our cosmetic end marker. */ 7618251881Speter SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n")); 7619251881Speter 7620251881Speter b->noderev->data_rep = rep; 7621251881Speter } 7622251881Speter 7623251881Speter /* Remove cleanup callback. */ 7624251881Speter apr_pool_cleanup_kill(b->pool, b, rep_write_cleanup); 7625251881Speter 7626251881Speter /* Write out the new node-rev information. */ 7627251881Speter SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev, FALSE, 7628251881Speter b->pool)); 7629251881Speter if (!old_rep) 7630251881Speter SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->pool)); 7631251881Speter 7632251881Speter SVN_ERR(svn_io_file_close(b->file, b->pool)); 7633251881Speter SVN_ERR(unlock_proto_rev(b->fs, rep->txn_id, b->lockcookie, b->pool)); 7634251881Speter svn_pool_destroy(b->pool); 7635251881Speter 7636251881Speter return SVN_NO_ERROR; 7637251881Speter} 7638251881Speter 7639251881Speter/* Store a writable stream in *CONTENTS_P that will receive all data 7640251881Speter written and store it as the file data representation referenced by 7641251881Speter NODEREV in filesystem FS. Perform temporary allocations in 7642251881Speter POOL. Only appropriate for file data, not props or directory 7643251881Speter contents. */ 7644251881Speterstatic svn_error_t * 7645251881Speterset_representation(svn_stream_t **contents_p, 7646251881Speter svn_fs_t *fs, 7647251881Speter node_revision_t *noderev, 7648251881Speter apr_pool_t *pool) 7649251881Speter{ 7650251881Speter struct rep_write_baton *wb; 7651251881Speter 7652251881Speter if (! svn_fs_fs__id_txn_id(noderev->id)) 7653251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 7654251881Speter _("Attempted to write to non-transaction '%s'"), 7655251881Speter svn_fs_fs__id_unparse(noderev->id, pool)->data); 7656251881Speter 7657251881Speter SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool)); 7658251881Speter 7659251881Speter *contents_p = svn_stream_create(wb, pool); 7660251881Speter svn_stream_set_write(*contents_p, rep_write_contents); 7661251881Speter svn_stream_set_close(*contents_p, rep_write_contents_close); 7662251881Speter 7663251881Speter return SVN_NO_ERROR; 7664251881Speter} 7665251881Speter 7666251881Spetersvn_error_t * 7667251881Spetersvn_fs_fs__set_contents(svn_stream_t **stream, 7668251881Speter svn_fs_t *fs, 7669251881Speter node_revision_t *noderev, 7670251881Speter apr_pool_t *pool) 7671251881Speter{ 7672251881Speter if (noderev->kind != svn_node_file) 7673251881Speter return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL, 7674251881Speter _("Can't set text contents of a directory")); 7675251881Speter 7676251881Speter return set_representation(stream, fs, noderev, pool); 7677251881Speter} 7678251881Speter 7679251881Spetersvn_error_t * 7680251881Spetersvn_fs_fs__create_successor(const svn_fs_id_t **new_id_p, 7681251881Speter svn_fs_t *fs, 7682251881Speter const svn_fs_id_t *old_idp, 7683251881Speter node_revision_t *new_noderev, 7684251881Speter const char *copy_id, 7685251881Speter const char *txn_id, 7686251881Speter apr_pool_t *pool) 7687251881Speter{ 7688251881Speter const svn_fs_id_t *id; 7689251881Speter 7690251881Speter if (! copy_id) 7691251881Speter copy_id = svn_fs_fs__id_copy_id(old_idp); 7692251881Speter id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id, 7693251881Speter txn_id, pool); 7694251881Speter 7695251881Speter new_noderev->id = id; 7696251881Speter 7697251881Speter if (! new_noderev->copyroot_path) 7698251881Speter { 7699251881Speter new_noderev->copyroot_path = apr_pstrdup(pool, 7700251881Speter new_noderev->created_path); 7701251881Speter new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id); 7702251881Speter } 7703251881Speter 7704251881Speter SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE, 7705251881Speter pool)); 7706251881Speter 7707251881Speter *new_id_p = id; 7708251881Speter 7709251881Speter return SVN_NO_ERROR; 7710251881Speter} 7711251881Speter 7712251881Spetersvn_error_t * 7713251881Spetersvn_fs_fs__set_proplist(svn_fs_t *fs, 7714251881Speter node_revision_t *noderev, 7715251881Speter apr_hash_t *proplist, 7716251881Speter apr_pool_t *pool) 7717251881Speter{ 7718251881Speter const char *filename = path_txn_node_props(fs, noderev->id, pool); 7719251881Speter apr_file_t *file; 7720251881Speter svn_stream_t *out; 7721251881Speter 7722251881Speter /* Dump the property list to the mutable property file. */ 7723251881Speter SVN_ERR(svn_io_file_open(&file, filename, 7724251881Speter APR_WRITE | APR_CREATE | APR_TRUNCATE 7725251881Speter | APR_BUFFERED, APR_OS_DEFAULT, pool)); 7726251881Speter out = svn_stream_from_aprfile2(file, TRUE, pool); 7727251881Speter SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool)); 7728251881Speter SVN_ERR(svn_io_file_close(file, pool)); 7729251881Speter 7730251881Speter /* Mark the node-rev's prop rep as mutable, if not already done. */ 7731251881Speter if (!noderev->prop_rep || !noderev->prop_rep->txn_id) 7732251881Speter { 7733251881Speter noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep)); 7734251881Speter noderev->prop_rep->txn_id = svn_fs_fs__id_txn_id(noderev->id); 7735251881Speter SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool)); 7736251881Speter } 7737251881Speter 7738251881Speter return SVN_NO_ERROR; 7739251881Speter} 7740251881Speter 7741251881Speter/* Read the 'current' file for filesystem FS and store the next 7742251881Speter available node id in *NODE_ID, and the next available copy id in 7743251881Speter *COPY_ID. Allocations are performed from POOL. */ 7744251881Speterstatic svn_error_t * 7745251881Speterget_next_revision_ids(const char **node_id, 7746251881Speter const char **copy_id, 7747251881Speter svn_fs_t *fs, 7748251881Speter apr_pool_t *pool) 7749251881Speter{ 7750251881Speter char *buf; 7751251881Speter char *str; 7752251881Speter svn_stringbuf_t *content; 7753251881Speter 7754251881Speter SVN_ERR(read_content(&content, svn_fs_fs__path_current(fs, pool), pool)); 7755251881Speter buf = content->data; 7756251881Speter 7757251881Speter str = svn_cstring_tokenize(" ", &buf); 7758251881Speter if (! str) 7759251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 7760251881Speter _("Corrupt 'current' file")); 7761251881Speter 7762251881Speter str = svn_cstring_tokenize(" ", &buf); 7763251881Speter if (! str) 7764251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 7765251881Speter _("Corrupt 'current' file")); 7766251881Speter 7767251881Speter *node_id = apr_pstrdup(pool, str); 7768251881Speter 7769251881Speter str = svn_cstring_tokenize(" \n", &buf); 7770251881Speter if (! str) 7771251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 7772251881Speter _("Corrupt 'current' file")); 7773251881Speter 7774251881Speter *copy_id = apr_pstrdup(pool, str); 7775251881Speter 7776251881Speter return SVN_NO_ERROR; 7777251881Speter} 7778251881Speter 7779251881Speter/* This baton is used by the stream created for write_hash_rep. */ 7780251881Speterstruct write_hash_baton 7781251881Speter{ 7782251881Speter svn_stream_t *stream; 7783251881Speter 7784251881Speter apr_size_t size; 7785251881Speter 7786251881Speter svn_checksum_ctx_t *md5_ctx; 7787251881Speter svn_checksum_ctx_t *sha1_ctx; 7788251881Speter}; 7789251881Speter 7790251881Speter/* The handler for the write_hash_rep stream. BATON is a 7791251881Speter write_hash_baton, DATA has the data to write and *LEN is the number 7792251881Speter of bytes to write. */ 7793251881Speterstatic svn_error_t * 7794251881Speterwrite_hash_handler(void *baton, 7795251881Speter const char *data, 7796251881Speter apr_size_t *len) 7797251881Speter{ 7798251881Speter struct write_hash_baton *whb = baton; 7799251881Speter 7800251881Speter SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len)); 7801251881Speter SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len)); 7802251881Speter 7803251881Speter SVN_ERR(svn_stream_write(whb->stream, data, len)); 7804251881Speter whb->size += *len; 7805251881Speter 7806251881Speter return SVN_NO_ERROR; 7807251881Speter} 7808251881Speter 7809251881Speter/* Write out the hash HASH as a text representation to file FILE. In 7810251881Speter the process, record position, the total size of the dump and MD5 as 7811251881Speter well as SHA1 in REP. If rep sharing has been enabled and REPS_HASH 7812251881Speter is not NULL, it will be used in addition to the on-disk cache to find 7813251881Speter earlier reps with the same content. When such existing reps can be 7814251881Speter found, we will truncate the one just written from the file and return 7815251881Speter the existing rep. Perform temporary allocations in POOL. */ 7816251881Speterstatic svn_error_t * 7817251881Speterwrite_hash_rep(representation_t *rep, 7818251881Speter apr_file_t *file, 7819251881Speter apr_hash_t *hash, 7820251881Speter svn_fs_t *fs, 7821251881Speter apr_hash_t *reps_hash, 7822251881Speter apr_pool_t *pool) 7823251881Speter{ 7824251881Speter svn_stream_t *stream; 7825251881Speter struct write_hash_baton *whb; 7826251881Speter representation_t *old_rep; 7827251881Speter 7828251881Speter SVN_ERR(get_file_offset(&rep->offset, file, pool)); 7829251881Speter 7830251881Speter whb = apr_pcalloc(pool, sizeof(*whb)); 7831251881Speter 7832251881Speter whb->stream = svn_stream_from_aprfile2(file, TRUE, pool); 7833251881Speter whb->size = 0; 7834251881Speter whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); 7835251881Speter whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool); 7836251881Speter 7837251881Speter stream = svn_stream_create(whb, pool); 7838251881Speter svn_stream_set_write(stream, write_hash_handler); 7839251881Speter 7840251881Speter SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n")); 7841251881Speter 7842251881Speter SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool)); 7843251881Speter 7844251881Speter /* Store the results. */ 7845251881Speter SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool)); 7846251881Speter SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool)); 7847251881Speter 7848251881Speter /* Check and see if we already have a representation somewhere that's 7849251881Speter identical to the one we just wrote out. */ 7850251881Speter SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool)); 7851251881Speter 7852251881Speter if (old_rep) 7853251881Speter { 7854251881Speter /* We need to erase from the protorev the data we just wrote. */ 7855251881Speter SVN_ERR(svn_io_file_trunc(file, rep->offset, pool)); 7856251881Speter 7857251881Speter /* Use the old rep for this content. */ 7858251881Speter memcpy(rep, old_rep, sizeof (*rep)); 7859251881Speter } 7860251881Speter else 7861251881Speter { 7862251881Speter /* Write out our cosmetic end marker. */ 7863251881Speter SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n")); 7864251881Speter 7865251881Speter /* update the representation */ 7866251881Speter rep->size = whb->size; 7867289166Speter rep->expanded_size = whb->size; 7868251881Speter } 7869251881Speter 7870251881Speter return SVN_NO_ERROR; 7871251881Speter} 7872251881Speter 7873251881Speter/* Write out the hash HASH pertaining to the NODEREV in FS as a deltified 7874251881Speter text representation to file FILE. In the process, record the total size 7875251881Speter and the md5 digest in REP. If rep sharing has been enabled and REPS_HASH 7876251881Speter is not NULL, it will be used in addition to the on-disk cache to find 7877251881Speter earlier reps with the same content. When such existing reps can be found, 7878251881Speter we will truncate the one just written from the file and return the existing 7879251881Speter rep. If PROPS is set, assume that we want to a props representation as 7880251881Speter the base for our delta. Perform temporary allocations in POOL. */ 7881251881Speterstatic svn_error_t * 7882251881Speterwrite_hash_delta_rep(representation_t *rep, 7883251881Speter apr_file_t *file, 7884251881Speter apr_hash_t *hash, 7885251881Speter svn_fs_t *fs, 7886251881Speter node_revision_t *noderev, 7887251881Speter apr_hash_t *reps_hash, 7888251881Speter svn_boolean_t props, 7889251881Speter apr_pool_t *pool) 7890251881Speter{ 7891251881Speter svn_txdelta_window_handler_t diff_wh; 7892251881Speter void *diff_whb; 7893251881Speter 7894251881Speter svn_stream_t *file_stream; 7895251881Speter svn_stream_t *stream; 7896251881Speter representation_t *base_rep; 7897251881Speter representation_t *old_rep; 7898251881Speter svn_stream_t *source; 7899251881Speter const char *header; 7900251881Speter 7901251881Speter apr_off_t rep_end = 0; 7902251881Speter apr_off_t delta_start = 0; 7903251881Speter 7904251881Speter struct write_hash_baton *whb; 7905251881Speter fs_fs_data_t *ffd = fs->fsap_data; 7906251881Speter int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0; 7907251881Speter 7908251881Speter /* Get the base for this delta. */ 7909251881Speter SVN_ERR(choose_delta_base(&base_rep, fs, noderev, props, pool)); 7910251881Speter SVN_ERR(read_representation(&source, fs, base_rep, pool)); 7911251881Speter 7912251881Speter SVN_ERR(get_file_offset(&rep->offset, file, pool)); 7913251881Speter 7914251881Speter /* Write out the rep header. */ 7915251881Speter if (base_rep) 7916251881Speter { 7917251881Speter header = apr_psprintf(pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %" 7918251881Speter SVN_FILESIZE_T_FMT "\n", 7919251881Speter base_rep->revision, base_rep->offset, 7920251881Speter base_rep->size); 7921251881Speter } 7922251881Speter else 7923251881Speter { 7924251881Speter header = REP_DELTA "\n"; 7925251881Speter } 7926251881Speter SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL, 7927251881Speter pool)); 7928251881Speter 7929251881Speter SVN_ERR(get_file_offset(&delta_start, file, pool)); 7930251881Speter file_stream = svn_stream_from_aprfile2(file, TRUE, pool); 7931251881Speter 7932251881Speter /* Prepare to write the svndiff data. */ 7933251881Speter svn_txdelta_to_svndiff3(&diff_wh, 7934251881Speter &diff_whb, 7935251881Speter file_stream, 7936251881Speter diff_version, 7937251881Speter SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 7938251881Speter pool); 7939251881Speter 7940251881Speter whb = apr_pcalloc(pool, sizeof(*whb)); 7941251881Speter whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source, pool); 7942251881Speter whb->size = 0; 7943251881Speter whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); 7944251881Speter whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool); 7945251881Speter 7946251881Speter /* serialize the hash */ 7947251881Speter stream = svn_stream_create(whb, pool); 7948251881Speter svn_stream_set_write(stream, write_hash_handler); 7949251881Speter 7950251881Speter SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool)); 7951251881Speter SVN_ERR(svn_stream_close(whb->stream)); 7952251881Speter 7953251881Speter /* Store the results. */ 7954251881Speter SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool)); 7955251881Speter SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool)); 7956251881Speter 7957251881Speter /* Check and see if we already have a representation somewhere that's 7958251881Speter identical to the one we just wrote out. */ 7959251881Speter SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool)); 7960251881Speter 7961251881Speter if (old_rep) 7962251881Speter { 7963251881Speter /* We need to erase from the protorev the data we just wrote. */ 7964251881Speter SVN_ERR(svn_io_file_trunc(file, rep->offset, pool)); 7965251881Speter 7966251881Speter /* Use the old rep for this content. */ 7967251881Speter memcpy(rep, old_rep, sizeof (*rep)); 7968251881Speter } 7969251881Speter else 7970251881Speter { 7971251881Speter /* Write out our cosmetic end marker. */ 7972251881Speter SVN_ERR(get_file_offset(&rep_end, file, pool)); 7973251881Speter SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n")); 7974251881Speter 7975251881Speter /* update the representation */ 7976251881Speter rep->expanded_size = whb->size; 7977251881Speter rep->size = rep_end - delta_start; 7978251881Speter } 7979251881Speter 7980251881Speter return SVN_NO_ERROR; 7981251881Speter} 7982251881Speter 7983251881Speter/* Sanity check ROOT_NODEREV, a candidate for being the root node-revision 7984251881Speter of (not yet committed) revision REV in FS. Use POOL for temporary 7985251881Speter allocations. 7986251881Speter 7987251881Speter If you change this function, consider updating svn_fs_fs__verify() too. 7988251881Speter */ 7989251881Speterstatic svn_error_t * 7990251881Spetervalidate_root_noderev(svn_fs_t *fs, 7991251881Speter node_revision_t *root_noderev, 7992251881Speter svn_revnum_t rev, 7993251881Speter apr_pool_t *pool) 7994251881Speter{ 7995251881Speter svn_revnum_t head_revnum = rev-1; 7996251881Speter int head_predecessor_count; 7997251881Speter 7998251881Speter SVN_ERR_ASSERT(rev > 0); 7999251881Speter 8000251881Speter /* Compute HEAD_PREDECESSOR_COUNT. */ 8001251881Speter { 8002251881Speter svn_fs_root_t *head_revision; 8003251881Speter const svn_fs_id_t *head_root_id; 8004251881Speter node_revision_t *head_root_noderev; 8005251881Speter 8006251881Speter /* Get /@HEAD's noderev. */ 8007251881Speter SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool)); 8008251881Speter SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool)); 8009251881Speter SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id, 8010251881Speter pool)); 8011251881Speter 8012251881Speter head_predecessor_count = head_root_noderev->predecessor_count; 8013251881Speter } 8014251881Speter 8015251881Speter /* Check that the root noderev's predecessor count equals REV. 8016251881Speter 8017251881Speter This kind of corruption was seen on svn.apache.org (both on 8018251881Speter the root noderev and on other fspaths' noderevs); see 8019251881Speter issue #4129. 8020251881Speter 8021251881Speter Normally (rev == root_noderev->predecessor_count), but here we 8022251881Speter use a more roundabout check that should only trigger on new instances 8023251881Speter of the corruption, rather then trigger on each and every new commit 8024251881Speter to a repository that has triggered the bug somewhere in its root 8025251881Speter noderev's history. 8026251881Speter */ 8027251881Speter if (root_noderev->predecessor_count != -1 8028251881Speter && (root_noderev->predecessor_count - head_predecessor_count) 8029251881Speter != (rev - head_revnum)) 8030251881Speter { 8031251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 8032251881Speter _("predecessor count for " 8033251881Speter "the root node-revision is wrong: " 8034251881Speter "found (%d+%ld != %d), committing r%ld"), 8035251881Speter head_predecessor_count, 8036251881Speter rev - head_revnum, /* This is equal to 1. */ 8037251881Speter root_noderev->predecessor_count, 8038251881Speter rev); 8039251881Speter } 8040251881Speter 8041251881Speter return SVN_NO_ERROR; 8042251881Speter} 8043251881Speter 8044251881Speter/* Copy a node-revision specified by id ID in fileystem FS from a 8045251881Speter transaction into the proto-rev-file FILE. Set *NEW_ID_P to a 8046251881Speter pointer to the new node-id which will be allocated in POOL. 8047251881Speter If this is a directory, copy all children as well. 8048251881Speter 8049251881Speter START_NODE_ID and START_COPY_ID are 8050251881Speter the first available node and copy ids for this filesystem, for older 8051251881Speter FS formats. 8052251881Speter 8053251881Speter REV is the revision number that this proto-rev-file will represent. 8054251881Speter 8055251881Speter INITIAL_OFFSET is the offset of the proto-rev-file on entry to 8056251881Speter commit_body. 8057251881Speter 8058251881Speter If REPS_TO_CACHE is not NULL, append to it a copy (allocated in 8059251881Speter REPS_POOL) of each data rep that is new in this revision. 8060251881Speter 8061251881Speter If REPS_HASH is not NULL, append copies (allocated in REPS_POOL) 8062251881Speter of the representations of each property rep that is new in this 8063251881Speter revision. 8064251881Speter 8065251881Speter AT_ROOT is true if the node revision being written is the root 8066251881Speter node-revision. It is only controls additional sanity checking 8067251881Speter logic. 8068251881Speter 8069251881Speter Temporary allocations are also from POOL. */ 8070251881Speterstatic svn_error_t * 8071251881Speterwrite_final_rev(const svn_fs_id_t **new_id_p, 8072251881Speter apr_file_t *file, 8073251881Speter svn_revnum_t rev, 8074251881Speter svn_fs_t *fs, 8075251881Speter const svn_fs_id_t *id, 8076251881Speter const char *start_node_id, 8077251881Speter const char *start_copy_id, 8078251881Speter apr_off_t initial_offset, 8079251881Speter apr_array_header_t *reps_to_cache, 8080251881Speter apr_hash_t *reps_hash, 8081251881Speter apr_pool_t *reps_pool, 8082251881Speter svn_boolean_t at_root, 8083251881Speter apr_pool_t *pool) 8084251881Speter{ 8085251881Speter node_revision_t *noderev; 8086251881Speter apr_off_t my_offset; 8087251881Speter char my_node_id_buf[MAX_KEY_SIZE + 2]; 8088251881Speter char my_copy_id_buf[MAX_KEY_SIZE + 2]; 8089251881Speter const svn_fs_id_t *new_id; 8090251881Speter const char *node_id, *copy_id, *my_node_id, *my_copy_id; 8091251881Speter fs_fs_data_t *ffd = fs->fsap_data; 8092251881Speter 8093251881Speter *new_id_p = NULL; 8094251881Speter 8095251881Speter /* Check to see if this is a transaction node. */ 8096251881Speter if (! svn_fs_fs__id_txn_id(id)) 8097251881Speter return SVN_NO_ERROR; 8098251881Speter 8099251881Speter SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool)); 8100251881Speter 8101251881Speter if (noderev->kind == svn_node_dir) 8102251881Speter { 8103251881Speter apr_pool_t *subpool; 8104251881Speter apr_hash_t *entries, *str_entries; 8105251881Speter apr_array_header_t *sorted_entries; 8106251881Speter int i; 8107251881Speter 8108251881Speter /* This is a directory. Write out all the children first. */ 8109251881Speter subpool = svn_pool_create(pool); 8110251881Speter 8111251881Speter SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool)); 8112251881Speter /* For the sake of the repository administrator sort the entries 8113251881Speter so that the final file is deterministic and repeatable, 8114251881Speter however the rest of the FSFS code doesn't require any 8115251881Speter particular order here. */ 8116251881Speter sorted_entries = svn_sort__hash(entries, svn_sort_compare_items_lexically, 8117251881Speter pool); 8118251881Speter for (i = 0; i < sorted_entries->nelts; ++i) 8119251881Speter { 8120251881Speter svn_fs_dirent_t *dirent = APR_ARRAY_IDX(sorted_entries, i, 8121251881Speter svn_sort__item_t).value; 8122251881Speter 8123251881Speter svn_pool_clear(subpool); 8124251881Speter SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id, 8125251881Speter start_node_id, start_copy_id, initial_offset, 8126251881Speter reps_to_cache, reps_hash, reps_pool, FALSE, 8127251881Speter subpool)); 8128251881Speter if (new_id && (svn_fs_fs__id_rev(new_id) == rev)) 8129251881Speter dirent->id = svn_fs_fs__id_copy(new_id, pool); 8130251881Speter } 8131251881Speter svn_pool_destroy(subpool); 8132251881Speter 8133251881Speter if (noderev->data_rep && noderev->data_rep->txn_id) 8134251881Speter { 8135251881Speter /* Write out the contents of this directory as a text rep. */ 8136251881Speter SVN_ERR(unparse_dir_entries(&str_entries, entries, pool)); 8137251881Speter 8138251881Speter noderev->data_rep->txn_id = NULL; 8139251881Speter noderev->data_rep->revision = rev; 8140251881Speter 8141251881Speter if (ffd->deltify_directories) 8142251881Speter SVN_ERR(write_hash_delta_rep(noderev->data_rep, file, 8143251881Speter str_entries, fs, noderev, NULL, 8144251881Speter FALSE, pool)); 8145251881Speter else 8146251881Speter SVN_ERR(write_hash_rep(noderev->data_rep, file, str_entries, 8147251881Speter fs, NULL, pool)); 8148251881Speter } 8149251881Speter } 8150251881Speter else 8151251881Speter { 8152251881Speter /* This is a file. We should make sure the data rep, if it 8153251881Speter exists in a "this" state, gets rewritten to our new revision 8154251881Speter num. */ 8155251881Speter 8156251881Speter if (noderev->data_rep && noderev->data_rep->txn_id) 8157251881Speter { 8158251881Speter noderev->data_rep->txn_id = NULL; 8159251881Speter noderev->data_rep->revision = rev; 8160251881Speter 8161251881Speter /* See issue 3845. Some unknown mechanism caused the 8162251881Speter protorev file to get truncated, so check for that 8163251881Speter here. */ 8164251881Speter if (noderev->data_rep->offset + noderev->data_rep->size 8165251881Speter > initial_offset) 8166251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 8167251881Speter _("Truncated protorev file detected")); 8168251881Speter } 8169251881Speter } 8170251881Speter 8171251881Speter /* Fix up the property reps. */ 8172251881Speter if (noderev->prop_rep && noderev->prop_rep->txn_id) 8173251881Speter { 8174251881Speter apr_hash_t *proplist; 8175251881Speter SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool)); 8176251881Speter 8177251881Speter noderev->prop_rep->txn_id = NULL; 8178251881Speter noderev->prop_rep->revision = rev; 8179251881Speter 8180251881Speter if (ffd->deltify_properties) 8181251881Speter SVN_ERR(write_hash_delta_rep(noderev->prop_rep, file, 8182251881Speter proplist, fs, noderev, reps_hash, 8183251881Speter TRUE, pool)); 8184251881Speter else 8185251881Speter SVN_ERR(write_hash_rep(noderev->prop_rep, file, proplist, 8186251881Speter fs, reps_hash, pool)); 8187251881Speter } 8188251881Speter 8189251881Speter 8190251881Speter /* Convert our temporary ID into a permanent revision one. */ 8191251881Speter SVN_ERR(get_file_offset(&my_offset, file, pool)); 8192251881Speter 8193251881Speter node_id = svn_fs_fs__id_node_id(noderev->id); 8194251881Speter if (*node_id == '_') 8195251881Speter { 8196251881Speter if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 8197251881Speter my_node_id = apr_psprintf(pool, "%s-%ld", node_id + 1, rev); 8198251881Speter else 8199251881Speter { 8200251881Speter svn_fs_fs__add_keys(start_node_id, node_id + 1, my_node_id_buf); 8201251881Speter my_node_id = my_node_id_buf; 8202251881Speter } 8203251881Speter } 8204251881Speter else 8205251881Speter my_node_id = node_id; 8206251881Speter 8207251881Speter copy_id = svn_fs_fs__id_copy_id(noderev->id); 8208251881Speter if (*copy_id == '_') 8209251881Speter { 8210251881Speter if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 8211251881Speter my_copy_id = apr_psprintf(pool, "%s-%ld", copy_id + 1, rev); 8212251881Speter else 8213251881Speter { 8214251881Speter svn_fs_fs__add_keys(start_copy_id, copy_id + 1, my_copy_id_buf); 8215251881Speter my_copy_id = my_copy_id_buf; 8216251881Speter } 8217251881Speter } 8218251881Speter else 8219251881Speter my_copy_id = copy_id; 8220251881Speter 8221251881Speter if (noderev->copyroot_rev == SVN_INVALID_REVNUM) 8222251881Speter noderev->copyroot_rev = rev; 8223251881Speter 8224251881Speter new_id = svn_fs_fs__id_rev_create(my_node_id, my_copy_id, rev, my_offset, 8225251881Speter pool); 8226251881Speter 8227251881Speter noderev->id = new_id; 8228251881Speter 8229251881Speter if (ffd->rep_sharing_allowed) 8230251881Speter { 8231251881Speter /* Save the data representation's hash in the rep cache. */ 8232251881Speter if ( noderev->data_rep && noderev->kind == svn_node_file 8233251881Speter && noderev->data_rep->revision == rev) 8234251881Speter { 8235251881Speter SVN_ERR_ASSERT(reps_to_cache && reps_pool); 8236251881Speter APR_ARRAY_PUSH(reps_to_cache, representation_t *) 8237251881Speter = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool); 8238251881Speter } 8239251881Speter 8240251881Speter if (noderev->prop_rep && noderev->prop_rep->revision == rev) 8241251881Speter { 8242251881Speter /* Add new property reps to hash and on-disk cache. */ 8243251881Speter representation_t *copy 8244251881Speter = svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool); 8245251881Speter 8246251881Speter SVN_ERR_ASSERT(reps_to_cache && reps_pool); 8247251881Speter APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy; 8248251881Speter 8249251881Speter apr_hash_set(reps_hash, 8250251881Speter copy->sha1_checksum->digest, 8251251881Speter APR_SHA1_DIGESTSIZE, 8252251881Speter copy); 8253251881Speter } 8254251881Speter } 8255251881Speter 8256251881Speter /* don't serialize SHA1 for dirs to disk (waste of space) */ 8257251881Speter if (noderev->data_rep && noderev->kind == svn_node_dir) 8258251881Speter noderev->data_rep->sha1_checksum = NULL; 8259251881Speter 8260251881Speter /* don't serialize SHA1 for props to disk (waste of space) */ 8261251881Speter if (noderev->prop_rep) 8262251881Speter noderev->prop_rep->sha1_checksum = NULL; 8263251881Speter 8264251881Speter /* Workaround issue #4031: is-fresh-txn-root in revision files. */ 8265251881Speter noderev->is_fresh_txn_root = FALSE; 8266251881Speter 8267251881Speter /* Write out our new node-revision. */ 8268251881Speter if (at_root) 8269251881Speter SVN_ERR(validate_root_noderev(fs, noderev, rev, pool)); 8270251881Speter 8271251881Speter SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(file, TRUE, pool), 8272251881Speter noderev, ffd->format, 8273251881Speter svn_fs_fs__fs_supports_mergeinfo(fs), 8274251881Speter pool)); 8275251881Speter 8276251881Speter /* Return our ID that references the revision file. */ 8277251881Speter *new_id_p = noderev->id; 8278251881Speter 8279251881Speter return SVN_NO_ERROR; 8280251881Speter} 8281251881Speter 8282251881Speter/* Write the changed path info from transaction TXN_ID in filesystem 8283251881Speter FS to the permanent rev-file FILE. *OFFSET_P is set the to offset 8284251881Speter in the file of the beginning of this information. Perform 8285251881Speter temporary allocations in POOL. */ 8286251881Speterstatic svn_error_t * 8287251881Speterwrite_final_changed_path_info(apr_off_t *offset_p, 8288251881Speter apr_file_t *file, 8289251881Speter svn_fs_t *fs, 8290251881Speter const char *txn_id, 8291251881Speter apr_pool_t *pool) 8292251881Speter{ 8293251881Speter apr_hash_t *changed_paths; 8294251881Speter apr_off_t offset; 8295251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 8296251881Speter fs_fs_data_t *ffd = fs->fsap_data; 8297251881Speter svn_boolean_t include_node_kinds = 8298251881Speter ffd->format >= SVN_FS_FS__MIN_KIND_IN_CHANGED_FORMAT; 8299251881Speter apr_array_header_t *sorted_changed_paths; 8300251881Speter int i; 8301251881Speter 8302251881Speter SVN_ERR(get_file_offset(&offset, file, pool)); 8303251881Speter 8304251881Speter SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, fs, txn_id, pool)); 8305251881Speter /* For the sake of the repository administrator sort the changes so 8306251881Speter that the final file is deterministic and repeatable, however the 8307251881Speter rest of the FSFS code doesn't require any particular order here. */ 8308251881Speter sorted_changed_paths = svn_sort__hash(changed_paths, 8309251881Speter svn_sort_compare_items_lexically, pool); 8310251881Speter 8311251881Speter /* Iterate through the changed paths one at a time, and convert the 8312251881Speter temporary node-id into a permanent one for each change entry. */ 8313251881Speter for (i = 0; i < sorted_changed_paths->nelts; ++i) 8314251881Speter { 8315251881Speter node_revision_t *noderev; 8316251881Speter const svn_fs_id_t *id; 8317251881Speter svn_fs_path_change2_t *change; 8318251881Speter const char *path; 8319251881Speter 8320251881Speter svn_pool_clear(iterpool); 8321251881Speter 8322251881Speter change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value; 8323251881Speter path = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).key; 8324251881Speter 8325251881Speter id = change->node_rev_id; 8326251881Speter 8327251881Speter /* If this was a delete of a mutable node, then it is OK to 8328251881Speter leave the change entry pointing to the non-existent temporary 8329251881Speter node, since it will never be used. */ 8330251881Speter if ((change->change_kind != svn_fs_path_change_delete) && 8331251881Speter (! svn_fs_fs__id_txn_id(id))) 8332251881Speter { 8333251881Speter SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, iterpool)); 8334251881Speter 8335251881Speter /* noderev has the permanent node-id at this point, so we just 8336251881Speter substitute it for the temporary one. */ 8337251881Speter change->node_rev_id = noderev->id; 8338251881Speter } 8339251881Speter 8340251881Speter /* Write out the new entry into the final rev-file. */ 8341251881Speter SVN_ERR(write_change_entry(file, path, change, include_node_kinds, 8342251881Speter iterpool)); 8343251881Speter } 8344251881Speter 8345251881Speter svn_pool_destroy(iterpool); 8346251881Speter 8347251881Speter *offset_p = offset; 8348251881Speter 8349251881Speter return SVN_NO_ERROR; 8350251881Speter} 8351251881Speter 8352251881Speter/* Atomically update the 'current' file to hold the specifed REV, 8353251881Speter NEXT_NODE_ID, and NEXT_COPY_ID. (The two next-ID parameters are 8354251881Speter ignored and may be NULL if the FS format does not use them.) 8355251881Speter Perform temporary allocations in POOL. */ 8356251881Speterstatic svn_error_t * 8357251881Speterwrite_current(svn_fs_t *fs, svn_revnum_t rev, const char *next_node_id, 8358251881Speter const char *next_copy_id, apr_pool_t *pool) 8359251881Speter{ 8360251881Speter char *buf; 8361251881Speter const char *tmp_name, *name; 8362251881Speter fs_fs_data_t *ffd = fs->fsap_data; 8363251881Speter 8364251881Speter /* Now we can just write out this line. */ 8365251881Speter if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 8366251881Speter buf = apr_psprintf(pool, "%ld\n", rev); 8367251881Speter else 8368251881Speter buf = apr_psprintf(pool, "%ld %s %s\n", rev, next_node_id, next_copy_id); 8369251881Speter 8370251881Speter name = svn_fs_fs__path_current(fs, pool); 8371251881Speter SVN_ERR(svn_io_write_unique(&tmp_name, 8372251881Speter svn_dirent_dirname(name, pool), 8373251881Speter buf, strlen(buf), 8374251881Speter svn_io_file_del_none, pool)); 8375251881Speter 8376251881Speter return move_into_place(tmp_name, name, name, pool); 8377251881Speter} 8378251881Speter 8379251881Speter/* Open a new svn_fs_t handle to FS, set that handle's concept of "current 8380251881Speter youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on 8381251881Speter NEW_REV's revision root. 8382251881Speter 8383251881Speter Intended to be called as the very last step in a commit before 'current' 8384251881Speter is bumped. This implies that we are holding the write lock. */ 8385251881Speterstatic svn_error_t * 8386251881Speterverify_as_revision_before_current_plus_plus(svn_fs_t *fs, 8387251881Speter svn_revnum_t new_rev, 8388251881Speter apr_pool_t *pool) 8389251881Speter{ 8390251881Speter#ifdef SVN_DEBUG 8391251881Speter fs_fs_data_t *ffd = fs->fsap_data; 8392251881Speter svn_fs_t *ft; /* fs++ == ft */ 8393251881Speter svn_fs_root_t *root; 8394251881Speter fs_fs_data_t *ft_ffd; 8395251881Speter apr_hash_t *fs_config; 8396251881Speter 8397251881Speter SVN_ERR_ASSERT(ffd->svn_fs_open_); 8398251881Speter 8399251881Speter /* make sure FT does not simply return data cached by other instances 8400251881Speter * but actually retrieves it from disk at least once. 8401251881Speter */ 8402251881Speter fs_config = apr_hash_make(pool); 8403251881Speter svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS, 8404251881Speter svn_uuid_generate(pool)); 8405251881Speter SVN_ERR(ffd->svn_fs_open_(&ft, fs->path, 8406251881Speter fs_config, 8407251881Speter pool)); 8408251881Speter ft_ffd = ft->fsap_data; 8409251881Speter /* Don't let FT consult rep-cache.db, either. */ 8410251881Speter ft_ffd->rep_sharing_allowed = FALSE; 8411251881Speter 8412251881Speter /* Time travel! */ 8413251881Speter ft_ffd->youngest_rev_cache = new_rev; 8414251881Speter 8415251881Speter SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool)); 8416251881Speter SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev); 8417251881Speter SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev); 8418251881Speter SVN_ERR(svn_fs_fs__verify_root(root, pool)); 8419251881Speter#endif /* SVN_DEBUG */ 8420251881Speter 8421251881Speter return SVN_NO_ERROR; 8422251881Speter} 8423251881Speter 8424251881Speter/* Update the 'current' file to hold the correct next node and copy_ids 8425251881Speter from transaction TXN_ID in filesystem FS. The current revision is 8426251881Speter set to REV. Perform temporary allocations in POOL. */ 8427251881Speterstatic svn_error_t * 8428251881Speterwrite_final_current(svn_fs_t *fs, 8429251881Speter const char *txn_id, 8430251881Speter svn_revnum_t rev, 8431251881Speter const char *start_node_id, 8432251881Speter const char *start_copy_id, 8433251881Speter apr_pool_t *pool) 8434251881Speter{ 8435251881Speter const char *txn_node_id, *txn_copy_id; 8436251881Speter char new_node_id[MAX_KEY_SIZE + 2]; 8437251881Speter char new_copy_id[MAX_KEY_SIZE + 2]; 8438251881Speter fs_fs_data_t *ffd = fs->fsap_data; 8439251881Speter 8440251881Speter if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 8441251881Speter return write_current(fs, rev, NULL, NULL, pool); 8442251881Speter 8443251881Speter /* To find the next available ids, we add the id that used to be in 8444251881Speter the 'current' file, to the next ids from the transaction file. */ 8445251881Speter SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool)); 8446251881Speter 8447251881Speter svn_fs_fs__add_keys(start_node_id, txn_node_id, new_node_id); 8448251881Speter svn_fs_fs__add_keys(start_copy_id, txn_copy_id, new_copy_id); 8449251881Speter 8450251881Speter return write_current(fs, rev, new_node_id, new_copy_id, pool); 8451251881Speter} 8452251881Speter 8453251881Speter/* Verify that the user registed with FS has all the locks necessary to 8454251881Speter permit all the changes associate with TXN_NAME. 8455251881Speter The FS write lock is assumed to be held by the caller. */ 8456251881Speterstatic svn_error_t * 8457251881Speterverify_locks(svn_fs_t *fs, 8458251881Speter const char *txn_name, 8459251881Speter apr_pool_t *pool) 8460251881Speter{ 8461251881Speter apr_pool_t *subpool = svn_pool_create(pool); 8462251881Speter apr_hash_t *changes; 8463251881Speter apr_hash_index_t *hi; 8464251881Speter apr_array_header_t *changed_paths; 8465251881Speter svn_stringbuf_t *last_recursed = NULL; 8466251881Speter int i; 8467251881Speter 8468251881Speter /* Fetch the changes for this transaction. */ 8469251881Speter SVN_ERR(svn_fs_fs__txn_changes_fetch(&changes, fs, txn_name, pool)); 8470251881Speter 8471251881Speter /* Make an array of the changed paths, and sort them depth-first-ily. */ 8472251881Speter changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1, 8473251881Speter sizeof(const char *)); 8474251881Speter for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi)) 8475251881Speter APR_ARRAY_PUSH(changed_paths, const char *) = svn__apr_hash_index_key(hi); 8476251881Speter qsort(changed_paths->elts, changed_paths->nelts, 8477251881Speter changed_paths->elt_size, svn_sort_compare_paths); 8478251881Speter 8479251881Speter /* Now, traverse the array of changed paths, verify locks. Note 8480251881Speter that if we need to do a recursive verification a path, we'll skip 8481251881Speter over children of that path when we get to them. */ 8482251881Speter for (i = 0; i < changed_paths->nelts; i++) 8483251881Speter { 8484251881Speter const char *path; 8485251881Speter svn_fs_path_change2_t *change; 8486251881Speter svn_boolean_t recurse = TRUE; 8487251881Speter 8488251881Speter svn_pool_clear(subpool); 8489251881Speter path = APR_ARRAY_IDX(changed_paths, i, const char *); 8490251881Speter 8491251881Speter /* If this path has already been verified as part of a recursive 8492251881Speter check of one of its parents, no need to do it again. */ 8493251881Speter if (last_recursed 8494251881Speter && svn_dirent_is_child(last_recursed->data, path, subpool)) 8495251881Speter continue; 8496251881Speter 8497251881Speter /* Fetch the change associated with our path. */ 8498251881Speter change = svn_hash_gets(changes, path); 8499251881Speter 8500251881Speter /* What does it mean to succeed at lock verification for a given 8501251881Speter path? For an existing file or directory getting modified 8502251881Speter (text, props), it means we hold the lock on the file or 8503251881Speter directory. For paths being added or removed, we need to hold 8504251881Speter the locks for that path and any children of that path. 8505251881Speter 8506251881Speter WHEW! We have no reliable way to determine the node kind 8507251881Speter of deleted items, but fortunately we are going to do a 8508251881Speter recursive check on deleted paths regardless of their kind. */ 8509251881Speter if (change->change_kind == svn_fs_path_change_modify) 8510251881Speter recurse = FALSE; 8511251881Speter SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE, 8512251881Speter subpool)); 8513251881Speter 8514251881Speter /* If we just did a recursive check, remember the path we 8515251881Speter checked (so children can be skipped). */ 8516251881Speter if (recurse) 8517251881Speter { 8518251881Speter if (! last_recursed) 8519251881Speter last_recursed = svn_stringbuf_create(path, pool); 8520251881Speter else 8521251881Speter svn_stringbuf_set(last_recursed, path); 8522251881Speter } 8523251881Speter } 8524251881Speter svn_pool_destroy(subpool); 8525251881Speter return SVN_NO_ERROR; 8526251881Speter} 8527251881Speter 8528251881Speter/* Baton used for commit_body below. */ 8529251881Speterstruct commit_baton { 8530251881Speter svn_revnum_t *new_rev_p; 8531251881Speter svn_fs_t *fs; 8532251881Speter svn_fs_txn_t *txn; 8533251881Speter apr_array_header_t *reps_to_cache; 8534251881Speter apr_hash_t *reps_hash; 8535251881Speter apr_pool_t *reps_pool; 8536251881Speter}; 8537251881Speter 8538251881Speter/* The work-horse for svn_fs_fs__commit, called with the FS write lock. 8539251881Speter This implements the svn_fs_fs__with_write_lock() 'body' callback 8540251881Speter type. BATON is a 'struct commit_baton *'. */ 8541251881Speterstatic svn_error_t * 8542251881Spetercommit_body(void *baton, apr_pool_t *pool) 8543251881Speter{ 8544251881Speter struct commit_baton *cb = baton; 8545251881Speter fs_fs_data_t *ffd = cb->fs->fsap_data; 8546251881Speter const char *old_rev_filename, *rev_filename, *proto_filename; 8547251881Speter const char *revprop_filename, *final_revprop; 8548251881Speter const svn_fs_id_t *root_id, *new_root_id; 8549251881Speter const char *start_node_id = NULL, *start_copy_id = NULL; 8550251881Speter svn_revnum_t old_rev, new_rev; 8551251881Speter apr_file_t *proto_file; 8552251881Speter void *proto_file_lockcookie; 8553251881Speter apr_off_t initial_offset, changed_path_offset; 8554251881Speter char *buf; 8555251881Speter apr_hash_t *txnprops; 8556251881Speter apr_array_header_t *txnprop_list; 8557251881Speter svn_prop_t prop; 8558251881Speter svn_string_t date; 8559251881Speter 8560251881Speter /* Get the current youngest revision. */ 8561251881Speter SVN_ERR(svn_fs_fs__youngest_rev(&old_rev, cb->fs, pool)); 8562251881Speter 8563251881Speter /* Check to make sure this transaction is based off the most recent 8564251881Speter revision. */ 8565251881Speter if (cb->txn->base_rev != old_rev) 8566251881Speter return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL, 8567251881Speter _("Transaction out of date")); 8568251881Speter 8569251881Speter /* Locks may have been added (or stolen) between the calling of 8570251881Speter previous svn_fs.h functions and svn_fs_commit_txn(), so we need 8571251881Speter to re-examine every changed-path in the txn and re-verify all 8572251881Speter discovered locks. */ 8573251881Speter SVN_ERR(verify_locks(cb->fs, cb->txn->id, pool)); 8574251881Speter 8575251881Speter /* Get the next node_id and copy_id to use. */ 8576251881Speter if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 8577251881Speter SVN_ERR(get_next_revision_ids(&start_node_id, &start_copy_id, cb->fs, 8578251881Speter pool)); 8579251881Speter 8580251881Speter /* We are going to be one better than this puny old revision. */ 8581251881Speter new_rev = old_rev + 1; 8582251881Speter 8583251881Speter /* Get a write handle on the proto revision file. */ 8584251881Speter SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie, 8585251881Speter cb->fs, cb->txn->id, pool)); 8586251881Speter SVN_ERR(get_file_offset(&initial_offset, proto_file, pool)); 8587251881Speter 8588251881Speter /* Write out all the node-revisions and directory contents. */ 8589251881Speter root_id = svn_fs_fs__id_txn_create("0", "0", cb->txn->id, pool); 8590251881Speter SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id, 8591251881Speter start_node_id, start_copy_id, initial_offset, 8592251881Speter cb->reps_to_cache, cb->reps_hash, cb->reps_pool, 8593251881Speter TRUE, pool)); 8594251881Speter 8595251881Speter /* Write the changed-path information. */ 8596251881Speter SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file, 8597251881Speter cb->fs, cb->txn->id, pool)); 8598251881Speter 8599251881Speter /* Write the final line. */ 8600251881Speter buf = apr_psprintf(pool, "\n%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n", 8601251881Speter svn_fs_fs__id_offset(new_root_id), 8602251881Speter changed_path_offset); 8603251881Speter SVN_ERR(svn_io_file_write_full(proto_file, buf, strlen(buf), NULL, 8604251881Speter pool)); 8605251881Speter SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool)); 8606251881Speter SVN_ERR(svn_io_file_close(proto_file, pool)); 8607251881Speter 8608251881Speter /* We don't unlock the prototype revision file immediately to avoid a 8609251881Speter race with another caller writing to the prototype revision file 8610251881Speter before we commit it. */ 8611251881Speter 8612251881Speter /* Remove any temporary txn props representing 'flags'. */ 8613251881Speter SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, cb->txn, pool)); 8614251881Speter txnprop_list = apr_array_make(pool, 3, sizeof(svn_prop_t)); 8615251881Speter prop.value = NULL; 8616251881Speter 8617251881Speter if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD)) 8618251881Speter { 8619251881Speter prop.name = SVN_FS__PROP_TXN_CHECK_OOD; 8620251881Speter APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop; 8621251881Speter } 8622251881Speter 8623251881Speter if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS)) 8624251881Speter { 8625251881Speter prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS; 8626251881Speter APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop; 8627251881Speter } 8628251881Speter 8629251881Speter if (! apr_is_empty_array(txnprop_list)) 8630251881Speter SVN_ERR(svn_fs_fs__change_txn_props(cb->txn, txnprop_list, pool)); 8631251881Speter 8632251881Speter /* Create the shard for the rev and revprop file, if we're sharding and 8633251881Speter this is the first revision of a new shard. We don't care if this 8634251881Speter fails because the shard already existed for some reason. */ 8635251881Speter if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0) 8636251881Speter { 8637251881Speter /* Create the revs shard. */ 8638251881Speter { 8639251881Speter const char *new_dir = path_rev_shard(cb->fs, new_rev, pool); 8640251881Speter svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool); 8641251881Speter if (err && !APR_STATUS_IS_EEXIST(err->apr_err)) 8642251881Speter return svn_error_trace(err); 8643251881Speter svn_error_clear(err); 8644251881Speter SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path, 8645251881Speter PATH_REVS_DIR, 8646251881Speter pool), 8647251881Speter new_dir, pool)); 8648251881Speter } 8649251881Speter 8650251881Speter /* Create the revprops shard. */ 8651251881Speter SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev)); 8652251881Speter { 8653251881Speter const char *new_dir = path_revprops_shard(cb->fs, new_rev, pool); 8654251881Speter svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool); 8655251881Speter if (err && !APR_STATUS_IS_EEXIST(err->apr_err)) 8656251881Speter return svn_error_trace(err); 8657251881Speter svn_error_clear(err); 8658251881Speter SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path, 8659251881Speter PATH_REVPROPS_DIR, 8660251881Speter pool), 8661251881Speter new_dir, pool)); 8662251881Speter } 8663251881Speter } 8664251881Speter 8665251881Speter /* Move the finished rev file into place. */ 8666251881Speter SVN_ERR(svn_fs_fs__path_rev_absolute(&old_rev_filename, 8667251881Speter cb->fs, old_rev, pool)); 8668251881Speter rev_filename = path_rev(cb->fs, new_rev, pool); 8669251881Speter proto_filename = path_txn_proto_rev(cb->fs, cb->txn->id, pool); 8670251881Speter SVN_ERR(move_into_place(proto_filename, rev_filename, old_rev_filename, 8671251881Speter pool)); 8672251881Speter 8673251881Speter /* Now that we've moved the prototype revision file out of the way, 8674251881Speter we can unlock it (since further attempts to write to the file 8675251881Speter will fail as it no longer exists). We must do this so that we can 8676251881Speter remove the transaction directory later. */ 8677251881Speter SVN_ERR(unlock_proto_rev(cb->fs, cb->txn->id, proto_file_lockcookie, pool)); 8678251881Speter 8679251881Speter /* Update commit time to ensure that svn:date revprops remain ordered. */ 8680251881Speter date.data = svn_time_to_cstring(apr_time_now(), pool); 8681251881Speter date.len = strlen(date.data); 8682251881Speter 8683251881Speter SVN_ERR(svn_fs_fs__change_txn_prop(cb->txn, SVN_PROP_REVISION_DATE, 8684251881Speter &date, pool)); 8685251881Speter 8686251881Speter /* Move the revprops file into place. */ 8687251881Speter SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev)); 8688251881Speter revprop_filename = path_txn_props(cb->fs, cb->txn->id, pool); 8689251881Speter final_revprop = path_revprops(cb->fs, new_rev, pool); 8690251881Speter SVN_ERR(move_into_place(revprop_filename, final_revprop, 8691251881Speter old_rev_filename, pool)); 8692251881Speter 8693251881Speter /* Update the 'current' file. */ 8694251881Speter SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool)); 8695251881Speter SVN_ERR(write_final_current(cb->fs, cb->txn->id, new_rev, start_node_id, 8696251881Speter start_copy_id, pool)); 8697251881Speter 8698251881Speter /* At this point the new revision is committed and globally visible 8699251881Speter so let the caller know it succeeded by giving it the new revision 8700251881Speter number, which fulfills svn_fs_commit_txn() contract. Any errors 8701251881Speter after this point do not change the fact that a new revision was 8702251881Speter created. */ 8703251881Speter *cb->new_rev_p = new_rev; 8704251881Speter 8705251881Speter ffd->youngest_rev_cache = new_rev; 8706251881Speter 8707251881Speter /* Remove this transaction directory. */ 8708251881Speter SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool)); 8709251881Speter 8710251881Speter return SVN_NO_ERROR; 8711251881Speter} 8712251881Speter 8713251881Speter/* Add the representations in REPS_TO_CACHE (an array of representation_t *) 8714251881Speter * to the rep-cache database of FS. */ 8715251881Speterstatic svn_error_t * 8716251881Speterwrite_reps_to_cache(svn_fs_t *fs, 8717251881Speter const apr_array_header_t *reps_to_cache, 8718251881Speter apr_pool_t *scratch_pool) 8719251881Speter{ 8720251881Speter int i; 8721251881Speter 8722251881Speter for (i = 0; i < reps_to_cache->nelts; i++) 8723251881Speter { 8724251881Speter representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *); 8725251881Speter 8726251881Speter /* FALSE because we don't care if another parallel commit happened to 8727251881Speter * collide with us. (Non-parallel collisions will not be detected.) */ 8728251881Speter SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, FALSE, scratch_pool)); 8729251881Speter } 8730251881Speter 8731251881Speter return SVN_NO_ERROR; 8732251881Speter} 8733251881Speter 8734251881Spetersvn_error_t * 8735251881Spetersvn_fs_fs__commit(svn_revnum_t *new_rev_p, 8736251881Speter svn_fs_t *fs, 8737251881Speter svn_fs_txn_t *txn, 8738251881Speter apr_pool_t *pool) 8739251881Speter{ 8740251881Speter struct commit_baton cb; 8741251881Speter fs_fs_data_t *ffd = fs->fsap_data; 8742251881Speter 8743251881Speter cb.new_rev_p = new_rev_p; 8744251881Speter cb.fs = fs; 8745251881Speter cb.txn = txn; 8746251881Speter 8747251881Speter if (ffd->rep_sharing_allowed) 8748251881Speter { 8749251881Speter cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *)); 8750251881Speter cb.reps_hash = apr_hash_make(pool); 8751251881Speter cb.reps_pool = pool; 8752251881Speter } 8753251881Speter else 8754251881Speter { 8755251881Speter cb.reps_to_cache = NULL; 8756251881Speter cb.reps_hash = NULL; 8757251881Speter cb.reps_pool = NULL; 8758251881Speter } 8759251881Speter 8760251881Speter SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool)); 8761251881Speter 8762251881Speter /* At this point, *NEW_REV_P has been set, so errors below won't affect 8763251881Speter the success of the commit. (See svn_fs_commit_txn().) */ 8764251881Speter 8765251881Speter if (ffd->rep_sharing_allowed) 8766251881Speter { 8767251881Speter SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); 8768251881Speter 8769251881Speter /* Write new entries to the rep-sharing database. 8770251881Speter * 8771251881Speter * We use an sqlite transaction to speed things up; 8772251881Speter * see <http://www.sqlite.org/faq.html#q19>. 8773251881Speter */ 8774251881Speter SVN_SQLITE__WITH_TXN( 8775251881Speter write_reps_to_cache(fs, cb.reps_to_cache, pool), 8776251881Speter ffd->rep_cache_db); 8777251881Speter } 8778251881Speter 8779251881Speter return SVN_NO_ERROR; 8780251881Speter} 8781251881Speter 8782251881Speter 8783251881Spetersvn_error_t * 8784251881Spetersvn_fs_fs__reserve_copy_id(const char **copy_id_p, 8785251881Speter svn_fs_t *fs, 8786251881Speter const char *txn_id, 8787251881Speter apr_pool_t *pool) 8788251881Speter{ 8789251881Speter const char *cur_node_id, *cur_copy_id; 8790251881Speter char *copy_id; 8791251881Speter apr_size_t len; 8792251881Speter 8793251881Speter /* First read in the current next-ids file. */ 8794251881Speter SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool)); 8795251881Speter 8796251881Speter copy_id = apr_pcalloc(pool, strlen(cur_copy_id) + 2); 8797251881Speter 8798251881Speter len = strlen(cur_copy_id); 8799251881Speter svn_fs_fs__next_key(cur_copy_id, &len, copy_id); 8800251881Speter 8801251881Speter SVN_ERR(write_next_ids(fs, txn_id, cur_node_id, copy_id, pool)); 8802251881Speter 8803251881Speter *copy_id_p = apr_pstrcat(pool, "_", cur_copy_id, (char *)NULL); 8804251881Speter 8805251881Speter return SVN_NO_ERROR; 8806251881Speter} 8807251881Speter 8808251881Speter/* Write out the zeroth revision for filesystem FS. */ 8809251881Speterstatic svn_error_t * 8810251881Speterwrite_revision_zero(svn_fs_t *fs) 8811251881Speter{ 8812251881Speter const char *path_revision_zero = path_rev(fs, 0, fs->pool); 8813251881Speter apr_hash_t *proplist; 8814251881Speter svn_string_t date; 8815251881Speter 8816251881Speter /* Write out a rev file for revision 0. */ 8817251881Speter SVN_ERR(svn_io_file_create(path_revision_zero, 8818251881Speter "PLAIN\nEND\nENDREP\n" 8819251881Speter "id: 0.0.r0/17\n" 8820251881Speter "type: dir\n" 8821251881Speter "count: 0\n" 8822251881Speter "text: 0 0 4 4 " 8823251881Speter "2d2977d1c96f487abe4a1e202dd03b4e\n" 8824251881Speter "cpath: /\n" 8825251881Speter "\n\n17 107\n", fs->pool)); 8826251881Speter SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, fs->pool)); 8827251881Speter 8828251881Speter /* Set a date on revision 0. */ 8829251881Speter date.data = svn_time_to_cstring(apr_time_now(), fs->pool); 8830251881Speter date.len = strlen(date.data); 8831251881Speter proplist = apr_hash_make(fs->pool); 8832251881Speter svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date); 8833251881Speter return set_revision_proplist(fs, 0, proplist, fs->pool); 8834251881Speter} 8835251881Speter 8836251881Spetersvn_error_t * 8837251881Spetersvn_fs_fs__create(svn_fs_t *fs, 8838251881Speter const char *path, 8839251881Speter apr_pool_t *pool) 8840251881Speter{ 8841251881Speter int format = SVN_FS_FS__FORMAT_NUMBER; 8842251881Speter fs_fs_data_t *ffd = fs->fsap_data; 8843251881Speter 8844251881Speter fs->path = apr_pstrdup(pool, path); 8845251881Speter /* See if compatibility with older versions was explicitly requested. */ 8846251881Speter if (fs->config) 8847251881Speter { 8848251881Speter if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE)) 8849251881Speter format = 1; 8850251881Speter else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE)) 8851251881Speter format = 2; 8852251881Speter else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE)) 8853251881Speter format = 3; 8854251881Speter else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE)) 8855251881Speter format = 4; 8856251881Speter } 8857251881Speter ffd->format = format; 8858251881Speter 8859251881Speter /* Override the default linear layout if this is a new-enough format. */ 8860251881Speter if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT) 8861251881Speter ffd->max_files_per_dir = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR; 8862251881Speter 8863251881Speter /* Create the revision data directories. */ 8864251881Speter if (ffd->max_files_per_dir) 8865251881Speter SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(fs, 0, pool), pool)); 8866251881Speter else 8867251881Speter SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_REVS_DIR, 8868251881Speter pool), 8869251881Speter pool)); 8870251881Speter 8871251881Speter /* Create the revprops directory. */ 8872251881Speter if (ffd->max_files_per_dir) 8873251881Speter SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(fs, 0, pool), 8874251881Speter pool)); 8875251881Speter else 8876251881Speter SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, 8877251881Speter PATH_REVPROPS_DIR, 8878251881Speter pool), 8879251881Speter pool)); 8880251881Speter 8881251881Speter /* Create the transaction directory. */ 8882251881Speter SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXNS_DIR, 8883251881Speter pool), 8884251881Speter pool)); 8885251881Speter 8886251881Speter /* Create the protorevs directory. */ 8887251881Speter if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) 8888251881Speter SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXN_PROTOS_DIR, 8889251881Speter pool), 8890251881Speter pool)); 8891251881Speter 8892251881Speter /* Create the 'current' file. */ 8893251881Speter SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(fs, pool), 8894251881Speter (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT 8895251881Speter ? "0\n" : "0 1 1\n"), 8896251881Speter pool)); 8897251881Speter SVN_ERR(svn_io_file_create(path_lock(fs, pool), "", pool)); 8898251881Speter SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, pool)); 8899251881Speter 8900251881Speter SVN_ERR(write_revision_zero(fs)); 8901251881Speter 8902269847Speter /* Create the fsfs.conf file if supported. Older server versions would 8903269847Speter simply ignore the file but that might result in a different behavior 8904269847Speter than with the later releases. Also, hotcopy would ignore, i.e. not 8905269847Speter copy, a fsfs.conf with old formats. */ 8906269847Speter if (ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE) 8907269847Speter SVN_ERR(write_config(fs, pool)); 8908251881Speter 8909251881Speter SVN_ERR(read_config(ffd, fs->path, pool)); 8910251881Speter 8911251881Speter /* Create the min unpacked rev file. */ 8912251881Speter if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 8913251881Speter SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool)); 8914251881Speter 8915251881Speter /* Create the txn-current file if the repository supports 8916251881Speter the transaction sequence file. */ 8917251881Speter if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 8918251881Speter { 8919251881Speter SVN_ERR(svn_io_file_create(path_txn_current(fs, pool), 8920251881Speter "0\n", pool)); 8921251881Speter SVN_ERR(svn_io_file_create(path_txn_current_lock(fs, pool), 8922251881Speter "", pool)); 8923251881Speter } 8924251881Speter 8925251881Speter /* This filesystem is ready. Stamp it with a format number. */ 8926251881Speter SVN_ERR(write_format(path_format(fs, pool), 8927251881Speter ffd->format, ffd->max_files_per_dir, FALSE, pool)); 8928251881Speter 8929251881Speter ffd->youngest_rev_cache = 0; 8930251881Speter return SVN_NO_ERROR; 8931251881Speter} 8932251881Speter 8933251881Speter/* Part of the recovery procedure. Return the largest revision *REV in 8934251881Speter filesystem FS. Use POOL for temporary allocation. */ 8935251881Speterstatic svn_error_t * 8936251881Speterrecover_get_largest_revision(svn_fs_t *fs, svn_revnum_t *rev, apr_pool_t *pool) 8937251881Speter{ 8938251881Speter /* Discovering the largest revision in the filesystem would be an 8939251881Speter expensive operation if we did a readdir() or searched linearly, 8940251881Speter so we'll do a form of binary search. left is a revision that we 8941251881Speter know exists, right a revision that we know does not exist. */ 8942251881Speter apr_pool_t *iterpool; 8943251881Speter svn_revnum_t left, right = 1; 8944251881Speter 8945251881Speter iterpool = svn_pool_create(pool); 8946251881Speter /* Keep doubling right, until we find a revision that doesn't exist. */ 8947251881Speter while (1) 8948251881Speter { 8949251881Speter svn_error_t *err; 8950251881Speter apr_file_t *file; 8951251881Speter 8952251881Speter err = open_pack_or_rev_file(&file, fs, right, iterpool); 8953251881Speter svn_pool_clear(iterpool); 8954251881Speter 8955251881Speter if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION) 8956251881Speter { 8957251881Speter svn_error_clear(err); 8958251881Speter break; 8959251881Speter } 8960251881Speter else 8961251881Speter SVN_ERR(err); 8962251881Speter 8963251881Speter right <<= 1; 8964251881Speter } 8965251881Speter 8966251881Speter left = right >> 1; 8967251881Speter 8968251881Speter /* We know that left exists and right doesn't. Do a normal bsearch to find 8969251881Speter the last revision. */ 8970251881Speter while (left + 1 < right) 8971251881Speter { 8972251881Speter svn_revnum_t probe = left + ((right - left) / 2); 8973251881Speter svn_error_t *err; 8974251881Speter apr_file_t *file; 8975251881Speter 8976251881Speter err = open_pack_or_rev_file(&file, fs, probe, iterpool); 8977251881Speter svn_pool_clear(iterpool); 8978251881Speter 8979251881Speter if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION) 8980251881Speter { 8981251881Speter svn_error_clear(err); 8982251881Speter right = probe; 8983251881Speter } 8984251881Speter else 8985251881Speter { 8986251881Speter SVN_ERR(err); 8987251881Speter left = probe; 8988251881Speter } 8989251881Speter } 8990251881Speter 8991251881Speter svn_pool_destroy(iterpool); 8992251881Speter 8993251881Speter /* left is now the largest revision that exists. */ 8994251881Speter *rev = left; 8995251881Speter return SVN_NO_ERROR; 8996251881Speter} 8997251881Speter 8998251881Speter/* A baton for reading a fixed amount from an open file. For 8999251881Speter recover_find_max_ids() below. */ 9000251881Speterstruct recover_read_from_file_baton 9001251881Speter{ 9002251881Speter apr_file_t *file; 9003251881Speter apr_pool_t *pool; 9004251881Speter apr_off_t remaining; 9005251881Speter}; 9006251881Speter 9007251881Speter/* A stream read handler used by recover_find_max_ids() below. 9008251881Speter Read and return at most BATON->REMAINING bytes from the stream, 9009251881Speter returning nothing after that to indicate EOF. */ 9010251881Speterstatic svn_error_t * 9011251881Speterread_handler_recover(void *baton, char *buffer, apr_size_t *len) 9012251881Speter{ 9013251881Speter struct recover_read_from_file_baton *b = baton; 9014251881Speter svn_filesize_t bytes_to_read = *len; 9015251881Speter 9016251881Speter if (b->remaining == 0) 9017251881Speter { 9018251881Speter /* Return a successful read of zero bytes to signal EOF. */ 9019251881Speter *len = 0; 9020251881Speter return SVN_NO_ERROR; 9021251881Speter } 9022251881Speter 9023251881Speter if (bytes_to_read > b->remaining) 9024251881Speter bytes_to_read = b->remaining; 9025251881Speter b->remaining -= bytes_to_read; 9026251881Speter 9027251881Speter return svn_io_file_read_full2(b->file, buffer, (apr_size_t) bytes_to_read, 9028251881Speter len, NULL, b->pool); 9029251881Speter} 9030251881Speter 9031251881Speter/* Part of the recovery procedure. Read the directory noderev at offset 9032251881Speter OFFSET of file REV_FILE (the revision file of revision REV of 9033251881Speter filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id 9034251881Speter and copy-id of that node, if greater than the current value stored 9035251881Speter in either. Recurse into any child directories that were modified in 9036251881Speter this revision. 9037251881Speter 9038251881Speter MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE. 9039251881Speter 9040251881Speter Perform temporary allocation in POOL. */ 9041251881Speterstatic svn_error_t * 9042251881Speterrecover_find_max_ids(svn_fs_t *fs, svn_revnum_t rev, 9043251881Speter apr_file_t *rev_file, apr_off_t offset, 9044251881Speter char *max_node_id, char *max_copy_id, 9045251881Speter apr_pool_t *pool) 9046251881Speter{ 9047251881Speter apr_hash_t *headers; 9048251881Speter char *value; 9049251881Speter representation_t *data_rep; 9050251881Speter struct rep_args *ra; 9051251881Speter struct recover_read_from_file_baton baton; 9052251881Speter svn_stream_t *stream; 9053251881Speter apr_hash_t *entries; 9054251881Speter apr_hash_index_t *hi; 9055251881Speter apr_pool_t *iterpool; 9056251881Speter 9057251881Speter SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool)); 9058251881Speter SVN_ERR(read_header_block(&headers, svn_stream_from_aprfile2(rev_file, TRUE, 9059251881Speter pool), 9060251881Speter pool)); 9061251881Speter 9062251881Speter /* Check that this is a directory. It should be. */ 9063251881Speter value = svn_hash_gets(headers, HEADER_TYPE); 9064251881Speter if (value == NULL || strcmp(value, KIND_DIR) != 0) 9065251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 9066251881Speter _("Recovery encountered a non-directory node")); 9067251881Speter 9068251881Speter /* Get the data location. No data location indicates an empty directory. */ 9069251881Speter value = svn_hash_gets(headers, HEADER_TEXT); 9070251881Speter if (!value) 9071251881Speter return SVN_NO_ERROR; 9072251881Speter SVN_ERR(read_rep_offsets(&data_rep, value, NULL, FALSE, pool)); 9073251881Speter 9074251881Speter /* If the directory's data representation wasn't changed in this revision, 9075251881Speter we've already scanned the directory's contents for noderevs, so we don't 9076251881Speter need to again. This will occur if a property is changed on a directory 9077251881Speter without changing the directory's contents. */ 9078251881Speter if (data_rep->revision != rev) 9079251881Speter return SVN_NO_ERROR; 9080251881Speter 9081251881Speter /* We could use get_dir_contents(), but this is much cheaper. It does 9082251881Speter rely on directory entries being stored as PLAIN reps, though. */ 9083251881Speter offset = data_rep->offset; 9084251881Speter SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool)); 9085251881Speter SVN_ERR(read_rep_line(&ra, rev_file, pool)); 9086251881Speter if (ra->is_delta) 9087251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 9088251881Speter _("Recovery encountered a deltified directory " 9089251881Speter "representation")); 9090251881Speter 9091251881Speter /* Now create a stream that's allowed to read only as much data as is 9092251881Speter stored in the representation. */ 9093251881Speter baton.file = rev_file; 9094251881Speter baton.pool = pool; 9095289166Speter baton.remaining = data_rep->expanded_size 9096289166Speter ? data_rep->expanded_size 9097289166Speter : data_rep->size; 9098251881Speter stream = svn_stream_create(&baton, pool); 9099251881Speter svn_stream_set_read(stream, read_handler_recover); 9100251881Speter 9101251881Speter /* Now read the entries from that stream. */ 9102251881Speter entries = apr_hash_make(pool); 9103251881Speter SVN_ERR(svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool)); 9104251881Speter SVN_ERR(svn_stream_close(stream)); 9105251881Speter 9106251881Speter /* Now check each of the entries in our directory to find new node and 9107251881Speter copy ids, and recurse into new subdirectories. */ 9108251881Speter iterpool = svn_pool_create(pool); 9109251881Speter for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) 9110251881Speter { 9111251881Speter char *str_val; 9112251881Speter char *str; 9113251881Speter svn_node_kind_t kind; 9114251881Speter svn_fs_id_t *id; 9115251881Speter const char *node_id, *copy_id; 9116251881Speter apr_off_t child_dir_offset; 9117251881Speter const svn_string_t *path = svn__apr_hash_index_val(hi); 9118251881Speter 9119251881Speter svn_pool_clear(iterpool); 9120251881Speter 9121251881Speter str_val = apr_pstrdup(iterpool, path->data); 9122251881Speter 9123251881Speter str = svn_cstring_tokenize(" ", &str_val); 9124251881Speter if (str == NULL) 9125251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 9126251881Speter _("Directory entry corrupt")); 9127251881Speter 9128251881Speter if (strcmp(str, KIND_FILE) == 0) 9129251881Speter kind = svn_node_file; 9130251881Speter else if (strcmp(str, KIND_DIR) == 0) 9131251881Speter kind = svn_node_dir; 9132251881Speter else 9133251881Speter { 9134251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 9135251881Speter _("Directory entry corrupt")); 9136251881Speter } 9137251881Speter 9138251881Speter str = svn_cstring_tokenize(" ", &str_val); 9139251881Speter if (str == NULL) 9140251881Speter return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 9141251881Speter _("Directory entry corrupt")); 9142251881Speter 9143251881Speter id = svn_fs_fs__id_parse(str, strlen(str), iterpool); 9144251881Speter 9145251881Speter if (svn_fs_fs__id_rev(id) != rev) 9146251881Speter { 9147251881Speter /* If the node wasn't modified in this revision, we've already 9148251881Speter checked the node and copy id. */ 9149251881Speter continue; 9150251881Speter } 9151251881Speter 9152251881Speter node_id = svn_fs_fs__id_node_id(id); 9153251881Speter copy_id = svn_fs_fs__id_copy_id(id); 9154251881Speter 9155251881Speter if (svn_fs_fs__key_compare(node_id, max_node_id) > 0) 9156251881Speter { 9157251881Speter SVN_ERR_ASSERT(strlen(node_id) < MAX_KEY_SIZE); 9158251881Speter apr_cpystrn(max_node_id, node_id, MAX_KEY_SIZE); 9159251881Speter } 9160251881Speter if (svn_fs_fs__key_compare(copy_id, max_copy_id) > 0) 9161251881Speter { 9162251881Speter SVN_ERR_ASSERT(strlen(copy_id) < MAX_KEY_SIZE); 9163251881Speter apr_cpystrn(max_copy_id, copy_id, MAX_KEY_SIZE); 9164251881Speter } 9165251881Speter 9166251881Speter if (kind == svn_node_file) 9167251881Speter continue; 9168251881Speter 9169251881Speter child_dir_offset = svn_fs_fs__id_offset(id); 9170251881Speter SVN_ERR(recover_find_max_ids(fs, rev, rev_file, child_dir_offset, 9171251881Speter max_node_id, max_copy_id, iterpool)); 9172251881Speter } 9173251881Speter svn_pool_destroy(iterpool); 9174251881Speter 9175251881Speter return SVN_NO_ERROR; 9176251881Speter} 9177251881Speter 9178251881Speter/* Return TRUE, if for REVISION in FS, we can find the revprop pack file. 9179251881Speter * Use POOL for temporary allocations. 9180251881Speter * Set *MISSING, if the reason is a missing manifest or pack file. 9181251881Speter */ 9182251881Speterstatic svn_boolean_t 9183251881Speterpacked_revprop_available(svn_boolean_t *missing, 9184251881Speter svn_fs_t *fs, 9185251881Speter svn_revnum_t revision, 9186251881Speter apr_pool_t *pool) 9187251881Speter{ 9188251881Speter fs_fs_data_t *ffd = fs->fsap_data; 9189251881Speter svn_stringbuf_t *content = NULL; 9190251881Speter 9191251881Speter /* try to read the manifest file */ 9192251881Speter const char *folder = path_revprops_pack_shard(fs, revision, pool); 9193251881Speter const char *manifest_path = svn_dirent_join(folder, PATH_MANIFEST, pool); 9194251881Speter 9195251881Speter svn_error_t *err = try_stringbuf_from_file(&content, 9196251881Speter missing, 9197251881Speter manifest_path, 9198251881Speter FALSE, 9199251881Speter pool); 9200251881Speter 9201251881Speter /* if the manifest cannot be read, consider the pack files inaccessible 9202251881Speter * even if the file itself exists. */ 9203251881Speter if (err) 9204251881Speter { 9205251881Speter svn_error_clear(err); 9206251881Speter return FALSE; 9207251881Speter } 9208251881Speter 9209251881Speter if (*missing) 9210251881Speter return FALSE; 9211251881Speter 9212251881Speter /* parse manifest content until we find the entry for REVISION. 9213251881Speter * Revision 0 is never packed. */ 9214251881Speter revision = revision < ffd->max_files_per_dir 9215251881Speter ? revision - 1 9216251881Speter : revision % ffd->max_files_per_dir; 9217251881Speter while (content->data) 9218251881Speter { 9219251881Speter char *next = strchr(content->data, '\n'); 9220251881Speter if (next) 9221251881Speter { 9222251881Speter *next = 0; 9223251881Speter ++next; 9224251881Speter } 9225251881Speter 9226251881Speter if (revision-- == 0) 9227251881Speter { 9228251881Speter /* the respective pack file must exist (and be a file) */ 9229251881Speter svn_node_kind_t kind; 9230251881Speter err = svn_io_check_path(svn_dirent_join(folder, content->data, 9231251881Speter pool), 9232251881Speter &kind, pool); 9233251881Speter if (err) 9234251881Speter { 9235251881Speter svn_error_clear(err); 9236251881Speter return FALSE; 9237251881Speter } 9238251881Speter 9239251881Speter *missing = kind == svn_node_none; 9240251881Speter return kind == svn_node_file; 9241251881Speter } 9242251881Speter 9243251881Speter content->data = next; 9244251881Speter } 9245251881Speter 9246251881Speter return FALSE; 9247251881Speter} 9248251881Speter 9249251881Speter/* Baton used for recover_body below. */ 9250251881Speterstruct recover_baton { 9251251881Speter svn_fs_t *fs; 9252251881Speter svn_cancel_func_t cancel_func; 9253251881Speter void *cancel_baton; 9254251881Speter}; 9255251881Speter 9256251881Speter/* The work-horse for svn_fs_fs__recover, called with the FS 9257251881Speter write lock. This implements the svn_fs_fs__with_write_lock() 9258251881Speter 'body' callback type. BATON is a 'struct recover_baton *'. */ 9259251881Speterstatic svn_error_t * 9260251881Speterrecover_body(void *baton, apr_pool_t *pool) 9261251881Speter{ 9262251881Speter struct recover_baton *b = baton; 9263251881Speter svn_fs_t *fs = b->fs; 9264251881Speter fs_fs_data_t *ffd = fs->fsap_data; 9265251881Speter svn_revnum_t max_rev; 9266251881Speter char next_node_id_buf[MAX_KEY_SIZE], next_copy_id_buf[MAX_KEY_SIZE]; 9267251881Speter char *next_node_id = NULL, *next_copy_id = NULL; 9268251881Speter svn_revnum_t youngest_rev; 9269251881Speter svn_node_kind_t youngest_revprops_kind; 9270251881Speter 9271251881Speter /* Lose potentially corrupted data in temp files */ 9272251881Speter SVN_ERR(cleanup_revprop_namespace(fs)); 9273251881Speter 9274251881Speter /* We need to know the largest revision in the filesystem. */ 9275251881Speter SVN_ERR(recover_get_largest_revision(fs, &max_rev, pool)); 9276251881Speter 9277251881Speter /* Get the expected youngest revision */ 9278251881Speter SVN_ERR(get_youngest(&youngest_rev, fs->path, pool)); 9279251881Speter 9280251881Speter /* Policy note: 9281251881Speter 9282251881Speter Since the revprops file is written after the revs file, the true 9283251881Speter maximum available revision is the youngest one for which both are 9284251881Speter present. That's probably the same as the max_rev we just found, 9285251881Speter but if it's not, we could, in theory, repeatedly decrement 9286251881Speter max_rev until we find a revision that has both a revs and 9287251881Speter revprops file, then write db/current with that. 9288251881Speter 9289251881Speter But we choose not to. If a repository is so corrupt that it's 9290251881Speter missing at least one revprops file, we shouldn't assume that the 9291251881Speter youngest revision for which both the revs and revprops files are 9292251881Speter present is healthy. In other words, we're willing to recover 9293251881Speter from a missing or out-of-date db/current file, because db/current 9294251881Speter is truly redundant -- it's basically a cache so we don't have to 9295251881Speter find max_rev each time, albeit a cache with unusual semantics, 9296251881Speter since it also officially defines when a revision goes live. But 9297251881Speter if we're missing more than the cache, it's time to back out and 9298251881Speter let the admin reconstruct things by hand: correctness at that 9299251881Speter point may depend on external things like checking a commit email 9300251881Speter list, looking in particular working copies, etc. 9301251881Speter 9302251881Speter This policy matches well with a typical naive backup scenario. 9303251881Speter Say you're rsyncing your FSFS repository nightly to the same 9304251881Speter location. Once revs and revprops are written, you've got the 9305251881Speter maximum rev; if the backup should bomb before db/current is 9306251881Speter written, then db/current could stay arbitrarily out-of-date, but 9307251881Speter we can still recover. It's a small window, but we might as well 9308251881Speter do what we can. */ 9309251881Speter 9310251881Speter /* Even if db/current were missing, it would be created with 0 by 9311251881Speter get_youngest(), so this conditional remains valid. */ 9312251881Speter if (youngest_rev > max_rev) 9313251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 9314251881Speter _("Expected current rev to be <= %ld " 9315251881Speter "but found %ld"), max_rev, youngest_rev); 9316251881Speter 9317251881Speter /* We only need to search for maximum IDs for old FS formats which 9318251881Speter se global ID counters. */ 9319251881Speter if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 9320251881Speter { 9321251881Speter /* Next we need to find the maximum node id and copy id in use across the 9322251881Speter filesystem. Unfortunately, the only way we can get this information 9323251881Speter is to scan all the noderevs of all the revisions and keep track as 9324251881Speter we go along. */ 9325251881Speter svn_revnum_t rev; 9326251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 9327251881Speter char max_node_id[MAX_KEY_SIZE] = "0", max_copy_id[MAX_KEY_SIZE] = "0"; 9328251881Speter apr_size_t len; 9329251881Speter 9330251881Speter for (rev = 0; rev <= max_rev; rev++) 9331251881Speter { 9332251881Speter apr_file_t *rev_file; 9333251881Speter apr_off_t root_offset; 9334251881Speter 9335251881Speter svn_pool_clear(iterpool); 9336251881Speter 9337251881Speter if (b->cancel_func) 9338251881Speter SVN_ERR(b->cancel_func(b->cancel_baton)); 9339251881Speter 9340251881Speter SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, iterpool)); 9341251881Speter SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file, fs, rev, 9342251881Speter iterpool)); 9343251881Speter SVN_ERR(recover_find_max_ids(fs, rev, rev_file, root_offset, 9344251881Speter max_node_id, max_copy_id, iterpool)); 9345251881Speter SVN_ERR(svn_io_file_close(rev_file, iterpool)); 9346251881Speter } 9347251881Speter svn_pool_destroy(iterpool); 9348251881Speter 9349251881Speter /* Now that we finally have the maximum revision, node-id and copy-id, we 9350251881Speter can bump the two ids to get the next of each. */ 9351251881Speter len = strlen(max_node_id); 9352251881Speter svn_fs_fs__next_key(max_node_id, &len, next_node_id_buf); 9353251881Speter next_node_id = next_node_id_buf; 9354251881Speter len = strlen(max_copy_id); 9355251881Speter svn_fs_fs__next_key(max_copy_id, &len, next_copy_id_buf); 9356251881Speter next_copy_id = next_copy_id_buf; 9357251881Speter } 9358251881Speter 9359251881Speter /* Before setting current, verify that there is a revprops file 9360251881Speter for the youngest revision. (Issue #2992) */ 9361251881Speter SVN_ERR(svn_io_check_path(path_revprops(fs, max_rev, pool), 9362251881Speter &youngest_revprops_kind, pool)); 9363251881Speter if (youngest_revprops_kind == svn_node_none) 9364251881Speter { 9365251881Speter svn_boolean_t missing = TRUE; 9366251881Speter if (!packed_revprop_available(&missing, fs, max_rev, pool)) 9367251881Speter { 9368251881Speter if (missing) 9369251881Speter { 9370251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 9371251881Speter _("Revision %ld has a revs file but no " 9372251881Speter "revprops file"), 9373251881Speter max_rev); 9374251881Speter } 9375251881Speter else 9376251881Speter { 9377251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 9378251881Speter _("Revision %ld has a revs file but the " 9379251881Speter "revprops file is inaccessible"), 9380251881Speter max_rev); 9381251881Speter } 9382251881Speter } 9383251881Speter } 9384251881Speter else if (youngest_revprops_kind != svn_node_file) 9385251881Speter { 9386251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 9387251881Speter _("Revision %ld has a non-file where its " 9388251881Speter "revprops file should be"), 9389251881Speter max_rev); 9390251881Speter } 9391251881Speter 9392251881Speter /* Prune younger-than-(newfound-youngest) revisions from the rep 9393251881Speter cache if sharing is enabled taking care not to create the cache 9394251881Speter if it does not exist. */ 9395251881Speter if (ffd->rep_sharing_allowed) 9396251881Speter { 9397251881Speter svn_boolean_t rep_cache_exists; 9398251881Speter 9399251881Speter SVN_ERR(svn_fs_fs__exists_rep_cache(&rep_cache_exists, fs, pool)); 9400251881Speter if (rep_cache_exists) 9401251881Speter SVN_ERR(svn_fs_fs__del_rep_reference(fs, max_rev, pool)); 9402251881Speter } 9403251881Speter 9404251881Speter /* Now store the discovered youngest revision, and the next IDs if 9405251881Speter relevant, in a new 'current' file. */ 9406251881Speter return write_current(fs, max_rev, next_node_id, next_copy_id, pool); 9407251881Speter} 9408251881Speter 9409251881Speter/* This implements the fs_library_vtable_t.recover() API. */ 9410251881Spetersvn_error_t * 9411251881Spetersvn_fs_fs__recover(svn_fs_t *fs, 9412251881Speter svn_cancel_func_t cancel_func, void *cancel_baton, 9413251881Speter apr_pool_t *pool) 9414251881Speter{ 9415251881Speter struct recover_baton b; 9416251881Speter 9417251881Speter /* We have no way to take out an exclusive lock in FSFS, so we're 9418251881Speter restricted as to the types of recovery we can do. Luckily, 9419251881Speter we just want to recreate the 'current' file, and we can do that just 9420251881Speter by blocking other writers. */ 9421251881Speter b.fs = fs; 9422251881Speter b.cancel_func = cancel_func; 9423251881Speter b.cancel_baton = cancel_baton; 9424251881Speter return svn_fs_fs__with_write_lock(fs, recover_body, &b, pool); 9425251881Speter} 9426251881Speter 9427251881Spetersvn_error_t * 9428251881Spetersvn_fs_fs__set_uuid(svn_fs_t *fs, 9429251881Speter const char *uuid, 9430251881Speter apr_pool_t *pool) 9431251881Speter{ 9432251881Speter char *my_uuid; 9433251881Speter apr_size_t my_uuid_len; 9434251881Speter const char *tmp_path; 9435251881Speter const char *uuid_path = path_uuid(fs, pool); 9436251881Speter 9437251881Speter if (! uuid) 9438251881Speter uuid = svn_uuid_generate(pool); 9439251881Speter 9440251881Speter /* Make sure we have a copy in FS->POOL, and append a newline. */ 9441251881Speter my_uuid = apr_pstrcat(fs->pool, uuid, "\n", (char *)NULL); 9442251881Speter my_uuid_len = strlen(my_uuid); 9443251881Speter 9444251881Speter SVN_ERR(svn_io_write_unique(&tmp_path, 9445251881Speter svn_dirent_dirname(uuid_path, pool), 9446251881Speter my_uuid, my_uuid_len, 9447251881Speter svn_io_file_del_none, pool)); 9448251881Speter 9449251881Speter /* We use the permissions of the 'current' file, because the 'uuid' 9450251881Speter file does not exist during repository creation. */ 9451251881Speter SVN_ERR(move_into_place(tmp_path, uuid_path, 9452251881Speter svn_fs_fs__path_current(fs, pool), pool)); 9453251881Speter 9454251881Speter /* Remove the newline we added, and stash the UUID. */ 9455251881Speter my_uuid[my_uuid_len - 1] = '\0'; 9456251881Speter fs->uuid = my_uuid; 9457251881Speter 9458251881Speter return SVN_NO_ERROR; 9459251881Speter} 9460251881Speter 9461251881Speter/** Node origin lazy cache. */ 9462251881Speter 9463251881Speter/* If directory PATH does not exist, create it and give it the same 9464251881Speter permissions as FS_path.*/ 9465251881Spetersvn_error_t * 9466251881Spetersvn_fs_fs__ensure_dir_exists(const char *path, 9467251881Speter const char *fs_path, 9468251881Speter apr_pool_t *pool) 9469251881Speter{ 9470251881Speter svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool); 9471251881Speter if (err && APR_STATUS_IS_EEXIST(err->apr_err)) 9472251881Speter { 9473251881Speter svn_error_clear(err); 9474251881Speter return SVN_NO_ERROR; 9475251881Speter } 9476251881Speter SVN_ERR(err); 9477251881Speter 9478251881Speter /* We successfully created a new directory. Dup the permissions 9479251881Speter from FS->path. */ 9480251881Speter return svn_io_copy_perms(fs_path, path, pool); 9481251881Speter} 9482251881Speter 9483251881Speter/* Set *NODE_ORIGINS to a hash mapping 'const char *' node IDs to 9484251881Speter 'svn_string_t *' node revision IDs. Use POOL for allocations. */ 9485251881Speterstatic svn_error_t * 9486251881Speterget_node_origins_from_file(svn_fs_t *fs, 9487251881Speter apr_hash_t **node_origins, 9488251881Speter const char *node_origins_file, 9489251881Speter apr_pool_t *pool) 9490251881Speter{ 9491251881Speter apr_file_t *fd; 9492251881Speter svn_error_t *err; 9493251881Speter svn_stream_t *stream; 9494251881Speter 9495251881Speter *node_origins = NULL; 9496251881Speter err = svn_io_file_open(&fd, node_origins_file, 9497251881Speter APR_READ, APR_OS_DEFAULT, pool); 9498251881Speter if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 9499251881Speter { 9500251881Speter svn_error_clear(err); 9501251881Speter return SVN_NO_ERROR; 9502251881Speter } 9503251881Speter SVN_ERR(err); 9504251881Speter 9505251881Speter stream = svn_stream_from_aprfile2(fd, FALSE, pool); 9506251881Speter *node_origins = apr_hash_make(pool); 9507251881Speter SVN_ERR(svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool)); 9508251881Speter return svn_stream_close(stream); 9509251881Speter} 9510251881Speter 9511251881Spetersvn_error_t * 9512251881Spetersvn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id, 9513251881Speter svn_fs_t *fs, 9514251881Speter const char *node_id, 9515251881Speter apr_pool_t *pool) 9516251881Speter{ 9517251881Speter apr_hash_t *node_origins; 9518251881Speter 9519251881Speter *origin_id = NULL; 9520251881Speter SVN_ERR(get_node_origins_from_file(fs, &node_origins, 9521251881Speter path_node_origin(fs, node_id, pool), 9522251881Speter pool)); 9523251881Speter if (node_origins) 9524251881Speter { 9525251881Speter svn_string_t *origin_id_str = 9526251881Speter svn_hash_gets(node_origins, node_id); 9527251881Speter if (origin_id_str) 9528251881Speter *origin_id = svn_fs_fs__id_parse(origin_id_str->data, 9529251881Speter origin_id_str->len, pool); 9530251881Speter } 9531251881Speter return SVN_NO_ERROR; 9532251881Speter} 9533251881Speter 9534251881Speter 9535251881Speter/* Helper for svn_fs_fs__set_node_origin. Takes a NODE_ID/NODE_REV_ID 9536251881Speter pair and adds it to the NODE_ORIGINS_PATH file. */ 9537251881Speterstatic svn_error_t * 9538251881Speterset_node_origins_for_file(svn_fs_t *fs, 9539251881Speter const char *node_origins_path, 9540251881Speter const char *node_id, 9541251881Speter svn_string_t *node_rev_id, 9542251881Speter apr_pool_t *pool) 9543251881Speter{ 9544251881Speter const char *path_tmp; 9545251881Speter svn_stream_t *stream; 9546251881Speter apr_hash_t *origins_hash; 9547251881Speter svn_string_t *old_node_rev_id; 9548251881Speter 9549251881Speter SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs->path, 9550251881Speter PATH_NODE_ORIGINS_DIR, 9551251881Speter pool), 9552251881Speter fs->path, pool)); 9553251881Speter 9554251881Speter /* Read the previously existing origins (if any), and merge our 9555251881Speter update with it. */ 9556251881Speter SVN_ERR(get_node_origins_from_file(fs, &origins_hash, 9557251881Speter node_origins_path, pool)); 9558251881Speter if (! origins_hash) 9559251881Speter origins_hash = apr_hash_make(pool); 9560251881Speter 9561251881Speter old_node_rev_id = svn_hash_gets(origins_hash, node_id); 9562251881Speter 9563251881Speter if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id)) 9564251881Speter return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 9565251881Speter _("Node origin for '%s' exists with a different " 9566251881Speter "value (%s) than what we were about to store " 9567251881Speter "(%s)"), 9568251881Speter node_id, old_node_rev_id->data, node_rev_id->data); 9569251881Speter 9570251881Speter svn_hash_sets(origins_hash, node_id, node_rev_id); 9571251881Speter 9572251881Speter /* Sure, there's a race condition here. Two processes could be 9573251881Speter trying to add different cache elements to the same file at the 9574251881Speter same time, and the entries added by the first one to write will 9575251881Speter be lost. But this is just a cache of reconstructible data, so 9576251881Speter we'll accept this problem in return for not having to deal with 9577251881Speter locking overhead. */ 9578251881Speter 9579251881Speter /* Create a temporary file, write out our hash, and close the file. */ 9580251881Speter SVN_ERR(svn_stream_open_unique(&stream, &path_tmp, 9581251881Speter svn_dirent_dirname(node_origins_path, pool), 9582251881Speter svn_io_file_del_none, pool, pool)); 9583251881Speter SVN_ERR(svn_hash_write2(origins_hash, stream, SVN_HASH_TERMINATOR, pool)); 9584251881Speter SVN_ERR(svn_stream_close(stream)); 9585251881Speter 9586251881Speter /* Rename the temp file as the real destination */ 9587251881Speter return svn_io_file_rename(path_tmp, node_origins_path, pool); 9588251881Speter} 9589251881Speter 9590251881Speter 9591251881Spetersvn_error_t * 9592251881Spetersvn_fs_fs__set_node_origin(svn_fs_t *fs, 9593251881Speter const char *node_id, 9594251881Speter const svn_fs_id_t *node_rev_id, 9595251881Speter apr_pool_t *pool) 9596251881Speter{ 9597251881Speter svn_error_t *err; 9598251881Speter const char *filename = path_node_origin(fs, node_id, pool); 9599251881Speter 9600251881Speter err = set_node_origins_for_file(fs, filename, 9601251881Speter node_id, 9602251881Speter svn_fs_fs__id_unparse(node_rev_id, pool), 9603251881Speter pool); 9604251881Speter if (err && APR_STATUS_IS_EACCES(err->apr_err)) 9605251881Speter { 9606251881Speter /* It's just a cache; stop trying if I can't write. */ 9607251881Speter svn_error_clear(err); 9608251881Speter err = NULL; 9609251881Speter } 9610251881Speter return svn_error_trace(err); 9611251881Speter} 9612251881Speter 9613251881Speter 9614251881Spetersvn_error_t * 9615251881Spetersvn_fs_fs__list_transactions(apr_array_header_t **names_p, 9616251881Speter svn_fs_t *fs, 9617251881Speter apr_pool_t *pool) 9618251881Speter{ 9619251881Speter const char *txn_dir; 9620251881Speter apr_hash_t *dirents; 9621251881Speter apr_hash_index_t *hi; 9622251881Speter apr_array_header_t *names; 9623251881Speter apr_size_t ext_len = strlen(PATH_EXT_TXN); 9624251881Speter 9625251881Speter names = apr_array_make(pool, 1, sizeof(const char *)); 9626251881Speter 9627251881Speter /* Get the transactions directory. */ 9628251881Speter txn_dir = svn_dirent_join(fs->path, PATH_TXNS_DIR, pool); 9629251881Speter 9630251881Speter /* Now find a listing of this directory. */ 9631251881Speter SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool)); 9632251881Speter 9633251881Speter /* Loop through all the entries and return anything that ends with '.txn'. */ 9634251881Speter for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi)) 9635251881Speter { 9636251881Speter const char *name = svn__apr_hash_index_key(hi); 9637251881Speter apr_ssize_t klen = svn__apr_hash_index_klen(hi); 9638251881Speter const char *id; 9639251881Speter 9640251881Speter /* The name must end with ".txn" to be considered a transaction. */ 9641251881Speter if ((apr_size_t) klen <= ext_len 9642251881Speter || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0) 9643251881Speter continue; 9644251881Speter 9645251881Speter /* Truncate the ".txn" extension and store the ID. */ 9646251881Speter id = apr_pstrndup(pool, name, strlen(name) - ext_len); 9647251881Speter APR_ARRAY_PUSH(names, const char *) = id; 9648251881Speter } 9649251881Speter 9650251881Speter *names_p = names; 9651251881Speter 9652251881Speter return SVN_NO_ERROR; 9653251881Speter} 9654251881Speter 9655251881Spetersvn_error_t * 9656251881Spetersvn_fs_fs__open_txn(svn_fs_txn_t **txn_p, 9657251881Speter svn_fs_t *fs, 9658251881Speter const char *name, 9659251881Speter apr_pool_t *pool) 9660251881Speter{ 9661251881Speter svn_fs_txn_t *txn; 9662251881Speter svn_node_kind_t kind; 9663251881Speter transaction_t *local_txn; 9664251881Speter 9665251881Speter /* First check to see if the directory exists. */ 9666251881Speter SVN_ERR(svn_io_check_path(path_txn_dir(fs, name, pool), &kind, pool)); 9667251881Speter 9668251881Speter /* Did we find it? */ 9669251881Speter if (kind != svn_node_dir) 9670251881Speter return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL, 9671251881Speter _("No such transaction '%s'"), 9672251881Speter name); 9673251881Speter 9674251881Speter txn = apr_pcalloc(pool, sizeof(*txn)); 9675251881Speter 9676251881Speter /* Read in the root node of this transaction. */ 9677251881Speter txn->id = apr_pstrdup(pool, name); 9678251881Speter txn->fs = fs; 9679251881Speter 9680251881Speter SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, name, pool)); 9681251881Speter 9682251881Speter txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id); 9683251881Speter 9684251881Speter txn->vtable = &txn_vtable; 9685251881Speter *txn_p = txn; 9686251881Speter 9687251881Speter return SVN_NO_ERROR; 9688251881Speter} 9689251881Speter 9690251881Spetersvn_error_t * 9691251881Spetersvn_fs_fs__txn_proplist(apr_hash_t **table_p, 9692251881Speter svn_fs_txn_t *txn, 9693251881Speter apr_pool_t *pool) 9694251881Speter{ 9695251881Speter apr_hash_t *proplist = apr_hash_make(pool); 9696251881Speter SVN_ERR(get_txn_proplist(proplist, txn->fs, txn->id, pool)); 9697251881Speter *table_p = proplist; 9698251881Speter 9699251881Speter return SVN_NO_ERROR; 9700251881Speter} 9701251881Speter 9702251881Spetersvn_error_t * 9703251881Spetersvn_fs_fs__delete_node_revision(svn_fs_t *fs, 9704251881Speter const svn_fs_id_t *id, 9705251881Speter apr_pool_t *pool) 9706251881Speter{ 9707251881Speter node_revision_t *noderev; 9708251881Speter 9709251881Speter SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool)); 9710251881Speter 9711251881Speter /* Delete any mutable property representation. */ 9712251881Speter if (noderev->prop_rep && noderev->prop_rep->txn_id) 9713251881Speter SVN_ERR(svn_io_remove_file2(path_txn_node_props(fs, id, pool), FALSE, 9714251881Speter pool)); 9715251881Speter 9716251881Speter /* Delete any mutable data representation. */ 9717251881Speter if (noderev->data_rep && noderev->data_rep->txn_id 9718251881Speter && noderev->kind == svn_node_dir) 9719251881Speter { 9720251881Speter fs_fs_data_t *ffd = fs->fsap_data; 9721251881Speter SVN_ERR(svn_io_remove_file2(path_txn_node_children(fs, id, pool), FALSE, 9722251881Speter pool)); 9723251881Speter 9724251881Speter /* remove the corresponding entry from the cache, if such exists */ 9725251881Speter if (ffd->txn_dir_cache) 9726251881Speter { 9727251881Speter const char *key = svn_fs_fs__id_unparse(id, pool)->data; 9728251881Speter SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL, pool)); 9729251881Speter } 9730251881Speter } 9731251881Speter 9732251881Speter return svn_io_remove_file2(path_txn_node_rev(fs, id, pool), FALSE, pool); 9733251881Speter} 9734251881Speter 9735251881Speter 9736251881Speter 9737251881Speter/*** Revisions ***/ 9738251881Speter 9739251881Spetersvn_error_t * 9740251881Spetersvn_fs_fs__revision_prop(svn_string_t **value_p, 9741251881Speter svn_fs_t *fs, 9742251881Speter svn_revnum_t rev, 9743251881Speter const char *propname, 9744251881Speter apr_pool_t *pool) 9745251881Speter{ 9746251881Speter apr_hash_t *table; 9747251881Speter 9748251881Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 9749251881Speter SVN_ERR(svn_fs_fs__revision_proplist(&table, fs, rev, pool)); 9750251881Speter 9751251881Speter *value_p = svn_hash_gets(table, propname); 9752251881Speter 9753251881Speter return SVN_NO_ERROR; 9754251881Speter} 9755251881Speter 9756251881Speter 9757251881Speter/* Baton used for change_rev_prop_body below. */ 9758251881Speterstruct change_rev_prop_baton { 9759251881Speter svn_fs_t *fs; 9760251881Speter svn_revnum_t rev; 9761251881Speter const char *name; 9762251881Speter const svn_string_t *const *old_value_p; 9763251881Speter const svn_string_t *value; 9764251881Speter}; 9765251881Speter 9766251881Speter/* The work-horse for svn_fs_fs__change_rev_prop, called with the FS 9767251881Speter write lock. This implements the svn_fs_fs__with_write_lock() 9768251881Speter 'body' callback type. BATON is a 'struct change_rev_prop_baton *'. */ 9769251881Speterstatic svn_error_t * 9770251881Speterchange_rev_prop_body(void *baton, apr_pool_t *pool) 9771251881Speter{ 9772251881Speter struct change_rev_prop_baton *cb = baton; 9773251881Speter apr_hash_t *table; 9774251881Speter 9775251881Speter SVN_ERR(svn_fs_fs__revision_proplist(&table, cb->fs, cb->rev, pool)); 9776251881Speter 9777251881Speter if (cb->old_value_p) 9778251881Speter { 9779251881Speter const svn_string_t *wanted_value = *cb->old_value_p; 9780251881Speter const svn_string_t *present_value = svn_hash_gets(table, cb->name); 9781251881Speter if ((!wanted_value != !present_value) 9782251881Speter || (wanted_value && present_value 9783251881Speter && !svn_string_compare(wanted_value, present_value))) 9784251881Speter { 9785251881Speter /* What we expected isn't what we found. */ 9786251881Speter return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL, 9787251881Speter _("revprop '%s' has unexpected value in " 9788251881Speter "filesystem"), 9789251881Speter cb->name); 9790251881Speter } 9791251881Speter /* Fall through. */ 9792251881Speter } 9793251881Speter svn_hash_sets(table, cb->name, cb->value); 9794251881Speter 9795251881Speter return set_revision_proplist(cb->fs, cb->rev, table, pool); 9796251881Speter} 9797251881Speter 9798251881Spetersvn_error_t * 9799251881Spetersvn_fs_fs__change_rev_prop(svn_fs_t *fs, 9800251881Speter svn_revnum_t rev, 9801251881Speter const char *name, 9802251881Speter const svn_string_t *const *old_value_p, 9803251881Speter const svn_string_t *value, 9804251881Speter apr_pool_t *pool) 9805251881Speter{ 9806251881Speter struct change_rev_prop_baton cb; 9807251881Speter 9808251881Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 9809251881Speter 9810251881Speter cb.fs = fs; 9811251881Speter cb.rev = rev; 9812251881Speter cb.name = name; 9813251881Speter cb.old_value_p = old_value_p; 9814251881Speter cb.value = value; 9815251881Speter 9816251881Speter return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool); 9817251881Speter} 9818251881Speter 9819251881Speter 9820251881Speter 9821251881Speter/*** Transactions ***/ 9822251881Speter 9823251881Spetersvn_error_t * 9824251881Spetersvn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p, 9825251881Speter const svn_fs_id_t **base_root_id_p, 9826251881Speter svn_fs_t *fs, 9827251881Speter const char *txn_name, 9828251881Speter apr_pool_t *pool) 9829251881Speter{ 9830251881Speter transaction_t *txn; 9831251881Speter SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_name, pool)); 9832251881Speter *root_id_p = txn->root_id; 9833251881Speter *base_root_id_p = txn->base_id; 9834251881Speter return SVN_NO_ERROR; 9835251881Speter} 9836251881Speter 9837251881Speter 9838251881Speter/* Generic transaction operations. */ 9839251881Speter 9840251881Spetersvn_error_t * 9841251881Spetersvn_fs_fs__txn_prop(svn_string_t **value_p, 9842251881Speter svn_fs_txn_t *txn, 9843251881Speter const char *propname, 9844251881Speter apr_pool_t *pool) 9845251881Speter{ 9846251881Speter apr_hash_t *table; 9847251881Speter svn_fs_t *fs = txn->fs; 9848251881Speter 9849251881Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 9850251881Speter SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool)); 9851251881Speter 9852251881Speter *value_p = svn_hash_gets(table, propname); 9853251881Speter 9854251881Speter return SVN_NO_ERROR; 9855251881Speter} 9856251881Speter 9857251881Spetersvn_error_t * 9858251881Spetersvn_fs_fs__begin_txn(svn_fs_txn_t **txn_p, 9859251881Speter svn_fs_t *fs, 9860251881Speter svn_revnum_t rev, 9861251881Speter apr_uint32_t flags, 9862251881Speter apr_pool_t *pool) 9863251881Speter{ 9864251881Speter svn_string_t date; 9865251881Speter svn_prop_t prop; 9866251881Speter apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t)); 9867251881Speter 9868251881Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 9869251881Speter 9870251881Speter SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool)); 9871251881Speter 9872251881Speter /* Put a datestamp on the newly created txn, so we always know 9873251881Speter exactly how old it is. (This will help sysadmins identify 9874251881Speter long-abandoned txns that may need to be manually removed.) When 9875251881Speter a txn is promoted to a revision, this property will be 9876251881Speter automatically overwritten with a revision datestamp. */ 9877251881Speter date.data = svn_time_to_cstring(apr_time_now(), pool); 9878251881Speter date.len = strlen(date.data); 9879251881Speter 9880251881Speter prop.name = SVN_PROP_REVISION_DATE; 9881251881Speter prop.value = &date; 9882251881Speter APR_ARRAY_PUSH(props, svn_prop_t) = prop; 9883251881Speter 9884251881Speter /* Set temporary txn props that represent the requested 'flags' 9885251881Speter behaviors. */ 9886251881Speter if (flags & SVN_FS_TXN_CHECK_OOD) 9887251881Speter { 9888251881Speter prop.name = SVN_FS__PROP_TXN_CHECK_OOD; 9889251881Speter prop.value = svn_string_create("true", pool); 9890251881Speter APR_ARRAY_PUSH(props, svn_prop_t) = prop; 9891251881Speter } 9892251881Speter 9893251881Speter if (flags & SVN_FS_TXN_CHECK_LOCKS) 9894251881Speter { 9895251881Speter prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS; 9896251881Speter prop.value = svn_string_create("true", pool); 9897251881Speter APR_ARRAY_PUSH(props, svn_prop_t) = prop; 9898251881Speter } 9899251881Speter 9900251881Speter return svn_fs_fs__change_txn_props(*txn_p, props, pool); 9901251881Speter} 9902251881Speter 9903251881Speter 9904251881Speter/****** Packing FSFS shards *********/ 9905251881Speter 9906251881Speter/* Write a file FILENAME in directory FS_PATH, containing a single line 9907251881Speter * with the number REVNUM in ASCII decimal. Move the file into place 9908251881Speter * atomically, overwriting any existing file. 9909251881Speter * 9910251881Speter * Similar to write_current(). */ 9911251881Speterstatic svn_error_t * 9912251881Speterwrite_revnum_file(const char *fs_path, 9913251881Speter const char *filename, 9914251881Speter svn_revnum_t revnum, 9915251881Speter apr_pool_t *scratch_pool) 9916251881Speter{ 9917251881Speter const char *final_path, *tmp_path; 9918251881Speter svn_stream_t *tmp_stream; 9919251881Speter 9920251881Speter final_path = svn_dirent_join(fs_path, filename, scratch_pool); 9921251881Speter SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_path, fs_path, 9922251881Speter svn_io_file_del_none, 9923251881Speter scratch_pool, scratch_pool)); 9924251881Speter SVN_ERR(svn_stream_printf(tmp_stream, scratch_pool, "%ld\n", revnum)); 9925251881Speter SVN_ERR(svn_stream_close(tmp_stream)); 9926251881Speter SVN_ERR(move_into_place(tmp_path, final_path, final_path, scratch_pool)); 9927251881Speter return SVN_NO_ERROR; 9928251881Speter} 9929251881Speter 9930251881Speter/* Pack the revision SHARD containing exactly MAX_FILES_PER_DIR revisions 9931251881Speter * from SHARD_PATH into the PACK_FILE_DIR, using POOL for allocations. 9932251881Speter * CANCEL_FUNC and CANCEL_BATON are what you think they are. 9933251881Speter * 9934251881Speter * If for some reason we detect a partial packing already performed, we 9935251881Speter * remove the pack file and start again. 9936251881Speter */ 9937251881Speterstatic svn_error_t * 9938251881Speterpack_rev_shard(const char *pack_file_dir, 9939251881Speter const char *shard_path, 9940251881Speter apr_int64_t shard, 9941251881Speter int max_files_per_dir, 9942251881Speter svn_cancel_func_t cancel_func, 9943251881Speter void *cancel_baton, 9944251881Speter apr_pool_t *pool) 9945251881Speter{ 9946251881Speter const char *pack_file_path, *manifest_file_path; 9947251881Speter svn_stream_t *pack_stream, *manifest_stream; 9948251881Speter svn_revnum_t start_rev, end_rev, rev; 9949251881Speter apr_off_t next_offset; 9950251881Speter apr_pool_t *iterpool; 9951251881Speter 9952251881Speter /* Some useful paths. */ 9953251881Speter pack_file_path = svn_dirent_join(pack_file_dir, PATH_PACKED, pool); 9954251881Speter manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST, pool); 9955251881Speter 9956251881Speter /* Remove any existing pack file for this shard, since it is incomplete. */ 9957251881Speter SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton, 9958251881Speter pool)); 9959251881Speter 9960251881Speter /* Create the new directory and pack and manifest files. */ 9961251881Speter SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, pool)); 9962251881Speter SVN_ERR(svn_stream_open_writable(&pack_stream, pack_file_path, pool, 9963251881Speter pool)); 9964251881Speter SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path, 9965251881Speter pool, pool)); 9966251881Speter 9967251881Speter start_rev = (svn_revnum_t) (shard * max_files_per_dir); 9968251881Speter end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1); 9969251881Speter next_offset = 0; 9970251881Speter iterpool = svn_pool_create(pool); 9971251881Speter 9972251881Speter /* Iterate over the revisions in this shard, squashing them together. */ 9973251881Speter for (rev = start_rev; rev <= end_rev; rev++) 9974251881Speter { 9975251881Speter svn_stream_t *rev_stream; 9976251881Speter apr_finfo_t finfo; 9977251881Speter const char *path; 9978251881Speter 9979251881Speter svn_pool_clear(iterpool); 9980251881Speter 9981251881Speter /* Get the size of the file. */ 9982251881Speter path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev), 9983251881Speter iterpool); 9984251881Speter SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool)); 9985251881Speter 9986251881Speter /* Update the manifest. */ 9987251881Speter SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%" APR_OFF_T_FMT 9988251881Speter "\n", next_offset)); 9989251881Speter next_offset += finfo.size; 9990251881Speter 9991251881Speter /* Copy all the bits from the rev file to the end of the pack file. */ 9992251881Speter SVN_ERR(svn_stream_open_readonly(&rev_stream, path, iterpool, iterpool)); 9993251881Speter SVN_ERR(svn_stream_copy3(rev_stream, svn_stream_disown(pack_stream, 9994251881Speter iterpool), 9995251881Speter cancel_func, cancel_baton, iterpool)); 9996251881Speter } 9997251881Speter 9998251881Speter SVN_ERR(svn_stream_close(manifest_stream)); 9999251881Speter SVN_ERR(svn_stream_close(pack_stream)); 10000251881Speter SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool)); 10001251881Speter SVN_ERR(svn_io_set_file_read_only(pack_file_path, FALSE, iterpool)); 10002251881Speter SVN_ERR(svn_io_set_file_read_only(manifest_file_path, FALSE, iterpool)); 10003251881Speter 10004251881Speter svn_pool_destroy(iterpool); 10005251881Speter 10006251881Speter return SVN_NO_ERROR; 10007251881Speter} 10008251881Speter 10009251881Speter/* Copy revprop files for revisions [START_REV, END_REV) from SHARD_PATH 10010251881Speter * to the pack file at PACK_FILE_NAME in PACK_FILE_DIR. 10011251881Speter * 10012251881Speter * The file sizes have already been determined and written to SIZES. 10013251881Speter * Please note that this function will be executed while the filesystem 10014251881Speter * has been locked and that revprops files will therefore not be modified 10015251881Speter * while the pack is in progress. 10016251881Speter * 10017251881Speter * COMPRESSION_LEVEL defines how well the resulting pack file shall be 10018251881Speter * compressed or whether is shall be compressed at all. TOTAL_SIZE is 10019251881Speter * a hint on which initial buffer size we should use to hold the pack file 10020251881Speter * content. 10021251881Speter * 10022251881Speter * CANCEL_FUNC and CANCEL_BATON are used as usual. Temporary allocations 10023251881Speter * are done in SCRATCH_POOL. 10024251881Speter */ 10025251881Speterstatic svn_error_t * 10026251881Spetercopy_revprops(const char *pack_file_dir, 10027251881Speter const char *pack_filename, 10028251881Speter const char *shard_path, 10029251881Speter svn_revnum_t start_rev, 10030251881Speter svn_revnum_t end_rev, 10031251881Speter apr_array_header_t *sizes, 10032251881Speter apr_size_t total_size, 10033251881Speter int compression_level, 10034251881Speter svn_cancel_func_t cancel_func, 10035251881Speter void *cancel_baton, 10036251881Speter apr_pool_t *scratch_pool) 10037251881Speter{ 10038251881Speter svn_stream_t *pack_stream; 10039251881Speter apr_file_t *pack_file; 10040251881Speter svn_revnum_t rev; 10041251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 10042251881Speter svn_stream_t *stream; 10043251881Speter 10044251881Speter /* create empty data buffer and a write stream on top of it */ 10045251881Speter svn_stringbuf_t *uncompressed 10046251881Speter = svn_stringbuf_create_ensure(total_size, scratch_pool); 10047251881Speter svn_stringbuf_t *compressed 10048251881Speter = svn_stringbuf_create_empty(scratch_pool); 10049251881Speter pack_stream = svn_stream_from_stringbuf(uncompressed, scratch_pool); 10050251881Speter 10051251881Speter /* write the pack file header */ 10052251881Speter SVN_ERR(serialize_revprops_header(pack_stream, start_rev, sizes, 0, 10053251881Speter sizes->nelts, iterpool)); 10054251881Speter 10055251881Speter /* Some useful paths. */ 10056251881Speter SVN_ERR(svn_io_file_open(&pack_file, svn_dirent_join(pack_file_dir, 10057251881Speter pack_filename, 10058251881Speter scratch_pool), 10059251881Speter APR_WRITE | APR_CREATE, APR_OS_DEFAULT, 10060251881Speter scratch_pool)); 10061251881Speter 10062251881Speter /* Iterate over the revisions in this shard, squashing them together. */ 10063251881Speter for (rev = start_rev; rev <= end_rev; rev++) 10064251881Speter { 10065251881Speter const char *path; 10066251881Speter 10067251881Speter svn_pool_clear(iterpool); 10068251881Speter 10069251881Speter /* Construct the file name. */ 10070251881Speter path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev), 10071251881Speter iterpool); 10072251881Speter 10073251881Speter /* Copy all the bits from the non-packed revprop file to the end of 10074251881Speter * the pack file. */ 10075251881Speter SVN_ERR(svn_stream_open_readonly(&stream, path, iterpool, iterpool)); 10076251881Speter SVN_ERR(svn_stream_copy3(stream, pack_stream, 10077251881Speter cancel_func, cancel_baton, iterpool)); 10078251881Speter } 10079251881Speter 10080251881Speter /* flush stream buffers to content buffer */ 10081251881Speter SVN_ERR(svn_stream_close(pack_stream)); 10082251881Speter 10083251881Speter /* compress the content (or just store it for COMPRESSION_LEVEL 0) */ 10084251881Speter SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed), 10085251881Speter compressed, compression_level)); 10086251881Speter 10087251881Speter /* write the pack file content to disk */ 10088251881Speter stream = svn_stream_from_aprfile2(pack_file, FALSE, scratch_pool); 10089251881Speter SVN_ERR(svn_stream_write(stream, compressed->data, &compressed->len)); 10090251881Speter SVN_ERR(svn_stream_close(stream)); 10091251881Speter 10092251881Speter svn_pool_destroy(iterpool); 10093251881Speter 10094251881Speter return SVN_NO_ERROR; 10095251881Speter} 10096251881Speter 10097251881Speter/* For the revprop SHARD at SHARD_PATH with exactly MAX_FILES_PER_DIR 10098251881Speter * revprop files in it, create a packed shared at PACK_FILE_DIR. 10099251881Speter * 10100251881Speter * COMPRESSION_LEVEL defines how well the resulting pack file shall be 10101251881Speter * compressed or whether is shall be compressed at all. Individual pack 10102251881Speter * file containing more than one revision will be limited to a size of 10103251881Speter * MAX_PACK_SIZE bytes before compression. 10104251881Speter * 10105251881Speter * CANCEL_FUNC and CANCEL_BATON are used in the usual way. Temporary 10106251881Speter * allocations are done in SCRATCH_POOL. 10107251881Speter */ 10108251881Speterstatic svn_error_t * 10109251881Speterpack_revprops_shard(const char *pack_file_dir, 10110251881Speter const char *shard_path, 10111251881Speter apr_int64_t shard, 10112251881Speter int max_files_per_dir, 10113251881Speter apr_off_t max_pack_size, 10114251881Speter int compression_level, 10115251881Speter svn_cancel_func_t cancel_func, 10116251881Speter void *cancel_baton, 10117251881Speter apr_pool_t *scratch_pool) 10118251881Speter{ 10119251881Speter const char *manifest_file_path, *pack_filename = NULL; 10120251881Speter svn_stream_t *manifest_stream; 10121251881Speter svn_revnum_t start_rev, end_rev, rev; 10122251881Speter apr_off_t total_size; 10123251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 10124251881Speter apr_array_header_t *sizes; 10125251881Speter 10126251881Speter /* Some useful paths. */ 10127251881Speter manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST, 10128251881Speter scratch_pool); 10129251881Speter 10130251881Speter /* Remove any existing pack file for this shard, since it is incomplete. */ 10131251881Speter SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton, 10132251881Speter scratch_pool)); 10133251881Speter 10134251881Speter /* Create the new directory and manifest file stream. */ 10135251881Speter SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, scratch_pool)); 10136251881Speter SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path, 10137251881Speter scratch_pool, scratch_pool)); 10138251881Speter 10139251881Speter /* revisions to handle. Special case: revision 0 */ 10140251881Speter start_rev = (svn_revnum_t) (shard * max_files_per_dir); 10141251881Speter end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1); 10142251881Speter if (start_rev == 0) 10143251881Speter ++start_rev; 10144251881Speter 10145251881Speter /* initialize the revprop size info */ 10146251881Speter sizes = apr_array_make(scratch_pool, max_files_per_dir, sizeof(apr_off_t)); 10147251881Speter total_size = 2 * SVN_INT64_BUFFER_SIZE; 10148251881Speter 10149251881Speter /* Iterate over the revisions in this shard, determine their size and 10150251881Speter * squashing them together into pack files. */ 10151251881Speter for (rev = start_rev; rev <= end_rev; rev++) 10152251881Speter { 10153251881Speter apr_finfo_t finfo; 10154251881Speter const char *path; 10155251881Speter 10156251881Speter svn_pool_clear(iterpool); 10157251881Speter 10158251881Speter /* Get the size of the file. */ 10159251881Speter path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev), 10160251881Speter iterpool); 10161251881Speter SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool)); 10162251881Speter 10163251881Speter /* if we already have started a pack file and this revprop cannot be 10164251881Speter * appended to it, write the previous pack file. */ 10165251881Speter if (sizes->nelts != 0 && 10166251881Speter total_size + SVN_INT64_BUFFER_SIZE + finfo.size > max_pack_size) 10167251881Speter { 10168251881Speter SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path, 10169251881Speter start_rev, rev-1, sizes, (apr_size_t)total_size, 10170251881Speter compression_level, cancel_func, cancel_baton, 10171251881Speter iterpool)); 10172251881Speter 10173251881Speter /* next pack file starts empty again */ 10174251881Speter apr_array_clear(sizes); 10175251881Speter total_size = 2 * SVN_INT64_BUFFER_SIZE; 10176251881Speter start_rev = rev; 10177251881Speter } 10178251881Speter 10179251881Speter /* Update the manifest. Allocate a file name for the current pack 10180251881Speter * file if it is a new one */ 10181251881Speter if (sizes->nelts == 0) 10182251881Speter pack_filename = apr_psprintf(scratch_pool, "%ld.0", rev); 10183251881Speter 10184251881Speter SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%s\n", 10185251881Speter pack_filename)); 10186251881Speter 10187251881Speter /* add to list of files to put into the current pack file */ 10188251881Speter APR_ARRAY_PUSH(sizes, apr_off_t) = finfo.size; 10189251881Speter total_size += SVN_INT64_BUFFER_SIZE + finfo.size; 10190251881Speter } 10191251881Speter 10192251881Speter /* write the last pack file */ 10193251881Speter if (sizes->nelts != 0) 10194251881Speter SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path, 10195251881Speter start_rev, rev-1, sizes, (apr_size_t)total_size, 10196251881Speter compression_level, cancel_func, cancel_baton, 10197251881Speter iterpool)); 10198251881Speter 10199251881Speter /* flush the manifest file and update permissions */ 10200251881Speter SVN_ERR(svn_stream_close(manifest_stream)); 10201251881Speter SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool)); 10202251881Speter 10203251881Speter svn_pool_destroy(iterpool); 10204251881Speter 10205251881Speter return SVN_NO_ERROR; 10206251881Speter} 10207251881Speter 10208251881Speter/* Delete the non-packed revprop SHARD at SHARD_PATH with exactly 10209251881Speter * MAX_FILES_PER_DIR revprop files in it. If this is shard 0, keep the 10210251881Speter * revprop file for revision 0. 10211251881Speter * 10212251881Speter * CANCEL_FUNC and CANCEL_BATON are used in the usual way. Temporary 10213251881Speter * allocations are done in SCRATCH_POOL. 10214251881Speter */ 10215251881Speterstatic svn_error_t * 10216251881Speterdelete_revprops_shard(const char *shard_path, 10217251881Speter apr_int64_t shard, 10218251881Speter int max_files_per_dir, 10219251881Speter svn_cancel_func_t cancel_func, 10220251881Speter void *cancel_baton, 10221251881Speter apr_pool_t *scratch_pool) 10222251881Speter{ 10223251881Speter if (shard == 0) 10224251881Speter { 10225251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 10226251881Speter int i; 10227251881Speter 10228251881Speter /* delete all files except the one for revision 0 */ 10229251881Speter for (i = 1; i < max_files_per_dir; ++i) 10230251881Speter { 10231251881Speter const char *path = svn_dirent_join(shard_path, 10232251881Speter apr_psprintf(iterpool, "%d", i), 10233251881Speter iterpool); 10234251881Speter if (cancel_func) 10235251881Speter SVN_ERR((*cancel_func)(cancel_baton)); 10236251881Speter 10237251881Speter SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool)); 10238251881Speter svn_pool_clear(iterpool); 10239251881Speter } 10240251881Speter 10241251881Speter svn_pool_destroy(iterpool); 10242251881Speter } 10243251881Speter else 10244251881Speter SVN_ERR(svn_io_remove_dir2(shard_path, TRUE, 10245251881Speter cancel_func, cancel_baton, scratch_pool)); 10246251881Speter 10247251881Speter return SVN_NO_ERROR; 10248251881Speter} 10249251881Speter 10250251881Speter/* In the file system at FS_PATH, pack the SHARD in REVS_DIR and 10251251881Speter * REVPROPS_DIR containing exactly MAX_FILES_PER_DIR revisions, using POOL 10252251881Speter * for allocations. REVPROPS_DIR will be NULL if revprop packing is not 10253251881Speter * supported. COMPRESSION_LEVEL and MAX_PACK_SIZE will be ignored in that 10254251881Speter * case. 10255251881Speter * 10256251881Speter * CANCEL_FUNC and CANCEL_BATON are what you think they are; similarly 10257251881Speter * NOTIFY_FUNC and NOTIFY_BATON. 10258251881Speter * 10259251881Speter * If for some reason we detect a partial packing already performed, we 10260251881Speter * remove the pack file and start again. 10261251881Speter */ 10262251881Speterstatic svn_error_t * 10263251881Speterpack_shard(const char *revs_dir, 10264251881Speter const char *revsprops_dir, 10265251881Speter const char *fs_path, 10266251881Speter apr_int64_t shard, 10267251881Speter int max_files_per_dir, 10268251881Speter apr_off_t max_pack_size, 10269251881Speter int compression_level, 10270251881Speter svn_fs_pack_notify_t notify_func, 10271251881Speter void *notify_baton, 10272251881Speter svn_cancel_func_t cancel_func, 10273251881Speter void *cancel_baton, 10274251881Speter apr_pool_t *pool) 10275251881Speter{ 10276251881Speter const char *rev_shard_path, *rev_pack_file_dir; 10277251881Speter const char *revprops_shard_path, *revprops_pack_file_dir; 10278251881Speter 10279251881Speter /* Notify caller we're starting to pack this shard. */ 10280251881Speter if (notify_func) 10281251881Speter SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_start, 10282251881Speter pool)); 10283251881Speter 10284251881Speter /* Some useful paths. */ 10285251881Speter rev_pack_file_dir = svn_dirent_join(revs_dir, 10286251881Speter apr_psprintf(pool, 10287251881Speter "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD, 10288251881Speter shard), 10289251881Speter pool); 10290251881Speter rev_shard_path = svn_dirent_join(revs_dir, 10291251881Speter apr_psprintf(pool, "%" APR_INT64_T_FMT, shard), 10292251881Speter pool); 10293251881Speter 10294251881Speter /* pack the revision content */ 10295251881Speter SVN_ERR(pack_rev_shard(rev_pack_file_dir, rev_shard_path, 10296251881Speter shard, max_files_per_dir, 10297251881Speter cancel_func, cancel_baton, pool)); 10298251881Speter 10299251881Speter /* if enabled, pack the revprops in an equivalent way */ 10300251881Speter if (revsprops_dir) 10301251881Speter { 10302251881Speter revprops_pack_file_dir = svn_dirent_join(revsprops_dir, 10303251881Speter apr_psprintf(pool, 10304251881Speter "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD, 10305251881Speter shard), 10306251881Speter pool); 10307251881Speter revprops_shard_path = svn_dirent_join(revsprops_dir, 10308251881Speter apr_psprintf(pool, "%" APR_INT64_T_FMT, shard), 10309251881Speter pool); 10310251881Speter 10311251881Speter SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path, 10312251881Speter shard, max_files_per_dir, 10313251881Speter (int)(0.9 * max_pack_size), 10314251881Speter compression_level, 10315251881Speter cancel_func, cancel_baton, pool)); 10316251881Speter } 10317251881Speter 10318251881Speter /* Update the min-unpacked-rev file to reflect our newly packed shard. 10319251881Speter * (This doesn't update ffd->min_unpacked_rev. That will be updated by 10320251881Speter * update_min_unpacked_rev() when necessary.) */ 10321251881Speter SVN_ERR(write_revnum_file(fs_path, PATH_MIN_UNPACKED_REV, 10322251881Speter (svn_revnum_t)((shard + 1) * max_files_per_dir), 10323251881Speter pool)); 10324251881Speter 10325251881Speter /* Finally, remove the existing shard directories. */ 10326251881Speter SVN_ERR(svn_io_remove_dir2(rev_shard_path, TRUE, 10327251881Speter cancel_func, cancel_baton, pool)); 10328251881Speter if (revsprops_dir) 10329251881Speter SVN_ERR(delete_revprops_shard(revprops_shard_path, 10330251881Speter shard, max_files_per_dir, 10331251881Speter cancel_func, cancel_baton, pool)); 10332251881Speter 10333251881Speter /* Notify caller we're starting to pack this shard. */ 10334251881Speter if (notify_func) 10335251881Speter SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_end, 10336251881Speter pool)); 10337251881Speter 10338251881Speter return SVN_NO_ERROR; 10339251881Speter} 10340251881Speter 10341251881Speterstruct pack_baton 10342251881Speter{ 10343251881Speter svn_fs_t *fs; 10344251881Speter svn_fs_pack_notify_t notify_func; 10345251881Speter void *notify_baton; 10346251881Speter svn_cancel_func_t cancel_func; 10347251881Speter void *cancel_baton; 10348251881Speter}; 10349251881Speter 10350251881Speter 10351251881Speter/* The work-horse for svn_fs_fs__pack, called with the FS write lock. 10352251881Speter This implements the svn_fs_fs__with_write_lock() 'body' callback 10353251881Speter type. BATON is a 'struct pack_baton *'. 10354251881Speter 10355251881Speter WARNING: if you add a call to this function, please note: 10356251881Speter The code currently assumes that any piece of code running with 10357251881Speter the write-lock set can rely on the ffd->min_unpacked_rev and 10358251881Speter ffd->min_unpacked_revprop caches to be up-to-date (and, by 10359251881Speter extension, on not having to use a retry when calling 10360251881Speter svn_fs_fs__path_rev_absolute() and friends). If you add a call 10361251881Speter to this function, consider whether you have to call 10362251881Speter update_min_unpacked_rev(). 10363251881Speter See this thread: http://thread.gmane.org/1291206765.3782.3309.camel@edith 10364251881Speter */ 10365251881Speterstatic svn_error_t * 10366251881Speterpack_body(void *baton, 10367251881Speter apr_pool_t *pool) 10368251881Speter{ 10369251881Speter struct pack_baton *pb = baton; 10370251881Speter fs_fs_data_t ffd = {0}; 10371251881Speter apr_int64_t completed_shards; 10372251881Speter apr_int64_t i; 10373251881Speter svn_revnum_t youngest; 10374251881Speter apr_pool_t *iterpool; 10375251881Speter const char *rev_data_path; 10376251881Speter const char *revprops_data_path = NULL; 10377251881Speter 10378251881Speter /* read repository settings */ 10379251881Speter SVN_ERR(read_format(&ffd.format, &ffd.max_files_per_dir, 10380251881Speter path_format(pb->fs, pool), pool)); 10381251881Speter SVN_ERR(check_format(ffd.format)); 10382251881Speter SVN_ERR(read_config(&ffd, pb->fs->path, pool)); 10383251881Speter 10384251881Speter /* If the repository isn't a new enough format, we don't support packing. 10385251881Speter Return a friendly error to that effect. */ 10386251881Speter if (ffd.format < SVN_FS_FS__MIN_PACKED_FORMAT) 10387251881Speter return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 10388251881Speter _("FSFS format (%d) too old to pack; please upgrade the filesystem."), 10389251881Speter ffd.format); 10390251881Speter 10391251881Speter /* If we aren't using sharding, we can't do any packing, so quit. */ 10392251881Speter if (!ffd.max_files_per_dir) 10393251881Speter return SVN_NO_ERROR; 10394251881Speter 10395251881Speter SVN_ERR(read_min_unpacked_rev(&ffd.min_unpacked_rev, 10396251881Speter path_min_unpacked_rev(pb->fs, pool), 10397251881Speter pool)); 10398251881Speter 10399251881Speter SVN_ERR(get_youngest(&youngest, pb->fs->path, pool)); 10400251881Speter completed_shards = (youngest + 1) / ffd.max_files_per_dir; 10401251881Speter 10402251881Speter /* See if we've already completed all possible shards thus far. */ 10403251881Speter if (ffd.min_unpacked_rev == (completed_shards * ffd.max_files_per_dir)) 10404251881Speter return SVN_NO_ERROR; 10405251881Speter 10406251881Speter rev_data_path = svn_dirent_join(pb->fs->path, PATH_REVS_DIR, pool); 10407251881Speter if (ffd.format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT) 10408251881Speter revprops_data_path = svn_dirent_join(pb->fs->path, PATH_REVPROPS_DIR, 10409251881Speter pool); 10410251881Speter 10411251881Speter iterpool = svn_pool_create(pool); 10412251881Speter for (i = ffd.min_unpacked_rev / ffd.max_files_per_dir; 10413251881Speter i < completed_shards; 10414251881Speter i++) 10415251881Speter { 10416251881Speter svn_pool_clear(iterpool); 10417251881Speter 10418251881Speter if (pb->cancel_func) 10419251881Speter SVN_ERR(pb->cancel_func(pb->cancel_baton)); 10420251881Speter 10421251881Speter SVN_ERR(pack_shard(rev_data_path, revprops_data_path, 10422251881Speter pb->fs->path, i, ffd.max_files_per_dir, 10423251881Speter ffd.revprop_pack_size, 10424251881Speter ffd.compress_packed_revprops 10425251881Speter ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT 10426251881Speter : SVN_DELTA_COMPRESSION_LEVEL_NONE, 10427251881Speter pb->notify_func, pb->notify_baton, 10428251881Speter pb->cancel_func, pb->cancel_baton, iterpool)); 10429251881Speter } 10430251881Speter 10431251881Speter svn_pool_destroy(iterpool); 10432251881Speter return SVN_NO_ERROR; 10433251881Speter} 10434251881Speter 10435251881Spetersvn_error_t * 10436251881Spetersvn_fs_fs__pack(svn_fs_t *fs, 10437251881Speter svn_fs_pack_notify_t notify_func, 10438251881Speter void *notify_baton, 10439251881Speter svn_cancel_func_t cancel_func, 10440251881Speter void *cancel_baton, 10441251881Speter apr_pool_t *pool) 10442251881Speter{ 10443251881Speter struct pack_baton pb = { 0 }; 10444251881Speter pb.fs = fs; 10445251881Speter pb.notify_func = notify_func; 10446251881Speter pb.notify_baton = notify_baton; 10447251881Speter pb.cancel_func = cancel_func; 10448251881Speter pb.cancel_baton = cancel_baton; 10449251881Speter return svn_fs_fs__with_write_lock(fs, pack_body, &pb, pool); 10450251881Speter} 10451251881Speter 10452251881Speter 10453251881Speter/** Verifying. **/ 10454251881Speter 10455251881Speter/* Baton type expected by verify_walker(). The purpose is to reuse open 10456251881Speter * rev / pack file handles between calls. Its contents need to be cleaned 10457251881Speter * periodically to limit resource usage. 10458251881Speter */ 10459251881Spetertypedef struct verify_walker_baton_t 10460251881Speter{ 10461251881Speter /* number of calls to verify_walker() since the last clean */ 10462251881Speter int iteration_count; 10463251881Speter 10464251881Speter /* number of files opened since the last clean */ 10465251881Speter int file_count; 10466251881Speter 10467251881Speter /* progress notification callback to invoke periodically (may be NULL) */ 10468251881Speter svn_fs_progress_notify_func_t notify_func; 10469251881Speter 10470251881Speter /* baton to use with NOTIFY_FUNC */ 10471251881Speter void *notify_baton; 10472251881Speter 10473251881Speter /* remember the last revision for which we called notify_func */ 10474251881Speter svn_revnum_t last_notified_revision; 10475251881Speter 10476251881Speter /* current file handle (or NULL) */ 10477251881Speter apr_file_t *file_hint; 10478251881Speter 10479251881Speter /* corresponding revision (or SVN_INVALID_REVNUM) */ 10480251881Speter svn_revnum_t rev_hint; 10481251881Speter 10482251881Speter /* pool to use for the file handles etc. */ 10483251881Speter apr_pool_t *pool; 10484251881Speter} verify_walker_baton_t; 10485251881Speter 10486251881Speter/* Used by svn_fs_fs__verify(). 10487251881Speter Implements svn_fs_fs__walk_rep_reference().walker. */ 10488251881Speterstatic svn_error_t * 10489251881Speterverify_walker(representation_t *rep, 10490251881Speter void *baton, 10491251881Speter svn_fs_t *fs, 10492251881Speter apr_pool_t *scratch_pool) 10493251881Speter{ 10494251881Speter struct rep_state *rs; 10495251881Speter struct rep_args *rep_args; 10496251881Speter 10497251881Speter if (baton) 10498251881Speter { 10499251881Speter verify_walker_baton_t *walker_baton = baton; 10500251881Speter apr_file_t * previous_file; 10501251881Speter 10502251881Speter /* notify and free resources periodically */ 10503251881Speter if ( walker_baton->iteration_count > 1000 10504251881Speter || walker_baton->file_count > 16) 10505251881Speter { 10506251881Speter if ( walker_baton->notify_func 10507251881Speter && rep->revision != walker_baton->last_notified_revision) 10508251881Speter { 10509251881Speter walker_baton->notify_func(rep->revision, 10510251881Speter walker_baton->notify_baton, 10511251881Speter scratch_pool); 10512251881Speter walker_baton->last_notified_revision = rep->revision; 10513251881Speter } 10514251881Speter 10515251881Speter svn_pool_clear(walker_baton->pool); 10516251881Speter 10517251881Speter walker_baton->iteration_count = 0; 10518251881Speter walker_baton->file_count = 0; 10519251881Speter walker_baton->file_hint = NULL; 10520251881Speter walker_baton->rev_hint = SVN_INVALID_REVNUM; 10521251881Speter } 10522251881Speter 10523251881Speter /* access the repo data */ 10524251881Speter previous_file = walker_baton->file_hint; 10525251881Speter SVN_ERR(create_rep_state(&rs, &rep_args, &walker_baton->file_hint, 10526251881Speter &walker_baton->rev_hint, rep, fs, 10527251881Speter walker_baton->pool)); 10528251881Speter 10529251881Speter /* update resource usage counters */ 10530251881Speter walker_baton->iteration_count++; 10531251881Speter if (previous_file != walker_baton->file_hint) 10532251881Speter walker_baton->file_count++; 10533251881Speter } 10534251881Speter else 10535251881Speter { 10536251881Speter /* ### Should this be using read_rep_line() directly? */ 10537251881Speter SVN_ERR(create_rep_state(&rs, &rep_args, NULL, NULL, rep, fs, 10538251881Speter scratch_pool)); 10539251881Speter } 10540251881Speter 10541251881Speter return SVN_NO_ERROR; 10542251881Speter} 10543251881Speter 10544251881Spetersvn_error_t * 10545251881Spetersvn_fs_fs__verify(svn_fs_t *fs, 10546251881Speter svn_revnum_t start, 10547251881Speter svn_revnum_t end, 10548251881Speter svn_fs_progress_notify_func_t notify_func, 10549251881Speter void *notify_baton, 10550251881Speter svn_cancel_func_t cancel_func, 10551251881Speter void *cancel_baton, 10552251881Speter apr_pool_t *pool) 10553251881Speter{ 10554251881Speter fs_fs_data_t *ffd = fs->fsap_data; 10555251881Speter svn_boolean_t exists; 10556251881Speter svn_revnum_t youngest = ffd->youngest_rev_cache; /* cache is current */ 10557251881Speter 10558251881Speter if (ffd->format < SVN_FS_FS__MIN_REP_SHARING_FORMAT) 10559251881Speter return SVN_NO_ERROR; 10560251881Speter 10561251881Speter /* Input validation. */ 10562251881Speter if (! SVN_IS_VALID_REVNUM(start)) 10563251881Speter start = 0; 10564251881Speter if (! SVN_IS_VALID_REVNUM(end)) 10565251881Speter end = youngest; 10566251881Speter SVN_ERR(ensure_revision_exists(fs, start, pool)); 10567251881Speter SVN_ERR(ensure_revision_exists(fs, end, pool)); 10568251881Speter 10569251881Speter /* rep-cache verification. */ 10570251881Speter SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool)); 10571251881Speter if (exists) 10572251881Speter { 10573251881Speter /* provide a baton to allow the reuse of open file handles between 10574251881Speter iterations (saves 2/3 of OS level file operations). */ 10575251881Speter verify_walker_baton_t *baton = apr_pcalloc(pool, sizeof(*baton)); 10576251881Speter baton->rev_hint = SVN_INVALID_REVNUM; 10577251881Speter baton->pool = svn_pool_create(pool); 10578251881Speter baton->last_notified_revision = SVN_INVALID_REVNUM; 10579251881Speter baton->notify_func = notify_func; 10580251881Speter baton->notify_baton = notify_baton; 10581251881Speter 10582251881Speter /* tell the user that we are now ready to do *something* */ 10583251881Speter if (notify_func) 10584251881Speter notify_func(SVN_INVALID_REVNUM, notify_baton, baton->pool); 10585251881Speter 10586251881Speter /* Do not attempt to walk the rep-cache database if its file does 10587251881Speter not exist, since doing so would create it --- which may confuse 10588251881Speter the administrator. Don't take any lock. */ 10589251881Speter SVN_ERR(svn_fs_fs__walk_rep_reference(fs, start, end, 10590251881Speter verify_walker, baton, 10591251881Speter cancel_func, cancel_baton, 10592251881Speter pool)); 10593251881Speter 10594251881Speter /* walker resource cleanup */ 10595251881Speter svn_pool_destroy(baton->pool); 10596251881Speter } 10597251881Speter 10598251881Speter return SVN_NO_ERROR; 10599251881Speter} 10600251881Speter 10601251881Speter 10602251881Speter/** Hotcopy. **/ 10603251881Speter 10604251881Speter/* Like svn_io_dir_file_copy(), but doesn't copy files that exist at 10605251881Speter * the destination and do not differ in terms of kind, size, and mtime. */ 10606251881Speterstatic svn_error_t * 10607251881Speterhotcopy_io_dir_file_copy(const char *src_path, 10608251881Speter const char *dst_path, 10609251881Speter const char *file, 10610251881Speter apr_pool_t *scratch_pool) 10611251881Speter{ 10612251881Speter const svn_io_dirent2_t *src_dirent; 10613251881Speter const svn_io_dirent2_t *dst_dirent; 10614251881Speter const char *src_target; 10615251881Speter const char *dst_target; 10616251881Speter 10617251881Speter /* Does the destination already exist? If not, we must copy it. */ 10618251881Speter dst_target = svn_dirent_join(dst_path, file, scratch_pool); 10619251881Speter SVN_ERR(svn_io_stat_dirent2(&dst_dirent, dst_target, FALSE, TRUE, 10620251881Speter scratch_pool, scratch_pool)); 10621251881Speter if (dst_dirent->kind != svn_node_none) 10622251881Speter { 10623251881Speter /* If the destination's stat information indicates that the file 10624251881Speter * is equal to the source, don't bother copying the file again. */ 10625251881Speter src_target = svn_dirent_join(src_path, file, scratch_pool); 10626251881Speter SVN_ERR(svn_io_stat_dirent2(&src_dirent, src_target, FALSE, FALSE, 10627251881Speter scratch_pool, scratch_pool)); 10628251881Speter if (src_dirent->kind == dst_dirent->kind && 10629251881Speter src_dirent->special == dst_dirent->special && 10630251881Speter src_dirent->filesize == dst_dirent->filesize && 10631251881Speter src_dirent->mtime <= dst_dirent->mtime) 10632251881Speter return SVN_NO_ERROR; 10633251881Speter } 10634251881Speter 10635251881Speter return svn_error_trace(svn_io_dir_file_copy(src_path, dst_path, file, 10636251881Speter scratch_pool)); 10637251881Speter} 10638251881Speter 10639251881Speter/* Set *NAME_P to the UTF-8 representation of directory entry NAME. 10640251881Speter * NAME is in the internal encoding used by APR; PARENT is in 10641251881Speter * UTF-8 and in internal (not local) style. 10642251881Speter * 10643251881Speter * Use PARENT only for generating an error string if the conversion 10644251881Speter * fails because NAME could not be represented in UTF-8. In that 10645251881Speter * case, return a two-level error in which the outer error's message 10646251881Speter * mentions PARENT, but the inner error's message does not mention 10647251881Speter * NAME (except possibly in hex) since NAME may not be printable. 10648251881Speter * Such a compound error at least allows the user to go looking in the 10649251881Speter * right directory for the problem. 10650251881Speter * 10651251881Speter * If there is any other error, just return that error directly. 10652251881Speter * 10653251881Speter * If there is any error, the effect on *NAME_P is undefined. 10654251881Speter * 10655251881Speter * *NAME_P and NAME may refer to the same storage. 10656251881Speter */ 10657251881Speterstatic svn_error_t * 10658251881Speterentry_name_to_utf8(const char **name_p, 10659251881Speter const char *name, 10660251881Speter const char *parent, 10661251881Speter apr_pool_t *pool) 10662251881Speter{ 10663251881Speter svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool); 10664251881Speter if (err && err->apr_err == APR_EINVAL) 10665251881Speter { 10666251881Speter return svn_error_createf(err->apr_err, err, 10667251881Speter _("Error converting entry " 10668251881Speter "in directory '%s' to UTF-8"), 10669251881Speter svn_dirent_local_style(parent, pool)); 10670251881Speter } 10671251881Speter return err; 10672251881Speter} 10673251881Speter 10674251881Speter/* Like svn_io_copy_dir_recursively() but doesn't copy regular files that 10675251881Speter * exist in the destination and do not differ from the source in terms of 10676251881Speter * kind, size, and mtime. */ 10677251881Speterstatic svn_error_t * 10678251881Speterhotcopy_io_copy_dir_recursively(const char *src, 10679251881Speter const char *dst_parent, 10680251881Speter const char *dst_basename, 10681251881Speter svn_boolean_t copy_perms, 10682251881Speter svn_cancel_func_t cancel_func, 10683251881Speter void *cancel_baton, 10684251881Speter apr_pool_t *pool) 10685251881Speter{ 10686251881Speter svn_node_kind_t kind; 10687251881Speter apr_status_t status; 10688251881Speter const char *dst_path; 10689251881Speter apr_dir_t *this_dir; 10690251881Speter apr_finfo_t this_entry; 10691251881Speter apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME; 10692251881Speter 10693251881Speter /* Make a subpool for recursion */ 10694251881Speter apr_pool_t *subpool = svn_pool_create(pool); 10695251881Speter 10696251881Speter /* The 'dst_path' is simply dst_parent/dst_basename */ 10697251881Speter dst_path = svn_dirent_join(dst_parent, dst_basename, pool); 10698251881Speter 10699251881Speter /* Sanity checks: SRC and DST_PARENT are directories, and 10700251881Speter DST_BASENAME doesn't already exist in DST_PARENT. */ 10701251881Speter SVN_ERR(svn_io_check_path(src, &kind, subpool)); 10702251881Speter if (kind != svn_node_dir) 10703251881Speter return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 10704251881Speter _("Source '%s' is not a directory"), 10705251881Speter svn_dirent_local_style(src, pool)); 10706251881Speter 10707251881Speter SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool)); 10708251881Speter if (kind != svn_node_dir) 10709251881Speter return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 10710251881Speter _("Destination '%s' is not a directory"), 10711251881Speter svn_dirent_local_style(dst_parent, pool)); 10712251881Speter 10713251881Speter SVN_ERR(svn_io_check_path(dst_path, &kind, subpool)); 10714251881Speter 10715251881Speter /* Create the new directory. */ 10716251881Speter /* ### TODO: copy permissions (needs apr_file_attrs_get()) */ 10717251881Speter SVN_ERR(svn_io_make_dir_recursively(dst_path, pool)); 10718251881Speter 10719251881Speter /* Loop over the dirents in SRC. ('.' and '..' are auto-excluded) */ 10720251881Speter SVN_ERR(svn_io_dir_open(&this_dir, src, subpool)); 10721251881Speter 10722251881Speter for (status = apr_dir_read(&this_entry, flags, this_dir); 10723251881Speter status == APR_SUCCESS; 10724251881Speter status = apr_dir_read(&this_entry, flags, this_dir)) 10725251881Speter { 10726251881Speter if ((this_entry.name[0] == '.') 10727251881Speter && ((this_entry.name[1] == '\0') 10728251881Speter || ((this_entry.name[1] == '.') 10729251881Speter && (this_entry.name[2] == '\0')))) 10730251881Speter { 10731251881Speter continue; 10732251881Speter } 10733251881Speter else 10734251881Speter { 10735251881Speter const char *entryname_utf8; 10736251881Speter 10737251881Speter if (cancel_func) 10738251881Speter SVN_ERR(cancel_func(cancel_baton)); 10739251881Speter 10740251881Speter SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name, 10741251881Speter src, subpool)); 10742251881Speter if (this_entry.filetype == APR_REG) /* regular file */ 10743251881Speter { 10744251881Speter SVN_ERR(hotcopy_io_dir_file_copy(src, dst_path, entryname_utf8, 10745251881Speter subpool)); 10746251881Speter } 10747251881Speter else if (this_entry.filetype == APR_LNK) /* symlink */ 10748251881Speter { 10749251881Speter const char *src_target = svn_dirent_join(src, entryname_utf8, 10750251881Speter subpool); 10751251881Speter const char *dst_target = svn_dirent_join(dst_path, 10752251881Speter entryname_utf8, 10753251881Speter subpool); 10754251881Speter SVN_ERR(svn_io_copy_link(src_target, dst_target, 10755251881Speter subpool)); 10756251881Speter } 10757251881Speter else if (this_entry.filetype == APR_DIR) /* recurse */ 10758251881Speter { 10759251881Speter const char *src_target; 10760251881Speter 10761251881Speter /* Prevent infinite recursion by filtering off our 10762251881Speter newly created destination path. */ 10763251881Speter if (strcmp(src, dst_parent) == 0 10764251881Speter && strcmp(entryname_utf8, dst_basename) == 0) 10765251881Speter continue; 10766251881Speter 10767251881Speter src_target = svn_dirent_join(src, entryname_utf8, subpool); 10768251881Speter SVN_ERR(hotcopy_io_copy_dir_recursively(src_target, 10769251881Speter dst_path, 10770251881Speter entryname_utf8, 10771251881Speter copy_perms, 10772251881Speter cancel_func, 10773251881Speter cancel_baton, 10774251881Speter subpool)); 10775251881Speter } 10776251881Speter /* ### support other APR node types someday?? */ 10777251881Speter 10778251881Speter } 10779251881Speter } 10780251881Speter 10781251881Speter if (! (APR_STATUS_IS_ENOENT(status))) 10782251881Speter return svn_error_wrap_apr(status, _("Can't read directory '%s'"), 10783251881Speter svn_dirent_local_style(src, pool)); 10784251881Speter 10785251881Speter status = apr_dir_close(this_dir); 10786251881Speter if (status) 10787251881Speter return svn_error_wrap_apr(status, _("Error closing directory '%s'"), 10788251881Speter svn_dirent_local_style(src, pool)); 10789251881Speter 10790251881Speter /* Free any memory used by recursion */ 10791251881Speter svn_pool_destroy(subpool); 10792251881Speter 10793251881Speter return SVN_NO_ERROR; 10794251881Speter} 10795251881Speter 10796251881Speter/* Copy an un-packed revision or revprop file for revision REV from SRC_SUBDIR 10797251881Speter * to DST_SUBDIR. Assume a sharding layout based on MAX_FILES_PER_DIR. 10798251881Speter * Use SCRATCH_POOL for temporary allocations. */ 10799251881Speterstatic svn_error_t * 10800251881Speterhotcopy_copy_shard_file(const char *src_subdir, 10801251881Speter const char *dst_subdir, 10802251881Speter svn_revnum_t rev, 10803251881Speter int max_files_per_dir, 10804251881Speter apr_pool_t *scratch_pool) 10805251881Speter{ 10806251881Speter const char *src_subdir_shard = src_subdir, 10807251881Speter *dst_subdir_shard = dst_subdir; 10808251881Speter 10809251881Speter if (max_files_per_dir) 10810251881Speter { 10811251881Speter const char *shard = apr_psprintf(scratch_pool, "%ld", 10812251881Speter rev / max_files_per_dir); 10813251881Speter src_subdir_shard = svn_dirent_join(src_subdir, shard, scratch_pool); 10814251881Speter dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool); 10815251881Speter 10816251881Speter if (rev % max_files_per_dir == 0) 10817251881Speter { 10818251881Speter SVN_ERR(svn_io_make_dir_recursively(dst_subdir_shard, scratch_pool)); 10819251881Speter SVN_ERR(svn_io_copy_perms(dst_subdir, dst_subdir_shard, 10820251881Speter scratch_pool)); 10821251881Speter } 10822251881Speter } 10823251881Speter 10824251881Speter SVN_ERR(hotcopy_io_dir_file_copy(src_subdir_shard, dst_subdir_shard, 10825251881Speter apr_psprintf(scratch_pool, "%ld", rev), 10826251881Speter scratch_pool)); 10827251881Speter return SVN_NO_ERROR; 10828251881Speter} 10829251881Speter 10830251881Speter 10831251881Speter/* Copy a packed shard containing revision REV, and which contains 10832251881Speter * MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS. 10833251881Speter * Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS. 10834251881Speter * Do not re-copy data which already exists in DST_FS. 10835251881Speter * Use SCRATCH_POOL for temporary allocations. */ 10836251881Speterstatic svn_error_t * 10837251881Speterhotcopy_copy_packed_shard(svn_revnum_t *dst_min_unpacked_rev, 10838251881Speter svn_fs_t *src_fs, 10839251881Speter svn_fs_t *dst_fs, 10840251881Speter svn_revnum_t rev, 10841251881Speter int max_files_per_dir, 10842251881Speter apr_pool_t *scratch_pool) 10843251881Speter{ 10844251881Speter const char *src_subdir; 10845251881Speter const char *dst_subdir; 10846251881Speter const char *packed_shard; 10847251881Speter const char *src_subdir_packed_shard; 10848251881Speter svn_revnum_t revprop_rev; 10849251881Speter apr_pool_t *iterpool; 10850251881Speter fs_fs_data_t *src_ffd = src_fs->fsap_data; 10851251881Speter 10852251881Speter /* Copy the packed shard. */ 10853251881Speter src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool); 10854251881Speter dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool); 10855251881Speter packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD, 10856251881Speter rev / max_files_per_dir); 10857251881Speter src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard, 10858251881Speter scratch_pool); 10859251881Speter SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard, 10860251881Speter dst_subdir, packed_shard, 10861251881Speter TRUE /* copy_perms */, 10862251881Speter NULL /* cancel_func */, NULL, 10863251881Speter scratch_pool)); 10864251881Speter 10865251881Speter /* Copy revprops belonging to revisions in this pack. */ 10866251881Speter src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, scratch_pool); 10867251881Speter dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, scratch_pool); 10868251881Speter 10869251881Speter if ( src_ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT 10870251881Speter || src_ffd->min_unpacked_rev < rev + max_files_per_dir) 10871251881Speter { 10872251881Speter /* copy unpacked revprops rev by rev */ 10873251881Speter iterpool = svn_pool_create(scratch_pool); 10874251881Speter for (revprop_rev = rev; 10875251881Speter revprop_rev < rev + max_files_per_dir; 10876251881Speter revprop_rev++) 10877251881Speter { 10878251881Speter svn_pool_clear(iterpool); 10879251881Speter 10880251881Speter SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir, 10881251881Speter revprop_rev, max_files_per_dir, 10882251881Speter iterpool)); 10883251881Speter } 10884251881Speter svn_pool_destroy(iterpool); 10885251881Speter } 10886251881Speter else 10887251881Speter { 10888251881Speter /* revprop for revision 0 will never be packed */ 10889251881Speter if (rev == 0) 10890251881Speter SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir, 10891251881Speter 0, max_files_per_dir, 10892251881Speter scratch_pool)); 10893251881Speter 10894251881Speter /* packed revprops folder */ 10895251881Speter packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD, 10896251881Speter rev / max_files_per_dir); 10897251881Speter src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard, 10898251881Speter scratch_pool); 10899251881Speter SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard, 10900251881Speter dst_subdir, packed_shard, 10901251881Speter TRUE /* copy_perms */, 10902251881Speter NULL /* cancel_func */, NULL, 10903251881Speter scratch_pool)); 10904251881Speter } 10905251881Speter 10906251881Speter /* If necessary, update the min-unpacked rev file in the hotcopy. */ 10907251881Speter if (*dst_min_unpacked_rev < rev + max_files_per_dir) 10908251881Speter { 10909251881Speter *dst_min_unpacked_rev = rev + max_files_per_dir; 10910251881Speter SVN_ERR(write_revnum_file(dst_fs->path, PATH_MIN_UNPACKED_REV, 10911251881Speter *dst_min_unpacked_rev, 10912251881Speter scratch_pool)); 10913251881Speter } 10914251881Speter 10915251881Speter return SVN_NO_ERROR; 10916251881Speter} 10917251881Speter 10918251881Speter/* If NEW_YOUNGEST is younger than *DST_YOUNGEST, update the 'current' 10919251881Speter * file in DST_FS and set *DST_YOUNGEST to NEW_YOUNGEST. 10920251881Speter * Use SCRATCH_POOL for temporary allocations. */ 10921251881Speterstatic svn_error_t * 10922251881Speterhotcopy_update_current(svn_revnum_t *dst_youngest, 10923251881Speter svn_fs_t *dst_fs, 10924251881Speter svn_revnum_t new_youngest, 10925251881Speter apr_pool_t *scratch_pool) 10926251881Speter{ 10927251881Speter char next_node_id[MAX_KEY_SIZE] = "0"; 10928251881Speter char next_copy_id[MAX_KEY_SIZE] = "0"; 10929251881Speter fs_fs_data_t *dst_ffd = dst_fs->fsap_data; 10930251881Speter 10931251881Speter if (*dst_youngest >= new_youngest) 10932251881Speter return SVN_NO_ERROR; 10933251881Speter 10934251881Speter /* If necessary, get new current next_node and next_copy IDs. */ 10935251881Speter if (dst_ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) 10936251881Speter { 10937251881Speter apr_off_t root_offset; 10938251881Speter apr_file_t *rev_file; 10939289166Speter char max_node_id[MAX_KEY_SIZE] = "0"; 10940289166Speter char max_copy_id[MAX_KEY_SIZE] = "0"; 10941289166Speter apr_size_t len; 10942251881Speter 10943251881Speter if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 10944251881Speter SVN_ERR(update_min_unpacked_rev(dst_fs, scratch_pool)); 10945251881Speter 10946251881Speter SVN_ERR(open_pack_or_rev_file(&rev_file, dst_fs, new_youngest, 10947251881Speter scratch_pool)); 10948251881Speter SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file, 10949251881Speter dst_fs, new_youngest, scratch_pool)); 10950251881Speter SVN_ERR(recover_find_max_ids(dst_fs, new_youngest, rev_file, 10951289166Speter root_offset, max_node_id, max_copy_id, 10952251881Speter scratch_pool)); 10953251881Speter SVN_ERR(svn_io_file_close(rev_file, scratch_pool)); 10954289166Speter 10955289166Speter /* We store the _next_ ids. */ 10956289166Speter len = strlen(max_node_id); 10957289166Speter svn_fs_fs__next_key(max_node_id, &len, next_node_id); 10958289166Speter len = strlen(max_copy_id); 10959289166Speter svn_fs_fs__next_key(max_copy_id, &len, next_copy_id); 10960251881Speter } 10961251881Speter 10962251881Speter /* Update 'current'. */ 10963251881Speter SVN_ERR(write_current(dst_fs, new_youngest, next_node_id, next_copy_id, 10964251881Speter scratch_pool)); 10965251881Speter 10966251881Speter *dst_youngest = new_youngest; 10967251881Speter 10968251881Speter return SVN_NO_ERROR; 10969251881Speter} 10970251881Speter 10971251881Speter 10972262253Speter/* Remove revision or revprop files between START_REV (inclusive) and 10973262253Speter * END_REV (non-inclusive) from folder DST_SUBDIR in DST_FS. Assume 10974262253Speter * sharding as per MAX_FILES_PER_DIR. 10975251881Speter * Use SCRATCH_POOL for temporary allocations. */ 10976251881Speterstatic svn_error_t * 10977262253Speterhotcopy_remove_files(svn_fs_t *dst_fs, 10978262253Speter const char *dst_subdir, 10979262253Speter svn_revnum_t start_rev, 10980262253Speter svn_revnum_t end_rev, 10981262253Speter int max_files_per_dir, 10982262253Speter apr_pool_t *scratch_pool) 10983251881Speter{ 10984251881Speter const char *shard; 10985251881Speter const char *dst_subdir_shard; 10986251881Speter svn_revnum_t rev; 10987251881Speter apr_pool_t *iterpool; 10988251881Speter 10989251881Speter /* Pre-compute paths for initial shard. */ 10990251881Speter shard = apr_psprintf(scratch_pool, "%ld", start_rev / max_files_per_dir); 10991251881Speter dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool); 10992251881Speter 10993251881Speter iterpool = svn_pool_create(scratch_pool); 10994251881Speter for (rev = start_rev; rev < end_rev; rev++) 10995251881Speter { 10996262253Speter const char *path; 10997251881Speter svn_pool_clear(iterpool); 10998251881Speter 10999251881Speter /* If necessary, update paths for shard. */ 11000251881Speter if (rev != start_rev && rev % max_files_per_dir == 0) 11001251881Speter { 11002251881Speter shard = apr_psprintf(iterpool, "%ld", rev / max_files_per_dir); 11003251881Speter dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool); 11004251881Speter } 11005251881Speter 11006262253Speter /* remove files for REV */ 11007262253Speter path = svn_dirent_join(dst_subdir_shard, 11008262253Speter apr_psprintf(iterpool, "%ld", rev), 11009262253Speter iterpool); 11010251881Speter 11011251881Speter /* Make the rev file writable and remove it. */ 11012262253Speter SVN_ERR(svn_io_set_file_read_write(path, TRUE, iterpool)); 11013262253Speter SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool)); 11014251881Speter } 11015262253Speter 11016251881Speter svn_pool_destroy(iterpool); 11017251881Speter 11018251881Speter return SVN_NO_ERROR; 11019251881Speter} 11020251881Speter 11021262253Speter/* Remove revisions between START_REV (inclusive) and END_REV (non-inclusive) 11022262253Speter * from DST_FS. Assume sharding as per MAX_FILES_PER_DIR. 11023262253Speter * Use SCRATCH_POOL for temporary allocations. */ 11024262253Speterstatic svn_error_t * 11025262253Speterhotcopy_remove_rev_files(svn_fs_t *dst_fs, 11026262253Speter svn_revnum_t start_rev, 11027262253Speter svn_revnum_t end_rev, 11028262253Speter int max_files_per_dir, 11029262253Speter apr_pool_t *scratch_pool) 11030262253Speter{ 11031262253Speter SVN_ERR_ASSERT(start_rev <= end_rev); 11032262253Speter SVN_ERR(hotcopy_remove_files(dst_fs, 11033262253Speter svn_dirent_join(dst_fs->path, 11034262253Speter PATH_REVS_DIR, 11035262253Speter scratch_pool), 11036262253Speter start_rev, end_rev, 11037262253Speter max_files_per_dir, scratch_pool)); 11038262253Speter 11039262253Speter return SVN_NO_ERROR; 11040262253Speter} 11041262253Speter 11042262253Speter/* Remove revision properties between START_REV (inclusive) and END_REV 11043262253Speter * (non-inclusive) from DST_FS. Assume sharding as per MAX_FILES_PER_DIR. 11044262253Speter * Use SCRATCH_POOL for temporary allocations. Revision 0 revprops will 11045262253Speter * not be deleted. */ 11046262253Speterstatic svn_error_t * 11047262253Speterhotcopy_remove_revprop_files(svn_fs_t *dst_fs, 11048262253Speter svn_revnum_t start_rev, 11049262253Speter svn_revnum_t end_rev, 11050262253Speter int max_files_per_dir, 11051262253Speter apr_pool_t *scratch_pool) 11052262253Speter{ 11053262253Speter SVN_ERR_ASSERT(start_rev <= end_rev); 11054262253Speter 11055262253Speter /* don't delete rev 0 props */ 11056262253Speter SVN_ERR(hotcopy_remove_files(dst_fs, 11057262253Speter svn_dirent_join(dst_fs->path, 11058262253Speter PATH_REVPROPS_DIR, 11059262253Speter scratch_pool), 11060262253Speter start_rev ? start_rev : 1, end_rev, 11061262253Speter max_files_per_dir, scratch_pool)); 11062262253Speter 11063262253Speter return SVN_NO_ERROR; 11064262253Speter} 11065262253Speter 11066251881Speter/* Verify that DST_FS is a suitable destination for an incremental 11067251881Speter * hotcopy from SRC_FS. */ 11068251881Speterstatic svn_error_t * 11069251881Speterhotcopy_incremental_check_preconditions(svn_fs_t *src_fs, 11070251881Speter svn_fs_t *dst_fs, 11071251881Speter apr_pool_t *pool) 11072251881Speter{ 11073251881Speter fs_fs_data_t *src_ffd = src_fs->fsap_data; 11074251881Speter fs_fs_data_t *dst_ffd = dst_fs->fsap_data; 11075251881Speter 11076251881Speter /* We only support incremental hotcopy between the same format. */ 11077251881Speter if (src_ffd->format != dst_ffd->format) 11078251881Speter return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 11079251881Speter _("The FSFS format (%d) of the hotcopy source does not match the " 11080251881Speter "FSFS format (%d) of the hotcopy destination; please upgrade " 11081251881Speter "both repositories to the same format"), 11082251881Speter src_ffd->format, dst_ffd->format); 11083251881Speter 11084251881Speter /* Make sure the UUID of source and destination match up. 11085251881Speter * We don't want to copy over a different repository. */ 11086251881Speter if (strcmp(src_fs->uuid, dst_fs->uuid) != 0) 11087251881Speter return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL, 11088251881Speter _("The UUID of the hotcopy source does " 11089251881Speter "not match the UUID of the hotcopy " 11090251881Speter "destination")); 11091251881Speter 11092251881Speter /* Also require same shard size. */ 11093251881Speter if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir) 11094251881Speter return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 11095251881Speter _("The sharding layout configuration " 11096251881Speter "of the hotcopy source does not match " 11097251881Speter "the sharding layout configuration of " 11098251881Speter "the hotcopy destination")); 11099251881Speter return SVN_NO_ERROR; 11100251881Speter} 11101251881Speter 11102262253Speter/* Remove folder PATH. Ignore errors due to the sub-tree not being empty. 11103262253Speter * CANCEL_FUNC and CANCEL_BATON do the usual thing. 11104262253Speter * Use POOL for temporary allocations. 11105262253Speter */ 11106262253Speterstatic svn_error_t * 11107262253Speterremove_folder(const char *path, 11108262253Speter svn_cancel_func_t cancel_func, 11109262253Speter void *cancel_baton, 11110262253Speter apr_pool_t *pool) 11111262253Speter{ 11112262253Speter svn_error_t *err = svn_io_remove_dir2(path, TRUE, 11113262253Speter cancel_func, cancel_baton, pool); 11114251881Speter 11115262253Speter if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err)) 11116262253Speter { 11117262253Speter svn_error_clear(err); 11118262253Speter err = SVN_NO_ERROR; 11119262253Speter } 11120262253Speter 11121262253Speter return svn_error_trace(err); 11122262253Speter} 11123262253Speter 11124251881Speter/* Baton for hotcopy_body(). */ 11125251881Speterstruct hotcopy_body_baton { 11126251881Speter svn_fs_t *src_fs; 11127251881Speter svn_fs_t *dst_fs; 11128251881Speter svn_boolean_t incremental; 11129251881Speter svn_cancel_func_t cancel_func; 11130251881Speter void *cancel_baton; 11131251881Speter} hotcopy_body_baton; 11132251881Speter 11133251881Speter/* Perform a hotcopy, either normal or incremental. 11134251881Speter * 11135251881Speter * Normal hotcopy assumes that the destination exists as an empty 11136251881Speter * directory. It behaves like an incremental hotcopy except that 11137251881Speter * none of the copied files already exist in the destination. 11138251881Speter * 11139251881Speter * An incremental hotcopy copies only changed or new files to the destination, 11140251881Speter * and removes files from the destination no longer present in the source. 11141251881Speter * While the incremental hotcopy is running, readers should still be able 11142251881Speter * to access the destintation repository without error and should not see 11143251881Speter * revisions currently in progress of being copied. Readers are able to see 11144251881Speter * new fully copied revisions even if the entire incremental hotcopy procedure 11145251881Speter * has not yet completed. 11146251881Speter * 11147251881Speter * Writers are blocked out completely during the entire incremental hotcopy 11148251881Speter * process to ensure consistency. This function assumes that the repository 11149251881Speter * write-lock is held. 11150251881Speter */ 11151251881Speterstatic svn_error_t * 11152251881Speterhotcopy_body(void *baton, apr_pool_t *pool) 11153251881Speter{ 11154251881Speter struct hotcopy_body_baton *hbb = baton; 11155251881Speter svn_fs_t *src_fs = hbb->src_fs; 11156251881Speter fs_fs_data_t *src_ffd = src_fs->fsap_data; 11157251881Speter svn_fs_t *dst_fs = hbb->dst_fs; 11158251881Speter fs_fs_data_t *dst_ffd = dst_fs->fsap_data; 11159251881Speter int max_files_per_dir = src_ffd->max_files_per_dir; 11160251881Speter svn_boolean_t incremental = hbb->incremental; 11161251881Speter svn_cancel_func_t cancel_func = hbb->cancel_func; 11162251881Speter void* cancel_baton = hbb->cancel_baton; 11163251881Speter svn_revnum_t src_youngest; 11164251881Speter svn_revnum_t dst_youngest; 11165251881Speter svn_revnum_t rev; 11166251881Speter svn_revnum_t src_min_unpacked_rev; 11167251881Speter svn_revnum_t dst_min_unpacked_rev; 11168251881Speter const char *src_subdir; 11169251881Speter const char *dst_subdir; 11170251881Speter const char *revprop_src_subdir; 11171251881Speter const char *revprop_dst_subdir; 11172251881Speter apr_pool_t *iterpool; 11173251881Speter svn_node_kind_t kind; 11174251881Speter 11175251881Speter /* Try to copy the config. 11176251881Speter * 11177251881Speter * ### We try copying the config file before doing anything else, 11178251881Speter * ### because higher layers will abort the hotcopy if we throw 11179251881Speter * ### an error from this function, and that renders the hotcopy 11180251881Speter * ### unusable anyway. */ 11181251881Speter if (src_ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE) 11182251881Speter { 11183251881Speter svn_error_t *err; 11184251881Speter 11185251881Speter err = svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG, 11186251881Speter pool); 11187251881Speter if (err) 11188251881Speter { 11189251881Speter if (APR_STATUS_IS_ENOENT(err->apr_err)) 11190251881Speter { 11191251881Speter /* 1.6.0 to 1.6.11 did not copy the configuration file during 11192251881Speter * hotcopy. So if we're hotcopying a repository which has been 11193251881Speter * created as a hotcopy itself, it's possible that fsfs.conf 11194251881Speter * does not exist. Ask the user to re-create it. 11195251881Speter * 11196251881Speter * ### It would be nice to make this a non-fatal error, 11197251881Speter * ### but this function does not get an svn_fs_t object 11198251881Speter * ### so we have no way of just printing a warning via 11199251881Speter * ### the fs->warning() callback. */ 11200251881Speter 11201251881Speter const char *msg; 11202251881Speter const char *src_abspath; 11203251881Speter const char *dst_abspath; 11204251881Speter const char *config_relpath; 11205251881Speter svn_error_t *err2; 11206251881Speter 11207251881Speter config_relpath = svn_dirent_join(src_fs->path, PATH_CONFIG, pool); 11208251881Speter err2 = svn_dirent_get_absolute(&src_abspath, src_fs->path, pool); 11209251881Speter if (err2) 11210251881Speter return svn_error_trace(svn_error_compose_create(err, err2)); 11211251881Speter err2 = svn_dirent_get_absolute(&dst_abspath, dst_fs->path, pool); 11212251881Speter if (err2) 11213251881Speter return svn_error_trace(svn_error_compose_create(err, err2)); 11214251881Speter 11215251881Speter /* ### hack: strip off the 'db/' directory from paths so 11216251881Speter * ### they make sense to the user */ 11217251881Speter src_abspath = svn_dirent_dirname(src_abspath, pool); 11218251881Speter dst_abspath = svn_dirent_dirname(dst_abspath, pool); 11219251881Speter 11220251881Speter msg = apr_psprintf(pool, 11221251881Speter _("Failed to create hotcopy at '%s'. " 11222251881Speter "The file '%s' is missing from the source " 11223251881Speter "repository. Please create this file, for " 11224251881Speter "instance by running 'svnadmin upgrade %s'"), 11225251881Speter dst_abspath, config_relpath, src_abspath); 11226251881Speter return svn_error_quick_wrap(err, msg); 11227251881Speter } 11228251881Speter else 11229251881Speter return svn_error_trace(err); 11230251881Speter } 11231251881Speter } 11232251881Speter 11233251881Speter if (cancel_func) 11234251881Speter SVN_ERR(cancel_func(cancel_baton)); 11235251881Speter 11236251881Speter /* Find the youngest revision in the source and destination. 11237251881Speter * We only support hotcopies from sources with an equal or greater amount 11238251881Speter * of revisions than the destination. 11239251881Speter * This also catches the case where users accidentally swap the 11240251881Speter * source and destination arguments. */ 11241251881Speter SVN_ERR(get_youngest(&src_youngest, src_fs->path, pool)); 11242251881Speter if (incremental) 11243251881Speter { 11244251881Speter SVN_ERR(get_youngest(&dst_youngest, dst_fs->path, pool)); 11245251881Speter if (src_youngest < dst_youngest) 11246251881Speter return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 11247251881Speter _("The hotcopy destination already contains more revisions " 11248251881Speter "(%lu) than the hotcopy source contains (%lu); are source " 11249251881Speter "and destination swapped?"), 11250251881Speter dst_youngest, src_youngest); 11251251881Speter } 11252251881Speter else 11253251881Speter dst_youngest = 0; 11254251881Speter 11255251881Speter if (cancel_func) 11256251881Speter SVN_ERR(cancel_func(cancel_baton)); 11257251881Speter 11258251881Speter /* Copy the min unpacked rev, and read its value. */ 11259251881Speter if (src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 11260251881Speter { 11261251881Speter const char *min_unpacked_rev_path; 11262251881Speter 11263251881Speter min_unpacked_rev_path = svn_dirent_join(src_fs->path, 11264251881Speter PATH_MIN_UNPACKED_REV, 11265251881Speter pool); 11266251881Speter SVN_ERR(read_min_unpacked_rev(&src_min_unpacked_rev, 11267251881Speter min_unpacked_rev_path, 11268251881Speter pool)); 11269251881Speter 11270251881Speter min_unpacked_rev_path = svn_dirent_join(dst_fs->path, 11271251881Speter PATH_MIN_UNPACKED_REV, 11272251881Speter pool); 11273251881Speter SVN_ERR(read_min_unpacked_rev(&dst_min_unpacked_rev, 11274251881Speter min_unpacked_rev_path, 11275251881Speter pool)); 11276251881Speter 11277251881Speter /* We only support packs coming from the hotcopy source. 11278251881Speter * The destination should not be packed independently from 11279251881Speter * the source. This also catches the case where users accidentally 11280251881Speter * swap the source and destination arguments. */ 11281251881Speter if (src_min_unpacked_rev < dst_min_unpacked_rev) 11282251881Speter return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 11283251881Speter _("The hotcopy destination already contains " 11284251881Speter "more packed revisions (%lu) than the " 11285251881Speter "hotcopy source contains (%lu)"), 11286251881Speter dst_min_unpacked_rev - 1, 11287251881Speter src_min_unpacked_rev - 1); 11288251881Speter 11289251881Speter SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path, 11290251881Speter PATH_MIN_UNPACKED_REV, pool)); 11291251881Speter } 11292251881Speter else 11293251881Speter { 11294251881Speter src_min_unpacked_rev = 0; 11295251881Speter dst_min_unpacked_rev = 0; 11296251881Speter } 11297251881Speter 11298251881Speter if (cancel_func) 11299251881Speter SVN_ERR(cancel_func(cancel_baton)); 11300251881Speter 11301251881Speter /* 11302251881Speter * Copy the necessary rev files. 11303251881Speter */ 11304251881Speter 11305251881Speter src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, pool); 11306251881Speter dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, pool); 11307251881Speter SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool)); 11308251881Speter 11309251881Speter iterpool = svn_pool_create(pool); 11310251881Speter /* First, copy packed shards. */ 11311251881Speter for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir) 11312251881Speter { 11313251881Speter svn_pool_clear(iterpool); 11314251881Speter 11315251881Speter if (cancel_func) 11316251881Speter SVN_ERR(cancel_func(cancel_baton)); 11317251881Speter 11318251881Speter /* Copy the packed shard. */ 11319251881Speter SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev, 11320251881Speter src_fs, dst_fs, 11321251881Speter rev, max_files_per_dir, 11322251881Speter iterpool)); 11323251881Speter 11324251881Speter /* If necessary, update 'current' to the most recent packed rev, 11325251881Speter * so readers can see new revisions which arrived in this pack. */ 11326251881Speter SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, 11327251881Speter rev + max_files_per_dir - 1, 11328251881Speter iterpool)); 11329251881Speter 11330251881Speter /* Remove revision files which are now packed. */ 11331251881Speter if (incremental) 11332262253Speter { 11333262253Speter SVN_ERR(hotcopy_remove_rev_files(dst_fs, rev, 11334262253Speter rev + max_files_per_dir, 11335262253Speter max_files_per_dir, iterpool)); 11336262253Speter if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT) 11337262253Speter SVN_ERR(hotcopy_remove_revprop_files(dst_fs, rev, 11338262253Speter rev + max_files_per_dir, 11339262253Speter max_files_per_dir, 11340262253Speter iterpool)); 11341262253Speter } 11342251881Speter 11343251881Speter /* Now that all revisions have moved into the pack, the original 11344251881Speter * rev dir can be removed. */ 11345262253Speter SVN_ERR(remove_folder(path_rev_shard(dst_fs, rev, iterpool), 11346262253Speter cancel_func, cancel_baton, iterpool)); 11347262253Speter if (rev > 0 && dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT) 11348262253Speter SVN_ERR(remove_folder(path_revprops_shard(dst_fs, rev, iterpool), 11349262253Speter cancel_func, cancel_baton, iterpool)); 11350251881Speter } 11351251881Speter 11352251881Speter if (cancel_func) 11353251881Speter SVN_ERR(cancel_func(cancel_baton)); 11354251881Speter 11355251881Speter /* Now, copy pairs of non-packed revisions and revprop files. 11356251881Speter * If necessary, update 'current' after copying all files from a shard. */ 11357251881Speter SVN_ERR_ASSERT(rev == src_min_unpacked_rev); 11358251881Speter SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev); 11359251881Speter revprop_src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, pool); 11360251881Speter revprop_dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, pool); 11361251881Speter SVN_ERR(svn_io_make_dir_recursively(revprop_dst_subdir, pool)); 11362251881Speter for (; rev <= src_youngest; rev++) 11363251881Speter { 11364251881Speter svn_error_t *err; 11365251881Speter 11366251881Speter svn_pool_clear(iterpool); 11367251881Speter 11368251881Speter if (cancel_func) 11369251881Speter SVN_ERR(cancel_func(cancel_baton)); 11370251881Speter 11371251881Speter /* Copy the rev file. */ 11372251881Speter err = hotcopy_copy_shard_file(src_subdir, dst_subdir, 11373251881Speter rev, max_files_per_dir, 11374251881Speter iterpool); 11375251881Speter if (err) 11376251881Speter { 11377251881Speter if (APR_STATUS_IS_ENOENT(err->apr_err) && 11378251881Speter src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 11379251881Speter { 11380251881Speter svn_error_clear(err); 11381251881Speter 11382251881Speter /* The source rev file does not exist. This can happen if the 11383251881Speter * source repository is being packed concurrently with this 11384251881Speter * hotcopy operation. 11385251881Speter * 11386251881Speter * If the new revision is now packed, and the youngest revision 11387251881Speter * we're interested in is not inside this pack, try to copy the 11388251881Speter * pack instead. 11389251881Speter * 11390251881Speter * If the youngest revision ended up being packed, don't try 11391251881Speter * to be smart and work around this. Just abort the hotcopy. */ 11392251881Speter SVN_ERR(update_min_unpacked_rev(src_fs, pool)); 11393251881Speter if (is_packed_rev(src_fs, rev)) 11394251881Speter { 11395251881Speter if (is_packed_rev(src_fs, src_youngest)) 11396251881Speter return svn_error_createf( 11397251881Speter SVN_ERR_FS_NO_SUCH_REVISION, NULL, 11398251881Speter _("The assumed HEAD revision (%lu) of the " 11399251881Speter "hotcopy source has been packed while the " 11400251881Speter "hotcopy was in progress; please restart " 11401251881Speter "the hotcopy operation"), 11402251881Speter src_youngest); 11403251881Speter 11404251881Speter SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev, 11405251881Speter src_fs, dst_fs, 11406251881Speter rev, max_files_per_dir, 11407251881Speter iterpool)); 11408251881Speter rev = dst_min_unpacked_rev; 11409251881Speter continue; 11410251881Speter } 11411251881Speter else 11412251881Speter return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 11413251881Speter _("Revision %lu disappeared from the " 11414251881Speter "hotcopy source while hotcopy was " 11415251881Speter "in progress"), rev); 11416251881Speter } 11417251881Speter else 11418251881Speter return svn_error_trace(err); 11419251881Speter } 11420251881Speter 11421251881Speter /* Copy the revprop file. */ 11422251881Speter SVN_ERR(hotcopy_copy_shard_file(revprop_src_subdir, 11423251881Speter revprop_dst_subdir, 11424251881Speter rev, max_files_per_dir, 11425251881Speter iterpool)); 11426251881Speter 11427251881Speter /* After completing a full shard, update 'current'. */ 11428251881Speter if (max_files_per_dir && rev % max_files_per_dir == 0) 11429251881Speter SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, rev, iterpool)); 11430251881Speter } 11431251881Speter svn_pool_destroy(iterpool); 11432251881Speter 11433251881Speter if (cancel_func) 11434251881Speter SVN_ERR(cancel_func(cancel_baton)); 11435251881Speter 11436251881Speter /* We assume that all revisions were copied now, i.e. we didn't exit the 11437251881Speter * above loop early. 'rev' was last incremented during exit of the loop. */ 11438251881Speter SVN_ERR_ASSERT(rev == src_youngest + 1); 11439251881Speter 11440251881Speter /* All revisions were copied. Update 'current'. */ 11441251881Speter SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, src_youngest, pool)); 11442251881Speter 11443251881Speter /* Replace the locks tree. 11444251881Speter * This is racy in case readers are currently trying to list locks in 11445251881Speter * the destination. However, we need to get rid of stale locks. 11446251881Speter * This is the simplest way of doing this, so we accept this small race. */ 11447251881Speter dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, pool); 11448251881Speter SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton, 11449251881Speter pool)); 11450251881Speter src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, pool); 11451251881Speter SVN_ERR(svn_io_check_path(src_subdir, &kind, pool)); 11452251881Speter if (kind == svn_node_dir) 11453251881Speter SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path, 11454251881Speter PATH_LOCKS_DIR, TRUE, 11455251881Speter cancel_func, cancel_baton, pool)); 11456251881Speter 11457251881Speter /* Now copy the node-origins cache tree. */ 11458251881Speter src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR, pool); 11459251881Speter SVN_ERR(svn_io_check_path(src_subdir, &kind, pool)); 11460251881Speter if (kind == svn_node_dir) 11461251881Speter SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir, dst_fs->path, 11462251881Speter PATH_NODE_ORIGINS_DIR, TRUE, 11463251881Speter cancel_func, cancel_baton, pool)); 11464251881Speter 11465251881Speter /* 11466251881Speter * NB: Data copied below is only read by writers, not readers. 11467251881Speter * Writers are still locked out at this point. 11468251881Speter */ 11469251881Speter 11470251881Speter if (dst_ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT) 11471251881Speter { 11472251881Speter /* Copy the rep cache and then remove entries for revisions 11473251881Speter * younger than the destination's youngest revision. */ 11474251881Speter src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, pool); 11475251881Speter dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, pool); 11476251881Speter SVN_ERR(svn_io_check_path(src_subdir, &kind, pool)); 11477251881Speter if (kind == svn_node_file) 11478251881Speter { 11479251881Speter SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, pool)); 11480251881Speter SVN_ERR(svn_fs_fs__del_rep_reference(dst_fs, dst_youngest, pool)); 11481251881Speter } 11482251881Speter } 11483251881Speter 11484251881Speter /* Copy the txn-current file. */ 11485251881Speter if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 11486251881Speter SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path, 11487251881Speter PATH_TXN_CURRENT, pool)); 11488251881Speter 11489251881Speter /* If a revprop generation file exists in the source filesystem, 11490251881Speter * reset it to zero (since this is on a different path, it will not 11491251881Speter * overlap with data already in cache). Also, clean up stale files 11492251881Speter * used for the named atomics implementation. */ 11493251881Speter SVN_ERR(svn_io_check_path(path_revprop_generation(src_fs, pool), 11494251881Speter &kind, pool)); 11495251881Speter if (kind == svn_node_file) 11496251881Speter SVN_ERR(write_revprop_generation_file(dst_fs, 0, pool)); 11497251881Speter 11498251881Speter SVN_ERR(cleanup_revprop_namespace(dst_fs)); 11499251881Speter 11500251881Speter /* Hotcopied FS is complete. Stamp it with a format file. */ 11501251881Speter SVN_ERR(write_format(svn_dirent_join(dst_fs->path, PATH_FORMAT, pool), 11502251881Speter dst_ffd->format, max_files_per_dir, TRUE, pool)); 11503251881Speter 11504251881Speter return SVN_NO_ERROR; 11505251881Speter} 11506251881Speter 11507251881Speter 11508251881Speter/* Set up shared data between SRC_FS and DST_FS. */ 11509251881Speterstatic void 11510251881Speterhotcopy_setup_shared_fs_data(svn_fs_t *src_fs, svn_fs_t *dst_fs) 11511251881Speter{ 11512251881Speter fs_fs_data_t *src_ffd = src_fs->fsap_data; 11513251881Speter fs_fs_data_t *dst_ffd = dst_fs->fsap_data; 11514251881Speter 11515251881Speter /* The common pool and mutexes are shared between src and dst filesystems. 11516251881Speter * During hotcopy we only grab the mutexes for the destination, so there 11517251881Speter * is no risk of dead-lock. We don't write to the src filesystem. Shared 11518251881Speter * data for the src_fs has already been initialised in fs_hotcopy(). */ 11519251881Speter dst_ffd->shared = src_ffd->shared; 11520251881Speter} 11521251881Speter 11522251881Speter/* Create an empty filesystem at DST_FS at DST_PATH with the same 11523251881Speter * configuration as SRC_FS (uuid, format, and other parameters). 11524251881Speter * After creation DST_FS has no revisions, not even revision zero. */ 11525251881Speterstatic svn_error_t * 11526251881Speterhotcopy_create_empty_dest(svn_fs_t *src_fs, 11527251881Speter svn_fs_t *dst_fs, 11528251881Speter const char *dst_path, 11529251881Speter apr_pool_t *pool) 11530251881Speter{ 11531251881Speter fs_fs_data_t *src_ffd = src_fs->fsap_data; 11532251881Speter fs_fs_data_t *dst_ffd = dst_fs->fsap_data; 11533251881Speter 11534251881Speter dst_fs->path = apr_pstrdup(pool, dst_path); 11535251881Speter 11536251881Speter dst_ffd->max_files_per_dir = src_ffd->max_files_per_dir; 11537251881Speter dst_ffd->config = src_ffd->config; 11538251881Speter dst_ffd->format = src_ffd->format; 11539251881Speter 11540251881Speter /* Create the revision data directories. */ 11541251881Speter if (dst_ffd->max_files_per_dir) 11542251881Speter SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(dst_fs, 0, pool), 11543251881Speter pool)); 11544251881Speter else 11545251881Speter SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, 11546251881Speter PATH_REVS_DIR, pool), 11547251881Speter pool)); 11548251881Speter 11549251881Speter /* Create the revprops directory. */ 11550251881Speter if (src_ffd->max_files_per_dir) 11551251881Speter SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(dst_fs, 0, pool), 11552251881Speter pool)); 11553251881Speter else 11554251881Speter SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, 11555251881Speter PATH_REVPROPS_DIR, 11556251881Speter pool), 11557251881Speter pool)); 11558251881Speter 11559251881Speter /* Create the transaction directory. */ 11560251881Speter SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, PATH_TXNS_DIR, 11561251881Speter pool), 11562251881Speter pool)); 11563251881Speter 11564251881Speter /* Create the protorevs directory. */ 11565251881Speter if (dst_ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) 11566251881Speter SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, 11567251881Speter PATH_TXN_PROTOS_DIR, 11568251881Speter pool), 11569251881Speter pool)); 11570251881Speter 11571251881Speter /* Create the 'current' file. */ 11572251881Speter SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(dst_fs, pool), 11573251881Speter (dst_ffd->format >= 11574251881Speter SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT 11575251881Speter ? "0\n" : "0 1 1\n"), 11576251881Speter pool)); 11577251881Speter 11578251881Speter /* Create lock file and UUID. */ 11579251881Speter SVN_ERR(svn_io_file_create(path_lock(dst_fs, pool), "", pool)); 11580251881Speter SVN_ERR(svn_fs_fs__set_uuid(dst_fs, src_fs->uuid, pool)); 11581251881Speter 11582251881Speter /* Create the min unpacked rev file. */ 11583251881Speter if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT) 11584251881Speter SVN_ERR(svn_io_file_create(path_min_unpacked_rev(dst_fs, pool), 11585251881Speter "0\n", pool)); 11586251881Speter /* Create the txn-current file if the repository supports 11587251881Speter the transaction sequence file. */ 11588251881Speter if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) 11589251881Speter { 11590251881Speter SVN_ERR(svn_io_file_create(path_txn_current(dst_fs, pool), 11591251881Speter "0\n", pool)); 11592251881Speter SVN_ERR(svn_io_file_create(path_txn_current_lock(dst_fs, pool), 11593251881Speter "", pool)); 11594251881Speter } 11595251881Speter 11596251881Speter dst_ffd->youngest_rev_cache = 0; 11597251881Speter 11598251881Speter hotcopy_setup_shared_fs_data(src_fs, dst_fs); 11599251881Speter SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool)); 11600251881Speter 11601251881Speter return SVN_NO_ERROR; 11602251881Speter} 11603251881Speter 11604251881Spetersvn_error_t * 11605251881Spetersvn_fs_fs__hotcopy(svn_fs_t *src_fs, 11606251881Speter svn_fs_t *dst_fs, 11607251881Speter const char *src_path, 11608251881Speter const char *dst_path, 11609251881Speter svn_boolean_t incremental, 11610251881Speter svn_cancel_func_t cancel_func, 11611251881Speter void *cancel_baton, 11612251881Speter apr_pool_t *pool) 11613251881Speter{ 11614251881Speter struct hotcopy_body_baton hbb; 11615251881Speter 11616251881Speter if (cancel_func) 11617251881Speter SVN_ERR(cancel_func(cancel_baton)); 11618251881Speter 11619251881Speter SVN_ERR(svn_fs_fs__open(src_fs, src_path, pool)); 11620251881Speter 11621251881Speter if (incremental) 11622251881Speter { 11623251881Speter const char *dst_format_abspath; 11624251881Speter svn_node_kind_t dst_format_kind; 11625251881Speter 11626251881Speter /* Check destination format to be sure we know how to incrementally 11627251881Speter * hotcopy to the destination FS. */ 11628251881Speter dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT, pool); 11629251881Speter SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind, pool)); 11630251881Speter if (dst_format_kind == svn_node_none) 11631251881Speter { 11632251881Speter /* Destination doesn't exist yet. Perform a normal hotcopy to a 11633251881Speter * empty destination using the same configuration as the source. */ 11634251881Speter SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool)); 11635251881Speter } 11636251881Speter else 11637251881Speter { 11638251881Speter /* Check the existing repository. */ 11639251881Speter SVN_ERR(svn_fs_fs__open(dst_fs, dst_path, pool)); 11640251881Speter SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs, 11641251881Speter pool)); 11642251881Speter hotcopy_setup_shared_fs_data(src_fs, dst_fs); 11643251881Speter SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool)); 11644251881Speter } 11645251881Speter } 11646251881Speter else 11647251881Speter { 11648251881Speter /* Start out with an empty destination using the same configuration 11649251881Speter * as the source. */ 11650251881Speter SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool)); 11651251881Speter } 11652251881Speter 11653251881Speter if (cancel_func) 11654251881Speter SVN_ERR(cancel_func(cancel_baton)); 11655251881Speter 11656251881Speter hbb.src_fs = src_fs; 11657251881Speter hbb.dst_fs = dst_fs; 11658251881Speter hbb.incremental = incremental; 11659251881Speter hbb.cancel_func = cancel_func; 11660251881Speter hbb.cancel_baton = cancel_baton; 11661251881Speter SVN_ERR(svn_fs_fs__with_write_lock(dst_fs, hotcopy_body, &hbb, pool)); 11662251881Speter 11663251881Speter return SVN_NO_ERROR; 11664251881Speter} 11665