1 /* lock.c : functions for manipulating filesystem locks.
3 * ====================================================================
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
20 * ====================================================================
23 #include "svn_pools.h"
24 #include "svn_error.h"
25 #include "svn_dirent_uri.h"
33 #include <apr_file_io.h>
34 #include <apr_file_info.h>
40 #include "../libsvn_fs/fs-loader.h"
42 #include "private/svn_fs_util.h"
43 #include "private/svn_fspath.h"
44 #include "private/svn_sorts_private.h"
45 #include "svn_private_config.h"
47 /* Names of hash keys used to store a lock for writing to disk. */
48 #define PATH_KEY "path"
49 #define TOKEN_KEY "token"
50 #define OWNER_KEY "owner"
51 #define CREATION_DATE_KEY "creation_date"
52 #define EXPIRATION_DATE_KEY "expiration_date"
53 #define COMMENT_KEY "comment"
54 #define IS_DAV_COMMENT_KEY "is_dav_comment"
55 #define CHILDREN_KEY "children"
57 /* Number of characters from the head of a digest file name used to
58 calculate a subdirectory in which to drop that file. */
59 #define DIGEST_SUBDIR_LEN 3
63 /*** Generic helper functions. ***/
65 /* Set *DIGEST to the MD5 hash of STR. */
67 make_digest(const char **digest,
71 svn_checksum_t *checksum;
73 SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, str, strlen(str), pool));
75 *digest = svn_checksum_to_cstring_display(checksum, pool);
80 /* Set the value of KEY (whose size is KEY_LEN, or APR_HASH_KEY_STRING
81 if unknown) to an svn_string_t-ized version of VALUE (whose size is
82 VALUE_LEN, or APR_HASH_KEY_STRING if unknown) in HASH. The value
83 will be allocated in POOL; KEY will not be duped. If either KEY or VALUE
84 is NULL, this function will do nothing. */
86 hash_store(apr_hash_t *hash,
90 apr_ssize_t value_len,
95 if (value_len == APR_HASH_KEY_STRING)
96 value_len = strlen(value);
97 apr_hash_set(hash, key, key_len,
98 svn_string_ncreate(value, value_len, pool));
102 /* Fetch the value of KEY from HASH, returning only the cstring data
103 of that value (if it exists). */
105 hash_fetch(apr_hash_t *hash,
108 svn_string_t *str = svn_hash_gets(hash, key);
109 return str ? str->data : NULL;
113 /* SVN_ERR_FS_CORRUPT: the lockfile for PATH in FS is corrupt. */
115 err_corrupt_lockfile(const char *fs_path, const char *path)
119 SVN_ERR_FS_CORRUPT, 0,
120 _("Corrupt lockfile for path '%s' in filesystem '%s'"),
125 /*** Digest file handling functions. ***/
127 /* Return the path of the lock/entries file for which DIGEST is the
128 hashed repository relative path. */
130 digest_path_from_digest(const char *fs_path,
134 return svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
135 apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN),
136 digest, SVN_VA_NULL);
140 /* Set *DIGEST_PATH to the path to the lock/entries digest file associate
141 with PATH, where PATH is the path to the lock file or lock entries file
144 digest_path_from_path(const char **digest_path,
150 SVN_ERR(make_digest(&digest, path, pool));
151 *digest_path = svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
152 apr_pstrmemdup(pool, digest,
154 digest, SVN_VA_NULL);
159 /* Write to DIGEST_PATH a representation of CHILDREN (which may be
160 empty, if the versioned path in FS represented by DIGEST_PATH has
161 no children) and LOCK (which may be NULL if that versioned path is
162 lock itself locked). Set the permissions of DIGEST_PATH to those of
163 PERMS_REFERENCE. Use POOL for all allocations.
166 write_digest_file(apr_hash_t *children,
169 const char *digest_path,
170 const char *perms_reference,
173 svn_error_t *err = SVN_NO_ERROR;
174 svn_stream_t *stream;
175 apr_hash_index_t *hi;
176 apr_hash_t *hash = apr_hash_make(pool);
177 const char *tmp_path;
179 SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs_path, PATH_LOCKS_DIR,
180 pool), fs_path, pool));
181 SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_dirname(digest_path, pool),
186 const char *creation_date = NULL, *expiration_date = NULL;
187 if (lock->creation_date)
188 creation_date = svn_time_to_cstring(lock->creation_date, pool);
189 if (lock->expiration_date)
190 expiration_date = svn_time_to_cstring(lock->expiration_date, pool);
191 hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1,
192 lock->path, APR_HASH_KEY_STRING, pool);
193 hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1,
194 lock->token, APR_HASH_KEY_STRING, pool);
195 hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1,
196 lock->owner, APR_HASH_KEY_STRING, pool);
197 hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1,
198 lock->comment, APR_HASH_KEY_STRING, pool);
199 hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1,
200 lock->is_dav_comment ? "1" : "0", 1, pool);
201 hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1,
202 creation_date, APR_HASH_KEY_STRING, pool);
203 hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1,
204 expiration_date, APR_HASH_KEY_STRING, pool);
206 if (apr_hash_count(children))
208 svn_stringbuf_t *children_list = svn_stringbuf_create_empty(pool);
209 for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
211 svn_stringbuf_appendbytes(children_list,
212 apr_hash_this_key(hi),
213 apr_hash_this_key_len(hi));
214 svn_stringbuf_appendbyte(children_list, '\n');
216 hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1,
217 children_list->data, children_list->len, pool);
220 SVN_ERR(svn_stream_open_unique(&stream, &tmp_path,
221 svn_dirent_dirname(digest_path, pool),
222 svn_io_file_del_none, pool, pool));
223 if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool)))
225 svn_error_clear(svn_stream_close(stream));
226 return svn_error_createf(err->apr_err,
228 _("Cannot write lock/entries hashfile '%s'"),
229 svn_dirent_local_style(tmp_path, pool));
232 SVN_ERR(svn_stream_close(stream));
233 SVN_ERR(svn_io_file_rename(tmp_path, digest_path, pool));
234 SVN_ERR(svn_io_copy_perms(perms_reference, digest_path, pool));
239 /* Parse the file at DIGEST_PATH, populating the lock LOCK_P in that
240 file (if it exists, and if *LOCK_P is non-NULL) and the hash of
241 CHILDREN_P (if any exist, and if *CHILDREN_P is non-NULL). Use POOL
242 for all allocations. */
244 read_digest_file(apr_hash_t **children_p,
247 const char *digest_path,
250 svn_error_t *err = SVN_NO_ERROR;
253 svn_stream_t *stream;
255 svn_node_kind_t kind;
260 *children_p = apr_hash_make(pool);
262 SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
263 if (kind == svn_node_none)
266 /* If our caller doesn't care about anything but the presence of the
268 if (kind == svn_node_file && !lock_p && !children_p)
271 SVN_ERR(svn_stream_open_readonly(&stream, digest_path, pool, pool));
273 hash = apr_hash_make(pool);
274 if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool)))
276 svn_error_clear(svn_stream_close(stream));
277 return svn_error_createf(err->apr_err,
279 _("Can't parse lock/entries hashfile '%s'"),
280 svn_dirent_local_style(digest_path, pool));
282 SVN_ERR(svn_stream_close(stream));
284 /* If our caller cares, see if we have a lock path in our hash. If
285 so, we'll assume we have a lock here. */
286 val = hash_fetch(hash, PATH_KEY);
289 const char *path = val;
291 /* Create our lock and load it up. */
292 lock = svn_lock_create(pool);
295 if (! ((lock->token = hash_fetch(hash, TOKEN_KEY))))
296 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
298 if (! ((lock->owner = hash_fetch(hash, OWNER_KEY))))
299 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
301 if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY))))
302 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
303 lock->is_dav_comment = (val[0] == '1');
305 if (! ((val = hash_fetch(hash, CREATION_DATE_KEY))))
306 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
307 SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool));
309 if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY)))
310 SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool));
312 lock->comment = hash_fetch(hash, COMMENT_KEY);
317 /* If our caller cares, see if we have any children for this path. */
318 val = hash_fetch(hash, CHILDREN_KEY);
319 if (val && children_p)
321 apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool);
324 for (i = 0; i < kiddos->nelts; i++)
326 svn_hash_sets(*children_p, APR_ARRAY_IDX(kiddos, i, const char *),
335 /*** Lock helper functions (path here are still FS paths, not on-disk
336 schema-supporting paths) ***/
339 /* Write LOCK in FS to the actual OS filesystem.
341 Use PERMS_REFERENCE for the permissions of any digest files.
344 set_lock(const char *fs_path,
346 const char *perms_reference,
349 const char *digest_path;
350 apr_hash_t *children;
352 SVN_ERR(digest_path_from_path(&digest_path, fs_path, lock->path, pool));
354 /* We could get away without reading the file as children should
355 always come back empty. */
356 SVN_ERR(read_digest_file(&children, NULL, fs_path, digest_path, pool));
358 SVN_ERR(write_digest_file(children, lock, fs_path, digest_path,
359 perms_reference, pool));
365 delete_lock(const char *fs_path,
369 const char *digest_path;
371 SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
373 SVN_ERR(svn_io_remove_file2(digest_path, TRUE, pool));
379 add_to_digest(const char *fs_path,
380 apr_array_header_t *paths,
381 const char *index_path,
382 const char *perms_reference,
385 const char *index_digest_path;
386 apr_hash_t *children;
389 unsigned int original_count;
391 SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool));
393 SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool));
395 original_count = apr_hash_count(children);
397 for (i = 0; i < paths->nelts; ++i)
399 const char *path = APR_ARRAY_IDX(paths, i, const char *);
400 const char *digest_path, *digest_file;
402 SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
403 digest_file = svn_dirent_basename(digest_path, NULL);
404 svn_hash_sets(children, digest_file, (void *)1);
407 if (apr_hash_count(children) != original_count)
408 SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
409 perms_reference, pool));
415 delete_from_digest(const char *fs_path,
416 apr_array_header_t *paths,
417 const char *index_path,
418 const char *perms_reference,
421 const char *index_digest_path;
422 apr_hash_t *children;
426 SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool));
428 SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool));
430 for (i = 0; i < paths->nelts; ++i)
432 const char *path = APR_ARRAY_IDX(paths, i, const char *);
433 const char *digest_path, *digest_file;
435 SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
436 digest_file = svn_dirent_basename(digest_path, NULL);
437 svn_hash_sets(children, digest_file, NULL);
440 if (apr_hash_count(children) || lock)
441 SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
442 perms_reference, pool));
444 SVN_ERR(svn_io_remove_file2(index_digest_path, TRUE, pool));
450 unlock_single(svn_fs_t *fs,
454 /* Check if LOCK has been already expired. */
455 static svn_boolean_t lock_expired(const svn_lock_t *lock)
457 return lock->expiration_date && (apr_time_now() > lock->expiration_date);
460 /* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
461 TRUE if the caller (or one of its callers) has taken out the
462 repository-wide write lock, FALSE otherwise. If MUST_EXIST is
463 not set, the function will simply return NULL in *LOCK_P instead
464 of creating an SVN_FS__ERR_NO_SUCH_LOCK error in case the lock
465 was not found (much faster). Use POOL for allocations. */
467 get_lock(svn_lock_t **lock_p,
470 svn_boolean_t have_write_lock,
471 svn_boolean_t must_exist,
474 svn_lock_t *lock = NULL;
475 const char *digest_path;
476 svn_node_kind_t kind;
478 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
479 SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
482 if (kind != svn_node_none)
483 SVN_ERR(read_digest_file(NULL, &lock, fs->path, digest_path, pool));
486 return must_exist ? SVN_FS__ERR_NO_SUCH_LOCK(fs, path) : SVN_NO_ERROR;
488 /* Don't return an expired lock. */
489 if (lock_expired(lock))
491 /* Only remove the lock if we have the write lock.
492 Read operations shouldn't change the filesystem. */
494 SVN_ERR(unlock_single(fs, lock, pool));
495 return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
503 /* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
504 TRUE if the caller (or one of its callers) has taken out the
505 repository-wide write lock, FALSE otherwise. Use POOL for
508 get_lock_helper(svn_fs_t *fs,
511 svn_boolean_t have_write_lock,
517 err = get_lock(&lock, fs, path, have_write_lock, FALSE, pool);
519 /* We've deliberately decided that this function doesn't tell the
520 caller *why* the lock is unavailable. */
521 if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
522 || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)))
524 svn_error_clear(err);
536 /* A function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
537 all locks in and under PATH in FS.
538 HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
539 has the FS write lock. */
541 walk_locks(svn_fs_t *fs,
542 const char *digest_path,
543 svn_fs_get_locks_callback_t get_locks_func,
544 void *get_locks_baton,
545 svn_boolean_t have_write_lock,
548 apr_hash_index_t *hi;
549 apr_hash_t *children;
553 /* First, send up any locks in the current digest file. */
554 SVN_ERR(read_digest_file(&children, &lock, fs->path, digest_path, pool));
556 if (lock && lock_expired(lock))
558 /* Only remove the lock if we have the write lock.
559 Read operations shouldn't change the filesystem. */
561 SVN_ERR(unlock_single(fs, lock, pool));
565 SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
568 /* Now, report all the child entries (if any; bail otherwise). */
569 if (! apr_hash_count(children))
571 subpool = svn_pool_create(pool);
572 for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
574 const char *digest = apr_hash_this_key(hi);
575 svn_pool_clear(subpool);
577 SVN_ERR(read_digest_file
578 (NULL, &lock, fs->path,
579 digest_path_from_digest(fs->path, digest, subpool), subpool));
581 if (lock && lock_expired(lock))
583 /* Only remove the lock if we have the write lock.
584 Read operations shouldn't change the filesystem. */
586 SVN_ERR(unlock_single(fs, lock, pool));
590 SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
593 svn_pool_destroy(subpool);
598 /* Utility function: verify that a lock can be used. Interesting
599 errors returned from this function:
601 SVN_ERR_FS_NO_USER: No username attached to FS.
602 SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner.
603 SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK.
606 verify_lock(svn_fs_t *fs,
610 if ((! fs->access_ctx) || (! fs->access_ctx->username))
611 return svn_error_createf
612 (SVN_ERR_FS_NO_USER, NULL,
613 _("Cannot verify lock on path '%s'; no username available"),
616 else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
617 return svn_error_createf
618 (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
619 _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
620 fs->access_ctx->username, lock->path, lock->owner);
622 else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
623 return svn_error_createf
624 (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
625 _("Cannot verify lock on path '%s'; no matching lock-token available"),
632 /* This implements the svn_fs_get_locks_callback_t interface, where
633 BATON is just an svn_fs_t object. */
635 get_locks_callback(void *baton,
639 return verify_lock(baton, lock, pool);
643 /* The main routine for lock enforcement, used throughout libsvn_fs_fs. */
645 svn_fs_fs__allow_locked_operation(const char *path,
647 svn_boolean_t recurse,
648 svn_boolean_t have_write_lock,
651 path = svn_fs__canonicalize_abspath(path, pool);
654 /* Discover all locks at or below the path. */
655 const char *digest_path;
656 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
657 SVN_ERR(walk_locks(fs, digest_path, get_locks_callback,
658 fs, have_write_lock, pool));
662 /* Discover and verify any lock attached to the path. */
664 SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock, pool));
666 SVN_ERR(verify_lock(fs, lock, pool));
671 /* Helper function called from the lock and unlock code.
672 UPDATES is a map from "const char *" parent paths to "apr_array_header_t *"
673 arrays of child paths. For all of the parent paths of PATH this function
674 adds PATH to the corresponding array of child paths. */
676 schedule_index_update(apr_hash_t *updates,
678 apr_pool_t *scratch_pool)
680 apr_pool_t *hashpool = apr_hash_pool_get(updates);
681 const char *parent_path = path;
683 while (! svn_fspath__is_root(parent_path, strlen(parent_path)))
685 apr_array_header_t *children;
687 parent_path = svn_fspath__dirname(parent_path, scratch_pool);
688 children = svn_hash_gets(updates, parent_path);
692 children = apr_array_make(hashpool, 8, sizeof(const char *));
693 svn_hash_sets(updates, apr_pstrdup(hashpool, parent_path), children);
696 APR_ARRAY_PUSH(children, const char *) = path;
700 /* The effective arguments for lock_body() below. */
703 apr_array_header_t *targets;
704 apr_array_header_t *infos;
706 svn_boolean_t is_dav_comment;
707 apr_time_t expiration_date;
708 svn_boolean_t steal_lock;
709 apr_pool_t *result_pool;
713 check_lock(svn_error_t **fs_err,
715 const svn_fs_lock_target_t *target,
716 struct lock_baton *lb,
718 svn_revnum_t youngest_rev,
721 svn_node_kind_t kind;
722 svn_lock_t *existing_lock;
724 *fs_err = SVN_NO_ERROR;
726 SVN_ERR(svn_fs_fs__check_path(&kind, root, path, pool));
727 if (kind == svn_node_dir)
729 *fs_err = SVN_FS__ERR_NOT_FILE(lb->fs, path);
733 /* While our locking implementation easily supports the locking of
734 nonexistent paths, we deliberately choose not to allow such madness. */
735 if (kind == svn_node_none)
737 if (SVN_IS_VALID_REVNUM(target->current_rev))
738 *fs_err = svn_error_createf(
739 SVN_ERR_FS_OUT_OF_DATE, NULL,
740 _("Path '%s' doesn't exist in HEAD revision"),
743 *fs_err = svn_error_createf(
744 SVN_ERR_FS_NOT_FOUND, NULL,
745 _("Path '%s' doesn't exist in HEAD revision"),
751 /* Is the caller attempting to lock an out-of-date working file? */
752 if (SVN_IS_VALID_REVNUM(target->current_rev))
754 svn_revnum_t created_rev;
756 if (target->current_rev > youngest_rev)
758 *fs_err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
759 _("No such revision %ld"),
760 target->current_rev);
764 SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, path,
767 /* SVN_INVALID_REVNUM means the path doesn't exist. So
768 apparently somebody is trying to lock something in their
769 working copy, but somebody else has deleted the thing
770 from HEAD. That counts as being 'out of date'. */
771 if (! SVN_IS_VALID_REVNUM(created_rev))
773 *fs_err = svn_error_createf
774 (SVN_ERR_FS_OUT_OF_DATE, NULL,
775 _("Path '%s' doesn't exist in HEAD revision"), path);
780 if (target->current_rev < created_rev)
782 *fs_err = svn_error_createf
783 (SVN_ERR_FS_OUT_OF_DATE, NULL,
784 _("Lock failed: newer version of '%s' exists"), path);
790 /* If the caller provided a TOKEN, we *really* need to see
791 if a lock already exists with that token, and if so, verify that
792 the lock's path matches PATH. Otherwise we run the risk of
793 breaking the 1-to-1 mapping of lock tokens to locked paths. */
794 /* ### TODO: actually do this check. This is tough, because the
795 schema doesn't supply a lookup-by-token mechanism. */
797 /* Is the path already locked?
799 Note that this next function call will automatically ignore any
800 errors about {the path not existing as a key, the path's token
801 not existing as a key, the lock just having been expired}. And
802 that's totally fine. Any of these three errors are perfectly
803 acceptable to ignore; it means that the path is now free and
804 clear for locking, because the fsfs funcs just cleared out both
805 of the tables for us. */
806 SVN_ERR(get_lock_helper(lb->fs, &existing_lock, path, TRUE, pool));
809 if (! lb->steal_lock)
811 /* Sorry, the path is already locked. */
812 *fs_err = SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
826 /* The body of svn_fs_fs__lock(), which see.
828 BATON is a 'struct lock_baton *' holding the effective arguments.
829 BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
830 path, mapping canonical path to 'svn_fs_lock_target_t'. Set
831 BATON->infos to an array of 'lock_info_t' holding the results. For
832 the other arguments, see svn_fs_lock_many().
834 This implements the svn_fs_fs__with_write_lock() 'body' callback
835 type, and assumes that the write lock is held.
838 lock_body(void *baton, apr_pool_t *pool)
840 struct lock_baton *lb = baton;
842 svn_revnum_t youngest;
843 const char *rev_0_path;
845 apr_hash_t *index_updates = apr_hash_make(pool);
846 apr_hash_index_t *hi;
847 apr_pool_t *iterpool = svn_pool_create(pool);
849 /* Until we implement directory locks someday, we only allow locks
850 on files or non-existent paths. */
851 /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
852 library dependencies, which are not portable. */
853 SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
854 SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
856 for (i = 0; i < lb->targets->nelts; ++i)
858 const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
860 struct lock_info_t info;
862 svn_pool_clear(iterpool);
864 info.path = item->key;
866 info.fs_err = SVN_NO_ERROR;
868 SVN_ERR(check_lock(&info.fs_err, info.path, item->value, lb, root,
869 youngest, iterpool));
871 /* If no error occurred while pre-checking, schedule the index updates for
874 schedule_index_update(index_updates, info.path, iterpool);
876 APR_ARRAY_PUSH(lb->infos, struct lock_info_t) = info;
879 rev_0_path = svn_fs_fs__path_rev_absolute(lb->fs, 0, pool);
881 /* We apply the scheduled index updates before writing the actual locks.
883 Writing indices before locks is correct: if interrupted it leaves
884 indices without locks rather than locks without indices. An
885 index without a lock is consistent in that it always shows up as
886 unlocked in svn_fs_fs__allow_locked_operation. A lock without an
887 index is inconsistent, svn_fs_fs__allow_locked_operation will
888 show locked on the file but unlocked on the parent. */
890 for (hi = apr_hash_first(pool, index_updates); hi; hi = apr_hash_next(hi))
892 const char *path = apr_hash_this_key(hi);
893 apr_array_header_t *children = apr_hash_this_val(hi);
895 svn_pool_clear(iterpool);
896 SVN_ERR(add_to_digest(lb->fs->path, children, path, rev_0_path,
900 for (i = 0; i < lb->infos->nelts; ++i)
902 struct lock_info_t *info = &APR_ARRAY_IDX(lb->infos, i,
904 svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i, svn_sort__item_t);
905 svn_fs_lock_target_t *target = item->value;
907 svn_pool_clear(iterpool);
911 info->lock = svn_lock_create(lb->result_pool);
913 info->lock->token = apr_pstrdup(lb->result_pool, target->token);
915 SVN_ERR(svn_fs_fs__generate_lock_token(&(info->lock->token), lb->fs,
918 /* The INFO->PATH is already allocated in LB->RESULT_POOL as a result
919 of svn_fspath__canonicalize() (see svn_fs_fs__lock()). */
920 info->lock->path = info->path;
921 info->lock->owner = apr_pstrdup(lb->result_pool,
922 lb->fs->access_ctx->username);
923 info->lock->comment = apr_pstrdup(lb->result_pool, lb->comment);
924 info->lock->is_dav_comment = lb->is_dav_comment;
925 info->lock->creation_date = apr_time_now();
926 info->lock->expiration_date = lb->expiration_date;
928 info->fs_err = set_lock(lb->fs->path, info->lock, rev_0_path,
933 svn_pool_destroy(iterpool);
937 /* The effective arguments for unlock_body() below. */
938 struct unlock_baton {
940 apr_array_header_t *targets;
941 apr_array_header_t *infos;
942 /* Set skip_check TRUE to prevent the checks that set infos[].fs_err. */
943 svn_boolean_t skip_check;
944 svn_boolean_t break_lock;
945 apr_pool_t *result_pool;
949 check_unlock(svn_error_t **fs_err,
952 struct unlock_baton *ub,
958 *fs_err = get_lock(&lock, ub->fs, path, TRUE, TRUE, pool);
959 if (!*fs_err && !ub->break_lock)
961 if (strcmp(token, lock->token) != 0)
962 *fs_err = SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, path);
963 else if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
964 *fs_err = SVN_FS__ERR_LOCK_OWNER_MISMATCH(ub->fs,
965 ub->fs->access_ctx->username,
972 struct unlock_info_t {
978 /* The body of svn_fs_fs__unlock(), which see.
980 BATON is a 'struct unlock_baton *' holding the effective arguments.
981 BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
982 path, mapping canonical path to (const char *) token. Set
983 BATON->infos to an array of 'unlock_info_t' results. For the other
984 arguments, see svn_fs_unlock_many().
986 This implements the svn_fs_fs__with_write_lock() 'body' callback
987 type, and assumes that the write lock is held.
990 unlock_body(void *baton, apr_pool_t *pool)
992 struct unlock_baton *ub = baton;
994 svn_revnum_t youngest;
995 const char *rev_0_path;
997 apr_hash_t *indices_updates = apr_hash_make(pool);
998 apr_hash_index_t *hi;
999 apr_pool_t *iterpool = svn_pool_create(pool);
1001 SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool));
1002 SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool));
1004 for (i = 0; i < ub->targets->nelts; ++i)
1006 const svn_sort__item_t *item = &APR_ARRAY_IDX(ub->targets, i,
1008 const char *token = item->value;
1009 struct unlock_info_t info;
1011 svn_pool_clear(iterpool);
1013 info.path = item->key;
1014 info.fs_err = SVN_NO_ERROR;
1017 if (!ub->skip_check)
1018 SVN_ERR(check_unlock(&info.fs_err, info.path, token, ub, root,
1021 /* If no error occurred while pre-checking, schedule the index updates for
1024 schedule_index_update(indices_updates, info.path, iterpool);
1026 APR_ARRAY_PUSH(ub->infos, struct unlock_info_t) = info;
1029 rev_0_path = svn_fs_fs__path_rev_absolute(ub->fs, 0, pool);
1031 /* Unlike the lock_body(), we need to delete locks *before* we start to
1034 for (i = 0; i < ub->infos->nelts; ++i)
1036 struct unlock_info_t *info = &APR_ARRAY_IDX(ub->infos, i,
1037 struct unlock_info_t);
1039 svn_pool_clear(iterpool);
1043 SVN_ERR(delete_lock(ub->fs->path, info->path, iterpool));
1048 for (hi = apr_hash_first(pool, indices_updates); hi; hi = apr_hash_next(hi))
1050 const char *path = apr_hash_this_key(hi);
1051 apr_array_header_t *children = apr_hash_this_val(hi);
1053 svn_pool_clear(iterpool);
1054 SVN_ERR(delete_from_digest(ub->fs->path, children, path, rev_0_path,
1058 svn_pool_destroy(iterpool);
1059 return SVN_NO_ERROR;
1062 /* Unlock the lock described by LOCK->path and LOCK->token in FS.
1064 This assumes that the write lock is held.
1066 static svn_error_t *
1067 unlock_single(svn_fs_t *fs,
1071 struct unlock_baton ub;
1072 svn_sort__item_t item;
1073 apr_array_header_t *targets = apr_array_make(pool, 1,
1074 sizeof(svn_sort__item_t));
1075 item.key = lock->path;
1076 item.klen = strlen(item.key);
1077 item.value = (char*)lock->token;
1078 APR_ARRAY_PUSH(targets, svn_sort__item_t) = item;
1081 ub.targets = targets;
1082 ub.infos = apr_array_make(pool, targets->nelts,
1083 sizeof(struct unlock_info_t));
1084 ub.skip_check = TRUE;
1085 ub.result_pool = pool;
1087 /* No ub.infos[].fs_err error because skip_check is TRUE. */
1088 SVN_ERR(unlock_body(&ub, pool));
1090 return SVN_NO_ERROR;
1094 /*** Public API implementations ***/
1097 svn_fs_fs__lock(svn_fs_t *fs,
1098 apr_hash_t *targets,
1099 const char *comment,
1100 svn_boolean_t is_dav_comment,
1101 apr_time_t expiration_date,
1102 svn_boolean_t steal_lock,
1103 svn_fs_lock_callback_t lock_callback,
1105 apr_pool_t *result_pool,
1106 apr_pool_t *scratch_pool)
1108 struct lock_baton lb;
1109 apr_array_header_t *sorted_targets;
1110 apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
1111 apr_hash_index_t *hi;
1112 apr_pool_t *iterpool;
1113 svn_error_t *err, *cb_err = SVN_NO_ERROR;
1116 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1118 /* We need to have a username attached to the fs. */
1119 if (!fs->access_ctx || !fs->access_ctx->username)
1120 return SVN_FS__ERR_NO_USER(fs);
1122 /* The FS locking API allows both canonical and non-canonical
1123 paths which means that the same canonical path could be
1124 represented more than once in the TARGETS hash. We just keep
1125 one, choosing one with a token if possible. */
1126 for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
1128 const char *path = apr_hash_this_key(hi);
1129 const svn_fs_lock_target_t *target = apr_hash_this_val(hi);
1130 const svn_fs_lock_target_t *other;
1132 path = svn_fspath__canonicalize(path, result_pool);
1133 other = svn_hash_gets(canonical_targets, path);
1135 if (!other || (!other->token && target->token))
1136 svn_hash_sets(canonical_targets, path, target);
1139 sorted_targets = svn_sort__hash(canonical_targets,
1140 svn_sort_compare_items_as_paths,
1144 lb.targets = sorted_targets;
1145 lb.infos = apr_array_make(result_pool, sorted_targets->nelts,
1146 sizeof(struct lock_info_t));
1147 lb.comment = comment;
1148 lb.is_dav_comment = is_dav_comment;
1149 lb.expiration_date = expiration_date;
1150 lb.steal_lock = steal_lock;
1151 lb.result_pool = result_pool;
1153 iterpool = svn_pool_create(scratch_pool);
1154 err = svn_fs_fs__with_write_lock(fs, lock_body, &lb, iterpool);
1155 for (i = 0; i < lb.infos->nelts; ++i)
1157 struct lock_info_t *info = &APR_ARRAY_IDX(lb.infos, i,
1158 struct lock_info_t);
1159 svn_pool_clear(iterpool);
1160 if (!cb_err && lock_callback)
1162 if (!info->lock && !info->fs_err)
1163 info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
1164 0, _("Failed to lock '%s'"),
1167 cb_err = lock_callback(lock_baton, info->path, info->lock,
1168 info->fs_err, iterpool);
1170 svn_error_clear(info->fs_err);
1172 svn_pool_destroy(iterpool);
1175 svn_error_compose(err, cb_err);
1179 return svn_error_trace(err);
1184 svn_fs_fs__generate_lock_token(const char **token,
1188 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1190 /* Notice that 'fs' is currently unused. But perhaps someday, we'll
1191 want to use the fs UUID + some incremented number? For now, we
1192 generate a URI that matches the DAV RFC. We could change this to
1193 some other URI scheme someday, if we wish. */
1194 *token = apr_pstrcat(pool, "opaquelocktoken:",
1195 svn_uuid_generate(pool), SVN_VA_NULL);
1196 return SVN_NO_ERROR;
1200 svn_fs_fs__unlock(svn_fs_t *fs,
1201 apr_hash_t *targets,
1202 svn_boolean_t break_lock,
1203 svn_fs_lock_callback_t lock_callback,
1205 apr_pool_t *result_pool,
1206 apr_pool_t *scratch_pool)
1208 struct unlock_baton ub;
1209 apr_array_header_t *sorted_targets;
1210 apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
1211 apr_hash_index_t *hi;
1212 apr_pool_t *iterpool;
1213 svn_error_t *err, *cb_err = SVN_NO_ERROR;
1216 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1218 /* We need to have a username attached to the fs. */
1219 if (!fs->access_ctx || !fs->access_ctx->username)
1220 return SVN_FS__ERR_NO_USER(fs);
1222 for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
1224 const char *path = apr_hash_this_key(hi);
1225 const char *token = apr_hash_this_val(hi);
1228 path = svn_fspath__canonicalize(path, result_pool);
1229 other = svn_hash_gets(canonical_targets, path);
1232 svn_hash_sets(canonical_targets, path, token);
1235 sorted_targets = svn_sort__hash(canonical_targets,
1236 svn_sort_compare_items_as_paths,
1240 ub.targets = sorted_targets;
1241 ub.infos = apr_array_make(result_pool, sorted_targets->nelts,
1242 sizeof(struct unlock_info_t));
1243 ub.skip_check = FALSE;
1244 ub.break_lock = break_lock;
1245 ub.result_pool = result_pool;
1247 iterpool = svn_pool_create(scratch_pool);
1248 err = svn_fs_fs__with_write_lock(fs, unlock_body, &ub, iterpool);
1249 for (i = 0; i < ub.infos->nelts; ++i)
1251 struct unlock_info_t *info = &APR_ARRAY_IDX(ub.infos, i,
1252 struct unlock_info_t);
1253 svn_pool_clear(iterpool);
1254 if (!cb_err && lock_callback)
1256 if (!info->done && !info->fs_err)
1257 info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
1258 0, _("Failed to unlock '%s'"),
1260 cb_err = lock_callback(lock_baton, info->path, NULL, info->fs_err,
1263 svn_error_clear(info->fs_err);
1265 svn_pool_destroy(iterpool);
1268 svn_error_compose(err, cb_err);
1272 return svn_error_trace(err);
1277 svn_fs_fs__get_lock(svn_lock_t **lock_p,
1282 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1283 path = svn_fs__canonicalize_abspath(path, pool);
1284 return get_lock_helper(fs, lock_p, path, FALSE, pool);
1288 /* Baton for get_locks_filter_func(). */
1289 typedef struct get_locks_filter_baton_t
1292 svn_depth_t requested_depth;
1293 svn_fs_get_locks_callback_t get_locks_func;
1294 void *get_locks_baton;
1296 } get_locks_filter_baton_t;
1299 /* A wrapper for the GET_LOCKS_FUNC passed to svn_fs_fs__get_locks()
1300 which filters out locks on paths that aren't within
1301 BATON->requested_depth of BATON->path before called
1302 BATON->get_locks_func() with BATON->get_locks_baton.
1304 NOTE: See issue #3660 for details about how the FSFS lock
1305 management code is inconsistent. Until that inconsistency is
1306 resolved, we take this filtering approach rather than honoring
1307 depth requests closer to the crawling code. In other words, once
1308 we decide how to resolve issue #3660, there might be a more
1309 performant way to honor the depth passed to svn_fs_fs__get_locks(). */
1310 static svn_error_t *
1311 get_locks_filter_func(void *baton,
1315 get_locks_filter_baton_t *b = baton;
1317 /* Filter out unwanted paths. Since Subversion only allows
1318 locks on files, we can treat depth=immediates the same as
1319 depth=files for filtering purposes. Meaning, we'll keep
1322 a) its path is the very path we queried, or
1323 b) we've asked for a fully recursive answer, or
1324 c) we've asked for depth=files or depth=immediates, and this
1325 lock is on an immediate child of our query path.
1327 if ((strcmp(b->path, lock->path) == 0)
1328 || (b->requested_depth == svn_depth_infinity))
1330 SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1332 else if ((b->requested_depth == svn_depth_files) ||
1333 (b->requested_depth == svn_depth_immediates))
1335 const char *rel_uri = svn_fspath__skip_ancestor(b->path, lock->path);
1336 if (rel_uri && (svn_path_component_count(rel_uri) == 1))
1337 SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1340 return SVN_NO_ERROR;
1344 svn_fs_fs__get_locks(svn_fs_t *fs,
1347 svn_fs_get_locks_callback_t get_locks_func,
1348 void *get_locks_baton,
1351 const char *digest_path;
1352 get_locks_filter_baton_t glfb;
1354 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1355 path = svn_fs__canonicalize_abspath(path, pool);
1358 glfb.requested_depth = depth;
1359 glfb.get_locks_func = get_locks_func;
1360 glfb.get_locks_baton = get_locks_baton;
1362 /* Get the top digest path in our tree of interest, and then walk it. */
1363 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
1364 SVN_ERR(walk_locks(fs, digest_path, get_locks_filter_func, &glfb,
1366 return SVN_NO_ERROR;