summaryrefslogtreecommitdiff
path: root/subversion/libsvn_fs_x/lock.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_fs_x/lock.c')
-rw-r--r--subversion/libsvn_fs_x/lock.c1492
1 files changed, 1492 insertions, 0 deletions
diff --git a/subversion/libsvn_fs_x/lock.c b/subversion/libsvn_fs_x/lock.c
new file mode 100644
index 0000000..6819f63
--- /dev/null
+++ b/subversion/libsvn_fs_x/lock.c
@@ -0,0 +1,1492 @@
+/* lock.c : functions for manipulating filesystem locks.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_fs.h"
+#include "svn_hash.h"
+#include "svn_time.h"
+#include "svn_utf.h"
+
+#include <apr_uuid.h>
+#include <apr_file_io.h>
+#include <apr_file_info.h>
+
+#include "lock.h"
+#include "tree.h"
+#include "fs_x.h"
+#include "transaction.h"
+#include "util.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "private/svn_fs_util.h"
+#include "private/svn_fspath.h"
+#include "private/svn_sorts_private.h"
+#include "svn_private_config.h"
+
+/* Names of hash keys used to store a lock for writing to disk. */
+#define PATH_KEY "path"
+#define TOKEN_KEY "token"
+#define OWNER_KEY "owner"
+#define CREATION_DATE_KEY "creation_date"
+#define EXPIRATION_DATE_KEY "expiration_date"
+#define COMMENT_KEY "comment"
+#define IS_DAV_COMMENT_KEY "is_dav_comment"
+#define CHILDREN_KEY "children"
+
+/* Number of characters from the head of a digest file name used to
+ calculate a subdirectory in which to drop that file. */
+#define DIGEST_SUBDIR_LEN 3
+
+
+
+/*** Generic helper functions. ***/
+
+/* Set *DIGEST to the MD5 hash of STR. */
+static svn_error_t *
+make_digest(const char **digest,
+ const char *str,
+ apr_pool_t *pool)
+{
+ svn_checksum_t *checksum;
+
+ SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, str, strlen(str), pool));
+
+ *digest = svn_checksum_to_cstring_display(checksum, pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Set the value of KEY (whose size is KEY_LEN, or APR_HASH_KEY_STRING
+ if unknown) to an svn_string_t-ized version of VALUE (whose size is
+ VALUE_LEN, or APR_HASH_KEY_STRING if unknown) in HASH. The value
+ will be allocated in POOL; KEY will not be duped. If either KEY or VALUE
+ is NULL, this function will do nothing. */
+static void
+hash_store(apr_hash_t *hash,
+ const char *key,
+ apr_ssize_t key_len,
+ const char *value,
+ apr_ssize_t value_len,
+ apr_pool_t *pool)
+{
+ if (! (key && value))
+ return;
+ if (value_len == APR_HASH_KEY_STRING)
+ value_len = strlen(value);
+ apr_hash_set(hash, key, key_len,
+ svn_string_ncreate(value, value_len, pool));
+}
+
+
+/* Fetch the value of KEY from HASH, returning only the cstring data
+ of that value (if it exists). */
+static const char *
+hash_fetch(apr_hash_t *hash,
+ const char *key)
+{
+ svn_string_t *str = svn_hash_gets(hash, key);
+ return str ? str->data : NULL;
+}
+
+
+/* SVN_ERR_FS_CORRUPT: the lockfile for PATH in FS is corrupt. */
+static svn_error_t *
+err_corrupt_lockfile(const char *fs_path, const char *path)
+{
+ return
+ svn_error_createf(
+ SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt lockfile for path '%s' in filesystem '%s'"),
+ path, fs_path);
+}
+
+
+/*** Digest file handling functions. ***/
+
+/* Return the path of the lock/entries file for which DIGEST is the
+ hashed repository relative path. */
+static const char *
+digest_path_from_digest(const char *fs_path,
+ const char *digest,
+ apr_pool_t *pool)
+{
+ return svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
+ apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN),
+ digest, SVN_VA_NULL);
+}
+
+
+/* Set *DIGEST_PATH to the path to the lock/entries digest file associate
+ with PATH, where PATH is the path to the lock file or lock entries file
+ in FS. */
+static svn_error_t *
+digest_path_from_path(const char **digest_path,
+ const char *fs_path,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const char *digest;
+ SVN_ERR(make_digest(&digest, path, pool));
+ *digest_path = svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
+ apr_pstrmemdup(pool, digest,
+ DIGEST_SUBDIR_LEN),
+ digest, SVN_VA_NULL);
+ return SVN_NO_ERROR;
+}
+
+
+/* Write to DIGEST_PATH a representation of CHILDREN (which may be
+ empty, if the versioned path in FS represented by DIGEST_PATH has
+ no children) and LOCK (which may be NULL if that versioned path is
+ lock itself locked). Set the permissions of DIGEST_PATH to those of
+ PERMS_REFERENCE. Use POOL for temporary allocations.
+ */
+static svn_error_t *
+write_digest_file(apr_hash_t *children,
+ svn_lock_t *lock,
+ const char *fs_path,
+ const char *digest_path,
+ const char *perms_reference,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ svn_stream_t *stream;
+ apr_hash_index_t *hi;
+ apr_hash_t *hash = apr_hash_make(scratch_pool);
+ const char *tmp_path;
+
+ SVN_ERR(svn_fs_x__ensure_dir_exists(svn_dirent_join(fs_path, PATH_LOCKS_DIR,
+ scratch_pool),
+ fs_path, scratch_pool));
+ SVN_ERR(svn_fs_x__ensure_dir_exists(svn_dirent_dirname(digest_path,
+ scratch_pool),
+ fs_path, scratch_pool));
+
+ if (lock)
+ {
+ const char *creation_date = NULL, *expiration_date = NULL;
+ if (lock->creation_date)
+ creation_date = svn_time_to_cstring(lock->creation_date,
+ scratch_pool);
+ if (lock->expiration_date)
+ expiration_date = svn_time_to_cstring(lock->expiration_date,
+ scratch_pool);
+
+ hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1,
+ lock->path, APR_HASH_KEY_STRING, scratch_pool);
+ hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1,
+ lock->token, APR_HASH_KEY_STRING, scratch_pool);
+ hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1,
+ lock->owner, APR_HASH_KEY_STRING, scratch_pool);
+ hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1,
+ lock->comment, APR_HASH_KEY_STRING, scratch_pool);
+ hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1,
+ lock->is_dav_comment ? "1" : "0", 1, scratch_pool);
+ hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1,
+ creation_date, APR_HASH_KEY_STRING, scratch_pool);
+ hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1,
+ expiration_date, APR_HASH_KEY_STRING, scratch_pool);
+ }
+ if (apr_hash_count(children))
+ {
+ svn_stringbuf_t *children_list
+ = svn_stringbuf_create_empty(scratch_pool);
+ for (hi = apr_hash_first(scratch_pool, children);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_stringbuf_appendbytes(children_list,
+ apr_hash_this_key(hi),
+ apr_hash_this_key_len(hi));
+ svn_stringbuf_appendbyte(children_list, '\n');
+ }
+ hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1,
+ children_list->data, children_list->len, scratch_pool);
+ }
+
+ SVN_ERR(svn_stream_open_unique(&stream, &tmp_path,
+ svn_dirent_dirname(digest_path,
+ scratch_pool),
+ svn_io_file_del_none, scratch_pool,
+ scratch_pool));
+ if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR,
+ scratch_pool)))
+ {
+ svn_error_clear(svn_stream_close(stream));
+ return svn_error_createf(err->apr_err,
+ err,
+ _("Cannot write lock/entries hashfile '%s'"),
+ svn_dirent_local_style(tmp_path,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_stream_close(stream));
+ SVN_ERR(svn_io_file_rename(tmp_path, digest_path, scratch_pool));
+ SVN_ERR(svn_io_copy_perms(perms_reference, digest_path, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* Parse the file at DIGEST_PATH, populating the lock LOCK_P in that
+ file (if it exists, and if *LOCK_P is non-NULL) and the hash of
+ CHILDREN_P (if any exist, and if *CHILDREN_P is non-NULL). Use POOL
+ for all allocations. */
+static svn_error_t *
+read_digest_file(apr_hash_t **children_p,
+ svn_lock_t **lock_p,
+ const char *fs_path,
+ const char *digest_path,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ svn_lock_t *lock;
+ apr_hash_t *hash;
+ svn_stream_t *stream;
+ const char *val;
+ svn_node_kind_t kind;
+
+ if (lock_p)
+ *lock_p = NULL;
+ if (children_p)
+ *children_p = apr_hash_make(pool);
+
+ SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
+ if (kind == svn_node_none)
+ return SVN_NO_ERROR;
+
+ /* If our caller doesn't care about anything but the presence of the
+ file... whatever. */
+ if (kind == svn_node_file && !lock_p && !children_p)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_stream_open_readonly(&stream, digest_path, pool, pool));
+
+ hash = apr_hash_make(pool);
+ if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool)))
+ {
+ svn_error_clear(svn_stream_close(stream));
+ return svn_error_createf(err->apr_err,
+ err,
+ _("Can't parse lock/entries hashfile '%s'"),
+ svn_dirent_local_style(digest_path, pool));
+ }
+ SVN_ERR(svn_stream_close(stream));
+
+ /* If our caller cares, see if we have a lock path in our hash. If
+ so, we'll assume we have a lock here. */
+ val = hash_fetch(hash, PATH_KEY);
+ if (val && lock_p)
+ {
+ const char *path = val;
+
+ /* Create our lock and load it up. */
+ lock = svn_lock_create(pool);
+ lock->path = path;
+
+ if (! ((lock->token = hash_fetch(hash, TOKEN_KEY))))
+ return svn_error_trace(err_corrupt_lockfile(fs_path, path));
+
+ if (! ((lock->owner = hash_fetch(hash, OWNER_KEY))))
+ return svn_error_trace(err_corrupt_lockfile(fs_path, path));
+
+ if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY))))
+ return svn_error_trace(err_corrupt_lockfile(fs_path, path));
+ lock->is_dav_comment = (val[0] == '1');
+
+ if (! ((val = hash_fetch(hash, CREATION_DATE_KEY))))
+ return svn_error_trace(err_corrupt_lockfile(fs_path, path));
+ SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool));
+
+ if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY)))
+ SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool));
+
+ lock->comment = hash_fetch(hash, COMMENT_KEY);
+
+ *lock_p = lock;
+ }
+
+ /* If our caller cares, see if we have any children for this path. */
+ val = hash_fetch(hash, CHILDREN_KEY);
+ if (val && children_p)
+ {
+ apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool);
+ int i;
+
+ for (i = 0; i < kiddos->nelts; i++)
+ {
+ svn_hash_sets(*children_p, APR_ARRAY_IDX(kiddos, i, const char *),
+ (void *)1);
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Lock helper functions (path here are still FS paths, not on-disk
+ schema-supporting paths) ***/
+
+
+/* Write LOCK in FS to the actual OS filesystem.
+
+ Use PERMS_REFERENCE for the permissions of any digest files.
+ */
+static svn_error_t *
+set_lock(const char *fs_path,
+ svn_lock_t *lock,
+ const char *perms_reference,
+ apr_pool_t *scratch_pool)
+{
+ const char *digest_path;
+ apr_hash_t *children;
+
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, lock->path,
+ scratch_pool));
+
+ /* We could get away without reading the file as children should
+ always come back empty. */
+ SVN_ERR(read_digest_file(&children, NULL, fs_path, digest_path,
+ scratch_pool));
+
+ SVN_ERR(write_digest_file(children, lock, fs_path, digest_path,
+ perms_reference, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_lock(const char *fs_path,
+ const char *path,
+ apr_pool_t *scratch_pool)
+{
+ const char *digest_path;
+
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, scratch_pool));
+
+ SVN_ERR(svn_io_remove_file2(digest_path, TRUE, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_to_digest(const char *fs_path,
+ apr_array_header_t *paths,
+ const char *index_path,
+ const char *perms_reference,
+ apr_pool_t *scratch_pool)
+{
+ const char *index_digest_path;
+ apr_hash_t *children;
+ svn_lock_t *lock;
+ int i;
+ unsigned int original_count;
+
+ SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path,
+ scratch_pool));
+ SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path,
+ scratch_pool));
+
+ original_count = apr_hash_count(children);
+
+ for (i = 0; i < paths->nelts; ++i)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ const char *digest_path, *digest_file;
+
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, path,
+ scratch_pool));
+ digest_file = svn_dirent_basename(digest_path, NULL);
+ svn_hash_sets(children, digest_file, (void *)1);
+ }
+
+ if (apr_hash_count(children) != original_count)
+ SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
+ perms_reference, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_from_digest(const char *fs_path,
+ apr_array_header_t *paths,
+ const char *index_path,
+ const char *perms_reference,
+ apr_pool_t *scratch_pool)
+{
+ const char *index_digest_path;
+ apr_hash_t *children;
+ svn_lock_t *lock;
+ int i;
+
+ SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path,
+ scratch_pool));
+ SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path,
+ scratch_pool));
+
+ for (i = 0; i < paths->nelts; ++i)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ const char *digest_path, *digest_file;
+
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, path,
+ scratch_pool));
+ digest_file = svn_dirent_basename(digest_path, NULL);
+ svn_hash_sets(children, digest_file, NULL);
+ }
+
+ if (apr_hash_count(children) || lock)
+ SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
+ perms_reference, scratch_pool));
+ else
+ SVN_ERR(svn_io_remove_file2(index_digest_path, TRUE, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+unlock_single(svn_fs_t *fs,
+ svn_lock_t *lock,
+ apr_pool_t *pool);
+
+/* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
+ TRUE if the caller (or one of its callers) has taken out the
+ repository-wide write lock, FALSE otherwise. If MUST_EXIST is
+ not set, the function will simply return NULL in *LOCK_P instead
+ of creating an SVN_FS__ERR_NO_SUCH_LOCK error in case the lock
+ was not found (much faster). Use POOL for allocations. */
+static svn_error_t *
+get_lock(svn_lock_t **lock_p,
+ svn_fs_t *fs,
+ const char *path,
+ svn_boolean_t have_write_lock,
+ svn_boolean_t must_exist,
+ apr_pool_t *pool)
+{
+ svn_lock_t *lock = NULL;
+ const char *digest_path;
+ svn_node_kind_t kind;
+
+ SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
+ SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
+
+ *lock_p = NULL;
+ if (kind != svn_node_none)
+ SVN_ERR(read_digest_file(NULL, &lock, fs->path, digest_path, pool));
+
+ if (! lock)
+ return must_exist ? SVN_FS__ERR_NO_SUCH_LOCK(fs, path) : SVN_NO_ERROR;
+
+ /* Don't return an expired lock. */
+ if (lock->expiration_date && (apr_time_now() > lock->expiration_date))
+ {
+ /* Only remove the lock if we have the write lock.
+ Read operations shouldn't change the filesystem. */
+ if (have_write_lock)
+ SVN_ERR(unlock_single(fs, lock, pool));
+ return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
+ }
+
+ *lock_p = lock;
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
+ TRUE if the caller (or one of its callers) has taken out the
+ repository-wide write lock, FALSE otherwise. Use POOL for
+ allocations. */
+static svn_error_t *
+get_lock_helper(svn_fs_t *fs,
+ svn_lock_t **lock_p,
+ const char *path,
+ svn_boolean_t have_write_lock,
+ apr_pool_t *pool)
+{
+ svn_lock_t *lock;
+ svn_error_t *err;
+
+ err = get_lock(&lock, fs, path, have_write_lock, FALSE, pool);
+
+ /* We've deliberately decided that this function doesn't tell the
+ caller *why* the lock is unavailable. */
+ if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
+ || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)))
+ {
+ svn_error_clear(err);
+ *lock_p = NULL;
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ *lock_p = lock;
+ return SVN_NO_ERROR;
+}
+
+
+/* Baton for locks_walker(). */
+typedef struct walk_locks_baton_t
+{
+ svn_fs_get_locks_callback_t get_locks_func;
+ void *get_locks_baton;
+ svn_fs_t *fs;
+} walk_locks_baton_t;
+
+/* Implements walk_digests_callback_t. */
+static svn_error_t *
+locks_walker(void *baton,
+ const char *fs_path,
+ const char *digest_path,
+ svn_lock_t *lock,
+ svn_boolean_t have_write_lock,
+ apr_pool_t *pool)
+{
+ walk_locks_baton_t *wlb = baton;
+
+ if (lock)
+ {
+ /* Don't report an expired lock. */
+ if (lock->expiration_date == 0
+ || (apr_time_now() <= lock->expiration_date))
+ {
+ if (wlb->get_locks_func)
+ SVN_ERR(wlb->get_locks_func(wlb->get_locks_baton, lock, pool));
+ }
+ else
+ {
+ /* Only remove the lock if we have the write lock.
+ Read operations shouldn't change the filesystem. */
+ if (have_write_lock)
+ SVN_ERR(unlock_single(wlb->fs, lock, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Callback type for walk_digest_files().
+ *
+ * LOCK come from a read_digest_file(digest_path) call.
+ */
+typedef svn_error_t *(*walk_digests_callback_t)(void *baton,
+ const char *fs_path,
+ const char *digest_path,
+ svn_lock_t *lock,
+ svn_boolean_t have_write_lock,
+ apr_pool_t *pool);
+
+/* A function that calls WALK_DIGESTS_FUNC/WALK_DIGESTS_BATON for
+ all lock digest files in and under PATH in FS.
+ HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
+ has the FS write lock. */
+static svn_error_t *
+walk_digest_files(const char *fs_path,
+ const char *digest_path,
+ walk_digests_callback_t walk_digests_func,
+ void *walk_digests_baton,
+ svn_boolean_t have_write_lock,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ apr_hash_t *children;
+ apr_pool_t *subpool;
+ svn_lock_t *lock;
+
+ /* First, send up any locks in the current digest file. */
+ SVN_ERR(read_digest_file(&children, &lock, fs_path, digest_path, pool));
+
+ SVN_ERR(walk_digests_func(walk_digests_baton, fs_path, digest_path, lock,
+ have_write_lock, pool));
+
+ /* Now, report all the child entries (if any; bail otherwise). */
+ if (! apr_hash_count(children))
+ return SVN_NO_ERROR;
+ subpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
+ {
+ const char *digest = apr_hash_this_key(hi);
+ svn_pool_clear(subpool);
+
+ SVN_ERR(read_digest_file
+ (NULL, &lock, fs_path,
+ digest_path_from_digest(fs_path, digest, subpool), subpool));
+
+ SVN_ERR(walk_digests_func(walk_digests_baton, fs_path, digest_path, lock,
+ have_write_lock, subpool));
+ }
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+/* A function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
+ all locks in and under PATH in FS.
+ HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
+ has the FS write lock. */
+static svn_error_t *
+walk_locks(svn_fs_t *fs,
+ const char *digest_path,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton,
+ svn_boolean_t have_write_lock,
+ apr_pool_t *pool)
+{
+ walk_locks_baton_t wlb;
+
+ wlb.get_locks_func = get_locks_func;
+ wlb.get_locks_baton = get_locks_baton;
+ wlb.fs = fs;
+ SVN_ERR(walk_digest_files(fs->path, digest_path, locks_walker, &wlb,
+ have_write_lock, pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* Utility function: verify that a lock can be used. Interesting
+ errors returned from this function:
+
+ SVN_ERR_FS_NO_USER: No username attached to FS.
+ SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner.
+ SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK.
+ */
+static svn_error_t *
+verify_lock(svn_fs_t *fs,
+ svn_lock_t *lock)
+{
+ if ((! fs->access_ctx) || (! fs->access_ctx->username))
+ return svn_error_createf
+ (SVN_ERR_FS_NO_USER, NULL,
+ _("Cannot verify lock on path '%s'; no username available"),
+ lock->path);
+
+ else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
+ return svn_error_createf
+ (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
+ _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
+ fs->access_ctx->username, lock->path, lock->owner);
+
+ else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
+ return svn_error_createf
+ (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
+ _("Cannot verify lock on path '%s'; no matching lock-token available"),
+ lock->path);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements the svn_fs_get_locks_callback_t interface, where
+ BATON is just an svn_fs_t object. */
+static svn_error_t *
+get_locks_callback(void *baton,
+ svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ return verify_lock(baton, lock);
+}
+
+
+/* The main routine for lock enforcement, used throughout libsvn_fs_x. */
+svn_error_t *
+svn_fs_x__allow_locked_operation(const char *path,
+ svn_fs_t *fs,
+ svn_boolean_t recurse,
+ svn_boolean_t have_write_lock,
+ apr_pool_t *scratch_pool)
+{
+ path = svn_fs__canonicalize_abspath(path, scratch_pool);
+ if (recurse)
+ {
+ /* Discover all locks at or below the path. */
+ const char *digest_path;
+ SVN_ERR(digest_path_from_path(&digest_path, fs->path, path,
+ scratch_pool));
+ SVN_ERR(walk_locks(fs, digest_path, get_locks_callback,
+ fs, have_write_lock, scratch_pool));
+ }
+ else
+ {
+ /* Discover and verify any lock attached to the path. */
+ svn_lock_t *lock;
+ SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock,
+ scratch_pool));
+ if (lock)
+ SVN_ERR(verify_lock(fs, lock));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* The effective arguments for lock_body() below. */
+typedef struct lock_baton_t {
+ svn_fs_t *fs;
+ apr_array_header_t *targets;
+ apr_array_header_t *infos;
+ const char *comment;
+ svn_boolean_t is_dav_comment;
+ apr_time_t expiration_date;
+ svn_boolean_t steal_lock;
+ apr_pool_t *result_pool;
+} lock_baton_t;
+
+static svn_error_t *
+check_lock(svn_error_t **fs_err,
+ const char *path,
+ const svn_fs_lock_target_t *target,
+ lock_baton_t *lb,
+ svn_fs_root_t *root,
+ svn_revnum_t youngest_rev,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ svn_lock_t *existing_lock;
+
+ *fs_err = SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_x__check_path(&kind, root, path, pool));
+ if (kind == svn_node_dir)
+ {
+ *fs_err = SVN_FS__ERR_NOT_FILE(lb->fs, path);
+ return SVN_NO_ERROR;
+ }
+
+ /* While our locking implementation easily supports the locking of
+ nonexistent paths, we deliberately choose not to allow such madness. */
+ if (kind == svn_node_none)
+ {
+ if (SVN_IS_VALID_REVNUM(target->current_rev))
+ *fs_err = svn_error_createf(
+ SVN_ERR_FS_OUT_OF_DATE, NULL,
+ _("Path '%s' doesn't exist in HEAD revision"),
+ path);
+ else
+ *fs_err = svn_error_createf(
+ SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Path '%s' doesn't exist in HEAD revision"),
+ path);
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Is the caller attempting to lock an out-of-date working file? */
+ if (SVN_IS_VALID_REVNUM(target->current_rev))
+ {
+ svn_revnum_t created_rev;
+
+ if (target->current_rev > youngest_rev)
+ {
+ *fs_err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("No such revision %ld"),
+ target->current_rev);
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_fs_x__node_created_rev(&created_rev, root, path,
+ pool));
+
+ /* SVN_INVALID_REVNUM means the path doesn't exist. So
+ apparently somebody is trying to lock something in their
+ working copy, but somebody else has deleted the thing
+ from HEAD. That counts as being 'out of date'. */
+ if (! SVN_IS_VALID_REVNUM(created_rev))
+ {
+ *fs_err = svn_error_createf
+ (SVN_ERR_FS_OUT_OF_DATE, NULL,
+ _("Path '%s' doesn't exist in HEAD revision"), path);
+
+ return SVN_NO_ERROR;
+ }
+
+ if (target->current_rev < created_rev)
+ {
+ *fs_err = svn_error_createf
+ (SVN_ERR_FS_OUT_OF_DATE, NULL,
+ _("Lock failed: newer version of '%s' exists"), path);
+
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* If the caller provided a TOKEN, we *really* need to see
+ if a lock already exists with that token, and if so, verify that
+ the lock's path matches PATH. Otherwise we run the risk of
+ breaking the 1-to-1 mapping of lock tokens to locked paths. */
+ /* ### TODO: actually do this check. This is tough, because the
+ schema doesn't supply a lookup-by-token mechanism. */
+
+ /* Is the path already locked?
+
+ Note that this next function call will automatically ignore any
+ errors about {the path not existing as a key, the path's token
+ not existing as a key, the lock just having been expired}. And
+ that's totally fine. Any of these three errors are perfectly
+ acceptable to ignore; it means that the path is now free and
+ clear for locking, because the fsx funcs just cleared out both
+ of the tables for us. */
+ SVN_ERR(get_lock_helper(lb->fs, &existing_lock, path, TRUE, pool));
+ if (existing_lock)
+ {
+ if (! lb->steal_lock)
+ {
+ /* Sorry, the path is already locked. */
+ *fs_err = SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+typedef struct lock_info_t {
+ const char *path;
+ const char *component;
+ svn_lock_t *lock;
+ svn_error_t *fs_err;
+} lock_info_t;
+
+/* The body of svn_fs_x__lock(), which see.
+
+ BATON is a 'lock_baton_t *' holding the effective arguments.
+ BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
+ path, mapping canonical path to 'svn_fs_lock_target_t'. Set
+ BATON->infos to an array of 'lock_info_t' holding the results. For
+ the other arguments, see svn_fs_lock_many().
+
+ This implements the svn_fs_x__with_write_lock() 'body' callback
+ type, and assumes that the write lock is held.
+ */
+static svn_error_t *
+lock_body(void *baton, apr_pool_t *pool)
+{
+ lock_baton_t *lb = baton;
+ svn_fs_root_t *root;
+ svn_revnum_t youngest;
+ const char *rev_0_path;
+ int i, outstanding = 0;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ lb->infos = apr_array_make(lb->result_pool, lb->targets->nelts,
+ sizeof(lock_info_t));
+
+ /* Until we implement directory locks someday, we only allow locks
+ on files or non-existent paths. */
+ /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
+ library dependencies, which are not portable. */
+ SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
+ SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
+
+ for (i = 0; i < lb->targets->nelts; ++i)
+ {
+ const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
+ svn_sort__item_t);
+ const svn_fs_lock_target_t *target = item->value;
+ lock_info_t info;
+
+ svn_pool_clear(iterpool);
+
+ info.path = item->key;
+ SVN_ERR(check_lock(&info.fs_err, info.path, target, lb, root,
+ youngest, iterpool));
+ info.lock = NULL;
+ info.component = NULL;
+ APR_ARRAY_PUSH(lb->infos, lock_info_t) = info;
+ if (!info.fs_err)
+ ++outstanding;
+ }
+
+ rev_0_path = svn_fs_x__path_rev_absolute(lb->fs, 0, pool);
+
+ /* Given the paths:
+
+ /foo/bar/f
+ /foo/bar/g
+ /zig/x
+
+ we loop through repeatedly. The first pass sees '/' on all paths
+ and writes the '/' index. The second pass sees '/foo' twice and
+ writes that index followed by '/zig' and that index. The third
+ pass sees '/foo/bar' twice and writes that index, and then writes
+ the lock for '/zig/x'. The fourth pass writes the locks for
+ '/foo/bar/f' and '/foo/bar/g'.
+
+ Writing indices before locks is correct: if interrupted it leaves
+ indices without locks rather than locks without indices. An
+ index without a lock is consistent in that it always shows up as
+ unlocked in svn_fs_x__allow_locked_operation. A lock without an
+ index is inconsistent, svn_fs_x__allow_locked_operation will
+ show locked on the file but unlocked on the parent. */
+
+
+ while (outstanding)
+ {
+ const char *last_path = NULL;
+ apr_array_header_t *paths;
+
+ svn_pool_clear(iterpool);
+ paths = apr_array_make(iterpool, 1, sizeof(const char *));
+
+ for (i = 0; i < lb->infos->nelts; ++i)
+ {
+ lock_info_t *info = &APR_ARRAY_IDX(lb->infos, i, lock_info_t);
+ const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
+ svn_sort__item_t);
+ const svn_fs_lock_target_t *target = item->value;
+
+ if (!info->fs_err && !info->lock)
+ {
+ if (!info->component)
+ {
+ info->component = info->path;
+ APR_ARRAY_PUSH(paths, const char *) = info->path;
+ last_path = "/";
+ }
+ else
+ {
+ info->component = strchr(info->component + 1, '/');
+ if (!info->component)
+ {
+ /* The component is a path to lock, this cannot
+ match a previous path that need to be indexed. */
+ if (paths->nelts)
+ {
+ SVN_ERR(add_to_digest(lb->fs->path, paths, last_path,
+ rev_0_path, iterpool));
+ apr_array_clear(paths);
+ last_path = NULL;
+ }
+
+ info->lock = svn_lock_create(lb->result_pool);
+ if (target->token)
+ info->lock->token = target->token;
+ else
+ SVN_ERR(svn_fs_x__generate_lock_token(
+ &(info->lock->token), lb->fs,
+ lb->result_pool));
+ info->lock->path = info->path;
+ info->lock->owner = lb->fs->access_ctx->username;
+ info->lock->comment = lb->comment;
+ info->lock->is_dav_comment = lb->is_dav_comment;
+ info->lock->creation_date = apr_time_now();
+ info->lock->expiration_date = lb->expiration_date;
+
+ info->fs_err = set_lock(lb->fs->path, info->lock,
+ rev_0_path, iterpool);
+ --outstanding;
+ }
+ else
+ {
+ /* The component is a path to an index. */
+ apr_size_t len = info->component - info->path;
+
+ if (last_path
+ && (strncmp(last_path, info->path, len)
+ || strlen(last_path) != len))
+ {
+ /* No match to the previous paths to index. */
+ SVN_ERR(add_to_digest(lb->fs->path, paths, last_path,
+ rev_0_path, iterpool));
+ apr_array_clear(paths);
+ last_path = NULL;
+ }
+ APR_ARRAY_PUSH(paths, const char *) = info->path;
+ if (!last_path)
+ last_path = apr_pstrndup(iterpool, info->path, len);
+ }
+ }
+ }
+
+ if (last_path && i == lb->infos->nelts - 1)
+ SVN_ERR(add_to_digest(lb->fs->path, paths, last_path,
+ rev_0_path, iterpool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* The effective arguments for unlock_body() below. */
+typedef struct unlock_baton_t {
+ svn_fs_t *fs;
+ apr_array_header_t *targets;
+ apr_array_header_t *infos;
+ /* Set skip_check TRUE to prevent the checks that set infos[].fs_err. */
+ svn_boolean_t skip_check;
+ svn_boolean_t break_lock;
+ apr_pool_t *result_pool;
+} unlock_baton_t;
+
+static svn_error_t *
+check_unlock(svn_error_t **fs_err,
+ const char *path,
+ const char *token,
+ unlock_baton_t *ub,
+ svn_fs_root_t *root,
+ apr_pool_t *pool)
+{
+ svn_lock_t *lock;
+
+ *fs_err = get_lock(&lock, ub->fs, path, TRUE, TRUE, pool);
+ if (!*fs_err && !ub->break_lock)
+ {
+ if (strcmp(token, lock->token) != 0)
+ *fs_err = SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, path);
+ else if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
+ *fs_err = SVN_FS__ERR_LOCK_OWNER_MISMATCH(ub->fs,
+ ub->fs->access_ctx->username,
+ lock->owner);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+typedef struct unlock_info_t {
+ const char *path;
+ const char *component;
+ svn_error_t *fs_err;
+ svn_boolean_t done;
+ int components;
+} unlock_info_t;
+
+/* The body of svn_fs_x__unlock(), which see.
+
+ BATON is a 'unlock_baton_t *' holding the effective arguments.
+ BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
+ path, mapping canonical path to (const char *) token. Set
+ BATON->infos to an array of 'unlock_info_t' results. For the other
+ arguments, see svn_fs_unlock_many().
+
+ This implements the svn_fs_x__with_write_lock() 'body' callback
+ type, and assumes that the write lock is held.
+ */
+static svn_error_t *
+unlock_body(void *baton, apr_pool_t *pool)
+{
+ unlock_baton_t *ub = baton;
+ svn_fs_root_t *root;
+ svn_revnum_t youngest;
+ const char *rev_0_path;
+ int i, max_components = 0, outstanding = 0;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ ub->infos = apr_array_make(ub->result_pool, ub->targets->nelts,
+ sizeof( unlock_info_t));
+
+ SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool));
+ SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool));
+
+ for (i = 0; i < ub->targets->nelts; ++i)
+ {
+ const svn_sort__item_t *item = &APR_ARRAY_IDX(ub->targets, i,
+ svn_sort__item_t);
+ const char *token = item->value;
+ unlock_info_t info = { 0 };
+
+ svn_pool_clear(iterpool);
+
+ info.path = item->key;
+ if (!ub->skip_check)
+ SVN_ERR(check_unlock(&info.fs_err, info.path, token, ub, root,
+ iterpool));
+ if (!info.fs_err)
+ {
+ const char *s;
+
+ info.components = 1;
+ info.component = info.path;
+ while((s = strchr(info.component + 1, '/')))
+ {
+ info.component = s;
+ ++info.components;
+ }
+
+ if (info.components > max_components)
+ max_components = info.components;
+
+ ++outstanding;
+ }
+ APR_ARRAY_PUSH(ub->infos, unlock_info_t) = info;
+ }
+
+ rev_0_path = svn_fs_x__path_rev_absolute(ub->fs, 0, pool);
+
+ for (i = max_components; i >= 0; --i)
+ {
+ const char *last_path = NULL;
+ apr_array_header_t *paths;
+ int j;
+
+ svn_pool_clear(iterpool);
+ paths = apr_array_make(pool, 1, sizeof(const char *));
+
+ for (j = 0; j < ub->infos->nelts; ++j)
+ {
+ unlock_info_t *info = &APR_ARRAY_IDX(ub->infos, j, unlock_info_t);
+
+ if (!info->fs_err && info->path)
+ {
+
+ if (info->components == i)
+ {
+ SVN_ERR(delete_lock(ub->fs->path, info->path, iterpool));
+ info->done = TRUE;
+ }
+ else if (info->components > i)
+ {
+ apr_size_t len = info->component - info->path;
+
+ if (last_path
+ && strcmp(last_path, "/")
+ && (strncmp(last_path, info->path, len)
+ || strlen(last_path) != len))
+ {
+ SVN_ERR(delete_from_digest(ub->fs->path, paths, last_path,
+ rev_0_path, iterpool));
+ apr_array_clear(paths);
+ last_path = NULL;
+ }
+ APR_ARRAY_PUSH(paths, const char *) = info->path;
+ if (!last_path)
+ {
+ if (info->component > info->path)
+ last_path = apr_pstrndup(pool, info->path, len);
+ else
+ last_path = "/";
+ }
+
+ if (info->component > info->path)
+ {
+ --info->component;
+ while(info->component[0] != '/')
+ --info->component;
+ }
+ }
+ }
+
+ if (last_path && j == ub->infos->nelts - 1)
+ SVN_ERR(delete_from_digest(ub->fs->path, paths, last_path,
+ rev_0_path, iterpool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Unlock the lock described by LOCK->path and LOCK->token in FS.
+
+ This assumes that the write lock is held.
+ */
+static svn_error_t *
+unlock_single(svn_fs_t *fs,
+ svn_lock_t *lock,
+ apr_pool_t *scratch_pool)
+{
+ unlock_baton_t ub;
+ svn_sort__item_t item;
+ apr_array_header_t *targets = apr_array_make(scratch_pool, 1,
+ sizeof(svn_sort__item_t));
+ item.key = lock->path;
+ item.klen = strlen(item.key);
+ item.value = (char*)lock->token;
+ APR_ARRAY_PUSH(targets, svn_sort__item_t) = item;
+
+ ub.fs = fs;
+ ub.targets = targets;
+ ub.skip_check = TRUE;
+ ub.result_pool = scratch_pool;
+
+ /* No ub.infos[].fs_err error because skip_check is TRUE. */
+ SVN_ERR(unlock_body(&ub, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** Public API implementations ***/
+
+svn_error_t *
+svn_fs_x__lock(svn_fs_t *fs,
+ apr_hash_t *targets,
+ const char *comment,
+ svn_boolean_t is_dav_comment,
+ apr_time_t expiration_date,
+ svn_boolean_t steal_lock,
+ svn_fs_lock_callback_t lock_callback,
+ void *lock_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ lock_baton_t lb;
+ apr_array_header_t *sorted_targets;
+ apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+ svn_error_t *err, *cb_err = SVN_NO_ERROR;
+ int i;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ /* We need to have a username attached to the fs. */
+ if (!fs->access_ctx || !fs->access_ctx->username)
+ return SVN_FS__ERR_NO_USER(fs);
+
+ /* The FS locking API allows both canonical and non-canonical
+ paths which means that the same canonical path could be
+ represented more than once in the TARGETS hash. We just keep
+ one, choosing one with a token if possible. */
+ for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = apr_hash_this_key(hi);
+ const svn_fs_lock_target_t *target = apr_hash_this_val(hi);
+ const svn_fs_lock_target_t *other;
+
+ path = svn_fspath__canonicalize(path, result_pool);
+ other = svn_hash_gets(canonical_targets, path);
+
+ if (!other || (!other->token && target->token))
+ svn_hash_sets(canonical_targets, path, target);
+ }
+
+ sorted_targets = svn_sort__hash(canonical_targets,
+ svn_sort_compare_items_as_paths,
+ scratch_pool);
+
+ lb.fs = fs;
+ lb.targets = sorted_targets;
+ lb.comment = comment;
+ lb.is_dav_comment = is_dav_comment;
+ lb.expiration_date = expiration_date;
+ lb.steal_lock = steal_lock;
+ lb.result_pool = result_pool;
+
+ iterpool = svn_pool_create(scratch_pool);
+ err = svn_fs_x__with_write_lock(fs, lock_body, &lb, iterpool);
+ for (i = 0; i < lb.infos->nelts; ++i)
+ {
+ struct lock_info_t *info = &APR_ARRAY_IDX(lb.infos, i,
+ struct lock_info_t);
+ svn_pool_clear(iterpool);
+ if (!cb_err && lock_callback)
+ {
+ if (!info->lock && !info->fs_err)
+ info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
+ 0, _("Failed to lock '%s'"),
+ info->path);
+
+ cb_err = lock_callback(lock_baton, info->path, info->lock,
+ info->fs_err, iterpool);
+ }
+ svn_error_clear(info->fs_err);
+ }
+ svn_pool_destroy(iterpool);
+
+ if (err && cb_err)
+ svn_error_compose(err, cb_err);
+ else if (!err)
+ err = cb_err;
+
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_fs_x__generate_lock_token(const char **token,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ /* Notice that 'fs' is currently unused. But perhaps someday, we'll
+ want to use the fs UUID + some incremented number? For now, we
+ generate a URI that matches the DAV RFC. We could change this to
+ some other URI scheme someday, if we wish. */
+ *token = apr_pstrcat(pool, "opaquelocktoken:",
+ svn_uuid_generate(pool), SVN_VA_NULL);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__unlock(svn_fs_t *fs,
+ apr_hash_t *targets,
+ svn_boolean_t break_lock,
+ svn_fs_lock_callback_t lock_callback,
+ void *lock_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ unlock_baton_t ub;
+ apr_array_header_t *sorted_targets;
+ apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+ svn_error_t *err, *cb_err = SVN_NO_ERROR;
+ int i;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ /* We need to have a username attached to the fs. */
+ if (!fs->access_ctx || !fs->access_ctx->username)
+ return SVN_FS__ERR_NO_USER(fs);
+
+ for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = apr_hash_this_key(hi);
+ const char *token = apr_hash_this_val(hi);
+ const char *other;
+
+ path = svn_fspath__canonicalize(path, result_pool);
+ other = svn_hash_gets(canonical_targets, path);
+
+ if (!other)
+ svn_hash_sets(canonical_targets, path, token);
+ }
+
+ sorted_targets = svn_sort__hash(canonical_targets,
+ svn_sort_compare_items_as_paths,
+ scratch_pool);
+
+ ub.fs = fs;
+ ub.targets = sorted_targets;
+ ub.skip_check = FALSE;
+ ub.break_lock = break_lock;
+ ub.result_pool = result_pool;
+
+ iterpool = svn_pool_create(scratch_pool);
+ err = svn_fs_x__with_write_lock(fs, unlock_body, &ub, iterpool);
+ for (i = 0; i < ub.infos->nelts; ++i)
+ {
+ unlock_info_t *info = &APR_ARRAY_IDX(ub.infos, i, unlock_info_t);
+ svn_pool_clear(iterpool);
+ if (!cb_err && lock_callback)
+ {
+ if (!info->done && !info->fs_err)
+ info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
+ 0, _("Failed to unlock '%s'"),
+ info->path);
+ cb_err = lock_callback(lock_baton, info->path, NULL, info->fs_err,
+ iterpool);
+ }
+ svn_error_clear(info->fs_err);
+ }
+ svn_pool_destroy(iterpool);
+
+ if (err && cb_err)
+ svn_error_compose(err, cb_err);
+ else if (!err)
+ err = cb_err;
+
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_fs_x__get_lock(svn_lock_t **lock_p,
+ svn_fs_t *fs,
+ const char *path,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+ path = svn_fs__canonicalize_abspath(path, pool);
+ return get_lock_helper(fs, lock_p, path, FALSE, pool);
+}
+
+
+/* Baton for get_locks_filter_func(). */
+typedef struct get_locks_filter_baton_t
+{
+ const char *path;
+ svn_depth_t requested_depth;
+ svn_fs_get_locks_callback_t get_locks_func;
+ void *get_locks_baton;
+
+} get_locks_filter_baton_t;
+
+
+/* A wrapper for the GET_LOCKS_FUNC passed to svn_fs_x__get_locks()
+ which filters out locks on paths that aren't within
+ BATON->requested_depth of BATON->path before called
+ BATON->get_locks_func() with BATON->get_locks_baton.
+
+ NOTE: See issue #3660 for details about how the FSX lock
+ management code is inconsistent. Until that inconsistency is
+ resolved, we take this filtering approach rather than honoring
+ depth requests closer to the crawling code. In other words, once
+ we decide how to resolve issue #3660, there might be a more
+ performant way to honor the depth passed to svn_fs_x__get_locks(). */
+static svn_error_t *
+get_locks_filter_func(void *baton,
+ svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ get_locks_filter_baton_t *b = baton;
+
+ /* Filter out unwanted paths. Since Subversion only allows
+ locks on files, we can treat depth=immediates the same as
+ depth=files for filtering purposes. Meaning, we'll keep
+ this lock if:
+
+ a) its path is the very path we queried, or
+ b) we've asked for a fully recursive answer, or
+ c) we've asked for depth=files or depth=immediates, and this
+ lock is on an immediate child of our query path.
+ */
+ if ((strcmp(b->path, lock->path) == 0)
+ || (b->requested_depth == svn_depth_infinity))
+ {
+ SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
+ }
+ else if ((b->requested_depth == svn_depth_files) ||
+ (b->requested_depth == svn_depth_immediates))
+ {
+ const char *rel_uri = svn_fspath__skip_ancestor(b->path, lock->path);
+ if (rel_uri && (svn_path_component_count(rel_uri) == 1))
+ SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__get_locks(svn_fs_t *fs,
+ const char *path,
+ svn_depth_t depth,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *digest_path;
+ get_locks_filter_baton_t glfb;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+ path = svn_fs__canonicalize_abspath(path, scratch_pool);
+
+ glfb.path = path;
+ glfb.requested_depth = depth;
+ glfb.get_locks_func = get_locks_func;
+ glfb.get_locks_baton = get_locks_baton;
+
+ /* Get the top digest path in our tree of interest, and then walk it. */
+ SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, scratch_pool));
+ SVN_ERR(walk_locks(fs, digest_path, get_locks_filter_func, &glfb,
+ FALSE, scratch_pool));
+ return SVN_NO_ERROR;
+}