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 /* If directory PATH does not exist, create it and give it the same
139 permissions as FS->path.*/
141 ensure_dir_exists(const char *path
,
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
);
153 /* We successfully created a new directory. Dup the permissions
155 SVN_ERR(svn_fs_fs__dup_perms(path
, fs
->path
, pool
));
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. */
166 write_digest_file(apr_hash_t
*children
,
169 const char *digest_path
,
172 svn_error_t
*err
= SVN_NO_ERROR
;
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
),
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
));
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
))
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
,
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
));
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. */
246 read_digest_file(apr_hash_t
**children_p
,
249 const char *digest_path
,
252 svn_error_t
*err
= SVN_NO_ERROR
;
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
);
271 /* If our caller doesn't care about anything but the presence of the
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
,
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
);
294 const char *path
= val
;
296 /* Create our lock and load it up. */
297 lock
= svn_lock_create(pool
);
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
);
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
);
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);
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. */
346 set_lock(svn_fs_t
*fs
,
350 svn_stringbuf_t
*this_path
= svn_stringbuf_create(lock
->path
, pool
);
351 svn_stringbuf_t
*last_child
= svn_stringbuf_create("", pool
);
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
);
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). */
382 svn_stringbuf_set(last_child
, digest_file
);
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
))
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] == '/'))
398 svn_stringbuf_set(this_path
,
399 svn_path_dirname(this_path
->data
, subpool
));
402 svn_pool_destroy(subpool
);
406 /* Delete LOCK from FS in the actual OS filesystem. */
408 delete_lock(svn_fs_t
*fs
,
412 svn_stringbuf_t
*this_path
= svn_stringbuf_create(lock
->path
, pool
);
413 svn_stringbuf_t
*child_to_kill
= svn_stringbuf_create("", pool
);
418 /* Iterate in reverse, deleting the lock for LOCK->path, and then
419 pruning entries from its parents. */
420 subpool
= svn_pool_create(pool
);
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). */
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
));
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] == '/'))
468 svn_stringbuf_set(this_path
,
469 svn_path_dirname(this_path
->data
, subpool
));
472 svn_pool_destroy(subpool
);
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
481 get_lock(svn_lock_t
**lock_p
,
484 svn_boolean_t have_write_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
));
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. */
500 SVN_ERR(delete_lock(fs
, lock
, pool
));
502 return SVN_FS__ERR_LOCK_EXPIRED(fs
, lock
->token
);
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
515 get_lock_helper(svn_fs_t
*fs
,
518 svn_boolean_t have_write_lock
,
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
);
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. */
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
,
555 apr_hash_t
*children
;
557 apr_hash_index_t
*hi
;
560 /* First, send up any locks in the current digest file. */
561 SVN_ERR(read_digest_file(&children
, &lock
, fs
, digest_path
, pool
));
564 /* Don't report an expired lock. */
565 if (lock
->expiration_date
== 0
566 || (apr_time_now() <= lock
->expiration_date
))
569 SVN_ERR(get_locks_func(get_locks_baton
, lock
, pool
));
573 /* Only remove the lock if we have the write lock.
574 Read operations shouldn't change the filesystem. */
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
))
583 subpool
= svn_pool_create(pool
);
584 for (hi
= apr_hash_first(pool
, children
); hi
; hi
= apr_hash_next(hi
))
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
);
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.
606 verify_lock(svn_fs_t
*fs
,
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"),
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"),
633 /* This implements the svn_fs_get_locks_callback_t interface, where
634 BATON is just an svn_fs_t object. */
636 get_locks_callback(void *baton
,
640 return verify_lock(baton
, lock
, pool
);
644 /* The main routine for lock enforcement, used throughout libsvn_fs_fs. */
646 svn_fs_fs__allow_locked_operation(const char *path
,
648 svn_boolean_t recurse
,
649 svn_boolean_t have_write_lock
,
652 path
= svn_fs__canonicalize_abspath(path
, pool
);
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
));
662 /* Discover and verify any lock attached to the path. */
664 SVN_ERR(get_lock_helper(fs
, &lock
, path
, have_write_lock
, pool
));
666 SVN_ERR(verify_lock(fs
, lock
, pool
));
671 /* Baton used for lock_body below. */
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
;
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 *'. */
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
;
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"),
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
,
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
));
761 if (! lb
->steal_lock
)
763 /* Sorry, the path is already locked. */
764 return SVN_FS__ERR_PATH_ALREADY_LOCKED(lb
->fs
, existing_lock
);
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
);
778 lock
->token
= apr_pstrdup(lb
->pool
, lb
->token
);
780 SVN_ERR(svn_fs_fs__generate_lock_token(&(lock
->token
), lb
->fs
,
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
));
794 /* Baton used for unlock_body below. */
795 struct unlock_baton
{
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 *'. */
806 unlock_body(void *baton
, apr_pool_t
*pool
)
808 struct unlock_baton
*ub
= baton
;
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
));
838 /*** Public API implementations ***/
841 svn_fs_fs__lock(svn_lock_t
**lock_p
,
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
,
852 struct lock_baton lb
;
854 SVN_ERR(svn_fs__check_fs(fs
));
855 path
= svn_fs__canonicalize_abspath(path
, pool
);
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
;
868 SVN_ERR(svn_fs_fs__with_write_lock(fs
, lock_body
, &lb
, pool
));
875 svn_fs_fs__generate_lock_token(const char **token
,
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
);
892 svn_fs_fs__unlock(svn_fs_t
*fs
,
895 svn_boolean_t break_lock
,
898 struct unlock_baton ub
;
900 SVN_ERR(svn_fs__check_fs(fs
));
901 path
= svn_fs__canonicalize_abspath(path
, pool
);
906 ub
.break_lock
= break_lock
;
908 SVN_ERR(svn_fs_fs__with_write_lock(fs
, unlock_body
, &ub
, pool
));
915 svn_fs_fs__get_lock(svn_lock_t
**lock_p
,
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
);
927 svn_fs_fs__get_locks(svn_fs_t
*fs
,
929 svn_fs_get_locks_callback_t get_locks_func
,
930 void *get_locks_baton
,
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
);