2 * lock.c: mod_dav_svn locking provider functions
4 * ====================================================================
5 * Copyright (c) 2000-2006 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
27 #include "svn_repos.h"
30 #include "svn_pools.h"
31 #include "private/svn_log.h"
36 struct dav_lockdb_private
38 /* These represent 'custom' request hearders only sent by svn clients: */
39 svn_boolean_t lock_steal
;
40 svn_boolean_t lock_break
;
41 svn_boolean_t keep_locks
;
42 svn_revnum_t working_revnum
;
44 /* The original request, so we can set 'custom' output headers. */
49 /* Helper func: convert an svn_lock_t to a dav_lock, allocated in
50 pool. EXISTS_P indicates whether slock->path actually exists or not.
51 If HIDE_AUTH_USER is set, then do not return the svn lock's 'owner'
55 svn_lock_to_dav_lock(dav_lock
**dlock
,
56 const svn_lock_t
*slock
,
57 svn_boolean_t hide_auth_user
,
58 svn_boolean_t exists_p
,
61 dav_lock
*lock
= apr_pcalloc(pool
, sizeof(*lock
));
62 dav_locktoken
*token
= apr_pcalloc(pool
, sizeof(*token
));
64 lock
->rectype
= DAV_LOCKREC_DIRECT
;
65 lock
->scope
= DAV_LOCKSCOPE_EXCLUSIVE
;
66 lock
->type
= DAV_LOCKTYPE_WRITE
;
68 lock
->is_locknull
= exists_p
;
70 token
->uuid_str
= apr_pstrdup(pool
, slock
->token
);
71 lock
->locktoken
= token
;
73 /* the svn_lock_t 'comment' field maps to the 'DAV:owner' field. */
76 if (! slock
->is_dav_comment
)
78 /* This comment was originally given to us by an svn client,
79 so, we need to wrap the naked comment with <DAV:owner>,
80 and xml-escape it for safe transport, lest we send back
81 an invalid http response. (mod_dav won't do it for us,
82 because it assumes that it personally created every lock
83 in the repository.) */
84 lock
->owner
= apr_pstrcat(pool
,
85 "<D:owner xmlns:D=\"DAV:\">",
86 apr_xml_quote_string(pool
,
92 lock
->owner
= apr_pstrdup(pool
, slock
->comment
);
98 /* the svn_lock_t 'owner' is the actual authenticated owner of the
99 lock, and maps to the 'auth_user' field in the mod_dav lock. */
101 /* (If the client ran 'svn unlock --force', then we don't want to
102 return lock->auth_user. Otherwise mod_dav will throw an error
103 when lock->auth_user and r->user don't match.) */
104 if (! hide_auth_user
)
105 lock
->auth_user
= apr_pstrdup(pool
, slock
->owner
);
107 /* This is absurd. apr_time.h has an apr_time_t->time_t func,
108 but not the reverse?? */
109 if (slock
->expiration_date
)
110 lock
->timeout
= (time_t) (slock
->expiration_date
/ APR_USEC_PER_SEC
);
112 lock
->timeout
= DAV_TIMEOUT_INFINITE
;
118 /* Helper func for dav_lock_to_svn_lock: take an incoming
119 "<D:owner><foo></D:owner>" tag and convert it to
122 unescape_xml(const char **output
,
126 apr_xml_parser
*xml_parser
= apr_xml_parser_create(pool
);
127 apr_xml_doc
*xml_doc
;
128 apr_status_t apr_err
;
129 const char *xml_input
= apr_pstrcat
130 (pool
, "<?xml version=\"1.0\" encoding=\"utf-8\"?>", input
, NULL
);
132 apr_err
= apr_xml_parser_feed(xml_parser
, xml_input
, strlen(xml_input
));
134 apr_err
= apr_xml_parser_done(xml_parser
, &xml_doc
);
139 (void)apr_xml_parser_geterror(xml_parser
, errbuf
, sizeof(errbuf
));
140 return dav_new_error(pool
, HTTP_INTERNAL_SERVER_ERROR
,
141 DAV_ERR_LOCK_SAVE_LOCK
, errbuf
);
144 apr_xml_to_text(pool
, xml_doc
->root
, APR_XML_X2T_INNER
,
145 xml_doc
->namespaces
, NULL
, output
, NULL
);
150 /* Helper func: convert a dav_lock to an svn_lock_t, allocated in pool. */
152 dav_lock_to_svn_lock(svn_lock_t
**slock
,
153 const dav_lock
*dlock
,
155 dav_lockdb_private
*info
,
156 svn_boolean_t is_svn_client
,
162 if (dlock
->type
!= DAV_LOCKTYPE_WRITE
)
163 return dav_new_error(pool
, HTTP_BAD_REQUEST
,
164 DAV_ERR_LOCK_SAVE_LOCK
,
165 "Only 'write' locks are supported.");
167 if (dlock
->scope
!= DAV_LOCKSCOPE_EXCLUSIVE
)
168 return dav_new_error(pool
, HTTP_BAD_REQUEST
,
169 DAV_ERR_LOCK_SAVE_LOCK
,
170 "Only exclusive locks are supported.");
172 lock
= svn_lock_create(pool
);
173 lock
->path
= apr_pstrdup(pool
, path
);
174 lock
->token
= apr_pstrdup(pool
, dlock
->locktoken
->uuid_str
);
176 /* DAV has no concept of lock creationdate, so assume 'now' */
177 lock
->creation_date
= apr_time_now();
179 if (dlock
->auth_user
)
180 lock
->owner
= apr_pstrdup(pool
, dlock
->auth_user
);
182 /* We need to be very careful about stripping the <D:owner> tag away
183 from the cdata. It's okay to do for svn clients, but not other
189 /* mod_dav has forcibly xml-escaped the comment before
190 handing it to us; we need to xml-unescape it (and remove
191 the <D:owner> wrapper) when storing in the repository, so
192 it looks reasonable to the rest of svn. */
194 lock
->is_dav_comment
= 0; /* comment is NOT xml-wrapped. */
195 derr
= unescape_xml(&(lock
->comment
), dlock
->owner
, pool
);
201 /* The comment comes from a non-svn client; don't touch
203 lock
->comment
= apr_pstrdup(pool
, dlock
->owner
);
204 lock
->is_dav_comment
= 1; /* comment IS xml-wrapped. */
208 if (dlock
->timeout
== DAV_TIMEOUT_INFINITE
)
209 lock
->expiration_date
= 0; /* never expires */
211 lock
->expiration_date
= ((apr_time_t
)dlock
->timeout
) * APR_USEC_PER_SEC
;
218 /* ---------------------------------------------------------------- */
219 /* mod_dav locking vtable starts here: */
222 /* Return the supportedlock property for a resource */
224 get_supportedlock(const dav_resource
*resource
)
226 /* This is imitating what mod_dav_fs is doing. Note that unlike
227 mod_dav_fs, however, we don't support "shared" locks, only
228 "exclusive" ones. Nor do we support locks on collections. */
229 static const char supported
[] = DEBUG_CR
230 "<D:lockentry>" DEBUG_CR
231 "<D:lockscope><D:exclusive/></D:lockscope>" DEBUG_CR
232 "<D:locktype><D:write/></D:locktype>" DEBUG_CR
233 "</D:lockentry>" DEBUG_CR
;
235 if (resource
->collection
)
242 /* Parse a lock token URI, returning a lock token object allocated
246 parse_locktoken(apr_pool_t
*pool
,
247 const char *char_token
,
248 dav_locktoken
**locktoken_p
)
250 dav_locktoken
*token
= apr_pcalloc(pool
, sizeof(*token
));
252 /* libsvn_fs already produces a valid locktoken URI. */
253 token
->uuid_str
= apr_pstrdup(pool
, char_token
);
255 *locktoken_p
= token
;
260 /* Format a lock token object into a URI string, allocated in
263 * Always returns non-NULL.
266 format_locktoken(apr_pool_t
*p
, const dav_locktoken
*locktoken
)
268 /* libsvn_fs already produces a valid locktoken URI. */
269 return apr_pstrdup(p
, locktoken
->uuid_str
);
273 /* Compare two lock tokens.
275 * Result < 0 => lt1 < lt2
276 * Result == 0 => lt1 == lt2
277 * Result > 0 => lt1 > lt2
280 compare_locktoken(const dav_locktoken
*lt1
, const dav_locktoken
*lt2
)
282 return strcmp(lt1
->uuid_str
, lt2
->uuid_str
);
286 /* Open the provider's lock database.
288 * The provider may or may not use a "real" database for locks
289 * (a lock could be an attribute on a resource, for example).
291 * The provider may choose to use the value of the DAVLockDB directive
292 * (as returned by dav_get_lockdb_path()) to decide where to place
293 * any storage it may need.
295 * The request storage pool should be associated with the lockdb,
296 * so it can be used in subsequent operations.
298 * If ro != 0, only readonly operations will be performed.
299 * If force == 0, the open can be "lazy"; no subsequent locking operations
301 * If force != 0, locking operations will definitely occur.
304 open_lockdb(request_rec
*r
, int ro
, int force
, dav_lockdb
**lockdb
)
306 const char *svn_client_options
, *version_name
;
307 dav_lockdb
*db
= apr_pcalloc(r
->pool
, sizeof(*db
));
308 dav_lockdb_private
*info
= apr_pcalloc(r
->pool
, sizeof(*info
));
312 /* Is this an svn client? */
314 /* Check to see if an svn client sent any custom X-SVN-* headers in
316 svn_client_options
= apr_table_get(r
->headers_in
, SVN_DAV_OPTIONS_HEADER
);
317 if (svn_client_options
)
319 /* 'svn [lock | unlock] --force' */
320 if (ap_strstr_c(svn_client_options
, SVN_DAV_OPTION_LOCK_BREAK
))
321 info
->lock_break
= TRUE
;
322 if (ap_strstr_c(svn_client_options
, SVN_DAV_OPTION_LOCK_STEAL
))
323 info
->lock_steal
= TRUE
;
324 if (ap_strstr_c(svn_client_options
, SVN_DAV_OPTION_KEEP_LOCKS
))
325 info
->keep_locks
= TRUE
;
328 /* 'svn lock' wants to make svn_fs_lock() do an out-of-dateness check. */
329 version_name
= apr_table_get(r
->headers_in
, SVN_DAV_VERSION_NAME_HEADER
);
330 info
->working_revnum
= version_name
?
331 SVN_STR_TO_REV(version_name
): SVN_INVALID_REVNUM
;
333 /* The generic lockdb structure. */
334 db
->hooks
= &dav_svn__hooks_locks
;
343 /* Indicates completion of locking operations */
345 close_lockdb(dav_lockdb
*lockdb
)
347 /* nothing to do here. */
352 /* Take a resource out of the lock-null state. */
354 remove_locknull_state(dav_lockdb
*lockdb
, const dav_resource
*resource
)
357 /* mod_dav_svn supports RFC2518bis which does not actually require
358 the server to create lock-null resources. Instead, we create
359 zero byte files when a lock comes in on a non-existent path.
360 mod_dav_svn never creates any lock-null resources, so this
361 function is never called by mod_dav. */
363 return 0; /* Just to suppress compiler warnings. */
368 ** Create a (direct) lock structure for the given resource. A locktoken
371 ** The lock provider may store private information into lock->info.
374 create_lock(dav_lockdb
*lockdb
, const dav_resource
*resource
, dav_lock
**lock
)
377 dav_locktoken
*token
= apr_pcalloc(resource
->pool
, sizeof(*token
));
378 dav_lock
*dlock
= apr_pcalloc(resource
->pool
, sizeof(*dlock
));
380 dlock
->rectype
= DAV_LOCKREC_DIRECT
;
381 dlock
->is_locknull
= resource
->exists
;
382 dlock
->scope
= DAV_LOCKSCOPE_UNKNOWN
;
383 dlock
->type
= DAV_LOCKTYPE_UNKNOWN
;
386 serr
= svn_fs_generate_lock_token(&(token
->uuid_str
),
387 resource
->info
->repos
->fs
,
390 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
391 "Failed to generate a lock token.",
393 dlock
->locktoken
= token
;
395 /* allowing mod_dav to fill in dlock->timeout, owner, auth_user. */
396 /* dlock->info and dlock->next are NULL by default. */
404 ** Get the locks associated with the specified resource.
406 ** If resolve_locks is true (non-zero), then any indirect locks are
407 ** resolved to their actual, direct lock (i.e. the reference to followed
408 ** to the original lock).
410 ** The locks, if any, are returned as a linked list in no particular
411 ** order. If no locks are present, then *locks will be NULL.
413 ** #define DAV_GETLOCKS_RESOLVED 0 -- resolve indirects to directs
414 ** #define DAV_GETLOCKS_PARTIAL 1 -- leave indirects partially filled
415 ** #define DAV_GETLOCKS_COMPLETE 2 -- fill out indirect locks
418 get_locks(dav_lockdb
*lockdb
,
419 const dav_resource
*resource
,
423 dav_lockdb_private
*info
= lockdb
->info
;
426 dav_lock
*lock
= NULL
;
428 /* We only support exclusive locks, not shared ones. So this
429 function always returns a "list" of exactly one lock, or just a
430 NULL list. The 'calltype' arg is also meaningless, since we
431 don't support locks on collections. */
433 /* Sanity check: if the resource has no associated path in the fs,
434 then there's nothing to do. */
435 if (! resource
->info
->repos_path
)
441 /* The Big Lie: if the client ran 'svn lock', then we have
442 to pretend that there's no existing lock. Otherwise mod_dav will
443 throw '403 Locked' without even attempting to create a new
444 lock. For the --force case, this is required and for the non-force case,
445 we allow the filesystem to produce a better error for svn clients.
447 if (info
->r
->method_number
== M_LOCK
)
453 /* If the resource's fs path is unreadable, we don't want to say
454 anything about locks attached to it.*/
455 if (! dav_svn__allow_read(resource
, SVN_INVALID_REVNUM
, resource
->pool
))
456 return dav_new_error(resource
->pool
, HTTP_FORBIDDEN
,
457 DAV_ERR_LOCK_SAVE_LOCK
,
458 "Path is not accessible.");
460 serr
= svn_fs_get_lock(&slock
,
461 resource
->info
->repos
->fs
,
462 resource
->info
->repos_path
,
465 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
466 "Failed to check path for a lock.",
471 svn_lock_to_dav_lock(&lock
, slock
, info
->lock_break
,
472 resource
->exists
, resource
->pool
);
474 /* Let svn clients know the creationdate of the slock. */
475 apr_table_setn(info
->r
->headers_out
, SVN_DAV_CREATIONDATE_HEADER
,
476 svn_time_to_cstring(slock
->creation_date
,
479 /* Let svn clients know who "owns" the slock. */
480 apr_table_setn(info
->r
->headers_out
, SVN_DAV_LOCK_OWNER_HEADER
,
490 ** Find a particular lock on a resource (specified by its locktoken).
492 ** *lock will be set to NULL if the lock is not found.
494 ** Note that the provider can optimize the unmarshalling -- only one
495 ** lock (or none) must be constructed and returned.
497 ** If partial_ok is true (non-zero), then an indirect lock can be
498 ** partially filled in. Otherwise, another lookup is done and the
499 ** lock structure will be filled out as a DAV_LOCKREC_INDIRECT.
502 find_lock(dav_lockdb
*lockdb
,
503 const dav_resource
*resource
,
504 const dav_locktoken
*locktoken
,
508 dav_lockdb_private
*info
= lockdb
->info
;
511 dav_lock
*dlock
= NULL
;
513 /* If the resource's fs path is unreadable, we don't want to say
514 anything about locks attached to it.*/
515 if (! dav_svn__allow_read(resource
, SVN_INVALID_REVNUM
, resource
->pool
))
516 return dav_new_error(resource
->pool
, HTTP_FORBIDDEN
,
517 DAV_ERR_LOCK_SAVE_LOCK
,
518 "Path is not accessible.");
520 serr
= svn_fs_get_lock(&slock
,
521 resource
->info
->repos
->fs
,
522 resource
->info
->repos_path
,
525 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
526 "Failed to look up lock by path.",
532 if (strcmp(locktoken
->uuid_str
, slock
->token
) != 0)
533 return dav_new_error(resource
->pool
, HTTP_BAD_REQUEST
,
534 DAV_ERR_LOCK_SAVE_LOCK
,
535 "Incoming token doesn't match existing lock.");
537 svn_lock_to_dav_lock(&dlock
, slock
, FALSE
,
538 resource
->exists
, resource
->pool
);
540 /* Let svn clients know the creationdate of the slock. */
541 apr_table_setn(info
->r
->headers_out
, SVN_DAV_CREATIONDATE_HEADER
,
542 svn_time_to_cstring(slock
->creation_date
,
545 /* Let svn clients know the 'owner' of the slock. */
546 apr_table_setn(info
->r
->headers_out
, SVN_DAV_LOCK_OWNER_HEADER
,
556 ** Quick test to see if the resource has *any* locks on it.
558 ** This is typically used to determine if a non-existent resource
559 ** has a lock and is (therefore) a locknull resource.
561 ** WARNING: this function may return TRUE even when timed-out locks
562 ** exist (i.e. it may not perform timeout checks).
565 has_locks(dav_lockdb
*lockdb
, const dav_resource
*resource
, int *locks_present
)
567 dav_lockdb_private
*info
= lockdb
->info
;
571 /* Sanity check: if the resource has no associated path in the fs,
572 then there's nothing to do. */
573 if (! resource
->info
->repos_path
)
579 /* The Big Lie: if the client ran 'svn lock', then we have
580 to pretend that there's no existing lock. Otherwise mod_dav will
581 throw '403 Locked' without even attempting to create a new
582 lock. For the --force case, this is required and for the non-force case,
583 we allow the filesystem to produce a better error for svn clients.
585 if (info
->r
->method_number
== M_LOCK
)
591 /* If the resource's fs path is unreadable, we don't want to say
592 anything about locks attached to it.*/
593 if (! dav_svn__allow_read(resource
, SVN_INVALID_REVNUM
, resource
->pool
))
594 return dav_new_error(resource
->pool
, HTTP_FORBIDDEN
,
595 DAV_ERR_LOCK_SAVE_LOCK
,
596 "Path is not accessible.");
598 serr
= svn_fs_get_lock(&slock
,
599 resource
->info
->repos
->fs
,
600 resource
->info
->repos_path
,
603 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
604 "Failed to check path for a lock.",
607 *locks_present
= slock
? 1 : 0;
613 ** Append the specified lock(s) to the set of locks on this resource.
615 ** If "make_indirect" is true (non-zero), then the specified lock(s)
616 ** should be converted to an indirect lock (if it is a direct lock)
617 ** before appending. Note that the conversion to an indirect lock does
618 ** not alter the passed-in lock -- the change is internal the
619 ** append_locks function.
621 ** Multiple locks are specified using the lock->next links.
624 append_locks(dav_lockdb
*lockdb
,
625 const dav_resource
*resource
,
627 const dav_lock
*lock
)
629 dav_lockdb_private
*info
= lockdb
->info
;
634 /* If the resource's fs path is unreadable, we don't allow a lock to
636 if (! dav_svn__allow_read(resource
, SVN_INVALID_REVNUM
, resource
->pool
))
637 return dav_new_error(resource
->pool
, HTTP_FORBIDDEN
,
638 DAV_ERR_LOCK_SAVE_LOCK
,
639 "Path is not accessible.");
642 return dav_new_error(resource
->pool
, HTTP_BAD_REQUEST
,
643 DAV_ERR_LOCK_SAVE_LOCK
,
644 "Tried to attach multiple locks to a resource.");
646 /* RFC2518bis (section 7.4) doesn't require us to support
647 'lock-null' resources at all. Instead, it asks that we treat
648 'LOCK nonexistentURL' as a PUT (followed by a LOCK) of a 0-byte file. */
649 if (! resource
->exists
)
651 svn_revnum_t rev
, new_rev
;
653 svn_fs_root_t
*txn_root
;
654 const char *conflict_msg
;
655 dav_svn_repos
*repos
= resource
->info
->repos
;
657 if (resource
->info
->repos
->is_svn_client
)
658 return dav_new_error(resource
->pool
, HTTP_METHOD_NOT_ALLOWED
,
659 DAV_ERR_LOCK_SAVE_LOCK
,
660 "Subversion clients may not lock "
661 "nonexistent paths.");
663 else if (! resource
->info
->repos
->autoversioning
)
664 return dav_new_error(resource
->pool
, HTTP_METHOD_NOT_ALLOWED
,
665 DAV_ERR_LOCK_SAVE_LOCK
,
666 "Attempted to lock non-existent path;"
667 " turn on autoversioning first.");
669 /* Commit a 0-byte file: */
671 if ((serr
= svn_fs_youngest_rev(&rev
, repos
->fs
, resource
->pool
)))
672 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
673 "Could not determine youngest revision",
676 if ((serr
= svn_repos_fs_begin_txn_for_commit(&txn
, repos
->repos
, rev
,
677 repos
->username
, NULL
,
679 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
680 "Could not begin a transaction",
683 if ((serr
= svn_fs_txn_root(&txn_root
, txn
, resource
->pool
)))
684 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
685 "Could not begin a transaction",
688 if ((serr
= svn_fs_make_file(txn_root
, resource
->info
->repos_path
,
690 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
691 "Could not create empty file.",
694 if ((serr
= dav_svn__attach_auto_revprops(txn
,
695 resource
->info
->repos_path
,
697 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
698 "Could not create empty file.",
701 if ((serr
= svn_repos_fs_commit_txn(&conflict_msg
, repos
->repos
,
702 &new_rev
, txn
, resource
->pool
)))
704 svn_error_clear(svn_fs_abort_txn(txn
, resource
->pool
));
705 return dav_svn__convert_err(serr
, HTTP_CONFLICT
,
706 apr_psprintf(resource
->pool
,
707 "Conflict when committing "
708 "'%s'.", conflict_msg
),
713 /* Convert the dav_lock into an svn_lock_t. */
714 derr
= dav_lock_to_svn_lock(&slock
, lock
, resource
->info
->repos_path
,
715 info
, resource
->info
->repos
->is_svn_client
,
720 /* Now use the svn_lock_t to actually perform the lock. */
721 serr
= svn_repos_fs_lock(&slock
,
722 resource
->info
->repos
->repos
,
726 slock
->is_dav_comment
,
727 slock
->expiration_date
,
728 info
->working_revnum
,
732 if (serr
&& serr
->apr_err
== SVN_ERR_FS_NO_USER
)
734 svn_error_clear(serr
);
735 return dav_new_error(resource
->pool
, HTTP_UNAUTHORIZED
,
736 DAV_ERR_LOCK_SAVE_LOCK
,
737 "Anonymous lock creation is not allowed.");
740 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
741 "Failed to create new lock.",
745 /* A standard webdav LOCK response doesn't include any information
746 about the creation date. We send it in a custom header, so that
747 svn clients can fill in svn_lock_t->creation_date. A generic DAV
748 client should just ignore the header. */
749 apr_table_setn(info
->r
->headers_out
, SVN_DAV_CREATIONDATE_HEADER
,
750 svn_time_to_cstring(slock
->creation_date
, resource
->pool
));
752 /* A standard webdav LOCK response doesn't include any information
753 about the owner of the lock. ('DAV:owner' has nothing to do with
754 authorization, it's just a comment that we map to
755 svn_lock_t->comment.) We send the owner in a custom header, so
756 that svn clients can fill in svn_lock_t->owner. A generic DAV
757 client should just ignore the header. */
758 apr_table_setn(info
->r
->headers_out
, SVN_DAV_LOCK_OWNER_HEADER
,
761 /* Log the locking as a 'high-level' action. */
762 dav_svn__operational_log(resource
->info
,
763 svn_log__lock_one_path(slock
->path
, info
->lock_steal
,
764 resource
->info
->r
->pool
));
771 ** Remove any lock that has the specified locktoken.
773 ** If locktoken == NULL, then ALL locks are removed.
776 remove_lock(dav_lockdb
*lockdb
,
777 const dav_resource
*resource
,
778 const dav_locktoken
*locktoken
)
780 dav_lockdb_private
*info
= lockdb
->info
;
783 const char *token
= NULL
;
785 /* Sanity check: if the resource has no associated path in the fs,
786 then there's nothing to do. */
787 if (! resource
->info
->repos_path
)
790 /* Another easy out: if an svn client sent a 'keep_locks' header
791 (typically in a DELETE request, as part of 'svn commit
792 --no-unlock'), then ignore dav_method_delete()'s attempt to
793 unconditionally remove the lock. */
794 if (info
->keep_locks
)
797 /* If the resource's fs path is unreadable, we don't allow a lock to
798 be removed from it. */
799 if (! dav_svn__allow_read(resource
, SVN_INVALID_REVNUM
, resource
->pool
))
800 return dav_new_error(resource
->pool
, HTTP_FORBIDDEN
,
801 DAV_ERR_LOCK_SAVE_LOCK
,
802 "Path is not accessible.");
804 if (locktoken
== NULL
)
806 /* Need to manually discover any lock on the resource. */
807 serr
= svn_fs_get_lock(&slock
,
808 resource
->info
->repos
->fs
,
809 resource
->info
->repos_path
,
812 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
813 "Failed to check path for a lock.",
816 token
= slock
->token
;
820 token
= locktoken
->uuid_str
;
825 /* Notice that a generic DAV client is unable to forcibly
826 'break' a lock, because info->lock_break will always be
827 FALSE. An svn client, however, can request a 'forced' break.*/
828 serr
= svn_repos_fs_unlock(resource
->info
->repos
->repos
,
829 resource
->info
->repos_path
,
834 if (serr
&& serr
->apr_err
== SVN_ERR_FS_NO_USER
)
836 svn_error_clear(serr
);
837 return dav_new_error(resource
->pool
, HTTP_UNAUTHORIZED
,
838 DAV_ERR_LOCK_SAVE_LOCK
,
839 "Anonymous lock removal is not allowed.");
842 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
843 "Failed to remove a lock.",
846 /* Log the unlocking as a 'high-level' action. */
847 dav_svn__operational_log(resource
->info
,
848 svn_log__unlock_one_path(
849 resource
->info
->repos_path
,
851 resource
->info
->r
->pool
));
859 ** Refresh all locks, found on the specified resource, which has a
860 ** locktoken in the provided list.
862 ** If the lock is indirect, then the direct lock is referenced and
865 ** Each lock that is updated is returned in the <locks> argument.
866 ** Note that the locks will be fully resolved.
869 refresh_locks(dav_lockdb
*lockdb
,
870 const dav_resource
*resource
,
871 const dav_locktoken_list
*ltl
,
875 /* We're not looping over a list of locks, since we only support one
876 lock per resource. */
877 dav_locktoken
*token
= ltl
->locktoken
;
882 /* If the resource's fs path is unreadable, we don't want to say
883 anything about locks attached to it.*/
884 if (! dav_svn__allow_read(resource
, SVN_INVALID_REVNUM
, resource
->pool
))
885 return dav_new_error(resource
->pool
, HTTP_FORBIDDEN
,
886 DAV_ERR_LOCK_SAVE_LOCK
,
887 "Path is not accessible.");
889 /* Convert the path into an svn_lock_t. */
890 serr
= svn_fs_get_lock(&slock
,
891 resource
->info
->repos
->fs
,
892 resource
->info
->repos_path
,
895 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
896 "Token doesn't point to a lock.",
899 /* Sanity check: does the incoming token actually represent the
900 current lock on the incoming resource? */
902 || (strcmp(token
->uuid_str
, slock
->token
) != 0))
903 return dav_new_error(resource
->pool
, HTTP_UNAUTHORIZED
,
904 DAV_ERR_LOCK_SAVE_LOCK
,
905 "Lock refresh request doesn't match existing lock.");
907 /* Now use the tweaked svn_lock_t to 'refresh' the existing lock. */
908 serr
= svn_repos_fs_lock(&slock
,
909 resource
->info
->repos
->repos
,
913 slock
->is_dav_comment
,
914 (new_time
== DAV_TIMEOUT_INFINITE
)
915 ? 0 : (apr_time_t
)new_time
* APR_USEC_PER_SEC
,
917 TRUE
, /* forcibly steal existing lock */
920 if (serr
&& serr
->apr_err
== SVN_ERR_FS_NO_USER
)
922 svn_error_clear(serr
);
923 return dav_new_error(resource
->pool
, HTTP_UNAUTHORIZED
,
924 DAV_ERR_LOCK_SAVE_LOCK
,
925 "Anonymous lock refreshing is not allowed.");
928 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
929 "Failed to refresh existing lock.",
932 /* Convert the refreshed lock into a dav_lock and return it. */
933 svn_lock_to_dav_lock(&dlock
, slock
, FALSE
, resource
->exists
, resource
->pool
);
940 /* The main locking vtable, provided to mod_dav */
941 const dav_hooks_locks dav_svn__hooks_locks
= {
948 remove_locknull_state
,
957 NULL
/* hook structure context */