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