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 * ====================================================================
20 #include "svn_pools.h"
21 #include "svn_error.h"
30 #include <apr_file_io.h>
31 #include <apr_file_info.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. */
63 make_digest(const char *str
,
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. */
79 hash_store(apr_hash_t
*hash
,
83 apr_ssize_t value_len
,
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). */
98 hash_fetch(apr_hash_t
*hash
,
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. */
113 digest_path_from_digest(svn_fs_t
*fs
,
117 return svn_path_join_many(pool
, fs
->path
, PATH_LOCKS_DIR
,
118 apr_pstrmemdup(pool
, digest
, DIGEST_SUBDIR_LEN
),
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
127 digest_path_from_path(svn_fs_t
*fs
,
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
),
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. */
143 write_digest_file(apr_hash_t
*children
,
146 const char *digest_path
,
149 svn_error_t
*err
= SVN_NO_ERROR
;
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
,
157 SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_path_dirname(digest_path
, pool
), fs
,
159 SVN_ERR(svn_io_open_unique_file2
160 (&fd
, &tmp_path
, digest_path
, ".tmp", svn_io_file_del_none
, pool
));
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
))
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
,
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
));
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. */
224 read_digest_file(apr_hash_t
**children_p
,
227 const char *digest_path
,
230 svn_error_t
*err
= SVN_NO_ERROR
;
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
);
249 /* If our caller doesn't care about anything but the presence of the
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
,
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
);
272 const char *path
= val
;
274 /* Create our lock and load it up. */
275 lock
= svn_lock_create(pool
);
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
);
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
);
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);
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. */
324 set_lock(svn_fs_t
*fs
,
328 svn_stringbuf_t
*this_path
= svn_stringbuf_create(lock
->path
, pool
);
329 svn_stringbuf_t
*last_child
= svn_stringbuf_create("", pool
);
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
);
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). */
360 svn_stringbuf_set(last_child
, digest_file
);
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
))
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] == '/'))
376 svn_stringbuf_set(this_path
,
377 svn_path_dirname(this_path
->data
, subpool
));
380 svn_pool_destroy(subpool
);
384 /* Delete LOCK from FS in the actual OS filesystem. */
386 delete_lock(svn_fs_t
*fs
,
390 svn_stringbuf_t
*this_path
= svn_stringbuf_create(lock
->path
, pool
);
391 svn_stringbuf_t
*child_to_kill
= svn_stringbuf_create("", pool
);
396 /* Iterate in reverse, deleting the lock for LOCK->path, and then
397 pruning entries from its parents. */
398 subpool
= svn_pool_create(pool
);
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). */
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
));
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] == '/'))
446 svn_stringbuf_set(this_path
,
447 svn_path_dirname(this_path
->data
, subpool
));
450 svn_pool_destroy(subpool
);
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
459 get_lock(svn_lock_t
**lock_p
,
462 svn_boolean_t have_write_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
));
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. */
478 SVN_ERR(delete_lock(fs
, lock
, pool
));
480 return SVN_FS__ERR_LOCK_EXPIRED(fs
, lock
->token
);
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
493 get_lock_helper(svn_fs_t
*fs
,
496 svn_boolean_t have_write_lock
,
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
);
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. */
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
,
533 apr_hash_t
*children
;
535 apr_hash_index_t
*hi
;
538 /* First, send up any locks in the current digest file. */
539 SVN_ERR(read_digest_file(&children
, &lock
, fs
, digest_path
, pool
));
542 /* Don't report an expired lock. */
543 if (lock
->expiration_date
== 0
544 || (apr_time_now() <= lock
->expiration_date
))
547 SVN_ERR(get_locks_func(get_locks_baton
, lock
, pool
));
551 /* Only remove the lock if we have the write lock.
552 Read operations shouldn't change the filesystem. */
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
))
561 subpool
= svn_pool_create(pool
);
562 for (hi
= apr_hash_first(pool
, children
); hi
; hi
= apr_hash_next(hi
))
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
);
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.
584 verify_lock(svn_fs_t
*fs
,
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"),
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"),
611 /* This implements the svn_fs_get_locks_callback_t interface, where
612 BATON is just an svn_fs_t object. */
614 get_locks_callback(void *baton
,
618 return verify_lock(baton
, lock
, pool
);
622 /* The main routine for lock enforcement, used throughout libsvn_fs_fs. */
624 svn_fs_fs__allow_locked_operation(const char *path
,
626 svn_boolean_t recurse
,
627 svn_boolean_t have_write_lock
,
630 path
= svn_fs__canonicalize_abspath(path
, pool
);
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
));
640 /* Discover and verify any lock attached to the path. */
642 SVN_ERR(get_lock_helper(fs
, &lock
, path
, have_write_lock
, pool
));
644 SVN_ERR(verify_lock(fs
, lock
, pool
));
649 /* Baton used for lock_body below. */
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
;
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 *'. */
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
;
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"),
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
,
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
));
739 if (! lb
->steal_lock
)
741 /* Sorry, the path is already locked. */
742 return SVN_FS__ERR_PATH_ALREADY_LOCKED(lb
->fs
, existing_lock
);
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
);
756 lock
->token
= apr_pstrdup(lb
->pool
, lb
->token
);
758 SVN_ERR(svn_fs_fs__generate_lock_token(&(lock
->token
), lb
->fs
,
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
));
772 /* Baton used for unlock_body below. */
773 struct unlock_baton
{
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 *'. */
784 unlock_body(void *baton
, apr_pool_t
*pool
)
786 struct unlock_baton
*ub
= baton
;
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
));
816 /*** Public API implementations ***/
819 svn_fs_fs__lock(svn_lock_t
**lock_p
,
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
,
830 struct lock_baton lb
;
832 SVN_ERR(svn_fs__check_fs(fs
, TRUE
));
833 path
= svn_fs__canonicalize_abspath(path
, pool
);
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
;
846 SVN_ERR(svn_fs_fs__with_write_lock(fs
, lock_body
, &lb
, pool
));
853 svn_fs_fs__generate_lock_token(const char **token
,
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
);
870 svn_fs_fs__unlock(svn_fs_t
*fs
,
873 svn_boolean_t break_lock
,
876 struct unlock_baton ub
;
878 SVN_ERR(svn_fs__check_fs(fs
, TRUE
));
879 path
= svn_fs__canonicalize_abspath(path
, pool
);
884 ub
.break_lock
= break_lock
;
886 SVN_ERR(svn_fs_fs__with_write_lock(fs
, unlock_body
, &ub
, pool
));
893 svn_fs_fs__get_lock(svn_lock_t
**lock_p
,
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
);
905 svn_fs_fs__get_locks(svn_fs_t
*fs
,
907 svn_fs_get_locks_callback_t get_locks_func
,
908 void *get_locks_baton
,
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
);