Fix compiler warning due to missing function prototype.
[svn.git] / subversion / mod_dav_svn / lock.c
bloba5f91702149d259375663ba03afb51c02b8bbbe3
1 /*
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 * ====================================================================
19 #include <apr_uuid.h>
20 #include <apr_time.h>
22 #include <httpd.h>
23 #include <http_log.h>
24 #include <mod_dav.h>
26 #include "svn_fs.h"
27 #include "svn_repos.h"
28 #include "svn_dav.h"
29 #include "svn_time.h"
30 #include "svn_pools.h"
31 #include "private/svn_log.h"
33 #include "dav_svn.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. */
45 request_rec *r;
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'
52 as dlock->auth_user.
54 static void
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,
59 apr_pool_t *pool)
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;
67 lock->depth = 0;
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. */
74 if (slock->comment)
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,
87 slock->comment, 1),
88 "</D:owner>", NULL);
90 else
92 lock->owner = apr_pstrdup(pool, slock->comment);
95 else
96 lock->owner = NULL;
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);
111 else
112 lock->timeout = DAV_TIMEOUT_INFINITE;
114 *dlock = lock;
118 /* Helper func for dav_lock_to_svn_lock: take an incoming
119 "<D:owner>&lt;foo&gt;</D:owner>" tag and convert it to
120 "<foo>". */
121 static dav_error *
122 unescape_xml(const char **output,
123 const char *input,
124 apr_pool_t *pool)
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));
133 if (!apr_err)
134 apr_err = apr_xml_parser_done(xml_parser, &xml_doc);
136 if (apr_err)
138 char errbuf[1024];
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);
146 return SVN_NO_ERROR;
150 /* Helper func: convert a dav_lock to an svn_lock_t, allocated in pool. */
151 static dav_error *
152 dav_lock_to_svn_lock(svn_lock_t **slock,
153 const dav_lock *dlock,
154 const char *path,
155 dav_lockdb_private *info,
156 svn_boolean_t is_svn_client,
157 apr_pool_t *pool)
159 svn_lock_t *lock;
161 /* Sanity checks */
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
184 DAV clients! */
185 if (dlock->owner)
187 if (is_svn_client)
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. */
193 dav_error *derr;
194 lock->is_dav_comment = 0; /* comment is NOT xml-wrapped. */
195 derr = unescape_xml(&(lock->comment), dlock->owner, pool);
196 if (derr)
197 return derr;
199 else
201 /* The comment comes from a non-svn client; don't touch
202 this data at all. */
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 */
210 else
211 lock->expiration_date = ((apr_time_t)dlock->timeout) * APR_USEC_PER_SEC;
213 *slock = lock;
214 return 0;
218 /* ---------------------------------------------------------------- */
219 /* mod_dav locking vtable starts here: */
222 /* Return the supportedlock property for a resource */
223 static const char *
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)
236 return NULL;
237 else
238 return supported;
242 /* Parse a lock token URI, returning a lock token object allocated
243 * in the given pool.
245 static dav_error *
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;
256 return 0;
260 /* Format a lock token object into a URI string, allocated in
261 * the given pool.
263 * Always returns non-NULL.
265 static const char *
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
279 static int
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
300 * may occur.
301 * If force != 0, locking operations will definitely occur.
303 static dav_error *
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));
310 info->r = r;
312 /* Is this an svn client? */
314 /* Check to see if an svn client sent any custom X-SVN-* headers in
315 the request. */
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;
335 db->ro = ro;
336 db->info = info;
338 *lockdb = db;
339 return 0;
343 /* Indicates completion of locking operations */
344 static void
345 close_lockdb(dav_lockdb *lockdb)
347 /* nothing to do here. */
348 return;
352 /* Take a resource out of the lock-null state. */
353 static dav_error *
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
369 ** will be created.
371 ** The lock provider may store private information into lock->info.
373 static dav_error *
374 create_lock(dav_lockdb *lockdb, const dav_resource *resource, dav_lock **lock)
376 svn_error_t *serr;
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;
384 dlock->depth = 0;
386 serr = svn_fs_generate_lock_token(&(token->uuid_str),
387 resource->info->repos->fs,
388 resource->pool);
389 if (serr)
390 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
391 "Failed to generate a lock token.",
392 resource->pool);
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. */
398 *lock = dlock;
399 return 0;
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
417 static dav_error *
418 get_locks(dav_lockdb *lockdb,
419 const dav_resource *resource,
420 int calltype,
421 dav_lock **locks)
423 dav_lockdb_private *info = lockdb->info;
424 svn_error_t *serr;
425 svn_lock_t *slock;
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)
437 *locks = NULL;
438 return 0;
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)
449 *locks = NULL;
450 return 0;
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,
463 resource->pool);
464 if (serr)
465 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
466 "Failed to check path for a lock.",
467 resource->pool);
469 if (slock != NULL)
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,
477 resource->pool));
479 /* Let svn clients know who "owns" the slock. */
480 apr_table_setn(info->r->headers_out, SVN_DAV_LOCK_OWNER_HEADER,
481 slock->owner);
484 *locks = lock;
485 return 0;
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.
501 static dav_error *
502 find_lock(dav_lockdb *lockdb,
503 const dav_resource *resource,
504 const dav_locktoken *locktoken,
505 int partial_ok,
506 dav_lock **lock)
508 dav_lockdb_private *info = lockdb->info;
509 svn_error_t *serr;
510 svn_lock_t *slock;
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,
523 resource->pool);
524 if (serr)
525 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
526 "Failed to look up lock by path.",
527 resource->pool);
529 if (slock != NULL)
531 /* Sanity check. */
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,
543 resource->pool));
545 /* Let svn clients know the 'owner' of the slock. */
546 apr_table_setn(info->r->headers_out, SVN_DAV_LOCK_OWNER_HEADER,
547 slock->owner);
550 *lock = dlock;
551 return 0;
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).
564 static dav_error *
565 has_locks(dav_lockdb *lockdb, const dav_resource *resource, int *locks_present)
567 dav_lockdb_private *info = lockdb->info;
568 svn_error_t *serr;
569 svn_lock_t *slock;
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)
575 *locks_present = 0;
576 return 0;
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)
587 *locks_present = 0;
588 return 0;
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,
601 resource->pool);
602 if (serr)
603 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
604 "Failed to check path for a lock.",
605 resource->pool);
607 *locks_present = slock ? 1 : 0;
608 return 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.
623 static dav_error *
624 append_locks(dav_lockdb *lockdb,
625 const dav_resource *resource,
626 int make_indirect,
627 const dav_lock *lock)
629 dav_lockdb_private *info = lockdb->info;
630 svn_lock_t *slock;
631 svn_error_t *serr;
632 dav_error *derr;
634 /* If the resource's fs path is unreadable, we don't allow a lock to
635 be created on it. */
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.");
641 if (lock->next)
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;
652 svn_fs_txn_t *txn;
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",
674 resource->pool);
676 if ((serr = svn_repos_fs_begin_txn_for_commit(&txn, repos->repos, rev,
677 repos->username, NULL,
678 resource->pool)))
679 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
680 "Could not begin a transaction",
681 resource->pool);
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",
686 resource->pool);
688 if ((serr = svn_fs_make_file(txn_root, resource->info->repos_path,
689 resource->pool)))
690 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
691 "Could not create empty file.",
692 resource->pool);
694 if ((serr = dav_svn__attach_auto_revprops(txn,
695 resource->info->repos_path,
696 resource->pool)))
697 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
698 "Could not create empty file.",
699 resource->pool);
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),
709 resource->pool);
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,
716 resource->pool);
717 if (derr)
718 return derr;
720 /* Now use the svn_lock_t to actually perform the lock. */
721 serr = svn_repos_fs_lock(&slock,
722 resource->info->repos->repos,
723 slock->path,
724 slock->token,
725 slock->comment,
726 slock->is_dav_comment,
727 slock->expiration_date,
728 info->working_revnum,
729 info->lock_steal,
730 resource->pool);
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.");
739 else if (serr)
740 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
741 "Failed to create new lock.",
742 resource->pool);
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,
759 slock->owner);
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));
766 return 0;
771 ** Remove any lock that has the specified locktoken.
773 ** If locktoken == NULL, then ALL locks are removed.
775 static dav_error *
776 remove_lock(dav_lockdb *lockdb,
777 const dav_resource *resource,
778 const dav_locktoken *locktoken)
780 dav_lockdb_private *info = lockdb->info;
781 svn_error_t *serr;
782 svn_lock_t *slock;
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)
788 return 0;
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)
795 return 0;
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,
810 resource->pool);
811 if (serr)
812 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
813 "Failed to check path for a lock.",
814 resource->pool);
815 if (slock)
816 token = slock->token;
818 else
820 token = locktoken->uuid_str;
823 if (token)
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,
830 token,
831 info->lock_break,
832 resource->pool);
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.");
841 else if (serr)
842 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
843 "Failed to remove a lock.",
844 resource->pool);
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,
850 info->lock_break,
851 resource->info->r->pool));
854 return 0;
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
863 ** refreshed.
865 ** Each lock that is updated is returned in the <locks> argument.
866 ** Note that the locks will be fully resolved.
868 static dav_error *
869 refresh_locks(dav_lockdb *lockdb,
870 const dav_resource *resource,
871 const dav_locktoken_list *ltl,
872 time_t new_time,
873 dav_lock **locks)
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;
878 svn_error_t *serr;
879 svn_lock_t *slock;
880 dav_lock *dlock;
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,
893 resource->pool);
894 if (serr)
895 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
896 "Token doesn't point to a lock.",
897 resource->pool);
899 /* Sanity check: does the incoming token actually represent the
900 current lock on the incoming resource? */
901 if ((! slock)
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,
910 slock->path,
911 slock->token,
912 slock->comment,
913 slock->is_dav_comment,
914 (new_time == DAV_TIMEOUT_INFINITE)
915 ? 0 : (apr_time_t)new_time * APR_USEC_PER_SEC,
916 SVN_INVALID_REVNUM,
917 TRUE, /* forcibly steal existing lock */
918 resource->pool);
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.");
927 else if (serr)
928 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
929 "Failed to refresh existing lock.",
930 resource->pool);
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);
934 *locks = dlock;
936 return 0;
940 /* The main locking vtable, provided to mod_dav */
941 const dav_hooks_locks dav_svn__hooks_locks = {
942 get_supportedlock,
943 parse_locktoken,
944 format_locktoken,
945 compare_locktoken,
946 open_lockdb,
947 close_lockdb,
948 remove_locknull_state,
949 create_lock,
950 get_locks,
951 find_lock,
952 has_locks,
953 append_locks,
954 remove_lock,
955 refresh_locks,
956 NULL,
957 NULL /* hook structure context */