1 /* lock.c : functions for manipulating filesystem locks.
3 * ====================================================================
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
20 * ====================================================================
25 #include "svn_pools.h"
26 #include "svn_error.h"
28 #include "svn_private_config.h"
35 #include "bdb/locks-table.h"
36 #include "bdb/lock-tokens-table.h"
37 #include "util/fs_skels.h"
38 #include "../libsvn_fs/fs-loader.h"
39 #include "private/svn_fs_util.h"
40 #include "private/svn_subr_private.h"
41 #include "private/svn_dep_compat.h"
42 #include "revs-txns.h"
45 /* Add LOCK and its associated LOCK_TOKEN (associated with PATH) as
48 add_lock_and_token(svn_lock_t *lock,
49 const char *lock_token,
53 SVN_ERR(svn_fs_bdb__lock_add(trail->fs, lock_token, lock,
55 return svn_fs_bdb__lock_token_add(trail->fs, path, lock_token,
60 /* Delete LOCK_TOKEN and its corresponding lock (associated with PATH,
61 whose KIND is supplied), as part of TRAIL. */
63 delete_lock_and_token(const char *lock_token,
67 SVN_ERR(svn_fs_bdb__lock_delete(trail->fs, lock_token,
69 return svn_fs_bdb__lock_token_delete(trail->fs, path,
74 /* The effective arguments for txn_body_lock() below. */
81 svn_boolean_t is_dav_comment;
82 svn_boolean_t steal_lock;
83 apr_time_t expiration_date;
84 svn_revnum_t current_rev;
85 apr_pool_t *result_pool;
89 /* The body of svn_fs_base__lock(), which see.
91 BATON is a 'struct lock_args *' holding the effective arguments.
92 BATON->path is the canonical abspath to lock. Set *BATON->lock_p
93 to the resulting lock. For the other arguments, see
96 This implements the svn_fs_base__retry_txn() 'body' callback type.
99 txn_body_lock(void *baton, trail_t *trail)
101 struct lock_args *args = baton;
102 svn_node_kind_t kind = svn_node_file;
103 svn_lock_t *existing_lock;
106 *args->lock_p = NULL;
108 SVN_ERR(svn_fs_base__get_path_kind(&kind, args->path, trail, trail->pool));
110 /* Until we implement directory locks someday, we only allow locks
112 if (kind == svn_node_dir)
113 return SVN_FS__ERR_NOT_FILE(trail->fs, args->path);
115 /* While our locking implementation easily supports the locking of
116 nonexistent paths, we deliberately choose not to allow such madness. */
117 if (kind == svn_node_none)
119 if (SVN_IS_VALID_REVNUM(args->current_rev))
120 return svn_error_createf(
121 SVN_ERR_FS_OUT_OF_DATE, NULL,
122 _("Path '%s' doesn't exist in HEAD revision"),
125 return svn_error_createf(
126 SVN_ERR_FS_NOT_FOUND, NULL,
127 _("Path '%s' doesn't exist in HEAD revision"),
131 /* There better be a username attached to the fs. */
132 if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
133 return SVN_FS__ERR_NO_USER(trail->fs);
135 /* Is the caller attempting to lock an out-of-date working file? */
136 if (SVN_IS_VALID_REVNUM(args->current_rev))
138 svn_revnum_t created_rev;
139 SVN_ERR(svn_fs_base__get_path_created_rev(&created_rev, args->path,
140 trail, trail->pool));
142 /* SVN_INVALID_REVNUM means the path doesn't exist. So
143 apparently somebody is trying to lock something in their
144 working copy, but somebody else has deleted the thing
145 from HEAD. That counts as being 'out of date'. */
146 if (! SVN_IS_VALID_REVNUM(created_rev))
147 return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
148 "Path '%s' doesn't exist in HEAD revision",
151 if (args->current_rev < created_rev)
152 return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
153 "Lock failed: newer version of '%s' exists",
157 /* If the caller provided a TOKEN, we *really* need to see
158 if a lock already exists with that token, and if so, verify that
159 the lock's path matches PATH. Otherwise we run the risk of
160 breaking the 1-to-1 mapping of lock tokens to locked paths. */
163 svn_lock_t *lock_from_token;
164 svn_error_t *err = svn_fs_bdb__lock_get(&lock_from_token, trail->fs,
167 if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
168 || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
170 svn_error_clear(err);
175 if (strcmp(lock_from_token->path, args->path) != 0)
176 return svn_error_create(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
177 "Lock failed: token refers to existing "
178 "lock with non-matching path.");
182 /* Is the path already locked?
184 Note that this next function call will automatically ignore any
185 errors about {the path not existing as a key, the path's token
186 not existing as a key, the lock just having been expired}. And
187 that's totally fine. Any of these three errors are perfectly
188 acceptable to ignore; it means that the path is now free and
189 clear for locking, because the bdb funcs just cleared out both
190 of the tables for us. */
191 SVN_ERR(svn_fs_base__get_lock_helper(&existing_lock, args->path,
192 trail, trail->pool));
195 if (! args->steal_lock)
197 /* Sorry, the path is already locked. */
198 return SVN_FS__ERR_PATH_ALREADY_LOCKED(trail->fs,
203 /* ARGS->steal_lock is set, so fs_username is "stealing" the
204 lock from lock->owner. Destroy the existing lock. */
205 SVN_ERR(delete_lock_and_token(existing_lock->token,
206 existing_lock->path, trail));
210 /* Create a new lock, and add it to the tables. */
211 lock = svn_lock_create(args->result_pool);
213 lock->token = apr_pstrdup(args->result_pool, args->token);
215 SVN_ERR(svn_fs_base__generate_lock_token(&(lock->token), trail->fs,
217 lock->path = args->path; /* Already in result_pool. */
218 lock->owner = apr_pstrdup(args->result_pool, trail->fs->access_ctx->username);
219 lock->comment = apr_pstrdup(args->result_pool, args->comment);
220 lock->is_dav_comment = args->is_dav_comment;
221 lock->creation_date = apr_time_now();
222 lock->expiration_date = args->expiration_date;
223 SVN_ERR(add_lock_and_token(lock, lock->token, args->path, trail));
224 *(args->lock_p) = lock;
232 svn_fs_base__lock(svn_fs_t *fs,
235 svn_boolean_t is_dav_comment,
236 apr_time_t expiration_date,
237 svn_boolean_t steal_lock,
238 svn_fs_lock_callback_t lock_callback,
240 apr_pool_t *result_pool,
241 apr_pool_t *scratch_pool)
243 apr_hash_index_t *hi;
244 svn_error_t *cb_err = SVN_NO_ERROR;
245 svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
246 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
248 SVN_ERR(svn_fs__check_fs(fs, TRUE));
249 SVN_ERR(svn_fs_base__youngest_rev(&youngest_rev, fs, scratch_pool));
251 for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
253 struct lock_args args;
254 const char *path = apr_hash_this_key(hi);
255 const svn_fs_lock_target_t *target = apr_hash_this_val(hi);
257 svn_error_t *err = NULL;
259 svn_pool_clear(iterpool);
261 args.path = svn_fs__canonicalize_abspath(path, result_pool);
262 args.token = target->token;
263 args.comment = comment;
264 args.is_dav_comment = is_dav_comment;
265 args.steal_lock = steal_lock;
266 args.expiration_date = expiration_date;
267 args.current_rev = target->current_rev;
268 args.result_pool = result_pool;
270 if (SVN_IS_VALID_REVNUM(target->current_rev))
272 if (target->current_rev > youngest_rev)
273 err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
274 _("No such revision %ld"),
275 target->current_rev);
279 err = svn_fs_base__retry_txn(fs, txn_body_lock, &args, TRUE,
281 if (!cb_err && lock_callback)
282 cb_err = lock_callback(lock_baton, args.path, lock, err, iterpool);
283 svn_error_clear(err);
285 svn_pool_destroy(iterpool);
287 return svn_error_trace(cb_err);
292 svn_fs_base__generate_lock_token(const char **token,
296 /* Notice that 'fs' is currently unused. But perhaps someday, we'll
297 want to use the fs UUID + some incremented number? For now, we
298 generate a URI that matches the DAV RFC. We could change this to
299 some other URI scheme someday, if we wish. */
300 *token = apr_pstrcat(pool, "opaquelocktoken:",
301 svn_uuid_generate(pool), SVN_VA_NULL);
306 /* The effective arguments for txn_body_unlock() below. */
311 svn_boolean_t break_lock;
315 /* The body of svn_fs_base__unlock(), which see.
317 BATON is a 'struct unlock_args *' holding the effective arguments.
318 BATON->path is the canonical path and BATON->token is the token.
319 For the other arguments, see svn_fs_unlock_many().
321 This implements the svn_fs_base__retry_txn() 'body' callback type.
324 txn_body_unlock(void *baton, trail_t *trail)
326 struct unlock_args *args = baton;
327 const char *lock_token;
330 /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
331 SVN_ERR(svn_fs_bdb__lock_token_get(&lock_token, trail->fs, args->path,
332 trail, trail->pool));
334 /* If not breaking the lock, we need to do some more checking. */
335 if (!args->break_lock)
337 /* Sanity check: The lock token must exist, and must match. */
338 if (args->token == NULL)
339 return svn_fs_base__err_no_lock_token(trail->fs, args->path);
340 else if (strcmp(lock_token, args->token) != 0)
341 return SVN_FS__ERR_NO_SUCH_LOCK(trail->fs, args->path);
343 SVN_ERR(svn_fs_bdb__lock_get(&lock, trail->fs, lock_token,
344 trail, trail->pool));
346 /* There better be a username attached to the fs. */
347 if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
348 return SVN_FS__ERR_NO_USER(trail->fs);
350 /* And that username better be the same as the lock's owner. */
351 if (strcmp(trail->fs->access_ctx->username, lock->owner) != 0)
352 return SVN_FS__ERR_LOCK_OWNER_MISMATCH(
354 trail->fs->access_ctx->username,
358 /* Remove a row from each of the locking tables. */
359 return delete_lock_and_token(lock_token, args->path, trail);
364 svn_fs_base__unlock(svn_fs_t *fs,
366 svn_boolean_t break_lock,
367 svn_fs_lock_callback_t lock_callback,
369 apr_pool_t *result_pool,
370 apr_pool_t *scratch_pool)
372 apr_hash_index_t *hi;
373 svn_error_t *cb_err = SVN_NO_ERROR;
374 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
376 SVN_ERR(svn_fs__check_fs(fs, TRUE));
378 for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
380 struct unlock_args args;
381 const char *path = apr_hash_this_key(hi);
382 const char *token = apr_hash_this_val(hi);
385 svn_pool_clear(iterpool);
386 args.path = svn_fs__canonicalize_abspath(path, result_pool);
388 args.break_lock = break_lock;
390 err = svn_fs_base__retry_txn(fs, txn_body_unlock, &args, TRUE,
392 if (!cb_err && lock_callback)
393 cb_err = lock_callback(lock_baton, path, NULL, err, iterpool);
394 svn_error_clear(err);
396 svn_pool_destroy(iterpool);
398 return svn_error_trace(cb_err);
403 svn_fs_base__get_lock_helper(svn_lock_t **lock_p,
408 const char *lock_token;
411 err = svn_fs_bdb__lock_token_get(&lock_token, trail->fs, path,
414 /* We've deliberately decided that this function doesn't tell the
415 caller *why* the lock is unavailable. */
416 if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
417 || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
418 || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
420 svn_error_clear(err);
427 /* Same situation here. */
428 err = svn_fs_bdb__lock_get(lock_p, trail->fs, lock_token, trail, pool);
429 if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
430 || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
432 svn_error_clear(err);
439 return svn_error_trace(err);
443 struct lock_token_get_args
451 txn_body_get_lock(void *baton, trail_t *trail)
453 struct lock_token_get_args *args = baton;
454 return svn_fs_base__get_lock_helper(args->lock_p, args->path,
460 svn_fs_base__get_lock(svn_lock_t **lock,
465 struct lock_token_get_args args;
467 SVN_ERR(svn_fs__check_fs(fs, TRUE));
469 args.path = svn_fs__canonicalize_abspath(path, pool);
471 return svn_fs_base__retry_txn(fs, txn_body_get_lock, &args, FALSE, pool);
474 /* Implements `svn_fs_get_locks_callback_t', spooling lock information
475 to a stream as the filesystem provides it. BATON is an 'svn_stream_t *'
476 object pointing to the stream. We'll write the spool stream with a
479 SKEL1_LEN "\n" SKEL1 "\n" SKEL2_LEN "\n" SKEL2 "\n" ...
481 where each skel is a lock skel (the same format we use to store
482 locks in the `locks' table). */
484 spool_locks_info(void *baton,
488 svn_skel_t *lock_skel;
489 svn_stream_t *stream = baton;
490 const char *skel_len;
491 svn_stringbuf_t *skel_buf;
494 SVN_ERR(svn_fs_base__unparse_lock_skel(&lock_skel, lock, pool));
495 skel_buf = svn_skel__unparse(lock_skel, pool);
496 skel_len = apr_psprintf(pool, "%" APR_SIZE_T_FMT "\n", skel_buf->len);
497 len = strlen(skel_len);
498 SVN_ERR(svn_stream_write(stream, skel_len, &len));
500 SVN_ERR(svn_stream_write(stream, skel_buf->data, &len));
502 return svn_stream_write(stream, "\n", &len);
506 struct locks_get_args
510 svn_stream_t *stream;
515 txn_body_get_locks(void *baton, trail_t *trail)
517 struct locks_get_args *args = baton;
518 return svn_fs_bdb__locks_get(trail->fs, args->path, args->depth,
519 spool_locks_info, args->stream,
525 svn_fs_base__get_locks(svn_fs_t *fs,
528 svn_fs_get_locks_callback_t get_locks_func,
529 void *get_locks_baton,
532 struct locks_get_args args;
533 svn_stream_t *stream;
534 svn_stringbuf_t *buf;
536 apr_pool_t *iterpool = svn_pool_create(pool);
538 SVN_ERR(svn_fs__check_fs(fs, TRUE));
540 args.path = svn_fs__canonicalize_abspath(path, pool);
542 /* Enough for 100+ locks if the comments are small. */
543 args.stream = svn_stream__from_spillbuf(svn_spillbuf__create(4 * 1024 /* blocksize */,
544 64 * 1024 /* maxsize */,
547 SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_locks, &args, FALSE, pool));
549 /* Read the stream calling GET_LOCKS_FUNC(). */
550 stream = args.stream;
554 apr_size_t len, skel_len;
556 svn_skel_t *lock_skel;
561 svn_pool_clear(iterpool);
563 /* Read a skel length line and parse it for the skel's length. */
564 SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, iterpool));
567 err = svn_cstring_strtoui64(&ui64, buf->data, 0, APR_SIZE_MAX, 10);
569 return svn_error_create(SVN_ERR_MALFORMED_FILE, err, NULL);
570 skel_len = (apr_size_t)ui64;
572 /* Now read that much into a buffer. */
573 skel_buf = apr_palloc(pool, skel_len + 1);
574 SVN_ERR(svn_stream_read_full(stream, skel_buf, &skel_len));
575 skel_buf[skel_len] = '\0';
577 /* Read the extra newline that follows the skel. */
579 SVN_ERR(svn_stream_read_full(stream, &c, &len));
581 return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, NULL);
583 /* Parse the skel into a lock, and notify the caller. */
584 lock_skel = svn_skel__parse(skel_buf, skel_len, iterpool);
585 SVN_ERR(svn_fs_base__parse_lock_skel(&lock, lock_skel, iterpool));
586 SVN_ERR(get_locks_func(get_locks_baton, lock, iterpool));
589 SVN_ERR(svn_stream_close(stream));
590 svn_pool_destroy(iterpool);
596 /* Utility function: verify that a lock can be used.
598 If no username is attached to the FS, return SVN_ERR_FS_NO_USER.
600 If the FS username doesn't match LOCK's owner, return
601 SVN_ERR_FS_LOCK_OWNER_MISMATCH.
603 If FS hasn't been supplied with a matching lock-token for LOCK,
604 return SVN_ERR_FS_BAD_LOCK_TOKEN.
606 Otherwise return SVN_NO_ERROR.
609 verify_lock(svn_fs_t *fs,
613 if ((! fs->access_ctx) || (! fs->access_ctx->username))
614 return svn_error_createf
615 (SVN_ERR_FS_NO_USER, NULL,
616 _("Cannot verify lock on path '%s'; no username available"),
619 else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
620 return svn_error_createf
621 (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
622 _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
623 fs->access_ctx->username, lock->path, lock->owner);
625 else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
626 return svn_error_createf
627 (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
628 _("Cannot verify lock on path '%s'; no matching lock-token available"),
635 /* This implements the svn_fs_get_locks_callback_t interface, where
636 BATON is just an svn_fs_t object. */
638 get_locks_callback(void *baton,
642 return verify_lock(baton, lock, pool);
646 /* The main routine for lock enforcement, used throughout libsvn_fs_base. */
648 svn_fs_base__allow_locked_operation(const char *path,
649 svn_boolean_t recurse,
655 /* Discover all locks at or below the path. */
656 SVN_ERR(svn_fs_bdb__locks_get(trail->fs, path, svn_depth_infinity,
658 trail->fs, trail, pool));
664 /* Discover any lock attached to the path. */
665 SVN_ERR(svn_fs_base__get_lock_helper(&lock, path, trail, pool));
667 SVN_ERR(verify_lock(trail->fs, lock, pool));