summaryrefslogtreecommitdiff
path: root/subversion/libsvn_fs_fs/lock.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_fs_fs/lock.c')
-rw-r--r--subversion/libsvn_fs_fs/lock.c944
1 files changed, 616 insertions, 328 deletions
diff --git a/subversion/libsvn_fs_fs/lock.c b/subversion/libsvn_fs_fs/lock.c
index 95bd943..c852025 100644
--- a/subversion/libsvn_fs_fs/lock.c
+++ b/subversion/libsvn_fs_fs/lock.c
@@ -20,7 +20,6 @@
* ====================================================================
*/
-
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_dirent_uri.h"
@@ -37,10 +36,12 @@
#include "lock.h"
#include "tree.h"
#include "fs_fs.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. */
@@ -102,8 +103,7 @@ hash_store(apr_hash_t *hash,
of that value (if it exists). */
static const char *
hash_fetch(apr_hash_t *hash,
- const char *key,
- apr_pool_t *pool)
+ const char *key)
{
svn_string_t *str = svn_hash_gets(hash, key);
return str ? str->data : NULL;
@@ -133,7 +133,7 @@ digest_path_from_digest(const char *fs_path,
{
return svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN),
- digest, NULL);
+ digest, SVN_VA_NULL);
}
@@ -151,7 +151,7 @@ digest_path_from_path(const char **digest_path,
*digest_path = svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
apr_pstrmemdup(pool, digest,
DIGEST_SUBDIR_LEN),
- digest, NULL);
+ digest, SVN_VA_NULL);
return SVN_NO_ERROR;
}
@@ -209,8 +209,8 @@ write_digest_file(apr_hash_t *children,
for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
{
svn_stringbuf_appendbytes(children_list,
- svn__apr_hash_index_key(hi),
- svn__apr_hash_index_klen(hi));
+ 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,
@@ -252,24 +252,23 @@ read_digest_file(apr_hash_t **children_p,
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);
- err = svn_stream_open_readonly(&stream, digest_path, pool, pool);
- if (err && APR_STATUS_IS_ENOENT(err->apr_err))
- {
- svn_error_clear(err);
- return SVN_NO_ERROR;
- }
- SVN_ERR(err);
+ 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 (! (lock_p || children_p))
- return svn_stream_close(stream);
+ 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)))
@@ -284,7 +283,7 @@ read_digest_file(apr_hash_t **children_p,
/* 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, pool);
+ val = hash_fetch(hash, PATH_KEY);
if (val && lock_p)
{
const char *path = val;
@@ -293,30 +292,30 @@ read_digest_file(apr_hash_t **children_p,
lock = svn_lock_create(pool);
lock->path = path;
- if (! ((lock->token = hash_fetch(hash, TOKEN_KEY, pool))))
+ 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, pool))))
+ 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, pool))))
+ 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, pool))))
+ 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, 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, 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, pool);
+ val = hash_fetch(hash, CHILDREN_KEY);
if (val && children_p)
{
apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool);
@@ -340,8 +339,6 @@ read_digest_file(apr_hash_t **children_p,
/* Write LOCK in FS to the actual OS filesystem.
Use PERMS_REFERENCE for the permissions of any digest files.
-
- Note: this takes an FS_PATH because it's called from the hotcopy logic.
*/
static svn_error_t *
set_lock(const char *fs_path,
@@ -349,130 +346,117 @@ set_lock(const char *fs_path,
const char *perms_reference,
apr_pool_t *pool)
{
- svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
- const char *lock_digest_path = NULL;
- apr_pool_t *subpool;
+ const char *digest_path;
+ apr_hash_t *children;
- SVN_ERR_ASSERT(lock);
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, lock->path, pool));
- /* Iterate in reverse, creating the lock for LOCK->path, and then
- just adding entries for its parent, until we reach a parent
- that's already listed in *its* parent. */
- subpool = svn_pool_create(pool);
- while (1729)
- {
- const char *digest_path, *digest_file;
- apr_hash_t *this_children;
- svn_lock_t *this_lock;
+ /* 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, pool));
- svn_pool_clear(subpool);
+ SVN_ERR(write_digest_file(children, lock, fs_path, digest_path,
+ perms_reference, pool));
- /* Calculate the DIGEST_PATH for the currently FS path, and then
- get its DIGEST_FILE basename. */
- SVN_ERR(digest_path_from_path(&digest_path, fs_path, this_path->data,
- subpool));
- digest_file = svn_dirent_basename(digest_path, subpool);
+ return SVN_NO_ERROR;
+}
- SVN_ERR(read_digest_file(&this_children, &this_lock, fs_path,
- digest_path, subpool));
+static svn_error_t *
+delete_lock(const char *fs_path,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const char *digest_path;
- /* We're either writing a new lock (first time through only) or
- a new entry (every time but the first). */
- if (lock)
- {
- this_lock = lock;
- lock = NULL;
- lock_digest_path = apr_pstrdup(pool, digest_file);
- }
- else
- {
- /* If we already have an entry for this path, we're done. */
- if (svn_hash_gets(this_children, lock_digest_path))
- break;
- svn_hash_sets(this_children, lock_digest_path, (void *)1);
- }
- SVN_ERR(write_digest_file(this_children, this_lock, fs_path,
- digest_path, perms_reference, subpool));
-
- /* Prep for next iteration, or bail if we're done. */
- if (svn_fspath__is_root(this_path->data, this_path->len))
- break;
- svn_stringbuf_set(this_path,
- svn_fspath__dirname(this_path->data, subpool));
- }
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
+
+ SVN_ERR(svn_io_remove_file2(digest_path, TRUE, pool));
- svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
-/* Delete LOCK from FS in the actual OS filesystem. */
static svn_error_t *
-delete_lock(svn_fs_t *fs,
- svn_lock_t *lock,
- apr_pool_t *pool)
+add_to_digest(const char *fs_path,
+ apr_array_header_t *paths,
+ const char *index_path,
+ const char *perms_reference,
+ apr_pool_t *pool)
{
- svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
- const char *child_to_kill = NULL;
- apr_pool_t *subpool;
+ const char *index_digest_path;
+ apr_hash_t *children;
+ svn_lock_t *lock;
+ int i;
+ unsigned int original_count;
- SVN_ERR_ASSERT(lock);
+ SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool));
- /* Iterate in reverse, deleting the lock for LOCK->path, and then
- deleting its entry as it appears in each of its parents. */
- subpool = svn_pool_create(pool);
- while (1729)
+ SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, 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;
- apr_hash_t *this_children;
- svn_lock_t *this_lock;
- svn_pool_clear(subpool);
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
+ digest_file = svn_dirent_basename(digest_path, NULL);
+ svn_hash_sets(children, digest_file, (void *)1);
+ }
- /* Calculate the DIGEST_PATH for the currently FS path, and then
- get its DIGEST_FILE basename. */
- SVN_ERR(digest_path_from_path(&digest_path, fs->path, this_path->data,
- subpool));
- digest_file = svn_dirent_basename(digest_path, subpool);
+ if (apr_hash_count(children) != original_count)
+ SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
+ perms_reference, pool));
- SVN_ERR(read_digest_file(&this_children, &this_lock, fs->path,
- digest_path, subpool));
+ return SVN_NO_ERROR;
+}
- /* Delete the lock (first time through only). */
- if (lock)
- {
- this_lock = NULL;
- lock = NULL;
- child_to_kill = apr_pstrdup(pool, digest_file);
- }
+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 *pool)
+{
+ const char *index_digest_path;
+ apr_hash_t *children;
+ svn_lock_t *lock;
+ int i;
- if (child_to_kill)
- svn_hash_sets(this_children, child_to_kill, NULL);
+ SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool));
- if (! (this_lock || apr_hash_count(this_children) != 0))
- {
- /* Special case: no goodz, no file. And remember to nix
- the entry for it in its parent. */
- SVN_ERR(svn_io_remove_file2(digest_path, FALSE, subpool));
- }
- else
- {
- const char *rev_0_path;
- SVN_ERR(svn_fs_fs__path_rev_absolute(&rev_0_path, fs, 0, pool));
- SVN_ERR(write_digest_file(this_children, this_lock, fs->path,
- digest_path, rev_0_path, subpool));
- }
+ SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool));
- /* Prep for next iteration, or bail if we're done. */
- if (svn_fspath__is_root(this_path->data, this_path->len))
- break;
- svn_stringbuf_set(this_path,
- svn_fspath__dirname(this_path->data, subpool));
+ 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, pool));
+ digest_file = svn_dirent_basename(digest_path, NULL);
+ svn_hash_sets(children, digest_file, NULL);
}
- svn_pool_destroy(subpool);
+ if (apr_hash_count(children) || lock)
+ SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
+ perms_reference, pool));
+ else
+ SVN_ERR(svn_io_remove_file2(index_digest_path, TRUE, pool));
+
return SVN_NO_ERROR;
}
+static svn_error_t *
+unlock_single(svn_fs_t *fs,
+ svn_lock_t *lock,
+ apr_pool_t *pool);
+
+/* Check if LOCK has been already expired. */
+static svn_boolean_t lock_expired(const svn_lock_t *lock)
+{
+ return lock->expiration_date && (apr_time_now() > lock->expiration_date);
+}
+
/* 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
@@ -502,12 +486,12 @@ get_lock(svn_lock_t **lock_p,
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))
+ if (lock_expired(lock))
{
/* Only remove the lock if we have the write lock.
Read operations shouldn't change the filesystem. */
if (have_write_lock)
- SVN_ERR(delete_lock(fs, lock, pool));
+ SVN_ERR(unlock_single(fs, lock, pool));
return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
}
@@ -549,69 +533,17 @@ get_lock_helper(svn_fs_t *fs,
}
-/* Baton for locks_walker(). */
-struct walk_locks_baton {
- svn_fs_get_locks_callback_t get_locks_func;
- void *get_locks_baton;
- svn_fs_t *fs;
-};
-
-/* Implements walk_digests_callback_t. */
-static svn_error_t *
-locks_walker(void *baton,
- const char *fs_path,
- const char *digest_path,
- apr_hash_t *children,
- svn_lock_t *lock,
- svn_boolean_t have_write_lock,
- apr_pool_t *pool)
-{
- struct walk_locks_baton *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(delete_lock(wlb->fs, lock, pool));
- }
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Callback type for walk_digest_files().
- *
- * CHILDREN and 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,
- apr_hash_t *children,
- svn_lock_t *lock,
- svn_boolean_t have_write_lock,
- apr_pool_t *pool);
-
-/* A recursive function that calls WALK_DIGESTS_FUNC/WALK_DIGESTS_BATON for
- all lock digest files in and under PATH in FS.
+/* 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_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)
+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)
{
apr_hash_index_t *hi;
apr_hash_t *children;
@@ -619,47 +551,46 @@ walk_digest_files(const char *fs_path,
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(read_digest_file(&children, &lock, fs->path, digest_path, pool));
- SVN_ERR(walk_digests_func(walk_digests_baton, fs_path, digest_path,
- children, lock,
- have_write_lock, pool));
+ if (lock && lock_expired(lock))
+ {
+ /* 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));
+ }
+ else if (lock)
+ {
+ SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
+ }
- /* Now, recurse on this thing's child entries (if any; bail otherwise). */
+ /* 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 = svn__apr_hash_index_key(hi);
+ const char *digest = apr_hash_this_key(hi);
svn_pool_clear(subpool);
- SVN_ERR(walk_digest_files
- (fs_path, digest_path_from_digest(fs_path, digest, subpool),
- walk_digests_func, walk_digests_baton, have_write_lock, subpool));
- }
- svn_pool_destroy(subpool);
- return SVN_NO_ERROR;
-}
-/* A recursive 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)
-{
- struct walk_locks_baton wlb;
+ SVN_ERR(read_digest_file
+ (NULL, &lock, fs->path,
+ digest_path_from_digest(fs->path, digest, subpool), subpool));
- 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));
+ if (lock && lock_expired(lock))
+ {
+ /* 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));
+ }
+ else if (lock)
+ {
+ SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
+ }
+ }
+ svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
@@ -737,70 +668,100 @@ svn_fs_fs__allow_locked_operation(const char *path,
return SVN_NO_ERROR;
}
-/* Baton used for lock_body below. */
+/* Helper function called from the lock and unlock code.
+ UPDATES is a map from "const char *" parent paths to "apr_array_header_t *"
+ arrays of child paths. For all of the parent paths of PATH this function
+ adds PATH to the corresponding array of child paths. */
+static void
+schedule_index_update(apr_hash_t *updates,
+ const char *path,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *hashpool = apr_hash_pool_get(updates);
+ const char *parent_path = path;
+
+ while (! svn_fspath__is_root(parent_path, strlen(parent_path)))
+ {
+ apr_array_header_t *children;
+
+ parent_path = svn_fspath__dirname(parent_path, scratch_pool);
+ children = svn_hash_gets(updates, parent_path);
+
+ if (! children)
+ {
+ children = apr_array_make(hashpool, 8, sizeof(const char *));
+ svn_hash_sets(updates, apr_pstrdup(hashpool, parent_path), children);
+ }
+
+ APR_ARRAY_PUSH(children, const char *) = path;
+ }
+}
+
+/* The effective arguments for lock_body() below. */
struct lock_baton {
- svn_lock_t **lock_p;
svn_fs_t *fs;
- const char *path;
- const char *token;
+ 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_revnum_t current_rev;
svn_boolean_t steal_lock;
- apr_pool_t *pool;
+ apr_pool_t *result_pool;
};
-
-/* This implements the svn_fs_fs__with_write_lock() 'body' callback
- type, and assumes that the write lock is held.
- BATON is a 'struct lock_baton *'. */
static svn_error_t *
-lock_body(void *baton, apr_pool_t *pool)
+check_lock(svn_error_t **fs_err,
+ const char *path,
+ const svn_fs_lock_target_t *target,
+ struct lock_baton *lb,
+ svn_fs_root_t *root,
+ svn_revnum_t youngest_rev,
+ apr_pool_t *pool)
{
- struct lock_baton *lb = baton;
svn_node_kind_t kind;
svn_lock_t *existing_lock;
- svn_lock_t *lock;
- svn_fs_root_t *root;
- svn_revnum_t youngest;
- const char *rev_0_path;
- /* 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));
- SVN_ERR(svn_fs_fs__check_path(&kind, root, lb->path, pool));
+ *fs_err = SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_fs__check_path(&kind, root, path, pool));
if (kind == svn_node_dir)
- return SVN_FS__ERR_NOT_FILE(lb->fs, lb->path);
+ {
+ *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(lb->current_rev))
- return svn_error_createf(
+ 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"),
- lb->path);
+ path);
else
- return svn_error_createf(
+ *fs_err = svn_error_createf(
SVN_ERR_FS_NOT_FOUND, NULL,
_("Path '%s' doesn't exist in HEAD revision"),
- lb->path);
- }
+ path);
- /* We need to have a username attached to the fs. */
- if (!lb->fs->access_ctx || !lb->fs->access_ctx->username)
- return SVN_FS__ERR_NO_USER(lb->fs);
+ return SVN_NO_ERROR;
+ }
/* Is the caller attempting to lock an out-of-date working file? */
- if (SVN_IS_VALID_REVNUM(lb->current_rev))
+ if (SVN_IS_VALID_REVNUM(target->current_rev))
{
svn_revnum_t created_rev;
- SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, lb->path,
+
+ 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_fs__node_created_rev(&created_rev, root, path,
pool));
/* SVN_INVALID_REVNUM means the path doesn't exist. So
@@ -808,14 +769,22 @@ lock_body(void *baton, apr_pool_t *pool)
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))
- return svn_error_createf
- (SVN_ERR_FS_OUT_OF_DATE, NULL,
- _("Path '%s' doesn't exist in HEAD revision"), lb->path);
-
- if (lb->current_rev < created_rev)
- return svn_error_createf
- (SVN_ERR_FS_OUT_OF_DATE, NULL,
- _("Lock failed: newer version of '%s' exists"), lb->path);
+ {
+ *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
@@ -834,116 +803,380 @@ lock_body(void *baton, apr_pool_t *pool)
acceptable to ignore; it means that the path is now free and
clear for locking, because the fsfs funcs just cleared out both
of the tables for us. */
- SVN_ERR(get_lock_helper(lb->fs, &existing_lock, lb->path, TRUE, pool));
+ 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. */
- return SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
+ *fs_err = SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
+ return SVN_NO_ERROR;
}
- else
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct lock_info_t {
+ const char *path;
+ svn_lock_t *lock;
+ svn_error_t *fs_err;
+};
+
+/* The body of svn_fs_fs__lock(), which see.
+
+ BATON is a 'struct lock_baton *' 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_fs__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)
+{
+ struct lock_baton *lb = baton;
+ svn_fs_root_t *root;
+ svn_revnum_t youngest;
+ const char *rev_0_path;
+ int i;
+ apr_hash_t *index_updates = apr_hash_make(pool);
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ /* 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);
+ struct lock_info_t info;
+
+ svn_pool_clear(iterpool);
+
+ info.path = item->key;
+ info.lock = NULL;
+ info.fs_err = SVN_NO_ERROR;
+
+ SVN_ERR(check_lock(&info.fs_err, info.path, item->value, lb, root,
+ youngest, iterpool));
+
+ /* If no error occurred while pre-checking, schedule the index updates for
+ this path. */
+ if (!info.fs_err)
+ schedule_index_update(index_updates, info.path, iterpool);
+
+ APR_ARRAY_PUSH(lb->infos, struct lock_info_t) = info;
+ }
+
+ rev_0_path = svn_fs_fs__path_rev_absolute(lb->fs, 0, pool);
+
+ /* We apply the scheduled index updates before writing the actual locks.
+
+ 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_fs__allow_locked_operation. A lock without an
+ index is inconsistent, svn_fs_fs__allow_locked_operation will
+ show locked on the file but unlocked on the parent. */
+
+ for (hi = apr_hash_first(pool, index_updates); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = apr_hash_this_key(hi);
+ apr_array_header_t *children = apr_hash_this_val(hi);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(add_to_digest(lb->fs->path, children, path, rev_0_path,
+ 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_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i, svn_sort__item_t);
+ svn_fs_lock_target_t *target = item->value;
+
+ svn_pool_clear(iterpool);
+
+ if (! info->fs_err)
{
- /* STEAL_LOCK was passed, so fs_username is "stealing" the
- lock from lock->owner. Destroy the existing lock. */
- SVN_ERR(delete_lock(lb->fs, existing_lock, pool));
+ info->lock = svn_lock_create(lb->result_pool);
+ if (target->token)
+ info->lock->token = apr_pstrdup(lb->result_pool, target->token);
+ else
+ SVN_ERR(svn_fs_fs__generate_lock_token(&(info->lock->token), lb->fs,
+ lb->result_pool));
+
+ /* The INFO->PATH is already allocated in LB->RESULT_POOL as a result
+ of svn_fspath__canonicalize() (see svn_fs_fs__lock()). */
+ info->lock->path = info->path;
+ info->lock->owner = apr_pstrdup(lb->result_pool,
+ lb->fs->access_ctx->username);
+ info->lock->comment = apr_pstrdup(lb->result_pool, 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);
}
}
- /* Create our new lock, and add it to the tables.
- Ensure that the lock is created in the correct pool. */
- lock = svn_lock_create(lb->pool);
- if (lb->token)
- lock->token = apr_pstrdup(lb->pool, lb->token);
- else
- SVN_ERR(svn_fs_fs__generate_lock_token(&(lock->token), lb->fs,
- lb->pool));
- lock->path = apr_pstrdup(lb->pool, lb->path);
- lock->owner = apr_pstrdup(lb->pool, lb->fs->access_ctx->username);
- lock->comment = apr_pstrdup(lb->pool, lb->comment);
- lock->is_dav_comment = lb->is_dav_comment;
- lock->creation_date = apr_time_now();
- lock->expiration_date = lb->expiration_date;
- SVN_ERR(svn_fs_fs__path_rev_absolute(&rev_0_path, lb->fs, 0, pool));
- SVN_ERR(set_lock(lb->fs->path, lock, rev_0_path, pool));
- *lb->lock_p = lock;
-
+ svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
-/* Baton used for unlock_body below. */
+/* The effective arguments for unlock_body() below. */
struct unlock_baton {
svn_fs_t *fs;
- const char *path;
- const char *token;
+ 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;
};
-/* This implements the svn_fs_fs__with_write_lock() 'body' callback
+static svn_error_t *
+check_unlock(svn_error_t **fs_err,
+ const char *path,
+ const char *token,
+ struct unlock_baton *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;
+}
+
+struct unlock_info_t {
+ const char *path;
+ svn_error_t *fs_err;
+ svn_boolean_t done;
+};
+
+/* The body of svn_fs_fs__unlock(), which see.
+
+ BATON is a 'struct unlock_baton *' 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_fs__with_write_lock() 'body' callback
type, and assumes that the write lock is held.
- BATON is a 'struct unlock_baton *'. */
+ */
static svn_error_t *
unlock_body(void *baton, apr_pool_t *pool)
{
struct unlock_baton *ub = baton;
- svn_lock_t *lock;
+ svn_fs_root_t *root;
+ svn_revnum_t youngest;
+ const char *rev_0_path;
+ int i;
+ apr_hash_t *indices_updates = apr_hash_make(pool);
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(pool);
- /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
- SVN_ERR(get_lock(&lock, ub->fs, ub->path, TRUE, TRUE, pool));
+ SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool));
+ SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool));
- /* Unless breaking the lock, we do some checks. */
- if (! ub->break_lock)
+ for (i = 0; i < ub->targets->nelts; ++i)
{
- /* Sanity check: the incoming token should match lock->token. */
- if (strcmp(ub->token, lock->token) != 0)
- return SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, lock->path);
-
- /* There better be a username attached to the fs. */
- if (! (ub->fs->access_ctx && ub->fs->access_ctx->username))
- return SVN_FS__ERR_NO_USER(ub->fs);
-
- /* And that username better be the same as the lock's owner. */
- if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
- return SVN_FS__ERR_LOCK_OWNER_MISMATCH(
- ub->fs, ub->fs->access_ctx->username, lock->owner);
+ const svn_sort__item_t *item = &APR_ARRAY_IDX(ub->targets, i,
+ svn_sort__item_t);
+ const char *token = item->value;
+ struct unlock_info_t info;
+
+ svn_pool_clear(iterpool);
+
+ info.path = item->key;
+ info.fs_err = SVN_NO_ERROR;
+ info.done = FALSE;
+
+ if (!ub->skip_check)
+ SVN_ERR(check_unlock(&info.fs_err, info.path, token, ub, root,
+ iterpool));
+
+ /* If no error occurred while pre-checking, schedule the index updates for
+ this path. */
+ if (!info.fs_err)
+ schedule_index_update(indices_updates, info.path, iterpool);
+
+ APR_ARRAY_PUSH(ub->infos, struct unlock_info_t) = info;
}
- /* Remove lock and lock token files. */
- return delete_lock(ub->fs, lock, pool);
+ rev_0_path = svn_fs_fs__path_rev_absolute(ub->fs, 0, pool);
+
+ /* Unlike the lock_body(), we need to delete locks *before* we start to
+ update indices. */
+
+ for (i = 0; i < ub->infos->nelts; ++i)
+ {
+ struct unlock_info_t *info = &APR_ARRAY_IDX(ub->infos, i,
+ struct unlock_info_t);
+
+ svn_pool_clear(iterpool);
+
+ if (! info->fs_err)
+ {
+ SVN_ERR(delete_lock(ub->fs->path, info->path, iterpool));
+ info->done = TRUE;
+ }
+ }
+
+ for (hi = apr_hash_first(pool, indices_updates); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = apr_hash_this_key(hi);
+ apr_array_header_t *children = apr_hash_this_val(hi);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(delete_from_digest(ub->fs->path, children, path, rev_0_path,
+ iterpool));
+ }
+
+ svn_pool_destroy(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 *pool)
+{
+ struct unlock_baton ub;
+ svn_sort__item_t item;
+ apr_array_header_t *targets = apr_array_make(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.infos = apr_array_make(pool, targets->nelts,
+ sizeof(struct unlock_info_t));
+ ub.skip_check = TRUE;
+ ub.result_pool = pool;
+
+ /* No ub.infos[].fs_err error because skip_check is TRUE. */
+ SVN_ERR(unlock_body(&ub, pool));
+
+ return SVN_NO_ERROR;
}
/*** Public API implementations ***/
svn_error_t *
-svn_fs_fs__lock(svn_lock_t **lock_p,
- svn_fs_t *fs,
- const char *path,
- const char *token,
+svn_fs_fs__lock(svn_fs_t *fs,
+ apr_hash_t *targets,
const char *comment,
svn_boolean_t is_dav_comment,
apr_time_t expiration_date,
- svn_revnum_t current_rev,
svn_boolean_t steal_lock,
- apr_pool_t *pool)
+ svn_fs_lock_callback_t lock_callback,
+ void *lock_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
struct lock_baton 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));
- path = svn_fs__canonicalize_abspath(path, pool);
- lb.lock_p = lock_p;
+ /* 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.path = path;
- lb.token = token;
+ lb.targets = sorted_targets;
+ lb.infos = apr_array_make(result_pool, sorted_targets->nelts,
+ sizeof(struct lock_info_t));
lb.comment = comment;
lb.is_dav_comment = is_dav_comment;
lb.expiration_date = expiration_date;
- lb.current_rev = current_rev;
lb.steal_lock = steal_lock;
- lb.pool = pool;
+ lb.result_pool = result_pool;
+
+ iterpool = svn_pool_create(scratch_pool);
+ err = svn_fs_fs__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_fs_fs__with_write_lock(fs, lock_body, &lb, pool);
+ return svn_error_trace(err);
}
@@ -959,29 +1192,84 @@ svn_fs_fs__generate_lock_token(const char **token,
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), (char *)NULL);
+ svn_uuid_generate(pool), SVN_VA_NULL);
return SVN_NO_ERROR;
}
-
svn_error_t *
svn_fs_fs__unlock(svn_fs_t *fs,
- const char *path,
- const char *token,
+ apr_hash_t *targets,
svn_boolean_t break_lock,
- apr_pool_t *pool)
+ svn_fs_lock_callback_t lock_callback,
+ void *lock_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
struct unlock_baton 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));
- path = svn_fs__canonicalize_abspath(path, pool);
+
+ /* 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.path = path;
- ub.token = token;
+ ub.targets = sorted_targets;
+ ub.infos = apr_array_make(result_pool, sorted_targets->nelts,
+ sizeof(struct unlock_info_t));
+ ub.skip_check = FALSE;
ub.break_lock = break_lock;
+ ub.result_pool = result_pool;
+
+ iterpool = svn_pool_create(scratch_pool);
+ err = svn_fs_fs__with_write_lock(fs, unlock_body, &ub, iterpool);
+ for (i = 0; i < ub.infos->nelts; ++i)
+ {
+ struct unlock_info_t *info = &APR_ARRAY_IDX(ub.infos, i,
+ struct 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_fs_fs__with_write_lock(fs, unlock_body, &ub, pool);
+ return svn_error_trace(err);
}