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, const char *path)
120 SVN_ERR_FS_CORRUPT, 0,
121 _("Corrupt lockfile for path '%s' in filesystem '%s'"),
126 /*** Digest file handling functions. ***/
128 /* Return the path of the lock/entries file for which DIGEST is the
129 hashed repository relative path. */
131 digest_path_from_digest(const char *fs_path,
135 return svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
136 apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN),
137 digest, SVN_VA_NULL);
141 /* Set *DIGEST_PATH to the path to the lock/entries digest file associate
142 with PATH, where PATH is the path to the lock file or lock entries file
145 digest_path_from_path(const char **digest_path,
151 SVN_ERR(make_digest(&digest, path, pool));
152 *digest_path = svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
153 apr_pstrmemdup(pool, digest,
155 digest, SVN_VA_NULL);
160 /* Write to DIGEST_PATH a representation of CHILDREN (which may be
161 empty, if the versioned path in FS represented by DIGEST_PATH has
162 no children) and LOCK (which may be NULL if that versioned path is
163 lock itself locked). Set the permissions of DIGEST_PATH to those of
164 PERMS_REFERENCE. Use POOL for temporary allocations.
167 write_digest_file(apr_hash_t *children,
170 const char *digest_path,
171 const char *perms_reference,
172 apr_pool_t *scratch_pool)
174 svn_error_t *err = SVN_NO_ERROR;
175 svn_stream_t *stream;
176 apr_hash_index_t *hi;
177 apr_hash_t *hash = apr_hash_make(scratch_pool);
178 const char *tmp_path;
180 SVN_ERR(svn_fs_x__ensure_dir_exists(svn_dirent_join(fs_path, PATH_LOCKS_DIR,
182 fs_path, scratch_pool));
183 SVN_ERR(svn_fs_x__ensure_dir_exists(svn_dirent_dirname(digest_path,
185 fs_path, scratch_pool));
189 const char *creation_date = NULL, *expiration_date = NULL;
190 if (lock->creation_date)
191 creation_date = svn_time_to_cstring(lock->creation_date,
193 if (lock->expiration_date)
194 expiration_date = svn_time_to_cstring(lock->expiration_date,
197 hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1,
198 lock->path, APR_HASH_KEY_STRING, scratch_pool);
199 hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1,
200 lock->token, APR_HASH_KEY_STRING, scratch_pool);
201 hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1,
202 lock->owner, APR_HASH_KEY_STRING, scratch_pool);
203 hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1,
204 lock->comment, APR_HASH_KEY_STRING, scratch_pool);
205 hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1,
206 lock->is_dav_comment ? "1" : "0", 1, scratch_pool);
207 hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1,
208 creation_date, APR_HASH_KEY_STRING, scratch_pool);
209 hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1,
210 expiration_date, APR_HASH_KEY_STRING, scratch_pool);
212 if (apr_hash_count(children))
214 svn_stringbuf_t *children_list
215 = svn_stringbuf_create_empty(scratch_pool);
216 for (hi = apr_hash_first(scratch_pool, children);
218 hi = apr_hash_next(hi))
220 svn_stringbuf_appendbytes(children_list,
221 apr_hash_this_key(hi),
222 apr_hash_this_key_len(hi));
223 svn_stringbuf_appendbyte(children_list, '\n');
225 hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1,
226 children_list->data, children_list->len, scratch_pool);
229 SVN_ERR(svn_stream_open_unique(&stream, &tmp_path,
230 svn_dirent_dirname(digest_path,
232 svn_io_file_del_none, scratch_pool,
234 if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR,
237 svn_error_clear(svn_stream_close(stream));
238 return svn_error_createf(err->apr_err,
240 _("Cannot write lock/entries hashfile '%s'"),
241 svn_dirent_local_style(tmp_path,
245 SVN_ERR(svn_stream_close(stream));
246 SVN_ERR(svn_io_file_rename(tmp_path, digest_path, scratch_pool));
247 SVN_ERR(svn_io_copy_perms(perms_reference, digest_path, scratch_pool));
252 /* Parse the file at DIGEST_PATH, populating the lock LOCK_P in that
253 file (if it exists, and if *LOCK_P is non-NULL) and the hash of
254 CHILDREN_P (if any exist, and if *CHILDREN_P is non-NULL). Use POOL
255 for all allocations. */
257 read_digest_file(apr_hash_t **children_p,
260 const char *digest_path,
263 svn_error_t *err = SVN_NO_ERROR;
266 svn_stream_t *stream;
268 svn_node_kind_t kind;
273 *children_p = apr_hash_make(pool);
275 SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
276 if (kind == svn_node_none)
279 /* If our caller doesn't care about anything but the presence of the
281 if (kind == svn_node_file && !lock_p && !children_p)
284 SVN_ERR(svn_stream_open_readonly(&stream, digest_path, pool, pool));
286 hash = apr_hash_make(pool);
287 if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool)))
289 svn_error_clear(svn_stream_close(stream));
290 return svn_error_createf(err->apr_err,
292 _("Can't parse lock/entries hashfile '%s'"),
293 svn_dirent_local_style(digest_path, pool));
295 SVN_ERR(svn_stream_close(stream));
297 /* If our caller cares, see if we have a lock path in our hash. If
298 so, we'll assume we have a lock here. */
299 val = hash_fetch(hash, PATH_KEY);
302 const char *path = val;
304 /* Create our lock and load it up. */
305 lock = svn_lock_create(pool);
308 if (! ((lock->token = hash_fetch(hash, TOKEN_KEY))))
309 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
311 if (! ((lock->owner = hash_fetch(hash, OWNER_KEY))))
312 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
314 if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY))))
315 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
316 lock->is_dav_comment = (val[0] == '1');
318 if (! ((val = hash_fetch(hash, CREATION_DATE_KEY))))
319 return svn_error_trace(err_corrupt_lockfile(fs_path, path));
320 SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool));
322 if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY)))
323 SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool));
325 lock->comment = hash_fetch(hash, COMMENT_KEY);
330 /* If our caller cares, see if we have any children for this path. */
331 val = hash_fetch(hash, CHILDREN_KEY);
332 if (val && children_p)
334 apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool);
337 for (i = 0; i < kiddos->nelts; i++)
339 svn_hash_sets(*children_p, APR_ARRAY_IDX(kiddos, i, const char *),
348 /*** Lock helper functions (path here are still FS paths, not on-disk
349 schema-supporting paths) ***/
352 /* Write LOCK in FS to the actual OS filesystem.
354 Use PERMS_REFERENCE for the permissions of any digest files.
357 set_lock(const char *fs_path,
359 const char *perms_reference,
360 apr_pool_t *scratch_pool)
362 const char *digest_path;
363 apr_hash_t *children;
365 SVN_ERR(digest_path_from_path(&digest_path, fs_path, lock->path,
368 /* We could get away without reading the file as children should
369 always come back empty. */
370 SVN_ERR(read_digest_file(&children, NULL, fs_path, digest_path,
373 SVN_ERR(write_digest_file(children, lock, fs_path, digest_path,
374 perms_reference, scratch_pool));
380 delete_lock(const char *fs_path,
382 apr_pool_t *scratch_pool)
384 const char *digest_path;
386 SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, scratch_pool));
388 SVN_ERR(svn_io_remove_file2(digest_path, TRUE, scratch_pool));
394 add_to_digest(const char *fs_path,
395 apr_array_header_t *paths,
396 const char *index_path,
397 const char *perms_reference,
398 apr_pool_t *scratch_pool)
400 const char *index_digest_path;
401 apr_hash_t *children;
404 unsigned int original_count;
406 SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path,
408 SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path,
411 original_count = apr_hash_count(children);
413 for (i = 0; i < paths->nelts; ++i)
415 const char *path = APR_ARRAY_IDX(paths, i, const char *);
416 const char *digest_path, *digest_file;
418 SVN_ERR(digest_path_from_path(&digest_path, fs_path, path,
420 digest_file = svn_dirent_basename(digest_path, NULL);
421 svn_hash_sets(children, digest_file, (void *)1);
424 if (apr_hash_count(children) != original_count)
425 SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
426 perms_reference, scratch_pool));
432 delete_from_digest(const char *fs_path,
433 apr_array_header_t *paths,
434 const char *index_path,
435 const char *perms_reference,
436 apr_pool_t *scratch_pool)
438 const char *index_digest_path;
439 apr_hash_t *children;
443 SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path,
445 SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path,
448 for (i = 0; i < paths->nelts; ++i)
450 const char *path = APR_ARRAY_IDX(paths, i, const char *);
451 const char *digest_path, *digest_file;
453 SVN_ERR(digest_path_from_path(&digest_path, fs_path, path,
455 digest_file = svn_dirent_basename(digest_path, NULL);
456 svn_hash_sets(children, digest_file, NULL);
459 if (apr_hash_count(children) || lock)
460 SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
461 perms_reference, scratch_pool));
463 SVN_ERR(svn_io_remove_file2(index_digest_path, TRUE, scratch_pool));
469 unlock_single(svn_fs_t *fs,
473 /* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
474 TRUE if the caller (or one of its callers) has taken out the
475 repository-wide write lock, FALSE otherwise. If MUST_EXIST is
476 not set, the function will simply return NULL in *LOCK_P instead
477 of creating an SVN_FS__ERR_NO_SUCH_LOCK error in case the lock
478 was not found (much faster). Use POOL for allocations. */
480 get_lock(svn_lock_t **lock_p,
483 svn_boolean_t have_write_lock,
484 svn_boolean_t must_exist,
487 svn_lock_t *lock = NULL;
488 const char *digest_path;
489 svn_node_kind_t kind;
491 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
492 SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
495 if (kind != svn_node_none)
496 SVN_ERR(read_digest_file(NULL, &lock, fs->path, digest_path, pool));
499 return must_exist ? SVN_FS__ERR_NO_SUCH_LOCK(fs, path) : SVN_NO_ERROR;
501 /* Don't return an expired lock. */
502 if (lock->expiration_date && (apr_time_now() > lock->expiration_date))
504 /* Only remove the lock if we have the write lock.
505 Read operations shouldn't change the filesystem. */
507 SVN_ERR(unlock_single(fs, lock, pool));
508 return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
516 /* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
517 TRUE if the caller (or one of its callers) has taken out the
518 repository-wide write lock, FALSE otherwise. Use POOL for
521 get_lock_helper(svn_fs_t *fs,
524 svn_boolean_t have_write_lock,
530 err = get_lock(&lock, fs, path, have_write_lock, FALSE, pool);
532 /* We've deliberately decided that this function doesn't tell the
533 caller *why* the lock is unavailable. */
534 if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
535 || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)))
537 svn_error_clear(err);
549 /* Baton for locks_walker(). */
550 typedef struct walk_locks_baton_t
552 svn_fs_get_locks_callback_t get_locks_func;
553 void *get_locks_baton;
555 } walk_locks_baton_t;
557 /* Implements walk_digests_callback_t. */
559 locks_walker(void *baton,
561 const char *digest_path,
563 svn_boolean_t have_write_lock,
566 walk_locks_baton_t *wlb = baton;
570 /* Don't report an expired lock. */
571 if (lock->expiration_date == 0
572 || (apr_time_now() <= lock->expiration_date))
574 if (wlb->get_locks_func)
575 SVN_ERR(wlb->get_locks_func(wlb->get_locks_baton, lock, pool));
579 /* Only remove the lock if we have the write lock.
580 Read operations shouldn't change the filesystem. */
582 SVN_ERR(unlock_single(wlb->fs, lock, pool));
589 /* Callback type for walk_digest_files().
591 * LOCK come from a read_digest_file(digest_path) call.
593 typedef svn_error_t *(*walk_digests_callback_t)(void *baton,
595 const char *digest_path,
597 svn_boolean_t have_write_lock,
600 /* A function that calls WALK_DIGESTS_FUNC/WALK_DIGESTS_BATON for
601 all lock digest files in and under PATH in FS.
602 HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
603 has the FS write lock. */
605 walk_digest_files(const char *fs_path,
606 const char *digest_path,
607 walk_digests_callback_t walk_digests_func,
608 void *walk_digests_baton,
609 svn_boolean_t have_write_lock,
612 apr_hash_index_t *hi;
613 apr_hash_t *children;
617 /* First, send up any locks in the current digest file. */
618 SVN_ERR(read_digest_file(&children, &lock, fs_path, digest_path, pool));
620 SVN_ERR(walk_digests_func(walk_digests_baton, fs_path, digest_path, lock,
621 have_write_lock, pool));
623 /* Now, report all the child entries (if any; bail otherwise). */
624 if (! apr_hash_count(children))
626 subpool = svn_pool_create(pool);
627 for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
629 const char *digest = apr_hash_this_key(hi);
630 svn_pool_clear(subpool);
632 SVN_ERR(read_digest_file
633 (NULL, &lock, fs_path,
634 digest_path_from_digest(fs_path, digest, subpool), subpool));
636 SVN_ERR(walk_digests_func(walk_digests_baton, fs_path, digest_path, lock,
637 have_write_lock, subpool));
639 svn_pool_destroy(subpool);
643 /* A function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
644 all locks in and under PATH in FS.
645 HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
646 has the FS write lock. */
648 walk_locks(svn_fs_t *fs,
649 const char *digest_path,
650 svn_fs_get_locks_callback_t get_locks_func,
651 void *get_locks_baton,
652 svn_boolean_t have_write_lock,
655 walk_locks_baton_t wlb;
657 wlb.get_locks_func = get_locks_func;
658 wlb.get_locks_baton = get_locks_baton;
660 SVN_ERR(walk_digest_files(fs->path, digest_path, locks_walker, &wlb,
661 have_write_lock, pool));
666 /* Utility function: verify that a lock can be used. Interesting
667 errors returned from this function:
669 SVN_ERR_FS_NO_USER: No username attached to FS.
670 SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner.
671 SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK.
674 verify_lock(svn_fs_t *fs,
677 if ((! fs->access_ctx) || (! fs->access_ctx->username))
678 return svn_error_createf
679 (SVN_ERR_FS_NO_USER, NULL,
680 _("Cannot verify lock on path '%s'; no username available"),
683 else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
684 return svn_error_createf
685 (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
686 _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
687 fs->access_ctx->username, lock->path, lock->owner);
689 else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
690 return svn_error_createf
691 (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
692 _("Cannot verify lock on path '%s'; no matching lock-token available"),
699 /* This implements the svn_fs_get_locks_callback_t interface, where
700 BATON is just an svn_fs_t object. */
702 get_locks_callback(void *baton,
706 return verify_lock(baton, lock);
710 /* The main routine for lock enforcement, used throughout libsvn_fs_x. */
712 svn_fs_x__allow_locked_operation(const char *path,
714 svn_boolean_t recurse,
715 svn_boolean_t have_write_lock,
716 apr_pool_t *scratch_pool)
718 path = svn_fs__canonicalize_abspath(path, scratch_pool);
721 /* Discover all locks at or below the path. */
722 const char *digest_path;
723 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path,
725 SVN_ERR(walk_locks(fs, digest_path, get_locks_callback,
726 fs, have_write_lock, scratch_pool));
730 /* Discover and verify any lock attached to the path. */
732 SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock,
735 SVN_ERR(verify_lock(fs, lock));
740 /* The effective arguments for lock_body() below. */
741 typedef struct lock_baton_t {
743 apr_array_header_t *targets;
744 apr_array_header_t *infos;
746 svn_boolean_t is_dav_comment;
747 apr_time_t expiration_date;
748 svn_boolean_t steal_lock;
749 apr_pool_t *result_pool;
753 check_lock(svn_error_t **fs_err,
755 const svn_fs_lock_target_t *target,
758 svn_revnum_t youngest_rev,
761 svn_node_kind_t kind;
762 svn_lock_t *existing_lock;
764 *fs_err = SVN_NO_ERROR;
766 SVN_ERR(svn_fs_x__check_path(&kind, root, path, pool));
767 if (kind == svn_node_dir)
769 *fs_err = SVN_FS__ERR_NOT_FILE(lb->fs, path);
773 /* While our locking implementation easily supports the locking of
774 nonexistent paths, we deliberately choose not to allow such madness. */
775 if (kind == svn_node_none)
777 if (SVN_IS_VALID_REVNUM(target->current_rev))
778 *fs_err = svn_error_createf(
779 SVN_ERR_FS_OUT_OF_DATE, NULL,
780 _("Path '%s' doesn't exist in HEAD revision"),
783 *fs_err = svn_error_createf(
784 SVN_ERR_FS_NOT_FOUND, NULL,
785 _("Path '%s' doesn't exist in HEAD revision"),
791 /* Is the caller attempting to lock an out-of-date working file? */
792 if (SVN_IS_VALID_REVNUM(target->current_rev))
794 svn_revnum_t created_rev;
796 if (target->current_rev > youngest_rev)
798 *fs_err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
799 _("No such revision %ld"),
800 target->current_rev);
804 SVN_ERR(svn_fs_x__node_created_rev(&created_rev, root, path,
807 /* SVN_INVALID_REVNUM means the path doesn't exist. So
808 apparently somebody is trying to lock something in their
809 working copy, but somebody else has deleted the thing
810 from HEAD. That counts as being 'out of date'. */
811 if (! SVN_IS_VALID_REVNUM(created_rev))
813 *fs_err = svn_error_createf
814 (SVN_ERR_FS_OUT_OF_DATE, NULL,
815 _("Path '%s' doesn't exist in HEAD revision"), path);
820 if (target->current_rev < created_rev)
822 *fs_err = svn_error_createf
823 (SVN_ERR_FS_OUT_OF_DATE, NULL,
824 _("Lock failed: newer version of '%s' exists"), path);
830 /* If the caller provided a TOKEN, we *really* need to see
831 if a lock already exists with that token, and if so, verify that
832 the lock's path matches PATH. Otherwise we run the risk of
833 breaking the 1-to-1 mapping of lock tokens to locked paths. */
834 /* ### TODO: actually do this check. This is tough, because the
835 schema doesn't supply a lookup-by-token mechanism. */
837 /* Is the path already locked?
839 Note that this next function call will automatically ignore any
840 errors about {the path not existing as a key, the path's token
841 not existing as a key, the lock just having been expired}. And
842 that's totally fine. Any of these three errors are perfectly
843 acceptable to ignore; it means that the path is now free and
844 clear for locking, because the fsx funcs just cleared out both
845 of the tables for us. */
846 SVN_ERR(get_lock_helper(lb->fs, &existing_lock, path, TRUE, pool));
849 if (! lb->steal_lock)
851 /* Sorry, the path is already locked. */
852 *fs_err = SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
860 typedef struct lock_info_t {
862 const char *component;
867 /* The body of svn_fs_x__lock(), which see.
869 BATON is a 'lock_baton_t *' holding the effective arguments.
870 BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
871 path, mapping canonical path to 'svn_fs_lock_target_t'. Set
872 BATON->infos to an array of 'lock_info_t' holding the results. For
873 the other arguments, see svn_fs_lock_many().
875 This implements the svn_fs_x__with_write_lock() 'body' callback
876 type, and assumes that the write lock is held.
879 lock_body(void *baton, apr_pool_t *pool)
881 lock_baton_t *lb = baton;
883 svn_revnum_t youngest;
884 const char *rev_0_path;
885 int i, outstanding = 0;
886 apr_pool_t *iterpool = svn_pool_create(pool);
888 lb->infos = apr_array_make(lb->result_pool, lb->targets->nelts,
889 sizeof(lock_info_t));
891 /* Until we implement directory locks someday, we only allow locks
892 on files or non-existent paths. */
893 /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
894 library dependencies, which are not portable. */
895 SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
896 SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
898 for (i = 0; i < lb->targets->nelts; ++i)
900 const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
902 const svn_fs_lock_target_t *target = item->value;
905 svn_pool_clear(iterpool);
907 info.path = item->key;
908 SVN_ERR(check_lock(&info.fs_err, info.path, target, lb, root,
909 youngest, iterpool));
911 info.component = NULL;
912 APR_ARRAY_PUSH(lb->infos, lock_info_t) = info;
917 rev_0_path = svn_fs_x__path_rev_absolute(lb->fs, 0, pool);
925 we loop through repeatedly. The first pass sees '/' on all paths
926 and writes the '/' index. The second pass sees '/foo' twice and
927 writes that index followed by '/zig' and that index. The third
928 pass sees '/foo/bar' twice and writes that index, and then writes
929 the lock for '/zig/x'. The fourth pass writes the locks for
930 '/foo/bar/f' and '/foo/bar/g'.
932 Writing indices before locks is correct: if interrupted it leaves
933 indices without locks rather than locks without indices. An
934 index without a lock is consistent in that it always shows up as
935 unlocked in svn_fs_x__allow_locked_operation. A lock without an
936 index is inconsistent, svn_fs_x__allow_locked_operation will
937 show locked on the file but unlocked on the parent. */
942 const char *last_path = NULL;
943 apr_array_header_t *paths;
945 svn_pool_clear(iterpool);
946 paths = apr_array_make(iterpool, 1, sizeof(const char *));
948 for (i = 0; i < lb->infos->nelts; ++i)
950 lock_info_t *info = &APR_ARRAY_IDX(lb->infos, i, lock_info_t);
951 const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
953 const svn_fs_lock_target_t *target = item->value;
955 if (!info->fs_err && !info->lock)
957 if (!info->component)
959 info->component = info->path;
960 APR_ARRAY_PUSH(paths, const char *) = info->path;
965 info->component = strchr(info->component + 1, '/');
966 if (!info->component)
968 /* The component is a path to lock, this cannot
969 match a previous path that need to be indexed. */
972 SVN_ERR(add_to_digest(lb->fs->path, paths, last_path,
973 rev_0_path, iterpool));
974 apr_array_clear(paths);
978 info->lock = svn_lock_create(lb->result_pool);
980 info->lock->token = target->token;
982 SVN_ERR(svn_fs_x__generate_lock_token(
983 &(info->lock->token), lb->fs,
985 info->lock->path = info->path;
986 info->lock->owner = lb->fs->access_ctx->username;
987 info->lock->comment = lb->comment;
988 info->lock->is_dav_comment = lb->is_dav_comment;
989 info->lock->creation_date = apr_time_now();
990 info->lock->expiration_date = lb->expiration_date;
992 info->fs_err = set_lock(lb->fs->path, info->lock,
993 rev_0_path, iterpool);
998 /* The component is a path to an index. */
999 apr_size_t len = info->component - info->path;
1002 && (strncmp(last_path, info->path, len)
1003 || strlen(last_path) != len))
1005 /* No match to the previous paths to index. */
1006 SVN_ERR(add_to_digest(lb->fs->path, paths, last_path,
1007 rev_0_path, iterpool));
1008 apr_array_clear(paths);
1011 APR_ARRAY_PUSH(paths, const char *) = info->path;
1013 last_path = apr_pstrndup(iterpool, info->path, len);
1018 if (last_path && i == lb->infos->nelts - 1)
1019 SVN_ERR(add_to_digest(lb->fs->path, paths, last_path,
1020 rev_0_path, iterpool));
1024 return SVN_NO_ERROR;
1027 /* The effective arguments for unlock_body() below. */
1028 typedef struct unlock_baton_t {
1030 apr_array_header_t *targets;
1031 apr_array_header_t *infos;
1032 /* Set skip_check TRUE to prevent the checks that set infos[].fs_err. */
1033 svn_boolean_t skip_check;
1034 svn_boolean_t break_lock;
1035 apr_pool_t *result_pool;
1038 static svn_error_t *
1039 check_unlock(svn_error_t **fs_err,
1043 svn_fs_root_t *root,
1048 *fs_err = get_lock(&lock, ub->fs, path, TRUE, TRUE, pool);
1049 if (!*fs_err && !ub->break_lock)
1051 if (strcmp(token, lock->token) != 0)
1052 *fs_err = SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, path);
1053 else if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
1054 *fs_err = SVN_FS__ERR_LOCK_OWNER_MISMATCH(ub->fs,
1055 ub->fs->access_ctx->username,
1059 return SVN_NO_ERROR;
1062 typedef struct unlock_info_t {
1064 const char *component;
1065 svn_error_t *fs_err;
1070 /* The body of svn_fs_x__unlock(), which see.
1072 BATON is a 'unlock_baton_t *' holding the effective arguments.
1073 BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
1074 path, mapping canonical path to (const char *) token. Set
1075 BATON->infos to an array of 'unlock_info_t' results. For the other
1076 arguments, see svn_fs_unlock_many().
1078 This implements the svn_fs_x__with_write_lock() 'body' callback
1079 type, and assumes that the write lock is held.
1081 static svn_error_t *
1082 unlock_body(void *baton, apr_pool_t *pool)
1084 unlock_baton_t *ub = baton;
1085 svn_fs_root_t *root;
1086 svn_revnum_t youngest;
1087 const char *rev_0_path;
1088 int i, max_components = 0, outstanding = 0;
1089 apr_pool_t *iterpool = svn_pool_create(pool);
1091 ub->infos = apr_array_make(ub->result_pool, ub->targets->nelts,
1092 sizeof( unlock_info_t));
1094 SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool));
1095 SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool));
1097 for (i = 0; i < ub->targets->nelts; ++i)
1099 const svn_sort__item_t *item = &APR_ARRAY_IDX(ub->targets, i,
1101 const char *token = item->value;
1102 unlock_info_t info = { 0 };
1104 svn_pool_clear(iterpool);
1106 info.path = item->key;
1107 if (!ub->skip_check)
1108 SVN_ERR(check_unlock(&info.fs_err, info.path, token, ub, root,
1114 info.components = 1;
1115 info.component = info.path;
1116 while((s = strchr(info.component + 1, '/')))
1122 if (info.components > max_components)
1123 max_components = info.components;
1127 APR_ARRAY_PUSH(ub->infos, unlock_info_t) = info;
1130 rev_0_path = svn_fs_x__path_rev_absolute(ub->fs, 0, pool);
1132 for (i = max_components; i >= 0; --i)
1134 const char *last_path = NULL;
1135 apr_array_header_t *paths;
1138 svn_pool_clear(iterpool);
1139 paths = apr_array_make(pool, 1, sizeof(const char *));
1141 for (j = 0; j < ub->infos->nelts; ++j)
1143 unlock_info_t *info = &APR_ARRAY_IDX(ub->infos, j, unlock_info_t);
1145 if (!info->fs_err && info->path)
1148 if (info->components == i)
1150 SVN_ERR(delete_lock(ub->fs->path, info->path, iterpool));
1153 else if (info->components > i)
1155 apr_size_t len = info->component - info->path;
1158 && strcmp(last_path, "/")
1159 && (strncmp(last_path, info->path, len)
1160 || strlen(last_path) != len))
1162 SVN_ERR(delete_from_digest(ub->fs->path, paths, last_path,
1163 rev_0_path, iterpool));
1164 apr_array_clear(paths);
1167 APR_ARRAY_PUSH(paths, const char *) = info->path;
1170 if (info->component > info->path)
1171 last_path = apr_pstrndup(pool, info->path, len);
1176 if (info->component > info->path)
1179 while(info->component[0] != '/')
1185 if (last_path && j == ub->infos->nelts - 1)
1186 SVN_ERR(delete_from_digest(ub->fs->path, paths, last_path,
1187 rev_0_path, iterpool));
1191 return SVN_NO_ERROR;
1194 /* Unlock the lock described by LOCK->path and LOCK->token in FS.
1196 This assumes that the write lock is held.
1198 static svn_error_t *
1199 unlock_single(svn_fs_t *fs,
1201 apr_pool_t *scratch_pool)
1204 svn_sort__item_t item;
1205 apr_array_header_t *targets = apr_array_make(scratch_pool, 1,
1206 sizeof(svn_sort__item_t));
1207 item.key = lock->path;
1208 item.klen = strlen(item.key);
1209 item.value = (char*)lock->token;
1210 APR_ARRAY_PUSH(targets, svn_sort__item_t) = item;
1213 ub.targets = targets;
1214 ub.skip_check = TRUE;
1215 ub.result_pool = scratch_pool;
1217 /* No ub.infos[].fs_err error because skip_check is TRUE. */
1218 SVN_ERR(unlock_body(&ub, scratch_pool));
1220 return SVN_NO_ERROR;
1224 /*** Public API implementations ***/
1227 svn_fs_x__lock(svn_fs_t *fs,
1228 apr_hash_t *targets,
1229 const char *comment,
1230 svn_boolean_t is_dav_comment,
1231 apr_time_t expiration_date,
1232 svn_boolean_t steal_lock,
1233 svn_fs_lock_callback_t lock_callback,
1235 apr_pool_t *result_pool,
1236 apr_pool_t *scratch_pool)
1239 apr_array_header_t *sorted_targets;
1240 apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
1241 apr_hash_index_t *hi;
1242 apr_pool_t *iterpool;
1243 svn_error_t *err, *cb_err = SVN_NO_ERROR;
1246 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1248 /* We need to have a username attached to the fs. */
1249 if (!fs->access_ctx || !fs->access_ctx->username)
1250 return SVN_FS__ERR_NO_USER(fs);
1252 /* The FS locking API allows both canonical and non-canonical
1253 paths which means that the same canonical path could be
1254 represented more than once in the TARGETS hash. We just keep
1255 one, choosing one with a token if possible. */
1256 for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
1258 const char *path = apr_hash_this_key(hi);
1259 const svn_fs_lock_target_t *target = apr_hash_this_val(hi);
1260 const svn_fs_lock_target_t *other;
1262 path = svn_fspath__canonicalize(path, result_pool);
1263 other = svn_hash_gets(canonical_targets, path);
1265 if (!other || (!other->token && target->token))
1266 svn_hash_sets(canonical_targets, path, target);
1269 sorted_targets = svn_sort__hash(canonical_targets,
1270 svn_sort_compare_items_as_paths,
1274 lb.targets = sorted_targets;
1275 lb.comment = comment;
1276 lb.is_dav_comment = is_dav_comment;
1277 lb.expiration_date = expiration_date;
1278 lb.steal_lock = steal_lock;
1279 lb.result_pool = result_pool;
1281 iterpool = svn_pool_create(scratch_pool);
1282 err = svn_fs_x__with_write_lock(fs, lock_body, &lb, iterpool);
1283 for (i = 0; i < lb.infos->nelts; ++i)
1285 struct lock_info_t *info = &APR_ARRAY_IDX(lb.infos, i,
1286 struct lock_info_t);
1287 svn_pool_clear(iterpool);
1288 if (!cb_err && lock_callback)
1290 if (!info->lock && !info->fs_err)
1291 info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
1292 0, _("Failed to lock '%s'"),
1295 cb_err = lock_callback(lock_baton, info->path, info->lock,
1296 info->fs_err, iterpool);
1298 svn_error_clear(info->fs_err);
1300 svn_pool_destroy(iterpool);
1303 svn_error_compose(err, cb_err);
1307 return svn_error_trace(err);
1312 svn_fs_x__generate_lock_token(const char **token,
1316 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1318 /* Notice that 'fs' is currently unused. But perhaps someday, we'll
1319 want to use the fs UUID + some incremented number? For now, we
1320 generate a URI that matches the DAV RFC. We could change this to
1321 some other URI scheme someday, if we wish. */
1322 *token = apr_pstrcat(pool, "opaquelocktoken:",
1323 svn_uuid_generate(pool), SVN_VA_NULL);
1324 return SVN_NO_ERROR;
1328 svn_fs_x__unlock(svn_fs_t *fs,
1329 apr_hash_t *targets,
1330 svn_boolean_t break_lock,
1331 svn_fs_lock_callback_t lock_callback,
1333 apr_pool_t *result_pool,
1334 apr_pool_t *scratch_pool)
1337 apr_array_header_t *sorted_targets;
1338 apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
1339 apr_hash_index_t *hi;
1340 apr_pool_t *iterpool;
1341 svn_error_t *err, *cb_err = SVN_NO_ERROR;
1344 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1346 /* We need to have a username attached to the fs. */
1347 if (!fs->access_ctx || !fs->access_ctx->username)
1348 return SVN_FS__ERR_NO_USER(fs);
1350 for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
1352 const char *path = apr_hash_this_key(hi);
1353 const char *token = apr_hash_this_val(hi);
1356 path = svn_fspath__canonicalize(path, result_pool);
1357 other = svn_hash_gets(canonical_targets, path);
1360 svn_hash_sets(canonical_targets, path, token);
1363 sorted_targets = svn_sort__hash(canonical_targets,
1364 svn_sort_compare_items_as_paths,
1368 ub.targets = sorted_targets;
1369 ub.skip_check = FALSE;
1370 ub.break_lock = break_lock;
1371 ub.result_pool = result_pool;
1373 iterpool = svn_pool_create(scratch_pool);
1374 err = svn_fs_x__with_write_lock(fs, unlock_body, &ub, iterpool);
1375 for (i = 0; i < ub.infos->nelts; ++i)
1377 unlock_info_t *info = &APR_ARRAY_IDX(ub.infos, i, unlock_info_t);
1378 svn_pool_clear(iterpool);
1379 if (!cb_err && lock_callback)
1381 if (!info->done && !info->fs_err)
1382 info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
1383 0, _("Failed to unlock '%s'"),
1385 cb_err = lock_callback(lock_baton, info->path, NULL, info->fs_err,
1388 svn_error_clear(info->fs_err);
1390 svn_pool_destroy(iterpool);
1393 svn_error_compose(err, cb_err);
1397 return svn_error_trace(err);
1402 svn_fs_x__get_lock(svn_lock_t **lock_p,
1407 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1408 path = svn_fs__canonicalize_abspath(path, pool);
1409 return get_lock_helper(fs, lock_p, path, FALSE, pool);
1413 /* Baton for get_locks_filter_func(). */
1414 typedef struct get_locks_filter_baton_t
1417 svn_depth_t requested_depth;
1418 svn_fs_get_locks_callback_t get_locks_func;
1419 void *get_locks_baton;
1421 } get_locks_filter_baton_t;
1424 /* A wrapper for the GET_LOCKS_FUNC passed to svn_fs_x__get_locks()
1425 which filters out locks on paths that aren't within
1426 BATON->requested_depth of BATON->path before called
1427 BATON->get_locks_func() with BATON->get_locks_baton.
1429 NOTE: See issue #3660 for details about how the FSX lock
1430 management code is inconsistent. Until that inconsistency is
1431 resolved, we take this filtering approach rather than honoring
1432 depth requests closer to the crawling code. In other words, once
1433 we decide how to resolve issue #3660, there might be a more
1434 performant way to honor the depth passed to svn_fs_x__get_locks(). */
1435 static svn_error_t *
1436 get_locks_filter_func(void *baton,
1440 get_locks_filter_baton_t *b = baton;
1442 /* Filter out unwanted paths. Since Subversion only allows
1443 locks on files, we can treat depth=immediates the same as
1444 depth=files for filtering purposes. Meaning, we'll keep
1447 a) its path is the very path we queried, or
1448 b) we've asked for a fully recursive answer, or
1449 c) we've asked for depth=files or depth=immediates, and this
1450 lock is on an immediate child of our query path.
1452 if ((strcmp(b->path, lock->path) == 0)
1453 || (b->requested_depth == svn_depth_infinity))
1455 SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1457 else if ((b->requested_depth == svn_depth_files) ||
1458 (b->requested_depth == svn_depth_immediates))
1460 const char *rel_uri = svn_fspath__skip_ancestor(b->path, lock->path);
1461 if (rel_uri && (svn_path_component_count(rel_uri) == 1))
1462 SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1465 return SVN_NO_ERROR;
1469 svn_fs_x__get_locks(svn_fs_t *fs,
1472 svn_fs_get_locks_callback_t get_locks_func,
1473 void *get_locks_baton,
1474 apr_pool_t *scratch_pool)
1476 const char *digest_path;
1477 get_locks_filter_baton_t glfb;
1479 SVN_ERR(svn_fs__check_fs(fs, TRUE));
1480 path = svn_fs__canonicalize_abspath(path, scratch_pool);
1483 glfb.requested_depth = depth;
1484 glfb.get_locks_func = get_locks_func;
1485 glfb.get_locks_baton = get_locks_baton;
1487 /* Get the top digest path in our tree of interest, and then walk it. */
1488 SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, scratch_pool));
1489 SVN_ERR(walk_locks(fs, digest_path, get_locks_filter_func, &glfb,
1490 FALSE, scratch_pool));
1491 return SVN_NO_ERROR;