Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_fs_fs / lock.c
blob2a9913ffeb39ea31744ff2b72e3f4da412d6eb36
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 /* Write to DIGEST_PATH a representation of CHILDREN (which may be
139 empty, if the versioned path in FS represented by DIGEST_PATH has
140 no children) and LOCK (which may be NULL if that versioned path is
141 lock itself locked). Use POOL for all allocations. */
142 static svn_error_t *
143 write_digest_file(apr_hash_t *children,
144 svn_lock_t *lock,
145 svn_fs_t *fs,
146 const char *digest_path,
147 apr_pool_t *pool)
149 svn_error_t *err = SVN_NO_ERROR;
150 apr_file_t *fd;
151 apr_hash_index_t *hi;
152 apr_hash_t *hash = apr_hash_make(pool);
153 const char *tmp_path;
155 SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_path_join(fs->path, PATH_LOCKS_DIR,
156 pool), fs, pool));
157 SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_path_dirname(digest_path, pool), fs,
158 pool));
159 SVN_ERR(svn_io_open_unique_file2
160 (&fd, &tmp_path, digest_path, ".tmp", svn_io_file_del_none, pool));
162 if (lock)
164 const char *creation_date = NULL, *expiration_date = NULL;
165 if (lock->creation_date)
166 creation_date = svn_time_to_cstring(lock->creation_date, pool);
167 if (lock->expiration_date)
168 expiration_date = svn_time_to_cstring(lock->expiration_date, pool);
169 hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1,
170 lock->path, APR_HASH_KEY_STRING, pool);
171 hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1,
172 lock->token, APR_HASH_KEY_STRING, pool);
173 hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1,
174 lock->owner, APR_HASH_KEY_STRING, pool);
175 hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1,
176 lock->comment, APR_HASH_KEY_STRING, pool);
177 hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1,
178 lock->is_dav_comment ? "1" : "0", 1, pool);
179 hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1,
180 creation_date, APR_HASH_KEY_STRING, pool);
181 hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1,
182 expiration_date, APR_HASH_KEY_STRING, pool);
184 if (apr_hash_count(children))
186 svn_stringbuf_t *children_list = svn_stringbuf_create("", pool);
187 for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
189 const void *key;
190 apr_ssize_t klen;
191 apr_hash_this(hi, &key, &klen, NULL);
192 svn_stringbuf_appendbytes(children_list, key, klen);
193 svn_stringbuf_appendbytes(children_list, "\n", 1);
195 hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1,
196 children_list->data, children_list->len, pool);
199 if ((err = svn_hash_write2(hash,
200 svn_stream_from_aprfile(fd, pool),
201 SVN_HASH_TERMINATOR, pool)))
203 svn_error_clear(svn_io_file_close(fd, pool));
204 return svn_error_createf(err->apr_err,
205 err,
206 _("Cannot write lock/entries hashfile '%s'"),
207 svn_path_local_style(tmp_path, pool));
210 SVN_ERR(svn_io_file_close(fd, pool));
211 SVN_ERR(svn_io_file_rename(tmp_path, digest_path, pool));
212 SVN_ERR(svn_fs_fs__dup_perms
213 (digest_path, svn_fs_fs__path_rev(fs, 0, pool), pool));
215 return SVN_NO_ERROR;
219 /* Parse the file at DIGEST_PATH, populating the lock LOCK_P in that
220 file (if it exists, and if *LOCK_P is non-NULL) and the hash of
221 CHILDREN_P (if any exist, and if *CHILDREN_P is non-NULL). Use POOL
222 for all allocations. */
223 static svn_error_t *
224 read_digest_file(apr_hash_t **children_p,
225 svn_lock_t **lock_p,
226 svn_fs_t *fs,
227 const char *digest_path,
228 apr_pool_t *pool)
230 svn_error_t *err = SVN_NO_ERROR;
231 svn_lock_t *lock;
232 apr_hash_t *hash;
233 apr_file_t *fd;
234 const char *val;
236 if (lock_p)
237 *lock_p = NULL;
238 if (children_p)
239 *children_p = apr_hash_make(pool);
241 err = svn_io_file_open(&fd, digest_path, APR_READ, APR_OS_DEFAULT, pool);
242 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
244 svn_error_clear(err);
245 return SVN_NO_ERROR;
247 SVN_ERR(err);
249 /* If our caller doesn't care about anything but the presence of the
250 file... whatever. */
251 if (! (lock_p || children_p))
252 return svn_io_file_close(fd, pool);
254 hash = apr_hash_make(pool);
255 if ((err = svn_hash_read2(hash,
256 svn_stream_from_aprfile(fd, pool),
257 SVN_HASH_TERMINATOR, pool)))
259 svn_error_clear(svn_io_file_close(fd, pool));
260 return svn_error_createf(err->apr_err,
261 err,
262 _("Can't parse lock/entries hashfile '%s'"),
263 svn_path_local_style(digest_path, pool));
265 SVN_ERR(svn_io_file_close(fd, pool));
267 /* If our caller cares, see if we have a lock path in our hash. If
268 so, we'll assume we have a lock here. */
269 val = hash_fetch(hash, PATH_KEY, pool);
270 if (val && lock_p)
272 const char *path = val;
274 /* Create our lock and load it up. */
275 lock = svn_lock_create(pool);
276 lock->path = path;
278 if (! ((lock->token = hash_fetch(hash, TOKEN_KEY, pool))))
279 return svn_fs_fs__err_corrupt_lockfile(fs, path);
281 if (! ((lock->owner = hash_fetch(hash, OWNER_KEY, pool))))
282 return svn_fs_fs__err_corrupt_lockfile(fs, path);
284 if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY, pool))))
285 return svn_fs_fs__err_corrupt_lockfile(fs, path);
286 lock->is_dav_comment = (val[0] == '1') ? TRUE : FALSE;
288 if (! ((val = hash_fetch(hash, CREATION_DATE_KEY, pool))))
289 return svn_fs_fs__err_corrupt_lockfile(fs, path);
290 SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool));
292 if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY, pool)))
293 SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool));
295 lock->comment = hash_fetch(hash, COMMENT_KEY, pool);
297 *lock_p = lock;
300 /* If our caller cares, see if we have any children for this path. */
301 val = hash_fetch(hash, CHILDREN_KEY, pool);
302 if (val && children_p)
304 apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool);
305 int i;
307 for (i = 0; i < kiddos->nelts; i++)
309 apr_hash_set(*children_p, APR_ARRAY_IDX(kiddos, i, const char *),
310 APR_HASH_KEY_STRING, (void *)1);
313 return SVN_NO_ERROR;
318 /*** Lock helper functions (path here are still FS paths, not on-disk
319 schema-supporting paths) ***/
322 /* Write LOCK in FS to the actual OS filesystem. */
323 static svn_error_t *
324 set_lock(svn_fs_t *fs,
325 svn_lock_t *lock,
326 apr_pool_t *pool)
328 svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
329 svn_stringbuf_t *last_child = svn_stringbuf_create("", pool);
330 apr_pool_t *subpool;
332 assert(lock);
334 /* Iterate in reverse, creating the lock for LOCK->path, and then
335 just adding entries for its parent, until we reach a parent
336 that's already listed in *its* parent. */
337 subpool = svn_pool_create(pool);
338 while (1729)
340 const char *digest_path, *parent_dir, *digest_file;
341 apr_hash_t *this_children;
342 svn_lock_t *this_lock;
344 svn_pool_clear(subpool);
346 /* Calculate the DIGEST_PATH for the currently FS path, and then
347 split it into a PARENT_DIR and DIGEST_FILE basename. */
348 digest_path = digest_path_from_path(fs, this_path->data, subpool);
349 svn_path_split(digest_path, &parent_dir, &digest_file, subpool);
351 SVN_ERR(read_digest_file(&this_children, &this_lock, fs,
352 digest_path, subpool));
354 /* We're either writing a new lock (first time through only) or
355 a new entry (every time but the first). */
356 if (lock)
358 this_lock = lock;
359 lock = NULL;
360 svn_stringbuf_set(last_child, digest_file);
362 else
364 /* If we already have an entry for this path, we're done. */
365 if (apr_hash_get(this_children, last_child->data, last_child->len))
366 break;
367 apr_hash_set(this_children, last_child->data,
368 last_child->len, (void *)1);
370 SVN_ERR(write_digest_file(this_children, this_lock, fs,
371 digest_path, subpool));
373 /* Prep for next iteration, or bail if we're done. */
374 if ((this_path->len == 1) && (this_path->data[0] == '/'))
375 break;
376 svn_stringbuf_set(this_path,
377 svn_path_dirname(this_path->data, subpool));
380 svn_pool_destroy(subpool);
381 return SVN_NO_ERROR;
384 /* Delete LOCK from FS in the actual OS filesystem. */
385 static svn_error_t *
386 delete_lock(svn_fs_t *fs,
387 svn_lock_t *lock,
388 apr_pool_t *pool)
390 svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
391 svn_stringbuf_t *child_to_kill = svn_stringbuf_create("", pool);
392 apr_pool_t *subpool;
394 assert(lock);
396 /* Iterate in reverse, deleting the lock for LOCK->path, and then
397 pruning entries from its parents. */
398 subpool = svn_pool_create(pool);
399 while (1729)
401 const char *digest_path, *parent_dir, *digest_file;
402 apr_hash_t *this_children;
403 svn_lock_t *this_lock;
405 svn_pool_clear(subpool);
407 /* Calculate the DIGEST_PATH for the currently FS path, and then
408 split it into a PARENT_DIR and DIGEST_FILE basename. */
409 digest_path = digest_path_from_path(fs, this_path->data, subpool);
410 svn_path_split(digest_path, &parent_dir, &digest_file, subpool);
412 SVN_ERR(read_digest_file(&this_children, &this_lock, fs,
413 digest_path, subpool));
415 /* If we are supposed to drop the last entry from this path's
416 children list, do so. */
417 if (child_to_kill->len)
418 apr_hash_set(this_children, child_to_kill->data,
419 child_to_kill->len, NULL);
421 /* Delete the lock (first time through only). */
422 if (lock)
424 this_lock = NULL;
425 lock = NULL;
428 if (! (this_lock || apr_hash_count(this_children) != 0))
430 /* Special case: no goodz, no file. And remember to nix
431 the entry for it in its parent. */
432 svn_stringbuf_set(child_to_kill,
433 svn_path_basename(digest_path, subpool));
434 SVN_ERR(svn_io_remove_file(digest_path, subpool));
436 else
438 SVN_ERR(write_digest_file(this_children, this_lock, fs,
439 digest_path, subpool));
440 svn_stringbuf_setempty(child_to_kill);
443 /* Prep for next iteration, or bail if we're done. */
444 if ((this_path->len == 1) && (this_path->data[0] == '/'))
445 break;
446 svn_stringbuf_set(this_path,
447 svn_path_dirname(this_path->data, subpool));
450 svn_pool_destroy(subpool);
451 return SVN_NO_ERROR;
454 /* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
455 TRUE if the caller (or one of its callers) has taken out the
456 repository-wide write lock, FALSE otherwise. Use POOL for
457 allocations. */
458 static svn_error_t *
459 get_lock(svn_lock_t **lock_p,
460 svn_fs_t *fs,
461 const char *path,
462 svn_boolean_t have_write_lock,
463 apr_pool_t *pool)
465 svn_lock_t *lock;
466 const char *digest_path = digest_path_from_path(fs, path, pool);
468 SVN_ERR(read_digest_file(NULL, &lock, fs, digest_path, pool));
469 if (! lock)
470 return SVN_FS__ERR_NO_SUCH_LOCK(fs, path);
472 /* Don't return an expired lock. */
473 if (lock->expiration_date && (apr_time_now() > lock->expiration_date))
475 /* Only remove the lock if we have the write lock.
476 Read operations shouldn't change the filesystem. */
477 if (have_write_lock)
478 SVN_ERR(delete_lock(fs, lock, pool));
479 *lock_p = NULL;
480 return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
483 *lock_p = lock;
484 return SVN_NO_ERROR;
488 /* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
489 TRUE if the caller (or one of its callers) has taken out the
490 repository-wide write lock, FALSE otherwise. Use POOL for
491 allocations. */
492 static svn_error_t *
493 get_lock_helper(svn_fs_t *fs,
494 svn_lock_t **lock_p,
495 const char *path,
496 svn_boolean_t have_write_lock,
497 apr_pool_t *pool)
499 svn_lock_t *lock;
500 svn_error_t *err;
502 err = get_lock(&lock, fs, path, have_write_lock, pool);
504 /* We've deliberately decided that this function doesn't tell the
505 caller *why* the lock is unavailable. */
506 if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
507 || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)))
509 svn_error_clear(err);
510 *lock_p = NULL;
511 return SVN_NO_ERROR;
513 else
514 SVN_ERR(err);
516 *lock_p = lock;
517 return SVN_NO_ERROR;
521 /* A recursive function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
522 all locks in and under PATH in FS.
523 HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
524 has the FS write lock. */
525 static svn_error_t *
526 walk_digest_files(svn_fs_t *fs,
527 const char *digest_path,
528 svn_fs_get_locks_callback_t get_locks_func,
529 void *get_locks_baton,
530 svn_boolean_t have_write_lock,
531 apr_pool_t *pool)
533 apr_hash_t *children;
534 svn_lock_t *lock;
535 apr_hash_index_t *hi;
536 apr_pool_t *subpool;
538 /* First, send up any locks in the current digest file. */
539 SVN_ERR(read_digest_file(&children, &lock, fs, digest_path, pool));
540 if (lock)
542 /* Don't report an expired lock. */
543 if (lock->expiration_date == 0
544 || (apr_time_now() <= lock->expiration_date))
546 if (get_locks_func)
547 SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
549 else
551 /* Only remove the lock if we have the write lock.
552 Read operations shouldn't change the filesystem. */
553 if (have_write_lock)
554 SVN_ERR(delete_lock(fs, lock, pool));
558 /* Now, recurse on this thing's child entries (if any; bail otherwise). */
559 if (! apr_hash_count(children))
560 return SVN_NO_ERROR;
561 subpool = svn_pool_create(pool);
562 for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
564 const void *key;
565 svn_pool_clear(subpool);
566 apr_hash_this(hi, &key, NULL, NULL);
567 SVN_ERR(walk_digest_files
568 (fs, digest_path_from_digest(fs, key, subpool),
569 get_locks_func, get_locks_baton, have_write_lock, subpool));
571 svn_pool_destroy(subpool);
572 return SVN_NO_ERROR;
576 /* Utility function: verify that a lock can be used. Interesting
577 errors returned from this function:
579 SVN_ERR_FS_NO_USER: No username attached to FS.
580 SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner.
581 SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK.
583 static svn_error_t *
584 verify_lock(svn_fs_t *fs,
585 svn_lock_t *lock,
586 apr_pool_t *pool)
588 if ((! fs->access_ctx) || (! fs->access_ctx->username))
589 return svn_error_createf
590 (SVN_ERR_FS_NO_USER, NULL,
591 _("Cannot verify lock on path '%s'; no username available"),
592 lock->path);
594 else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
595 return svn_error_createf
596 (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
597 _("User %s does not own lock on path '%s' (currently locked by %s)"),
598 fs->access_ctx->username, lock->path, lock->owner);
600 else if (apr_hash_get(fs->access_ctx->lock_tokens, lock->token,
601 APR_HASH_KEY_STRING) == NULL)
602 return svn_error_createf
603 (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
604 _("Cannot verify lock on path '%s'; no matching lock-token available"),
605 lock->path);
607 return SVN_NO_ERROR;
611 /* This implements the svn_fs_get_locks_callback_t interface, where
612 BATON is just an svn_fs_t object. */
613 static svn_error_t *
614 get_locks_callback(void *baton,
615 svn_lock_t *lock,
616 apr_pool_t *pool)
618 return verify_lock(baton, lock, pool);
622 /* The main routine for lock enforcement, used throughout libsvn_fs_fs. */
623 svn_error_t *
624 svn_fs_fs__allow_locked_operation(const char *path,
625 svn_fs_t *fs,
626 svn_boolean_t recurse,
627 svn_boolean_t have_write_lock,
628 apr_pool_t *pool)
630 path = svn_fs__canonicalize_abspath(path, pool);
631 if (recurse)
633 /* Discover all locks at or below the path. */
634 const char *digest_path = digest_path_from_path(fs, path, pool);
635 SVN_ERR(walk_digest_files(fs, digest_path, get_locks_callback,
636 fs, have_write_lock, pool));
638 else
640 /* Discover and verify any lock attached to the path. */
641 svn_lock_t *lock;
642 SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock, pool));
643 if (lock)
644 SVN_ERR(verify_lock(fs, lock, pool));
646 return SVN_NO_ERROR;
649 /* Baton used for lock_body below. */
650 struct lock_baton {
651 svn_lock_t **lock_p;
652 svn_fs_t *fs;
653 const char *path;
654 const char *token;
655 const char *comment;
656 svn_boolean_t is_dav_comment;
657 apr_time_t expiration_date;
658 svn_revnum_t current_rev;
659 svn_boolean_t steal_lock;
660 apr_pool_t *pool;
664 /* This implements the svn_fs_fs__with_write_lock() 'body' callback
665 type, and assumes that the write lock is held.
666 BATON is a 'struct lock_baton *'. */
667 static svn_error_t *
668 lock_body(void *baton, apr_pool_t *pool)
670 struct lock_baton *lb = baton;
671 svn_node_kind_t kind;
672 svn_lock_t *existing_lock;
673 svn_lock_t *lock;
674 svn_fs_root_t *root;
675 svn_revnum_t youngest;
677 /* Until we implement directory locks someday, we only allow locks
678 on files or non-existent paths. */
679 /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
680 library dependencies, which are not portable. */
681 SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
682 SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
683 SVN_ERR(svn_fs_fs__check_path(&kind, root, lb->path, pool));
684 if (kind == svn_node_dir)
685 return SVN_FS__ERR_NOT_FILE(lb->fs, lb->path);
687 /* While our locking implementation easily supports the locking of
688 nonexistent paths, we deliberately choose not to allow such madness. */
689 if (kind == svn_node_none)
690 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
691 _("Path '%s' doesn't exist in HEAD revision"),
692 lb->path);
694 /* We need to have a username attached to the fs. */
695 if (!lb->fs->access_ctx || !lb->fs->access_ctx->username)
696 return SVN_FS__ERR_NO_USER(lb->fs);
698 /* Is the caller attempting to lock an out-of-date working file? */
699 if (SVN_IS_VALID_REVNUM(lb->current_rev))
701 svn_revnum_t created_rev;
702 SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, lb->path,
703 pool));
705 /* SVN_INVALID_REVNUM means the path doesn't exist. So
706 apparently somebody is trying to lock something in their
707 working copy, but somebody else has deleted the thing
708 from HEAD. That counts as being 'out of date'. */
709 if (! SVN_IS_VALID_REVNUM(created_rev))
710 return svn_error_createf
711 (SVN_ERR_FS_OUT_OF_DATE, NULL,
712 _("Path '%s' doesn't exist in HEAD revision"), lb->path);
714 if (lb->current_rev < created_rev)
715 return svn_error_createf
716 (SVN_ERR_FS_OUT_OF_DATE, NULL,
717 _("Lock failed: newer version of '%s' exists"), lb->path);
720 /* If the caller provided a TOKEN, we *really* need to see
721 if a lock already exists with that token, and if so, verify that
722 the lock's path matches PATH. Otherwise we run the risk of
723 breaking the 1-to-1 mapping of lock tokens to locked paths. */
724 /* ### TODO: actually do this check. This is tough, because the
725 schema doesn't supply a lookup-by-token mechanism. */
727 /* Is the path already locked?
729 Note that this next function call will automatically ignore any
730 errors about {the path not existing as a key, the path's token
731 not existing as a key, the lock just having been expired}. And
732 that's totally fine. Any of these three errors are perfectly
733 acceptable to ignore; it means that the path is now free and
734 clear for locking, because the fsfs funcs just cleared out both
735 of the tables for us. */
736 SVN_ERR(get_lock_helper(lb->fs, &existing_lock, lb->path, TRUE, pool));
737 if (existing_lock)
739 if (! lb->steal_lock)
741 /* Sorry, the path is already locked. */
742 return SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
744 else
746 /* STEAL_LOCK was passed, so fs_username is "stealing" the
747 lock from lock->owner. Destroy the existing lock. */
748 SVN_ERR(delete_lock(lb->fs, existing_lock, pool));
752 /* Create our new lock, and add it to the tables.
753 Ensure that the lock is created in the correct pool. */
754 lock = svn_lock_create(lb->pool);
755 if (lb->token)
756 lock->token = apr_pstrdup(lb->pool, lb->token);
757 else
758 SVN_ERR(svn_fs_fs__generate_lock_token(&(lock->token), lb->fs,
759 lb->pool));
760 lock->path = apr_pstrdup(lb->pool, lb->path);
761 lock->owner = apr_pstrdup(lb->pool, lb->fs->access_ctx->username);
762 lock->comment = apr_pstrdup(lb->pool, lb->comment);
763 lock->is_dav_comment = lb->is_dav_comment;
764 lock->creation_date = apr_time_now();
765 lock->expiration_date = lb->expiration_date;
766 SVN_ERR(set_lock(lb->fs, lock, pool));
767 *lb->lock_p = lock;
769 return SVN_NO_ERROR;
772 /* Baton used for unlock_body below. */
773 struct unlock_baton {
774 svn_fs_t *fs;
775 const char *path;
776 const char *token;
777 svn_boolean_t break_lock;
780 /* This implements the svn_fs_fs__with_write_lock() 'body' callback
781 type, and assumes that the write lock is held.
782 BATON is a 'struct unlock_baton *'. */
783 static svn_error_t *
784 unlock_body(void *baton, apr_pool_t *pool)
786 struct unlock_baton *ub = baton;
787 svn_lock_t *lock;
789 /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
790 SVN_ERR(get_lock(&lock, ub->fs, ub->path, TRUE, pool));
792 /* Unless breaking the lock, we do some checks. */
793 if (! ub->break_lock)
795 /* Sanity check: the incoming token should match lock->token. */
796 if (strcmp(ub->token, lock->token) != 0)
797 return SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, lock->path);
799 /* There better be a username attached to the fs. */
800 if (! (ub->fs->access_ctx && ub->fs->access_ctx->username))
801 return SVN_FS__ERR_NO_USER(ub->fs);
803 /* And that username better be the same as the lock's owner. */
804 if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
805 return SVN_FS__ERR_LOCK_OWNER_MISMATCH
806 (ub->fs, ub->fs->access_ctx->username, lock->owner);
809 /* Remove lock and lock token files. */
810 SVN_ERR(delete_lock(ub->fs, lock, pool));
812 return SVN_NO_ERROR;
816 /*** Public API implementations ***/
818 svn_error_t *
819 svn_fs_fs__lock(svn_lock_t **lock_p,
820 svn_fs_t *fs,
821 const char *path,
822 const char *token,
823 const char *comment,
824 svn_boolean_t is_dav_comment,
825 apr_time_t expiration_date,
826 svn_revnum_t current_rev,
827 svn_boolean_t steal_lock,
828 apr_pool_t *pool)
830 struct lock_baton lb;
832 SVN_ERR(svn_fs__check_fs(fs, TRUE));
833 path = svn_fs__canonicalize_abspath(path, pool);
835 lb.lock_p = lock_p;
836 lb.fs = fs;
837 lb.path = path;
838 lb.token = token;
839 lb.comment = comment;
840 lb.is_dav_comment = is_dav_comment;
841 lb.expiration_date = expiration_date;
842 lb.current_rev = current_rev;
843 lb.steal_lock = steal_lock;
844 lb.pool = pool;
846 SVN_ERR(svn_fs_fs__with_write_lock(fs, lock_body, &lb, pool));
848 return SVN_NO_ERROR;
852 svn_error_t *
853 svn_fs_fs__generate_lock_token(const char **token,
854 svn_fs_t *fs,
855 apr_pool_t *pool)
857 SVN_ERR(svn_fs__check_fs(fs, TRUE));
859 /* Notice that 'fs' is currently unused. But perhaps someday, we'll
860 want to use the fs UUID + some incremented number? For now, we
861 generate a URI that matches the DAV RFC. We could change this to
862 some other URI scheme someday, if we wish. */
863 *token = apr_pstrcat(pool, "opaquelocktoken:",
864 svn_uuid_generate(pool), NULL);
865 return SVN_NO_ERROR;
869 svn_error_t *
870 svn_fs_fs__unlock(svn_fs_t *fs,
871 const char *path,
872 const char *token,
873 svn_boolean_t break_lock,
874 apr_pool_t *pool)
876 struct unlock_baton ub;
878 SVN_ERR(svn_fs__check_fs(fs, TRUE));
879 path = svn_fs__canonicalize_abspath(path, pool);
881 ub.fs = fs;
882 ub.path = path;
883 ub.token = token;
884 ub.break_lock = break_lock;
886 SVN_ERR(svn_fs_fs__with_write_lock(fs, unlock_body, &ub, pool));
888 return SVN_NO_ERROR;
892 svn_error_t *
893 svn_fs_fs__get_lock(svn_lock_t **lock_p,
894 svn_fs_t *fs,
895 const char *path,
896 apr_pool_t *pool)
898 SVN_ERR(svn_fs__check_fs(fs, TRUE));
899 path = svn_fs__canonicalize_abspath(path, pool);
900 return get_lock_helper(fs, lock_p, path, FALSE, pool);
904 svn_error_t *
905 svn_fs_fs__get_locks(svn_fs_t *fs,
906 const char *path,
907 svn_fs_get_locks_callback_t get_locks_func,
908 void *get_locks_baton,
909 apr_pool_t *pool)
911 const char *digest_path;
913 SVN_ERR(svn_fs__check_fs(fs, TRUE));
914 path = svn_fs__canonicalize_abspath(path, pool);
916 /* Get the top digest path in our tree of interest, and then walk it. */
917 digest_path = digest_path_from_path(fs, path, pool);
918 return walk_digest_files(fs, digest_path, get_locks_func,
919 get_locks_baton, FALSE, pool);