1
/* lock.c : functions for manipulating filesystem locks.
3
* ====================================================================
4
* Copyright (c) 2000-2004 CollabNet. All rights reserved.
6
* This software is licensed as described in the file COPYING, which
7
* you should have received as part of this distribution. The terms
8
* are also available at http://subversion.tigris.org/license-1.html.
9
* If newer versions of this license are posted there, you may use a
10
* newer version instead, at your option.
12
* This software consists of voluntary contributions made by many
13
* individuals. For exact contribution history, see the revision
14
* history and logs, available at http://subversion.tigris.org/.
15
* ====================================================================
19
#include "svn_pools.h"
20
#include "svn_error.h"
22
#include "svn_private_config.h"
29
#include "bdb/locks-table.h"
30
#include "bdb/lock-tokens-table.h"
31
#include "../libsvn_fs/fs-loader.h"
34
/* Add LOCK and its associated LOCK_TOKEN (associated with PATH) as
37
add_lock_and_token (svn_lock_t *lock,
38
const char *lock_token,
42
SVN_ERR (svn_fs_bdb__lock_add (trail->fs, lock_token, lock,
44
SVN_ERR (svn_fs_bdb__lock_token_add (trail->fs, path, lock_token,
50
/* Delete LOCK_TOKEN and its corresponding lock (associated with PATH,
51
whose KIND is supplied), as part of TRAIL. */
53
delete_lock_and_token (const char *lock_token,
57
SVN_ERR (svn_fs_bdb__lock_delete (trail->fs, lock_token,
59
SVN_ERR (svn_fs_bdb__lock_token_delete (trail->fs, path,
71
svn_boolean_t is_dav_comment;
72
svn_boolean_t steal_lock;
73
apr_time_t expiration_date;
74
svn_revnum_t current_rev;
79
txn_body_lock (void *baton, trail_t *trail)
81
struct lock_args *args = baton;
82
svn_node_kind_t kind = svn_node_file;
83
svn_lock_t *existing_lock;
84
const char *fs_username;
87
SVN_ERR (svn_fs_base__get_path_kind (&kind, args->path, trail, trail->pool));
89
/* Until we implement directory locks someday, we only allow locks
90
on files or non-existent paths. */
91
if (kind == svn_node_dir)
92
return svn_fs_base__err_not_file (trail->fs, args->path);
94
/* While our locking implementation easily supports the locking of
95
nonexistent paths, we deliberately choose not to allow such madness. */
96
if (kind == svn_node_none)
97
return svn_error_createf (SVN_ERR_FS_NOT_FOUND, NULL,
98
"Path '%s' doesn't exist in HEAD revision",
101
/* There better be a username attached to the fs. */
102
if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
103
return svn_fs_base__err_no_user (trail->fs);
105
fs_username = trail->fs->access_ctx->username; /* for convenience */
107
/* Is the caller attempting to lock an out-of-date working file? */
108
if (SVN_IS_VALID_REVNUM(args->current_rev))
110
svn_revnum_t created_rev;
111
SVN_ERR (svn_fs_base__get_path_created_rev (&created_rev, args->path,
112
trail, trail->pool));
114
/* SVN_INVALID_REVNUM means the path doesn't exist. So
115
apparently somebody is trying to lock something in their
116
working copy, but somebody else has deleted the thing
117
from HEAD. That counts as being 'out of date'. */
118
if (! SVN_IS_VALID_REVNUM(created_rev))
119
return svn_error_createf (SVN_ERR_FS_OUT_OF_DATE, NULL,
120
"Path '%s' doesn't exist in HEAD revision",
123
if (args->current_rev < created_rev)
124
return svn_error_createf (SVN_ERR_FS_OUT_OF_DATE, NULL,
125
"Lock failed: newer version of '%s' exists",
129
/* If the caller provided a TOKEN, we *really* need to see
130
if a lock already exists with that token, and if so, verify that
131
the lock's path matches PATH. Otherwise we run the risk of
132
breaking the 1-to-1 mapping of lock tokens to locked paths. */
135
svn_lock_t *lock_from_token;
136
svn_error_t *err = svn_fs_bdb__lock_get (&lock_from_token, trail->fs,
139
if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
140
|| (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
142
svn_error_clear (err);
147
if (strcmp (lock_from_token->path, args->path) != 0)
148
return svn_error_create (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
149
"Lock failed: token refers to existing "
150
"lock with non-matching path.");
154
/* Is the path already locked?
156
Note that this next function call will automatically ignore any
157
errors about {the path not existing as a key, the path's token
158
not existing as a key, the lock just having been expired}. And
159
that's totally fine. Any of these three errors are perfectly
160
acceptable to ignore; it means that the path is now free and
161
clear for locking, because the bdb funcs just cleared out both
162
of the tables for us. */
163
SVN_ERR (svn_fs_base__get_lock_helper (&existing_lock, args->path,
164
trail, trail->pool));
167
if (! args->steal_lock)
169
/* Sorry, the path is already locked. */
170
return svn_fs_base__err_path_already_locked (trail->fs,
175
/* ARGS->steal_lock is set, so fs_username is "stealing" the
176
lock from lock->owner. Destroy the existing lock. */
177
SVN_ERR (delete_lock_and_token (existing_lock->token,
178
existing_lock->path, trail));
182
/* Create a new lock, and add it to the tables. */
183
lock = svn_lock_create (trail->pool);
185
lock->token = apr_pstrdup (trail->pool, args->token);
187
SVN_ERR (svn_fs_base__generate_lock_token (&(lock->token), trail->fs,
189
lock->path = apr_pstrdup (trail->pool, args->path);
190
lock->owner = apr_pstrdup (trail->pool, trail->fs->access_ctx->username);
191
lock->comment = apr_pstrdup (trail->pool, args->comment);
192
lock->is_dav_comment = args->is_dav_comment;
193
lock->creation_date = apr_time_now();
194
lock->expiration_date = args->expiration_date;
195
SVN_ERR (add_lock_and_token (lock, lock->token, args->path, trail));
196
*(args->lock_p) = lock;
204
svn_fs_base__lock (svn_lock_t **lock,
209
svn_boolean_t is_dav_comment,
210
apr_time_t expiration_date,
211
svn_revnum_t current_rev,
212
svn_boolean_t steal_lock,
215
struct lock_args args;
217
SVN_ERR (svn_fs_base__check_fs (fs));
220
args.path = svn_fs_base__canonicalize_abspath (path, pool);
222
args.comment = comment;
223
args.is_dav_comment = is_dav_comment;
224
args.steal_lock = steal_lock;
225
args.expiration_date = expiration_date;
226
args.current_rev = current_rev;
228
return svn_fs_base__retry_txn (fs, txn_body_lock, &args, pool);
234
svn_fs_base__generate_lock_token (const char **token,
238
/* Notice that 'fs' is currently unused. But perhaps someday,
239
we'll want to use the fs UUID + some incremented number? */
241
char *uuid_str = apr_pcalloc (pool, APR_UUID_FORMATTED_LENGTH + 1);
243
apr_uuid_get (&uuid);
244
apr_uuid_format (uuid_str, &uuid);
246
/* For now, we generate a URI that matches the DAV RFC. We could
247
change this to some other URI scheme someday, if we wish. */
248
*token = apr_pstrcat (pool, "opaquelocktoken:", uuid_str, NULL);
257
svn_boolean_t break_lock;
262
txn_body_unlock (void *baton, trail_t *trail)
264
struct unlock_args *args = baton;
265
const char *lock_token;
268
/* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
269
SVN_ERR (svn_fs_bdb__lock_token_get (&lock_token, trail->fs, args->path,
270
trail, trail->pool));
272
/* If not breaking the lock, we need to do some more checking. */
273
if (!args->break_lock)
275
/* Sanity check: The lock token must exist, and must match. */
276
if (args->token == NULL)
277
return svn_fs_base__err_no_lock_token (trail->fs, args->path);
278
else if (strcmp (lock_token, args->token) != 0)
279
return svn_fs_base__err_no_such_lock (trail->fs, args->path);
281
SVN_ERR (svn_fs_bdb__lock_get (&lock, trail->fs, lock_token,
282
trail, trail->pool));
284
/* There better be a username attached to the fs. */
285
if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
286
return svn_fs_base__err_no_user (trail->fs);
288
/* And that username better be the same as the lock's owner. */
289
if (strcmp(trail->fs->access_ctx->username, lock->owner) != 0)
290
return svn_fs_base__err_lock_owner_mismatch
292
trail->fs->access_ctx->username,
296
/* Remove a row from each of the locking tables. */
297
SVN_ERR (delete_lock_and_token (lock_token, args->path, trail));
303
svn_fs_base__unlock (svn_fs_t *fs,
306
svn_boolean_t break_lock,
309
struct unlock_args args;
311
SVN_ERR (svn_fs_base__check_fs (fs));
313
args.path = svn_fs_base__canonicalize_abspath (path, pool);
315
args.break_lock = break_lock;
316
return svn_fs_base__retry_txn (fs, txn_body_unlock, &args, pool);
321
svn_fs_base__get_lock_helper (svn_lock_t **lock_p,
326
const char *lock_token;
329
err = svn_fs_bdb__lock_token_get (&lock_token, trail->fs, path,
332
/* We've deliberately decided that this function doesn't tell the
333
caller *why* the lock is unavailable. */
334
if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
335
|| (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
336
|| (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
338
svn_error_clear (err);
345
/* Same situation here. */
346
err = svn_fs_bdb__lock_get (lock_p, trail->fs, lock_token, trail, pool);
347
if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
348
|| (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
350
svn_error_clear (err);
361
struct lock_token_get_args
369
txn_body_get_lock (void *baton, trail_t *trail)
371
struct lock_token_get_args *args = baton;
372
return svn_fs_base__get_lock_helper (args->lock_p, args->path,
378
svn_fs_base__get_lock (svn_lock_t **lock,
383
struct lock_token_get_args args;
386
SVN_ERR (svn_fs_base__check_fs (fs));
388
args.path = svn_fs_base__canonicalize_abspath (path, pool);
390
return svn_fs_base__retry_txn (fs, txn_body_get_lock, &args, pool);
395
struct locks_get_args
398
svn_fs_get_locks_callback_t get_locks_func;
399
void *get_locks_baton;
404
txn_body_get_locks (void *baton, trail_t *trail)
406
struct locks_get_args *args = baton;
407
return svn_fs_bdb__locks_get (trail->fs, args->path,
408
args->get_locks_func, args->get_locks_baton,
414
svn_fs_base__get_locks (svn_fs_t *fs,
416
svn_fs_get_locks_callback_t get_locks_func,
417
void *get_locks_baton,
420
struct locks_get_args args;
422
SVN_ERR (svn_fs_base__check_fs (fs));
423
args.path = svn_fs_base__canonicalize_abspath (path, pool);
424
args.get_locks_func = get_locks_func;
425
args.get_locks_baton = get_locks_baton;
426
return svn_fs_base__retry_txn (fs, txn_body_get_locks, &args, pool);
431
/* Utility function: verify that a lock can be used.
433
If no username is attached to the FS, return SVN_ERR_FS_NO_USER.
435
If the FS username doesn't match LOCK's owner, return
436
SVN_ERR_FS_LOCK_OWNER_MISMATCH.
438
If FS hasn't been supplied with a matching lock-token for LOCK,
439
return SVN_ERR_FS_BAD_LOCK_TOKEN.
441
Otherwise return SVN_NO_ERROR.
444
verify_lock (svn_fs_t *fs,
448
if ((! fs->access_ctx) || (! fs->access_ctx->username))
449
return svn_error_createf
450
(SVN_ERR_FS_NO_USER, NULL,
451
_("Cannot verify lock on path '%s'; no username available"),
454
else if (strcmp (fs->access_ctx->username, lock->owner) != 0)
455
return svn_error_createf
456
(SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
457
_("User %s does not own lock on path '%s' (currently locked by %s)"),
458
fs->access_ctx->username, lock->path, lock->owner);
460
else if (apr_hash_get (fs->access_ctx->lock_tokens, lock->token,
461
APR_HASH_KEY_STRING) == NULL)
462
return svn_error_createf
463
(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
464
_("Cannot verify lock on path '%s'; no matching lock-token available"),
471
/* This implements the svn_fs_get_locks_callback_t interface, where
472
BATON is just an svn_fs_t object. */
474
get_locks_callback (void *baton,
478
return verify_lock (baton, lock, pool);
482
/* The main routine for lock enforcement, used throughout libsvn_fs_base. */
484
svn_fs_base__allow_locked_operation (const char *path,
485
svn_boolean_t recurse,
491
/* Discover all locks at or below the path. */
492
SVN_ERR (svn_fs_bdb__locks_get (trail->fs, path, get_locks_callback,
493
trail->fs, trail, pool));
499
/* Discover any lock attached to the path. */
500
SVN_ERR (svn_fs_base__get_lock_helper (&lock, path, trail, pool));
502
SVN_ERR (verify_lock (trail->fs, lock, pool));