1289177Speter/* lock.c : functions for manipulating filesystem locks. 2289177Speter * 3289177Speter * ==================================================================== 4289177Speter * Licensed to the Apache Software Foundation (ASF) under one 5289177Speter * or more contributor license agreements. See the NOTICE file 6289177Speter * distributed with this work for additional information 7289177Speter * regarding copyright ownership. The ASF licenses this file 8289177Speter * to you under the Apache License, Version 2.0 (the 9289177Speter * "License"); you may not use this file except in compliance 10289177Speter * with the License. You may obtain a copy of the License at 11289177Speter * 12289177Speter * http://www.apache.org/licenses/LICENSE-2.0 13289177Speter * 14289177Speter * Unless required by applicable law or agreed to in writing, 15289177Speter * software distributed under the License is distributed on an 16289177Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17289177Speter * KIND, either express or implied. See the License for the 18289177Speter * specific language governing permissions and limitations 19289177Speter * under the License. 20289177Speter * ==================================================================== 21289177Speter */ 22289177Speter 23289177Speter#include "svn_pools.h" 24289177Speter#include "svn_error.h" 25289177Speter#include "svn_dirent_uri.h" 26289177Speter#include "svn_path.h" 27289177Speter#include "svn_fs.h" 28289177Speter#include "svn_hash.h" 29289177Speter#include "svn_time.h" 30289177Speter#include "svn_utf.h" 31289177Speter 32289177Speter#include <apr_uuid.h> 33289177Speter#include <apr_file_io.h> 34289177Speter#include <apr_file_info.h> 35289177Speter 36289177Speter#include "lock.h" 37289177Speter#include "tree.h" 38289177Speter#include "fs_x.h" 39289177Speter#include "transaction.h" 40289177Speter#include "util.h" 41289177Speter#include "../libsvn_fs/fs-loader.h" 42289177Speter 43289177Speter#include "private/svn_fs_util.h" 44289177Speter#include "private/svn_fspath.h" 45289177Speter#include "private/svn_sorts_private.h" 46289177Speter#include "svn_private_config.h" 47289177Speter 48289177Speter/* Names of hash keys used to store a lock for writing to disk. */ 49289177Speter#define PATH_KEY "path" 50289177Speter#define TOKEN_KEY "token" 51289177Speter#define OWNER_KEY "owner" 52289177Speter#define CREATION_DATE_KEY "creation_date" 53289177Speter#define EXPIRATION_DATE_KEY "expiration_date" 54289177Speter#define COMMENT_KEY "comment" 55289177Speter#define IS_DAV_COMMENT_KEY "is_dav_comment" 56289177Speter#define CHILDREN_KEY "children" 57289177Speter 58289177Speter/* Number of characters from the head of a digest file name used to 59289177Speter calculate a subdirectory in which to drop that file. */ 60289177Speter#define DIGEST_SUBDIR_LEN 3 61289177Speter 62289177Speter 63289177Speter 64289177Speter/*** Generic helper functions. ***/ 65289177Speter 66289177Speter/* Set *DIGEST to the MD5 hash of STR. */ 67289177Speterstatic svn_error_t * 68289177Spetermake_digest(const char **digest, 69289177Speter const char *str, 70289177Speter apr_pool_t *pool) 71289177Speter{ 72289177Speter svn_checksum_t *checksum; 73289177Speter 74289177Speter SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, str, strlen(str), pool)); 75289177Speter 76289177Speter *digest = svn_checksum_to_cstring_display(checksum, pool); 77289177Speter return SVN_NO_ERROR; 78289177Speter} 79289177Speter 80289177Speter 81289177Speter/* Set the value of KEY (whose size is KEY_LEN, or APR_HASH_KEY_STRING 82289177Speter if unknown) to an svn_string_t-ized version of VALUE (whose size is 83289177Speter VALUE_LEN, or APR_HASH_KEY_STRING if unknown) in HASH. The value 84289177Speter will be allocated in POOL; KEY will not be duped. If either KEY or VALUE 85289177Speter is NULL, this function will do nothing. */ 86289177Speterstatic void 87289177Speterhash_store(apr_hash_t *hash, 88289177Speter const char *key, 89289177Speter apr_ssize_t key_len, 90289177Speter const char *value, 91289177Speter apr_ssize_t value_len, 92289177Speter apr_pool_t *pool) 93289177Speter{ 94289177Speter if (! (key && value)) 95289177Speter return; 96289177Speter if (value_len == APR_HASH_KEY_STRING) 97289177Speter value_len = strlen(value); 98289177Speter apr_hash_set(hash, key, key_len, 99289177Speter svn_string_ncreate(value, value_len, pool)); 100289177Speter} 101289177Speter 102289177Speter 103289177Speter/* Fetch the value of KEY from HASH, returning only the cstring data 104289177Speter of that value (if it exists). */ 105289177Speterstatic const char * 106289177Speterhash_fetch(apr_hash_t *hash, 107289177Speter const char *key) 108289177Speter{ 109289177Speter svn_string_t *str = svn_hash_gets(hash, key); 110289177Speter return str ? str->data : NULL; 111289177Speter} 112289177Speter 113289177Speter 114289177Speter/* SVN_ERR_FS_CORRUPT: the lockfile for PATH in FS is corrupt. */ 115289177Speterstatic svn_error_t * 116289177Spetererr_corrupt_lockfile(const char *fs_path, const char *path) 117289177Speter{ 118289177Speter return 119289177Speter svn_error_createf( 120289177Speter SVN_ERR_FS_CORRUPT, 0, 121289177Speter _("Corrupt lockfile for path '%s' in filesystem '%s'"), 122289177Speter path, fs_path); 123289177Speter} 124289177Speter 125289177Speter 126289177Speter/*** Digest file handling functions. ***/ 127289177Speter 128289177Speter/* Return the path of the lock/entries file for which DIGEST is the 129289177Speter hashed repository relative path. */ 130289177Speterstatic const char * 131289177Speterdigest_path_from_digest(const char *fs_path, 132289177Speter const char *digest, 133289177Speter apr_pool_t *pool) 134289177Speter{ 135289177Speter return svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR, 136289177Speter apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN), 137289177Speter digest, SVN_VA_NULL); 138289177Speter} 139289177Speter 140289177Speter 141289177Speter/* Set *DIGEST_PATH to the path to the lock/entries digest file associate 142289177Speter with PATH, where PATH is the path to the lock file or lock entries file 143289177Speter in FS. */ 144289177Speterstatic svn_error_t * 145289177Speterdigest_path_from_path(const char **digest_path, 146289177Speter const char *fs_path, 147289177Speter const char *path, 148289177Speter apr_pool_t *pool) 149289177Speter{ 150289177Speter const char *digest; 151289177Speter SVN_ERR(make_digest(&digest, path, pool)); 152289177Speter *digest_path = svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR, 153289177Speter apr_pstrmemdup(pool, digest, 154289177Speter DIGEST_SUBDIR_LEN), 155289177Speter digest, SVN_VA_NULL); 156289177Speter return SVN_NO_ERROR; 157289177Speter} 158289177Speter 159289177Speter 160289177Speter/* Write to DIGEST_PATH a representation of CHILDREN (which may be 161289177Speter empty, if the versioned path in FS represented by DIGEST_PATH has 162289177Speter no children) and LOCK (which may be NULL if that versioned path is 163289177Speter lock itself locked). Set the permissions of DIGEST_PATH to those of 164289177Speter PERMS_REFERENCE. Use POOL for temporary allocations. 165289177Speter */ 166289177Speterstatic svn_error_t * 167289177Speterwrite_digest_file(apr_hash_t *children, 168289177Speter svn_lock_t *lock, 169289177Speter const char *fs_path, 170289177Speter const char *digest_path, 171289177Speter const char *perms_reference, 172289177Speter apr_pool_t *scratch_pool) 173289177Speter{ 174289177Speter svn_error_t *err = SVN_NO_ERROR; 175289177Speter svn_stream_t *stream; 176289177Speter apr_hash_index_t *hi; 177289177Speter apr_hash_t *hash = apr_hash_make(scratch_pool); 178289177Speter const char *tmp_path; 179289177Speter 180289177Speter SVN_ERR(svn_fs_x__ensure_dir_exists(svn_dirent_join(fs_path, PATH_LOCKS_DIR, 181289177Speter scratch_pool), 182289177Speter fs_path, scratch_pool)); 183289177Speter SVN_ERR(svn_fs_x__ensure_dir_exists(svn_dirent_dirname(digest_path, 184289177Speter scratch_pool), 185289177Speter fs_path, scratch_pool)); 186289177Speter 187289177Speter if (lock) 188289177Speter { 189289177Speter const char *creation_date = NULL, *expiration_date = NULL; 190289177Speter if (lock->creation_date) 191289177Speter creation_date = svn_time_to_cstring(lock->creation_date, 192289177Speter scratch_pool); 193289177Speter if (lock->expiration_date) 194289177Speter expiration_date = svn_time_to_cstring(lock->expiration_date, 195289177Speter scratch_pool); 196289177Speter 197289177Speter hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1, 198289177Speter lock->path, APR_HASH_KEY_STRING, scratch_pool); 199289177Speter hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1, 200289177Speter lock->token, APR_HASH_KEY_STRING, scratch_pool); 201289177Speter hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1, 202289177Speter lock->owner, APR_HASH_KEY_STRING, scratch_pool); 203289177Speter hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1, 204289177Speter lock->comment, APR_HASH_KEY_STRING, scratch_pool); 205289177Speter hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1, 206289177Speter lock->is_dav_comment ? "1" : "0", 1, scratch_pool); 207289177Speter hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1, 208289177Speter creation_date, APR_HASH_KEY_STRING, scratch_pool); 209289177Speter hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1, 210289177Speter expiration_date, APR_HASH_KEY_STRING, scratch_pool); 211289177Speter } 212289177Speter if (apr_hash_count(children)) 213289177Speter { 214289177Speter svn_stringbuf_t *children_list 215289177Speter = svn_stringbuf_create_empty(scratch_pool); 216289177Speter for (hi = apr_hash_first(scratch_pool, children); 217289177Speter hi; 218289177Speter hi = apr_hash_next(hi)) 219289177Speter { 220289177Speter svn_stringbuf_appendbytes(children_list, 221289177Speter apr_hash_this_key(hi), 222289177Speter apr_hash_this_key_len(hi)); 223289177Speter svn_stringbuf_appendbyte(children_list, '\n'); 224289177Speter } 225289177Speter hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1, 226289177Speter children_list->data, children_list->len, scratch_pool); 227289177Speter } 228289177Speter 229289177Speter SVN_ERR(svn_stream_open_unique(&stream, &tmp_path, 230289177Speter svn_dirent_dirname(digest_path, 231289177Speter scratch_pool), 232289177Speter svn_io_file_del_none, scratch_pool, 233289177Speter scratch_pool)); 234289177Speter if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, 235289177Speter scratch_pool))) 236289177Speter { 237289177Speter svn_error_clear(svn_stream_close(stream)); 238289177Speter return svn_error_createf(err->apr_err, 239289177Speter err, 240289177Speter _("Cannot write lock/entries hashfile '%s'"), 241289177Speter svn_dirent_local_style(tmp_path, 242289177Speter scratch_pool)); 243289177Speter } 244289177Speter 245289177Speter SVN_ERR(svn_stream_close(stream)); 246289177Speter SVN_ERR(svn_io_file_rename(tmp_path, digest_path, scratch_pool)); 247289177Speter SVN_ERR(svn_io_copy_perms(perms_reference, digest_path, scratch_pool)); 248289177Speter return SVN_NO_ERROR; 249289177Speter} 250289177Speter 251289177Speter 252289177Speter/* Parse the file at DIGEST_PATH, populating the lock LOCK_P in that 253289177Speter file (if it exists, and if *LOCK_P is non-NULL) and the hash of 254289177Speter CHILDREN_P (if any exist, and if *CHILDREN_P is non-NULL). Use POOL 255289177Speter for all allocations. */ 256289177Speterstatic svn_error_t * 257289177Speterread_digest_file(apr_hash_t **children_p, 258289177Speter svn_lock_t **lock_p, 259289177Speter const char *fs_path, 260289177Speter const char *digest_path, 261289177Speter apr_pool_t *pool) 262289177Speter{ 263289177Speter svn_error_t *err = SVN_NO_ERROR; 264289177Speter svn_lock_t *lock; 265289177Speter apr_hash_t *hash; 266289177Speter svn_stream_t *stream; 267289177Speter const char *val; 268289177Speter svn_node_kind_t kind; 269289177Speter 270289177Speter if (lock_p) 271289177Speter *lock_p = NULL; 272289177Speter if (children_p) 273289177Speter *children_p = apr_hash_make(pool); 274289177Speter 275289177Speter SVN_ERR(svn_io_check_path(digest_path, &kind, pool)); 276289177Speter if (kind == svn_node_none) 277289177Speter return SVN_NO_ERROR; 278289177Speter 279289177Speter /* If our caller doesn't care about anything but the presence of the 280289177Speter file... whatever. */ 281289177Speter if (kind == svn_node_file && !lock_p && !children_p) 282289177Speter return SVN_NO_ERROR; 283289177Speter 284289177Speter SVN_ERR(svn_stream_open_readonly(&stream, digest_path, pool, pool)); 285289177Speter 286289177Speter hash = apr_hash_make(pool); 287289177Speter if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool))) 288289177Speter { 289289177Speter svn_error_clear(svn_stream_close(stream)); 290289177Speter return svn_error_createf(err->apr_err, 291289177Speter err, 292289177Speter _("Can't parse lock/entries hashfile '%s'"), 293289177Speter svn_dirent_local_style(digest_path, pool)); 294289177Speter } 295289177Speter SVN_ERR(svn_stream_close(stream)); 296289177Speter 297289177Speter /* If our caller cares, see if we have a lock path in our hash. If 298289177Speter so, we'll assume we have a lock here. */ 299289177Speter val = hash_fetch(hash, PATH_KEY); 300289177Speter if (val && lock_p) 301289177Speter { 302289177Speter const char *path = val; 303289177Speter 304289177Speter /* Create our lock and load it up. */ 305289177Speter lock = svn_lock_create(pool); 306289177Speter lock->path = path; 307289177Speter 308289177Speter if (! ((lock->token = hash_fetch(hash, TOKEN_KEY)))) 309289177Speter return svn_error_trace(err_corrupt_lockfile(fs_path, path)); 310289177Speter 311289177Speter if (! ((lock->owner = hash_fetch(hash, OWNER_KEY)))) 312289177Speter return svn_error_trace(err_corrupt_lockfile(fs_path, path)); 313289177Speter 314289177Speter if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY)))) 315289177Speter return svn_error_trace(err_corrupt_lockfile(fs_path, path)); 316289177Speter lock->is_dav_comment = (val[0] == '1'); 317289177Speter 318289177Speter if (! ((val = hash_fetch(hash, CREATION_DATE_KEY)))) 319289177Speter return svn_error_trace(err_corrupt_lockfile(fs_path, path)); 320289177Speter SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool)); 321289177Speter 322289177Speter if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY))) 323289177Speter SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool)); 324289177Speter 325289177Speter lock->comment = hash_fetch(hash, COMMENT_KEY); 326289177Speter 327289177Speter *lock_p = lock; 328289177Speter } 329289177Speter 330289177Speter /* If our caller cares, see if we have any children for this path. */ 331289177Speter val = hash_fetch(hash, CHILDREN_KEY); 332289177Speter if (val && children_p) 333289177Speter { 334289177Speter apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool); 335289177Speter int i; 336289177Speter 337289177Speter for (i = 0; i < kiddos->nelts; i++) 338289177Speter { 339289177Speter svn_hash_sets(*children_p, APR_ARRAY_IDX(kiddos, i, const char *), 340289177Speter (void *)1); 341289177Speter } 342289177Speter } 343289177Speter return SVN_NO_ERROR; 344289177Speter} 345289177Speter 346289177Speter 347289177Speter 348289177Speter/*** Lock helper functions (path here are still FS paths, not on-disk 349289177Speter schema-supporting paths) ***/ 350289177Speter 351289177Speter 352289177Speter/* Write LOCK in FS to the actual OS filesystem. 353289177Speter 354289177Speter Use PERMS_REFERENCE for the permissions of any digest files. 355289177Speter */ 356289177Speterstatic svn_error_t * 357289177Speterset_lock(const char *fs_path, 358289177Speter svn_lock_t *lock, 359289177Speter const char *perms_reference, 360289177Speter apr_pool_t *scratch_pool) 361289177Speter{ 362289177Speter const char *digest_path; 363289177Speter apr_hash_t *children; 364289177Speter 365289177Speter SVN_ERR(digest_path_from_path(&digest_path, fs_path, lock->path, 366289177Speter scratch_pool)); 367289177Speter 368289177Speter /* We could get away without reading the file as children should 369289177Speter always come back empty. */ 370289177Speter SVN_ERR(read_digest_file(&children, NULL, fs_path, digest_path, 371289177Speter scratch_pool)); 372289177Speter 373289177Speter SVN_ERR(write_digest_file(children, lock, fs_path, digest_path, 374289177Speter perms_reference, scratch_pool)); 375289177Speter 376289177Speter return SVN_NO_ERROR; 377289177Speter} 378289177Speter 379289177Speterstatic svn_error_t * 380289177Speterdelete_lock(const char *fs_path, 381289177Speter const char *path, 382289177Speter apr_pool_t *scratch_pool) 383289177Speter{ 384289177Speter const char *digest_path; 385289177Speter 386289177Speter SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, scratch_pool)); 387289177Speter 388289177Speter SVN_ERR(svn_io_remove_file2(digest_path, TRUE, scratch_pool)); 389289177Speter 390289177Speter return SVN_NO_ERROR; 391289177Speter} 392289177Speter 393289177Speterstatic svn_error_t * 394289177Speteradd_to_digest(const char *fs_path, 395289177Speter apr_array_header_t *paths, 396289177Speter const char *index_path, 397289177Speter const char *perms_reference, 398289177Speter apr_pool_t *scratch_pool) 399289177Speter{ 400289177Speter const char *index_digest_path; 401289177Speter apr_hash_t *children; 402289177Speter svn_lock_t *lock; 403289177Speter int i; 404289177Speter unsigned int original_count; 405289177Speter 406289177Speter SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, 407289177Speter scratch_pool)); 408289177Speter SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, 409289177Speter scratch_pool)); 410289177Speter 411289177Speter original_count = apr_hash_count(children); 412289177Speter 413289177Speter for (i = 0; i < paths->nelts; ++i) 414289177Speter { 415289177Speter const char *path = APR_ARRAY_IDX(paths, i, const char *); 416289177Speter const char *digest_path, *digest_file; 417289177Speter 418289177Speter SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, 419289177Speter scratch_pool)); 420289177Speter digest_file = svn_dirent_basename(digest_path, NULL); 421289177Speter svn_hash_sets(children, digest_file, (void *)1); 422289177Speter } 423289177Speter 424289177Speter if (apr_hash_count(children) != original_count) 425289177Speter SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path, 426289177Speter perms_reference, scratch_pool)); 427289177Speter 428289177Speter return SVN_NO_ERROR; 429289177Speter} 430289177Speter 431289177Speterstatic svn_error_t * 432289177Speterdelete_from_digest(const char *fs_path, 433289177Speter apr_array_header_t *paths, 434289177Speter const char *index_path, 435289177Speter const char *perms_reference, 436289177Speter apr_pool_t *scratch_pool) 437289177Speter{ 438289177Speter const char *index_digest_path; 439289177Speter apr_hash_t *children; 440289177Speter svn_lock_t *lock; 441289177Speter int i; 442289177Speter 443289177Speter SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, 444289177Speter scratch_pool)); 445289177Speter SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, 446289177Speter scratch_pool)); 447289177Speter 448289177Speter for (i = 0; i < paths->nelts; ++i) 449289177Speter { 450289177Speter const char *path = APR_ARRAY_IDX(paths, i, const char *); 451289177Speter const char *digest_path, *digest_file; 452289177Speter 453289177Speter SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, 454289177Speter scratch_pool)); 455289177Speter digest_file = svn_dirent_basename(digest_path, NULL); 456289177Speter svn_hash_sets(children, digest_file, NULL); 457289177Speter } 458289177Speter 459289177Speter if (apr_hash_count(children) || lock) 460289177Speter SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path, 461289177Speter perms_reference, scratch_pool)); 462289177Speter else 463289177Speter SVN_ERR(svn_io_remove_file2(index_digest_path, TRUE, scratch_pool)); 464289177Speter 465289177Speter return SVN_NO_ERROR; 466289177Speter} 467289177Speter 468289177Speterstatic svn_error_t * 469289177Speterunlock_single(svn_fs_t *fs, 470289177Speter svn_lock_t *lock, 471289177Speter apr_pool_t *pool); 472289177Speter 473289177Speter/* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be 474289177Speter TRUE if the caller (or one of its callers) has taken out the 475289177Speter repository-wide write lock, FALSE otherwise. If MUST_EXIST is 476289177Speter not set, the function will simply return NULL in *LOCK_P instead 477289177Speter of creating an SVN_FS__ERR_NO_SUCH_LOCK error in case the lock 478289177Speter was not found (much faster). Use POOL for allocations. */ 479289177Speterstatic svn_error_t * 480289177Speterget_lock(svn_lock_t **lock_p, 481289177Speter svn_fs_t *fs, 482289177Speter const char *path, 483289177Speter svn_boolean_t have_write_lock, 484289177Speter svn_boolean_t must_exist, 485289177Speter apr_pool_t *pool) 486289177Speter{ 487289177Speter svn_lock_t *lock = NULL; 488289177Speter const char *digest_path; 489289177Speter svn_node_kind_t kind; 490289177Speter 491289177Speter SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool)); 492289177Speter SVN_ERR(svn_io_check_path(digest_path, &kind, pool)); 493289177Speter 494289177Speter *lock_p = NULL; 495289177Speter if (kind != svn_node_none) 496289177Speter SVN_ERR(read_digest_file(NULL, &lock, fs->path, digest_path, pool)); 497289177Speter 498289177Speter if (! lock) 499289177Speter return must_exist ? SVN_FS__ERR_NO_SUCH_LOCK(fs, path) : SVN_NO_ERROR; 500289177Speter 501289177Speter /* Don't return an expired lock. */ 502289177Speter if (lock->expiration_date && (apr_time_now() > lock->expiration_date)) 503289177Speter { 504289177Speter /* Only remove the lock if we have the write lock. 505289177Speter Read operations shouldn't change the filesystem. */ 506289177Speter if (have_write_lock) 507289177Speter SVN_ERR(unlock_single(fs, lock, pool)); 508289177Speter return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token); 509289177Speter } 510289177Speter 511289177Speter *lock_p = lock; 512289177Speter return SVN_NO_ERROR; 513289177Speter} 514289177Speter 515289177Speter 516289177Speter/* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be 517289177Speter TRUE if the caller (or one of its callers) has taken out the 518289177Speter repository-wide write lock, FALSE otherwise. Use POOL for 519289177Speter allocations. */ 520289177Speterstatic svn_error_t * 521289177Speterget_lock_helper(svn_fs_t *fs, 522289177Speter svn_lock_t **lock_p, 523289177Speter const char *path, 524289177Speter svn_boolean_t have_write_lock, 525289177Speter apr_pool_t *pool) 526289177Speter{ 527289177Speter svn_lock_t *lock; 528289177Speter svn_error_t *err; 529289177Speter 530289177Speter err = get_lock(&lock, fs, path, have_write_lock, FALSE, pool); 531289177Speter 532289177Speter /* We've deliberately decided that this function doesn't tell the 533289177Speter caller *why* the lock is unavailable. */ 534289177Speter if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK) 535289177Speter || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED))) 536289177Speter { 537289177Speter svn_error_clear(err); 538289177Speter *lock_p = NULL; 539289177Speter return SVN_NO_ERROR; 540289177Speter } 541289177Speter else 542289177Speter SVN_ERR(err); 543289177Speter 544289177Speter *lock_p = lock; 545289177Speter return SVN_NO_ERROR; 546289177Speter} 547289177Speter 548289177Speter 549289177Speter/* Baton for locks_walker(). */ 550289177Spetertypedef struct walk_locks_baton_t 551289177Speter{ 552289177Speter svn_fs_get_locks_callback_t get_locks_func; 553289177Speter void *get_locks_baton; 554289177Speter svn_fs_t *fs; 555289177Speter} walk_locks_baton_t; 556289177Speter 557289177Speter/* Implements walk_digests_callback_t. */ 558289177Speterstatic svn_error_t * 559289177Speterlocks_walker(void *baton, 560289177Speter const char *fs_path, 561289177Speter const char *digest_path, 562289177Speter svn_lock_t *lock, 563289177Speter svn_boolean_t have_write_lock, 564289177Speter apr_pool_t *pool) 565289177Speter{ 566289177Speter walk_locks_baton_t *wlb = baton; 567289177Speter 568289177Speter if (lock) 569289177Speter { 570289177Speter /* Don't report an expired lock. */ 571289177Speter if (lock->expiration_date == 0 572289177Speter || (apr_time_now() <= lock->expiration_date)) 573289177Speter { 574289177Speter if (wlb->get_locks_func) 575289177Speter SVN_ERR(wlb->get_locks_func(wlb->get_locks_baton, lock, pool)); 576289177Speter } 577289177Speter else 578289177Speter { 579289177Speter /* Only remove the lock if we have the write lock. 580289177Speter Read operations shouldn't change the filesystem. */ 581289177Speter if (have_write_lock) 582289177Speter SVN_ERR(unlock_single(wlb->fs, lock, pool)); 583289177Speter } 584289177Speter } 585289177Speter 586289177Speter return SVN_NO_ERROR; 587289177Speter} 588289177Speter 589289177Speter/* Callback type for walk_digest_files(). 590289177Speter * 591289177Speter * LOCK come from a read_digest_file(digest_path) call. 592289177Speter */ 593289177Spetertypedef svn_error_t *(*walk_digests_callback_t)(void *baton, 594289177Speter const char *fs_path, 595289177Speter const char *digest_path, 596289177Speter svn_lock_t *lock, 597289177Speter svn_boolean_t have_write_lock, 598289177Speter apr_pool_t *pool); 599289177Speter 600289177Speter/* A function that calls WALK_DIGESTS_FUNC/WALK_DIGESTS_BATON for 601289177Speter all lock digest files in and under PATH in FS. 602289177Speter HAVE_WRITE_LOCK should be true if the caller (directly or indirectly) 603289177Speter has the FS write lock. */ 604289177Speterstatic svn_error_t * 605289177Speterwalk_digest_files(const char *fs_path, 606289177Speter const char *digest_path, 607289177Speter walk_digests_callback_t walk_digests_func, 608289177Speter void *walk_digests_baton, 609289177Speter svn_boolean_t have_write_lock, 610289177Speter apr_pool_t *pool) 611289177Speter{ 612289177Speter apr_hash_index_t *hi; 613289177Speter apr_hash_t *children; 614289177Speter apr_pool_t *subpool; 615289177Speter svn_lock_t *lock; 616289177Speter 617289177Speter /* First, send up any locks in the current digest file. */ 618289177Speter SVN_ERR(read_digest_file(&children, &lock, fs_path, digest_path, pool)); 619289177Speter 620289177Speter SVN_ERR(walk_digests_func(walk_digests_baton, fs_path, digest_path, lock, 621289177Speter have_write_lock, pool)); 622289177Speter 623289177Speter /* Now, report all the child entries (if any; bail otherwise). */ 624289177Speter if (! apr_hash_count(children)) 625289177Speter return SVN_NO_ERROR; 626289177Speter subpool = svn_pool_create(pool); 627289177Speter for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi)) 628289177Speter { 629289177Speter const char *digest = apr_hash_this_key(hi); 630289177Speter svn_pool_clear(subpool); 631289177Speter 632289177Speter SVN_ERR(read_digest_file 633289177Speter (NULL, &lock, fs_path, 634289177Speter digest_path_from_digest(fs_path, digest, subpool), subpool)); 635289177Speter 636289177Speter SVN_ERR(walk_digests_func(walk_digests_baton, fs_path, digest_path, lock, 637289177Speter have_write_lock, subpool)); 638289177Speter } 639289177Speter svn_pool_destroy(subpool); 640289177Speter return SVN_NO_ERROR; 641289177Speter} 642289177Speter 643289177Speter/* A function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for 644289177Speter all locks in and under PATH in FS. 645289177Speter HAVE_WRITE_LOCK should be true if the caller (directly or indirectly) 646289177Speter has the FS write lock. */ 647289177Speterstatic svn_error_t * 648289177Speterwalk_locks(svn_fs_t *fs, 649289177Speter const char *digest_path, 650289177Speter svn_fs_get_locks_callback_t get_locks_func, 651289177Speter void *get_locks_baton, 652289177Speter svn_boolean_t have_write_lock, 653289177Speter apr_pool_t *pool) 654289177Speter{ 655289177Speter walk_locks_baton_t wlb; 656289177Speter 657289177Speter wlb.get_locks_func = get_locks_func; 658289177Speter wlb.get_locks_baton = get_locks_baton; 659289177Speter wlb.fs = fs; 660289177Speter SVN_ERR(walk_digest_files(fs->path, digest_path, locks_walker, &wlb, 661289177Speter have_write_lock, pool)); 662289177Speter return SVN_NO_ERROR; 663289177Speter} 664289177Speter 665289177Speter 666289177Speter/* Utility function: verify that a lock can be used. Interesting 667289177Speter errors returned from this function: 668289177Speter 669289177Speter SVN_ERR_FS_NO_USER: No username attached to FS. 670289177Speter SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner. 671289177Speter SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK. 672289177Speter */ 673289177Speterstatic svn_error_t * 674289177Speterverify_lock(svn_fs_t *fs, 675289177Speter svn_lock_t *lock) 676289177Speter{ 677289177Speter if ((! fs->access_ctx) || (! fs->access_ctx->username)) 678289177Speter return svn_error_createf 679289177Speter (SVN_ERR_FS_NO_USER, NULL, 680289177Speter _("Cannot verify lock on path '%s'; no username available"), 681289177Speter lock->path); 682289177Speter 683289177Speter else if (strcmp(fs->access_ctx->username, lock->owner) != 0) 684289177Speter return svn_error_createf 685289177Speter (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL, 686289177Speter _("User '%s' does not own lock on path '%s' (currently locked by '%s')"), 687289177Speter fs->access_ctx->username, lock->path, lock->owner); 688289177Speter 689289177Speter else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL) 690289177Speter return svn_error_createf 691289177Speter (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL, 692289177Speter _("Cannot verify lock on path '%s'; no matching lock-token available"), 693289177Speter lock->path); 694289177Speter 695289177Speter return SVN_NO_ERROR; 696289177Speter} 697289177Speter 698289177Speter 699289177Speter/* This implements the svn_fs_get_locks_callback_t interface, where 700289177Speter BATON is just an svn_fs_t object. */ 701289177Speterstatic svn_error_t * 702289177Speterget_locks_callback(void *baton, 703289177Speter svn_lock_t *lock, 704289177Speter apr_pool_t *pool) 705289177Speter{ 706289177Speter return verify_lock(baton, lock); 707289177Speter} 708289177Speter 709289177Speter 710289177Speter/* The main routine for lock enforcement, used throughout libsvn_fs_x. */ 711289177Spetersvn_error_t * 712289177Spetersvn_fs_x__allow_locked_operation(const char *path, 713289177Speter svn_fs_t *fs, 714289177Speter svn_boolean_t recurse, 715289177Speter svn_boolean_t have_write_lock, 716289177Speter apr_pool_t *scratch_pool) 717289177Speter{ 718289177Speter path = svn_fs__canonicalize_abspath(path, scratch_pool); 719289177Speter if (recurse) 720289177Speter { 721289177Speter /* Discover all locks at or below the path. */ 722289177Speter const char *digest_path; 723289177Speter SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, 724289177Speter scratch_pool)); 725289177Speter SVN_ERR(walk_locks(fs, digest_path, get_locks_callback, 726289177Speter fs, have_write_lock, scratch_pool)); 727289177Speter } 728289177Speter else 729289177Speter { 730289177Speter /* Discover and verify any lock attached to the path. */ 731289177Speter svn_lock_t *lock; 732289177Speter SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock, 733289177Speter scratch_pool)); 734289177Speter if (lock) 735289177Speter SVN_ERR(verify_lock(fs, lock)); 736289177Speter } 737289177Speter return SVN_NO_ERROR; 738289177Speter} 739289177Speter 740289177Speter/* The effective arguments for lock_body() below. */ 741289177Spetertypedef struct lock_baton_t { 742289177Speter svn_fs_t *fs; 743289177Speter apr_array_header_t *targets; 744289177Speter apr_array_header_t *infos; 745289177Speter const char *comment; 746289177Speter svn_boolean_t is_dav_comment; 747289177Speter apr_time_t expiration_date; 748289177Speter svn_boolean_t steal_lock; 749289177Speter apr_pool_t *result_pool; 750289177Speter} lock_baton_t; 751289177Speter 752289177Speterstatic svn_error_t * 753289177Spetercheck_lock(svn_error_t **fs_err, 754289177Speter const char *path, 755289177Speter const svn_fs_lock_target_t *target, 756289177Speter lock_baton_t *lb, 757289177Speter svn_fs_root_t *root, 758289177Speter svn_revnum_t youngest_rev, 759289177Speter apr_pool_t *pool) 760289177Speter{ 761289177Speter svn_node_kind_t kind; 762289177Speter svn_lock_t *existing_lock; 763289177Speter 764289177Speter *fs_err = SVN_NO_ERROR; 765289177Speter 766289177Speter SVN_ERR(svn_fs_x__check_path(&kind, root, path, pool)); 767289177Speter if (kind == svn_node_dir) 768289177Speter { 769289177Speter *fs_err = SVN_FS__ERR_NOT_FILE(lb->fs, path); 770289177Speter return SVN_NO_ERROR; 771289177Speter } 772289177Speter 773289177Speter /* While our locking implementation easily supports the locking of 774289177Speter nonexistent paths, we deliberately choose not to allow such madness. */ 775289177Speter if (kind == svn_node_none) 776289177Speter { 777289177Speter if (SVN_IS_VALID_REVNUM(target->current_rev)) 778289177Speter *fs_err = svn_error_createf( 779289177Speter SVN_ERR_FS_OUT_OF_DATE, NULL, 780289177Speter _("Path '%s' doesn't exist in HEAD revision"), 781289177Speter path); 782289177Speter else 783289177Speter *fs_err = svn_error_createf( 784289177Speter SVN_ERR_FS_NOT_FOUND, NULL, 785289177Speter _("Path '%s' doesn't exist in HEAD revision"), 786289177Speter path); 787289177Speter 788289177Speter return SVN_NO_ERROR; 789289177Speter } 790289177Speter 791289177Speter /* Is the caller attempting to lock an out-of-date working file? */ 792289177Speter if (SVN_IS_VALID_REVNUM(target->current_rev)) 793289177Speter { 794289177Speter svn_revnum_t created_rev; 795289177Speter 796289177Speter if (target->current_rev > youngest_rev) 797289177Speter { 798289177Speter *fs_err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 799289177Speter _("No such revision %ld"), 800289177Speter target->current_rev); 801289177Speter return SVN_NO_ERROR; 802289177Speter } 803289177Speter 804289177Speter SVN_ERR(svn_fs_x__node_created_rev(&created_rev, root, path, 805289177Speter pool)); 806289177Speter 807289177Speter /* SVN_INVALID_REVNUM means the path doesn't exist. So 808289177Speter apparently somebody is trying to lock something in their 809289177Speter working copy, but somebody else has deleted the thing 810289177Speter from HEAD. That counts as being 'out of date'. */ 811289177Speter if (! SVN_IS_VALID_REVNUM(created_rev)) 812289177Speter { 813289177Speter *fs_err = svn_error_createf 814289177Speter (SVN_ERR_FS_OUT_OF_DATE, NULL, 815289177Speter _("Path '%s' doesn't exist in HEAD revision"), path); 816289177Speter 817289177Speter return SVN_NO_ERROR; 818289177Speter } 819289177Speter 820289177Speter if (target->current_rev < created_rev) 821289177Speter { 822289177Speter *fs_err = svn_error_createf 823289177Speter (SVN_ERR_FS_OUT_OF_DATE, NULL, 824289177Speter _("Lock failed: newer version of '%s' exists"), path); 825289177Speter 826289177Speter return SVN_NO_ERROR; 827289177Speter } 828289177Speter } 829289177Speter 830289177Speter /* If the caller provided a TOKEN, we *really* need to see 831289177Speter if a lock already exists with that token, and if so, verify that 832289177Speter the lock's path matches PATH. Otherwise we run the risk of 833289177Speter breaking the 1-to-1 mapping of lock tokens to locked paths. */ 834289177Speter /* ### TODO: actually do this check. This is tough, because the 835289177Speter schema doesn't supply a lookup-by-token mechanism. */ 836289177Speter 837289177Speter /* Is the path already locked? 838289177Speter 839289177Speter Note that this next function call will automatically ignore any 840289177Speter errors about {the path not existing as a key, the path's token 841289177Speter not existing as a key, the lock just having been expired}. And 842289177Speter that's totally fine. Any of these three errors are perfectly 843289177Speter acceptable to ignore; it means that the path is now free and 844289177Speter clear for locking, because the fsx funcs just cleared out both 845289177Speter of the tables for us. */ 846289177Speter SVN_ERR(get_lock_helper(lb->fs, &existing_lock, path, TRUE, pool)); 847289177Speter if (existing_lock) 848289177Speter { 849289177Speter if (! lb->steal_lock) 850289177Speter { 851289177Speter /* Sorry, the path is already locked. */ 852289177Speter *fs_err = SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock); 853289177Speter return SVN_NO_ERROR; 854289177Speter } 855289177Speter } 856289177Speter 857289177Speter return SVN_NO_ERROR; 858289177Speter} 859289177Speter 860289177Spetertypedef struct lock_info_t { 861289177Speter const char *path; 862289177Speter const char *component; 863289177Speter svn_lock_t *lock; 864289177Speter svn_error_t *fs_err; 865289177Speter} lock_info_t; 866289177Speter 867289177Speter/* The body of svn_fs_x__lock(), which see. 868289177Speter 869289177Speter BATON is a 'lock_baton_t *' holding the effective arguments. 870289177Speter BATON->targets is an array of 'svn_sort__item_t' targets, sorted by 871289177Speter path, mapping canonical path to 'svn_fs_lock_target_t'. Set 872289177Speter BATON->infos to an array of 'lock_info_t' holding the results. For 873289177Speter the other arguments, see svn_fs_lock_many(). 874289177Speter 875289177Speter This implements the svn_fs_x__with_write_lock() 'body' callback 876289177Speter type, and assumes that the write lock is held. 877289177Speter */ 878289177Speterstatic svn_error_t * 879289177Speterlock_body(void *baton, apr_pool_t *pool) 880289177Speter{ 881289177Speter lock_baton_t *lb = baton; 882289177Speter svn_fs_root_t *root; 883289177Speter svn_revnum_t youngest; 884289177Speter const char *rev_0_path; 885289177Speter int i, outstanding = 0; 886289177Speter apr_pool_t *iterpool = svn_pool_create(pool); 887289177Speter 888289177Speter lb->infos = apr_array_make(lb->result_pool, lb->targets->nelts, 889289177Speter sizeof(lock_info_t)); 890289177Speter 891289177Speter /* Until we implement directory locks someday, we only allow locks 892289177Speter on files or non-existent paths. */ 893289177Speter /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular 894289177Speter library dependencies, which are not portable. */ 895289177Speter SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool)); 896289177Speter SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool)); 897289177Speter 898289177Speter for (i = 0; i < lb->targets->nelts; ++i) 899289177Speter { 900289177Speter const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i, 901289177Speter svn_sort__item_t); 902289177Speter const svn_fs_lock_target_t *target = item->value; 903289177Speter lock_info_t info; 904289177Speter 905289177Speter svn_pool_clear(iterpool); 906289177Speter 907289177Speter info.path = item->key; 908289177Speter SVN_ERR(check_lock(&info.fs_err, info.path, target, lb, root, 909289177Speter youngest, iterpool)); 910289177Speter info.lock = NULL; 911289177Speter info.component = NULL; 912289177Speter APR_ARRAY_PUSH(lb->infos, lock_info_t) = info; 913289177Speter if (!info.fs_err) 914289177Speter ++outstanding; 915289177Speter } 916289177Speter 917289177Speter rev_0_path = svn_fs_x__path_rev_absolute(lb->fs, 0, pool); 918289177Speter 919289177Speter /* Given the paths: 920289177Speter 921289177Speter /foo/bar/f 922289177Speter /foo/bar/g 923289177Speter /zig/x 924289177Speter 925289177Speter we loop through repeatedly. The first pass sees '/' on all paths 926289177Speter and writes the '/' index. The second pass sees '/foo' twice and 927289177Speter writes that index followed by '/zig' and that index. The third 928289177Speter pass sees '/foo/bar' twice and writes that index, and then writes 929289177Speter the lock for '/zig/x'. The fourth pass writes the locks for 930289177Speter '/foo/bar/f' and '/foo/bar/g'. 931289177Speter 932289177Speter Writing indices before locks is correct: if interrupted it leaves 933289177Speter indices without locks rather than locks without indices. An 934289177Speter index without a lock is consistent in that it always shows up as 935289177Speter unlocked in svn_fs_x__allow_locked_operation. A lock without an 936289177Speter index is inconsistent, svn_fs_x__allow_locked_operation will 937289177Speter show locked on the file but unlocked on the parent. */ 938289177Speter 939289177Speter 940289177Speter while (outstanding) 941289177Speter { 942289177Speter const char *last_path = NULL; 943289177Speter apr_array_header_t *paths; 944289177Speter 945289177Speter svn_pool_clear(iterpool); 946289177Speter paths = apr_array_make(iterpool, 1, sizeof(const char *)); 947289177Speter 948289177Speter for (i = 0; i < lb->infos->nelts; ++i) 949289177Speter { 950289177Speter lock_info_t *info = &APR_ARRAY_IDX(lb->infos, i, lock_info_t); 951289177Speter const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i, 952289177Speter svn_sort__item_t); 953289177Speter const svn_fs_lock_target_t *target = item->value; 954289177Speter 955289177Speter if (!info->fs_err && !info->lock) 956289177Speter { 957289177Speter if (!info->component) 958289177Speter { 959289177Speter info->component = info->path; 960289177Speter APR_ARRAY_PUSH(paths, const char *) = info->path; 961289177Speter last_path = "/"; 962289177Speter } 963289177Speter else 964289177Speter { 965289177Speter info->component = strchr(info->component + 1, '/'); 966289177Speter if (!info->component) 967289177Speter { 968289177Speter /* The component is a path to lock, this cannot 969289177Speter match a previous path that need to be indexed. */ 970289177Speter if (paths->nelts) 971289177Speter { 972289177Speter SVN_ERR(add_to_digest(lb->fs->path, paths, last_path, 973289177Speter rev_0_path, iterpool)); 974289177Speter apr_array_clear(paths); 975289177Speter last_path = NULL; 976289177Speter } 977289177Speter 978289177Speter info->lock = svn_lock_create(lb->result_pool); 979289177Speter if (target->token) 980289177Speter info->lock->token = target->token; 981289177Speter else 982289177Speter SVN_ERR(svn_fs_x__generate_lock_token( 983289177Speter &(info->lock->token), lb->fs, 984289177Speter lb->result_pool)); 985289177Speter info->lock->path = info->path; 986289177Speter info->lock->owner = lb->fs->access_ctx->username; 987289177Speter info->lock->comment = lb->comment; 988289177Speter info->lock->is_dav_comment = lb->is_dav_comment; 989289177Speter info->lock->creation_date = apr_time_now(); 990289177Speter info->lock->expiration_date = lb->expiration_date; 991289177Speter 992289177Speter info->fs_err = set_lock(lb->fs->path, info->lock, 993289177Speter rev_0_path, iterpool); 994289177Speter --outstanding; 995289177Speter } 996289177Speter else 997289177Speter { 998289177Speter /* The component is a path to an index. */ 999289177Speter apr_size_t len = info->component - info->path; 1000289177Speter 1001289177Speter if (last_path 1002289177Speter && (strncmp(last_path, info->path, len) 1003289177Speter || strlen(last_path) != len)) 1004289177Speter { 1005289177Speter /* No match to the previous paths to index. */ 1006289177Speter SVN_ERR(add_to_digest(lb->fs->path, paths, last_path, 1007289177Speter rev_0_path, iterpool)); 1008289177Speter apr_array_clear(paths); 1009289177Speter last_path = NULL; 1010289177Speter } 1011289177Speter APR_ARRAY_PUSH(paths, const char *) = info->path; 1012289177Speter if (!last_path) 1013289177Speter last_path = apr_pstrndup(iterpool, info->path, len); 1014289177Speter } 1015289177Speter } 1016289177Speter } 1017289177Speter 1018289177Speter if (last_path && i == lb->infos->nelts - 1) 1019289177Speter SVN_ERR(add_to_digest(lb->fs->path, paths, last_path, 1020289177Speter rev_0_path, iterpool)); 1021289177Speter } 1022289177Speter } 1023289177Speter 1024289177Speter return SVN_NO_ERROR; 1025289177Speter} 1026289177Speter 1027289177Speter/* The effective arguments for unlock_body() below. */ 1028289177Spetertypedef struct unlock_baton_t { 1029289177Speter svn_fs_t *fs; 1030289177Speter apr_array_header_t *targets; 1031289177Speter apr_array_header_t *infos; 1032289177Speter /* Set skip_check TRUE to prevent the checks that set infos[].fs_err. */ 1033289177Speter svn_boolean_t skip_check; 1034289177Speter svn_boolean_t break_lock; 1035289177Speter apr_pool_t *result_pool; 1036289177Speter} unlock_baton_t; 1037289177Speter 1038289177Speterstatic svn_error_t * 1039289177Spetercheck_unlock(svn_error_t **fs_err, 1040289177Speter const char *path, 1041289177Speter const char *token, 1042289177Speter unlock_baton_t *ub, 1043289177Speter svn_fs_root_t *root, 1044289177Speter apr_pool_t *pool) 1045289177Speter{ 1046289177Speter svn_lock_t *lock; 1047289177Speter 1048289177Speter *fs_err = get_lock(&lock, ub->fs, path, TRUE, TRUE, pool); 1049289177Speter if (!*fs_err && !ub->break_lock) 1050289177Speter { 1051289177Speter if (strcmp(token, lock->token) != 0) 1052289177Speter *fs_err = SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, path); 1053289177Speter else if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0) 1054289177Speter *fs_err = SVN_FS__ERR_LOCK_OWNER_MISMATCH(ub->fs, 1055289177Speter ub->fs->access_ctx->username, 1056289177Speter lock->owner); 1057289177Speter } 1058289177Speter 1059289177Speter return SVN_NO_ERROR; 1060289177Speter} 1061289177Speter 1062289177Spetertypedef struct unlock_info_t { 1063289177Speter const char *path; 1064289177Speter const char *component; 1065289177Speter svn_error_t *fs_err; 1066289177Speter svn_boolean_t done; 1067289177Speter int components; 1068289177Speter} unlock_info_t; 1069289177Speter 1070289177Speter/* The body of svn_fs_x__unlock(), which see. 1071289177Speter 1072289177Speter BATON is a 'unlock_baton_t *' holding the effective arguments. 1073289177Speter BATON->targets is an array of 'svn_sort__item_t' targets, sorted by 1074289177Speter path, mapping canonical path to (const char *) token. Set 1075289177Speter BATON->infos to an array of 'unlock_info_t' results. For the other 1076289177Speter arguments, see svn_fs_unlock_many(). 1077289177Speter 1078289177Speter This implements the svn_fs_x__with_write_lock() 'body' callback 1079289177Speter type, and assumes that the write lock is held. 1080289177Speter */ 1081289177Speterstatic svn_error_t * 1082289177Speterunlock_body(void *baton, apr_pool_t *pool) 1083289177Speter{ 1084289177Speter unlock_baton_t *ub = baton; 1085289177Speter svn_fs_root_t *root; 1086289177Speter svn_revnum_t youngest; 1087289177Speter const char *rev_0_path; 1088289177Speter int i, max_components = 0, outstanding = 0; 1089289177Speter apr_pool_t *iterpool = svn_pool_create(pool); 1090289177Speter 1091289177Speter ub->infos = apr_array_make(ub->result_pool, ub->targets->nelts, 1092289177Speter sizeof( unlock_info_t)); 1093289177Speter 1094289177Speter SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool)); 1095289177Speter SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool)); 1096289177Speter 1097289177Speter for (i = 0; i < ub->targets->nelts; ++i) 1098289177Speter { 1099289177Speter const svn_sort__item_t *item = &APR_ARRAY_IDX(ub->targets, i, 1100289177Speter svn_sort__item_t); 1101289177Speter const char *token = item->value; 1102289177Speter unlock_info_t info = { 0 }; 1103289177Speter 1104289177Speter svn_pool_clear(iterpool); 1105289177Speter 1106289177Speter info.path = item->key; 1107289177Speter if (!ub->skip_check) 1108289177Speter SVN_ERR(check_unlock(&info.fs_err, info.path, token, ub, root, 1109289177Speter iterpool)); 1110289177Speter if (!info.fs_err) 1111289177Speter { 1112289177Speter const char *s; 1113289177Speter 1114289177Speter info.components = 1; 1115289177Speter info.component = info.path; 1116289177Speter while((s = strchr(info.component + 1, '/'))) 1117289177Speter { 1118289177Speter info.component = s; 1119289177Speter ++info.components; 1120289177Speter } 1121289177Speter 1122289177Speter if (info.components > max_components) 1123289177Speter max_components = info.components; 1124289177Speter 1125289177Speter ++outstanding; 1126289177Speter } 1127289177Speter APR_ARRAY_PUSH(ub->infos, unlock_info_t) = info; 1128289177Speter } 1129289177Speter 1130289177Speter rev_0_path = svn_fs_x__path_rev_absolute(ub->fs, 0, pool); 1131289177Speter 1132289177Speter for (i = max_components; i >= 0; --i) 1133289177Speter { 1134289177Speter const char *last_path = NULL; 1135289177Speter apr_array_header_t *paths; 1136289177Speter int j; 1137289177Speter 1138289177Speter svn_pool_clear(iterpool); 1139289177Speter paths = apr_array_make(pool, 1, sizeof(const char *)); 1140289177Speter 1141289177Speter for (j = 0; j < ub->infos->nelts; ++j) 1142289177Speter { 1143289177Speter unlock_info_t *info = &APR_ARRAY_IDX(ub->infos, j, unlock_info_t); 1144289177Speter 1145289177Speter if (!info->fs_err && info->path) 1146289177Speter { 1147289177Speter 1148289177Speter if (info->components == i) 1149289177Speter { 1150289177Speter SVN_ERR(delete_lock(ub->fs->path, info->path, iterpool)); 1151289177Speter info->done = TRUE; 1152289177Speter } 1153289177Speter else if (info->components > i) 1154289177Speter { 1155289177Speter apr_size_t len = info->component - info->path; 1156289177Speter 1157289177Speter if (last_path 1158289177Speter && strcmp(last_path, "/") 1159289177Speter && (strncmp(last_path, info->path, len) 1160289177Speter || strlen(last_path) != len)) 1161289177Speter { 1162289177Speter SVN_ERR(delete_from_digest(ub->fs->path, paths, last_path, 1163289177Speter rev_0_path, iterpool)); 1164289177Speter apr_array_clear(paths); 1165289177Speter last_path = NULL; 1166289177Speter } 1167289177Speter APR_ARRAY_PUSH(paths, const char *) = info->path; 1168289177Speter if (!last_path) 1169289177Speter { 1170289177Speter if (info->component > info->path) 1171289177Speter last_path = apr_pstrndup(pool, info->path, len); 1172289177Speter else 1173289177Speter last_path = "/"; 1174289177Speter } 1175289177Speter 1176289177Speter if (info->component > info->path) 1177289177Speter { 1178289177Speter --info->component; 1179289177Speter while(info->component[0] != '/') 1180289177Speter --info->component; 1181289177Speter } 1182289177Speter } 1183289177Speter } 1184289177Speter 1185289177Speter if (last_path && j == ub->infos->nelts - 1) 1186289177Speter SVN_ERR(delete_from_digest(ub->fs->path, paths, last_path, 1187289177Speter rev_0_path, iterpool)); 1188289177Speter } 1189289177Speter } 1190289177Speter 1191289177Speter return SVN_NO_ERROR; 1192289177Speter} 1193289177Speter 1194289177Speter/* Unlock the lock described by LOCK->path and LOCK->token in FS. 1195289177Speter 1196289177Speter This assumes that the write lock is held. 1197289177Speter */ 1198289177Speterstatic svn_error_t * 1199289177Speterunlock_single(svn_fs_t *fs, 1200289177Speter svn_lock_t *lock, 1201289177Speter apr_pool_t *scratch_pool) 1202289177Speter{ 1203289177Speter unlock_baton_t ub; 1204289177Speter svn_sort__item_t item; 1205289177Speter apr_array_header_t *targets = apr_array_make(scratch_pool, 1, 1206289177Speter sizeof(svn_sort__item_t)); 1207289177Speter item.key = lock->path; 1208289177Speter item.klen = strlen(item.key); 1209289177Speter item.value = (char*)lock->token; 1210289177Speter APR_ARRAY_PUSH(targets, svn_sort__item_t) = item; 1211289177Speter 1212289177Speter ub.fs = fs; 1213289177Speter ub.targets = targets; 1214289177Speter ub.skip_check = TRUE; 1215289177Speter ub.result_pool = scratch_pool; 1216289177Speter 1217289177Speter /* No ub.infos[].fs_err error because skip_check is TRUE. */ 1218289177Speter SVN_ERR(unlock_body(&ub, scratch_pool)); 1219289177Speter 1220289177Speter return SVN_NO_ERROR; 1221289177Speter} 1222289177Speter 1223289177Speter 1224289177Speter/*** Public API implementations ***/ 1225289177Speter 1226289177Spetersvn_error_t * 1227289177Spetersvn_fs_x__lock(svn_fs_t *fs, 1228289177Speter apr_hash_t *targets, 1229289177Speter const char *comment, 1230289177Speter svn_boolean_t is_dav_comment, 1231289177Speter apr_time_t expiration_date, 1232289177Speter svn_boolean_t steal_lock, 1233289177Speter svn_fs_lock_callback_t lock_callback, 1234289177Speter void *lock_baton, 1235289177Speter apr_pool_t *result_pool, 1236289177Speter apr_pool_t *scratch_pool) 1237289177Speter{ 1238289177Speter lock_baton_t lb; 1239289177Speter apr_array_header_t *sorted_targets; 1240289177Speter apr_hash_t *canonical_targets = apr_hash_make(scratch_pool); 1241289177Speter apr_hash_index_t *hi; 1242289177Speter apr_pool_t *iterpool; 1243289177Speter svn_error_t *err, *cb_err = SVN_NO_ERROR; 1244289177Speter int i; 1245289177Speter 1246289177Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1247289177Speter 1248289177Speter /* We need to have a username attached to the fs. */ 1249289177Speter if (!fs->access_ctx || !fs->access_ctx->username) 1250289177Speter return SVN_FS__ERR_NO_USER(fs); 1251289177Speter 1252289177Speter /* The FS locking API allows both canonical and non-canonical 1253289177Speter paths which means that the same canonical path could be 1254289177Speter represented more than once in the TARGETS hash. We just keep 1255289177Speter one, choosing one with a token if possible. */ 1256289177Speter for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi)) 1257289177Speter { 1258289177Speter const char *path = apr_hash_this_key(hi); 1259289177Speter const svn_fs_lock_target_t *target = apr_hash_this_val(hi); 1260289177Speter const svn_fs_lock_target_t *other; 1261289177Speter 1262289177Speter path = svn_fspath__canonicalize(path, result_pool); 1263289177Speter other = svn_hash_gets(canonical_targets, path); 1264289177Speter 1265289177Speter if (!other || (!other->token && target->token)) 1266289177Speter svn_hash_sets(canonical_targets, path, target); 1267289177Speter } 1268289177Speter 1269289177Speter sorted_targets = svn_sort__hash(canonical_targets, 1270289177Speter svn_sort_compare_items_as_paths, 1271289177Speter scratch_pool); 1272289177Speter 1273289177Speter lb.fs = fs; 1274289177Speter lb.targets = sorted_targets; 1275289177Speter lb.comment = comment; 1276289177Speter lb.is_dav_comment = is_dav_comment; 1277289177Speter lb.expiration_date = expiration_date; 1278289177Speter lb.steal_lock = steal_lock; 1279289177Speter lb.result_pool = result_pool; 1280289177Speter 1281289177Speter iterpool = svn_pool_create(scratch_pool); 1282289177Speter err = svn_fs_x__with_write_lock(fs, lock_body, &lb, iterpool); 1283289177Speter for (i = 0; i < lb.infos->nelts; ++i) 1284289177Speter { 1285289177Speter struct lock_info_t *info = &APR_ARRAY_IDX(lb.infos, i, 1286289177Speter struct lock_info_t); 1287289177Speter svn_pool_clear(iterpool); 1288289177Speter if (!cb_err && lock_callback) 1289289177Speter { 1290289177Speter if (!info->lock && !info->fs_err) 1291289177Speter info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED, 1292289177Speter 0, _("Failed to lock '%s'"), 1293289177Speter info->path); 1294289177Speter 1295289177Speter cb_err = lock_callback(lock_baton, info->path, info->lock, 1296289177Speter info->fs_err, iterpool); 1297289177Speter } 1298289177Speter svn_error_clear(info->fs_err); 1299289177Speter } 1300289177Speter svn_pool_destroy(iterpool); 1301289177Speter 1302289177Speter if (err && cb_err) 1303289177Speter svn_error_compose(err, cb_err); 1304289177Speter else if (!err) 1305289177Speter err = cb_err; 1306289177Speter 1307289177Speter return svn_error_trace(err); 1308289177Speter} 1309289177Speter 1310289177Speter 1311289177Spetersvn_error_t * 1312289177Spetersvn_fs_x__generate_lock_token(const char **token, 1313289177Speter svn_fs_t *fs, 1314289177Speter apr_pool_t *pool) 1315289177Speter{ 1316289177Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1317289177Speter 1318289177Speter /* Notice that 'fs' is currently unused. But perhaps someday, we'll 1319289177Speter want to use the fs UUID + some incremented number? For now, we 1320289177Speter generate a URI that matches the DAV RFC. We could change this to 1321289177Speter some other URI scheme someday, if we wish. */ 1322289177Speter *token = apr_pstrcat(pool, "opaquelocktoken:", 1323289177Speter svn_uuid_generate(pool), SVN_VA_NULL); 1324289177Speter return SVN_NO_ERROR; 1325289177Speter} 1326289177Speter 1327289177Spetersvn_error_t * 1328289177Spetersvn_fs_x__unlock(svn_fs_t *fs, 1329289177Speter apr_hash_t *targets, 1330289177Speter svn_boolean_t break_lock, 1331289177Speter svn_fs_lock_callback_t lock_callback, 1332289177Speter void *lock_baton, 1333289177Speter apr_pool_t *result_pool, 1334289177Speter apr_pool_t *scratch_pool) 1335289177Speter{ 1336289177Speter unlock_baton_t ub; 1337289177Speter apr_array_header_t *sorted_targets; 1338289177Speter apr_hash_t *canonical_targets = apr_hash_make(scratch_pool); 1339289177Speter apr_hash_index_t *hi; 1340289177Speter apr_pool_t *iterpool; 1341289177Speter svn_error_t *err, *cb_err = SVN_NO_ERROR; 1342289177Speter int i; 1343289177Speter 1344289177Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1345289177Speter 1346289177Speter /* We need to have a username attached to the fs. */ 1347289177Speter if (!fs->access_ctx || !fs->access_ctx->username) 1348289177Speter return SVN_FS__ERR_NO_USER(fs); 1349289177Speter 1350289177Speter for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi)) 1351289177Speter { 1352289177Speter const char *path = apr_hash_this_key(hi); 1353289177Speter const char *token = apr_hash_this_val(hi); 1354289177Speter const char *other; 1355289177Speter 1356289177Speter path = svn_fspath__canonicalize(path, result_pool); 1357289177Speter other = svn_hash_gets(canonical_targets, path); 1358289177Speter 1359289177Speter if (!other) 1360289177Speter svn_hash_sets(canonical_targets, path, token); 1361289177Speter } 1362289177Speter 1363289177Speter sorted_targets = svn_sort__hash(canonical_targets, 1364289177Speter svn_sort_compare_items_as_paths, 1365289177Speter scratch_pool); 1366289177Speter 1367289177Speter ub.fs = fs; 1368289177Speter ub.targets = sorted_targets; 1369289177Speter ub.skip_check = FALSE; 1370289177Speter ub.break_lock = break_lock; 1371289177Speter ub.result_pool = result_pool; 1372289177Speter 1373289177Speter iterpool = svn_pool_create(scratch_pool); 1374289177Speter err = svn_fs_x__with_write_lock(fs, unlock_body, &ub, iterpool); 1375289177Speter for (i = 0; i < ub.infos->nelts; ++i) 1376289177Speter { 1377289177Speter unlock_info_t *info = &APR_ARRAY_IDX(ub.infos, i, unlock_info_t); 1378289177Speter svn_pool_clear(iterpool); 1379289177Speter if (!cb_err && lock_callback) 1380289177Speter { 1381289177Speter if (!info->done && !info->fs_err) 1382289177Speter info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED, 1383289177Speter 0, _("Failed to unlock '%s'"), 1384289177Speter info->path); 1385289177Speter cb_err = lock_callback(lock_baton, info->path, NULL, info->fs_err, 1386289177Speter iterpool); 1387289177Speter } 1388289177Speter svn_error_clear(info->fs_err); 1389289177Speter } 1390289177Speter svn_pool_destroy(iterpool); 1391289177Speter 1392289177Speter if (err && cb_err) 1393289177Speter svn_error_compose(err, cb_err); 1394289177Speter else if (!err) 1395289177Speter err = cb_err; 1396289177Speter 1397289177Speter return svn_error_trace(err); 1398289177Speter} 1399289177Speter 1400289177Speter 1401289177Spetersvn_error_t * 1402289177Spetersvn_fs_x__get_lock(svn_lock_t **lock_p, 1403289177Speter svn_fs_t *fs, 1404289177Speter const char *path, 1405289177Speter apr_pool_t *pool) 1406289177Speter{ 1407289177Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1408289177Speter path = svn_fs__canonicalize_abspath(path, pool); 1409289177Speter return get_lock_helper(fs, lock_p, path, FALSE, pool); 1410289177Speter} 1411289177Speter 1412289177Speter 1413289177Speter/* Baton for get_locks_filter_func(). */ 1414289177Spetertypedef struct get_locks_filter_baton_t 1415289177Speter{ 1416289177Speter const char *path; 1417289177Speter svn_depth_t requested_depth; 1418289177Speter svn_fs_get_locks_callback_t get_locks_func; 1419289177Speter void *get_locks_baton; 1420289177Speter 1421289177Speter} get_locks_filter_baton_t; 1422289177Speter 1423289177Speter 1424289177Speter/* A wrapper for the GET_LOCKS_FUNC passed to svn_fs_x__get_locks() 1425289177Speter which filters out locks on paths that aren't within 1426289177Speter BATON->requested_depth of BATON->path before called 1427289177Speter BATON->get_locks_func() with BATON->get_locks_baton. 1428289177Speter 1429289177Speter NOTE: See issue #3660 for details about how the FSX lock 1430289177Speter management code is inconsistent. Until that inconsistency is 1431289177Speter resolved, we take this filtering approach rather than honoring 1432289177Speter depth requests closer to the crawling code. In other words, once 1433289177Speter we decide how to resolve issue #3660, there might be a more 1434289177Speter performant way to honor the depth passed to svn_fs_x__get_locks(). */ 1435289177Speterstatic svn_error_t * 1436289177Speterget_locks_filter_func(void *baton, 1437289177Speter svn_lock_t *lock, 1438289177Speter apr_pool_t *pool) 1439289177Speter{ 1440289177Speter get_locks_filter_baton_t *b = baton; 1441289177Speter 1442289177Speter /* Filter out unwanted paths. Since Subversion only allows 1443289177Speter locks on files, we can treat depth=immediates the same as 1444289177Speter depth=files for filtering purposes. Meaning, we'll keep 1445289177Speter this lock if: 1446289177Speter 1447289177Speter a) its path is the very path we queried, or 1448289177Speter b) we've asked for a fully recursive answer, or 1449289177Speter c) we've asked for depth=files or depth=immediates, and this 1450289177Speter lock is on an immediate child of our query path. 1451289177Speter */ 1452289177Speter if ((strcmp(b->path, lock->path) == 0) 1453289177Speter || (b->requested_depth == svn_depth_infinity)) 1454289177Speter { 1455289177Speter SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool)); 1456289177Speter } 1457289177Speter else if ((b->requested_depth == svn_depth_files) || 1458289177Speter (b->requested_depth == svn_depth_immediates)) 1459289177Speter { 1460289177Speter const char *rel_uri = svn_fspath__skip_ancestor(b->path, lock->path); 1461289177Speter if (rel_uri && (svn_path_component_count(rel_uri) == 1)) 1462289177Speter SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool)); 1463289177Speter } 1464289177Speter 1465289177Speter return SVN_NO_ERROR; 1466289177Speter} 1467289177Speter 1468289177Spetersvn_error_t * 1469289177Spetersvn_fs_x__get_locks(svn_fs_t *fs, 1470289177Speter const char *path, 1471289177Speter svn_depth_t depth, 1472289177Speter svn_fs_get_locks_callback_t get_locks_func, 1473289177Speter void *get_locks_baton, 1474289177Speter apr_pool_t *scratch_pool) 1475289177Speter{ 1476289177Speter const char *digest_path; 1477289177Speter get_locks_filter_baton_t glfb; 1478289177Speter 1479289177Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1480289177Speter path = svn_fs__canonicalize_abspath(path, scratch_pool); 1481289177Speter 1482289177Speter glfb.path = path; 1483289177Speter glfb.requested_depth = depth; 1484289177Speter glfb.get_locks_func = get_locks_func; 1485289177Speter glfb.get_locks_baton = get_locks_baton; 1486289177Speter 1487289177Speter /* Get the top digest path in our tree of interest, and then walk it. */ 1488289177Speter SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, scratch_pool)); 1489289177Speter SVN_ERR(walk_locks(fs, digest_path, get_locks_filter_func, &glfb, 1490289177Speter FALSE, scratch_pool)); 1491289177Speter return SVN_NO_ERROR; 1492289177Speter} 1493