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"
44 /* Add LOCK and its associated LOCK_TOKEN (associated with PATH) as
47 add_lock_and_token(svn_lock_t *lock,
48 const char *lock_token,
52 SVN_ERR(svn_fs_bdb__lock_add(trail->fs, lock_token, lock,
54 return svn_fs_bdb__lock_token_add(trail->fs, path, lock_token,
59 /* Delete LOCK_TOKEN and its corresponding lock (associated with PATH,
60 whose KIND is supplied), as part of TRAIL. */
62 delete_lock_and_token(const char *lock_token,
66 SVN_ERR(svn_fs_bdb__lock_delete(trail->fs, lock_token,
68 return svn_fs_bdb__lock_token_delete(trail->fs, path,
79 svn_boolean_t is_dav_comment;
80 svn_boolean_t steal_lock;
81 apr_time_t expiration_date;
82 svn_revnum_t current_rev;
87 txn_body_lock(void *baton, trail_t *trail)
89 struct lock_args *args = baton;
90 svn_node_kind_t kind = svn_node_file;
91 svn_lock_t *existing_lock;
94 SVN_ERR(svn_fs_base__get_path_kind(&kind, args->path, trail, trail->pool));
96 /* Until we implement directory locks someday, we only allow locks
97 on files or non-existent paths. */
98 if (kind == svn_node_dir)
99 return SVN_FS__ERR_NOT_FILE(trail->fs, args->path);
101 /* While our locking implementation easily supports the locking of
102 nonexistent paths, we deliberately choose not to allow such madness. */
103 if (kind == svn_node_none)
105 if (SVN_IS_VALID_REVNUM(args->current_rev))
106 return svn_error_createf(
107 SVN_ERR_FS_OUT_OF_DATE, NULL,
108 _("Path '%s' doesn't exist in HEAD revision"),
111 return svn_error_createf(
112 SVN_ERR_FS_NOT_FOUND, NULL,
113 _("Path '%s' doesn't exist in HEAD revision"),
117 /* There better be a username attached to the fs. */
118 if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
119 return SVN_FS__ERR_NO_USER(trail->fs);
121 /* Is the caller attempting to lock an out-of-date working file? */
122 if (SVN_IS_VALID_REVNUM(args->current_rev))
124 svn_revnum_t created_rev;
125 SVN_ERR(svn_fs_base__get_path_created_rev(&created_rev, args->path,
126 trail, trail->pool));
128 /* SVN_INVALID_REVNUM means the path doesn't exist. So
129 apparently somebody is trying to lock something in their
130 working copy, but somebody else has deleted the thing
131 from HEAD. That counts as being 'out of date'. */
132 if (! SVN_IS_VALID_REVNUM(created_rev))
133 return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
134 "Path '%s' doesn't exist in HEAD revision",
137 if (args->current_rev < created_rev)
138 return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
139 "Lock failed: newer version of '%s' exists",
143 /* If the caller provided a TOKEN, we *really* need to see
144 if a lock already exists with that token, and if so, verify that
145 the lock's path matches PATH. Otherwise we run the risk of
146 breaking the 1-to-1 mapping of lock tokens to locked paths. */
149 svn_lock_t *lock_from_token;
150 svn_error_t *err = svn_fs_bdb__lock_get(&lock_from_token, trail->fs,
153 if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
154 || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
156 svn_error_clear(err);
161 if (strcmp(lock_from_token->path, args->path) != 0)
162 return svn_error_create(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
163 "Lock failed: token refers to existing "
164 "lock with non-matching path.");
168 /* Is the path already locked?
170 Note that this next function call will automatically ignore any
171 errors about {the path not existing as a key, the path's token
172 not existing as a key, the lock just having been expired}. And
173 that's totally fine. Any of these three errors are perfectly
174 acceptable to ignore; it means that the path is now free and
175 clear for locking, because the bdb funcs just cleared out both
176 of the tables for us. */
177 SVN_ERR(svn_fs_base__get_lock_helper(&existing_lock, args->path,
178 trail, trail->pool));
181 if (! args->steal_lock)
183 /* Sorry, the path is already locked. */
184 return SVN_FS__ERR_PATH_ALREADY_LOCKED(trail->fs,
189 /* ARGS->steal_lock is set, so fs_username is "stealing" the
190 lock from lock->owner. Destroy the existing lock. */
191 SVN_ERR(delete_lock_and_token(existing_lock->token,
192 existing_lock->path, trail));
196 /* Create a new lock, and add it to the tables. */
197 lock = svn_lock_create(trail->pool);
199 lock->token = apr_pstrdup(trail->pool, args->token);
201 SVN_ERR(svn_fs_base__generate_lock_token(&(lock->token), trail->fs,
203 lock->path = apr_pstrdup(trail->pool, args->path);
204 lock->owner = apr_pstrdup(trail->pool, trail->fs->access_ctx->username);
205 lock->comment = apr_pstrdup(trail->pool, args->comment);
206 lock->is_dav_comment = args->is_dav_comment;
207 lock->creation_date = apr_time_now();
208 lock->expiration_date = args->expiration_date;
209 SVN_ERR(add_lock_and_token(lock, lock->token, args->path, trail));
210 *(args->lock_p) = lock;
218 svn_fs_base__lock(svn_lock_t **lock,
223 svn_boolean_t is_dav_comment,
224 apr_time_t expiration_date,
225 svn_revnum_t current_rev,
226 svn_boolean_t steal_lock,
229 struct lock_args args;
231 SVN_ERR(svn_fs__check_fs(fs, TRUE));
234 args.path = svn_fs__canonicalize_abspath(path, pool);
236 args.comment = comment;
237 args.is_dav_comment = is_dav_comment;
238 args.steal_lock = steal_lock;
239 args.expiration_date = expiration_date;
240 args.current_rev = current_rev;
242 return svn_fs_base__retry_txn(fs, txn_body_lock, &args, FALSE, pool);
247 svn_fs_base__generate_lock_token(const char **token,
251 /* Notice that 'fs' is currently unused. But perhaps someday, we'll
252 want to use the fs UUID + some incremented number? For now, we
253 generate a URI that matches the DAV RFC. We could change this to
254 some other URI scheme someday, if we wish. */
255 *token = apr_pstrcat(pool, "opaquelocktoken:",
256 svn_uuid_generate(pool), (char *)NULL);
265 svn_boolean_t break_lock;
270 txn_body_unlock(void *baton, trail_t *trail)
272 struct unlock_args *args = baton;
273 const char *lock_token;
276 /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
277 SVN_ERR(svn_fs_bdb__lock_token_get(&lock_token, trail->fs, args->path,
278 trail, trail->pool));
280 /* If not breaking the lock, we need to do some more checking. */
281 if (!args->break_lock)
283 /* Sanity check: The lock token must exist, and must match. */
284 if (args->token == NULL)
285 return svn_fs_base__err_no_lock_token(trail->fs, args->path);
286 else if (strcmp(lock_token, args->token) != 0)
287 return SVN_FS__ERR_NO_SUCH_LOCK(trail->fs, args->path);
289 SVN_ERR(svn_fs_bdb__lock_get(&lock, trail->fs, lock_token,
290 trail, trail->pool));
292 /* There better be a username attached to the fs. */
293 if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
294 return SVN_FS__ERR_NO_USER(trail->fs);
296 /* And that username better be the same as the lock's owner. */
297 if (strcmp(trail->fs->access_ctx->username, lock->owner) != 0)
298 return SVN_FS__ERR_LOCK_OWNER_MISMATCH(
300 trail->fs->access_ctx->username,
304 /* Remove a row from each of the locking tables. */
305 return delete_lock_and_token(lock_token, args->path, trail);
310 svn_fs_base__unlock(svn_fs_t *fs,
313 svn_boolean_t break_lock,
316 struct unlock_args args;
318 SVN_ERR(svn_fs__check_fs(fs, TRUE));
320 args.path = svn_fs__canonicalize_abspath(path, pool);
322 args.break_lock = break_lock;
323 return svn_fs_base__retry_txn(fs, txn_body_unlock, &args, TRUE, pool);
328 svn_fs_base__get_lock_helper(svn_lock_t **lock_p,
333 const char *lock_token;
336 err = svn_fs_bdb__lock_token_get(&lock_token, trail->fs, path,
339 /* We've deliberately decided that this function doesn't tell the
340 caller *why* the lock is unavailable. */
341 if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
342 || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
343 || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
345 svn_error_clear(err);
352 /* Same situation here. */
353 err = svn_fs_bdb__lock_get(lock_p, trail->fs, lock_token, trail, pool);
354 if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
355 || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
357 svn_error_clear(err);
364 return svn_error_trace(err);
368 struct lock_token_get_args
376 txn_body_get_lock(void *baton, trail_t *trail)
378 struct lock_token_get_args *args = baton;
379 return svn_fs_base__get_lock_helper(args->lock_p, args->path,
385 svn_fs_base__get_lock(svn_lock_t **lock,
390 struct lock_token_get_args args;
392 SVN_ERR(svn_fs__check_fs(fs, TRUE));
394 args.path = svn_fs__canonicalize_abspath(path, pool);
396 return svn_fs_base__retry_txn(fs, txn_body_get_lock, &args, FALSE, pool);
399 /* Implements `svn_fs_get_locks_callback_t', spooling lock information
400 to a stream as the filesystem provides it. BATON is an 'svn_stream_t *'
401 object pointing to the stream. We'll write the spool stream with a
404 SKEL1_LEN "\n" SKEL1 "\n" SKEL2_LEN "\n" SKEL2 "\n" ...
406 where each skel is a lock skel (the same format we use to store
407 locks in the `locks' table). */
409 spool_locks_info(void *baton,
413 svn_skel_t *lock_skel;
414 svn_stream_t *stream = baton;
415 const char *skel_len;
416 svn_stringbuf_t *skel_buf;
419 SVN_ERR(svn_fs_base__unparse_lock_skel(&lock_skel, lock, pool));
420 skel_buf = svn_skel__unparse(lock_skel, pool);
421 skel_len = apr_psprintf(pool, "%" APR_SIZE_T_FMT "\n", skel_buf->len);
422 len = strlen(skel_len);
423 SVN_ERR(svn_stream_write(stream, skel_len, &len));
425 SVN_ERR(svn_stream_write(stream, skel_buf->data, &len));
427 return svn_stream_write(stream, "\n", &len);
431 struct locks_get_args
435 svn_stream_t *stream;
440 txn_body_get_locks(void *baton, trail_t *trail)
442 struct locks_get_args *args = baton;
443 return svn_fs_bdb__locks_get(trail->fs, args->path, args->depth,
444 spool_locks_info, args->stream,
450 svn_fs_base__get_locks(svn_fs_t *fs,
453 svn_fs_get_locks_callback_t get_locks_func,
454 void *get_locks_baton,
457 struct locks_get_args args;
458 svn_stream_t *stream;
459 svn_stringbuf_t *buf;
461 apr_pool_t *iterpool = svn_pool_create(pool);
463 SVN_ERR(svn_fs__check_fs(fs, TRUE));
465 args.path = svn_fs__canonicalize_abspath(path, pool);
467 /* Enough for 100+ locks if the comments are small. */
468 args.stream = svn_stream__from_spillbuf(4 * 1024 /* blocksize */,
469 64 * 1024 /* maxsize */,
471 SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_locks, &args, FALSE, pool));
473 /* Read the stream calling GET_LOCKS_FUNC(). */
474 stream = args.stream;
478 apr_size_t len, skel_len;
480 svn_skel_t *lock_skel;
485 svn_pool_clear(iterpool);
487 /* Read a skel length line and parse it for the skel's length. */
488 SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, iterpool));
491 err = svn_cstring_strtoui64(&ui64, buf->data, 0, APR_SIZE_MAX, 10);
493 return svn_error_create(SVN_ERR_MALFORMED_FILE, err, NULL);
494 skel_len = (apr_size_t)ui64;
496 /* Now read that much into a buffer. */
497 skel_buf = apr_palloc(pool, skel_len + 1);
498 SVN_ERR(svn_stream_read(stream, skel_buf, &skel_len));
499 skel_buf[skel_len] = '\0';
501 /* Read the extra newline that follows the skel. */
503 SVN_ERR(svn_stream_read(stream, &c, &len));
505 return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, NULL);
507 /* Parse the skel into a lock, and notify the caller. */
508 lock_skel = svn_skel__parse(skel_buf, skel_len, iterpool);
509 SVN_ERR(svn_fs_base__parse_lock_skel(&lock, lock_skel, iterpool));
510 SVN_ERR(get_locks_func(get_locks_baton, lock, iterpool));
513 SVN_ERR(svn_stream_close(stream));
514 svn_pool_destroy(iterpool);
520 /* Utility function: verify that a lock can be used.
522 If no username is attached to the FS, return SVN_ERR_FS_NO_USER.
524 If the FS username doesn't match LOCK's owner, return
525 SVN_ERR_FS_LOCK_OWNER_MISMATCH.
527 If FS hasn't been supplied with a matching lock-token for LOCK,
528 return SVN_ERR_FS_BAD_LOCK_TOKEN.
530 Otherwise return SVN_NO_ERROR.
533 verify_lock(svn_fs_t *fs,
537 if ((! fs->access_ctx) || (! fs->access_ctx->username))
538 return svn_error_createf
539 (SVN_ERR_FS_NO_USER, NULL,
540 _("Cannot verify lock on path '%s'; no username available"),
543 else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
544 return svn_error_createf
545 (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
546 _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
547 fs->access_ctx->username, lock->path, lock->owner);
549 else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
550 return svn_error_createf
551 (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
552 _("Cannot verify lock on path '%s'; no matching lock-token available"),
559 /* This implements the svn_fs_get_locks_callback_t interface, where
560 BATON is just an svn_fs_t object. */
562 get_locks_callback(void *baton,
566 return verify_lock(baton, lock, pool);
570 /* The main routine for lock enforcement, used throughout libsvn_fs_base. */
572 svn_fs_base__allow_locked_operation(const char *path,
573 svn_boolean_t recurse,
579 /* Discover all locks at or below the path. */
580 SVN_ERR(svn_fs_bdb__locks_get(trail->fs, path, svn_depth_infinity,
582 trail->fs, trail, pool));
588 /* Discover any lock attached to the path. */
589 SVN_ERR(svn_fs_base__get_lock_helper(&lock, path, trail, pool));
591 SVN_ERR(verify_lock(trail->fs, lock, pool));