1251881Speter/* lock.c : functions for manipulating filesystem locks. 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 "svn_pools.h" 24251881Speter#include "svn_error.h" 25251881Speter#include "svn_dirent_uri.h" 26251881Speter#include "svn_path.h" 27251881Speter#include "svn_fs.h" 28251881Speter#include "svn_hash.h" 29251881Speter#include "svn_time.h" 30251881Speter#include "svn_utf.h" 31251881Speter 32251881Speter#include <apr_uuid.h> 33251881Speter#include <apr_file_io.h> 34251881Speter#include <apr_file_info.h> 35251881Speter 36251881Speter#include "lock.h" 37251881Speter#include "tree.h" 38251881Speter#include "fs_fs.h" 39299742Sdim#include "util.h" 40251881Speter#include "../libsvn_fs/fs-loader.h" 41251881Speter 42251881Speter#include "private/svn_fs_util.h" 43251881Speter#include "private/svn_fspath.h" 44299742Sdim#include "private/svn_sorts_private.h" 45251881Speter#include "svn_private_config.h" 46251881Speter 47251881Speter/* Names of hash keys used to store a lock for writing to disk. */ 48251881Speter#define PATH_KEY "path" 49251881Speter#define TOKEN_KEY "token" 50251881Speter#define OWNER_KEY "owner" 51251881Speter#define CREATION_DATE_KEY "creation_date" 52251881Speter#define EXPIRATION_DATE_KEY "expiration_date" 53251881Speter#define COMMENT_KEY "comment" 54251881Speter#define IS_DAV_COMMENT_KEY "is_dav_comment" 55251881Speter#define CHILDREN_KEY "children" 56251881Speter 57251881Speter/* Number of characters from the head of a digest file name used to 58251881Speter calculate a subdirectory in which to drop that file. */ 59251881Speter#define DIGEST_SUBDIR_LEN 3 60251881Speter 61251881Speter 62251881Speter 63251881Speter/*** Generic helper functions. ***/ 64251881Speter 65251881Speter/* Set *DIGEST to the MD5 hash of STR. */ 66251881Speterstatic svn_error_t * 67251881Spetermake_digest(const char **digest, 68251881Speter const char *str, 69251881Speter apr_pool_t *pool) 70251881Speter{ 71251881Speter svn_checksum_t *checksum; 72251881Speter 73251881Speter SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, str, strlen(str), pool)); 74251881Speter 75251881Speter *digest = svn_checksum_to_cstring_display(checksum, pool); 76251881Speter return SVN_NO_ERROR; 77251881Speter} 78251881Speter 79251881Speter 80251881Speter/* Set the value of KEY (whose size is KEY_LEN, or APR_HASH_KEY_STRING 81251881Speter if unknown) to an svn_string_t-ized version of VALUE (whose size is 82251881Speter VALUE_LEN, or APR_HASH_KEY_STRING if unknown) in HASH. The value 83251881Speter will be allocated in POOL; KEY will not be duped. If either KEY or VALUE 84251881Speter is NULL, this function will do nothing. */ 85251881Speterstatic void 86251881Speterhash_store(apr_hash_t *hash, 87251881Speter const char *key, 88251881Speter apr_ssize_t key_len, 89251881Speter const char *value, 90251881Speter apr_ssize_t value_len, 91251881Speter apr_pool_t *pool) 92251881Speter{ 93251881Speter if (! (key && value)) 94251881Speter return; 95251881Speter if (value_len == APR_HASH_KEY_STRING) 96251881Speter value_len = strlen(value); 97251881Speter apr_hash_set(hash, key, key_len, 98251881Speter svn_string_ncreate(value, value_len, pool)); 99251881Speter} 100251881Speter 101251881Speter 102251881Speter/* Fetch the value of KEY from HASH, returning only the cstring data 103251881Speter of that value (if it exists). */ 104251881Speterstatic const char * 105251881Speterhash_fetch(apr_hash_t *hash, 106299742Sdim const char *key) 107251881Speter{ 108251881Speter svn_string_t *str = svn_hash_gets(hash, key); 109251881Speter return str ? str->data : NULL; 110251881Speter} 111251881Speter 112251881Speter 113251881Speter/* SVN_ERR_FS_CORRUPT: the lockfile for PATH in FS is corrupt. */ 114251881Speterstatic svn_error_t * 115251881Spetererr_corrupt_lockfile(const char *fs_path, const char *path) 116251881Speter{ 117251881Speter return 118251881Speter svn_error_createf( 119251881Speter SVN_ERR_FS_CORRUPT, 0, 120251881Speter _("Corrupt lockfile for path '%s' in filesystem '%s'"), 121251881Speter path, fs_path); 122251881Speter} 123251881Speter 124251881Speter 125251881Speter/*** Digest file handling functions. ***/ 126251881Speter 127251881Speter/* Return the path of the lock/entries file for which DIGEST is the 128251881Speter hashed repository relative path. */ 129251881Speterstatic const char * 130251881Speterdigest_path_from_digest(const char *fs_path, 131251881Speter const char *digest, 132251881Speter apr_pool_t *pool) 133251881Speter{ 134251881Speter return svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR, 135251881Speter apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN), 136299742Sdim digest, SVN_VA_NULL); 137251881Speter} 138251881Speter 139251881Speter 140251881Speter/* Set *DIGEST_PATH to the path to the lock/entries digest file associate 141251881Speter with PATH, where PATH is the path to the lock file or lock entries file 142251881Speter in FS. */ 143251881Speterstatic svn_error_t * 144251881Speterdigest_path_from_path(const char **digest_path, 145251881Speter const char *fs_path, 146251881Speter const char *path, 147251881Speter apr_pool_t *pool) 148251881Speter{ 149251881Speter const char *digest; 150251881Speter SVN_ERR(make_digest(&digest, path, pool)); 151251881Speter *digest_path = svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR, 152251881Speter apr_pstrmemdup(pool, digest, 153251881Speter DIGEST_SUBDIR_LEN), 154299742Sdim digest, SVN_VA_NULL); 155251881Speter return SVN_NO_ERROR; 156251881Speter} 157251881Speter 158251881Speter 159251881Speter/* Write to DIGEST_PATH a representation of CHILDREN (which may be 160251881Speter empty, if the versioned path in FS represented by DIGEST_PATH has 161251881Speter no children) and LOCK (which may be NULL if that versioned path is 162251881Speter lock itself locked). Set the permissions of DIGEST_PATH to those of 163251881Speter PERMS_REFERENCE. Use POOL for all allocations. 164251881Speter */ 165251881Speterstatic svn_error_t * 166251881Speterwrite_digest_file(apr_hash_t *children, 167251881Speter svn_lock_t *lock, 168251881Speter const char *fs_path, 169251881Speter const char *digest_path, 170251881Speter const char *perms_reference, 171251881Speter apr_pool_t *pool) 172251881Speter{ 173251881Speter svn_error_t *err = SVN_NO_ERROR; 174251881Speter svn_stream_t *stream; 175251881Speter apr_hash_index_t *hi; 176251881Speter apr_hash_t *hash = apr_hash_make(pool); 177251881Speter const char *tmp_path; 178251881Speter 179251881Speter SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs_path, PATH_LOCKS_DIR, 180251881Speter pool), fs_path, pool)); 181251881Speter SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_dirname(digest_path, pool), 182251881Speter fs_path, pool)); 183251881Speter 184251881Speter if (lock) 185251881Speter { 186251881Speter const char *creation_date = NULL, *expiration_date = NULL; 187251881Speter if (lock->creation_date) 188251881Speter creation_date = svn_time_to_cstring(lock->creation_date, pool); 189251881Speter if (lock->expiration_date) 190251881Speter expiration_date = svn_time_to_cstring(lock->expiration_date, pool); 191251881Speter hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1, 192251881Speter lock->path, APR_HASH_KEY_STRING, pool); 193251881Speter hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1, 194251881Speter lock->token, APR_HASH_KEY_STRING, pool); 195251881Speter hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1, 196251881Speter lock->owner, APR_HASH_KEY_STRING, pool); 197251881Speter hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1, 198251881Speter lock->comment, APR_HASH_KEY_STRING, pool); 199251881Speter hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1, 200251881Speter lock->is_dav_comment ? "1" : "0", 1, pool); 201251881Speter hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1, 202251881Speter creation_date, APR_HASH_KEY_STRING, pool); 203251881Speter hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1, 204251881Speter expiration_date, APR_HASH_KEY_STRING, pool); 205251881Speter } 206251881Speter if (apr_hash_count(children)) 207251881Speter { 208251881Speter svn_stringbuf_t *children_list = svn_stringbuf_create_empty(pool); 209251881Speter for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi)) 210251881Speter { 211251881Speter svn_stringbuf_appendbytes(children_list, 212299742Sdim apr_hash_this_key(hi), 213299742Sdim apr_hash_this_key_len(hi)); 214251881Speter svn_stringbuf_appendbyte(children_list, '\n'); 215251881Speter } 216251881Speter hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1, 217251881Speter children_list->data, children_list->len, pool); 218251881Speter } 219251881Speter 220251881Speter SVN_ERR(svn_stream_open_unique(&stream, &tmp_path, 221251881Speter svn_dirent_dirname(digest_path, pool), 222251881Speter svn_io_file_del_none, pool, pool)); 223251881Speter if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool))) 224251881Speter { 225251881Speter svn_error_clear(svn_stream_close(stream)); 226251881Speter return svn_error_createf(err->apr_err, 227251881Speter err, 228251881Speter _("Cannot write lock/entries hashfile '%s'"), 229251881Speter svn_dirent_local_style(tmp_path, pool)); 230251881Speter } 231251881Speter 232251881Speter SVN_ERR(svn_stream_close(stream)); 233251881Speter SVN_ERR(svn_io_file_rename(tmp_path, digest_path, pool)); 234251881Speter SVN_ERR(svn_io_copy_perms(perms_reference, digest_path, pool)); 235251881Speter return SVN_NO_ERROR; 236251881Speter} 237251881Speter 238251881Speter 239251881Speter/* Parse the file at DIGEST_PATH, populating the lock LOCK_P in that 240251881Speter file (if it exists, and if *LOCK_P is non-NULL) and the hash of 241251881Speter CHILDREN_P (if any exist, and if *CHILDREN_P is non-NULL). Use POOL 242251881Speter for all allocations. */ 243251881Speterstatic svn_error_t * 244251881Speterread_digest_file(apr_hash_t **children_p, 245251881Speter svn_lock_t **lock_p, 246251881Speter const char *fs_path, 247251881Speter const char *digest_path, 248251881Speter apr_pool_t *pool) 249251881Speter{ 250251881Speter svn_error_t *err = SVN_NO_ERROR; 251251881Speter svn_lock_t *lock; 252251881Speter apr_hash_t *hash; 253251881Speter svn_stream_t *stream; 254251881Speter const char *val; 255299742Sdim svn_node_kind_t kind; 256251881Speter 257251881Speter if (lock_p) 258251881Speter *lock_p = NULL; 259251881Speter if (children_p) 260251881Speter *children_p = apr_hash_make(pool); 261251881Speter 262299742Sdim SVN_ERR(svn_io_check_path(digest_path, &kind, pool)); 263299742Sdim if (kind == svn_node_none) 264299742Sdim return SVN_NO_ERROR; 265251881Speter 266251881Speter /* If our caller doesn't care about anything but the presence of the 267251881Speter file... whatever. */ 268299742Sdim if (kind == svn_node_file && !lock_p && !children_p) 269299742Sdim return SVN_NO_ERROR; 270251881Speter 271299742Sdim SVN_ERR(svn_stream_open_readonly(&stream, digest_path, pool, pool)); 272299742Sdim 273251881Speter hash = apr_hash_make(pool); 274251881Speter if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool))) 275251881Speter { 276251881Speter svn_error_clear(svn_stream_close(stream)); 277251881Speter return svn_error_createf(err->apr_err, 278251881Speter err, 279251881Speter _("Can't parse lock/entries hashfile '%s'"), 280251881Speter svn_dirent_local_style(digest_path, pool)); 281251881Speter } 282251881Speter SVN_ERR(svn_stream_close(stream)); 283251881Speter 284251881Speter /* If our caller cares, see if we have a lock path in our hash. If 285251881Speter so, we'll assume we have a lock here. */ 286299742Sdim val = hash_fetch(hash, PATH_KEY); 287251881Speter if (val && lock_p) 288251881Speter { 289251881Speter const char *path = val; 290251881Speter 291251881Speter /* Create our lock and load it up. */ 292251881Speter lock = svn_lock_create(pool); 293251881Speter lock->path = path; 294251881Speter 295299742Sdim if (! ((lock->token = hash_fetch(hash, TOKEN_KEY)))) 296251881Speter return svn_error_trace(err_corrupt_lockfile(fs_path, path)); 297251881Speter 298299742Sdim if (! ((lock->owner = hash_fetch(hash, OWNER_KEY)))) 299251881Speter return svn_error_trace(err_corrupt_lockfile(fs_path, path)); 300251881Speter 301299742Sdim if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY)))) 302251881Speter return svn_error_trace(err_corrupt_lockfile(fs_path, path)); 303251881Speter lock->is_dav_comment = (val[0] == '1'); 304251881Speter 305299742Sdim if (! ((val = hash_fetch(hash, CREATION_DATE_KEY)))) 306251881Speter return svn_error_trace(err_corrupt_lockfile(fs_path, path)); 307251881Speter SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool)); 308251881Speter 309299742Sdim if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY))) 310251881Speter SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool)); 311251881Speter 312299742Sdim lock->comment = hash_fetch(hash, COMMENT_KEY); 313251881Speter 314251881Speter *lock_p = lock; 315251881Speter } 316251881Speter 317251881Speter /* If our caller cares, see if we have any children for this path. */ 318299742Sdim val = hash_fetch(hash, CHILDREN_KEY); 319251881Speter if (val && children_p) 320251881Speter { 321251881Speter apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool); 322251881Speter int i; 323251881Speter 324251881Speter for (i = 0; i < kiddos->nelts; i++) 325251881Speter { 326251881Speter svn_hash_sets(*children_p, APR_ARRAY_IDX(kiddos, i, const char *), 327251881Speter (void *)1); 328251881Speter } 329251881Speter } 330251881Speter return SVN_NO_ERROR; 331251881Speter} 332251881Speter 333251881Speter 334251881Speter 335251881Speter/*** Lock helper functions (path here are still FS paths, not on-disk 336251881Speter schema-supporting paths) ***/ 337251881Speter 338251881Speter 339251881Speter/* Write LOCK in FS to the actual OS filesystem. 340251881Speter 341251881Speter Use PERMS_REFERENCE for the permissions of any digest files. 342251881Speter */ 343251881Speterstatic svn_error_t * 344251881Speterset_lock(const char *fs_path, 345251881Speter svn_lock_t *lock, 346251881Speter const char *perms_reference, 347251881Speter apr_pool_t *pool) 348251881Speter{ 349299742Sdim const char *digest_path; 350299742Sdim apr_hash_t *children; 351251881Speter 352299742Sdim SVN_ERR(digest_path_from_path(&digest_path, fs_path, lock->path, pool)); 353251881Speter 354299742Sdim /* We could get away without reading the file as children should 355299742Sdim always come back empty. */ 356299742Sdim SVN_ERR(read_digest_file(&children, NULL, fs_path, digest_path, pool)); 357251881Speter 358299742Sdim SVN_ERR(write_digest_file(children, lock, fs_path, digest_path, 359299742Sdim perms_reference, pool)); 360251881Speter 361299742Sdim return SVN_NO_ERROR; 362299742Sdim} 363251881Speter 364299742Sdimstatic svn_error_t * 365299742Sdimdelete_lock(const char *fs_path, 366299742Sdim const char *path, 367299742Sdim apr_pool_t *pool) 368299742Sdim{ 369299742Sdim const char *digest_path; 370251881Speter 371299742Sdim SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool)); 372251881Speter 373299742Sdim SVN_ERR(svn_io_remove_file2(digest_path, TRUE, pool)); 374251881Speter 375251881Speter return SVN_NO_ERROR; 376251881Speter} 377251881Speter 378251881Speterstatic svn_error_t * 379299742Sdimadd_to_digest(const char *fs_path, 380299742Sdim apr_array_header_t *paths, 381299742Sdim const char *index_path, 382299742Sdim const char *perms_reference, 383299742Sdim apr_pool_t *pool) 384251881Speter{ 385299742Sdim const char *index_digest_path; 386299742Sdim apr_hash_t *children; 387299742Sdim svn_lock_t *lock; 388299742Sdim int i; 389299742Sdim unsigned int original_count; 390251881Speter 391299742Sdim SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool)); 392251881Speter 393299742Sdim SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool)); 394299742Sdim 395299742Sdim original_count = apr_hash_count(children); 396299742Sdim 397299742Sdim for (i = 0; i < paths->nelts; ++i) 398251881Speter { 399299742Sdim const char *path = APR_ARRAY_IDX(paths, i, const char *); 400251881Speter const char *digest_path, *digest_file; 401251881Speter 402299742Sdim SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool)); 403299742Sdim digest_file = svn_dirent_basename(digest_path, NULL); 404299742Sdim svn_hash_sets(children, digest_file, (void *)1); 405299742Sdim } 406251881Speter 407299742Sdim if (apr_hash_count(children) != original_count) 408299742Sdim SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path, 409299742Sdim perms_reference, pool)); 410251881Speter 411299742Sdim return SVN_NO_ERROR; 412299742Sdim} 413251881Speter 414299742Sdimstatic svn_error_t * 415299742Sdimdelete_from_digest(const char *fs_path, 416299742Sdim apr_array_header_t *paths, 417299742Sdim const char *index_path, 418299742Sdim const char *perms_reference, 419299742Sdim apr_pool_t *pool) 420299742Sdim{ 421299742Sdim const char *index_digest_path; 422299742Sdim apr_hash_t *children; 423299742Sdim svn_lock_t *lock; 424299742Sdim int i; 425251881Speter 426299742Sdim SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool)); 427251881Speter 428299742Sdim SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool)); 429251881Speter 430299742Sdim for (i = 0; i < paths->nelts; ++i) 431299742Sdim { 432299742Sdim const char *path = APR_ARRAY_IDX(paths, i, const char *); 433299742Sdim const char *digest_path, *digest_file; 434299742Sdim 435299742Sdim SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool)); 436299742Sdim digest_file = svn_dirent_basename(digest_path, NULL); 437299742Sdim svn_hash_sets(children, digest_file, NULL); 438251881Speter } 439251881Speter 440299742Sdim if (apr_hash_count(children) || lock) 441299742Sdim SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path, 442299742Sdim perms_reference, pool)); 443299742Sdim else 444299742Sdim SVN_ERR(svn_io_remove_file2(index_digest_path, TRUE, pool)); 445299742Sdim 446251881Speter return SVN_NO_ERROR; 447251881Speter} 448251881Speter 449299742Sdimstatic svn_error_t * 450299742Sdimunlock_single(svn_fs_t *fs, 451299742Sdim svn_lock_t *lock, 452299742Sdim apr_pool_t *pool); 453299742Sdim 454299742Sdim/* Check if LOCK has been already expired. */ 455299742Sdimstatic svn_boolean_t lock_expired(const svn_lock_t *lock) 456299742Sdim{ 457299742Sdim return lock->expiration_date && (apr_time_now() > lock->expiration_date); 458299742Sdim} 459299742Sdim 460251881Speter/* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be 461251881Speter TRUE if the caller (or one of its callers) has taken out the 462251881Speter repository-wide write lock, FALSE otherwise. If MUST_EXIST is 463251881Speter not set, the function will simply return NULL in *LOCK_P instead 464251881Speter of creating an SVN_FS__ERR_NO_SUCH_LOCK error in case the lock 465251881Speter was not found (much faster). Use POOL for allocations. */ 466251881Speterstatic svn_error_t * 467251881Speterget_lock(svn_lock_t **lock_p, 468251881Speter svn_fs_t *fs, 469251881Speter const char *path, 470251881Speter svn_boolean_t have_write_lock, 471251881Speter svn_boolean_t must_exist, 472251881Speter apr_pool_t *pool) 473251881Speter{ 474251881Speter svn_lock_t *lock = NULL; 475251881Speter const char *digest_path; 476251881Speter svn_node_kind_t kind; 477251881Speter 478251881Speter SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool)); 479251881Speter SVN_ERR(svn_io_check_path(digest_path, &kind, pool)); 480251881Speter 481251881Speter *lock_p = NULL; 482251881Speter if (kind != svn_node_none) 483251881Speter SVN_ERR(read_digest_file(NULL, &lock, fs->path, digest_path, pool)); 484251881Speter 485251881Speter if (! lock) 486251881Speter return must_exist ? SVN_FS__ERR_NO_SUCH_LOCK(fs, path) : SVN_NO_ERROR; 487251881Speter 488251881Speter /* Don't return an expired lock. */ 489299742Sdim if (lock_expired(lock)) 490251881Speter { 491251881Speter /* Only remove the lock if we have the write lock. 492251881Speter Read operations shouldn't change the filesystem. */ 493251881Speter if (have_write_lock) 494299742Sdim SVN_ERR(unlock_single(fs, lock, pool)); 495251881Speter return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token); 496251881Speter } 497251881Speter 498251881Speter *lock_p = lock; 499251881Speter return SVN_NO_ERROR; 500251881Speter} 501251881Speter 502251881Speter 503251881Speter/* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be 504251881Speter TRUE if the caller (or one of its callers) has taken out the 505251881Speter repository-wide write lock, FALSE otherwise. Use POOL for 506251881Speter allocations. */ 507251881Speterstatic svn_error_t * 508251881Speterget_lock_helper(svn_fs_t *fs, 509251881Speter svn_lock_t **lock_p, 510251881Speter const char *path, 511251881Speter svn_boolean_t have_write_lock, 512251881Speter apr_pool_t *pool) 513251881Speter{ 514251881Speter svn_lock_t *lock; 515251881Speter svn_error_t *err; 516251881Speter 517251881Speter err = get_lock(&lock, fs, path, have_write_lock, FALSE, pool); 518251881Speter 519251881Speter /* We've deliberately decided that this function doesn't tell the 520251881Speter caller *why* the lock is unavailable. */ 521251881Speter if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK) 522251881Speter || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED))) 523251881Speter { 524251881Speter svn_error_clear(err); 525251881Speter *lock_p = NULL; 526251881Speter return SVN_NO_ERROR; 527251881Speter } 528251881Speter else 529251881Speter SVN_ERR(err); 530251881Speter 531251881Speter *lock_p = lock; 532251881Speter return SVN_NO_ERROR; 533251881Speter} 534251881Speter 535251881Speter 536299742Sdim/* A function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for 537299742Sdim all locks in and under PATH in FS. 538251881Speter HAVE_WRITE_LOCK should be true if the caller (directly or indirectly) 539251881Speter has the FS write lock. */ 540251881Speterstatic svn_error_t * 541299742Sdimwalk_locks(svn_fs_t *fs, 542299742Sdim const char *digest_path, 543299742Sdim svn_fs_get_locks_callback_t get_locks_func, 544299742Sdim void *get_locks_baton, 545299742Sdim svn_boolean_t have_write_lock, 546299742Sdim apr_pool_t *pool) 547251881Speter{ 548251881Speter apr_hash_index_t *hi; 549251881Speter apr_hash_t *children; 550251881Speter apr_pool_t *subpool; 551251881Speter svn_lock_t *lock; 552251881Speter 553251881Speter /* First, send up any locks in the current digest file. */ 554299742Sdim SVN_ERR(read_digest_file(&children, &lock, fs->path, digest_path, pool)); 555251881Speter 556299742Sdim if (lock && lock_expired(lock)) 557299742Sdim { 558299742Sdim /* Only remove the lock if we have the write lock. 559299742Sdim Read operations shouldn't change the filesystem. */ 560299742Sdim if (have_write_lock) 561299742Sdim SVN_ERR(unlock_single(fs, lock, pool)); 562299742Sdim } 563299742Sdim else if (lock) 564299742Sdim { 565299742Sdim SVN_ERR(get_locks_func(get_locks_baton, lock, pool)); 566299742Sdim } 567251881Speter 568299742Sdim /* Now, report all the child entries (if any; bail otherwise). */ 569251881Speter if (! apr_hash_count(children)) 570251881Speter return SVN_NO_ERROR; 571251881Speter subpool = svn_pool_create(pool); 572251881Speter for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi)) 573251881Speter { 574299742Sdim const char *digest = apr_hash_this_key(hi); 575251881Speter svn_pool_clear(subpool); 576299742Sdim 577299742Sdim SVN_ERR(read_digest_file 578299742Sdim (NULL, &lock, fs->path, 579299742Sdim digest_path_from_digest(fs->path, digest, subpool), subpool)); 580299742Sdim 581299742Sdim if (lock && lock_expired(lock)) 582299742Sdim { 583299742Sdim /* Only remove the lock if we have the write lock. 584299742Sdim Read operations shouldn't change the filesystem. */ 585299742Sdim if (have_write_lock) 586299742Sdim SVN_ERR(unlock_single(fs, lock, pool)); 587299742Sdim } 588299742Sdim else if (lock) 589299742Sdim { 590299742Sdim SVN_ERR(get_locks_func(get_locks_baton, lock, pool)); 591299742Sdim } 592251881Speter } 593251881Speter svn_pool_destroy(subpool); 594251881Speter return SVN_NO_ERROR; 595251881Speter} 596251881Speter 597251881Speter 598251881Speter/* Utility function: verify that a lock can be used. Interesting 599251881Speter errors returned from this function: 600251881Speter 601251881Speter SVN_ERR_FS_NO_USER: No username attached to FS. 602251881Speter SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner. 603251881Speter SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK. 604251881Speter */ 605251881Speterstatic svn_error_t * 606251881Speterverify_lock(svn_fs_t *fs, 607251881Speter svn_lock_t *lock, 608251881Speter apr_pool_t *pool) 609251881Speter{ 610251881Speter if ((! fs->access_ctx) || (! fs->access_ctx->username)) 611251881Speter return svn_error_createf 612251881Speter (SVN_ERR_FS_NO_USER, NULL, 613251881Speter _("Cannot verify lock on path '%s'; no username available"), 614251881Speter lock->path); 615251881Speter 616251881Speter else if (strcmp(fs->access_ctx->username, lock->owner) != 0) 617251881Speter return svn_error_createf 618251881Speter (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL, 619251881Speter _("User '%s' does not own lock on path '%s' (currently locked by '%s')"), 620251881Speter fs->access_ctx->username, lock->path, lock->owner); 621251881Speter 622251881Speter else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL) 623251881Speter return svn_error_createf 624251881Speter (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL, 625251881Speter _("Cannot verify lock on path '%s'; no matching lock-token available"), 626251881Speter lock->path); 627251881Speter 628251881Speter return SVN_NO_ERROR; 629251881Speter} 630251881Speter 631251881Speter 632251881Speter/* This implements the svn_fs_get_locks_callback_t interface, where 633251881Speter BATON is just an svn_fs_t object. */ 634251881Speterstatic svn_error_t * 635251881Speterget_locks_callback(void *baton, 636251881Speter svn_lock_t *lock, 637251881Speter apr_pool_t *pool) 638251881Speter{ 639251881Speter return verify_lock(baton, lock, pool); 640251881Speter} 641251881Speter 642251881Speter 643251881Speter/* The main routine for lock enforcement, used throughout libsvn_fs_fs. */ 644251881Spetersvn_error_t * 645251881Spetersvn_fs_fs__allow_locked_operation(const char *path, 646251881Speter svn_fs_t *fs, 647251881Speter svn_boolean_t recurse, 648251881Speter svn_boolean_t have_write_lock, 649251881Speter apr_pool_t *pool) 650251881Speter{ 651251881Speter path = svn_fs__canonicalize_abspath(path, pool); 652251881Speter if (recurse) 653251881Speter { 654251881Speter /* Discover all locks at or below the path. */ 655251881Speter const char *digest_path; 656251881Speter SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool)); 657251881Speter SVN_ERR(walk_locks(fs, digest_path, get_locks_callback, 658251881Speter fs, have_write_lock, pool)); 659251881Speter } 660251881Speter else 661251881Speter { 662251881Speter /* Discover and verify any lock attached to the path. */ 663251881Speter svn_lock_t *lock; 664251881Speter SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock, pool)); 665251881Speter if (lock) 666251881Speter SVN_ERR(verify_lock(fs, lock, pool)); 667251881Speter } 668251881Speter return SVN_NO_ERROR; 669251881Speter} 670251881Speter 671299742Sdim/* Helper function called from the lock and unlock code. 672299742Sdim UPDATES is a map from "const char *" parent paths to "apr_array_header_t *" 673299742Sdim arrays of child paths. For all of the parent paths of PATH this function 674299742Sdim adds PATH to the corresponding array of child paths. */ 675299742Sdimstatic void 676299742Sdimschedule_index_update(apr_hash_t *updates, 677299742Sdim const char *path, 678299742Sdim apr_pool_t *scratch_pool) 679299742Sdim{ 680299742Sdim apr_pool_t *hashpool = apr_hash_pool_get(updates); 681299742Sdim const char *parent_path = path; 682299742Sdim 683299742Sdim while (! svn_fspath__is_root(parent_path, strlen(parent_path))) 684299742Sdim { 685299742Sdim apr_array_header_t *children; 686299742Sdim 687299742Sdim parent_path = svn_fspath__dirname(parent_path, scratch_pool); 688299742Sdim children = svn_hash_gets(updates, parent_path); 689299742Sdim 690299742Sdim if (! children) 691299742Sdim { 692299742Sdim children = apr_array_make(hashpool, 8, sizeof(const char *)); 693299742Sdim svn_hash_sets(updates, apr_pstrdup(hashpool, parent_path), children); 694299742Sdim } 695299742Sdim 696299742Sdim APR_ARRAY_PUSH(children, const char *) = path; 697299742Sdim } 698299742Sdim} 699299742Sdim 700299742Sdim/* The effective arguments for lock_body() below. */ 701251881Speterstruct lock_baton { 702251881Speter svn_fs_t *fs; 703299742Sdim apr_array_header_t *targets; 704299742Sdim apr_array_header_t *infos; 705251881Speter const char *comment; 706251881Speter svn_boolean_t is_dav_comment; 707251881Speter apr_time_t expiration_date; 708251881Speter svn_boolean_t steal_lock; 709299742Sdim apr_pool_t *result_pool; 710251881Speter}; 711251881Speter 712251881Speterstatic svn_error_t * 713299742Sdimcheck_lock(svn_error_t **fs_err, 714299742Sdim const char *path, 715299742Sdim const svn_fs_lock_target_t *target, 716299742Sdim struct lock_baton *lb, 717299742Sdim svn_fs_root_t *root, 718299742Sdim svn_revnum_t youngest_rev, 719299742Sdim apr_pool_t *pool) 720251881Speter{ 721251881Speter svn_node_kind_t kind; 722251881Speter svn_lock_t *existing_lock; 723251881Speter 724299742Sdim *fs_err = SVN_NO_ERROR; 725299742Sdim 726299742Sdim SVN_ERR(svn_fs_fs__check_path(&kind, root, path, pool)); 727251881Speter if (kind == svn_node_dir) 728299742Sdim { 729299742Sdim *fs_err = SVN_FS__ERR_NOT_FILE(lb->fs, path); 730299742Sdim return SVN_NO_ERROR; 731299742Sdim } 732251881Speter 733251881Speter /* While our locking implementation easily supports the locking of 734251881Speter nonexistent paths, we deliberately choose not to allow such madness. */ 735251881Speter if (kind == svn_node_none) 736251881Speter { 737299742Sdim if (SVN_IS_VALID_REVNUM(target->current_rev)) 738299742Sdim *fs_err = svn_error_createf( 739251881Speter SVN_ERR_FS_OUT_OF_DATE, NULL, 740251881Speter _("Path '%s' doesn't exist in HEAD revision"), 741299742Sdim path); 742251881Speter else 743299742Sdim *fs_err = svn_error_createf( 744251881Speter SVN_ERR_FS_NOT_FOUND, NULL, 745251881Speter _("Path '%s' doesn't exist in HEAD revision"), 746299742Sdim path); 747299742Sdim 748299742Sdim return SVN_NO_ERROR; 749251881Speter } 750251881Speter 751251881Speter /* Is the caller attempting to lock an out-of-date working file? */ 752299742Sdim if (SVN_IS_VALID_REVNUM(target->current_rev)) 753251881Speter { 754251881Speter svn_revnum_t created_rev; 755299742Sdim 756299742Sdim if (target->current_rev > youngest_rev) 757299742Sdim { 758299742Sdim *fs_err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 759299742Sdim _("No such revision %ld"), 760299742Sdim target->current_rev); 761299742Sdim return SVN_NO_ERROR; 762299742Sdim } 763299742Sdim 764299742Sdim SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, path, 765251881Speter pool)); 766251881Speter 767251881Speter /* SVN_INVALID_REVNUM means the path doesn't exist. So 768251881Speter apparently somebody is trying to lock something in their 769251881Speter working copy, but somebody else has deleted the thing 770251881Speter from HEAD. That counts as being 'out of date'. */ 771251881Speter if (! SVN_IS_VALID_REVNUM(created_rev)) 772299742Sdim { 773299742Sdim *fs_err = svn_error_createf 774299742Sdim (SVN_ERR_FS_OUT_OF_DATE, NULL, 775299742Sdim _("Path '%s' doesn't exist in HEAD revision"), path); 776251881Speter 777299742Sdim return SVN_NO_ERROR; 778299742Sdim } 779299742Sdim 780299742Sdim if (target->current_rev < created_rev) 781299742Sdim { 782299742Sdim *fs_err = svn_error_createf 783299742Sdim (SVN_ERR_FS_OUT_OF_DATE, NULL, 784299742Sdim _("Lock failed: newer version of '%s' exists"), path); 785299742Sdim 786299742Sdim return SVN_NO_ERROR; 787299742Sdim } 788251881Speter } 789251881Speter 790251881Speter /* If the caller provided a TOKEN, we *really* need to see 791251881Speter if a lock already exists with that token, and if so, verify that 792251881Speter the lock's path matches PATH. Otherwise we run the risk of 793251881Speter breaking the 1-to-1 mapping of lock tokens to locked paths. */ 794251881Speter /* ### TODO: actually do this check. This is tough, because the 795251881Speter schema doesn't supply a lookup-by-token mechanism. */ 796251881Speter 797251881Speter /* Is the path already locked? 798251881Speter 799251881Speter Note that this next function call will automatically ignore any 800251881Speter errors about {the path not existing as a key, the path's token 801251881Speter not existing as a key, the lock just having been expired}. And 802251881Speter that's totally fine. Any of these three errors are perfectly 803251881Speter acceptable to ignore; it means that the path is now free and 804251881Speter clear for locking, because the fsfs funcs just cleared out both 805251881Speter of the tables for us. */ 806299742Sdim SVN_ERR(get_lock_helper(lb->fs, &existing_lock, path, TRUE, pool)); 807251881Speter if (existing_lock) 808251881Speter { 809251881Speter if (! lb->steal_lock) 810251881Speter { 811251881Speter /* Sorry, the path is already locked. */ 812299742Sdim *fs_err = SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock); 813299742Sdim return SVN_NO_ERROR; 814251881Speter } 815299742Sdim } 816299742Sdim 817299742Sdim return SVN_NO_ERROR; 818299742Sdim} 819299742Sdim 820299742Sdimstruct lock_info_t { 821299742Sdim const char *path; 822299742Sdim svn_lock_t *lock; 823299742Sdim svn_error_t *fs_err; 824299742Sdim}; 825299742Sdim 826299742Sdim/* The body of svn_fs_fs__lock(), which see. 827299742Sdim 828299742Sdim BATON is a 'struct lock_baton *' holding the effective arguments. 829299742Sdim BATON->targets is an array of 'svn_sort__item_t' targets, sorted by 830299742Sdim path, mapping canonical path to 'svn_fs_lock_target_t'. Set 831299742Sdim BATON->infos to an array of 'lock_info_t' holding the results. For 832299742Sdim the other arguments, see svn_fs_lock_many(). 833299742Sdim 834299742Sdim This implements the svn_fs_fs__with_write_lock() 'body' callback 835299742Sdim type, and assumes that the write lock is held. 836299742Sdim */ 837299742Sdimstatic svn_error_t * 838299742Sdimlock_body(void *baton, apr_pool_t *pool) 839299742Sdim{ 840299742Sdim struct lock_baton *lb = baton; 841299742Sdim svn_fs_root_t *root; 842299742Sdim svn_revnum_t youngest; 843299742Sdim const char *rev_0_path; 844299742Sdim int i; 845299742Sdim apr_hash_t *index_updates = apr_hash_make(pool); 846299742Sdim apr_hash_index_t *hi; 847299742Sdim apr_pool_t *iterpool = svn_pool_create(pool); 848299742Sdim 849299742Sdim /* Until we implement directory locks someday, we only allow locks 850299742Sdim on files or non-existent paths. */ 851299742Sdim /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular 852299742Sdim library dependencies, which are not portable. */ 853299742Sdim SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool)); 854299742Sdim SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool)); 855299742Sdim 856299742Sdim for (i = 0; i < lb->targets->nelts; ++i) 857299742Sdim { 858299742Sdim const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i, 859299742Sdim svn_sort__item_t); 860299742Sdim struct lock_info_t info; 861299742Sdim 862299742Sdim svn_pool_clear(iterpool); 863299742Sdim 864299742Sdim info.path = item->key; 865299742Sdim info.lock = NULL; 866299742Sdim info.fs_err = SVN_NO_ERROR; 867299742Sdim 868299742Sdim SVN_ERR(check_lock(&info.fs_err, info.path, item->value, lb, root, 869299742Sdim youngest, iterpool)); 870299742Sdim 871299742Sdim /* If no error occurred while pre-checking, schedule the index updates for 872299742Sdim this path. */ 873299742Sdim if (!info.fs_err) 874299742Sdim schedule_index_update(index_updates, info.path, iterpool); 875299742Sdim 876299742Sdim APR_ARRAY_PUSH(lb->infos, struct lock_info_t) = info; 877299742Sdim } 878299742Sdim 879299742Sdim rev_0_path = svn_fs_fs__path_rev_absolute(lb->fs, 0, pool); 880299742Sdim 881299742Sdim /* We apply the scheduled index updates before writing the actual locks. 882299742Sdim 883299742Sdim Writing indices before locks is correct: if interrupted it leaves 884299742Sdim indices without locks rather than locks without indices. An 885299742Sdim index without a lock is consistent in that it always shows up as 886299742Sdim unlocked in svn_fs_fs__allow_locked_operation. A lock without an 887299742Sdim index is inconsistent, svn_fs_fs__allow_locked_operation will 888299742Sdim show locked on the file but unlocked on the parent. */ 889299742Sdim 890299742Sdim for (hi = apr_hash_first(pool, index_updates); hi; hi = apr_hash_next(hi)) 891299742Sdim { 892299742Sdim const char *path = apr_hash_this_key(hi); 893299742Sdim apr_array_header_t *children = apr_hash_this_val(hi); 894299742Sdim 895299742Sdim svn_pool_clear(iterpool); 896299742Sdim SVN_ERR(add_to_digest(lb->fs->path, children, path, rev_0_path, 897299742Sdim iterpool)); 898299742Sdim } 899299742Sdim 900299742Sdim for (i = 0; i < lb->infos->nelts; ++i) 901299742Sdim { 902299742Sdim struct lock_info_t *info = &APR_ARRAY_IDX(lb->infos, i, 903299742Sdim struct lock_info_t); 904299742Sdim svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i, svn_sort__item_t); 905299742Sdim svn_fs_lock_target_t *target = item->value; 906299742Sdim 907299742Sdim svn_pool_clear(iterpool); 908299742Sdim 909299742Sdim if (! info->fs_err) 910251881Speter { 911299742Sdim info->lock = svn_lock_create(lb->result_pool); 912299742Sdim if (target->token) 913299742Sdim info->lock->token = apr_pstrdup(lb->result_pool, target->token); 914299742Sdim else 915299742Sdim SVN_ERR(svn_fs_fs__generate_lock_token(&(info->lock->token), lb->fs, 916299742Sdim lb->result_pool)); 917299742Sdim 918299742Sdim /* The INFO->PATH is already allocated in LB->RESULT_POOL as a result 919299742Sdim of svn_fspath__canonicalize() (see svn_fs_fs__lock()). */ 920299742Sdim info->lock->path = info->path; 921299742Sdim info->lock->owner = apr_pstrdup(lb->result_pool, 922299742Sdim lb->fs->access_ctx->username); 923299742Sdim info->lock->comment = apr_pstrdup(lb->result_pool, lb->comment); 924299742Sdim info->lock->is_dav_comment = lb->is_dav_comment; 925299742Sdim info->lock->creation_date = apr_time_now(); 926299742Sdim info->lock->expiration_date = lb->expiration_date; 927299742Sdim 928299742Sdim info->fs_err = set_lock(lb->fs->path, info->lock, rev_0_path, 929299742Sdim iterpool); 930251881Speter } 931251881Speter } 932251881Speter 933299742Sdim svn_pool_destroy(iterpool); 934251881Speter return SVN_NO_ERROR; 935251881Speter} 936251881Speter 937299742Sdim/* The effective arguments for unlock_body() below. */ 938251881Speterstruct unlock_baton { 939251881Speter svn_fs_t *fs; 940299742Sdim apr_array_header_t *targets; 941299742Sdim apr_array_header_t *infos; 942299742Sdim /* Set skip_check TRUE to prevent the checks that set infos[].fs_err. */ 943299742Sdim svn_boolean_t skip_check; 944251881Speter svn_boolean_t break_lock; 945299742Sdim apr_pool_t *result_pool; 946251881Speter}; 947251881Speter 948299742Sdimstatic svn_error_t * 949299742Sdimcheck_unlock(svn_error_t **fs_err, 950299742Sdim const char *path, 951299742Sdim const char *token, 952299742Sdim struct unlock_baton *ub, 953299742Sdim svn_fs_root_t *root, 954299742Sdim apr_pool_t *pool) 955299742Sdim{ 956299742Sdim svn_lock_t *lock; 957299742Sdim 958299742Sdim *fs_err = get_lock(&lock, ub->fs, path, TRUE, TRUE, pool); 959299742Sdim if (!*fs_err && !ub->break_lock) 960299742Sdim { 961299742Sdim if (strcmp(token, lock->token) != 0) 962299742Sdim *fs_err = SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, path); 963299742Sdim else if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0) 964299742Sdim *fs_err = SVN_FS__ERR_LOCK_OWNER_MISMATCH(ub->fs, 965299742Sdim ub->fs->access_ctx->username, 966299742Sdim lock->owner); 967299742Sdim } 968299742Sdim 969299742Sdim return SVN_NO_ERROR; 970299742Sdim} 971299742Sdim 972299742Sdimstruct unlock_info_t { 973299742Sdim const char *path; 974299742Sdim svn_error_t *fs_err; 975299742Sdim svn_boolean_t done; 976299742Sdim}; 977299742Sdim 978299742Sdim/* The body of svn_fs_fs__unlock(), which see. 979299742Sdim 980299742Sdim BATON is a 'struct unlock_baton *' holding the effective arguments. 981299742Sdim BATON->targets is an array of 'svn_sort__item_t' targets, sorted by 982299742Sdim path, mapping canonical path to (const char *) token. Set 983299742Sdim BATON->infos to an array of 'unlock_info_t' results. For the other 984299742Sdim arguments, see svn_fs_unlock_many(). 985299742Sdim 986299742Sdim This implements the svn_fs_fs__with_write_lock() 'body' callback 987251881Speter type, and assumes that the write lock is held. 988299742Sdim */ 989251881Speterstatic svn_error_t * 990251881Speterunlock_body(void *baton, apr_pool_t *pool) 991251881Speter{ 992251881Speter struct unlock_baton *ub = baton; 993299742Sdim svn_fs_root_t *root; 994299742Sdim svn_revnum_t youngest; 995299742Sdim const char *rev_0_path; 996299742Sdim int i; 997299742Sdim apr_hash_t *indices_updates = apr_hash_make(pool); 998299742Sdim apr_hash_index_t *hi; 999299742Sdim apr_pool_t *iterpool = svn_pool_create(pool); 1000251881Speter 1001299742Sdim SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool)); 1002299742Sdim SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool)); 1003251881Speter 1004299742Sdim for (i = 0; i < ub->targets->nelts; ++i) 1005251881Speter { 1006299742Sdim const svn_sort__item_t *item = &APR_ARRAY_IDX(ub->targets, i, 1007299742Sdim svn_sort__item_t); 1008299742Sdim const char *token = item->value; 1009299742Sdim struct unlock_info_t info; 1010251881Speter 1011299742Sdim svn_pool_clear(iterpool); 1012251881Speter 1013299742Sdim info.path = item->key; 1014299742Sdim info.fs_err = SVN_NO_ERROR; 1015299742Sdim info.done = FALSE; 1016299742Sdim 1017299742Sdim if (!ub->skip_check) 1018299742Sdim SVN_ERR(check_unlock(&info.fs_err, info.path, token, ub, root, 1019299742Sdim iterpool)); 1020299742Sdim 1021299742Sdim /* If no error occurred while pre-checking, schedule the index updates for 1022299742Sdim this path. */ 1023299742Sdim if (!info.fs_err) 1024299742Sdim schedule_index_update(indices_updates, info.path, iterpool); 1025299742Sdim 1026299742Sdim APR_ARRAY_PUSH(ub->infos, struct unlock_info_t) = info; 1027251881Speter } 1028251881Speter 1029299742Sdim rev_0_path = svn_fs_fs__path_rev_absolute(ub->fs, 0, pool); 1030299742Sdim 1031299742Sdim /* Unlike the lock_body(), we need to delete locks *before* we start to 1032299742Sdim update indices. */ 1033299742Sdim 1034299742Sdim for (i = 0; i < ub->infos->nelts; ++i) 1035299742Sdim { 1036299742Sdim struct unlock_info_t *info = &APR_ARRAY_IDX(ub->infos, i, 1037299742Sdim struct unlock_info_t); 1038299742Sdim 1039299742Sdim svn_pool_clear(iterpool); 1040299742Sdim 1041299742Sdim if (! info->fs_err) 1042299742Sdim { 1043299742Sdim SVN_ERR(delete_lock(ub->fs->path, info->path, iterpool)); 1044299742Sdim info->done = TRUE; 1045299742Sdim } 1046299742Sdim } 1047299742Sdim 1048299742Sdim for (hi = apr_hash_first(pool, indices_updates); hi; hi = apr_hash_next(hi)) 1049299742Sdim { 1050299742Sdim const char *path = apr_hash_this_key(hi); 1051299742Sdim apr_array_header_t *children = apr_hash_this_val(hi); 1052299742Sdim 1053299742Sdim svn_pool_clear(iterpool); 1054299742Sdim SVN_ERR(delete_from_digest(ub->fs->path, children, path, rev_0_path, 1055299742Sdim iterpool)); 1056299742Sdim } 1057299742Sdim 1058299742Sdim svn_pool_destroy(iterpool); 1059299742Sdim return SVN_NO_ERROR; 1060251881Speter} 1061251881Speter 1062299742Sdim/* Unlock the lock described by LOCK->path and LOCK->token in FS. 1063299742Sdim 1064299742Sdim This assumes that the write lock is held. 1065299742Sdim */ 1066299742Sdimstatic svn_error_t * 1067299742Sdimunlock_single(svn_fs_t *fs, 1068299742Sdim svn_lock_t *lock, 1069299742Sdim apr_pool_t *pool) 1070299742Sdim{ 1071299742Sdim struct unlock_baton ub; 1072299742Sdim svn_sort__item_t item; 1073299742Sdim apr_array_header_t *targets = apr_array_make(pool, 1, 1074299742Sdim sizeof(svn_sort__item_t)); 1075299742Sdim item.key = lock->path; 1076299742Sdim item.klen = strlen(item.key); 1077299742Sdim item.value = (char*)lock->token; 1078299742Sdim APR_ARRAY_PUSH(targets, svn_sort__item_t) = item; 1079299742Sdim 1080299742Sdim ub.fs = fs; 1081299742Sdim ub.targets = targets; 1082299742Sdim ub.infos = apr_array_make(pool, targets->nelts, 1083299742Sdim sizeof(struct unlock_info_t)); 1084299742Sdim ub.skip_check = TRUE; 1085299742Sdim ub.result_pool = pool; 1086299742Sdim 1087299742Sdim /* No ub.infos[].fs_err error because skip_check is TRUE. */ 1088299742Sdim SVN_ERR(unlock_body(&ub, pool)); 1089299742Sdim 1090299742Sdim return SVN_NO_ERROR; 1091299742Sdim} 1092299742Sdim 1093251881Speter 1094251881Speter/*** Public API implementations ***/ 1095251881Speter 1096251881Spetersvn_error_t * 1097299742Sdimsvn_fs_fs__lock(svn_fs_t *fs, 1098299742Sdim apr_hash_t *targets, 1099251881Speter const char *comment, 1100251881Speter svn_boolean_t is_dav_comment, 1101251881Speter apr_time_t expiration_date, 1102251881Speter svn_boolean_t steal_lock, 1103299742Sdim svn_fs_lock_callback_t lock_callback, 1104299742Sdim void *lock_baton, 1105299742Sdim apr_pool_t *result_pool, 1106299742Sdim apr_pool_t *scratch_pool) 1107251881Speter{ 1108251881Speter struct lock_baton lb; 1109299742Sdim apr_array_header_t *sorted_targets; 1110299742Sdim apr_hash_t *canonical_targets = apr_hash_make(scratch_pool); 1111299742Sdim apr_hash_index_t *hi; 1112299742Sdim apr_pool_t *iterpool; 1113299742Sdim svn_error_t *err, *cb_err = SVN_NO_ERROR; 1114299742Sdim int i; 1115251881Speter 1116251881Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1117251881Speter 1118299742Sdim /* We need to have a username attached to the fs. */ 1119299742Sdim if (!fs->access_ctx || !fs->access_ctx->username) 1120299742Sdim return SVN_FS__ERR_NO_USER(fs); 1121299742Sdim 1122299742Sdim /* The FS locking API allows both canonical and non-canonical 1123299742Sdim paths which means that the same canonical path could be 1124299742Sdim represented more than once in the TARGETS hash. We just keep 1125299742Sdim one, choosing one with a token if possible. */ 1126299742Sdim for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi)) 1127299742Sdim { 1128299742Sdim const char *path = apr_hash_this_key(hi); 1129299742Sdim const svn_fs_lock_target_t *target = apr_hash_this_val(hi); 1130299742Sdim const svn_fs_lock_target_t *other; 1131299742Sdim 1132299742Sdim path = svn_fspath__canonicalize(path, result_pool); 1133299742Sdim other = svn_hash_gets(canonical_targets, path); 1134299742Sdim 1135299742Sdim if (!other || (!other->token && target->token)) 1136299742Sdim svn_hash_sets(canonical_targets, path, target); 1137299742Sdim } 1138299742Sdim 1139299742Sdim sorted_targets = svn_sort__hash(canonical_targets, 1140299742Sdim svn_sort_compare_items_as_paths, 1141299742Sdim scratch_pool); 1142299742Sdim 1143251881Speter lb.fs = fs; 1144299742Sdim lb.targets = sorted_targets; 1145299742Sdim lb.infos = apr_array_make(result_pool, sorted_targets->nelts, 1146299742Sdim sizeof(struct lock_info_t)); 1147251881Speter lb.comment = comment; 1148251881Speter lb.is_dav_comment = is_dav_comment; 1149251881Speter lb.expiration_date = expiration_date; 1150251881Speter lb.steal_lock = steal_lock; 1151299742Sdim lb.result_pool = result_pool; 1152251881Speter 1153299742Sdim iterpool = svn_pool_create(scratch_pool); 1154299742Sdim err = svn_fs_fs__with_write_lock(fs, lock_body, &lb, iterpool); 1155299742Sdim for (i = 0; i < lb.infos->nelts; ++i) 1156299742Sdim { 1157299742Sdim struct lock_info_t *info = &APR_ARRAY_IDX(lb.infos, i, 1158299742Sdim struct lock_info_t); 1159299742Sdim svn_pool_clear(iterpool); 1160299742Sdim if (!cb_err && lock_callback) 1161299742Sdim { 1162299742Sdim if (!info->lock && !info->fs_err) 1163299742Sdim info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED, 1164299742Sdim 0, _("Failed to lock '%s'"), 1165299742Sdim info->path); 1166299742Sdim 1167299742Sdim cb_err = lock_callback(lock_baton, info->path, info->lock, 1168299742Sdim info->fs_err, iterpool); 1169299742Sdim } 1170299742Sdim svn_error_clear(info->fs_err); 1171299742Sdim } 1172299742Sdim svn_pool_destroy(iterpool); 1173299742Sdim 1174299742Sdim if (err && cb_err) 1175299742Sdim svn_error_compose(err, cb_err); 1176299742Sdim else if (!err) 1177299742Sdim err = cb_err; 1178299742Sdim 1179299742Sdim return svn_error_trace(err); 1180251881Speter} 1181251881Speter 1182251881Speter 1183251881Spetersvn_error_t * 1184251881Spetersvn_fs_fs__generate_lock_token(const char **token, 1185251881Speter svn_fs_t *fs, 1186251881Speter apr_pool_t *pool) 1187251881Speter{ 1188251881Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1189251881Speter 1190251881Speter /* Notice that 'fs' is currently unused. But perhaps someday, we'll 1191251881Speter want to use the fs UUID + some incremented number? For now, we 1192251881Speter generate a URI that matches the DAV RFC. We could change this to 1193251881Speter some other URI scheme someday, if we wish. */ 1194251881Speter *token = apr_pstrcat(pool, "opaquelocktoken:", 1195299742Sdim svn_uuid_generate(pool), SVN_VA_NULL); 1196251881Speter return SVN_NO_ERROR; 1197251881Speter} 1198251881Speter 1199251881Spetersvn_error_t * 1200251881Spetersvn_fs_fs__unlock(svn_fs_t *fs, 1201299742Sdim apr_hash_t *targets, 1202251881Speter svn_boolean_t break_lock, 1203299742Sdim svn_fs_lock_callback_t lock_callback, 1204299742Sdim void *lock_baton, 1205299742Sdim apr_pool_t *result_pool, 1206299742Sdim apr_pool_t *scratch_pool) 1207251881Speter{ 1208251881Speter struct unlock_baton ub; 1209299742Sdim apr_array_header_t *sorted_targets; 1210299742Sdim apr_hash_t *canonical_targets = apr_hash_make(scratch_pool); 1211299742Sdim apr_hash_index_t *hi; 1212299742Sdim apr_pool_t *iterpool; 1213299742Sdim svn_error_t *err, *cb_err = SVN_NO_ERROR; 1214299742Sdim int i; 1215251881Speter 1216251881Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1217251881Speter 1218299742Sdim /* We need to have a username attached to the fs. */ 1219299742Sdim if (!fs->access_ctx || !fs->access_ctx->username) 1220299742Sdim return SVN_FS__ERR_NO_USER(fs); 1221299742Sdim 1222299742Sdim for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi)) 1223299742Sdim { 1224299742Sdim const char *path = apr_hash_this_key(hi); 1225299742Sdim const char *token = apr_hash_this_val(hi); 1226299742Sdim const char *other; 1227299742Sdim 1228299742Sdim path = svn_fspath__canonicalize(path, result_pool); 1229299742Sdim other = svn_hash_gets(canonical_targets, path); 1230299742Sdim 1231299742Sdim if (!other) 1232299742Sdim svn_hash_sets(canonical_targets, path, token); 1233299742Sdim } 1234299742Sdim 1235299742Sdim sorted_targets = svn_sort__hash(canonical_targets, 1236299742Sdim svn_sort_compare_items_as_paths, 1237299742Sdim scratch_pool); 1238299742Sdim 1239251881Speter ub.fs = fs; 1240299742Sdim ub.targets = sorted_targets; 1241299742Sdim ub.infos = apr_array_make(result_pool, sorted_targets->nelts, 1242299742Sdim sizeof(struct unlock_info_t)); 1243299742Sdim ub.skip_check = FALSE; 1244251881Speter ub.break_lock = break_lock; 1245299742Sdim ub.result_pool = result_pool; 1246251881Speter 1247299742Sdim iterpool = svn_pool_create(scratch_pool); 1248299742Sdim err = svn_fs_fs__with_write_lock(fs, unlock_body, &ub, iterpool); 1249299742Sdim for (i = 0; i < ub.infos->nelts; ++i) 1250299742Sdim { 1251299742Sdim struct unlock_info_t *info = &APR_ARRAY_IDX(ub.infos, i, 1252299742Sdim struct unlock_info_t); 1253299742Sdim svn_pool_clear(iterpool); 1254299742Sdim if (!cb_err && lock_callback) 1255299742Sdim { 1256299742Sdim if (!info->done && !info->fs_err) 1257299742Sdim info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED, 1258299742Sdim 0, _("Failed to unlock '%s'"), 1259299742Sdim info->path); 1260299742Sdim cb_err = lock_callback(lock_baton, info->path, NULL, info->fs_err, 1261299742Sdim iterpool); 1262299742Sdim } 1263299742Sdim svn_error_clear(info->fs_err); 1264299742Sdim } 1265299742Sdim svn_pool_destroy(iterpool); 1266299742Sdim 1267299742Sdim if (err && cb_err) 1268299742Sdim svn_error_compose(err, cb_err); 1269299742Sdim else if (!err) 1270299742Sdim err = cb_err; 1271299742Sdim 1272299742Sdim return svn_error_trace(err); 1273251881Speter} 1274251881Speter 1275251881Speter 1276251881Spetersvn_error_t * 1277251881Spetersvn_fs_fs__get_lock(svn_lock_t **lock_p, 1278251881Speter svn_fs_t *fs, 1279251881Speter const char *path, 1280251881Speter apr_pool_t *pool) 1281251881Speter{ 1282251881Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1283251881Speter path = svn_fs__canonicalize_abspath(path, pool); 1284251881Speter return get_lock_helper(fs, lock_p, path, FALSE, pool); 1285251881Speter} 1286251881Speter 1287251881Speter 1288251881Speter/* Baton for get_locks_filter_func(). */ 1289251881Spetertypedef struct get_locks_filter_baton_t 1290251881Speter{ 1291251881Speter const char *path; 1292251881Speter svn_depth_t requested_depth; 1293251881Speter svn_fs_get_locks_callback_t get_locks_func; 1294251881Speter void *get_locks_baton; 1295251881Speter 1296251881Speter} get_locks_filter_baton_t; 1297251881Speter 1298251881Speter 1299251881Speter/* A wrapper for the GET_LOCKS_FUNC passed to svn_fs_fs__get_locks() 1300251881Speter which filters out locks on paths that aren't within 1301251881Speter BATON->requested_depth of BATON->path before called 1302251881Speter BATON->get_locks_func() with BATON->get_locks_baton. 1303251881Speter 1304251881Speter NOTE: See issue #3660 for details about how the FSFS lock 1305251881Speter management code is inconsistent. Until that inconsistency is 1306251881Speter resolved, we take this filtering approach rather than honoring 1307251881Speter depth requests closer to the crawling code. In other words, once 1308251881Speter we decide how to resolve issue #3660, there might be a more 1309251881Speter performant way to honor the depth passed to svn_fs_fs__get_locks(). */ 1310251881Speterstatic svn_error_t * 1311251881Speterget_locks_filter_func(void *baton, 1312251881Speter svn_lock_t *lock, 1313251881Speter apr_pool_t *pool) 1314251881Speter{ 1315251881Speter get_locks_filter_baton_t *b = baton; 1316251881Speter 1317251881Speter /* Filter out unwanted paths. Since Subversion only allows 1318251881Speter locks on files, we can treat depth=immediates the same as 1319251881Speter depth=files for filtering purposes. Meaning, we'll keep 1320251881Speter this lock if: 1321251881Speter 1322251881Speter a) its path is the very path we queried, or 1323251881Speter b) we've asked for a fully recursive answer, or 1324251881Speter c) we've asked for depth=files or depth=immediates, and this 1325251881Speter lock is on an immediate child of our query path. 1326251881Speter */ 1327251881Speter if ((strcmp(b->path, lock->path) == 0) 1328251881Speter || (b->requested_depth == svn_depth_infinity)) 1329251881Speter { 1330251881Speter SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool)); 1331251881Speter } 1332251881Speter else if ((b->requested_depth == svn_depth_files) || 1333251881Speter (b->requested_depth == svn_depth_immediates)) 1334251881Speter { 1335251881Speter const char *rel_uri = svn_fspath__skip_ancestor(b->path, lock->path); 1336251881Speter if (rel_uri && (svn_path_component_count(rel_uri) == 1)) 1337251881Speter SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool)); 1338251881Speter } 1339251881Speter 1340251881Speter return SVN_NO_ERROR; 1341251881Speter} 1342251881Speter 1343251881Spetersvn_error_t * 1344251881Spetersvn_fs_fs__get_locks(svn_fs_t *fs, 1345251881Speter const char *path, 1346251881Speter svn_depth_t depth, 1347251881Speter svn_fs_get_locks_callback_t get_locks_func, 1348251881Speter void *get_locks_baton, 1349251881Speter apr_pool_t *pool) 1350251881Speter{ 1351251881Speter const char *digest_path; 1352251881Speter get_locks_filter_baton_t glfb; 1353251881Speter 1354251881Speter SVN_ERR(svn_fs__check_fs(fs, TRUE)); 1355251881Speter path = svn_fs__canonicalize_abspath(path, pool); 1356251881Speter 1357251881Speter glfb.path = path; 1358251881Speter glfb.requested_depth = depth; 1359251881Speter glfb.get_locks_func = get_locks_func; 1360251881Speter glfb.get_locks_baton = get_locks_baton; 1361251881Speter 1362251881Speter /* Get the top digest path in our tree of interest, and then walk it. */ 1363251881Speter SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool)); 1364251881Speter SVN_ERR(walk_locks(fs, digest_path, get_locks_filter_func, &glfb, 1365251881Speter FALSE, pool)); 1366251881Speter return SVN_NO_ERROR; 1367251881Speter} 1368