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 * ====================================================================
24 #include "svn_pools.h"
25 #include "svn_error.h"
26 #include "svn_dirent_uri.h"
34 #include <apr_file_io.h>
35 #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 "svn_private_config.h"
46 /* Names of hash keys used to store a lock for writing to disk. */
47 #define PATH_KEY "path"
48 #define TOKEN_KEY "token"
49 #define OWNER_KEY "owner"
50 #define CREATION_DATE_KEY "creation_date"
51 #define EXPIRATION_DATE_KEY "expiration_date"
52 #define COMMENT_KEY "comment"
53 #define IS_DAV_COMMENT_KEY "is_dav_comment"
54 #define CHILDREN_KEY "children"
56 /* Number of characters from the head of a digest file name used to
57 calculate a subdirectory in which to drop that file. */
58 #define DIGEST_SUBDIR_LEN 3
62 /*** Generic helper functions. ***/
64 /* Set *DIGEST to the MD5 hash of STR. */
66 make_digest(const char **digest,
70 svn_checksum_t *checksum;
72 SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, str, strlen(str), pool));
74 *digest = svn_checksum_to_cstring_display(checksum, pool);
79 /* Set the value of KEY (whose size is KEY_LEN, or APR_HASH_KEY_STRING
80 if unknown) to an svn_string_t-ized version of VALUE (whose size is
81 VALUE_LEN, or APR_HASH_KEY_STRING if unknown) in HASH. The value
82 will be allocated in POOL; KEY will not be duped. If either KEY or VALUE
83 is NULL, this function will do nothing. */
85 hash_store(apr_hash_t *hash,
89 apr_ssize_t value_len,
94 if (value_len == APR_HASH_KEY_STRING)
95 value_len = strlen(value);
96 apr_hash_set(hash, key, key_len,
97 svn_string_ncreate(value, value_len, pool));
101 /* Fetch the value of KEY from HASH, returning only the cstring data
102 of that value (if it exists). */
104 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),
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,
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 svn__apr_hash_index_key(hi),
213 svn__apr_hash_index_klen(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;
259 *children_p = apr_hash_make(pool);
261 err = svn_stream_open_readonly(&stream, digest_path, pool, pool);
262 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
264 svn_error_clear(err);
269 /* If our caller doesn't care about anything but the presence of the
271 if (! (lock_p || children_p))
272 return svn_stream_close(stream);
274 hash = apr_hash_make(pool);
275 if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool)))
277 svn_error_clear(svn_stream_close(stream));
278 return svn_error_createf(err->apr_err,
280 _("Can't parse lock/entries hashfile '%s'"),
281 svn_dirent_local_style(digest_path, pool));
283 SVN_ERR(svn_stream_close(stream));
285 /* If our caller cares, see if we have a lock path in our hash. If
286 so, we'll assume we have a lock here. */
287 val = hash_fetch(hash, PATH_KEY, pool);
290 const char *path = val;
292 /* Create our lock and load it up. */
293 lock = svn_lock_create(pool);
296 if (! ((lock->token = hash_fetch(hash, TOKEN_KEY, pool))))
297 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
299 if (! ((lock->owner = hash_fetch(hash, OWNER_KEY, pool))))
300 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
302 if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY, pool))))
303 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
304 lock->is_dav_comment = (val[0] == '1');
306 if (! ((val = hash_fetch(hash, CREATION_DATE_KEY, pool))))
307 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
308 SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool));
310 if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY, pool)))
311 SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool));
313 lock->comment = hash_fetch(hash, COMMENT_KEY, pool);
318 /* If our caller cares, see if we have any children for this path. */
319 val = hash_fetch(hash, CHILDREN_KEY, pool);
320 if (val && children_p)
322 apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool);
325 for (i = 0; i < kiddos->nelts; i++)
327 svn_hash_sets(*children_p, APR_ARRAY_IDX(kiddos, i, const char *),
336 /*** Lock helper functions (path here are still FS paths, not on-disk
337 schema-supporting paths) ***/
340 /* Write LOCK in FS to the actual OS filesystem.
342 Use PERMS_REFERENCE for the permissions of any digest files.
344 Note: this takes an FS_PATH because it's called from the hotcopy logic.
347 set_lock(const char *fs_path,
349 const char *perms_reference,
352 svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
353 const char *lock_digest_path = NULL;
356 SVN_ERR_ASSERT(lock);
358 /* Iterate in reverse, creating the lock for LOCK->path, and then
359 just adding entries for its parent, until we reach a parent
360 that's already listed in *its* parent. */
361 subpool = svn_pool_create(pool);
364 const char *digest_path, *digest_file;
365 apr_hash_t *this_children;
366 svn_lock_t *this_lock;
368 svn_pool_clear(subpool);
370 /* Calculate the DIGEST_PATH for the currently FS path, and then
371 get its DIGEST_FILE basename. */
372 SVN_ERR(digest_path_from_path(&digest_path, fs_path, this_path->data,
374 digest_file = svn_dirent_basename(digest_path, subpool);
376 SVN_ERR(read_digest_file(&this_children, &this_lock, fs_path,
377 digest_path, subpool));
379 /* We're either writing a new lock (first time through only) or
380 a new entry (every time but the first). */
385 lock_digest_path = apr_pstrdup(pool, digest_file);
389 /* If we already have an entry for this path, we're done. */
390 if (svn_hash_gets(this_children, lock_digest_path))
392 svn_hash_sets(this_children, lock_digest_path, (void *)1);
394 SVN_ERR(write_digest_file(this_children, this_lock, fs_path,
395 digest_path, perms_reference, subpool));
397 /* Prep for next iteration, or bail if we're done. */
398 if (svn_fspath__is_root(this_path->data, this_path->len))
400 svn_stringbuf_set(this_path,
401 svn_fspath__dirname(this_path->data, subpool));
404 svn_pool_destroy(subpool);
408 /* Delete LOCK from FS in the actual OS filesystem. */
410 delete_lock(svn_fs_t *fs,
414 svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
415 const char *child_to_kill = NULL;
418 SVN_ERR_ASSERT(lock);
420 /* Iterate in reverse, deleting the lock for LOCK->path, and then
421 deleting its entry as it appears in each of its parents. */
422 subpool = svn_pool_create(pool);
425 const char *digest_path, *digest_file;
426 apr_hash_t *this_children;
427 svn_lock_t *this_lock;
429 svn_pool_clear(subpool);
431 /* Calculate the DIGEST_PATH for the currently FS path, and then
432 get its DIGEST_FILE basename. */
433 SVN_ERR(digest_path_from_path(&digest_path, fs->path, this_path->data,
435 digest_file = svn_dirent_basename(digest_path, subpool);
437 SVN_ERR(read_digest_file(&this_children, &this_lock, fs->path,
438 digest_path, subpool));
440 /* Delete the lock (first time through only). */
445 child_to_kill = apr_pstrdup(pool, digest_file);
449 svn_hash_sets(this_children, child_to_kill, NULL);
451 if (! (this_lock || apr_hash_count(this_children) != 0))
453 /* Special case: no goodz, no file. And remember to nix
454 the entry for it in its parent. */
455 SVN_ERR(svn_io_remove_file2(digest_path, FALSE, subpool));
459 const char *rev_0_path;
460 SVN_ERR(svn_fs_fs__path_rev_absolute(&rev_0_path, fs, 0, pool));
461 SVN_ERR(write_digest_file(this_children, this_lock, fs->path,
462 digest_path, rev_0_path, subpool));
465 /* Prep for next iteration, or bail if we're done. */
466 if (svn_fspath__is_root(this_path->data, this_path->len))
468 svn_stringbuf_set(this_path,
469 svn_fspath__dirname(this_path->data, subpool));
472 svn_pool_destroy(subpool);
476 /* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
477 TRUE if the caller (or one of its callers) has taken out the
478 repository-wide write lock, FALSE otherwise. If MUST_EXIST is
479 not set, the function will simply return NULL in *LOCK_P instead
480 of creating an SVN_FS__ERR_NO_SUCH_LOCK error in case the lock
481 was not found (much faster). Use POOL for allocations. */
483 get_lock(svn_lock_t **lock_p,
486 svn_boolean_t have_write_lock,
487 svn_boolean_t must_exist,
490 svn_lock_t *lock = NULL;
491 const char *digest_path;
492 svn_node_kind_t kind;
494 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
495 SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
498 if (kind != svn_node_none)
499 SVN_ERR(read_digest_file(NULL, &lock, fs->path, digest_path, pool));
502 return must_exist ? SVN_FS__ERR_NO_SUCH_LOCK(fs, path) : SVN_NO_ERROR;
504 /* Don't return an expired lock. */
505 if (lock->expiration_date && (apr_time_now() > lock->expiration_date))
507 /* Only remove the lock if we have the write lock.
508 Read operations shouldn't change the filesystem. */
510 SVN_ERR(delete_lock(fs, lock, pool));
511 return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
519 /* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
520 TRUE if the caller (or one of its callers) has taken out the
521 repository-wide write lock, FALSE otherwise. Use POOL for
524 get_lock_helper(svn_fs_t *fs,
527 svn_boolean_t have_write_lock,
533 err = get_lock(&lock, fs, path, have_write_lock, FALSE, pool);
535 /* We've deliberately decided that this function doesn't tell the
536 caller *why* the lock is unavailable. */
537 if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
538 || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)))
540 svn_error_clear(err);
552 /* Baton for locks_walker(). */
553 struct walk_locks_baton {
554 svn_fs_get_locks_callback_t get_locks_func;
555 void *get_locks_baton;
559 /* Implements walk_digests_callback_t. */
561 locks_walker(void *baton,
563 const char *digest_path,
564 apr_hash_t *children,
566 svn_boolean_t have_write_lock,
569 struct walk_locks_baton *wlb = baton;
573 /* Don't report an expired lock. */
574 if (lock->expiration_date == 0
575 || (apr_time_now() <= lock->expiration_date))
577 if (wlb->get_locks_func)
578 SVN_ERR(wlb->get_locks_func(wlb->get_locks_baton, lock, pool));
582 /* Only remove the lock if we have the write lock.
583 Read operations shouldn't change the filesystem. */
585 SVN_ERR(delete_lock(wlb->fs, lock, pool));
592 /* Callback type for walk_digest_files().
594 * CHILDREN and LOCK come from a read_digest_file(digest_path) call.
596 typedef svn_error_t *(*walk_digests_callback_t)(void *baton,
598 const char *digest_path,
599 apr_hash_t *children,
601 svn_boolean_t have_write_lock,
604 /* A recursive function that calls WALK_DIGESTS_FUNC/WALK_DIGESTS_BATON for
605 all lock digest files in and under PATH in FS.
606 HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
607 has the FS write lock. */
609 walk_digest_files(const char *fs_path,
610 const char *digest_path,
611 walk_digests_callback_t walk_digests_func,
612 void *walk_digests_baton,
613 svn_boolean_t have_write_lock,
616 apr_hash_index_t *hi;
617 apr_hash_t *children;
621 /* First, send up any locks in the current digest file. */
622 SVN_ERR(read_digest_file(&children, &lock, fs_path, digest_path, pool));
624 SVN_ERR(walk_digests_func(walk_digests_baton, fs_path, digest_path,
626 have_write_lock, pool));
628 /* Now, recurse on this thing's child entries (if any; bail otherwise). */
629 if (! apr_hash_count(children))
631 subpool = svn_pool_create(pool);
632 for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
634 const char *digest = svn__apr_hash_index_key(hi);
635 svn_pool_clear(subpool);
636 SVN_ERR(walk_digest_files
637 (fs_path, digest_path_from_digest(fs_path, digest, subpool),
638 walk_digests_func, walk_digests_baton, have_write_lock, subpool));
640 svn_pool_destroy(subpool);
644 /* A recursive function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
645 all locks in and under PATH in FS.
646 HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
647 has the FS write lock. */
649 walk_locks(svn_fs_t *fs,
650 const char *digest_path,
651 svn_fs_get_locks_callback_t get_locks_func,
652 void *get_locks_baton,
653 svn_boolean_t have_write_lock,
656 struct walk_locks_baton wlb;
658 wlb.get_locks_func = get_locks_func;
659 wlb.get_locks_baton = get_locks_baton;
661 SVN_ERR(walk_digest_files(fs->path, digest_path, locks_walker, &wlb,
662 have_write_lock, pool));
667 /* Utility function: verify that a lock can be used. Interesting
668 errors returned from this function:
670 SVN_ERR_FS_NO_USER: No username attached to FS.
671 SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner.
672 SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK.
675 verify_lock(svn_fs_t *fs,
679 if ((! fs->access_ctx) || (! fs->access_ctx->username))
680 return svn_error_createf
681 (SVN_ERR_FS_NO_USER, NULL,
682 _("Cannot verify lock on path '%s'; no username available"),
685 else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
686 return svn_error_createf
687 (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
688 _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
689 fs->access_ctx->username, lock->path, lock->owner);
691 else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
692 return svn_error_createf
693 (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
694 _("Cannot verify lock on path '%s'; no matching lock-token available"),
701 /* This implements the svn_fs_get_locks_callback_t interface, where
702 BATON is just an svn_fs_t object. */
704 get_locks_callback(void *baton,
708 return verify_lock(baton, lock, pool);
712 /* The main routine for lock enforcement, used throughout libsvn_fs_fs. */
714 svn_fs_fs__allow_locked_operation(const char *path,
716 svn_boolean_t recurse,
717 svn_boolean_t have_write_lock,
720 path = svn_fs__canonicalize_abspath(path, pool);
723 /* Discover all locks at or below the path. */
724 const char *digest_path;
725 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
726 SVN_ERR(walk_locks(fs, digest_path, get_locks_callback,
727 fs, have_write_lock, pool));
731 /* Discover and verify any lock attached to the path. */
733 SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock, pool));
735 SVN_ERR(verify_lock(fs, lock, pool));
740 /* Baton used for lock_body below. */
747 svn_boolean_t is_dav_comment;
748 apr_time_t expiration_date;
749 svn_revnum_t current_rev;
750 svn_boolean_t steal_lock;
755 /* This implements the svn_fs_fs__with_write_lock() 'body' callback
756 type, and assumes that the write lock is held.
757 BATON is a 'struct lock_baton *'. */
759 lock_body(void *baton, apr_pool_t *pool)
761 struct lock_baton *lb = baton;
762 svn_node_kind_t kind;
763 svn_lock_t *existing_lock;
766 svn_revnum_t youngest;
767 const char *rev_0_path;
769 /* Until we implement directory locks someday, we only allow locks
770 on files or non-existent paths. */
771 /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
772 library dependencies, which are not portable. */
773 SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
774 SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
775 SVN_ERR(svn_fs_fs__check_path(&kind, root, lb->path, pool));
776 if (kind == svn_node_dir)
777 return SVN_FS__ERR_NOT_FILE(lb->fs, lb->path);
779 /* While our locking implementation easily supports the locking of
780 nonexistent paths, we deliberately choose not to allow such madness. */
781 if (kind == svn_node_none)
783 if (SVN_IS_VALID_REVNUM(lb->current_rev))
784 return svn_error_createf(
785 SVN_ERR_FS_OUT_OF_DATE, NULL,
786 _("Path '%s' doesn't exist in HEAD revision"),
789 return svn_error_createf(
790 SVN_ERR_FS_NOT_FOUND, NULL,
791 _("Path '%s' doesn't exist in HEAD revision"),
795 /* We need to have a username attached to the fs. */
796 if (!lb->fs->access_ctx || !lb->fs->access_ctx->username)
797 return SVN_FS__ERR_NO_USER(lb->fs);
799 /* Is the caller attempting to lock an out-of-date working file? */
800 if (SVN_IS_VALID_REVNUM(lb->current_rev))
802 svn_revnum_t created_rev;
803 SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, lb->path,
806 /* SVN_INVALID_REVNUM means the path doesn't exist. So
807 apparently somebody is trying to lock something in their
808 working copy, but somebody else has deleted the thing
809 from HEAD. That counts as being 'out of date'. */
810 if (! SVN_IS_VALID_REVNUM(created_rev))
811 return svn_error_createf
812 (SVN_ERR_FS_OUT_OF_DATE, NULL,
813 _("Path '%s' doesn't exist in HEAD revision"), lb->path);
815 if (lb->current_rev < created_rev)
816 return svn_error_createf
817 (SVN_ERR_FS_OUT_OF_DATE, NULL,
818 _("Lock failed: newer version of '%s' exists"), lb->path);
821 /* If the caller provided a TOKEN, we *really* need to see
822 if a lock already exists with that token, and if so, verify that
823 the lock's path matches PATH. Otherwise we run the risk of
824 breaking the 1-to-1 mapping of lock tokens to locked paths. */
825 /* ### TODO: actually do this check. This is tough, because the
826 schema doesn't supply a lookup-by-token mechanism. */
828 /* Is the path already locked?
830 Note that this next function call will automatically ignore any
831 errors about {the path not existing as a key, the path's token
832 not existing as a key, the lock just having been expired}. And
833 that's totally fine. Any of these three errors are perfectly
834 acceptable to ignore; it means that the path is now free and
835 clear for locking, because the fsfs funcs just cleared out both
836 of the tables for us. */
837 SVN_ERR(get_lock_helper(lb->fs, &existing_lock, lb->path, TRUE, pool));
840 if (! lb->steal_lock)
842 /* Sorry, the path is already locked. */
843 return SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
847 /* STEAL_LOCK was passed, so fs_username is "stealing" the
848 lock from lock->owner. Destroy the existing lock. */
849 SVN_ERR(delete_lock(lb->fs, existing_lock, pool));
853 /* Create our new lock, and add it to the tables.
854 Ensure that the lock is created in the correct pool. */
855 lock = svn_lock_create(lb->pool);
857 lock->token = apr_pstrdup(lb->pool, lb->token);
859 SVN_ERR(svn_fs_fs__generate_lock_token(&(lock->token), lb->fs,
861 lock->path = apr_pstrdup(lb->pool, lb->path);
862 lock->owner = apr_pstrdup(lb->pool, lb->fs->access_ctx->username);
863 lock->comment = apr_pstrdup(lb->pool, lb->comment);
864 lock->is_dav_comment = lb->is_dav_comment;
865 lock->creation_date = apr_time_now();
866 lock->expiration_date = lb->expiration_date;
867 SVN_ERR(svn_fs_fs__path_rev_absolute(&rev_0_path, lb->fs, 0, pool));
868 SVN_ERR(set_lock(lb->fs->path, lock, rev_0_path, pool));
874 /* Baton used for unlock_body below. */
875 struct unlock_baton {
879 svn_boolean_t break_lock;
882 /* This implements the svn_fs_fs__with_write_lock() 'body' callback
883 type, and assumes that the write lock is held.
884 BATON is a 'struct unlock_baton *'. */
886 unlock_body(void *baton, apr_pool_t *pool)
888 struct unlock_baton *ub = baton;
891 /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
892 SVN_ERR(get_lock(&lock, ub->fs, ub->path, TRUE, TRUE, pool));
894 /* Unless breaking the lock, we do some checks. */
895 if (! ub->break_lock)
897 /* Sanity check: the incoming token should match lock->token. */
898 if (strcmp(ub->token, lock->token) != 0)
899 return SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, lock->path);
901 /* There better be a username attached to the fs. */
902 if (! (ub->fs->access_ctx && ub->fs->access_ctx->username))
903 return SVN_FS__ERR_NO_USER(ub->fs);
905 /* And that username better be the same as the lock's owner. */
906 if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
907 return SVN_FS__ERR_LOCK_OWNER_MISMATCH(
908 ub->fs, ub->fs->access_ctx->username, lock->owner);
911 /* Remove lock and lock token files. */
912 return delete_lock(ub->fs, lock, pool);
916 /*** Public API implementations ***/
919 svn_fs_fs__lock(svn_lock_t **lock_p,
924 svn_boolean_t is_dav_comment,
925 apr_time_t expiration_date,
926 svn_revnum_t current_rev,
927 svn_boolean_t steal_lock,
930 struct lock_baton lb;
932 SVN_ERR(svn_fs__check_fs(fs, TRUE));
933 path = svn_fs__canonicalize_abspath(path, pool);
939 lb.comment = comment;
940 lb.is_dav_comment = is_dav_comment;
941 lb.expiration_date = expiration_date;
942 lb.current_rev = current_rev;
943 lb.steal_lock = steal_lock;
946 return svn_fs_fs__with_write_lock(fs, lock_body, &lb, pool);
951 svn_fs_fs__generate_lock_token(const char **token,
955 SVN_ERR(svn_fs__check_fs(fs, TRUE));
957 /* Notice that 'fs' is currently unused. But perhaps someday, we'll
958 want to use the fs UUID + some incremented number? For now, we
959 generate a URI that matches the DAV RFC. We could change this to
960 some other URI scheme someday, if we wish. */
961 *token = apr_pstrcat(pool, "opaquelocktoken:",
962 svn_uuid_generate(pool), (char *)NULL);
968 svn_fs_fs__unlock(svn_fs_t *fs,
971 svn_boolean_t break_lock,
974 struct unlock_baton ub;
976 SVN_ERR(svn_fs__check_fs(fs, TRUE));
977 path = svn_fs__canonicalize_abspath(path, pool);
982 ub.break_lock = break_lock;
984 return svn_fs_fs__with_write_lock(fs, unlock_body, &ub, pool);
989 svn_fs_fs__get_lock(svn_lock_t **lock_p,
994 SVN_ERR(svn_fs__check_fs(fs, TRUE));
995 path = svn_fs__canonicalize_abspath(path, pool);
996 return get_lock_helper(fs, lock_p, path, FALSE, pool);
1000 /* Baton for get_locks_filter_func(). */
1001 typedef struct get_locks_filter_baton_t
1004 svn_depth_t requested_depth;
1005 svn_fs_get_locks_callback_t get_locks_func;
1006 void *get_locks_baton;
1008 } get_locks_filter_baton_t;
1011 /* A wrapper for the GET_LOCKS_FUNC passed to svn_fs_fs__get_locks()
1012 which filters out locks on paths that aren't within
1013 BATON->requested_depth of BATON->path before called
1014 BATON->get_locks_func() with BATON->get_locks_baton.
1016 NOTE: See issue #3660 for details about how the FSFS lock
1017 management code is inconsistent. Until that inconsistency is
1018 resolved, we take this filtering approach rather than honoring
1019 depth requests closer to the crawling code. In other words, once
1020 we decide how to resolve issue #3660, there might be a more
1021 performant way to honor the depth passed to svn_fs_fs__get_locks(). */
1022 static svn_error_t *
1023 get_locks_filter_func(void *baton,
1027 get_locks_filter_baton_t *b = baton;
1029 /* Filter out unwanted paths. Since Subversion only allows
1030 locks on files, we can treat depth=immediates the same as
1031 depth=files for filtering purposes. Meaning, we'll keep
1034 a) its path is the very path we queried, or
1035 b) we've asked for a fully recursive answer, or
1036 c) we've asked for depth=files or depth=immediates, and this
1037 lock is on an immediate child of our query path.
1039 if ((strcmp(b->path, lock->path) == 0)
1040 || (b->requested_depth == svn_depth_infinity))
1042 SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1044 else if ((b->requested_depth == svn_depth_files) ||
1045 (b->requested_depth == svn_depth_immediates))
1047 const char *rel_uri = svn_fspath__skip_ancestor(b->path, lock->path);
1048 if (rel_uri && (svn_path_component_count(rel_uri) == 1))
1049 SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1052 return SVN_NO_ERROR;
1056 svn_fs_fs__get_locks(svn_fs_t *fs,
1059 svn_fs_get_locks_callback_t get_locks_func,
1060 void *get_locks_baton,
1063 const char *digest_path;
1064 get_locks_filter_baton_t glfb;
1066 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1067 path = svn_fs__canonicalize_abspath(path, pool);
1070 glfb.requested_depth = depth;
1071 glfb.get_locks_func = get_locks_func;
1072 glfb.get_locks_baton = get_locks_baton;
1074 /* Get the top digest path in our tree of interest, and then walk it. */
1075 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
1076 SVN_ERR(walk_locks(fs, digest_path, get_locks_filter_func, &glfb,
1078 return SVN_NO_ERROR;