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