In the command-line client, forbid
[svn.git] / subversion / libsvn_fs_fs / lock.c
blob3a93eaf34a79f09a9e614e667afbb61fa515fb5e
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 <assert.h>
20 #include "svn_pools.h"
21 #include "svn_error.h"
22 #include "svn_path.h"
23 #include "svn_fs.h"
24 #include "svn_hash.h"
25 #include "svn_time.h"
26 #include "svn_utf.h"
27 #include "svn_md5.h"
29 #include <apr_uuid.h>
30 #include <apr_file_io.h>
31 #include <apr_file_info.h>
32 #include <apr_md5.h>
34 #include "lock.h"
35 #include "tree.h"
36 #include "err.h"
37 #include "fs_fs.h"
38 #include "../libsvn_fs/fs-loader.h"
40 #include "private/svn_fs_util.h"
41 #include "svn_private_config.h"
43 /* Names of hash keys used to store a lock for writing to disk. */
44 #define PATH_KEY "path"
45 #define TOKEN_KEY "token"
46 #define OWNER_KEY "owner"
47 #define CREATION_DATE_KEY "creation_date"
48 #define EXPIRATION_DATE_KEY "expiration_date"
49 #define COMMENT_KEY "comment"
50 #define IS_DAV_COMMENT_KEY "is_dav_comment"
51 #define CHILDREN_KEY "children"
53 /* Number of characters from the head of a digest file name used to
54 calculate a subdirectory in which to drop that file. */
55 #define DIGEST_SUBDIR_LEN 3
59 /*** Generic helper functions. ***/
61 /* Return the MD5 hash of STR. */
62 static const char *
63 make_digest(const char *str,
64 apr_pool_t *pool)
66 unsigned char digest[APR_MD5_DIGESTSIZE];
68 apr_md5(digest, str, strlen(str));
69 return svn_md5_digest_to_cstring_display(digest, pool);
73 /* Set the value of KEY (whose size is KEY_LEN, or APR_HASH_KEY_STRING
74 if unknown) to an svn_string_t-ized version of VALUE (whose size is
75 VALUE_LEN, or APR_HASH_KEY_STRING if unknown) in HASH. The value
76 will be allocated in POOL; KEY will not be duped. If either KEY or VALUE
77 is NULL, this function will do nothing. */
78 static void
79 hash_store(apr_hash_t *hash,
80 const char *key,
81 apr_ssize_t key_len,
82 const char *value,
83 apr_ssize_t value_len,
84 apr_pool_t *pool)
86 if (! (key && value))
87 return;
88 if (value_len == APR_HASH_KEY_STRING)
89 value_len = strlen(value);
90 apr_hash_set(hash, key, key_len,
91 svn_string_ncreate(value, value_len, pool));
95 /* Fetch the value of KEY from HASH, returning only the cstring data
96 of that value (if it exists). */
97 static const char *
98 hash_fetch(apr_hash_t *hash,
99 const char *key,
100 apr_pool_t *pool)
102 svn_string_t *str = apr_hash_get(hash, key, APR_HASH_KEY_STRING);
103 return str ? str->data : NULL;
108 /*** Digest file handling functions. ***/
110 /* Return the path of the lock/entries file for which DIGEST is the
111 hashed repository relative path. */
112 static const char *
113 digest_path_from_digest(svn_fs_t *fs,
114 const char *digest,
115 apr_pool_t *pool)
117 return svn_path_join_many(pool, fs->path, PATH_LOCKS_DIR,
118 apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN),
119 digest, NULL);
123 /* Return the path to the lock/entries digest file associate with
124 PATH, where PATH is the path to the lock file or lock entries file
125 in FS. */
126 static const char *
127 digest_path_from_path(svn_fs_t *fs,
128 const char *path,
129 apr_pool_t *pool)
131 const char *digest = make_digest(path, pool);
132 return svn_path_join_many(pool, fs->path, PATH_LOCKS_DIR,
133 apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN),
134 digest, NULL);
138 /* If directory PATH does not exist, create it and give it the same
139 permissions as FS->path.*/
140 static svn_error_t *
141 ensure_dir_exists(const char *path,
142 svn_fs_t *fs,
143 apr_pool_t *pool)
145 svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
146 if (err && APR_STATUS_IS_EEXIST(err->apr_err))
148 svn_error_clear(err);
149 return SVN_NO_ERROR;
151 SVN_ERR(err);
153 /* We successfully created a new directory. Dup the permissions
154 from FS->path. */
155 SVN_ERR(svn_fs_fs__dup_perms(path, fs->path, pool));
157 return SVN_NO_ERROR;
161 /* Write to DIGEST_PATH a representation of CHILDREN (which may be
162 empty, if the versioned path in FS represented by DIGEST_PATH has
163 no children) and LOCK (which may be NULL if that versioned path is
164 lock itself locked). Use POOL for all allocations. */
165 static svn_error_t *
166 write_digest_file(apr_hash_t *children,
167 svn_lock_t *lock,
168 svn_fs_t *fs,
169 const char *digest_path,
170 apr_pool_t *pool)
172 svn_error_t *err = SVN_NO_ERROR;
173 apr_file_t *fd;
174 apr_hash_index_t *hi;
175 apr_hash_t *hash = apr_hash_make(pool);
176 const char *tmp_path;
178 SVN_ERR(ensure_dir_exists(svn_path_join(fs->path, PATH_LOCKS_DIR, pool),
179 fs, pool));
180 SVN_ERR(ensure_dir_exists(svn_path_dirname(digest_path, pool), fs, pool));
181 SVN_ERR(svn_io_open_unique_file2
182 (&fd, &tmp_path, digest_path, ".tmp", svn_io_file_del_none, pool));
184 if (lock)
186 const char *creation_date = NULL, *expiration_date = NULL;
187 if (lock->creation_date)
188 creation_date = svn_time_to_cstring(lock->creation_date, pool);
189 if (lock->expiration_date)
190 expiration_date = svn_time_to_cstring(lock->expiration_date, pool);
191 hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1,
192 lock->path, APR_HASH_KEY_STRING, pool);
193 hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1,
194 lock->token, APR_HASH_KEY_STRING, pool);
195 hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1,
196 lock->owner, APR_HASH_KEY_STRING, pool);
197 hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1,
198 lock->comment, APR_HASH_KEY_STRING, pool);
199 hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1,
200 lock->is_dav_comment ? "1" : "0", 1, pool);
201 hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1,
202 creation_date, APR_HASH_KEY_STRING, pool);
203 hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1,
204 expiration_date, APR_HASH_KEY_STRING, pool);
206 if (apr_hash_count(children))
208 svn_stringbuf_t *children_list = svn_stringbuf_create("", pool);
209 for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
211 const void *key;
212 apr_ssize_t klen;
213 apr_hash_this(hi, &key, &klen, NULL);
214 svn_stringbuf_appendbytes(children_list, key, klen);
215 svn_stringbuf_appendbytes(children_list, "\n", 1);
217 hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1,
218 children_list->data, children_list->len, pool);
221 if ((err = svn_hash_write2(hash,
222 svn_stream_from_aprfile(fd, pool),
223 SVN_HASH_TERMINATOR, pool)))
225 svn_error_clear(svn_io_file_close(fd, pool));
226 return svn_error_createf(err->apr_err,
227 err,
228 _("Cannot write lock/entries hashfile '%s'"),
229 svn_path_local_style(tmp_path, pool));
232 SVN_ERR(svn_io_file_close(fd, pool));
233 SVN_ERR(svn_io_file_rename(tmp_path, digest_path, pool));
234 SVN_ERR(svn_fs_fs__dup_perms
235 (digest_path, svn_fs_fs__path_rev(fs, 0, pool), pool));
237 return SVN_NO_ERROR;
241 /* Parse the file at DIGEST_PATH, populating the lock LOCK_P in that
242 file (if it exists, and if *LOCK_P is non-NULL) and the hash of
243 CHILDREN_P (if any exist, and if *CHILDREN_P is non-NULL). Use POOL
244 for all allocations. */
245 static svn_error_t *
246 read_digest_file(apr_hash_t **children_p,
247 svn_lock_t **lock_p,
248 svn_fs_t *fs,
249 const char *digest_path,
250 apr_pool_t *pool)
252 svn_error_t *err = SVN_NO_ERROR;
253 svn_lock_t *lock;
254 apr_hash_t *hash;
255 apr_file_t *fd;
256 const char *val;
258 if (lock_p)
259 *lock_p = NULL;
260 if (children_p)
261 *children_p = apr_hash_make(pool);
263 err = svn_io_file_open(&fd, digest_path, APR_READ, APR_OS_DEFAULT, pool);
264 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
266 svn_error_clear(err);
267 return SVN_NO_ERROR;
269 SVN_ERR(err);
271 /* If our caller doesn't care about anything but the presence of the
272 file... whatever. */
273 if (! (lock_p || children_p))
274 return svn_io_file_close(fd, pool);
276 hash = apr_hash_make(pool);
277 if ((err = svn_hash_read2(hash,
278 svn_stream_from_aprfile(fd, pool),
279 SVN_HASH_TERMINATOR, pool)))
281 svn_error_clear(svn_io_file_close(fd, pool));
282 return svn_error_createf(err->apr_err,
283 err,
284 _("Can't parse lock/entries hashfile '%s'"),
285 svn_path_local_style(digest_path, pool));
287 SVN_ERR(svn_io_file_close(fd, pool));
289 /* If our caller cares, see if we have a lock path in our hash. If
290 so, we'll assume we have a lock here. */
291 val = hash_fetch(hash, PATH_KEY, pool);
292 if (val && lock_p)
294 const char *path = val;
296 /* Create our lock and load it up. */
297 lock = svn_lock_create(pool);
298 lock->path = path;
300 if (! ((lock->token = hash_fetch(hash, TOKEN_KEY, pool))))
301 return svn_fs_fs__err_corrupt_lockfile(fs, path);
303 if (! ((lock->owner = hash_fetch(hash, OWNER_KEY, pool))))
304 return svn_fs_fs__err_corrupt_lockfile(fs, path);
306 if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY, pool))))
307 return svn_fs_fs__err_corrupt_lockfile(fs, path);
308 lock->is_dav_comment = (val[0] == '1') ? TRUE : FALSE;
310 if (! ((val = hash_fetch(hash, CREATION_DATE_KEY, pool))))
311 return svn_fs_fs__err_corrupt_lockfile(fs, path);
312 SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool));
314 if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY, pool)))
315 SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool));
317 lock->comment = hash_fetch(hash, COMMENT_KEY, pool);
319 *lock_p = lock;
322 /* If our caller cares, see if we have any children for this path. */
323 val = hash_fetch(hash, CHILDREN_KEY, pool);
324 if (val && children_p)
326 apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool);
327 int i;
329 for (i = 0; i < kiddos->nelts; i++)
331 apr_hash_set(*children_p, APR_ARRAY_IDX(kiddos, i, const char *),
332 APR_HASH_KEY_STRING, (void *)1);
335 return SVN_NO_ERROR;
340 /*** Lock helper functions (path here are still FS paths, not on-disk
341 schema-supporting paths) ***/
344 /* Write LOCK in FS to the actual OS filesystem. */
345 static svn_error_t *
346 set_lock(svn_fs_t *fs,
347 svn_lock_t *lock,
348 apr_pool_t *pool)
350 svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
351 svn_stringbuf_t *last_child = svn_stringbuf_create("", pool);
352 apr_pool_t *subpool;
354 assert(lock);
356 /* Iterate in reverse, creating the lock for LOCK->path, and then
357 just adding entries for its parent, until we reach a parent
358 that's already listed in *its* parent. */
359 subpool = svn_pool_create(pool);
360 while (1729)
362 const char *digest_path, *parent_dir, *digest_file;
363 apr_hash_t *this_children;
364 svn_lock_t *this_lock;
366 svn_pool_clear(subpool);
368 /* Calculate the DIGEST_PATH for the currently FS path, and then
369 split it into a PARENT_DIR and DIGEST_FILE basename. */
370 digest_path = digest_path_from_path(fs, this_path->data, subpool);
371 svn_path_split(digest_path, &parent_dir, &digest_file, subpool);
373 SVN_ERR(read_digest_file(&this_children, &this_lock, fs,
374 digest_path, subpool));
376 /* We're either writing a new lock (first time through only) or
377 a new entry (every time but the first). */
378 if (lock)
380 this_lock = lock;
381 lock = NULL;
382 svn_stringbuf_set(last_child, digest_file);
384 else
386 /* If we already have an entry for this path, we're done. */
387 if (apr_hash_get(this_children, last_child->data, last_child->len))
388 break;
389 apr_hash_set(this_children, last_child->data,
390 last_child->len, (void *)1);
392 SVN_ERR(write_digest_file(this_children, this_lock, fs,
393 digest_path, subpool));
395 /* Prep for next iteration, or bail if we're done. */
396 if ((this_path->len == 1) && (this_path->data[0] == '/'))
397 break;
398 svn_stringbuf_set(this_path,
399 svn_path_dirname(this_path->data, subpool));
402 svn_pool_destroy(subpool);
403 return SVN_NO_ERROR;
406 /* Delete LOCK from FS in the actual OS filesystem. */
407 static svn_error_t *
408 delete_lock(svn_fs_t *fs,
409 svn_lock_t *lock,
410 apr_pool_t *pool)
412 svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
413 svn_stringbuf_t *child_to_kill = svn_stringbuf_create("", pool);
414 apr_pool_t *subpool;
416 assert(lock);
418 /* Iterate in reverse, deleting the lock for LOCK->path, and then
419 pruning entries from its parents. */
420 subpool = svn_pool_create(pool);
421 while (1729)
423 const char *digest_path, *parent_dir, *digest_file;
424 apr_hash_t *this_children;
425 svn_lock_t *this_lock;
427 svn_pool_clear(subpool);
429 /* Calculate the DIGEST_PATH for the currently FS path, and then
430 split it into a PARENT_DIR and DIGEST_FILE basename. */
431 digest_path = digest_path_from_path(fs, this_path->data, subpool);
432 svn_path_split(digest_path, &parent_dir, &digest_file, subpool);
434 SVN_ERR(read_digest_file(&this_children, &this_lock, fs,
435 digest_path, subpool));
437 /* If we are supposed to drop the last entry from this path's
438 children list, do so. */
439 if (child_to_kill->len)
440 apr_hash_set(this_children, child_to_kill->data,
441 child_to_kill->len, NULL);
443 /* Delete the lock (first time through only). */
444 if (lock)
446 this_lock = NULL;
447 lock = NULL;
450 if (! (this_lock || apr_hash_count(this_children) != 0))
452 /* Special case: no goodz, no file. And remember to nix
453 the entry for it in its parent. */
454 svn_stringbuf_set(child_to_kill,
455 svn_path_basename(digest_path, subpool));
456 SVN_ERR(svn_io_remove_file(digest_path, subpool));
458 else
460 SVN_ERR(write_digest_file(this_children, this_lock, fs,
461 digest_path, subpool));
462 svn_stringbuf_setempty(child_to_kill);
465 /* Prep for next iteration, or bail if we're done. */
466 if ((this_path->len == 1) && (this_path->data[0] == '/'))
467 break;
468 svn_stringbuf_set(this_path,
469 svn_path_dirname(this_path->data, subpool));
472 svn_pool_destroy(subpool);
473 return SVN_NO_ERROR;
476 /* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
477 TRUE if the caller (or one of its callers) has taken out the
478 repository-wide write lock, FALSE otherwise. Use POOL for
479 allocations. */
480 static svn_error_t *
481 get_lock(svn_lock_t **lock_p,
482 svn_fs_t *fs,
483 const char *path,
484 svn_boolean_t have_write_lock,
485 apr_pool_t *pool)
487 svn_lock_t *lock;
488 const char *digest_path = digest_path_from_path(fs, path, pool);
490 SVN_ERR(read_digest_file(NULL, &lock, fs, digest_path, pool));
491 if (! lock)
492 return SVN_FS__ERR_NO_SUCH_LOCK(fs, path);
494 /* Don't return an expired lock. */
495 if (lock->expiration_date && (apr_time_now() > lock->expiration_date))
497 /* Only remove the lock if we have the write lock.
498 Read operations shouldn't change the filesystem. */
499 if (have_write_lock)
500 SVN_ERR(delete_lock(fs, lock, pool));
501 *lock_p = NULL;
502 return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
505 *lock_p = lock;
506 return SVN_NO_ERROR;
510 /* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
511 TRUE if the caller (or one of its callers) has taken out the
512 repository-wide write lock, FALSE otherwise. Use POOL for
513 allocations. */
514 static svn_error_t *
515 get_lock_helper(svn_fs_t *fs,
516 svn_lock_t **lock_p,
517 const char *path,
518 svn_boolean_t have_write_lock,
519 apr_pool_t *pool)
521 svn_lock_t *lock;
522 svn_error_t *err;
524 err = get_lock(&lock, fs, path, have_write_lock, pool);
526 /* We've deliberately decided that this function doesn't tell the
527 caller *why* the lock is unavailable. */
528 if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
529 || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)))
531 svn_error_clear(err);
532 *lock_p = NULL;
533 return SVN_NO_ERROR;
535 else
536 SVN_ERR(err);
538 *lock_p = lock;
539 return SVN_NO_ERROR;
543 /* A recursive function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
544 all locks in and under PATH in FS.
545 HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
546 has the FS write lock. */
547 static svn_error_t *
548 walk_digest_files(svn_fs_t *fs,
549 const char *digest_path,
550 svn_fs_get_locks_callback_t get_locks_func,
551 void *get_locks_baton,
552 svn_boolean_t have_write_lock,
553 apr_pool_t *pool)
555 apr_hash_t *children;
556 svn_lock_t *lock;
557 apr_hash_index_t *hi;
558 apr_pool_t *subpool;
560 /* First, send up any locks in the current digest file. */
561 SVN_ERR(read_digest_file(&children, &lock, fs, digest_path, pool));
562 if (lock)
564 /* Don't report an expired lock. */
565 if (lock->expiration_date == 0
566 || (apr_time_now() <= lock->expiration_date))
568 if (get_locks_func)
569 SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
571 else
573 /* Only remove the lock if we have the write lock.
574 Read operations shouldn't change the filesystem. */
575 if (have_write_lock)
576 SVN_ERR(delete_lock(fs, lock, pool));
580 /* Now, recurse on this thing's child entries (if any; bail otherwise). */
581 if (! apr_hash_count(children))
582 return SVN_NO_ERROR;
583 subpool = svn_pool_create(pool);
584 for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
586 const void *key;
587 svn_pool_clear(subpool);
588 apr_hash_this(hi, &key, NULL, NULL);
589 SVN_ERR(walk_digest_files
590 (fs, digest_path_from_digest(fs, key, subpool),
591 get_locks_func, get_locks_baton, have_write_lock, subpool));
593 svn_pool_destroy(subpool);
594 return SVN_NO_ERROR;
598 /* Utility function: verify that a lock can be used. Interesting
599 errors returned from this function:
601 SVN_ERR_FS_NO_USER: No username attached to FS.
602 SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner.
603 SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK.
605 static svn_error_t *
606 verify_lock(svn_fs_t *fs,
607 svn_lock_t *lock,
608 apr_pool_t *pool)
610 if ((! fs->access_ctx) || (! fs->access_ctx->username))
611 return svn_error_createf
612 (SVN_ERR_FS_NO_USER, NULL,
613 _("Cannot verify lock on path '%s'; no username available"),
614 lock->path);
616 else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
617 return svn_error_createf
618 (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
619 _("User %s does not own lock on path '%s' (currently locked by %s)"),
620 fs->access_ctx->username, lock->path, lock->owner);
622 else if (apr_hash_get(fs->access_ctx->lock_tokens, lock->token,
623 APR_HASH_KEY_STRING) == NULL)
624 return svn_error_createf
625 (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
626 _("Cannot verify lock on path '%s'; no matching lock-token available"),
627 lock->path);
629 return SVN_NO_ERROR;
633 /* This implements the svn_fs_get_locks_callback_t interface, where
634 BATON is just an svn_fs_t object. */
635 static svn_error_t *
636 get_locks_callback(void *baton,
637 svn_lock_t *lock,
638 apr_pool_t *pool)
640 return verify_lock(baton, lock, pool);
644 /* The main routine for lock enforcement, used throughout libsvn_fs_fs. */
645 svn_error_t *
646 svn_fs_fs__allow_locked_operation(const char *path,
647 svn_fs_t *fs,
648 svn_boolean_t recurse,
649 svn_boolean_t have_write_lock,
650 apr_pool_t *pool)
652 path = svn_fs__canonicalize_abspath(path, pool);
653 if (recurse)
655 /* Discover all locks at or below the path. */
656 const char *digest_path = digest_path_from_path(fs, path, pool);
657 SVN_ERR(walk_digest_files(fs, digest_path, get_locks_callback,
658 fs, have_write_lock, pool));
660 else
662 /* Discover and verify any lock attached to the path. */
663 svn_lock_t *lock;
664 SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock, pool));
665 if (lock)
666 SVN_ERR(verify_lock(fs, lock, pool));
668 return SVN_NO_ERROR;
671 /* Baton used for lock_body below. */
672 struct lock_baton {
673 svn_lock_t **lock_p;
674 svn_fs_t *fs;
675 const char *path;
676 const char *token;
677 const char *comment;
678 svn_boolean_t is_dav_comment;
679 apr_time_t expiration_date;
680 svn_revnum_t current_rev;
681 svn_boolean_t steal_lock;
682 apr_pool_t *pool;
686 /* This implements the svn_fs_fs__with_write_lock() 'body' callback
687 type, and assumes that the write lock is held.
688 BATON is a 'struct lock_baton *'. */
689 static svn_error_t *
690 lock_body(void *baton, apr_pool_t *pool)
692 struct lock_baton *lb = baton;
693 svn_node_kind_t kind;
694 svn_lock_t *existing_lock;
695 svn_lock_t *lock;
696 svn_fs_root_t *root;
697 svn_revnum_t youngest;
699 /* Until we implement directory locks someday, we only allow locks
700 on files or non-existent paths. */
701 /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
702 library dependencies, which are not portable. */
703 SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
704 SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
705 SVN_ERR(svn_fs_fs__check_path(&kind, root, lb->path, pool));
706 if (kind == svn_node_dir)
707 return SVN_FS__ERR_NOT_FILE(lb->fs, lb->path);
709 /* While our locking implementation easily supports the locking of
710 nonexistent paths, we deliberately choose not to allow such madness. */
711 if (kind == svn_node_none)
712 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
713 _("Path '%s' doesn't exist in HEAD revision"),
714 lb->path);
716 /* We need to have a username attached to the fs. */
717 if (!lb->fs->access_ctx || !lb->fs->access_ctx->username)
718 return SVN_FS__ERR_NO_USER(lb->fs);
720 /* Is the caller attempting to lock an out-of-date working file? */
721 if (SVN_IS_VALID_REVNUM(lb->current_rev))
723 svn_revnum_t created_rev;
724 SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, lb->path,
725 pool));
727 /* SVN_INVALID_REVNUM means the path doesn't exist. So
728 apparently somebody is trying to lock something in their
729 working copy, but somebody else has deleted the thing
730 from HEAD. That counts as being 'out of date'. */
731 if (! SVN_IS_VALID_REVNUM(created_rev))
732 return svn_error_createf
733 (SVN_ERR_FS_OUT_OF_DATE, NULL,
734 _("Path '%s' doesn't exist in HEAD revision"), lb->path);
736 if (lb->current_rev < created_rev)
737 return svn_error_createf
738 (SVN_ERR_FS_OUT_OF_DATE, NULL,
739 _("Lock failed: newer version of '%s' exists"), lb->path);
742 /* If the caller provided a TOKEN, we *really* need to see
743 if a lock already exists with that token, and if so, verify that
744 the lock's path matches PATH. Otherwise we run the risk of
745 breaking the 1-to-1 mapping of lock tokens to locked paths. */
746 /* ### TODO: actually do this check. This is tough, because the
747 schema doesn't supply a lookup-by-token mechanism. */
749 /* Is the path already locked?
751 Note that this next function call will automatically ignore any
752 errors about {the path not existing as a key, the path's token
753 not existing as a key, the lock just having been expired}. And
754 that's totally fine. Any of these three errors are perfectly
755 acceptable to ignore; it means that the path is now free and
756 clear for locking, because the fsfs funcs just cleared out both
757 of the tables for us. */
758 SVN_ERR(get_lock_helper(lb->fs, &existing_lock, lb->path, TRUE, pool));
759 if (existing_lock)
761 if (! lb->steal_lock)
763 /* Sorry, the path is already locked. */
764 return SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
766 else
768 /* STEAL_LOCK was passed, so fs_username is "stealing" the
769 lock from lock->owner. Destroy the existing lock. */
770 SVN_ERR(delete_lock(lb->fs, existing_lock, pool));
774 /* Create our new lock, and add it to the tables.
775 Ensure that the lock is created in the correct pool. */
776 lock = svn_lock_create(lb->pool);
777 if (lb->token)
778 lock->token = apr_pstrdup(lb->pool, lb->token);
779 else
780 SVN_ERR(svn_fs_fs__generate_lock_token(&(lock->token), lb->fs,
781 lb->pool));
782 lock->path = apr_pstrdup(lb->pool, lb->path);
783 lock->owner = apr_pstrdup(lb->pool, lb->fs->access_ctx->username);
784 lock->comment = apr_pstrdup(lb->pool, lb->comment);
785 lock->is_dav_comment = lb->is_dav_comment;
786 lock->creation_date = apr_time_now();
787 lock->expiration_date = lb->expiration_date;
788 SVN_ERR(set_lock(lb->fs, lock, pool));
789 *lb->lock_p = lock;
791 return SVN_NO_ERROR;
794 /* Baton used for unlock_body below. */
795 struct unlock_baton {
796 svn_fs_t *fs;
797 const char *path;
798 const char *token;
799 svn_boolean_t break_lock;
802 /* This implements the svn_fs_fs__with_write_lock() 'body' callback
803 type, and assumes that the write lock is held.
804 BATON is a 'struct unlock_baton *'. */
805 static svn_error_t *
806 unlock_body(void *baton, apr_pool_t *pool)
808 struct unlock_baton *ub = baton;
809 svn_lock_t *lock;
811 /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
812 SVN_ERR(get_lock(&lock, ub->fs, ub->path, TRUE, pool));
814 /* Unless breaking the lock, we do some checks. */
815 if (! ub->break_lock)
817 /* Sanity check: the incoming token should match lock->token. */
818 if (strcmp(ub->token, lock->token) != 0)
819 return SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, lock->path);
821 /* There better be a username attached to the fs. */
822 if (! (ub->fs->access_ctx && ub->fs->access_ctx->username))
823 return SVN_FS__ERR_NO_USER(ub->fs);
825 /* And that username better be the same as the lock's owner. */
826 if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
827 return SVN_FS__ERR_LOCK_OWNER_MISMATCH
828 (ub->fs, ub->fs->access_ctx->username, lock->owner);
831 /* Remove lock and lock token files. */
832 SVN_ERR(delete_lock(ub->fs, lock, pool));
834 return SVN_NO_ERROR;
838 /*** Public API implementations ***/
840 svn_error_t *
841 svn_fs_fs__lock(svn_lock_t **lock_p,
842 svn_fs_t *fs,
843 const char *path,
844 const char *token,
845 const char *comment,
846 svn_boolean_t is_dav_comment,
847 apr_time_t expiration_date,
848 svn_revnum_t current_rev,
849 svn_boolean_t steal_lock,
850 apr_pool_t *pool)
852 struct lock_baton lb;
854 SVN_ERR(svn_fs__check_fs(fs));
855 path = svn_fs__canonicalize_abspath(path, pool);
857 lb.lock_p = lock_p;
858 lb.fs = fs;
859 lb.path = path;
860 lb.token = token;
861 lb.comment = comment;
862 lb.is_dav_comment = is_dav_comment;
863 lb.expiration_date = expiration_date;
864 lb.current_rev = current_rev;
865 lb.steal_lock = steal_lock;
866 lb.pool = pool;
868 SVN_ERR(svn_fs_fs__with_write_lock(fs, lock_body, &lb, pool));
870 return SVN_NO_ERROR;
874 svn_error_t *
875 svn_fs_fs__generate_lock_token(const char **token,
876 svn_fs_t *fs,
877 apr_pool_t *pool)
879 SVN_ERR(svn_fs__check_fs(fs));
881 /* Notice that 'fs' is currently unused. But perhaps someday, we'll
882 want to use the fs UUID + some incremented number? For now, we
883 generate a URI that matches the DAV RFC. We could change this to
884 some other URI scheme someday, if we wish. */
885 *token = apr_pstrcat(pool, "opaquelocktoken:",
886 svn_uuid_generate(pool), NULL);
887 return SVN_NO_ERROR;
891 svn_error_t *
892 svn_fs_fs__unlock(svn_fs_t *fs,
893 const char *path,
894 const char *token,
895 svn_boolean_t break_lock,
896 apr_pool_t *pool)
898 struct unlock_baton ub;
900 SVN_ERR(svn_fs__check_fs(fs));
901 path = svn_fs__canonicalize_abspath(path, pool);
903 ub.fs = fs;
904 ub.path = path;
905 ub.token = token;
906 ub.break_lock = break_lock;
908 SVN_ERR(svn_fs_fs__with_write_lock(fs, unlock_body, &ub, pool));
910 return SVN_NO_ERROR;
914 svn_error_t *
915 svn_fs_fs__get_lock(svn_lock_t **lock_p,
916 svn_fs_t *fs,
917 const char *path,
918 apr_pool_t *pool)
920 SVN_ERR(svn_fs__check_fs(fs));
921 path = svn_fs__canonicalize_abspath(path, pool);
922 return get_lock_helper(fs, lock_p, path, FALSE, pool);
926 svn_error_t *
927 svn_fs_fs__get_locks(svn_fs_t *fs,
928 const char *path,
929 svn_fs_get_locks_callback_t get_locks_func,
930 void *get_locks_baton,
931 apr_pool_t *pool)
933 const char *digest_path;
935 SVN_ERR(svn_fs__check_fs(fs));
936 path = svn_fs__canonicalize_abspath(path, pool);
938 /* Get the top digest path in our tree of interest, and then walk it. */
939 digest_path = digest_path_from_path(fs, path, pool);
940 return walk_digest_files(fs, digest_path, get_locks_func,
941 get_locks_baton, FALSE, pool);