summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_fs_base/lock.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_fs_base/lock.c')
-rw-r--r--subversion/libsvn_fs_base/lock.c594
1 files changed, 594 insertions, 0 deletions
diff --git a/subversion/libsvn_fs_base/lock.c b/subversion/libsvn_fs_base/lock.c
new file mode 100644
index 0000000..79f72cc
--- /dev/null
+++ b/subversion/libsvn_fs_base/lock.c
@@ -0,0 +1,594 @@
+/* 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_hash.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_fs.h"
+#include "svn_private_config.h"
+
+#include <apr_uuid.h>
+
+#include "lock.h"
+#include "tree.h"
+#include "err.h"
+#include "bdb/locks-table.h"
+#include "bdb/lock-tokens-table.h"
+#include "util/fs_skels.h"
+#include "../libsvn_fs/fs-loader.h"
+#include "private/svn_fs_util.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_dep_compat.h"
+
+
+/* Add LOCK and its associated LOCK_TOKEN (associated with PATH) as
+ part of TRAIL. */
+static svn_error_t *
+add_lock_and_token(svn_lock_t *lock,
+ const char *lock_token,
+ const char *path,
+ trail_t *trail)
+{
+ SVN_ERR(svn_fs_bdb__lock_add(trail->fs, lock_token, lock,
+ trail, trail->pool));
+ return svn_fs_bdb__lock_token_add(trail->fs, path, lock_token,
+ trail, trail->pool);
+}
+
+
+/* Delete LOCK_TOKEN and its corresponding lock (associated with PATH,
+ whose KIND is supplied), as part of TRAIL. */
+static svn_error_t *
+delete_lock_and_token(const char *lock_token,
+ const char *path,
+ trail_t *trail)
+{
+ SVN_ERR(svn_fs_bdb__lock_delete(trail->fs, lock_token,
+ trail, trail->pool));
+ return svn_fs_bdb__lock_token_delete(trail->fs, path,
+ trail, trail->pool);
+}
+
+
+struct lock_args
+{
+ svn_lock_t **lock_p;
+ const char *path;
+ const char *token;
+ const char *comment;
+ svn_boolean_t is_dav_comment;
+ svn_boolean_t steal_lock;
+ apr_time_t expiration_date;
+ svn_revnum_t current_rev;
+};
+
+
+static svn_error_t *
+txn_body_lock(void *baton, trail_t *trail)
+{
+ struct lock_args *args = baton;
+ svn_node_kind_t kind = svn_node_file;
+ svn_lock_t *existing_lock;
+ svn_lock_t *lock;
+
+ SVN_ERR(svn_fs_base__get_path_kind(&kind, args->path, trail, trail->pool));
+
+ /* Until we implement directory locks someday, we only allow locks
+ on files or non-existent paths. */
+ if (kind == svn_node_dir)
+ return SVN_FS__ERR_NOT_FILE(trail->fs, args->path);
+
+ /* 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(args->current_rev))
+ return svn_error_createf(
+ SVN_ERR_FS_OUT_OF_DATE, NULL,
+ _("Path '%s' doesn't exist in HEAD revision"),
+ args->path);
+ else
+ return svn_error_createf(
+ SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Path '%s' doesn't exist in HEAD revision"),
+ args->path);
+ }
+
+ /* There better be a username attached to the fs. */
+ if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
+ return SVN_FS__ERR_NO_USER(trail->fs);
+
+ /* Is the caller attempting to lock an out-of-date working file? */
+ if (SVN_IS_VALID_REVNUM(args->current_rev))
+ {
+ svn_revnum_t created_rev;
+ SVN_ERR(svn_fs_base__get_path_created_rev(&created_rev, args->path,
+ trail, trail->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))
+ return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
+ "Path '%s' doesn't exist in HEAD revision",
+ args->path);
+
+ if (args->current_rev < created_rev)
+ return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
+ "Lock failed: newer version of '%s' exists",
+ args->path);
+ }
+
+ /* 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. */
+ if (args->token)
+ {
+ svn_lock_t *lock_from_token;
+ svn_error_t *err = svn_fs_bdb__lock_get(&lock_from_token, trail->fs,
+ args->token, trail,
+ trail->pool);
+ if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
+ || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
+ {
+ svn_error_clear(err);
+ }
+ else
+ {
+ SVN_ERR(err);
+ if (strcmp(lock_from_token->path, args->path) != 0)
+ return svn_error_create(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
+ "Lock failed: token refers to existing "
+ "lock with non-matching path.");
+ }
+ }
+
+ /* 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 bdb funcs just cleared out both
+ of the tables for us. */
+ SVN_ERR(svn_fs_base__get_lock_helper(&existing_lock, args->path,
+ trail, trail->pool));
+ if (existing_lock)
+ {
+ if (! args->steal_lock)
+ {
+ /* Sorry, the path is already locked. */
+ return SVN_FS__ERR_PATH_ALREADY_LOCKED(trail->fs,
+ existing_lock);
+ }
+ else
+ {
+ /* ARGS->steal_lock is set, so fs_username is "stealing" the
+ lock from lock->owner. Destroy the existing lock. */
+ SVN_ERR(delete_lock_and_token(existing_lock->token,
+ existing_lock->path, trail));
+ }
+ }
+
+ /* Create a new lock, and add it to the tables. */
+ lock = svn_lock_create(trail->pool);
+ if (args->token)
+ lock->token = apr_pstrdup(trail->pool, args->token);
+ else
+ SVN_ERR(svn_fs_base__generate_lock_token(&(lock->token), trail->fs,
+ trail->pool));
+ lock->path = apr_pstrdup(trail->pool, args->path);
+ lock->owner = apr_pstrdup(trail->pool, trail->fs->access_ctx->username);
+ lock->comment = apr_pstrdup(trail->pool, args->comment);
+ lock->is_dav_comment = args->is_dav_comment;
+ lock->creation_date = apr_time_now();
+ lock->expiration_date = args->expiration_date;
+ SVN_ERR(add_lock_and_token(lock, lock->token, args->path, trail));
+ *(args->lock_p) = lock;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_error_t *
+svn_fs_base__lock(svn_lock_t **lock,
+ svn_fs_t *fs,
+ const char *path,
+ const char *token,
+ 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)
+{
+ struct lock_args args;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.lock_p = lock;
+ args.path = svn_fs__canonicalize_abspath(path, pool);
+ args.token = token;
+ args.comment = comment;
+ args.is_dav_comment = is_dav_comment;
+ args.steal_lock = steal_lock;
+ args.expiration_date = expiration_date;
+ args.current_rev = current_rev;
+
+ return svn_fs_base__retry_txn(fs, txn_body_lock, &args, FALSE, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__generate_lock_token(const char **token,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ /* 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), (char *)NULL);
+ return SVN_NO_ERROR;
+}
+
+
+struct unlock_args
+{
+ const char *path;
+ const char *token;
+ svn_boolean_t break_lock;
+};
+
+
+static svn_error_t *
+txn_body_unlock(void *baton, trail_t *trail)
+{
+ struct unlock_args *args = baton;
+ const char *lock_token;
+ svn_lock_t *lock;
+
+ /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
+ SVN_ERR(svn_fs_bdb__lock_token_get(&lock_token, trail->fs, args->path,
+ trail, trail->pool));
+
+ /* If not breaking the lock, we need to do some more checking. */
+ if (!args->break_lock)
+ {
+ /* Sanity check: The lock token must exist, and must match. */
+ if (args->token == NULL)
+ return svn_fs_base__err_no_lock_token(trail->fs, args->path);
+ else if (strcmp(lock_token, args->token) != 0)
+ return SVN_FS__ERR_NO_SUCH_LOCK(trail->fs, args->path);
+
+ SVN_ERR(svn_fs_bdb__lock_get(&lock, trail->fs, lock_token,
+ trail, trail->pool));
+
+ /* There better be a username attached to the fs. */
+ if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
+ return SVN_FS__ERR_NO_USER(trail->fs);
+
+ /* And that username better be the same as the lock's owner. */
+ if (strcmp(trail->fs->access_ctx->username, lock->owner) != 0)
+ return SVN_FS__ERR_LOCK_OWNER_MISMATCH(
+ trail->fs,
+ trail->fs->access_ctx->username,
+ lock->owner);
+ }
+
+ /* Remove a row from each of the locking tables. */
+ return delete_lock_and_token(lock_token, args->path, trail);
+}
+
+
+svn_error_t *
+svn_fs_base__unlock(svn_fs_t *fs,
+ const char *path,
+ const char *token,
+ svn_boolean_t break_lock,
+ apr_pool_t *pool)
+{
+ struct unlock_args args;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.path = svn_fs__canonicalize_abspath(path, pool);
+ args.token = token;
+ args.break_lock = break_lock;
+ return svn_fs_base__retry_txn(fs, txn_body_unlock, &args, TRUE, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__get_lock_helper(svn_lock_t **lock_p,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const char *lock_token;
+ svn_error_t *err;
+
+ err = svn_fs_bdb__lock_token_get(&lock_token, trail->fs, path,
+ trail, 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)
+ || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
+ {
+ svn_error_clear(err);
+ *lock_p = NULL;
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ /* Same situation here. */
+ err = svn_fs_bdb__lock_get(lock_p, trail->fs, lock_token, trail, pool);
+ if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
+ || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
+ {
+ svn_error_clear(err);
+ *lock_p = NULL;
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ return svn_error_trace(err);
+}
+
+
+struct lock_token_get_args
+{
+ svn_lock_t **lock_p;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_get_lock(void *baton, trail_t *trail)
+{
+ struct lock_token_get_args *args = baton;
+ return svn_fs_base__get_lock_helper(args->lock_p, args->path,
+ trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__get_lock(svn_lock_t **lock,
+ svn_fs_t *fs,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct lock_token_get_args args;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.path = svn_fs__canonicalize_abspath(path, pool);
+ args.lock_p = lock;
+ return svn_fs_base__retry_txn(fs, txn_body_get_lock, &args, FALSE, pool);
+}
+
+/* Implements `svn_fs_get_locks_callback_t', spooling lock information
+ to a stream as the filesystem provides it. BATON is an 'svn_stream_t *'
+ object pointing to the stream. We'll write the spool stream with a
+ format like so:
+
+ SKEL1_LEN "\n" SKEL1 "\n" SKEL2_LEN "\n" SKEL2 "\n" ...
+
+ where each skel is a lock skel (the same format we use to store
+ locks in the `locks' table). */
+static svn_error_t *
+spool_locks_info(void *baton,
+ svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ svn_skel_t *lock_skel;
+ svn_stream_t *stream = baton;
+ const char *skel_len;
+ svn_stringbuf_t *skel_buf;
+ apr_size_t len;
+
+ SVN_ERR(svn_fs_base__unparse_lock_skel(&lock_skel, lock, pool));
+ skel_buf = svn_skel__unparse(lock_skel, pool);
+ skel_len = apr_psprintf(pool, "%" APR_SIZE_T_FMT "\n", skel_buf->len);
+ len = strlen(skel_len);
+ SVN_ERR(svn_stream_write(stream, skel_len, &len));
+ len = skel_buf->len;
+ SVN_ERR(svn_stream_write(stream, skel_buf->data, &len));
+ len = 1;
+ return svn_stream_write(stream, "\n", &len);
+}
+
+
+struct locks_get_args
+{
+ const char *path;
+ svn_depth_t depth;
+ svn_stream_t *stream;
+};
+
+
+static svn_error_t *
+txn_body_get_locks(void *baton, trail_t *trail)
+{
+ struct locks_get_args *args = baton;
+ return svn_fs_bdb__locks_get(trail->fs, args->path, args->depth,
+ spool_locks_info, args->stream,
+ trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__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 *pool)
+{
+ struct locks_get_args args;
+ svn_stream_t *stream;
+ svn_stringbuf_t *buf;
+ svn_boolean_t eof;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.path = svn_fs__canonicalize_abspath(path, pool);
+ args.depth = depth;
+ /* Enough for 100+ locks if the comments are small. */
+ args.stream = svn_stream__from_spillbuf(4 * 1024 /* blocksize */,
+ 64 * 1024 /* maxsize */,
+ pool);
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_locks, &args, FALSE, pool));
+
+ /* Read the stream calling GET_LOCKS_FUNC(). */
+ stream = args.stream;
+
+ while (1)
+ {
+ apr_size_t len, skel_len;
+ char c, *skel_buf;
+ svn_skel_t *lock_skel;
+ svn_lock_t *lock;
+ apr_uint64_t ui64;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ /* Read a skel length line and parse it for the skel's length. */
+ SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, iterpool));
+ if (eof)
+ break;
+ err = svn_cstring_strtoui64(&ui64, buf->data, 0, APR_SIZE_MAX, 10);
+ if (err)
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, err, NULL);
+ skel_len = (apr_size_t)ui64;
+
+ /* Now read that much into a buffer. */
+ skel_buf = apr_palloc(pool, skel_len + 1);
+ SVN_ERR(svn_stream_read(stream, skel_buf, &skel_len));
+ skel_buf[skel_len] = '\0';
+
+ /* Read the extra newline that follows the skel. */
+ len = 1;
+ SVN_ERR(svn_stream_read(stream, &c, &len));
+ if (c != '\n')
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, NULL);
+
+ /* Parse the skel into a lock, and notify the caller. */
+ lock_skel = svn_skel__parse(skel_buf, skel_len, iterpool);
+ SVN_ERR(svn_fs_base__parse_lock_skel(&lock, lock_skel, iterpool));
+ SVN_ERR(get_locks_func(get_locks_baton, lock, iterpool));
+ }
+
+ SVN_ERR(svn_stream_close(stream));
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Utility function: verify that a lock can be used.
+
+ If no username is attached to the FS, return SVN_ERR_FS_NO_USER.
+
+ If the FS username doesn't match LOCK's owner, return
+ SVN_ERR_FS_LOCK_OWNER_MISMATCH.
+
+ If FS hasn't been supplied with a matching lock-token for LOCK,
+ return SVN_ERR_FS_BAD_LOCK_TOKEN.
+
+ Otherwise return SVN_NO_ERROR.
+ */
+static svn_error_t *
+verify_lock(svn_fs_t *fs,
+ svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ 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, pool);
+}
+
+
+/* The main routine for lock enforcement, used throughout libsvn_fs_base. */
+svn_error_t *
+svn_fs_base__allow_locked_operation(const char *path,
+ svn_boolean_t recurse,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ if (recurse)
+ {
+ /* Discover all locks at or below the path. */
+ SVN_ERR(svn_fs_bdb__locks_get(trail->fs, path, svn_depth_infinity,
+ get_locks_callback,
+ trail->fs, trail, pool));
+ }
+ else
+ {
+ svn_lock_t *lock;
+
+ /* Discover any lock attached to the path. */
+ SVN_ERR(svn_fs_base__get_lock_helper(&lock, path, trail, pool));
+ if (lock)
+ SVN_ERR(verify_lock(trail->fs, lock, pool));
+ }
+ return SVN_NO_ERROR;
+}
OpenPOWER on IntegriCloud