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"
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"
32 #include "private/svn_fs_util.h"
35 /* Add LOCK and its associated LOCK_TOKEN (associated with PATH) as
38 add_lock_and_token(svn_lock_t
*lock
,
39 const char *lock_token
,
43 SVN_ERR(svn_fs_bdb__lock_add(trail
->fs
, lock_token
, lock
,
45 SVN_ERR(svn_fs_bdb__lock_token_add(trail
->fs
, path
, lock_token
,
51 /* Delete LOCK_TOKEN and its corresponding lock (associated with PATH,
52 whose KIND is supplied), as part of TRAIL. */
54 delete_lock_and_token(const char *lock_token
,
58 SVN_ERR(svn_fs_bdb__lock_delete(trail
->fs
, lock_token
,
60 SVN_ERR(svn_fs_bdb__lock_token_delete(trail
->fs
, path
,
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
;
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
;
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",
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
);
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",
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",
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. */
136 svn_lock_t
*lock_from_token
;
137 svn_error_t
*err
= svn_fs_bdb__lock_get(&lock_from_token
, trail
->fs
,
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
);
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
));
168 if (! args
->steal_lock
)
170 /* Sorry, the path is already locked. */
171 return SVN_FS__ERR_PATH_ALREADY_LOCKED(trail
->fs
,
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
);
186 lock
->token
= apr_pstrdup(trail
->pool
, args
->token
);
188 SVN_ERR(svn_fs_base__generate_lock_token(&(lock
->token
), trail
->fs
,
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
;
205 svn_fs_base__lock(svn_lock_t
**lock
,
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
,
216 struct lock_args args
;
218 SVN_ERR(svn_fs__check_fs(fs
, TRUE
));
221 args
.path
= svn_fs__canonicalize_abspath(path
, pool
);
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
);
235 svn_fs_base__generate_lock_token(const char **token
,
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
);
253 svn_boolean_t break_lock
;
258 txn_body_unlock(void *baton
, trail_t
*trail
)
260 struct unlock_args
*args
= baton
;
261 const char *lock_token
;
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
288 trail
->fs
->access_ctx
->username
,
292 /* Remove a row from each of the locking tables. */
293 SVN_ERR(delete_lock_and_token(lock_token
, args
->path
, trail
));
299 svn_fs_base__unlock(svn_fs_t
*fs
,
302 svn_boolean_t break_lock
,
305 struct unlock_args args
;
307 SVN_ERR(svn_fs__check_fs(fs
, TRUE
));
309 args
.path
= svn_fs__canonicalize_abspath(path
, pool
);
311 args
.break_lock
= break_lock
;
312 return svn_fs_base__retry_txn(fs
, txn_body_unlock
, &args
, pool
);
317 svn_fs_base__get_lock_helper(svn_lock_t
**lock_p
,
322 const char *lock_token
;
325 err
= svn_fs_bdb__lock_token_get(&lock_token
, trail
->fs
, path
,
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
);
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
);
357 struct lock_token_get_args
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
,
374 svn_fs_base__get_lock(svn_lock_t
**lock
,
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
);
385 return svn_fs_base__retry_txn(fs
, txn_body_get_lock
, &args
, pool
);
389 struct locks_get_args
392 svn_fs_get_locks_callback_t get_locks_func
;
393 void *get_locks_baton
;
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
,
408 svn_fs_base__get_locks(svn_fs_t
*fs
,
410 svn_fs_get_locks_callback_t get_locks_func
,
411 void *get_locks_baton
,
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.
438 verify_lock(svn_fs_t
*fs
,
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"),
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"),
465 /* This implements the svn_fs_get_locks_callback_t interface, where
466 BATON is just an svn_fs_t object. */
468 get_locks_callback(void *baton
,
472 return verify_lock(baton
, lock
, pool
);
476 /* The main routine for lock enforcement, used throughout libsvn_fs_base. */
478 svn_fs_base__allow_locked_operation(const char *path
,
479 svn_boolean_t 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
));
493 /* Discover any lock attached to the path. */
494 SVN_ERR(svn_fs_base__get_lock_helper(&lock
, path
, trail
, pool
));
496 SVN_ERR(verify_lock(trail
->fs
, lock
, pool
));