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>
39 #include "transaction.h"
41 #include "../libsvn_fs/fs-loader.h"
43 #include "private/svn_fs_util.h"
44 #include "private/svn_fspath.h"
45 #include "private/svn_sorts_private.h"
46 #include "svn_private_config.h"
48 /* Names of hash keys used to store a lock for writing to disk. */
49 #define PATH_KEY "path"
50 #define TOKEN_KEY "token"
51 #define OWNER_KEY "owner"
52 #define CREATION_DATE_KEY "creation_date"
53 #define EXPIRATION_DATE_KEY "expiration_date"
54 #define COMMENT_KEY "comment"
55 #define IS_DAV_COMMENT_KEY "is_dav_comment"
56 #define CHILDREN_KEY "children"
58 /* Number of characters from the head of a digest file name used to
59 calculate a subdirectory in which to drop that file. */
60 #define DIGEST_SUBDIR_LEN 3
64 /*** Generic helper functions. ***/
66 /* Set *DIGEST to the MD5 hash of STR. */
68 make_digest(const char **digest,
72 svn_checksum_t *checksum;
74 SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, str, strlen(str), pool));
76 *digest = svn_checksum_to_cstring_display(checksum, pool);
81 /* Set the value of KEY (whose size is KEY_LEN, or APR_HASH_KEY_STRING
82 if unknown) to an svn_string_t-ized version of VALUE (whose size is
83 VALUE_LEN, or APR_HASH_KEY_STRING if unknown) in HASH. The value
84 will be allocated in POOL; KEY will not be duped. If either KEY or VALUE
85 is NULL, this function will do nothing. */
87 hash_store(apr_hash_t *hash,
91 apr_ssize_t value_len,
96 if (value_len == APR_HASH_KEY_STRING)
97 value_len = strlen(value);
98 apr_hash_set(hash, key, key_len,
99 svn_string_ncreate(value, value_len, pool));
103 /* Fetch the value of KEY from HASH, returning only the cstring data
104 of that value (if it exists). */
106 hash_fetch(apr_hash_t *hash,
109 svn_string_t *str = svn_hash_gets(hash, key);
110 return str ? str->data : NULL;
114 /* SVN_ERR_FS_CORRUPT: the lockfile for PATH in FS is corrupt. */
116 err_corrupt_lockfile(const char *fs_path,
121 SVN_ERR_FS_CORRUPT, 0,
122 _("Corrupt lockfile for path '%s' in filesystem '%s'"),
127 /*** Digest file handling functions. ***/
129 /* Return the path of the lock/entries file for which DIGEST is the
130 hashed repository relative path. */
132 digest_path_from_digest(const char *fs_path,
136 return svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
137 apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN),
138 digest, SVN_VA_NULL);
142 /* Set *DIGEST_PATH to the path to the lock/entries digest file associate
143 with PATH, where PATH is the path to the lock file or lock entries file
146 digest_path_from_path(const char **digest_path,
152 SVN_ERR(make_digest(&digest, path, pool));
153 *digest_path = svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
154 apr_pstrmemdup(pool, digest,
156 digest, SVN_VA_NULL);
161 /* Write to DIGEST_PATH a representation of CHILDREN (which may be
162 empty, if the versioned path in FS represented by DIGEST_PATH has
163 no children) and LOCK (which may be NULL if that versioned path is
164 lock itself locked). Set the permissions of DIGEST_PATH to those of
165 PERMS_REFERENCE. Use POOL for temporary allocations.
168 write_digest_file(apr_hash_t *children,
171 const char *digest_path,
172 const char *perms_reference,
173 apr_pool_t *scratch_pool)
175 svn_error_t *err = SVN_NO_ERROR;
176 svn_stream_t *stream;
177 apr_hash_index_t *hi;
178 apr_hash_t *hash = apr_hash_make(scratch_pool);
179 const char *tmp_path;
181 SVN_ERR(svn_fs_x__ensure_dir_exists(svn_dirent_join(fs_path, PATH_LOCKS_DIR,
183 fs_path, scratch_pool));
184 SVN_ERR(svn_fs_x__ensure_dir_exists(svn_dirent_dirname(digest_path,
186 fs_path, scratch_pool));
190 const char *creation_date = NULL, *expiration_date = NULL;
191 if (lock->creation_date)
192 creation_date = svn_time_to_cstring(lock->creation_date,
194 if (lock->expiration_date)
195 expiration_date = svn_time_to_cstring(lock->expiration_date,
198 hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1,
199 lock->path, APR_HASH_KEY_STRING, scratch_pool);
200 hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1,
201 lock->token, APR_HASH_KEY_STRING, scratch_pool);
202 hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1,
203 lock->owner, APR_HASH_KEY_STRING, scratch_pool);
204 hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1,
205 lock->comment, APR_HASH_KEY_STRING, scratch_pool);
206 hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1,
207 lock->is_dav_comment ? "1" : "0", 1, scratch_pool);
208 hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1,
209 creation_date, APR_HASH_KEY_STRING, scratch_pool);
210 hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1,
211 expiration_date, APR_HASH_KEY_STRING, scratch_pool);
213 if (apr_hash_count(children))
215 svn_stringbuf_t *children_list
216 = svn_stringbuf_create_empty(scratch_pool);
217 for (hi = apr_hash_first(scratch_pool, children);
219 hi = apr_hash_next(hi))
221 svn_stringbuf_appendbytes(children_list,
222 apr_hash_this_key(hi),
223 apr_hash_this_key_len(hi));
224 svn_stringbuf_appendbyte(children_list, '\n');
226 hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1,
227 children_list->data, children_list->len, scratch_pool);
230 SVN_ERR(svn_stream_open_unique(&stream, &tmp_path,
231 svn_dirent_dirname(digest_path,
233 svn_io_file_del_none, scratch_pool,
235 if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR,
238 err = svn_error_compose_create(err, svn_stream_close(stream));
239 return svn_error_createf(err->apr_err,
241 _("Cannot write lock/entries hashfile '%s'"),
242 svn_dirent_local_style(tmp_path,
246 SVN_ERR(svn_stream_close(stream));
247 SVN_ERR(svn_io_file_rename2(tmp_path, digest_path, FALSE, scratch_pool));
248 SVN_ERR(svn_io_copy_perms(perms_reference, digest_path, scratch_pool));
253 /* Parse the file at DIGEST_PATH, populating the lock LOCK_P in that
254 file (if it exists, and if *LOCK_P is non-NULL) and the hash of
255 CHILDREN_P (if any exist, and if *CHILDREN_P is non-NULL). Use POOL
256 for all allocations. */
258 read_digest_file(apr_hash_t **children_p,
261 const char *digest_path,
264 svn_error_t *err = SVN_NO_ERROR;
267 svn_stream_t *stream;
269 svn_node_kind_t kind;
274 *children_p = apr_hash_make(pool);
276 SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
277 if (kind == svn_node_none)
280 /* If our caller doesn't care about anything but the presence of the
282 if (kind == svn_node_file && !lock_p && !children_p)
285 SVN_ERR(svn_stream_open_readonly(&stream, digest_path, pool, pool));
287 hash = apr_hash_make(pool);
288 if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool)))
290 err = svn_error_compose_create(err, svn_stream_close(stream));
291 return svn_error_createf(err->apr_err,
293 _("Can't parse lock/entries hashfile '%s'"),
294 svn_dirent_local_style(digest_path, pool));
296 SVN_ERR(svn_stream_close(stream));
298 /* If our caller cares, see if we have a lock path in our hash. If
299 so, we'll assume we have a lock here. */
300 val = hash_fetch(hash, PATH_KEY);
303 const char *path = val;
305 /* Create our lock and load it up. */
306 lock = svn_lock_create(pool);
309 if (! ((lock->token = hash_fetch(hash, TOKEN_KEY))))
310 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
312 if (! ((lock->owner = hash_fetch(hash, OWNER_KEY))))
313 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
315 if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY))))
316 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
317 lock->is_dav_comment = (val[0] == '1');
319 if (! ((val = hash_fetch(hash, CREATION_DATE_KEY))))
320 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
321 SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool));
323 if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY)))
324 SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool));
326 lock->comment = hash_fetch(hash, COMMENT_KEY);
331 /* If our caller cares, see if we have any children for this path. */
332 val = hash_fetch(hash, CHILDREN_KEY);
333 if (val && children_p)
335 apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool);
338 for (i = 0; i < kiddos->nelts; i++)
340 svn_hash_sets(*children_p, APR_ARRAY_IDX(kiddos, i, const char *),
349 /*** Lock helper functions (path here are still FS paths, not on-disk
350 schema-supporting paths) ***/
353 /* Write LOCK in FS to the actual OS filesystem.
355 Use PERMS_REFERENCE for the permissions of any digest files.
358 set_lock(const char *fs_path,
360 const char *perms_reference,
361 apr_pool_t *scratch_pool)
363 const char *digest_path;
364 apr_hash_t *children;
366 SVN_ERR(digest_path_from_path(&digest_path, fs_path, lock->path,
369 /* We could get away without reading the file as children should
370 always come back empty. */
371 SVN_ERR(read_digest_file(&children, NULL, fs_path, digest_path,
374 SVN_ERR(write_digest_file(children, lock, fs_path, digest_path,
375 perms_reference, scratch_pool));
381 delete_lock(const char *fs_path,
383 apr_pool_t *scratch_pool)
385 const char *digest_path;
387 SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, scratch_pool));
389 SVN_ERR(svn_io_remove_file2(digest_path, TRUE, scratch_pool));
395 add_to_digest(const char *fs_path,
396 apr_array_header_t *paths,
397 const char *index_path,
398 const char *perms_reference,
399 apr_pool_t *scratch_pool)
401 const char *index_digest_path;
402 apr_hash_t *children;
405 unsigned int original_count;
407 SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path,
409 SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path,
412 original_count = apr_hash_count(children);
414 for (i = 0; i < paths->nelts; ++i)
416 const char *path = APR_ARRAY_IDX(paths, i, const char *);
417 const char *digest_path, *digest_file;
419 SVN_ERR(digest_path_from_path(&digest_path, fs_path, path,
421 digest_file = svn_dirent_basename(digest_path, NULL);
422 svn_hash_sets(children, digest_file, (void *)1);
425 if (apr_hash_count(children) != original_count)
426 SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
427 perms_reference, scratch_pool));
433 delete_from_digest(const char *fs_path,
434 apr_array_header_t *paths,
435 const char *index_path,
436 const char *perms_reference,
437 apr_pool_t *scratch_pool)
439 const char *index_digest_path;
440 apr_hash_t *children;
444 SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path,
446 SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path,
449 for (i = 0; i < paths->nelts; ++i)
451 const char *path = APR_ARRAY_IDX(paths, i, const char *);
452 const char *digest_path, *digest_file;
454 SVN_ERR(digest_path_from_path(&digest_path, fs_path, path,
456 digest_file = svn_dirent_basename(digest_path, NULL);
457 svn_hash_sets(children, digest_file, NULL);
460 if (apr_hash_count(children) || lock)
461 SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
462 perms_reference, scratch_pool));
464 SVN_ERR(svn_io_remove_file2(index_digest_path, TRUE, scratch_pool));
470 unlock_single(svn_fs_t *fs,
474 /* Check if LOCK has been already expired. */
475 static svn_boolean_t lock_expired(const svn_lock_t *lock)
477 return lock->expiration_date && (apr_time_now() > lock->expiration_date);
480 /* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
481 TRUE if the caller (or one of its callers) has taken out the
482 repository-wide write lock, FALSE otherwise. If MUST_EXIST is
483 not set, the function will simply return NULL in *LOCK_P instead
484 of creating an SVN_FS__ERR_NO_SUCH_LOCK error in case the lock
485 was not found (much faster). Use POOL for allocations. */
487 get_lock(svn_lock_t **lock_p,
490 svn_boolean_t have_write_lock,
491 svn_boolean_t must_exist,
494 svn_lock_t *lock = NULL;
495 const char *digest_path;
496 svn_node_kind_t kind;
498 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
499 SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
502 if (kind != svn_node_none)
503 SVN_ERR(read_digest_file(NULL, &lock, fs->path, digest_path, pool));
506 return must_exist ? SVN_FS__ERR_NO_SUCH_LOCK(fs, path) : SVN_NO_ERROR;
508 /* Don't return an expired lock. */
509 if (lock_expired(lock))
511 /* Only remove the lock if we have the write lock.
512 Read operations shouldn't change the filesystem. */
514 SVN_ERR(unlock_single(fs, lock, pool));
515 return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
523 /* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
524 TRUE if the caller (or one of its callers) has taken out the
525 repository-wide write lock, FALSE otherwise. Use POOL for
528 get_lock_helper(svn_fs_t *fs,
531 svn_boolean_t have_write_lock,
537 err = get_lock(&lock, fs, path, have_write_lock, FALSE, pool);
539 /* We've deliberately decided that this function doesn't tell the
540 caller *why* the lock is unavailable. */
541 if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
542 || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)))
544 svn_error_clear(err);
556 /* A function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
557 all locks in and under PATH in FS.
558 HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
559 has the FS write lock. */
561 walk_locks(svn_fs_t *fs,
562 const char *digest_path,
563 svn_fs_get_locks_callback_t get_locks_func,
564 void *get_locks_baton,
565 svn_boolean_t have_write_lock,
568 apr_hash_index_t *hi;
569 apr_hash_t *children;
573 /* First, send up any locks in the current digest file. */
574 SVN_ERR(read_digest_file(&children, &lock, fs->path, digest_path, pool));
576 if (lock && lock_expired(lock))
578 /* Only remove the lock if we have the write lock.
579 Read operations shouldn't change the filesystem. */
581 SVN_ERR(unlock_single(fs, lock, pool));
585 SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
588 /* Now, report all the child entries (if any; bail otherwise). */
589 if (! apr_hash_count(children))
591 subpool = svn_pool_create(pool);
592 for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
594 const char *digest = apr_hash_this_key(hi);
595 svn_pool_clear(subpool);
597 SVN_ERR(read_digest_file
598 (NULL, &lock, fs->path,
599 digest_path_from_digest(fs->path, digest, subpool), subpool));
601 if (lock && lock_expired(lock))
603 /* Only remove the lock if we have the write lock.
604 Read operations shouldn't change the filesystem. */
606 SVN_ERR(unlock_single(fs, lock, pool));
610 SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
613 svn_pool_destroy(subpool);
617 /* Utility function: verify that a lock can be used. Interesting
618 errors returned from this function:
620 SVN_ERR_FS_NO_USER: No username attached to FS.
621 SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner.
622 SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK.
625 verify_lock(svn_fs_t *fs,
628 if ((! fs->access_ctx) || (! fs->access_ctx->username))
629 return svn_error_createf
630 (SVN_ERR_FS_NO_USER, NULL,
631 _("Cannot verify lock on path '%s'; no username available"),
634 else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
635 return svn_error_createf
636 (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
637 _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
638 fs->access_ctx->username, lock->path, lock->owner);
640 else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
641 return svn_error_createf
642 (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
643 _("Cannot verify lock on path '%s'; no matching lock-token available"),
650 /* This implements the svn_fs_get_locks_callback_t interface, where
651 BATON is just an svn_fs_t object. */
653 get_locks_callback(void *baton,
657 return verify_lock(baton, lock);
661 /* The main routine for lock enforcement, used throughout libsvn_fs_x. */
663 svn_fs_x__allow_locked_operation(const char *path,
665 svn_boolean_t recurse,
666 svn_boolean_t have_write_lock,
667 apr_pool_t *scratch_pool)
669 path = svn_fs__canonicalize_abspath(path, scratch_pool);
672 /* Discover all locks at or below the path. */
673 const char *digest_path;
674 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path,
676 SVN_ERR(walk_locks(fs, digest_path, get_locks_callback,
677 fs, have_write_lock, scratch_pool));
681 /* Discover and verify any lock attached to the path. */
683 SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock,
686 SVN_ERR(verify_lock(fs, lock));
691 /* Helper function called from the lock and unlock code.
692 UPDATES is a map from "const char *" parent paths to "apr_array_header_t *"
693 arrays of child paths. For all of the parent paths of PATH this function
694 adds PATH to the corresponding array of child paths. */
696 schedule_index_update(apr_hash_t *updates,
698 apr_pool_t *scratch_pool)
700 apr_pool_t *hashpool = apr_hash_pool_get(updates);
701 const char *parent_path = path;
703 while (! svn_fspath__is_root(parent_path, strlen(parent_path)))
705 apr_array_header_t *children;
707 parent_path = svn_fspath__dirname(parent_path, scratch_pool);
708 children = svn_hash_gets(updates, parent_path);
712 children = apr_array_make(hashpool, 8, sizeof(const char *));
713 svn_hash_sets(updates, apr_pstrdup(hashpool, parent_path), children);
716 APR_ARRAY_PUSH(children, const char *) = path;
720 /* The effective arguments for lock_body() below. */
721 typedef struct lock_baton_t {
723 apr_array_header_t *targets;
724 apr_array_header_t *infos;
726 svn_boolean_t is_dav_comment;
727 apr_time_t expiration_date;
728 svn_boolean_t steal_lock;
729 apr_pool_t *result_pool;
733 check_lock(svn_error_t **fs_err,
735 const svn_fs_lock_target_t *target,
738 svn_revnum_t youngest_rev,
741 svn_node_kind_t kind;
742 svn_lock_t *existing_lock;
744 *fs_err = SVN_NO_ERROR;
746 SVN_ERR(svn_fs_x__check_path(&kind, root, path, pool));
747 if (kind == svn_node_dir)
749 *fs_err = SVN_FS__ERR_NOT_FILE(lb->fs, path);
753 /* While our locking implementation easily supports the locking of
754 nonexistent paths, we deliberately choose not to allow such madness. */
755 if (kind == svn_node_none)
757 if (SVN_IS_VALID_REVNUM(target->current_rev))
758 *fs_err = svn_error_createf(
759 SVN_ERR_FS_OUT_OF_DATE, NULL,
760 _("Path '%s' doesn't exist in HEAD revision"),
763 *fs_err = svn_error_createf(
764 SVN_ERR_FS_NOT_FOUND, NULL,
765 _("Path '%s' doesn't exist in HEAD revision"),
771 /* Is the caller attempting to lock an out-of-date working file? */
772 if (SVN_IS_VALID_REVNUM(target->current_rev))
774 svn_revnum_t created_rev;
776 if (target->current_rev > youngest_rev)
778 *fs_err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
779 _("No such revision %ld"),
780 target->current_rev);
784 SVN_ERR(svn_fs_x__node_created_rev(&created_rev, root, path,
787 /* SVN_INVALID_REVNUM means the path doesn't exist. So
788 apparently somebody is trying to lock something in their
789 working copy, but somebody else has deleted the thing
790 from HEAD. That counts as being 'out of date'. */
791 if (! SVN_IS_VALID_REVNUM(created_rev))
793 *fs_err = svn_error_createf
794 (SVN_ERR_FS_OUT_OF_DATE, NULL,
795 _("Path '%s' doesn't exist in HEAD revision"), path);
800 if (target->current_rev < created_rev)
802 *fs_err = svn_error_createf
803 (SVN_ERR_FS_OUT_OF_DATE, NULL,
804 _("Lock failed: newer version of '%s' exists"), path);
810 /* If the caller provided a TOKEN, we *really* need to see
811 if a lock already exists with that token, and if so, verify that
812 the lock's path matches PATH. Otherwise we run the risk of
813 breaking the 1-to-1 mapping of lock tokens to locked paths. */
814 /* ### TODO: actually do this check. This is tough, because the
815 schema doesn't supply a lookup-by-token mechanism. */
817 /* Is the path already locked?
819 Note that this next function call will automatically ignore any
820 errors about {the path not existing as a key, the path's token
821 not existing as a key, the lock just having been expired}. And
822 that's totally fine. Any of these three errors are perfectly
823 acceptable to ignore; it means that the path is now free and
824 clear for locking, because the fsx funcs just cleared out both
825 of the tables for us. */
826 SVN_ERR(get_lock_helper(lb->fs, &existing_lock, path, TRUE, pool));
829 if (! lb->steal_lock)
831 /* Sorry, the path is already locked. */
832 *fs_err = SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
840 typedef struct lock_info_t {
846 /* The body of svn_fs_x__lock(), which see.
848 BATON is a 'lock_baton_t *' holding the effective arguments.
849 BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
850 path, mapping canonical path to 'svn_fs_lock_target_t'. Set
851 BATON->infos to an array of 'lock_info_t' holding the results. For
852 the other arguments, see svn_fs_lock_many().
854 This implements the svn_fs_x__with_write_lock() 'body' callback
855 type, and assumes that the write lock is held.
858 lock_body(void *baton,
861 lock_baton_t *lb = baton;
863 svn_revnum_t youngest;
864 const char *rev_0_path;
866 apr_hash_t *index_updates = apr_hash_make(pool);
867 apr_hash_index_t *hi;
868 apr_pool_t *iterpool = svn_pool_create(pool);
870 /* Until we implement directory locks someday, we only allow locks
872 /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
873 library dependencies, which are not portable. */
874 SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
875 SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
877 for (i = 0; i < lb->targets->nelts; ++i)
879 const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
883 svn_pool_clear(iterpool);
885 info.path = item->key;
887 info.fs_err = SVN_NO_ERROR;
889 SVN_ERR(check_lock(&info.fs_err, info.path, item->value, lb, root,
890 youngest, iterpool));
892 /* If no error occurred while pre-checking, schedule the index updates for
895 schedule_index_update(index_updates, info.path, iterpool);
897 APR_ARRAY_PUSH(lb->infos, lock_info_t) = info;
900 rev_0_path = svn_fs_x__path_rev_absolute(lb->fs, 0, pool);
902 /* We apply the scheduled index updates before writing the actual locks.
904 Writing indices before locks is correct: if interrupted it leaves
905 indices without locks rather than locks without indices. An
906 index without a lock is consistent in that it always shows up as
907 unlocked in svn_fs_x__allow_locked_operation. A lock without an
908 index is inconsistent, svn_fs_x__allow_locked_operation will
909 show locked on the file but unlocked on the parent. */
911 for (hi = apr_hash_first(pool, index_updates); hi; hi = apr_hash_next(hi))
913 const char *path = apr_hash_this_key(hi);
914 apr_array_header_t *children = apr_hash_this_val(hi);
916 svn_pool_clear(iterpool);
917 SVN_ERR(add_to_digest(lb->fs->path, children, path, rev_0_path,
921 for (i = 0; i < lb->infos->nelts; ++i)
923 struct lock_info_t *info = &APR_ARRAY_IDX(lb->infos, i,
925 svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i, svn_sort__item_t);
926 svn_fs_lock_target_t *target = item->value;
928 svn_pool_clear(iterpool);
932 info->lock = svn_lock_create(lb->result_pool);
934 info->lock->token = apr_pstrdup(lb->result_pool, target->token);
936 SVN_ERR(svn_fs_x__generate_lock_token(&(info->lock->token), lb->fs,
939 /* The INFO->PATH is already allocated in LB->RESULT_POOL as a result
940 of svn_fspath__canonicalize() (see svn_fs_x__lock()). */
941 info->lock->path = info->path;
942 info->lock->owner = apr_pstrdup(lb->result_pool,
943 lb->fs->access_ctx->username);
944 info->lock->comment = apr_pstrdup(lb->result_pool, lb->comment);
945 info->lock->is_dav_comment = lb->is_dav_comment;
946 info->lock->creation_date = apr_time_now();
947 info->lock->expiration_date = lb->expiration_date;
949 info->fs_err = set_lock(lb->fs->path, info->lock, rev_0_path,
954 svn_pool_destroy(iterpool);
958 /* The effective arguments for unlock_body() below. */
959 typedef struct unlock_baton_t {
961 apr_array_header_t *targets;
962 apr_array_header_t *infos;
963 /* Set skip_check TRUE to prevent the checks that set infos[].fs_err. */
964 svn_boolean_t skip_check;
965 svn_boolean_t break_lock;
966 apr_pool_t *result_pool;
970 check_unlock(svn_error_t **fs_err,
979 *fs_err = get_lock(&lock, ub->fs, path, TRUE, TRUE, pool);
980 if (!*fs_err && !ub->break_lock)
982 if (strcmp(token, lock->token) != 0)
983 *fs_err = SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, path);
984 else if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
985 *fs_err = SVN_FS__ERR_LOCK_OWNER_MISMATCH(ub->fs,
986 ub->fs->access_ctx->username,
993 typedef struct unlock_info_t {
999 /* The body of svn_fs_x__unlock(), which see.
1001 BATON is a 'unlock_baton_t *' holding the effective arguments.
1002 BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
1003 path, mapping canonical path to (const char *) token. Set
1004 BATON->infos to an array of 'unlock_info_t' results. For the other
1005 arguments, see svn_fs_unlock_many().
1007 This implements the svn_fs_x__with_write_lock() 'body' callback
1008 type, and assumes that the write lock is held.
1010 static svn_error_t *
1011 unlock_body(void *baton,
1014 unlock_baton_t *ub = baton;
1015 svn_fs_root_t *root;
1016 svn_revnum_t youngest;
1017 const char *rev_0_path;
1019 apr_hash_t *indices_updates = apr_hash_make(pool);
1020 apr_hash_index_t *hi;
1021 apr_pool_t *iterpool = svn_pool_create(pool);
1023 SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool));
1024 SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool));
1026 for (i = 0; i < ub->targets->nelts; ++i)
1028 const svn_sort__item_t *item = &APR_ARRAY_IDX(ub->targets, i,
1030 const char *token = item->value;
1033 svn_pool_clear(iterpool);
1035 info.path = item->key;
1036 info.fs_err = SVN_NO_ERROR;
1039 if (!ub->skip_check)
1040 SVN_ERR(check_unlock(&info.fs_err, info.path, token, ub, root,
1043 /* If no error occurred while pre-checking, schedule the index updates for
1046 schedule_index_update(indices_updates, info.path, iterpool);
1048 APR_ARRAY_PUSH(ub->infos, unlock_info_t) = info;
1051 rev_0_path = svn_fs_x__path_rev_absolute(ub->fs, 0, pool);
1053 /* Unlike the lock_body(), we need to delete locks *before* we start to
1056 for (i = 0; i < ub->infos->nelts; ++i)
1058 struct unlock_info_t *info = &APR_ARRAY_IDX(ub->infos, i,
1059 struct unlock_info_t);
1061 svn_pool_clear(iterpool);
1065 SVN_ERR(delete_lock(ub->fs->path, info->path, iterpool));
1070 for (hi = apr_hash_first(pool, indices_updates); hi; hi = apr_hash_next(hi))
1072 const char *path = apr_hash_this_key(hi);
1073 apr_array_header_t *children = apr_hash_this_val(hi);
1075 svn_pool_clear(iterpool);
1076 SVN_ERR(delete_from_digest(ub->fs->path, children, path, rev_0_path,
1080 svn_pool_destroy(iterpool);
1081 return SVN_NO_ERROR;
1084 /* Unlock the lock described by LOCK->path and LOCK->token in FS.
1086 This assumes that the write lock is held.
1088 static svn_error_t *
1089 unlock_single(svn_fs_t *fs,
1091 apr_pool_t *scratch_pool)
1094 svn_sort__item_t item;
1095 apr_array_header_t *targets = apr_array_make(scratch_pool, 1,
1096 sizeof(svn_sort__item_t));
1097 item.key = lock->path;
1098 item.klen = strlen(item.key);
1099 item.value = (char*)lock->token;
1100 APR_ARRAY_PUSH(targets, svn_sort__item_t) = item;
1103 ub.targets = targets;
1104 ub.infos = apr_array_make(scratch_pool, targets->nelts,
1105 sizeof(struct unlock_info_t));
1106 ub.skip_check = TRUE;
1107 ub.result_pool = scratch_pool;
1109 /* No ub.infos[].fs_err error because skip_check is TRUE. */
1110 SVN_ERR(unlock_body(&ub, scratch_pool));
1112 return SVN_NO_ERROR;
1116 /*** Public API implementations ***/
1119 svn_fs_x__lock(svn_fs_t *fs,
1120 apr_hash_t *targets,
1121 const char *comment,
1122 svn_boolean_t is_dav_comment,
1123 apr_time_t expiration_date,
1124 svn_boolean_t steal_lock,
1125 svn_fs_lock_callback_t lock_callback,
1127 apr_pool_t *result_pool,
1128 apr_pool_t *scratch_pool)
1131 apr_array_header_t *sorted_targets;
1132 apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
1133 apr_hash_index_t *hi;
1134 apr_pool_t *iterpool;
1135 svn_error_t *err, *cb_err = SVN_NO_ERROR;
1138 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1140 /* We need to have a username attached to the fs. */
1141 if (!fs->access_ctx || !fs->access_ctx->username)
1142 return SVN_FS__ERR_NO_USER(fs);
1144 /* The FS locking API allows both canonical and non-canonical
1145 paths which means that the same canonical path could be
1146 represented more than once in the TARGETS hash. We just keep
1147 one, choosing one with a token if possible. */
1148 for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
1150 const char *path = apr_hash_this_key(hi);
1151 const svn_fs_lock_target_t *target = apr_hash_this_val(hi);
1152 const svn_fs_lock_target_t *other;
1154 path = svn_fspath__canonicalize(path, result_pool);
1155 other = svn_hash_gets(canonical_targets, path);
1157 if (!other || (!other->token && target->token))
1158 svn_hash_sets(canonical_targets, path, target);
1161 sorted_targets = svn_sort__hash(canonical_targets,
1162 svn_sort_compare_items_as_paths,
1166 lb.targets = sorted_targets;
1167 lb.infos = apr_array_make(result_pool, sorted_targets->nelts,
1168 sizeof(struct lock_info_t));
1169 lb.comment = comment;
1170 lb.is_dav_comment = is_dav_comment;
1171 lb.expiration_date = expiration_date;
1172 lb.steal_lock = steal_lock;
1173 lb.result_pool = result_pool;
1175 iterpool = svn_pool_create(scratch_pool);
1176 err = svn_fs_x__with_write_lock(fs, lock_body, &lb, iterpool);
1177 for (i = 0; i < lb.infos->nelts; ++i)
1179 struct lock_info_t *info = &APR_ARRAY_IDX(lb.infos, i,
1180 struct lock_info_t);
1181 svn_pool_clear(iterpool);
1182 if (!cb_err && lock_callback)
1184 if (!info->lock && !info->fs_err)
1185 info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
1186 0, _("Failed to lock '%s'"),
1189 cb_err = lock_callback(lock_baton, info->path, info->lock,
1190 info->fs_err, iterpool);
1192 svn_error_clear(info->fs_err);
1194 svn_pool_destroy(iterpool);
1197 svn_error_compose(err, cb_err);
1201 return svn_error_trace(err);
1206 svn_fs_x__generate_lock_token(const char **token,
1210 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1212 /* Notice that 'fs' is currently unused. But perhaps someday, we'll
1213 want to use the fs UUID + some incremented number? For now, we
1214 generate a URI that matches the DAV RFC. We could change this to
1215 some other URI scheme someday, if we wish. */
1216 *token = apr_pstrcat(pool, "opaquelocktoken:",
1217 svn_uuid_generate(pool), SVN_VA_NULL);
1218 return SVN_NO_ERROR;
1222 svn_fs_x__unlock(svn_fs_t *fs,
1223 apr_hash_t *targets,
1224 svn_boolean_t break_lock,
1225 svn_fs_lock_callback_t lock_callback,
1227 apr_pool_t *result_pool,
1228 apr_pool_t *scratch_pool)
1231 apr_array_header_t *sorted_targets;
1232 apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
1233 apr_hash_index_t *hi;
1234 apr_pool_t *iterpool;
1235 svn_error_t *err, *cb_err = SVN_NO_ERROR;
1238 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1240 /* We need to have a username attached to the fs. */
1241 if (!fs->access_ctx || !fs->access_ctx->username)
1242 return SVN_FS__ERR_NO_USER(fs);
1244 for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
1246 const char *path = apr_hash_this_key(hi);
1247 const char *token = apr_hash_this_val(hi);
1250 path = svn_fspath__canonicalize(path, result_pool);
1251 other = svn_hash_gets(canonical_targets, path);
1254 svn_hash_sets(canonical_targets, path, token);
1257 sorted_targets = svn_sort__hash(canonical_targets,
1258 svn_sort_compare_items_as_paths,
1262 ub.targets = sorted_targets;
1263 ub.infos = apr_array_make(result_pool, sorted_targets->nelts,
1264 sizeof(struct unlock_info_t));
1265 ub.skip_check = FALSE;
1266 ub.break_lock = break_lock;
1267 ub.result_pool = result_pool;
1269 iterpool = svn_pool_create(scratch_pool);
1270 err = svn_fs_x__with_write_lock(fs, unlock_body, &ub, iterpool);
1271 for (i = 0; i < ub.infos->nelts; ++i)
1273 unlock_info_t *info = &APR_ARRAY_IDX(ub.infos, i, unlock_info_t);
1274 svn_pool_clear(iterpool);
1275 if (!cb_err && lock_callback)
1277 if (!info->done && !info->fs_err)
1278 info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
1279 0, _("Failed to unlock '%s'"),
1281 cb_err = lock_callback(lock_baton, info->path, NULL, info->fs_err,
1284 svn_error_clear(info->fs_err);
1286 svn_pool_destroy(iterpool);
1289 svn_error_compose(err, cb_err);
1293 return svn_error_trace(err);
1298 svn_fs_x__get_lock(svn_lock_t **lock_p,
1303 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1304 path = svn_fs__canonicalize_abspath(path, pool);
1305 return get_lock_helper(fs, lock_p, path, FALSE, pool);
1309 /* Baton for get_locks_filter_func(). */
1310 typedef struct get_locks_filter_baton_t
1313 svn_depth_t requested_depth;
1314 svn_fs_get_locks_callback_t get_locks_func;
1315 void *get_locks_baton;
1317 } get_locks_filter_baton_t;
1320 /* A wrapper for the GET_LOCKS_FUNC passed to svn_fs_x__get_locks()
1321 which filters out locks on paths that aren't within
1322 BATON->requested_depth of BATON->path before called
1323 BATON->get_locks_func() with BATON->get_locks_baton.
1325 NOTE: See issue #3660 for details about how the FSX lock
1326 management code is inconsistent. Until that inconsistency is
1327 resolved, we take this filtering approach rather than honoring
1328 depth requests closer to the crawling code. In other words, once
1329 we decide how to resolve issue #3660, there might be a more
1330 performant way to honor the depth passed to svn_fs_x__get_locks(). */
1331 static svn_error_t *
1332 get_locks_filter_func(void *baton,
1336 get_locks_filter_baton_t *b = baton;
1338 /* Filter out unwanted paths. Since Subversion only allows
1339 locks on files, we can treat depth=immediates the same as
1340 depth=files for filtering purposes. Meaning, we'll keep
1343 a) its path is the very path we queried, or
1344 b) we've asked for a fully recursive answer, or
1345 c) we've asked for depth=files or depth=immediates, and this
1346 lock is on an immediate child of our query path.
1348 if ((strcmp(b->path, lock->path) == 0)
1349 || (b->requested_depth == svn_depth_infinity))
1351 SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1353 else if ((b->requested_depth == svn_depth_files) ||
1354 (b->requested_depth == svn_depth_immediates))
1356 const char *rel_uri = svn_fspath__skip_ancestor(b->path, lock->path);
1357 if (rel_uri && (svn_path_component_count(rel_uri) == 1))
1358 SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1361 return SVN_NO_ERROR;
1365 svn_fs_x__get_locks(svn_fs_t *fs,
1368 svn_fs_get_locks_callback_t get_locks_func,
1369 void *get_locks_baton,
1370 apr_pool_t *scratch_pool)
1372 const char *digest_path;
1373 get_locks_filter_baton_t glfb;
1375 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1376 path = svn_fs__canonicalize_abspath(path, scratch_pool);
1379 glfb.requested_depth = depth;
1380 glfb.get_locks_func = get_locks_func;
1381 glfb.get_locks_baton = get_locks_baton;
1383 /* Get the top digest path in our tree of interest, and then walk it. */
1384 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, scratch_pool));
1385 SVN_ERR(walk_locks(fs, digest_path, get_locks_filter_func, &glfb,
1386 FALSE, scratch_pool));
1387 return SVN_NO_ERROR;