#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"
#include "revs-txns.h"
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);
}
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;
apr_pool_t *result_pool;
};
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;
*args->lock_p = NULL;
SVN_ERR(svn_fs_base__get_path_kind(&kind, args->path, trail, trail->pool));
if (kind == svn_node_dir)
return SVN_FS__ERR_NOT_FILE(trail->fs, args->path);
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);
}
if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
return SVN_FS__ERR_NO_USER(trail->fs);
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));
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 (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.");
}
}
SVN_ERR(svn_fs_base__get_lock_helper(&existing_lock, args->path,
trail, trail->pool));
if (existing_lock)
{
if (! args->steal_lock)
{
return SVN_FS__ERR_PATH_ALREADY_LOCKED(trail->fs,
existing_lock);
}
else
{
SVN_ERR(delete_lock_and_token(existing_lock->token,
existing_lock->path, trail));
}
}
lock = svn_lock_create(args->result_pool);
if (args->token)
lock->token = apr_pstrdup(args->result_pool, args->token);
else
SVN_ERR(svn_fs_base__generate_lock_token(&(lock->token), trail->fs,
args->result_pool));
lock->path = args->path;
lock->owner = apr_pstrdup(args->result_pool, trail->fs->access_ctx->username);
lock->comment = apr_pstrdup(args->result_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_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)
{
apr_hash_index_t *hi;
svn_error_t *cb_err = SVN_NO_ERROR;
svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
SVN_ERR(svn_fs__check_fs(fs, TRUE));
SVN_ERR(svn_fs_base__youngest_rev(&youngest_rev, fs, scratch_pool));
for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
{
struct lock_args args;
const char *path = apr_hash_this_key(hi);
const svn_fs_lock_target_t *target = apr_hash_this_val(hi);
svn_lock_t *lock;
svn_error_t *err = NULL;
svn_pool_clear(iterpool);
args.lock_p = &lock;
args.path = svn_fs__canonicalize_abspath(path, result_pool);
args.token = target->token;
args.comment = comment;
args.is_dav_comment = is_dav_comment;
args.steal_lock = steal_lock;
args.expiration_date = expiration_date;
args.current_rev = target->current_rev;
args.result_pool = result_pool;
if (SVN_IS_VALID_REVNUM(target->current_rev))
{
if (target->current_rev > youngest_rev)
err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
_("No such revision %ld"),
target->current_rev);
}
if (!err)
err = svn_fs_base__retry_txn(fs, txn_body_lock, &args, TRUE,
iterpool);
if (!cb_err && lock_callback)
cb_err = lock_callback(lock_baton, args.path, lock, err, iterpool);
svn_error_clear(err);
}
svn_pool_destroy(iterpool);
return svn_error_trace(cb_err);
}
svn_error_t *
svn_fs_base__generate_lock_token(const char **token,
svn_fs_t *fs,
apr_pool_t *pool)
{
*token = apr_pstrcat(pool, "opaquelocktoken:",
svn_uuid_generate(pool), SVN_VA_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;
SVN_ERR(svn_fs_bdb__lock_token_get(&lock_token, trail->fs, args->path,
trail, trail->pool));
if (!args->break_lock)
{
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));
if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
return SVN_FS__ERR_NO_USER(trail->fs);
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);
}
return delete_lock_and_token(lock_token, args->path, trail);
}
svn_error_t *
svn_fs_base__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)
{
apr_hash_index_t *hi;
svn_error_t *cb_err = SVN_NO_ERROR;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
SVN_ERR(svn_fs__check_fs(fs, TRUE));
for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
{
struct unlock_args args;
const char *path = apr_hash_this_key(hi);
const char *token = apr_hash_this_val(hi);
svn_error_t *err;
svn_pool_clear(iterpool);
args.path = svn_fs__canonicalize_abspath(path, result_pool);
args.token = token;
args.break_lock = break_lock;
err = svn_fs_base__retry_txn(fs, txn_body_unlock, &args, TRUE,
iterpool);
if (!cb_err && lock_callback)
cb_err = lock_callback(lock_baton, path, NULL, err, iterpool);
svn_error_clear(err);
}
svn_pool_destroy(iterpool);
return svn_error_trace(cb_err);
}
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);
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);
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);
}
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;
args.stream = svn_stream__from_spillbuf(svn_spillbuf__create(4 * 1024 ,
64 * 1024 ,
pool),
pool);
SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_locks, &args, FALSE, pool));
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);
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;
skel_buf = apr_palloc(pool, skel_len + 1);
SVN_ERR(svn_stream_read_full(stream, skel_buf, &skel_len));
skel_buf[skel_len] = '\0';
len = 1;
SVN_ERR(svn_stream_read_full(stream, &c, &len));
if (c != '\n')
return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, NULL);
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;
}
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;
}
static svn_error_t *
get_locks_callback(void *baton,
svn_lock_t *lock,
apr_pool_t *pool)
{
return verify_lock(baton, lock, pool);
}
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)
{
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;
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;
}