Mark many merge tests as skip-against-old-server.
[svn.git] / subversion / libsvn_fs_base / lock.c
blob6c47e4e36382e0f51fa745935d4da790d11dc04a
1 /* lock.c : functions for manipulating filesystem locks.
3 * ====================================================================
4 * Copyright (c) 2000-2007 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"
21 #include "svn_fs.h"
22 #include "svn_private_config.h"
24 #include <apr_uuid.h>
26 #include "lock.h"
27 #include "tree.h"
28 #include "err.h"
29 #include "bdb/locks-table.h"
30 #include "bdb/lock-tokens-table.h"
31 #include "../libsvn_fs/fs-loader.h"
32 #include "private/svn_fs_util.h"
35 /* Add LOCK and its associated LOCK_TOKEN (associated with PATH) as
36 part of TRAIL. */
37 static svn_error_t *
38 add_lock_and_token(svn_lock_t *lock,
39 const char *lock_token,
40 const char *path,
41 trail_t *trail)
43 SVN_ERR(svn_fs_bdb__lock_add(trail->fs, lock_token, lock,
44 trail, trail->pool));
45 SVN_ERR(svn_fs_bdb__lock_token_add(trail->fs, path, lock_token,
46 trail, trail->pool));
47 return SVN_NO_ERROR;
51 /* Delete LOCK_TOKEN and its corresponding lock (associated with PATH,
52 whose KIND is supplied), as part of TRAIL. */
53 static svn_error_t *
54 delete_lock_and_token(const char *lock_token,
55 const char *path,
56 trail_t *trail)
58 SVN_ERR(svn_fs_bdb__lock_delete(trail->fs, lock_token,
59 trail, trail->pool));
60 SVN_ERR(svn_fs_bdb__lock_token_delete(trail->fs, path,
61 trail, trail->pool));
62 return SVN_NO_ERROR;
66 struct lock_args
68 svn_lock_t **lock_p;
69 const char *path;
70 const char *token;
71 const char *comment;
72 svn_boolean_t is_dav_comment;
73 svn_boolean_t steal_lock;
74 apr_time_t expiration_date;
75 svn_revnum_t current_rev;
79 static svn_error_t *
80 txn_body_lock(void *baton, trail_t *trail)
82 struct lock_args *args = baton;
83 svn_node_kind_t kind = svn_node_file;
84 svn_lock_t *existing_lock;
85 const char *fs_username;
86 svn_lock_t *lock;
88 SVN_ERR(svn_fs_base__get_path_kind(&kind, args->path, trail, trail->pool));
90 /* Until we implement directory locks someday, we only allow locks
91 on files or non-existent paths. */
92 if (kind == svn_node_dir)
93 return SVN_FS__ERR_NOT_FILE(trail->fs, args->path);
95 /* While our locking implementation easily supports the locking of
96 nonexistent paths, we deliberately choose not to allow such madness. */
97 if (kind == svn_node_none)
98 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
99 "Path '%s' doesn't exist in HEAD revision",
100 args->path);
102 /* There better be a username attached to the fs. */
103 if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
104 return SVN_FS__ERR_NO_USER(trail->fs);
105 else
106 fs_username = trail->fs->access_ctx->username; /* for convenience */
108 /* Is the caller attempting to lock an out-of-date working file? */
109 if (SVN_IS_VALID_REVNUM(args->current_rev))
111 svn_revnum_t created_rev;
112 SVN_ERR(svn_fs_base__get_path_created_rev(&created_rev, args->path,
113 trail, trail->pool));
115 /* SVN_INVALID_REVNUM means the path doesn't exist. So
116 apparently somebody is trying to lock something in their
117 working copy, but somebody else has deleted the thing
118 from HEAD. That counts as being 'out of date'. */
119 if (! SVN_IS_VALID_REVNUM(created_rev))
120 return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
121 "Path '%s' doesn't exist in HEAD revision",
122 args->path);
124 if (args->current_rev < created_rev)
125 return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
126 "Lock failed: newer version of '%s' exists",
127 args->path);
130 /* If the caller provided a TOKEN, we *really* need to see
131 if a lock already exists with that token, and if so, verify that
132 the lock's path matches PATH. Otherwise we run the risk of
133 breaking the 1-to-1 mapping of lock tokens to locked paths. */
134 if (args->token)
136 svn_lock_t *lock_from_token;
137 svn_error_t *err = svn_fs_bdb__lock_get(&lock_from_token, trail->fs,
138 args->token, trail,
139 trail->pool);
140 if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
141 || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
143 svn_error_clear(err);
145 else
147 SVN_ERR(err);
148 if (strcmp(lock_from_token->path, args->path) != 0)
149 return svn_error_create(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
150 "Lock failed: token refers to existing "
151 "lock with non-matching path.");
155 /* Is the path already locked?
157 Note that this next function call will automatically ignore any
158 errors about {the path not existing as a key, the path's token
159 not existing as a key, the lock just having been expired}. And
160 that's totally fine. Any of these three errors are perfectly
161 acceptable to ignore; it means that the path is now free and
162 clear for locking, because the bdb funcs just cleared out both
163 of the tables for us. */
164 SVN_ERR(svn_fs_base__get_lock_helper(&existing_lock, args->path,
165 trail, trail->pool));
166 if (existing_lock)
168 if (! args->steal_lock)
170 /* Sorry, the path is already locked. */
171 return SVN_FS__ERR_PATH_ALREADY_LOCKED(trail->fs,
172 existing_lock);
174 else
176 /* ARGS->steal_lock is set, so fs_username is "stealing" the
177 lock from lock->owner. Destroy the existing lock. */
178 SVN_ERR(delete_lock_and_token(existing_lock->token,
179 existing_lock->path, trail));
183 /* Create a new lock, and add it to the tables. */
184 lock = svn_lock_create(trail->pool);
185 if (args->token)
186 lock->token = apr_pstrdup(trail->pool, args->token);
187 else
188 SVN_ERR(svn_fs_base__generate_lock_token(&(lock->token), trail->fs,
189 trail->pool));
190 lock->path = apr_pstrdup(trail->pool, args->path);
191 lock->owner = apr_pstrdup(trail->pool, trail->fs->access_ctx->username);
192 lock->comment = apr_pstrdup(trail->pool, args->comment);
193 lock->is_dav_comment = args->is_dav_comment;
194 lock->creation_date = apr_time_now();
195 lock->expiration_date = args->expiration_date;
196 SVN_ERR(add_lock_and_token(lock, lock->token, args->path, trail));
197 *(args->lock_p) = lock;
199 return SVN_NO_ERROR;
204 svn_error_t *
205 svn_fs_base__lock(svn_lock_t **lock,
206 svn_fs_t *fs,
207 const char *path,
208 const char *token,
209 const char *comment,
210 svn_boolean_t is_dav_comment,
211 apr_time_t expiration_date,
212 svn_revnum_t current_rev,
213 svn_boolean_t steal_lock,
214 apr_pool_t *pool)
216 struct lock_args args;
218 SVN_ERR(svn_fs__check_fs(fs, TRUE));
220 args.lock_p = lock;
221 args.path = svn_fs__canonicalize_abspath(path, pool);
222 args.token = token;
223 args.comment = comment;
224 args.is_dav_comment = is_dav_comment;
225 args.steal_lock = steal_lock;
226 args.expiration_date = expiration_date;
227 args.current_rev = current_rev;
229 return svn_fs_base__retry_txn(fs, txn_body_lock, &args, pool);
234 svn_error_t *
235 svn_fs_base__generate_lock_token(const char **token,
236 svn_fs_t *fs,
237 apr_pool_t *pool)
239 /* Notice that 'fs' is currently unused. But perhaps someday, we'll
240 want to use the fs UUID + some incremented number? For now, we
241 generate a URI that matches the DAV RFC. We could change this to
242 some other URI scheme someday, if we wish. */
243 *token = apr_pstrcat(pool, "opaquelocktoken:",
244 svn_uuid_generate(pool), NULL);
245 return SVN_NO_ERROR;
249 struct unlock_args
251 const char *path;
252 const char *token;
253 svn_boolean_t break_lock;
257 static svn_error_t *
258 txn_body_unlock(void *baton, trail_t *trail)
260 struct unlock_args *args = baton;
261 const char *lock_token;
262 svn_lock_t *lock;
264 /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
265 SVN_ERR(svn_fs_bdb__lock_token_get(&lock_token, trail->fs, args->path,
266 trail, trail->pool));
268 /* If not breaking the lock, we need to do some more checking. */
269 if (!args->break_lock)
271 /* Sanity check: The lock token must exist, and must match. */
272 if (args->token == NULL)
273 return svn_fs_base__err_no_lock_token(trail->fs, args->path);
274 else if (strcmp(lock_token, args->token) != 0)
275 return SVN_FS__ERR_NO_SUCH_LOCK(trail->fs, args->path);
277 SVN_ERR(svn_fs_bdb__lock_get(&lock, trail->fs, lock_token,
278 trail, trail->pool));
280 /* There better be a username attached to the fs. */
281 if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
282 return SVN_FS__ERR_NO_USER(trail->fs);
284 /* And that username better be the same as the lock's owner. */
285 if (strcmp(trail->fs->access_ctx->username, lock->owner) != 0)
286 return SVN_FS__ERR_LOCK_OWNER_MISMATCH
287 (trail->fs,
288 trail->fs->access_ctx->username,
289 lock->owner);
292 /* Remove a row from each of the locking tables. */
293 SVN_ERR(delete_lock_and_token(lock_token, args->path, trail));
294 return SVN_NO_ERROR;
298 svn_error_t *
299 svn_fs_base__unlock(svn_fs_t *fs,
300 const char *path,
301 const char *token,
302 svn_boolean_t break_lock,
303 apr_pool_t *pool)
305 struct unlock_args args;
307 SVN_ERR(svn_fs__check_fs(fs, TRUE));
309 args.path = svn_fs__canonicalize_abspath(path, pool);
310 args.token = token;
311 args.break_lock = break_lock;
312 return svn_fs_base__retry_txn(fs, txn_body_unlock, &args, pool);
316 svn_error_t *
317 svn_fs_base__get_lock_helper(svn_lock_t **lock_p,
318 const char *path,
319 trail_t *trail,
320 apr_pool_t *pool)
322 const char *lock_token;
323 svn_error_t *err;
325 err = svn_fs_bdb__lock_token_get(&lock_token, trail->fs, path,
326 trail, pool);
328 /* We've deliberately decided that this function doesn't tell the
329 caller *why* the lock is unavailable. */
330 if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
331 || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
332 || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
334 svn_error_clear(err);
335 *lock_p = NULL;
336 return SVN_NO_ERROR;
338 else
339 SVN_ERR(err);
341 /* Same situation here. */
342 err = svn_fs_bdb__lock_get(lock_p, trail->fs, lock_token, trail, pool);
343 if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
344 || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
346 svn_error_clear(err);
347 *lock_p = NULL;
348 return SVN_NO_ERROR;
350 else
351 SVN_ERR(err);
353 return err;
357 struct lock_token_get_args
359 svn_lock_t **lock_p;
360 const char *path;
364 static svn_error_t *
365 txn_body_get_lock(void *baton, trail_t *trail)
367 struct lock_token_get_args *args = baton;
368 return svn_fs_base__get_lock_helper(args->lock_p, args->path,
369 trail, trail->pool);
373 svn_error_t *
374 svn_fs_base__get_lock(svn_lock_t **lock,
375 svn_fs_t *fs,
376 const char *path,
377 apr_pool_t *pool)
379 struct lock_token_get_args args;
381 SVN_ERR(svn_fs__check_fs(fs, TRUE));
383 args.path = svn_fs__canonicalize_abspath(path, pool);
384 args.lock_p = lock;
385 return svn_fs_base__retry_txn(fs, txn_body_get_lock, &args, pool);
389 struct locks_get_args
391 const char *path;
392 svn_fs_get_locks_callback_t get_locks_func;
393 void *get_locks_baton;
397 static svn_error_t *
398 txn_body_get_locks(void *baton, trail_t *trail)
400 struct locks_get_args *args = baton;
401 return svn_fs_bdb__locks_get(trail->fs, args->path,
402 args->get_locks_func, args->get_locks_baton,
403 trail, trail->pool);
407 svn_error_t *
408 svn_fs_base__get_locks(svn_fs_t *fs,
409 const char *path,
410 svn_fs_get_locks_callback_t get_locks_func,
411 void *get_locks_baton,
412 apr_pool_t *pool)
414 struct locks_get_args args;
416 SVN_ERR(svn_fs__check_fs(fs, TRUE));
417 args.path = svn_fs__canonicalize_abspath(path, pool);
418 args.get_locks_func = get_locks_func;
419 args.get_locks_baton = get_locks_baton;
420 return svn_fs_base__retry_txn(fs, txn_body_get_locks, &args, pool);
425 /* Utility function: verify that a lock can be used.
427 If no username is attached to the FS, return SVN_ERR_FS_NO_USER.
429 If the FS username doesn't match LOCK's owner, return
430 SVN_ERR_FS_LOCK_OWNER_MISMATCH.
432 If FS hasn't been supplied with a matching lock-token for LOCK,
433 return SVN_ERR_FS_BAD_LOCK_TOKEN.
435 Otherwise return SVN_NO_ERROR.
437 static svn_error_t *
438 verify_lock(svn_fs_t *fs,
439 svn_lock_t *lock,
440 apr_pool_t *pool)
442 if ((! fs->access_ctx) || (! fs->access_ctx->username))
443 return svn_error_createf
444 (SVN_ERR_FS_NO_USER, NULL,
445 _("Cannot verify lock on path '%s'; no username available"),
446 lock->path);
448 else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
449 return svn_error_createf
450 (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
451 _("User %s does not own lock on path '%s' (currently locked by %s)"),
452 fs->access_ctx->username, lock->path, lock->owner);
454 else if (apr_hash_get(fs->access_ctx->lock_tokens, lock->token,
455 APR_HASH_KEY_STRING) == NULL)
456 return svn_error_createf
457 (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
458 _("Cannot verify lock on path '%s'; no matching lock-token available"),
459 lock->path);
461 return SVN_NO_ERROR;
465 /* This implements the svn_fs_get_locks_callback_t interface, where
466 BATON is just an svn_fs_t object. */
467 static svn_error_t *
468 get_locks_callback(void *baton,
469 svn_lock_t *lock,
470 apr_pool_t *pool)
472 return verify_lock(baton, lock, pool);
476 /* The main routine for lock enforcement, used throughout libsvn_fs_base. */
477 svn_error_t *
478 svn_fs_base__allow_locked_operation(const char *path,
479 svn_boolean_t recurse,
480 trail_t *trail,
481 apr_pool_t *pool)
483 if (recurse)
485 /* Discover all locks at or below the path. */
486 SVN_ERR(svn_fs_bdb__locks_get(trail->fs, path, get_locks_callback,
487 trail->fs, trail, pool));
489 else
491 svn_lock_t *lock;
493 /* Discover any lock attached to the path. */
494 SVN_ERR(svn_fs_base__get_lock_helper(&lock, path, trail, pool));
495 if (lock)
496 SVN_ERR(verify_lock(trail->fs, lock, pool));
498 return SVN_NO_ERROR;